import React, { createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
import axios from "axios";
import { notifyError } from "../shared/toast";
import { pagesOf, pageMeta, richParts } from "@/shared/formWalk";

// THE FORM BUILDER — the questions as they will be asked, edited in place.
//
// Shaped like Google Forms deliberately, and for the reason the owner gave: *"better have similar
// ui/ux control as google form did … so the user can easily adapted"*. Organizations arrive here
// with forms they already run there; a builder that works the way theirs does is one they do not
// have to be taught. They sent fifteen screenshots of their own form, and this is built to them:
// one card per question, a floating item toolbar down the right, a section tab, the type on the
// card, per-option routing, and a footer of duplicate / delete / Required.
//
// NO separate preview — the owner again: *"because google don't have preview, it combines the edit
// section and preview together"*. A preview is only needed when editing does not look like the
// thing.
// GOOGLE'S OWN MENU, group for group and in its order — somebody moving across finds each type
// where they expect it. The last group is ours: Google asks for a number, an email or a phone by
// putting validation on a short answer; naming them is fewer steps and the same result.
const KINDS = [
    ["text", "Short answer", "▁"], ["long_text", "Paragraph", "¶"],
    ["choice", "Multiple choice", "◉"], ["checkbox", "Checkboxes", "☑"], ["dropdown", "Drop-down", "⌄"],
    ["file", "File upload", "⇧"],
    ["scale", "Linear scale", "⇹"], ["rating", "Rating", "☆"],
    ["grid_choice", "Multiple-choice grid", "⠿"], ["grid_checkbox", "Tick box grid", "▦"],
    ["date", "Date", "📅"], ["time", "Time", "🕐"],
    ["number", "Number", "#"], ["email", "Email", "@"], ["phone", "Phone", "☎"],
];
const KIND_LABEL = Object.fromEntries(KINDS.map(([v, l]) => [v, l]));
const CHOICE_KINDS = ["choice", "checkbox", "dropdown"];
const GRID_KINDS = ["grid_choice", "grid_checkbox"];
const DISPLAY_KINDS = ["note", "image", "video"];
const MARK = { choice: "○", checkbox: "☐", dropdown: "▾" };

// RESPONSE VALIDATION, in Google's own words — a type, then the conditions that type offers.
const RULE_OPS = {
    number: [["gt", "Greater than"], ["gte", "Greater than or equal to"], ["lt", "Less than"],
        ["lte", "Less than or equal to"], ["eq", "Equal to"], ["neq", "Not equal to"],
        ["between", "Between"], ["not_between", "Not between"], ["is_number", "Is number"],
        ["whole_number", "Whole number"]],
    text: [["contains", "Contains"], ["not_contains", "Doesn't contain"], ["email", "Email"], ["url", "URL"]],
    length: [["max_chars", "Maximum character count"], ["min_chars", "Minimum character count"]],
    regex: [["matches", "Matches"], ["not_matches", "Doesn't match"], ["contains", "Contains"],
        ["not_contains", "Doesn't contain"]],
    checkbox: [["at_least", "Select at least"], ["at_most", "Select at most"], ["exactly", "Select exactly"]],
};
const RULE_TYPES = [["number", "Number"], ["text", "Text"], ["length", "Length"], ["regex", "Regular expression"]];
const NEEDS_NOTHING = ["is_number", "whole_number", "email", "url"];

export function nameFrom(label) {
    return String(label || "").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 64);
}

export const blankField = (page = 1) => ({ name: "", label: "", kind: "text", required: false, options: [], page });

const label = { fontSize: 11, color: "var(--muted)" };
const card = {
    border: "1px solid var(--line)", borderRadius: 10, padding: 16, marginBottom: 12,
    background: "var(--card, #fff)", position: "relative",
};
const focused = { ...card, borderLeft: "5px solid var(--brand)" };
const optionOf = o => (typeof o === "string" ? { label: o } : (o || { label: "" }));

// A label with its marks taken off, for the places a label is read as one line rather than shown.
const plainLabel = text => String(text || "")
    .replace(/\*\*|__|\*/g, "")
    .replace(/\[([^\]]*)\]\([^)]*\)/g, "$1")
    .trim();

// The four marks, as elements. The card that is NOT being edited IS the preview, so a label that
// shows its own asterisks is the preview lying about what the customer will read.
function Marked({ text }) {
    return <>{richParts(text).map((p, i) => {
        if (p.bold) return <b key={i}>{p.text}</b>;
        if (p.italic) return <i key={i}>{p.text}</i>;
        if (p.underline) return <u key={i}>{p.text}</u>;
        if (p.href) return <a key={i} href={p.href} target="_blank" rel="noreferrer">{p.text}</a>;

        return <span key={i}>{p.text}</span>;
    })}</>;
}

// The ⋮ menus. Closes on the next click anywhere, which is what everybody expects of one.
function Menu({ items }) {
    const [open, setOpen] = useState(false);
    const [up, setUp] = useState(false);
    const box = useRef(null);

    useEffect(() => {
        if (!open) return;
        const shut = e => { if (!box.current?.contains(e.target)) setOpen(false); };
        document.addEventListener("mousedown", shut);

        return () => document.removeEventListener("mousedown", shut);
    }, [open]);

    // OPENS UPWARD when there is no room below. Low in the dialog, the last item was drawn under
    // the dialog's footer and had to be scrolled to. The room is measured to the bottom of the box
    // that scrolls, not the window, because that box is what clips it. (Arfu, 06/10/2026)
    const toggle = () => {
        if (!open && box.current) {
            let clip = box.current.parentElement;
            while (clip && !/(auto|scroll|hidden)/.test(getComputedStyle(clip).overflowY)) clip = clip.parentElement;
            const bottom = clip ? clip.getBoundingClientRect().bottom : window.innerHeight;
            const needed = 44 + 36 * items.filter(Boolean).length;
            setUp(bottom - box.current.getBoundingClientRect().bottom < needed);
        }
        setOpen(o => !o);
    };

    return (
        <span ref={box} style={{ position: "relative" }}>
            <button type="button" className="pt-btn pt-btn-sm" title="More" onClick={toggle}>⋮</button>
            {open && (
                <div style={{
                    position: "absolute", right: 0, ...(up ? { bottom: "100%" } : { top: "100%" }), zIndex: 30, minWidth: 210,
                    background: "var(--card, #fff)", border: "1px solid var(--line)", borderRadius: 8,
                    boxShadow: "0 8px 24px rgba(0,0,0,.12)", padding: 4,
                }}>
                    {items.filter(Boolean).map(([text, run], i) => (
                        <button key={i} type="button" onClick={() => { setOpen(false); run(); }}
                            style={{
                                display: "block", width: "100%", textAlign: "left", padding: "8px 10px",
                                border: "none", background: "none", cursor: "pointer", fontSize: 12.5,
                                color: "var(--ink)", borderRadius: 6,
                            }}>{text}</button>
                    ))}
                </div>
            )}
        </span>
    );
}

// B / I / U / link / clear — the four marks the schema carries, written into the text as marks
// rather than as markup. Applies to the selection, like a word processor's.
function RichBar({ forId, value, onChange }) {
    const wrap = (before, after = before) => {
        const el = document.getElementById(forId);
        if (!el) return;
        const [from, to] = [el.selectionStart ?? 0, el.selectionEnd ?? 0];
        const picked = value.slice(from, to) || "text";
        onChange(value.slice(0, from) + before + picked + after + value.slice(to));
    };
    const btn = { border: "none", background: "none", cursor: "pointer", color: "var(--muted)", padding: "2px 7px", fontSize: 13 };

    return (
        <div style={{ display: "flex", gap: 2, margin: "4px 0 2px" }}>
            <button type="button" style={{ ...btn, fontWeight: 800 }} title="Bold" onClick={() => wrap("**")}>B</button>
            <button type="button" style={{ ...btn, fontStyle: "italic" }} title="Italic" onClick={() => wrap("*")}>I</button>
            <button type="button" style={{ ...btn, textDecoration: "underline" }} title="Underline" onClick={() => wrap("__")}>U</button>
            <button type="button" style={btn} title="Link" onClick={() => wrap("[", "](https://)")}>🔗</button>
            <button type="button" style={btn} title="Clear formatting"
                onClick={() => onChange(value.replace(/\*\*|__|\*/g, "").replace(/\[([^\]]*)\]\([^)]*\)/g, "$1"))}>✕̶</button>
        </div>
    );
}

// One question, as it will be asked — and, when it is the one being edited, its controls.
// DRAG TO REORDER, done properly (Arfu: "the drag drop function is not smooth enough").
//
// The browser's own drag-and-drop was the first attempt and it is coarse: no gap opening where the
// card will land, a drop that only counts over another card's middle, a ghost image of the whole
// card, and no scrolling when you reach the edge of the dialog. This is the sortable everybody
// actually means — pointer events, the list reordering live underneath, and a light preview
// following the cursor.
//
// The CARDS REORDER AS YOU MOVE rather than on release, so what you see is what you will get; the
// card being dragged stays in the list as the gap, which is why nothing has to be measured twice.
function useSortable(count, onMove) {
    const nodes = useRef(new Map());
    const [drag, setDrag] = useState(null);
    const state = useRef(null);
    // NOT called `move`: the pointermove handler below is a local `move`, and inside that effect
    // the local shadows this ref — `move.current` then reads a function that has no `.current`,
    // which is exactly the throw that made every drag look like it would not drop. (18/09/2026)
    const onMoveRef = useRef(onMove);
    onMoveRef.current = onMove;
    const size = useRef(count);
    size.current = count;

    // Where the pointer is, and what to draw following it.
    const [ghost, setGhost] = useState(null);

    useEffect(() => {
        if (!drag) return;

        const scroller = () => {
            let el = nodes.current.get(state.current?.at);
            while (el && el !== document.body) {
                const style = getComputedStyle(el);
                if (/(auto|scroll)/.test(style.overflowY) && el.scrollHeight > el.clientHeight + 4) return el;
                el = el.parentElement;
            }

            return null;
        };

        const move = (e) => {
            const me = state.current;
            if (!me) return;
            setGhost({ x: e.clientX - me.grabX, y: e.clientY - me.grabY });

            // WHERE IT WOULD LAND: the first card whose middle is below the pointer.
            // IN LIST ORDER. A Map iterates by insertion, and a card that was re-rendered is
            // re-inserted at the end — so without this sort "the first card below the pointer" is
            // whichever one happened to render last.
            let to = null;
            for (const index of [...nodes.current.keys()].sort((a, b) => a - b)) {
                const node = nodes.current.get(index);
                if (!node) continue;
                const box = node.getBoundingClientRect();
                if (e.clientY < box.top + box.height / 2) { to = index; break; }
            }
            if (to === null) to = size.current - 1;
            if (to !== me.at) {
                onMoveRef.current(me.at, to);
                me.at = to;
                // AND THE TINT MOVES WITH IT. `drag.index` was where the card STARTED, so once the
                // list had reordered underneath, the card wearing the dashed outline was whichever
                // one had inherited that position — not the one in your hand. (Arfu, 18/09/2026)
                setDrag(d => (d && d.index !== to ? { ...d, index: to } : d));
            }

            // Scroll when the pointer reaches the edge of whatever is scrolling — a long form is
            // otherwise a drag you have to let go of half way.
            const box = scroller();
            if (box) {
                const area = box.getBoundingClientRect();
                if (e.clientY < area.top + 70) box.scrollTop -= 14;
                else if (e.clientY > area.bottom - 70) box.scrollTop += 14;
            }
        };
        const up = () => { setDrag(null); setGhost(null); state.current = null; };

        window.addEventListener("pointermove", move);
        window.addEventListener("pointerup", up);
        window.addEventListener("pointercancel", up);

        return () => {
            window.removeEventListener("pointermove", move);
            window.removeEventListener("pointerup", up);
            window.removeEventListener("pointercancel", up);
        };
    }, [drag]);

    const start = (index, label) => (e) => {
        // Never from inside something you type in or press: selecting a word in a label must not
        // pick the card up.
        if (e.button !== 0 || e.target.closest("input, textarea, select, button, label, a")) return;
        const box = nodes.current.get(index)?.getBoundingClientRect();
        if (!box) return;
        state.current = { at: index, grabX: e.clientX - box.left, grabY: e.clientY - box.top };
        setGhost({ x: box.left, y: box.top });
        setDrag({ index, label, width: box.width });
        e.preventDefault();
    };

    const hold = (index) => (node) => {
        if (node) nodes.current.set(index, node);
        else nodes.current.delete(index);
    };

    // WHAT FOLLOWS THE CURSOR. Tinted, not white-on-white: a pale card with a hairline border over
    // a page of pale cards reads as a rendering glitch rather than as something being carried.
    // (Arfu: "quite strange when drag dropping … how about use diff. bg color", 18/09/2026.)
    // Narrower than the card too, and the label's marks are stripped — a floating **asterisk** is
    // the one place the raw text is nobody's business.
    const preview = drag && ghost ? (
        <div style={{
            position: "fixed", left: ghost.x, top: ghost.y, width: Math.min(drag.width, 460), zIndex: 80,
            pointerEvents: "none", background: "var(--brand)", color: "#fff",
            borderRadius: 10, padding: "10px 14px", boxShadow: "0 16px 36px rgba(0,0,0,.28)",
            transform: "rotate(-1deg) scale(.98)", fontSize: 13, fontWeight: 600,
            whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis",
            display: "flex", alignItems: "center", gap: 10,
        }}>
            <span style={{ opacity: .75, letterSpacing: "2px" }}>⠿</span>
            {plainLabel(drag.label) || "Untitled question"}
        </div>
    ) : null;

    return { start, hold, dragging: drag?.index ?? null, preview };
}

// The grip at the top of a card. The WHOLE card drags (see the wrapper below) — this is the thing
// that says so, and it is a comfortable target in its own right rather than six pixels of dots.
function Grip() {
    return (
        <div title="Drag to reorder"
            style={{ textAlign: "center", color: "var(--faint)", fontSize: 18, lineHeight: "18px",
                letterSpacing: "2px", cursor: "grab", marginTop: -10, marginBottom: 6,
                padding: "2px 0", userSelect: "none" }}>⠿</div>
    );
}

// ONE FORM, TWO LANGUAGES (v0.30, 24/09/2026). With 繁體中文 switched on for the form, a box for the
// Chinese sits under every English text the customer reads. Context rather than a prop through
// every card: the switch is the form's, and every text input below it answers to it.
const ZhOn = createContext(false);

function Zh({ value, onChange, placeholder = "繁體中文", multiline = false, style }) {
    if (!useContext(ZhOn)) return null;
    const box = { marginTop: 4, fontSize: 12.5, borderStyle: "dashed", ...style };

    return multiline
        ? <textarea className="pt-input" rows={2} value={value || ""} placeholder={placeholder} style={box}
            onChange={e => onChange(e.target.value)} />
        : <input className="pt-input" value={value || ""} placeholder={placeholder} style={box}
            onChange={e => onChange(e.target.value)} />;
}

function Question({ field, index, sections, sectionTitles, branch, fallback = null, editing, onOpen, onPatch, onRemove,
    onMove, onDuplicate, onImage, onOptionImage, onRoute, first, lastOne }) {
    const kind = field.kind || "text";
    const options = (field.options || []).map(optionOf);
    const rule = field.validate || {};
    const setRule = p => onPatch({ validate: { ...rule, ...p } });
    const [extras, setExtras] = useState({ help: !!field.help, rule: !!rule.op, route: !!branch });
    const id = `q-label-${index}`;
    const setZh = p => onPatch({ zh: { ...(field.zh || {}), ...p } });

    // A DISPLAY ITEM: a title block, a picture or a video. It asks nothing, so it has no type
    // dropdown, no Required and no stored name.
    if (DISPLAY_KINDS.includes(kind)) {
        return (
            <div style={editing ? focused : card} onClick={editing ? undefined : onOpen}>
                <input className="pt-input" value={field.label || ""} placeholder={kind === "note" ? "Title" : "Caption"}
                    style={{ fontSize: 14, fontWeight: 600 }} readOnly={!editing}
                    onChange={e => onPatch({ label: e.target.value })} />
                {editing && <Zh value={field.zh?.label} onChange={v => setZh({ label: v })} placeholder={kind === "note" ? "標題（繁體）" : "說明文字（繁體）"} />}
                {kind === "note" && (
                    <textarea className="pt-input" rows={2} value={field.help || ""} placeholder="Description"
                        style={{ marginTop: 8, fontSize: 12.5 }} readOnly={!editing}
                        onChange={e => onPatch({ help: e.target.value })} />
                )}
                {kind === "note" && editing && <Zh multiline value={field.zh?.help} onChange={v => setZh({ help: v })} placeholder="描述（繁體）" />}
                {kind === "video" && (
                    <input className="pt-input" value={field.url || ""} placeholder="https://www.youtube.com/watch?v=…"
                        style={{ marginTop: 8, fontSize: 12.5 }} readOnly={!editing}
                        onChange={e => onPatch({ url: e.target.value })} />
                )}
                {kind === "image" && (field.image
                    ? <img src={`/storage/${field.image}`} alt="" style={{ maxWidth: 280, borderRadius: 8, display: "block", marginTop: 8 }} />
                    : <label className="pt-btn pt-btn-sm" style={{ marginTop: 8, display: "inline-block", cursor: "pointer" }}>
                        + Choose a picture
                        <input type="file" accept="image/*" hidden onChange={e => onImage(e.target.files?.[0])} />
                    </label>)}
                {editing && (
                    <div style={{ marginTop: 10, paddingTop: 10, borderTop: "1px solid var(--line)", display: "flex", gap: 4, justifyContent: "flex-end" }}>
                        <button type="button" className="pt-btn pt-btn-sm" disabled={first} onClick={() => onMove(-1)} title="Move up">↑</button>
                        <button type="button" className="pt-btn pt-btn-sm" disabled={lastOne} onClick={() => onMove(1)} title="Move down">↓</button>
                        <button type="button" className="pt-btn pt-btn-sm" onClick={onRemove} title="Remove">🗑</button>
                    </div>
                )}
            </div>
        );
    }

    if (!editing) {
        return (
            <div style={{ ...card, cursor: "pointer" }} onClick={onOpen}>
                <Grip />
                <div style={{ fontSize: 13.5, fontWeight: 600, marginBottom: 6 }}>
                    {field.label
                        ? <Marked text={field.label} />
                        : <span style={{ color: "var(--faint)" }}>Untitled question</span>}
                    {field.required && <span style={{ color: "var(--red)" }} title="Required"> *</span>}
                </div>
                {field.help && <div style={{ fontSize: 11.5, color: "var(--faint)", marginBottom: 6 }}><Marked text={field.help} /></div>}
                {field.image && <img src={`/storage/${field.image}`} alt="" style={{ maxWidth: 260, borderRadius: 8, display: "block", marginBottom: 8 }} />}
                {CHOICE_KINDS.includes(kind) ? (
                    <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
                        {options.filter(o => o.label).map((o, n) => (
                            <div key={n} style={{ fontSize: 12.5, color: "var(--ink-2)" }}>{MARK[kind]} {o.label}</div>
                        ))}
                        {field.other && <div style={{ fontSize: 12.5, color: "var(--faint)" }}>{MARK[kind]} {field.other_label || "Other"}…</div>}
                    </div>
                ) : GRID_KINDS.includes(kind) ? (
                    <div style={{ fontSize: 12, color: "var(--ink-2)" }}>
                        {(field.rows || []).length} rows × {(field.columns || []).length} columns
                    </div>
                ) : kind === "scale" ? (
                    <div style={{ fontSize: 12.5, color: "var(--ink-2)" }}>
                        {(field.scale?.min ?? 1)} — {(field.scale?.max ?? 5)}
                    </div>
                ) : kind === "rating" ? (
                    <div style={{ fontSize: 15, color: "var(--faint)" }}>
                        {"☆".repeat(field.rating?.max ?? 5)}
                    </div>
                ) : (
                    <div style={{ fontSize: 12, color: "var(--faint)", borderBottom: "1px dashed var(--line)", paddingBottom: 6 }}>
                        {KIND_LABEL[kind]} text
                    </div>
                )}
            </div>
        );
    }

    const ops = RULE_OPS[kind === "checkbox" ? "checkbox" : (rule.type || "number")] || [];

    return (
        <div style={focused}>
            <Grip />

            <div style={{ display: "flex", gap: 10, alignItems: "flex-start" }}>
                <div style={{ flex: 1 }}>
                    <input id={id} className="pt-input" value={field.label} placeholder="Question"
                        style={{ fontSize: 14, fontWeight: 600, background: "var(--bg-2, #f7f7f8)" }}
                        onChange={e => onPatch({ label: e.target.value, ...(field.nameLocked ? {} : { name: nameFrom(e.target.value) }) })} />
                    <RichBar forId={id} value={field.label || ""} onChange={v => onPatch({ label: v })} />
                    <Zh value={field.zh?.label} onChange={v => setZh({ label: v })} placeholder="問題（繁體）— **粗體** 等標記同英文一樣" />
                </div>
                <label className="pt-btn pt-btn-sm" style={{ cursor: "pointer", marginTop: 2 }} title="Add image">
                    🖼
                    <input type="file" accept="image/*" hidden onChange={e => onImage(e.target.files?.[0])} />
                </label>
                <select className="pt-input" style={{ width: 190, marginTop: 2 }} value={kind}
                    onChange={e => onPatch({ kind: e.target.value })}>
                    {KINDS.map(([v, l, icon]) => <option key={v} value={v}>{icon}  {l}</option>)}
                </select>
            </div>

            {field.image && (
                <div style={{ margin: "6px 0" }}>
                    <img src={`/storage/${field.image}`} alt="" style={{ maxWidth: 260, borderRadius: 8, display: "block" }} />
                    <button type="button" className="pt-btn pt-btn-sm" style={{ marginTop: 6 }}
                        onClick={() => onPatch({ image: "" })}>Remove image</button>
                </div>
            )}

            {extras.help && (
                <input className="pt-input" value={field.help || ""} placeholder="Description"
                    style={{ marginTop: 6, fontSize: 12.5 }}
                    onChange={e => onPatch({ help: e.target.value })} />
            )}
            {extras.help && <Zh value={field.zh?.help} onChange={v => setZh({ help: v })} placeholder="描述（繁體）" />}

            {/* THE OPTIONS, each with its own picture, its own remove, and — when this question
                decides where the section goes — its own "Go to section". */}
            {CHOICE_KINDS.includes(kind) && (
                <div style={{ marginTop: 10 }}>
                    {options.map((o, n) => (
                        <div key={n} style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 7 }}>
                            <span style={{ color: "var(--faint)" }}>{MARK[kind]}</span>
                            <input className="pt-input" value={o.label} style={{ flex: 1, height: 32 }}
                                onChange={e => onPatch({
                                    options: options.map((x, m) => m === n ? { ...x, label: e.target.value } : x),
                                })} />
                            {/* Kept ON the option, so moving or deleting it keeps its translation with it. */}
                            <Zh value={o.zh} placeholder="繁體" style={{ marginTop: 0, width: 200, height: 32 }}
                                onChange={v => onPatch({ options: options.map((x, m) => m === n ? { ...x, zh: v } : x) })} />
                            {o.image
                                ? <img src={`/storage/${o.image}`} alt="" style={{ height: 32, borderRadius: 6 }} />
                                : <label className="pt-btn pt-btn-sm" style={{ cursor: "pointer" }} title="Add image">
                                    🖼
                                    <input type="file" accept="image/*" hidden onChange={e => onOptionImage(n, e.target.files?.[0])} />
                                </label>}
                            {/* Moving an option moves the whole object — its 繁體, picture and value go
                                with it, and a go-to is keyed by the label, so routing follows too.
                                (Arfu, 06/10/2026) */}
                            <button type="button" className="pt-btn pt-btn-sm" title="Move up" disabled={n === 0}
                                onClick={() => onPatch({ options: swap(options, n, n - 1) })}>↑</button>
                            <button type="button" className="pt-btn pt-btn-sm" title="Move down" disabled={n === options.length - 1}
                                onClick={() => onPatch({ options: swap(options, n, n + 1) })}>↓</button>
                            <button type="button" className="pt-btn pt-btn-sm" title="Remove"
                                onClick={() => onPatch({ options: options.filter((_, m) => m !== n) })}>✕</button>
                            {extras.route && (
                                <select className="pt-input" style={{ height: 32, width: 240 }}
                                    value={(branch?.cases || []).find(c => c.value === o.label)?.page ?? ""}
                                    onChange={e => onRoute(o.label, e.target.value)}>
                                    {/* An answer with no go-to of its own follows the section's
                                        "After section" line, so the blank choice says where that is.
                                        It read "Continue to next section" while the form skipped to
                                        section 4. (Arfu, 06/10/2026) */}
                                    <option value="">{fallback
                                        ? `Same as after section ${field.page || 1} — ${goToLabel(fallback, field.page || 1, sectionTitles)}`
                                        : "Continue to next section"}</option>
                                    {/* EARLIER sections too since 24/09/2026 — a form may send an answer
                                        back, as Day Day Help's does; never to its own section. */}
                                    {sections.filter(sn => sn !== (field.page || 1)).map(sn => (
                                        <option key={sn} value={sn}>{sn < (field.page || 1) ? "Go back to" : "Go to"} section {sn}{sectionTitles[sn] ? ` (${sectionTitles[sn]})` : ""}</option>
                                    ))}
                                    <option value="submit">Submit form</option>
                                </select>
                            )}
                        </div>
                    ))}
                    {field.other && (
                        <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 7 }}>
                            <span style={{ color: "var(--faint)" }}>{MARK[kind]}</span>
                            <input className="pt-input" style={{ flex: 1, height: 32 }} value={field.other_label || ""}
                                placeholder="Other…" onChange={e => onPatch({ other_label: e.target.value })} />
                            <Zh value={field.zh?.other_label} onChange={v => setZh({ other_label: v })} placeholder="其他（繁體）"
                                style={{ marginTop: 0, width: 200, height: 32 }} />
                            <button type="button" className="pt-btn pt-btn-sm"
                                onClick={() => onPatch({ other: false, other_label: "" })}>✕</button>
                        </div>
                    )}
                    <div style={{ display: "flex", alignItems: "center", gap: 8, fontSize: 12.5, color: "var(--muted)" }}>
                        <span style={{ color: "var(--faint)" }}>{MARK[kind]}</span>
                        <button type="button" onClick={() => onPatch({ options: [...options, { label: "" }] })}
                            style={{ border: "none", background: "none", cursor: "pointer", color: "var(--muted)", padding: 0 }}>
                            Add option
                        </button>
                        {!field.other && <>
                            <span>or</span>
                            <button type="button" onClick={() => onPatch({ other: true })}
                                style={{ border: "none", background: "none", cursor: "pointer", color: "var(--brand)", padding: 0, fontWeight: 600 }}>
                                Add “Other”
                            </button>
                        </>}
                    </div>
                </div>
            )}

            {GRID_KINDS.includes(kind) && (
                <div style={{ display: "flex", gap: 14, marginTop: 10 }}>
                    {["rows", "columns"].map(half => (
                        <div key={half} style={{ flex: 1 }}>
                            <div style={{ ...label, marginBottom: 4, textTransform: "capitalize" }}>{half}</div>
                            <textarea className="pt-input" rows={4} style={{ fontSize: 12.5 }}
                                placeholder={half === "rows" ? "One row per line" : "One column per line"}
                                value={(field[half] || []).join("\n")}
                                onChange={e => onPatch({ [half]: e.target.value.split("\n") })} />
                        </div>
                    ))}
                </div>
            )}

            {kind === "scale" && (
                <div style={{ display: "flex", gap: 8, alignItems: "center", marginTop: 10, flexWrap: "wrap" }}>
                    <select className="pt-input" style={{ width: 80, height: 32 }} value={field.scale?.min ?? 1}
                        onChange={e => onPatch({ scale: { ...(field.scale || {}), min: Number(e.target.value) } })}>
                        {[0, 1].map(n => <option key={n} value={n}>{n}</option>)}
                    </select>
                    <span style={{ color: "var(--faint)" }}>to</span>
                    <select className="pt-input" style={{ width: 80, height: 32 }} value={field.scale?.max ?? 5}
                        onChange={e => onPatch({ scale: { ...(field.scale || {}), max: Number(e.target.value) } })}>
                        {[2, 3, 4, 5, 6, 7, 8, 9, 10].map(n => <option key={n} value={n}>{n}</option>)}
                    </select>
                    <input className="pt-input" style={{ width: 170, height: 32, fontSize: 12.5 }} placeholder="Label at the low end"
                        value={field.scale?.min_label || ""}
                        onChange={e => onPatch({ scale: { ...(field.scale || {}), min_label: e.target.value } })} />
                    <input className="pt-input" style={{ width: 170, height: 32, fontSize: 12.5 }} placeholder="Label at the high end"
                        value={field.scale?.max_label || ""}
                        onChange={e => onPatch({ scale: { ...(field.scale || {}), max_label: e.target.value } })} />
                </div>
            )}

            {kind === "rating" && (
                <div style={{ display: "flex", gap: 8, alignItems: "center", marginTop: 10 }}>
                    <select className="pt-input" style={{ width: 90, height: 32 }} value={field.rating?.max ?? 5}
                        onChange={e => onPatch({ rating: { ...(field.rating || {}), max: Number(e.target.value) } })}>
                        {[3, 4, 5, 6, 7, 8, 9, 10].map(n => <option key={n} value={n}>{n}</option>)}
                    </select>
                    <select className="pt-input" style={{ width: 130, height: 32 }} value={field.rating?.icon ?? "star"}
                        onChange={e => onPatch({ rating: { ...(field.rating || {}), icon: e.target.value } })}>
                        <option value="star">★ Stars</option>
                        <option value="heart">♥ Hearts</option>
                        <option value="thumb">👍 Thumbs</option>
                    </select>
                </div>
            )}

            {kind === "file" && (
                <div style={{ display: "flex", gap: 8, alignItems: "center", marginTop: 10 }}>
                    <span style={label}>At most</span>
                    <input className="pt-input" type="number" min={1} max={5} style={{ width: 90, height: 32 }}
                        value={field.files?.max ?? ""}
                        onChange={e => onPatch({ files: { ...(field.files || {}), max: Number(e.target.value) || undefined } })} />
                    <span style={label}>files, each up to</span>
                    <input className="pt-input" type="number" min={1} max={10} style={{ width: 90, height: 32 }}
                        value={field.files?.mb ?? ""}
                        onChange={e => onPatch({ files: { ...(field.files || {}), mb: Number(e.target.value) || undefined } })} />
                    <span style={label}>MB</span>
                </div>
            )}

            {/* RESPONSE VALIDATION, read left to right as a sentence: type · condition · value · what
                to say. Google's row, and the same words. */}
            {extras.rule && (
                <div style={{ display: "flex", flexWrap: "wrap", gap: 8, alignItems: "center", marginTop: 12,
                    paddingTop: 10, borderTop: "1px solid var(--line)" }}>
                    {kind === "checkbox" ? <span style={label}>Checkbox</span> : (
                        <select className="pt-input" style={{ height: 32, width: 170 }} value={rule.type || "number"}
                            onChange={e => setRule({ type: e.target.value, op: "", value: "", value2: "" })}>
                            {RULE_TYPES.map(([v, l]) => <option key={v} value={v}>{l}</option>)}
                        </select>
                    )}
                    <select className="pt-input" style={{ height: 32, width: 200 }} value={rule.op || ""}
                        onChange={e => setRule({ type: kind === "checkbox" ? "checkbox" : (rule.type || "number"), op: e.target.value })}>
                        <option value="">— condition —</option>
                        {ops.map(([v, l]) => <option key={v} value={v}>{l}</option>)}
                    </select>
                    {!NEEDS_NOTHING.includes(rule.op) && (
                        <input className="pt-input" style={{ height: 32, width: 150, fontFamily: rule.type === "regex" ? "var(--mono)" : "inherit" }}
                            placeholder="Value" value={rule.value ?? ""}
                            onChange={e => setRule({ value: e.target.value })} />
                    )}
                    {["between", "not_between"].includes(rule.op) && (
                        <input className="pt-input" style={{ height: 32, width: 150 }} placeholder="and"
                            value={rule.value2 ?? ""} onChange={e => setRule({ value2: e.target.value })} />
                    )}
                    <input className="pt-input" style={{ flex: 1, minWidth: 180, height: 32, fontSize: 12.5 }}
                        placeholder="Custom error text" value={rule.message ?? ""}
                        onChange={e => setRule({ message: e.target.value })} />
                    <Zh value={field.zh?.message} onChange={v => setZh({ message: v })} placeholder="錯誤訊息（繁體）"
                        style={{ marginTop: 0, flex: 1, minWidth: 180, height: 32 }} />
                    <button type="button" className="pt-btn pt-btn-sm" title="Remove validation"
                        onClick={() => { onPatch({ validate: {} }); setExtras(x => ({ ...x, rule: false })); }}>✕</button>
                </div>
            )}

            <div style={{ marginTop: 12, paddingTop: 10, borderTop: "1px solid var(--line)",
                display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap" }}>
                <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
                    <span style={label}>Saved as</span>
                    <input className="pt-input" value={field.name} placeholder="full_name"
                        style={{ width: 160, height: 30, fontFamily: "var(--mono)", fontSize: 12 }}
                        onChange={e => onPatch({ name: e.target.value, nameLocked: true })} />
                </div>
                {sections.length > 1 && (
                    <select className="pt-input" style={{ width: 130, height: 30 }} value={field.page || 1}
                        onChange={e => onPatch({ page: Number(e.target.value) })}>
                        {sections.map(n => <option key={n} value={n}>Section {n}</option>)}
                    </select>
                )}
                <div style={{ marginLeft: "auto", display: "flex", alignItems: "center", gap: 6 }}>
                    <button type="button" className="pt-btn pt-btn-sm" disabled={first} onClick={() => onMove(-1)} title="Move up">↑</button>
                    <button type="button" className="pt-btn pt-btn-sm" disabled={lastOne} onClick={() => onMove(1)} title="Move down">↓</button>
                    <button type="button" className="pt-btn pt-btn-sm" onClick={onDuplicate} title="Make a copy">⧉</button>
                    <button type="button" className="pt-btn pt-btn-sm" onClick={onRemove} title="Delete">🗑</button>
                    <span style={{ width: 1, height: 20, background: "var(--line)" }} />
                    <label style={{ fontSize: 12.5, display: "flex", alignItems: "center", gap: 6 }}>
                        <input type="checkbox" checked={!!field.required}
                            onChange={e => onPatch({ required: e.target.checked })} />
                        Required
                    </label>
                    <Menu items={[
                        ["Description", () => setExtras(x => ({ ...x, help: !x.help }))],
                        ["Response validation", () => setExtras(x => ({ ...x, rule: !x.rule }))],
                        CHOICE_KINDS.includes(kind) && ["Shuffle option order", () => onPatch({ shuffle: !field.shuffle })],
                        ["choice", "dropdown"].includes(kind) && ["Go to section based on answer",
                            () => { setExtras(x => ({ ...x, route: !x.route })); onRoute(null, null); }],
                    ]} />
                </div>
            </div>
        </div>
    );
}

// A copy of the list with two entries exchanged.
function swap(list, a, b) {
    const out = [...list];
    [out[a], out[b]] = [out[b], out[a]];

    return out;
}

// One routing target in words, the way the selects word it.
function goToLabel(to, page, sectionTitles) {
    if (to === "submit") return "Submit form";
    const n = Number(to);

    return `${n < page ? "Go back to" : "Go to"} section ${n}${sectionTitles[n] ? ` (${sectionTitles[n]})` : ""}`;
}

// Where a section sends its answers when no answer decided it — Google's "After section N" line.
function Routing({ page, meta, sections, sectionTitles, onPatch }) {
    // Every section but this one — going BACK is allowed since 24/09/2026.
    const others = sections.filter(n => n !== page);
    const branch = meta.branch || null;
    // With a branch and no `else` the walk falls through to `next` (formWalk.nextPage), so that is
    // what this line shows.
    const value = branch ? (branch.else ?? meta.next ?? "") : (meta.next ?? "");

    return (
        <div style={{ display: "flex", alignItems: "center", gap: 10, margin: "4px 0 22px", fontSize: 12.5 }}>
            <span style={{ color: "var(--muted)" }}>After section {page}</span>
            <select className="pt-input" style={{ height: 32, width: 320 }} value={value}
                onChange={e => {
                    const to = e.target.value || undefined;
                    if (branch) return onPatch({ branch: { ...branch, else: to } });
                    onPatch({ next: to });
                }}>
                <option value="">Continue to next section</option>
                {others.map(n => (
                    <option key={n} value={n}>{n < page ? "Go back to" : "Go to"} section {n}{sectionTitles[n] ? ` (${sectionTitles[n]})` : ""}</option>
                ))}
                <option value="submit">Submit form</option>
            </select>
        </div>
    );
}

export default function FormBuilder({ draft, patch, sectioned, bilingual = false }) {
    const [editing, setEditing] = useState(null);
    const [busy, setBusy] = useState(false);
    const [shut, setShut] = useState({});
    const fields = draft.fields || [];
    const sections = sectioned ? pagesOf({ fields, pages: draft.pages || [], layout: 3 }) : [1];
    const sectionTitles = Object.fromEntries(
        (draft.pages || []).map(p => [Number(p.page) || 1, p.title || ""]),
    );

    const patchField = (i, p) => patch({ fields: fields.map((f, n) => n === i ? { ...f, ...p } : f) });

    // ONE STEP OF THE DRAG: the card moves to where the pointer is, and takes the SECTION it lands
    // in — dragging across a section header is how a question moves between sections in Google's,
    // and leaving the page behind would move a question that still answers somewhere else.
    const slide = useCallback((from, to) => {
        setEditing(e => (e === from ? to : e));
        patch(d => {
            const next = [...(d.fields || [])];
            const [moved] = next.splice(from, 1);
            next.splice(to, 0, { ...moved, page: (d.fields[to] || moved).page || 1 });

            return { fields: next };
        });
    }, [patch]);

    const sortable = useSortable(fields.length, slide);
    const removeField = (i) => patch({ fields: fields.filter((_, n) => n !== i) });

    const moveField = (i, by) => {
        const page = fields[i].page || 1;
        const siblings = fields.map((f, n) => ({ f, n })).filter(({ f }) => (f.page || 1) === page);
        const at = siblings.findIndex(({ n }) => n === i);
        const swap = siblings[at + by];
        if (!swap) return;
        const next = [...fields];
        [next[i], next[swap.n]] = [next[swap.n], next[i]];
        patch({ fields: next });
    };

    // A COPY, below the original and with a name of its own: two questions sharing a stored name
    // would keep one answer between them.
    const duplicate = (i) => {
        const copy = { ...fields[i], name: `${fields[i].name || "question"}_copy`, nameLocked: true };
        patch({ fields: [...fields.slice(0, i + 1), copy, ...fields.slice(i + 1)] });
        setEditing(i + 1);
    };

    const patchPage = (page, p) => {
        const pages = [...(draft.pages || [])];
        const at = pages.findIndex(x => Number(x.page) === page);
        const merged = { ...(at >= 0 ? pages[at] : { page }), ...p };
        if (at >= 0) pages[at] = merged; else pages.push(merged);
        patch({ pages });
    };

    // PER-OPTION ROUTING, stored as the SECTION's branch — the same thing said Google's way. The
    // question that owns the options is the question the branch routes on.
    const routeOption = (i) => (optionLabel, to) => {
        const field = fields[i];
        const page = field.page || 1;
        const meta = pageMeta({ pages: draft.pages || [] }, page);
        const branch = meta.branch || { field: field.name, cases: [] };
        if (optionLabel === null) {
            // Turning it on or off from the ⋮ menu.
            return patchPage(page, meta.branch ? { branch: undefined } : { branch: { field: field.name, cases: [] } });
        }
        const cases = [...(branch.cases || []).filter(c => c.value !== optionLabel)];
        if (to) cases.push({ value: optionLabel, page: to });
        patchPage(page, { branch: { ...branch, field: field.name, cases } });
    };

    const upload = async (file, onDone) => {
        if (!file) return;
        setBusy(true);
        try {
            const body = new FormData();
            body.append("file", file);
            const res = await axios.post("/form_template/image", body);
            onDone(res.data?.data?.path);
        } catch (err) {
            notifyError(err?.response?.data?.message || "Could not add that image.");
        } finally {
            setBusy(false);
        }
    };

    const addItem = (kind, page) => {
        const item = kind === "question"
            ? { ...blankField(page) }
            : { ...blankField(page), kind, name: "", label: "", required: false };
        patch({ fields: [...fields, item] });
        setEditing(fields.length);
    };

    const addSection = () => {
        const next = Math.max(...sections) + 1;
        patch({ pages: [...(draft.pages || []), { page: next, title: "" }], fields: [...fields, { ...blankField(next) }] });
        setEditing(fields.length);
    };

    const duplicateSection = (page) => {
        const next = Math.max(...sections) + 1;
        const meta = pageMeta({ pages: draft.pages || [] }, page);
        const copies = fields.filter(f => (f.page || 1) === page)
            .map(f => ({ ...f, page: next, name: f.name ? `${f.name}_copy` : "", nameLocked: true }));
        patch({
            pages: [...(draft.pages || []), { ...meta, page: next, branch: undefined, next: undefined }],
            fields: [...fields, ...copies],
        });
    };

    const removeSection = (page) => {
        // Its questions move back a section rather than being deleted with it — losing a question
        // to a button labelled "delete section" is a surprise nobody recovers from.
        const to = [...sections].reverse().find(n => n < page) || 1;
        patch({
            pages: (draft.pages || []).filter(p => Number(p.page) !== page),
            fields: fields.map(f => (f.page || 1) === page ? { ...f, page: to } : f),
        });
    };

    const last = sections[sections.length - 1];

    return (
        <ZhOn.Provider value={bilingual}>
        <div style={{ display: "flex", gap: 14, alignItems: "flex-start" }}>
            <div style={{ flex: 1, minWidth: 0 }}>
                {sections.map(page => {
                    const meta = pageMeta({ pages: draft.pages || [] }, page);
                    const mine = fields.map((f, i) => ({ f, i })).filter(({ f }) => (f.page || 1) === page);

                    return (
                        // `data-section` is a test hook: the sortable's promise is that a card
                        // takes the section it lands in, and that is only checkable from outside.
                        <div key={page} data-section={page}>
                            {sectioned && (
                                <div style={{ ...card, borderTop: "6px solid var(--brand)", marginBottom: shut[page] ? 22 : 12 }}>
                                    <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 8 }}>
                                        <span style={{ fontSize: 11, fontWeight: 700, color: "var(--brand)", textTransform: "uppercase", letterSpacing: ".06em" }}>
                                            Section {page} of {sections.length}
                                        </span>
                                        <div style={{ marginLeft: "auto", display: "flex", gap: 4, alignItems: "center" }}>
                                            <button type="button" className="pt-btn pt-btn-sm" title={shut[page] ? "Expand" : "Collapse"}
                                                onClick={() => setShut(s => ({ ...s, [page]: !s[page] }))}>{shut[page] ? "⌄" : "⌃"}</button>
                                            <Menu items={[
                                                ["Duplicate section", () => duplicateSection(page)],
                                                sections.length > 1 && ["Delete section", () => removeSection(page)],
                                            ]} />
                                        </div>
                                    </div>
                                    <input className="pt-input" value={meta.title || ""} placeholder="Untitled section"
                                        style={{ fontSize: 15, fontWeight: 600, marginBottom: 6 }}
                                        onChange={e => patchPage(page, { title: e.target.value })} />
                                    <Zh value={meta.zh?.title} placeholder="部分標題（繁體）" style={{ marginTop: 0, marginBottom: 6 }}
                                        onChange={v => patchPage(page, { zh: { ...(meta.zh || {}), title: v } })} />
                                    <textarea className="pt-input" rows={2} value={meta.description || ""}
                                        placeholder="Description (optional)" style={{ fontSize: 12.5 }}
                                        onChange={e => patchPage(page, { description: e.target.value })} />
                                    <Zh multiline value={meta.zh?.description} placeholder="描述（繁體）"
                                        onChange={v => patchPage(page, { zh: { ...(meta.zh || {}), description: v } })} />
                                    {meta.image ? (
                                        <div style={{ marginTop: 8 }}>
                                            <img src={`/storage/${meta.image}`} alt="" style={{ maxWidth: 280, borderRadius: 8, display: "block" }} />
                                            <button type="button" className="pt-btn pt-btn-sm" style={{ marginTop: 6 }}
                                                onClick={() => patchPage(page, { image: "" })}>Remove image</button>
                                        </div>
                                    ) : (
                                        <label className="pt-btn pt-btn-sm" style={{ marginTop: 8, display: "inline-block", cursor: "pointer" }}>
                                            {busy ? "Adding…" : "+ Add image"}
                                            <input type="file" accept="image/*" hidden
                                                onChange={e => upload(e.target.files?.[0], path => patchPage(page, { image: path }))} />
                                        </label>
                                    )}
                                </div>
                            )}

                            {/* THE WHOLE CARD DRAGS, except when the drag starts inside something
                                you can type in or press. The card being dragged stays where it is
                                in the list and dims — it IS the gap the others open around. */}
                            {!shut[page] && mine.map(({ f, i }, n) => (
                                <div key={i} ref={sortable.hold(i)} onPointerDown={sortable.start(i, f.label)}
                                    // Named for the spec that drags these — see form-builder.spec.js.
                                    data-question-card={f.name || f.kind}
                                    style={sortable.dragging === i ? {
                                        // `--card` is what the card inside paints itself with, so
                                        // setting it here tints the whole card without the card
                                        // having to know a drag is happening.
                                        "--card": "var(--blush-bg, #eef4f1)",
                                        opacity: .9, borderRadius: 12, cursor: "grabbing",
                                        outline: "2px dashed var(--brand)", outlineOffset: "-3px",
                                    } : { transition: "transform .12s ease" }}>
                                <Question field={f} index={i} sections={sections} sectionTitles={sectionTitles}
                                    branch={meta.branch && meta.branch.field === f.name ? meta.branch : null}
                                    fallback={meta.branch?.else ?? meta.next ?? null}
                                    editing={editing === i}
                                    onOpen={() => setEditing(i)}
                                    onPatch={p => patchField(i, p)}
                                    onRemove={() => { removeField(i); setEditing(null); }}
                                    onMove={by => moveField(i, by)}
                                    onDuplicate={() => duplicate(i)}
                                    onImage={file => upload(file, path => patchField(i, { image: path }))}
                                    onOptionImage={(n2, file) => upload(file, path => patchField(i, {
                                        options: (f.options || []).map(optionOf).map((o, m) => m === n2 ? { ...o, image: path } : o),
                                    }))}
                                    onRoute={routeOption(i)}
                                    first={n === 0} lastOne={n === mine.length - 1} />
                                </div>
                            ))}

                            {!shut[page] && sectioned && mine.length === 0 && (
                                <div onPointerUp={() => {
                                        // A section with nothing in it has no card to slide past,
                                        // so releasing over it is the move.
                                        if (sortable.dragging === null) return;
                                        patch({ fields: fields.map((f, n) => n === sortable.dragging ? { ...f, page } : f) });
                                    }}
                                    style={{ ...card, borderStyle: "dashed", textAlign: "center",
                                        color: "var(--faint)", fontSize: 12.5 }}>
                                    Nothing here yet — drag a question in, or add one.
                                </div>
                            )}

                            {!shut[page] && sectioned && (
                                <Routing page={page} meta={meta} sections={sections} sectionTitles={sectionTitles}
                                    onPatch={p => patchPage(page, p)} />
                            )}
                            {!sectioned && (
                                <button type="button" className="pt-btn" style={{ marginBottom: 18 }}
                                    onClick={() => addItem("question", 1)}>+ Add question</button>
                            )}
                        </div>
                    );
                })}
            </div>

            {sortable.preview}

            {/* THE ITEM TOOLBAR, down the right of the canvas — the same six controls in the same
                order as Google's, because that is where somebody coming from there will look. */}
            {sectioned && (
                <div style={{
                    position: "sticky", top: 8, display: "flex", flexDirection: "column", gap: 4, padding: 6,
                    border: "1px solid var(--line)", borderRadius: 12, background: "var(--card, #fff)",
                }}>
                    <button type="button" className="pt-btn pt-btn-sm" title="Add question"
                        onClick={() => addItem("question", last)}>⊕</button>
                    <button type="button" className="pt-btn pt-btn-sm" title="Add title and description"
                        onClick={() => addItem("note", last)}>Tт</button>
                    <button type="button" className="pt-btn pt-btn-sm" title="Add image"
                        onClick={() => addItem("image", last)}>🖼</button>
                    <button type="button" className="pt-btn pt-btn-sm" title="Add video"
                        onClick={() => addItem("video", last)}>▶</button>
                    <button type="button" className="pt-btn pt-btn-sm" title="Add section"
                        onClick={addSection}>▤</button>
                </div>
            )}
        </div>
        </ZhOn.Provider>
    );
}
