/* ==================================================================
   Forms → raise a form, read what came back
   ------------------------------------------------------------------
   The office half of Forms Adarshabani (forms-adarshabani/, served at
   forms.adarshabani.in). Two tabs, because they are two jobs:

     • Forms        — raise an admission intake or a vacancy, open it,
                      close it, add extra questions to it.
     • Applications — the inbox. Every filled form, with the applicant's
                      answers, their bio-data attachments, and the funnel
                      the office moves them along.

   Reading is open to any signed-in staff member. Raising a form, moving
   a status, writing a note and exporting are admin / HoD / coordinator
   only — enforced server-side; `can_edit` on the responses is what hides
   the controls for everyone else.

   This is NOT the "Intent to Apply" register (screen-admissions.jsx).
   That one is the fixed pre-admission funnel wired to the button on the
   website. This is the general form desk: the office defines the form,
   so its questions are data rather than schema.

   Endpoints: /api/forms*  ·  public half: /api/public/forms*
   ================================================================== */

const { Icon: FmIcon } = window.KXUI;

// Mirrors SUBMISSION_STATUSES in backend/src/routes/forms.ts. The server is
// still the authority — /forms/meta is fetched on mount and replaces this —
// but having it inline means the first paint shows labels, not raw keys.
const FM_FALLBACK_STATUSES = {
  admission: [
    { key: "received",          label: "Received" },
    { key: "under_review",      label: "Under review" },
    { key: "documents_pending", label: "Documents pending" },
    { key: "interview_called",  label: "Called for interview" },
    { key: "selected",          label: "Selected" },
    { key: "admitted",          label: "Admitted" },
  ],
  vacancy: [
    { key: "received",         label: "Received" },
    { key: "shortlisted",      label: "Shortlisted" },
    { key: "interview_called", label: "Called for interview" },
    { key: "interviewed",      label: "Interviewed" },
    { key: "offered",          label: "Offer sent" },
    { key: "hired",            label: "Appointed" },
  ],
};
const FM_FALLBACK_TERMINAL = [
  { key: "rejected",  label: "Not selected" },
  { key: "withdrawn", label: "Withdrawn" },
];

// Cool → warm as the applicant gets closer to a seat or a post; the two
// terminal statuses are grey/red so they never read as progress.
// `fg` is a TEXT weight and `bd` an explicit border — see the matching note on
// ADM_STAGE_TINT in screen-admissions.jsx for why both changed with the light
// theme.
const FM_TINT = {
  received:          { fg: "var(--ink-3)",  bg: "rgba(138,138,147,.12)", bd: "rgba(138,138,147,.30)" },
  under_review:      { fg: "var(--blue)",   bg: "rgba(59,123,246,.12)",  bd: "rgba(59,123,246,.28)" },
  shortlisted:       { fg: "var(--blue)",   bg: "rgba(59,123,246,.12)",  bd: "rgba(59,123,246,.28)" },
  documents_pending: { fg: "var(--amber)",  bg: "rgba(255,159,10,.14)",  bd: "rgba(255,159,10,.32)" },
  interview_called:  { fg: "var(--amber)",  bg: "rgba(255,159,10,.14)",  bd: "rgba(255,159,10,.32)" },
  interviewed:       { fg: "var(--violet)", bg: "rgba(107,91,210,.12)",  bd: "rgba(107,91,210,.28)" },
  offered:           { fg: "var(--green)",  bg: "rgba(47,190,95,.12)",   bd: "rgba(47,190,95,.30)" },
  selected:          { fg: "var(--green)",  bg: "rgba(47,190,95,.12)",   bd: "rgba(47,190,95,.30)" },
  admitted:          { fg: "var(--green)",  bg: "rgba(47,190,95,.20)",   bd: "rgba(47,190,95,.40)" },
  hired:             { fg: "var(--green)",  bg: "rgba(47,190,95,.20)",   bd: "rgba(47,190,95,.40)" },
  rejected:          { fg: "var(--red)",    bg: "rgba(229,72,77,.12)",   bd: "rgba(229,72,77,.30)" },
  withdrawn:         { fg: "var(--ink-3)",  bg: "rgba(138,138,147,.12)", bd: "rgba(138,138,147,.30)" },
};

// First-paint fallback only. Once /forms/meta lands, kind labels come from the
// templates the school actually has — including ones it built itself.
const FM_KIND_LABEL = { admission: "Admission", vacancy: "Vacancy" };
const fmKindLabel = (meta, key) =>
  (meta?.kinds || []).find(k => k.key === key)?.label || FM_KIND_LABEL[key] || key;

const fmDate = (iso) => iso
  ? new Date(iso).toLocaleDateString(undefined, { day: "numeric", month: "short", year: "numeric" })
  : "—";

const fmDateTime = (iso) => iso
  ? new Date(iso).toLocaleString(undefined, { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" })
  : "—";

// A <input type="date"> wants YYYY-MM-DD; the API hands back a full ISO
// timestamp. Converting here keeps the composer's inputs controlled.
const fmDateInput = (iso) => iso ? new Date(iso).toISOString().slice(0, 10) : "";

const FmPill = ({ status, label }) => {
  const t = FM_TINT[status] || FM_TINT.received;
  return (
    <span style={{
      display: "inline-block", padding: "3px 10px", borderRadius: 999,
      fontSize: 11, whiteSpace: "nowrap",
      color: t.fg, background: t.bg, border: `1px solid ${t.bd}`,
    }}>{label || status}</span>
  );
};

// Whether a form is accepting submissions right now. The server computes the
// same thing as `is_open`; this only labels it.
const FmFormState = ({ form }) => {
  if (form.status === "draft") {
    return <span className="pill">Draft</span>;
  }
  if (form.is_open) {
    return <span className="pill green"><span className="swatch"></span>Open</span>;
  }
  // status === 'open' but outside its window reads as scheduled/expired, not
  // as "someone closed it" — the distinction matters when chasing why a link
  // 409s for a family.
  if (form.status === "open" && form.opens_at && new Date(form.opens_at) > new Date()) {
    return <span className="pill amber"><span className="swatch"></span>Opens {fmDate(form.opens_at)}</span>;
  }
  return <span className="pill">Closed</span>;
};

const FmField = ({ label, value }) => (
  <div style={{ marginBottom: 10 }}>
    <div className="muted" style={{ fontSize: 10.5, textTransform: "uppercase", letterSpacing: ".07em" }}>{label}</div>
    <div style={{ color: "var(--ink-1)", fontSize: 12.5, whiteSpace: "pre-wrap" }}>{value || "—"}</div>
  </div>
);

const fmInput = {
  padding: "7px 10px", background: "var(--bg-2)", border: "1px solid var(--line-soft)",
  borderRadius: 8, color: "var(--ink-0)", fontSize: 12.5, width: "100%",
};

/* ------------------------------------------------------------------
   Modal shell — one dialog chrome for the composer and the template
   editor.
   ------------------------------------------------------------------
   Two things here were wrong before and are worth stating:

   • z-index. The redesigned cockpit puts `.cx-topwrap` at 150 and the
     roster sheet at 250, so a dialog at 60 rendered UNDERNEATH the top
     bar — the title sat behind the nav pill and looked clipped. 300
     clears everything the design system defines.

   • Scrolling. The backdrop used to scroll as one long column, so a
     tall form pushed its own heading and its Save button off-screen and
     you had to scroll the page to find them. Now the panel is capped at
     the viewport, the header and footer are pinned, and only the body
     between them moves.
   ------------------------------------------------------------------ */

const FmModal = ({ title, subtitle, onClose, footer, children, width = 820 }) => {
  // Escape closes, and the page behind must not scroll while a dialog is up —
  // on a trackpad it is very easy to scroll the cockpit instead of the form.
  React.useEffect(() => {
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", onKey);
    const prev = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    return () => {
      window.removeEventListener("keydown", onKey);
      document.body.style.overflow = prev;
    };
  }, [onClose]);

  return (
    <div
      role="dialog" aria-modal="true"
      style={{
        position: "fixed", inset: 0, zIndex: 300,
        background: "rgba(20,20,23,0.38)",
        WebkitBackdropFilter: "blur(2px)", backdropFilter: "blur(2px)",
        display: "flex", alignItems: "center", justifyContent: "center",
        padding: "24px 20px",
      }}
      onClick={e => { if (e.target === e.currentTarget) onClose(); }}
    >
      <div style={{
        width: `min(${width}px, 100%)`,
        maxHeight: "calc(100dvh - 48px)",
        display: "flex", flexDirection: "column",
        background: "var(--bg-1)", border: "1px solid var(--line)",
        borderRadius: 18, boxShadow: "var(--shadow-pop)", overflow: "hidden",
      }}>
        <div style={{
          flexShrink: 0, display: "flex", alignItems: "flex-start", gap: 12,
          justifyContent: "space-between", padding: "18px 22px 14px",
          borderBottom: "1px solid var(--line-soft)",
        }}>
          <div style={{ minWidth: 0 }}>
            <h2 style={{ margin: 0, color: "var(--ink-0)", fontSize: 17, lineHeight: 1.25 }}>{title}</h2>
            {subtitle && (
              <p className="muted" style={{ margin: "5px 0 0", fontSize: 12.5, lineHeight: 1.45 }}>{subtitle}</p>
            )}
          </div>
          <button className="btn ghost sm" onClick={onClose} aria-label="Close" style={{ flexShrink: 0 }}>
            <FmIcon name="x" size={11}/>
          </button>
        </div>

        {/* The only scrolling region. */}
        <div style={{ flex: 1, minHeight: 0, overflowY: "auto", padding: "18px 22px" }}>
          {children}
        </div>

        {footer && (
          <div style={{
            flexShrink: 0, display: "flex", gap: 8, justifyContent: "flex-end",
            padding: "14px 22px", borderTop: "1px solid var(--line-soft)",
            background: "var(--bg-2)",
          }}>
            {footer}
          </div>
        )}
      </div>
    </div>
  );
};

/* ------------------------------------------------------------------
   Composer — raise a form, or edit one already raised.
   ------------------------------------------------------------------ */

const FmComposer = ({ meta, initial, onClose, onSaved }) => {
  const editing = !!initial;
  const [kind, setKind]   = React.useState(initial?.kind || "admission");
  const [form, setForm]   = React.useState(() => ({
    title:       initial?.title || "",
    subtitle:    initial?.subtitle || "",
    description: initial?.description || "",
    status:      initial?.status || "draft",
    opens_at:    fmDateInput(initial?.opens_at),
    closes_at:   fmDateInput(initial?.closes_at),
  }));
  const [metaFields, setMetaFields] = React.useState(() => initial?.meta || {});
  const [fields, setFields] = React.useState(() => initial?.fields || []);
  const [busy, setBusy] = React.useState(false);
  const [err, setErr]   = React.useState(null);

  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));
  const setMeta = (k, v) => setMetaFields(m => ({ ...m, [k]: v }));

  // The class list on an admission form. Left empty means "every class the
  // school admits into" — the server falls back to the built-in list, so the
  // office only has to touch this for a narrowed intake.
  const allClasses = ["Nursery", "Infant I", "Infant II", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X"];
  const chosenClasses = Array.isArray(metaFields.classes) ? metaFields.classes : [];
  const toggleClass = (c) => setMeta("classes",
    chosenClasses.includes(c) ? chosenClasses.filter(x => x !== c) : [...chosenClasses, c]);

  const addField = () => setFields(fs => [...fs, { key: "", label: "", type: "text", required: false, options: [] }]);
  const setField = (i, patch) => setFields(fs => fs.map((f, idx) => idx === i ? { ...f, ...patch } : f));
  const dropField = (i) => setFields(fs => fs.filter((_, idx) => idx !== i));

  const save = async () => {
    if (!form.title.trim()) { setErr("Give the form a title."); return; }

    // Tidy the select options ONCE, here — the textarea deliberately keeps the
    // raw lines while you type (see the comment on it) so blank lines and
    // trailing spaces mid-edit don't fight the caret. The server sanitises
    // again on its side; this keeps the composer's own state honest so a save
    // followed by more editing doesn't show the untidied text back.
    const cleaned = fields.map(f => f.type === "select"
      ? { ...f, options: (f.options || []).map(o => String(o).trim()).filter(Boolean) }
      : f);

    // A select with nothing to pick from is a dead end for the applicant: the
    // public form would render an empty dropdown, and a *required* one could
    // never be satisfied. Catch it here rather than let it reach a family.
    const empty = cleaned.find(f => f.type === "select" && f.label.trim() && f.options.length === 0);
    if (empty) {
      setErr(`“${empty.label}” is a multiple-choice question with no options. Add at least one, or change its answer type.`);
      return;
    }

    setBusy(true); setErr(null);
    try {
      const body = { ...form, meta: metaFields, fields: cleaned };
      if (editing) {
        // kind and slug are frozen after create — the slug is a URL people
        // already hold, and submissions copy the kind at write time.
        await window.KXApi.patch(`/forms/${initial.id}`, body);
      } else {
        await window.KXApi.post("/forms", { ...body, kind });
      }
      onSaved();
    } catch (e) {
      setErr(String(e.message || e));
    } finally {
      setBusy(false);
    }
  };

  const fieldTypes = meta?.field_types?.length
    ? meta.field_types
    : ["text", "textarea", "select", "number", "date", "tel", "email", "checkbox"];

  return (
    <FmModal
      title={editing ? `Edit · ${initial.title}` : "Raise a form"}
      subtitle={editing
        ? "The kind and the public link are fixed once a form exists — families and applicants may already hold the link."
        : "The form appears on forms.adarshabani.in as soon as you set it to Open. Save it as a draft while you work on it."}
      onClose={onClose}
      footer={<>
        <button className="btn ghost sm" onClick={onClose} disabled={busy}>Cancel</button>
        <button className="btn sm primary" onClick={save} disabled={busy}>
          {busy ? "Saving…" : editing ? "Save changes" : "Raise this form"}
        </button>
      </>}
    >

        {err && (
          <div style={{ marginBottom: 14, padding: "9px 12px", borderRadius: 8, background: "rgba(255,107,107,.10)", border: "1px solid rgba(255,107,107,.3)", color: "#ff9b9b", fontSize: 12.5 }}>
            {err}
          </div>
        )}

        {!editing && (
          <div style={{ marginBottom: 16 }}>
            <div className="muted" style={{ fontSize: 10.5, textTransform: "uppercase", letterSpacing: ".07em", marginBottom: 6 }}>
              What kind of form
            </div>
            <div style={{ display: "flex", gap: 6 }}>
              {(meta?.kinds || [{ key: "admission", label: "Admission form" }, { key: "vacancy", label: "Career / vacancy" }]).map(k => (
                <button key={k.key} className="btn ghost sm"
                  onClick={() => { setKind(k.key); setMetaFields({}); }}
                  style={{
                    borderColor: kind === k.key ? "var(--accent)" : "var(--line-soft)",
                    color: kind === k.key ? "var(--accent)" : "var(--ink-2)",
                  }}>
                  {k.label}
                </button>
              ))}
            </div>
          </div>
        )}

        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12, marginBottom: 12 }}>
          <label style={{ gridColumn: "1 / -1" }}>
            <div className="muted" style={{ fontSize: 10.5, textTransform: "uppercase", letterSpacing: ".07em", marginBottom: 5 }}>Title</div>
            <input style={fmInput} value={form.title} onChange={e => set("title", e.target.value)}
              placeholder={kind === "vacancy" ? "PGT · Physics" : "Admission 2027"}/>
          </label>
          <label style={{ gridColumn: "1 / -1" }}>
            <div className="muted" style={{ fontSize: 10.5, textTransform: "uppercase", letterSpacing: ".07em", marginBottom: 5 }}>Subtitle</div>
            <input style={fmInput} value={form.subtitle} onChange={e => set("subtitle", e.target.value)}
              placeholder={kind === "vacancy" ? "Full-time teaching post · senior section" : "Nursery to Class X · session 2027"}/>
          </label>
          <label style={{ gridColumn: "1 / -1" }}>
            <div className="muted" style={{ fontSize: 10.5, textTransform: "uppercase", letterSpacing: ".07em", marginBottom: 5 }}>
              Description — shown above the form
            </div>
            <textarea style={{ ...fmInput, minHeight: 84, resize: "vertical", fontFamily: "inherit" }}
              value={form.description} onChange={e => set("description", e.target.value)}/>
          </label>
        </div>

        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 12, marginBottom: 18 }}>
          <label>
            <div className="muted" style={{ fontSize: 10.5, textTransform: "uppercase", letterSpacing: ".07em", marginBottom: 5 }}>Status</div>
            <select style={fmInput} value={form.status} onChange={e => set("status", e.target.value)}>
              <option value="draft">Draft — not public</option>
              <option value="open">Open — accepting</option>
              <option value="closed">Closed — read only</option>
            </select>
          </label>
          <label>
            <div className="muted" style={{ fontSize: 10.5, textTransform: "uppercase", letterSpacing: ".07em", marginBottom: 5 }}>Opens on</div>
            <input type="date" style={fmInput} value={form.opens_at} onChange={e => set("opens_at", e.target.value)}/>
          </label>
          <label>
            <div className="muted" style={{ fontSize: 10.5, textTransform: "uppercase", letterSpacing: ".07em", marginBottom: 5 }}>Closes after</div>
            <input type="date" style={fmInput} value={form.closes_at} onChange={e => set("closes_at", e.target.value)}/>
          </label>
        </div>
        <p className="muted" style={{ margin: "-8px 0 18px", fontSize: 11.5 }}>
          Leave the dates blank to control it by hand. A closing date shuts the form on its own — nobody has to remember.
        </p>

        {/* Kind-specific facts the public card and page render. */}
        {kind === "vacancy" ? (
          <div style={{ borderTop: "1px solid var(--line-soft)", paddingTop: 16, marginBottom: 18 }}>
            <div className="muted" style={{ fontSize: 10.5, textTransform: "uppercase", letterSpacing: ".07em", marginBottom: 10 }}>
              About the post
            </div>
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
              {[
                ["post", "Post", "PGT Physics"],
                ["department", "Department", "Science"],
                ["positions", "How many posts", "1"],
                ["employment_type", "Type", "Full-time"],
                ["qualification", "Qualification wanted", "M.Sc. Physics with B.Ed."],
                ["experience", "Experience wanted", "2+ years, classes IX–XII"],
                ["salary_range", "Salary", "As per school scale"],
                ["location", "Location", "Adarshabani campus"],
              ].map(([k, label, ph]) => (
                <label key={k}>
                  <div className="muted" style={{ fontSize: 10.5, marginBottom: 5 }}>{label}</div>
                  <input style={fmInput} placeholder={ph}
                    type={k === "positions" ? "number" : "text"}
                    value={metaFields[k] ?? ""} onChange={e => setMeta(k, e.target.value)}/>
                </label>
              ))}
            </div>
          </div>
        ) : kind === "admission" ? (
          <div style={{ borderTop: "1px solid var(--line-soft)", paddingTop: 16, marginBottom: 18 }}>
            <div className="muted" style={{ fontSize: 10.5, textTransform: "uppercase", letterSpacing: ".07em", marginBottom: 10 }}>
              About the intake
            </div>
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12, marginBottom: 12 }}>
              <label>
                <div className="muted" style={{ fontSize: 10.5, marginBottom: 5 }}>Session / intake year</div>
                <input style={fmInput} type="number" placeholder="2027"
                  value={metaFields.intake_year ?? ""} onChange={e => setMeta("intake_year", e.target.value)}/>
              </label>
              <label>
                <div className="muted" style={{ fontSize: 10.5, marginBottom: 5 }}>Session label</div>
                <input style={fmInput} placeholder="Session 2027–28"
                  value={metaFields.session_label ?? ""} onChange={e => setMeta("session_label", e.target.value)}/>
              </label>
            </div>
            <div className="muted" style={{ fontSize: 10.5, marginBottom: 6 }}>
              Classes this intake admits into — leave all unticked to offer every class
            </div>
            <div style={{ display: "flex", flexWrap: "wrap", gap: 5 }}>
              {allClasses.map(c => (
                <button key={c} className="btn ghost sm" onClick={() => toggleClass(c)}
                  style={{
                    borderColor: chosenClasses.includes(c) ? "var(--accent)" : "var(--line-soft)",
                    color: chosenClasses.includes(c) ? "var(--accent)" : "var(--ink-2)",
                  }}>
                  {c}
                </button>
              ))}
            </div>
          </div>
        ) : null}

        {/* Extra questions on top of the built-in ones the kind already asks. */}
        <div style={{ borderTop: "1px solid var(--line-soft)", paddingTop: 16, marginBottom: 18 }}>
          <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 10 }}>
            <div className="muted" style={{ fontSize: 10.5, textTransform: "uppercase", letterSpacing: ".07em" }}>
              Extra questions
            </div>
            <button className="btn ghost sm" onClick={addField}><FmIcon name="plus" size={11}/> Add a question</button>
          </div>
          <p className="muted" style={{ margin: "0 0 12px", fontSize: 11.5 }}>
            {kind === "vacancy"
              ? "Name, date of birth, contact, address, the post, qualifications and work experience are already asked, and a CV can already be attached. Add only what is missing."
              : "The child's details, the guardian's details, the class, the present school, boarding and bus are already asked. Add only what is missing."}
          </p>

          {fields.length === 0 && (
            <p className="muted" style={{ fontSize: 12, fontStyle: "italic" }}>No extra questions.</p>
          )}

          {fields.map((f, i) => (
            <div key={i} style={{ border: "1px solid var(--line-soft)", borderRadius: 10, padding: 12, marginBottom: 8 }}>
              <div style={{ display: "grid", gridTemplateColumns: "2fr 1fr auto auto", gap: 8, alignItems: "end" }}>
                <label>
                  <div className="muted" style={{ fontSize: 10.5, marginBottom: 5 }}>Question</div>
                  <input style={fmInput} value={f.label} onChange={e => setField(i, { label: e.target.value })}
                    placeholder="e.g. Do you have a two-wheeler licence?"/>
                </label>
                <label>
                  <div className="muted" style={{ fontSize: 10.5, marginBottom: 5 }}>Answer type</div>
                  <select style={fmInput} value={f.type} onChange={e => setField(i, { type: e.target.value })}>
                    {fieldTypes.map(t => <option key={t} value={t}>{t}</option>)}
                  </select>
                </label>
                <label style={{ display: "flex", alignItems: "center", gap: 6, paddingBottom: 8, whiteSpace: "nowrap" }}>
                  <input type="checkbox" checked={!!f.required} onChange={e => setField(i, { required: e.target.checked })}/>
                  <span style={{ fontSize: 12, color: "var(--ink-2)" }}>Required</span>
                </label>
                <button className="btn ghost sm" onClick={() => dropField(i)} style={{ marginBottom: 4 }}>
                  <FmIcon name="x" size={11}/>
                </button>
              </div>
              {f.type === "select" && (
                <label style={{ display: "block", marginTop: 8 }}>
                  <div className="muted" style={{ fontSize: 10.5, marginBottom: 5 }}>Options — one per line</div>
                  {/* Split ONLY. Do not trim or drop empties here: this is a
                      controlled textarea, so whatever this handler stores is
                      what the box redisplays on the very next keystroke.
                      Filtering blanks meant pressing Enter after "Yes" gave
                      ["Yes", ""] → ["Yes"] → "Yes", the newline vanished as
                      fast as it was typed, and a second option could never be
                      started. `split("\n").join("\n")` is lossless, so the box
                      now shows exactly what was typed; the trimming and blank
                      dropping happen once, in save(). */}
                  <textarea style={{ ...fmInput, minHeight: 60, resize: "vertical", fontFamily: "inherit" }}
                    placeholder={"Yes\nNo"}
                    value={(f.options || []).join("\n")}
                    onChange={e => setField(i, { options: e.target.value.split("\n") })}/>
                </label>
              )}
            </div>
          ))}
        </div>

    </FmModal>
  );
};

/* ------------------------------------------------------------------
   Tab 1 — the forms the office raised.
   ------------------------------------------------------------------ */

const FmFormsTab = ({ meta, onOpenInbox }) => {
  const [data, setData]       = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [err, setErr]         = React.useState(null);
  const [composing, setComposing] = React.useState(null); // null | { } | form
  const [busy, setBusy]       = React.useState({});

  const load = React.useCallback(async () => {
    setLoading(true); setErr(null);
    try { setData(await window.KXApi.get("/forms")); }
    catch (e) { setErr(String(e.message || e)); }
    finally { setLoading(false); }
  }, []);

  React.useEffect(() => { load(); }, [load]);

  const canEdit = data?.can_edit;
  const forms = data?.forms || [];

  const setStatus = async (form, status) => {
    setBusy(b => ({ ...b, [form.id]: true }));
    try { await window.KXApi.patch(`/forms/${form.id}`, { status }); await load(); }
    catch (e) { window.alert("Could not update: " + (e.message || e)); }
    finally { setBusy(b => { const n = { ...b }; delete n[form.id]; return n; }); }
  };

  const remove = async (form) => {
    if (!window.confirm(`Delete “${form.title}”? This cannot be undone.`)) return;
    setBusy(b => ({ ...b, [form.id]: true }));
    try { await window.KXApi.del(`/forms/${form.id}`); await load(); }
    catch (e) { window.alert(e.message || String(e)); }
    finally { setBusy(b => { const n = { ...b }; delete n[form.id]; return n; }); }
  };

  const publicUrl = (form) => `https://forms.adarshabani.in/apply/${form.slug}`;

  const copyLink = async (form) => {
    try {
      await navigator.clipboard.writeText(publicUrl(form));
      window.alert("Link copied — paste it into WhatsApp or the notice.");
    } catch {
      window.prompt("Copy this link:", publicUrl(form));
    }
  };

  return (
    <div>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 12, marginBottom: 14 }}>
        <p className="muted" style={{ margin: 0, fontSize: 12.5, maxWidth: 720 }}>
          Every form the office has raised. A form is only fillable while it is
          <strong style={{ color: "var(--ink-1)" }}> Open</strong> — drafts are invisible to the public,
          and a closing date shuts it without anyone having to remember.
        </p>
        <div style={{ display: "flex", gap: 8 }}>
          <button className="btn sm" onClick={load}>Refresh</button>
          {canEdit && (
            <button className="btn sm primary" onClick={() => setComposing({})}>
              <FmIcon name="plus" size={11}/> Raise a form
            </button>
          )}
        </div>
      </div>

      {err && (
        <div style={{ marginBottom: 14, padding: "10px 14px", borderRadius: 8, background: "rgba(255,107,107,.10)", border: "1px solid rgba(255,107,107,.3)", color: "#ff9b9b", fontSize: 12.5 }}>
          {err}
        </div>
      )}

      {loading && !data && <p className="muted">Loading…</p>}

      {data && forms.length === 0 && (
        <div className="cx-emptycard" style={{ marginTop: 12 }}>
          <p className="muted" style={{ margin: 0, fontSize: 13.5 }}>
            No forms raised yet. {canEdit
              ? "Raise one and it appears on forms.adarshabani.in the moment you set it to Open."
              : "An admin, HoD or coordinator can raise one."}
          </p>
        </div>
      )}

      <div style={{ display: "grid", gap: 10 }}>
        {forms.map(form => (
          <div key={form.id} style={{
            border: "1px solid var(--line-soft)", borderRadius: 12, padding: 16,
            background: form.status === "draft" ? "transparent" : "var(--bg-2)",
          }}>
            <div style={{ display: "flex", justifyContent: "space-between", gap: 16, flexWrap: "wrap" }}>
              <div style={{ minWidth: 260, flex: 1 }}>
                <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
                  <span className="pill">{fmKindLabel(meta, form.kind)}</span>
                  <FmFormState form={form}/>
                  {form.closes_at && form.is_open && (
                    <span className="muted" style={{ fontSize: 11 }}>closes {fmDate(form.closes_at)}</span>
                  )}
                </div>
                <h3 style={{ margin: "8px 0 2px", color: "var(--ink-0)", fontSize: 15.5 }}>{form.title}</h3>
                {form.subtitle && <div className="muted" style={{ fontSize: 12.5 }}>{form.subtitle}</div>}
                <div className="muted" style={{ fontSize: 11.5, marginTop: 8, fontFamily: "ui-monospace, monospace" }}>
                  /apply/{form.slug}
                  {form.created_by_name && <span style={{ fontFamily: "inherit" }}> · raised by {form.created_by_name}</span>}
                </div>
              </div>

              <div style={{ textAlign: "right", minWidth: 150 }}>
                <div style={{ fontSize: 26, color: "var(--ink-0)", lineHeight: 1 }}>{form.submission_count || 0}</div>
                <div className="muted" style={{ fontSize: 11, marginTop: 3 }}>
                  {form.submission_count === 1 ? "application" : "applications"}
                  {form.new_count > 0 && (
                    <span style={{ color: "var(--accent)" }}> · {form.new_count} unread</span>
                  )}
                </div>
                {form.last_at && (
                  <div className="muted" style={{ fontSize: 10.5, marginTop: 3 }}>last {fmDateTime(form.last_at)}</div>
                )}
              </div>
            </div>

            <div style={{ display: "flex", gap: 6, flexWrap: "wrap", marginTop: 14, paddingTop: 12, borderTop: "1px solid var(--line-soft)" }}>
              <button className="btn ghost sm" onClick={() => onOpenInbox(form)}>
                <FmIcon name="inbox" size={11}/> Applications
              </button>
              <button className="btn ghost sm" onClick={() => copyLink(form)}>
                <FmIcon name="copy" size={11}/> Copy public link
              </button>
              {canEdit && (
                <>
                  <button className="btn ghost sm" disabled={busy[form.id]} onClick={() => setComposing(form)}>
                    <FmIcon name="edit" size={11}/> Edit
                  </button>
                  {form.status !== "open" && (
                    <button className="btn ghost sm" disabled={busy[form.id]} onClick={() => setStatus(form, "open")}>
                      Open it
                    </button>
                  )}
                  {form.status === "open" && (
                    <button className="btn ghost sm" disabled={busy[form.id]} onClick={() => setStatus(form, "closed")}>
                      Close it
                    </button>
                  )}
                  {form.submission_count === 0 && (
                    <button className="btn ghost sm" disabled={busy[form.id]} onClick={() => remove(form)}
                      style={{ color: "#ff9b9b", borderColor: "rgba(255,107,107,.3)" }}>
                      Delete
                    </button>
                  )}
                </>
              )}
            </div>
          </div>
        ))}
      </div>

      {composing && (
        <FmComposer
          meta={meta}
          initial={composing.id ? composing : null}
          onClose={() => setComposing(null)}
          onSaved={() => { setComposing(null); load(); }}
        />
      )}
    </div>
  );
};

/* ------------------------------------------------------------------
   Tab 2 — the inbox.
   ------------------------------------------------------------------ */

// One submission, expanded. The payload is a bag of keys, so it is rendered
// against the form's own field list — without that the office would be reading
// raw slugs like `present_school`.
const FmSubmissionDetail = ({ id, onChanged, canEdit }) => {
  const [d, setD]       = React.useState(null);
  const [err, setErr]   = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  const [note, setNote] = React.useState("");

  React.useEffect(() => {
    let alive = true;
    window.KXApi.get(`/forms/submissions/${id}`)
      .then(res => { if (!alive) return; setD(res); setNote(res.submission.office_note || ""); })
      .catch(e => { if (alive) setErr(String(e.message || e)); });
    return () => { alive = false; };
  }, [id]);

  if (err) return <p style={{ color: "#ff9b9b", fontSize: 12.5, padding: "10px 4px" }}>{err}</p>;
  if (!d)  return <p className="muted" style={{ fontSize: 12.5, padding: "10px 4px" }}>Loading…</p>;

  const s = d.submission;
  const payload = s.payload || {};
  const allStatuses = [...(d.statuses || []), ...(d.terminal_statuses || [])];

  // Labels for every key the payload might carry: the built-ins for the kind,
  // plus whatever the office added to this particular form.
  const labelled = [
    ...(d.built_in || []),
    ...((d.form?.fields) || []),
  ];

  const patch = async (body) => {
    setBusy(true);
    try { await window.KXApi.patch(`/forms/submissions/${s.id}`, body); onChanged(); }
    catch (e) { window.alert("Could not update: " + (e.message || e)); }
    finally { setBusy(false); }
  };

  const renderValue = (f) => {
    const v = payload[f.key];
    if (f.type === "checkbox") return v ? "Yes" : "No";
    if (v === null || v === undefined || v === "") return "—";
    return String(v);
  };

  return (
    <div style={{ display: "grid", gridTemplateColumns: "1.15fr 1fr", gap: 26, padding: "14px 4px 18px" }}>
      <div>
        {/* Answers, grouped the way the public form groups them. */}
        {Object.entries(
          labelled.reduce((acc, f) => {
            const sec = f.section || "More about the applicant";
            (acc[sec] = acc[sec] || []).push(f);
            return acc;
          }, {})
        ).map(([section, fs]) => (
          <div key={section} style={{ marginBottom: 18 }}>
            <div className="muted" style={{ fontSize: 10.5, textTransform: "uppercase", letterSpacing: ".07em", marginBottom: 8, paddingBottom: 5, borderBottom: "1px solid var(--line-soft)" }}>
              {section}
            </div>
            {fs.map(f => <FmField key={f.key} label={f.label} value={renderValue(f)}/>)}
          </div>
        ))}

        {/* Repeating bio-data tables — academic record, work experience. */}
        {(d.repeaters || []).map(rep => {
          const rows = Array.isArray(payload[rep.key]) ? payload[rep.key] : [];
          if (rows.length === 0) return null;
          return (
            <div key={rep.key} style={{ marginBottom: 18 }}>
              <div className="muted" style={{ fontSize: 10.5, textTransform: "uppercase", letterSpacing: ".07em", marginBottom: 8, paddingBottom: 5, borderBottom: "1px solid var(--line-soft)" }}>
                {rep.label}
              </div>
              <table className="cx-table compact">
                <thead>
                  <tr style={{ color: "var(--ink-3)", textAlign: "left" }}>
                    {rep.columns.map(c => (
                      <th key={c.key} style={{ padding: "4px 8px 4px 0", fontWeight: 500 }}>{c.label}</th>
                    ))}
                  </tr>
                </thead>
                <tbody>
                  {rows.map((r, i) => (
                    <tr key={i} style={{ borderTop: "1px solid var(--line-soft)" }}>
                      {rep.columns.map(c => (
                        <td key={c.key} style={{ padding: "6px 8px 6px 0", color: "var(--ink-1)" }}>{r[c.key] || "—"}</td>
                      ))}
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          );
        })}
      </div>

      <div>
        {/* Attachments. The blob URLs are public by UUID (same as every other
            attachment in this codebase), so a plain link opens them without
            re-sending the token. */}
        {/* The office's copy of the filled form — same document the applicant
            downloads, but with no expiry because this side is authenticated.
            The endpoint is behind the Bearer token, so a plain <a href> would
            come back 401; fetch it with the token and hand over a blob. */}
        <button className="btn ghost sm" style={{ marginBottom: 14 }}
          onClick={async () => {
            try {
              const token = window.KXApi.getToken ? window.KXApi.getToken() : null;
              const r = await fetch(`/api/forms/submissions/${s.id}/receipt.pdf`, {
                headers: token ? { authorization: `Bearer ${token}` } : {},
              });
              if (!r.ok) throw new Error(`download failed (${r.status})`);
              const url = URL.createObjectURL(await r.blob());
              const a = document.createElement("a");
              a.href = url;
              a.download = `${s.ref_no}-adarshabani.pdf`;
              a.click();
              URL.revokeObjectURL(url);
            } catch (e) {
              window.alert("Could not download: " + (e.message || e));
            }
          }}>
          <FmIcon name="report" size={11}/> Download the filled form (PDF)
        </button>

        <div className="muted" style={{ fontSize: 10.5, textTransform: "uppercase", letterSpacing: ".07em", marginBottom: 8 }}>
          Attachments
        </div>
        {(s.attachments || []).length === 0 ? (
          <p className="muted" style={{ fontSize: 12, fontStyle: "italic", marginBottom: 18 }}>Nothing attached.</p>
        ) : (
          <div style={{ display: "grid", gap: 6, marginBottom: 18 }}>
            {s.attachments.map(a => (
              <a key={a.file} href={a.url} target="_blank" rel="noopener noreferrer"
                style={{
                  display: "flex", alignItems: "center", gap: 8, padding: "8px 10px",
                  border: "1px solid var(--line-soft)", borderRadius: 8,
                  color: "var(--ink-1)", fontSize: 12, textDecoration: "none",
                }}>
                <FmIcon name="report" size={13}/>
                <span style={{ flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                  {a.name || a.file}
                </span>
                <span className="muted" style={{ fontSize: 10.5, textTransform: "uppercase" }}>{a.kind}</span>
              </a>
            ))}
          </div>
        )}

        <div className="muted" style={{ fontSize: 10.5, textTransform: "uppercase", letterSpacing: ".07em", marginBottom: 8 }}>
          Move to
        </div>
        {canEdit ? (
          <div style={{ display: "flex", flexWrap: "wrap", gap: 5, marginBottom: 18 }}>
            {allStatuses.map(st => (
              <button key={st.key} className="btn ghost sm"
                disabled={busy || st.key === s.status}
                onClick={() => patch({ status: st.key })}
                style={{
                  opacity: st.key === s.status ? .45 : 1,
                  borderColor: st.key === s.status ? (FM_TINT[st.key]?.fg || "var(--accent)") : "var(--line-soft)",
                }}>
                {st.label}
              </button>
            ))}
          </div>
        ) : (
          <p className="muted" style={{ fontSize: 12, marginBottom: 18 }}>
            Only an admin, HoD or coordinator can move an application along.
          </p>
        )}

        <div className="muted" style={{ fontSize: 10.5, textTransform: "uppercase", letterSpacing: ".07em", marginBottom: 6 }}>
          Note to the applicant
        </div>
        {/* This field is PUBLIC. Whoever holds the reference number and the
            phone number sees it verbatim on forms.adarshabani.in/track — that
            is the point of it, it is how the office tells someone what to
            bring. The warning is here rather than in a comment because the
            person who needs to read it is the clerk typing, not the developer.
            Anything internal must not go in this box. */}
        <div style={{
          display: "flex", gap: 7, alignItems: "flex-start",
          padding: "7px 10px", marginBottom: 8, borderRadius: 7,
          background: "rgba(109,211,255,.08)", border: "1px solid rgba(109,211,255,.25)",
          color: "#9fd9f5", fontSize: 11.5, lineHeight: 1.45,
        }}>
          <span style={{ flexShrink: 0, marginTop: 1 }}><FmIcon name="warn" size={12}/></span>
          <span>
            <strong>The applicant reads this.</strong> It shows on the tracking page
            when they look up their reference number. Keep internal remarks out of it.
          </span>
        </div>
        {canEdit ? (
          <>
            <textarea value={note} onChange={e => setNote(e.target.value)}
              placeholder="Please bring your original documents to the interview."
              style={{ ...fmInput, minHeight: 76, resize: "vertical", fontFamily: "inherit" }}/>
            <button className="btn sm" style={{ marginTop: 8 }} disabled={busy}
              onClick={() => patch({ office_note: note })}>
              Save note
            </button>
          </>
        ) : (
          <p style={{ fontSize: 12.5, color: "var(--ink-1)", whiteSpace: "pre-wrap" }}>{s.office_note || "—"}</p>
        )}

        <div style={{ marginTop: 20, paddingTop: 12, borderTop: "1px solid var(--line-soft)" }}>
          <FmField label="Submitted" value={fmDateTime(s.created_at)}/>
          <FmField label="Last updated" value={fmDateTime(s.updated_at)}/>
          {s.reviewed_by_name && <FmField label="Last handled by" value={`${s.reviewed_by_name} · ${fmDateTime(s.reviewed_at)}`}/>}
        </div>
      </div>
    </div>
  );
};

const FmInboxTab = ({ meta, formFilter, onClearFormFilter }) => {
  const [data, setData]       = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [err, setErr]         = React.useState(null);
  const [kind, setKind]       = React.useState("");
  const [status, setStatus]   = React.useState("");
  const [q, setQ]             = React.useState("");
  const [openId, setOpenId]   = React.useState(null);

  const load = React.useCallback(async () => {
    setLoading(true); setErr(null);
    try {
      const qs = new URLSearchParams();
      if (formFilter) qs.set("form_id", formFilter.id);
      if (kind)   qs.set("kind", kind);
      if (status) qs.set("status", status);
      if (q.trim()) qs.set("q", q.trim());
      setData(await window.KXApi.get(`/forms/submissions?${qs}`));
    } catch (e) { setErr(String(e.message || e)); }
    finally { setLoading(false); }
  }, [formFilter, kind, status, q]);

  // Debounced so typing in the search box doesn't fire a request per keystroke.
  React.useEffect(() => {
    const t = setTimeout(load, q ? 300 : 0);
    return () => clearTimeout(t);
  }, [load, q]);

  const rows = data?.items || [];
  const counts = data?.counts || {};
  const canEdit = data?.can_edit;

  // Which funnel to show as chips. Filtering by one kind (or by a form, whose
  // kind we know) shows that kind's statuses; with a mixed list there is no
  // single ordered funnel, so the union is the honest thing to offer.
  const shownKind = formFilter?.kind || kind;
  const statusesFor = (k) => (meta?.submission_statuses?.[k]) || FM_FALLBACK_STATUSES[k] || [];
  const terminal = meta?.terminal_statuses || FM_FALLBACK_TERMINAL;
  const chipStatuses = shownKind
    ? [...statusesFor(shownKind), ...terminal]
    : [...new Map([
        ...statusesFor("admission"), ...statusesFor("vacancy"), ...terminal,
      ].map(s => [s.key, s])).values()];

  const statusLabel = (key) => chipStatuses.find(s => s.key === key)?.label
    || [...statusesFor("admission"), ...statusesFor("vacancy"), ...terminal].find(s => s.key === key)?.label
    || key;

  // The CSV endpoint is behind the Bearer token, so a plain <a href> would come
  // back 401. Fetch it with the token and hand the browser a blob instead.
  const exportCsv = async () => {
    try {
      const qs = new URLSearchParams();
      if (formFilter) qs.set("form_id", formFilter.id);
      if (kind) qs.set("kind", kind);
      const token = window.KXApi.getToken ? window.KXApi.getToken() : null;
      const r = await fetch(`/api/forms/export.csv?${qs}`, {
        headers: token ? { authorization: `Bearer ${token}` } : {},
      });
      if (!r.ok) throw new Error(`export failed (${r.status})`);
      const url = URL.createObjectURL(await r.blob());
      const a = document.createElement("a");
      a.href = url;
      a.download = `form-submissions-${new Date().toISOString().slice(0, 10)}.csv`;
      a.click();
      URL.revokeObjectURL(url);
    } catch (e) {
      window.alert("Could not export: " + (e.message || e));
    }
  };

  return (
    <div>
      {formFilter && (
        <div style={{
          display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12,
          padding: "10px 14px", marginBottom: 14, borderRadius: 10,
          border: "1px solid var(--accent)", background: "rgba(255,186,90,.07)",
        }}>
          <span style={{ fontSize: 12.5, color: "var(--ink-1)" }}>
            Showing applications to <strong>{formFilter.title}</strong> only.
          </span>
          <button className="btn ghost sm" onClick={onClearFormFilter}>Show all forms</button>
        </div>
      )}

      <div style={{ display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap", marginBottom: 12 }}>
        <div style={{ position: "relative" }}>
          <span style={{ position: "absolute", left: 9, top: 8, color: "var(--ink-3)" }}><FmIcon name="search" size={13}/></span>
          <input value={q} onChange={e => setQ(e.target.value)}
            placeholder="Name, phone, email or what they applied for…"
            style={{ ...fmInput, padding: "7px 10px 7px 28px", width: 320 }}/>
        </div>
        {!formFilter && (
          <select value={kind} onChange={e => setKind(e.target.value)} style={{ ...fmInput, width: "auto" }}>
            <option value="">Both kinds</option>
            <option value="admission">Admission</option>
            <option value="vacancy">Vacancy</option>
          </select>
        )}
        <button className="btn sm" onClick={load}>Refresh</button>
        {canEdit && (
          <button className="btn sm" onClick={exportCsv}><FmIcon name="upload" size={11}/> Export CSV</button>
        )}
      </div>

      {/* Status chips — counts are over the whole form/kind scope, so they stay
          put while you click through them. */}
      <div style={{ display: "flex", gap: 6, flexWrap: "wrap", marginBottom: 14 }}>
        <button className="btn ghost sm" onClick={() => setStatus("")}
          style={{
            borderColor: status === "" ? "var(--accent)" : "var(--line-soft)",
            color: status === "" ? "var(--accent)" : "var(--ink-2)",
          }}>
          All <span style={{ opacity: .6, marginLeft: 4 }}>{data?.total ?? "—"}</span>
        </button>
        {chipStatuses.map(s => (
          <button key={s.key} className="btn ghost sm" onClick={() => setStatus(status === s.key ? "" : s.key)}
            style={{
              borderColor: status === s.key ? (FM_TINT[s.key]?.fg || "var(--accent)") : "var(--line-soft)",
              color: status === s.key ? (FM_TINT[s.key]?.fg || "var(--accent)") : "var(--ink-2)",
            }}>
            {s.label} <span style={{ opacity: .6, marginLeft: 4 }}>{counts[s.key] || 0}</span>
          </button>
        ))}
      </div>

      {err && (
        <div style={{ marginBottom: 14, padding: "10px 14px", borderRadius: 8, background: "rgba(255,107,107,.10)", border: "1px solid rgba(255,107,107,.3)", color: "#ff9b9b", fontSize: 12.5 }}>
          {err}
        </div>
      )}

      {loading && !data && <p className="muted">Loading…</p>}

      {data && rows.length === 0 && !loading && (
        <div className="cx-emptycard" style={{ marginTop: 12 }}>
          <p className="muted" style={{ margin: 0, fontSize: 13.5 }}>
            {q || status || kind || formFilter
              ? "No applications match this filter."
              : "Nothing has been submitted yet. Raise a form and share its public link."}
          </p>
        </div>
      )}

      {rows.length > 0 && (
        <div className="cx-tablecard"><div className="cx-tablescroll">
          <table className="cx-table">
            <thead>
              <tr style={{ background: "var(--bg-2)", color: "var(--ink-2)", textAlign: "left" }}>
                <th style={{ padding: "9px 12px", fontWeight: 500 }}>Ref</th>
                <th style={{ padding: "9px 12px", fontWeight: 500 }}>Applicant</th>
                <th style={{ padding: "9px 12px", fontWeight: 500 }}>Applied for</th>
                <th style={{ padding: "9px 12px", fontWeight: 500 }}>Form</th>
                <th style={{ padding: "9px 12px", fontWeight: 500 }}>Contact</th>
                <th style={{ padding: "9px 12px", fontWeight: 500 }}>Files</th>
                <th style={{ padding: "9px 12px", fontWeight: 500 }}>Status</th>
                <th style={{ padding: "9px 12px", fontWeight: 500 }}>Received</th>
              </tr>
            </thead>
            <tbody>
              {rows.map(r => {
                const open = openId === r.id;
                return (
                  <React.Fragment key={r.id}>
                    <tr onClick={() => setOpenId(open ? null : r.id)}
                      style={{ borderTop: "1px solid var(--line-soft)", cursor: "pointer", background: open ? "var(--bg-2)" : "transparent" }}>
                      <td style={{ padding: "9px 12px", fontFamily: "ui-monospace, monospace", color: "var(--ink-2)" }}>{r.ref_no}</td>
                      <td style={{ padding: "9px 12px", color: "var(--ink-0)" }}>{r.applicant_name}</td>
                      <td style={{ padding: "9px 12px", color: "var(--ink-1)" }}>{r.headline || "—"}</td>
                      <td style={{ padding: "9px 12px", color: "var(--ink-2)" }}>{r.form_title}</td>
                      <td style={{ padding: "9px 12px", fontFamily: "ui-monospace, monospace", color: "var(--ink-2)" }}>
                        {r.contact_phone}
                      </td>
                      <td style={{ padding: "9px 12px", color: "var(--ink-2)" }}>
                        {(r.attachments || []).length || "—"}
                      </td>
                      <td style={{ padding: "9px 12px" }}>
                        <FmPill status={r.status} label={statusLabel(r.status)}/>
                      </td>
                      <td style={{ padding: "9px 12px", color: "var(--ink-3)" }}>{fmDate(r.created_at)}</td>
                    </tr>
                    {open && (
                      <tr style={{ background: "var(--bg-2)" }}>
                        <td colSpan={8} style={{ padding: "0 12px 12px" }}>
                          <FmSubmissionDetail id={r.id} canEdit={canEdit} onChanged={load}/>
                        </td>
                      </tr>
                    )}
                  </React.Fragment>
                );
              })}
            </tbody>
          </table>
          </div>
        </div>
      )}
    </div>
  );
};

/* ------------------------------------------------------------------
   Templates — what a KIND of form asks, before the office adds
   anything to one particular form.
   ------------------------------------------------------------------
   Editing a template changes every form built on it, open ones
   included. That is deliberate: "add a question to the admission form"
   should not mean rebuilding the intake. Two consequences the editor
   makes visible rather than hiding:

     • Answers already submitted are keyed by the field's KEY, not its
       label. Renaming a label is safe. Removing a field hides it from
       the form but leaves the answers in the payload and the CSV.
     • Every template needs one field marked as the applicant's name and
       one as the contact phone. They become real columns on the
       submission — the office's list is keyed on the first, and the
       "track my application" lookup and duplicate-matching on the
       second. The server refuses a template without them.
   ------------------------------------------------------------------ */

// Move an item within a list. Used by every reorder button below.
const fmMove = (list, i, delta) => {
  const j = i + delta;
  if (j < 0 || j >= list.length) return list;
  const next = list.slice();
  [next[i], next[j]] = [next[j], next[i]];
  return next;
};

const FmRowTools = ({ i, list, onChange, onRemove, canRemove = true }) => (
  <div style={{ display: "flex", gap: 4, flexShrink: 0 }}>
    <button className="btn ghost sm" title="Move up" disabled={i === 0}
      onClick={() => onChange(fmMove(list, i, -1))} style={{ padding: "4px 8px" }}>↑</button>
    <button className="btn ghost sm" title="Move down" disabled={i === list.length - 1}
      onClick={() => onChange(fmMove(list, i, 1))} style={{ padding: "4px 8px" }}>↓</button>
    {canRemove && (
      <button className="btn ghost sm" title="Remove" onClick={onRemove} style={{ padding: "4px 8px" }}>
        <FmIcon name="x" size={11}/>
      </button>
    )}
  </div>
);

const FM_CORE_LABEL = {
  "":         "Just a question",
  name:       "The applicant's name",
  phone:      "Contact phone",
  email:      "Contact email",
  headline:   "What they applied for",
};

const FmTemplateEditor = ({ meta, initial, onClose, onSaved }) => {
  const editing = !!initial;
  const [t, setT] = React.useState(() => ({
    name: initial?.name || "",
    bn: initial?.bn || "",
    description: initial?.description || "",
    fields: initial?.fields ? JSON.parse(JSON.stringify(initial.fields)) : [],
    repeaters: initial?.repeaters ? JSON.parse(JSON.stringify(initial.repeaters)) : [],
    attachments: initial?.attachments ? JSON.parse(JSON.stringify(initial.attachments)) : [],
    statuses: initial?.statuses ? JSON.parse(JSON.stringify(initial.statuses)) : [
      { key: "received", label: "Received", bn: "" },
    ],
    submitted_notice: initial?.submitted_notice || "",
    next_steps: initial?.next_steps ? initial.next_steps.slice() : [],
  }));
  const [busy, setBusy] = React.useState(false);
  const [err, setErr]   = React.useState(null);

  const set = (k, v) => setT(prev => ({ ...prev, [k]: v }));
  const setAt = (k, i, patch) =>
    setT(prev => ({ ...prev, [k]: prev[k].map((x, idx) => idx === i ? { ...x, ...patch } : x) }));

  const fieldTypes = meta?.field_types?.length ? meta.field_types
    : ["text", "textarea", "select", "number", "date", "tel", "email", "checkbox"];

  // The section names already in use, so a new question can join an existing
  // group instead of inventing a near-duplicate heading.
  const sections = [...new Set(t.fields.map(f => f.section).filter(Boolean))];

  const addField = () => set("fields", [...t.fields, {
    key: "", label: "", type: "text", required: false, options: [],
    section: sections[sections.length - 1] || "Details", core: "", help: "",
  }]);

  const save = async () => {
    setErr(null);

    // Mirror the server's rules so the message arrives before the round-trip.
    if (!t.name.trim()) { setErr("Give the template a name."); return; }
    if (!t.fields.some(f => f.core === "name")) {
      setErr("Mark one question as the applicant's name — the office's list is keyed on it.");
      return;
    }
    const phone = t.fields.find(f => f.core === "phone");
    if (!phone) {
      setErr("Mark one question as the contact phone — it is how an applicant looks their application up again.");
      return;
    }
    if (phone.type !== "tel") {
      setErr(`“${phone.label || "That question"}” is the contact phone, so its answer type must be tel.`);
      return;
    }
    // A field whose options come from the form itself (an intake's class list)
     // legitimately carries none here — don't demand them.
    const emptySelect = t.fields.find(f =>
      f.type === "select" && !f.options_from && !(f.options || []).some(o => String(o).trim()));
    if (emptySelect) {
      setErr(`“${emptySelect.label || "A multiple-choice question"}” has no options. Add at least one, or change its answer type.`);
      return;
    }
    if (t.statuses.length === 0) { setErr("Add at least one step to the funnel."); return; }

    // Tidy select options ONCE here, for the same reason the form composer
    // does: the textareas keep raw lines while you type so Enter works.
    const body = {
      ...t,
      fields: t.fields.map(f => f.type === "select"
        ? { ...f, options: (f.options || []).map(o => String(o).trim()).filter(Boolean) }
        : f),
      next_steps: (t.next_steps || []).map(x => String(x).trim()).filter(Boolean),
    };

    setBusy(true);
    try {
      if (editing) await window.KXApi.patch(`/forms/templates/${initial.key}`, body);
      else         await window.KXApi.post("/forms/templates", body);
      onSaved();
    } catch (e) {
      setErr(String(e.message || e));
    } finally {
      setBusy(false);
    }
  };

  const label = (txt) => (
    <div className="muted" style={{ fontSize: 10.5, marginBottom: 5 }}>{txt}</div>
  );
  const sectionHead = (title, action) => (
    <div style={{
      display: "flex", alignItems: "center", justifyContent: "space-between",
      marginBottom: 10, marginTop: 20, paddingTop: 16, borderTop: "1px solid var(--line-soft)",
    }}>
      <div className="muted" style={{ fontSize: 10.5, textTransform: "uppercase", letterSpacing: ".07em" }}>
        {title}
      </div>
      {action}
    </div>
  );

  return (
    <FmModal
      width={900}
      title={editing ? `Template · ${initial.name}` : "Build a template"}
      subtitle={editing
        ? "Changes apply to every form built on this template, including ones already open. Answers already submitted are kept — they are stored against each question's key, not its label."
        : "A template is what a kind of form asks. Build one, then raise as many forms on it as you like."}
      onClose={onClose}
      footer={<>
        <button className="btn ghost sm" onClick={onClose} disabled={busy}>Cancel</button>
        <button className="btn sm primary" onClick={save} disabled={busy}>
          {busy ? "Saving…" : editing ? "Save template" : "Create template"}
        </button>
      </>}
    >
      {err && (
        <div style={{
          marginBottom: 14, padding: "9px 12px", borderRadius: 8,
          background: "var(--red-bg)", border: "1px solid var(--red)", color: "var(--red)", fontSize: 12.5,
        }}>{err}</div>
      )}

      {initial?.is_system && (
        <div style={{
          display: "flex", gap: 7, alignItems: "flex-start", marginBottom: 14,
          padding: "8px 11px", borderRadius: 8,
          background: "var(--blue-bg)", border: "1px solid rgba(59,123,246,.28)",
          color: "var(--blue)", fontSize: 11.5, lineHeight: 1.45,
        }}>
          <span style={{ flexShrink: 0, marginTop: 1 }}><FmIcon name="warn" size={12}/></span>
          <span>
            <strong>This is a built-in template.</strong> Edit it freely — it cannot be
            deleted, because every form and application already filed under it
            points here.
          </span>
        </div>
      )}

      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
        <label>
          {label("Name")}
          <input style={fmInput} value={t.name} onChange={e => set("name", e.target.value)}
            placeholder="Transfer certificate request"/>
        </label>
        <label>
          {label("Name in Bengali (optional)")}
          <input style={fmInput} value={t.bn} onChange={e => set("bn", e.target.value)}/>
        </label>
        <label style={{ gridColumn: "1 / -1" }}>
          {label("What this kind of form is for — staff only, not shown to applicants")}
          <textarea style={{ ...fmInput, minHeight: 56, resize: "vertical", fontFamily: "inherit" }}
            value={t.description} onChange={e => set("description", e.target.value)}/>
        </label>
      </div>

      {/* ── Questions ─────────────────────────────────────────────────── */}
      {sectionHead(
        `Questions (${t.fields.length})`,
        <button className="btn ghost sm" onClick={addField}>
          <FmIcon name="plus" size={11}/> Add a question
        </button>,
      )}
      <p className="muted" style={{ margin: "0 0 12px", fontSize: 11.5 }}>
        These appear on every form of this kind, grouped under the section heading
        you give them. Mark one as the applicant’s name and one as the contact phone.
      </p>

      {t.fields.length === 0 && (
        <p className="muted" style={{ fontSize: 12, fontStyle: "italic" }}>
          No questions yet — a template needs at least a name and a phone number.
        </p>
      )}

      {t.fields.map((f, i) => (
        <div key={i} style={{
          border: "1px solid var(--line-soft)", borderRadius: 10, padding: 12, marginBottom: 8,
          background: f.core ? "var(--bg-2)" : "transparent",
        }}>
          <div style={{ display: "grid", gridTemplateColumns: "2fr 1fr auto", gap: 8, alignItems: "end" }}>
            <label>
              {label("Question")}
              <input style={fmInput} value={f.label}
                onChange={e => setAt("fields", i, { label: e.target.value })}
                placeholder="Child's full name"/>
            </label>
            <label>
              {label("Answer type")}
              <select style={fmInput} value={f.type}
                onChange={e => setAt("fields", i, { type: e.target.value })}>
                {fieldTypes.map(ft => <option key={ft} value={ft}>{ft}</option>)}
              </select>
            </label>
            <FmRowTools i={i} list={t.fields} onChange={v => set("fields", v)}
              onRemove={() => set("fields", t.fields.filter((_, idx) => idx !== i))}/>
          </div>

          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr auto", gap: 8, alignItems: "end", marginTop: 8 }}>
            <label>
              {label("Group it under")}
              <input style={fmInput} value={f.section || ""} list={`fm-sections-${i}`}
                onChange={e => setAt("fields", i, { section: e.target.value })}
                placeholder="The child"/>
              <datalist id={`fm-sections-${i}`}>
                {sections.map(sec => <option key={sec} value={sec}/>)}
              </datalist>
            </label>
            <label>
              {label("This answer is…")}
              <select style={fmInput} value={f.core || ""}
                onChange={e => setAt("fields", i, { core: e.target.value })}>
                {Object.entries(FM_CORE_LABEL).map(([k, v]) => (
                  <option key={k} value={k}>{v}</option>
                ))}
              </select>
            </label>
            <label style={{ display: "flex", alignItems: "center", gap: 6, paddingBottom: 8, whiteSpace: "nowrap" }}>
              <input type="checkbox" checked={!!f.required}
                onChange={e => setAt("fields", i, { required: e.target.checked })}/>
              <span style={{ fontSize: 12, color: "var(--ink-2)" }}>Required</span>
            </label>
          </div>

          {f.type === "select" && f.options_from && (
            <div className="muted" style={{
              marginTop: 8, padding: "7px 10px", borderRadius: 8,
              background: "var(--bg-3)", fontSize: 11.5, lineHeight: 1.45,
            }}>
              The choices for this question come from each form, not from the
              template — an intake picks which classes it admits into when it is
              raised. Nothing to set here.
            </div>
          )}

          {f.type === "select" && !f.options_from && (
            <label style={{ display: "block", marginTop: 8 }}>
              {label("Options — one per line")}
              {/* Split only; tidied on save. See the same note on the form
                  composer: filtering blanks here ate the newline as you typed. */}
              <textarea style={{ ...fmInput, minHeight: 58, resize: "vertical", fontFamily: "inherit" }}
                placeholder={"Yes\nNo"}
                value={(f.options || []).join("\n")}
                onChange={e => setAt("fields", i, { options: e.target.value.split("\n") })}/>
            </label>
          )}

          <label style={{ display: "block", marginTop: 8 }}>
            {label("Helper text under the question (optional)")}
            <input style={fmInput} value={f.help || ""}
              onChange={e => setAt("fields", i, { help: e.target.value })}
              placeholder="Write “None” if the child is not enrolled anywhere yet."/>
          </label>

          {f.key && (
            <div className="muted" style={{ fontSize: 10.5, marginTop: 7, fontFamily: "ui-monospace, monospace" }}>
              stored as {f.key} — answers already submitted are filed under this
            </div>
          )}
        </div>
      ))}

      {/* ── Repeating tables ──────────────────────────────────────────── */}
      {sectionHead(
        `Repeating tables (${t.repeaters.length})`,
        <button className="btn ghost sm" onClick={() => set("repeaters", [...t.repeaters, {
          key: "", label: "", addLabel: "Add a row", max: 8,
          columns: [{ key: "", label: "" }],
        }])}>
          <FmIcon name="plus" size={11}/> Add a table
        </button>,
      )}
      <p className="muted" style={{ margin: "0 0 12px", fontSize: 11.5 }}>
        For lists that repeat — an academic record, a work history. The applicant
        gets an “Add a row” button under it.
      </p>

      {t.repeaters.map((r, i) => (
        <div key={i} style={{ border: "1px solid var(--line-soft)", borderRadius: 10, padding: 12, marginBottom: 8 }}>
          <div style={{ display: "grid", gridTemplateColumns: "2fr 1.4fr 70px auto", gap: 8, alignItems: "end" }}>
            <label>
              {label("Table heading")}
              <input style={fmInput} value={r.label}
                onChange={e => setAt("repeaters", i, { label: e.target.value })}
                placeholder="Work experience"/>
            </label>
            <label>
              {label("Button text")}
              <input style={fmInput} value={r.addLabel || ""}
                onChange={e => setAt("repeaters", i, { addLabel: e.target.value })}
                placeholder="Add an employer"/>
            </label>
            <label>
              {label("Max rows")}
              <input style={fmInput} type="number" value={r.max || 8}
                onChange={e => setAt("repeaters", i, { max: e.target.value })}/>
            </label>
            <FmRowTools i={i} list={t.repeaters} onChange={v => set("repeaters", v)}
              onRemove={() => set("repeaters", t.repeaters.filter((_, idx) => idx !== i))}/>
          </div>
          <label style={{ display: "block", marginTop: 8 }}>
            {label("Columns — one per line")}
            <textarea style={{ ...fmInput, minHeight: 58, resize: "vertical", fontFamily: "inherit" }}
              placeholder={"School / organisation\nDesignation\nFrom\nTo"}
              value={(r.columns || []).map(c => c.label).join("\n")}
              onChange={e => setAt("repeaters", i, {
                // Keep each column's existing key by position, so renaming a
                // column heading does not orphan the rows already submitted.
                columns: e.target.value.split("\n").map((lbl, ci) => ({
                  key: (r.columns || [])[ci]?.key || "",
                  label: lbl,
                })),
              })}/>
          </label>
        </div>
      ))}

      {/* ── Attachments ───────────────────────────────────────────────── */}
      {sectionHead(
        `Attachments (${t.attachments.length})`,
        <button className="btn ghost sm" onClick={() => set("attachments", [...t.attachments, {
          key: "", label: "", required: false, help: "",
        }])}>
          <FmIcon name="plus" size={11}/> Add a slot
        </button>,
      )}

      {t.attachments.map((a, i) => (
        <div key={i} style={{ display: "grid", gridTemplateColumns: "1.3fr 1.6fr auto auto", gap: 8,
          alignItems: "end", border: "1px solid var(--line-soft)", borderRadius: 10, padding: 12, marginBottom: 8 }}>
          <label>
            {label("What to attach")}
            <input style={fmInput} value={a.label}
              onChange={e => setAt("attachments", i, { label: e.target.value })}
              placeholder="Bio-data / CV"/>
          </label>
          <label>
            {label("Hint")}
            <input style={fmInput} value={a.help || ""}
              onChange={e => setAt("attachments", i, { help: e.target.value })}
              placeholder="PDF preferred. Up to 10 MB."/>
          </label>
          <label style={{ display: "flex", alignItems: "center", gap: 6, paddingBottom: 8, whiteSpace: "nowrap" }}>
            <input type="checkbox" checked={!!a.required}
              onChange={e => setAt("attachments", i, { required: e.target.checked })}/>
            <span style={{ fontSize: 12, color: "var(--ink-2)" }}>Required</span>
          </label>
          <FmRowTools i={i} list={t.attachments} onChange={v => set("attachments", v)}
            onRemove={() => set("attachments", t.attachments.filter((_, idx) => idx !== i))}/>
        </div>
      ))}

      {/* ── What the applicant is told on submitting ──────────────────── */}
      {sectionHead("After they submit")}
      <p className="muted" style={{ margin: "0 0 12px", fontSize: 11.5 }}>
        The confirmation screen, in your words. This is where a family is told
        that an admission form registers <em>intent</em> and that the payment link
        follows once the process opens — say it here and it can never go stale in
        the app’s code.
      </p>

      <label style={{ display: "block", marginBottom: 12 }}>
        {label("Highlighted notice — the one thing they must not misread (optional)")}
        <textarea style={{ ...fmInput, minHeight: 54, resize: "vertical", fontFamily: "inherit" }}
          value={t.submitted_notice}
          onChange={e => set("submitted_notice", e.target.value)}
          placeholder="This form registers your INTENT to apply. It is not an admission, and no seat is held by it yet."/>
      </label>

      <label style={{ display: "block", marginBottom: 8 }}>
        {label("What happens next — one step per line, shown as a numbered list")}
        {/* Split only; blanks dropped on save. Same reason as every other
            multi-line box here: filtering while typing eats the newline. */}
        <textarea style={{ ...fmInput, minHeight: 110, resize: "vertical", fontFamily: "inherit" }}
          value={(t.next_steps || []).join("\n")}
          onChange={e => set("next_steps", e.target.value.split("\n"))}
          placeholder={"The office has your details. Nothing more is needed from you right now.\nWhen the admission process opens, someone will call you on the number you gave.\nYou will then be sent the payment link for the admission fee."}/>
      </label>

      {/* ── Funnel ────────────────────────────────────────────────────── */}
      {sectionHead(
        `Funnel (${t.statuses.length} steps)`,
        <button className="btn ghost sm" onClick={() => set("statuses", [...t.statuses, { key: "", label: "", bn: "" }])}>
          <FmIcon name="plus" size={11}/> Add a step
        </button>,
      )}
      <p className="muted" style={{ margin: "0 0 12px", fontSize: 11.5 }}>
        The steps the office moves an application through, in order. The applicant
        sees this as a progress rail when they track their reference number.
        “Not selected” and “Withdrawn” are always available and are not listed here.
      </p>

      {t.statuses.map((st, i) => (
        <div key={i} style={{ display: "grid", gridTemplateColumns: "24px 1.3fr 1.3fr auto", gap: 8,
          alignItems: "end", marginBottom: 8 }}>
          <div className="muted" style={{ fontSize: 12, paddingBottom: 9, textAlign: "right" }}>{i + 1}</div>
          <label>
            {i === 0 && label("Step")}
            <input style={fmInput} value={st.label}
              onChange={e => setAt("statuses", i, { label: e.target.value })}
              placeholder="Called for interview"/>
          </label>
          <label>
            {i === 0 && label("In Bengali (shown to the applicant)")}
            <input style={fmInput} value={st.bn || ""}
              onChange={e => setAt("statuses", i, { bn: e.target.value })}/>
          </label>
          <FmRowTools i={i} list={t.statuses} onChange={v => set("statuses", v)}
            onRemove={() => set("statuses", t.statuses.filter((_, idx) => idx !== i))}/>
        </div>
      ))}
    </FmModal>
  );
};

const FmTemplatesTab = ({ onChanged }) => {
  const [data, setData]   = React.useState(null);
  const [err, setErr]     = React.useState(null);
  const [editing, setEditing] = React.useState(null);   // null | {} | template
  const [busy, setBusy]   = React.useState({});

  const load = React.useCallback(async () => {
    setErr(null);
    try { setData(await window.KXApi.get("/forms/templates")); }
    catch (e) { setErr(String(e.message || e)); }
  }, []);
  React.useEffect(() => { load(); }, [load]);

  const canEdit = data?.can_edit;
  const templates = data?.templates || [];

  const setArchived = async (tpl, archived) => {
    setBusy(b => ({ ...b, [tpl.key]: true }));
    try { await window.KXApi.patch(`/forms/templates/${tpl.key}`, { archived }); await load(); onChanged?.(); }
    catch (e) { window.alert(e.message || String(e)); }
    finally { setBusy(b => { const n = { ...b }; delete n[tpl.key]; return n; }); }
  };

  const remove = async (tpl) => {
    if (!window.confirm(`Delete the “${tpl.name}” template? This cannot be undone.`)) return;
    setBusy(b => ({ ...b, [tpl.key]: true }));
    try { await window.KXApi.del(`/forms/templates/${tpl.key}`); await load(); onChanged?.(); }
    catch (e) { window.alert(e.message || String(e)); }
    finally { setBusy(b => { const n = { ...b }; delete n[tpl.key]; return n; }); }
  };

  return (
    <div>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 12, marginBottom: 14 }}>
        <p className="muted" style={{ margin: 0, fontSize: 12.5, maxWidth: 720 }}>
          A template is what a <strong style={{ color: "var(--ink-1)" }}>kind</strong> of form asks —
          the questions, the repeating tables, the files, and the funnel the office
          moves applications along. Edit the built-in ones, or build your own.
        </p>
        <div style={{ display: "flex", gap: 8 }}>
          <button className="btn sm" onClick={load}>Refresh</button>
          {canEdit && (
            <button className="btn sm primary" onClick={() => setEditing({})}>
              <FmIcon name="plus" size={11}/> Build a template
            </button>
          )}
        </div>
      </div>

      {err && (
        <div style={{ marginBottom: 14, padding: "10px 14px", borderRadius: 8,
          background: "var(--red-bg)", border: "1px solid var(--red)", color: "var(--red)", fontSize: 12.5 }}>
          {err}
        </div>
      )}

      {!data && !err && <p className="muted">Loading…</p>}

      <div style={{ display: "grid", gap: 10 }}>
        {templates.map(tpl => (
          <div key={tpl.key} style={{
            border: "1px solid var(--line-soft)", borderRadius: 12, padding: 16,
            background: tpl.archived ? "transparent" : "var(--bg-2)",
            opacity: tpl.archived ? 0.65 : 1,
          }}>
            <div style={{ display: "flex", justifyContent: "space-between", gap: 16, flexWrap: "wrap" }}>
              <div style={{ minWidth: 260, flex: 1 }}>
                <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
                  {tpl.is_system
                    ? <span className="pill">Built in</span>
                    : <span className="pill">Custom</span>}
                  {tpl.archived && <span className="pill">Archived</span>}
                </div>
                <h3 style={{ margin: "8px 0 2px", color: "var(--ink-0)", fontSize: 15.5 }}>{tpl.name}</h3>
                {tpl.description && (
                  <div className="muted" style={{ fontSize: 12.5, maxWidth: 620 }}>{tpl.description}</div>
                )}
                <div className="muted" style={{ fontSize: 11.5, marginTop: 8 }}>
                  {tpl.fields.length} question{tpl.fields.length === 1 ? "" : "s"}
                  {tpl.repeaters.length > 0 && ` · ${tpl.repeaters.length} repeating table${tpl.repeaters.length === 1 ? "" : "s"}`}
                  {tpl.attachments.length > 0 && ` · ${tpl.attachments.length} attachment${tpl.attachments.length === 1 ? "" : "s"}`}
                  {` · ${tpl.statuses.length}-step funnel`}
                  <span style={{ fontFamily: "ui-monospace, monospace" }}> · {tpl.key}</span>
                </div>
              </div>
              <div style={{ textAlign: "right", minWidth: 120 }}>
                <div style={{ fontSize: 26, color: "var(--ink-0)", lineHeight: 1 }}>{tpl.in_use || 0}</div>
                <div className="muted" style={{ fontSize: 11, marginTop: 3 }}>
                  {tpl.in_use === 1 ? "form uses it" : "forms use it"}
                </div>
              </div>
            </div>

            <div style={{ display: "flex", gap: 6, flexWrap: "wrap", marginTop: 14, paddingTop: 12,
              borderTop: "1px solid var(--line-soft)" }}>
              <button className="btn ghost sm" onClick={() => setEditing(tpl)}>
                <FmIcon name="edit" size={11}/> {canEdit ? "Edit questions" : "View questions"}
              </button>
              {canEdit && (
                <>
                  <button className="btn ghost sm" disabled={busy[tpl.key]}
                    onClick={() => setArchived(tpl, !tpl.archived)}>
                    {tpl.archived ? "Un-archive" : "Archive"}
                  </button>
                  {!tpl.is_system && (tpl.in_use || 0) === 0 && (
                    <button className="btn ghost sm" disabled={busy[tpl.key]} onClick={() => remove(tpl)}
                      style={{ color: "var(--red)", borderColor: "var(--red)" }}>
                      Delete
                    </button>
                  )}
                </>
              )}
            </div>
          </div>
        ))}
      </div>

      {editing && (
        <FmTemplateEditor
          meta={data}
          initial={editing.key ? editing : null}
          onClose={() => setEditing(null)}
          onSaved={() => { setEditing(null); load(); onChanged?.(); }}
        />
      )}
    </div>
  );
};

/* ------------------------------------------------------------------
   Screen root.
   ------------------------------------------------------------------ */

const FormsScreen = () => {
  const [tab, setTab]   = React.useState("forms");
  const [meta, setMeta] = React.useState(null);
  const [formFilter, setFormFilter] = React.useState(null);

  const loadMeta = React.useCallback(() => {
    window.KXApi.get("/forms/meta")
      .then(setMeta)
      .catch(() => { /* the tabs fall back to their inline vocabularies */ });
  }, []);
  React.useEffect(() => { loadMeta(); }, [loadMeta]);

  // Arriving from a "new application" notification lands on the inbox rather
  // than the forms list — the bell said there was something to read.
  React.useEffect(() => {
    if (window.KX?.FORMS_CONTEXT?.tab) {
      setTab(window.KX.FORMS_CONTEXT.tab);
      delete window.KX.FORMS_CONTEXT;
    }
  }, []);

  const openInbox = (form) => { setFormFilter(form); setTab("inbox"); };

  return (
    <div style={{ overflow: "auto", padding: "24px 28px", height: "100%" }}>
      <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 4 }}>
        <span style={{ color: "var(--accent)" }}><FmIcon name="inbox" size={18}/></span>
        <div className="muted" style={{ fontSize: 11, letterSpacing: ".08em", textTransform: "uppercase" }}>
          Forms Adarshabani
        </div>
      </div>

      <h1 style={{ margin: "4px 0 6px", color: "var(--ink-0)", fontFamily: "'Instrument Serif', serif", fontWeight: 400, fontSize: 32, letterSpacing: "-0.01em" }}>
        The form desk
      </h1>
      <p className="muted" style={{ margin: "0 0 18px", fontSize: 13.5, maxWidth: 780 }}>
        Raise an admission intake or a vacancy, share its link, and read what
        comes back. Change what a kind of form asks — or build a new kind — under
        Templates. The public side lives at{" "}
        <strong style={{ color: "var(--ink-1)" }}>forms.adarshabani.in</strong>.
      </p>

      <div style={{ display: "flex", gap: 6, marginBottom: 18, borderBottom: "1px solid var(--line-soft)", paddingBottom: 12 }}>
        {[
          { key: "forms", label: "Forms" },
          { key: "inbox", label: "Applications" },
          { key: "templates", label: "Templates" },
        ].map(t => (
          <button key={t.key} className="btn ghost sm"
            onClick={() => { setTab(t.key); if (t.key === "forms") setFormFilter(null); }}
            style={{
              borderColor: tab === t.key ? "var(--accent)" : "var(--line-soft)",
              color: tab === t.key ? "var(--accent)" : "var(--ink-2)",
            }}>
            {t.label}
          </button>
        ))}
      </div>

      {tab === "forms"
        ? <FmFormsTab meta={meta} onOpenInbox={openInbox}/>
        : tab === "templates"
        // Editing a template changes the vocabularies the other two tabs render
        // (kind labels, funnel chips), so refetch /forms/meta when one is saved.
        ? <FmTemplatesTab onChanged={loadMeta}/>
        : <FmInboxTab meta={meta} formFilter={formFilter} onClearFormFilter={() => setFormFilter(null)}/>}
    </div>
  );
};

window.FormsScreen = FormsScreen;
