// screen_admin.jsx — Administration: onboard companies (tenants) and the user
// accounts that sign in to them. Admin-only (gated in app.jsx AND server-side —
// every /api/admin route refuses non-admin tokens with a 403).
// New users start unverified: their first sign-in triggers the e-mail OTP flow.

// Value strings follow the existing company_type convention in the DB ("oem",
// "tier1", …) — the meta endpoint's Brands dropdown filters on lower() = 'oem'.
const ADMIN_COMPANY_TYPES = [
  ["oem", "OEM"],
  ["tier1", "Tier-1 supplier"],
  ["tier2", "Tier-2 supplier"],
  ["fleet", "Fleet operator"],
  ["iot", "IoT vendor"],
  ["other", "Other"],
];

// Password strength check — mirrors the server-side PasswordPolicy so the admin
// gets instant feedback. The backend re-validates and is authoritative; this is
// purely UX. Returns an error string, or "" when the password is strong enough.
const PW_MIN_LENGTH = 8;
function checkPasswordStrength(pw, username) {
  if (!pw) return "Password is required.";
  const missing = [];
  if (pw.length < PW_MIN_LENGTH) missing.push(`at least ${PW_MIN_LENGTH} characters`);
  if (!/[A-Z]/.test(pw)) missing.push("an uppercase letter");
  if (!/[a-z]/.test(pw)) missing.push("a lowercase letter");
  if (!/[0-9]/.test(pw)) missing.push("a number");
  if (!/[^A-Za-z0-9]/.test(pw)) missing.push("a symbol (e.g. ! @ # $ %)");
  if (missing.length) {
    const list = missing.length === 1 ? missing[0]
      : missing.slice(0, -1).join(", ") + " and " + missing[missing.length - 1];
    return "Password must contain " + list + ".";
  }
  if (username && username.trim() &&
      pw.toLowerCase().includes(username.trim().toLowerCase()))
    return "Password must not contain the username.";
  return "";
}

// Friendly label for a stored company_type value ("tier1" → "Tier-1 supplier").
function companyTypeLabel(t) {
  const hit = ADMIN_COMPANY_TYPES.find(([v]) => v === (t || "").toLowerCase());
  return hit ? hit[1] : (t || "—");
}

// One inline form feedback line (error or success) under a card's form.
function AdminNote({ err, ok }) {
  if (!err && !ok) return null;
  return err ? (
    <div className="login-err"><Icon name="alert" size={14} />{err}</div>
  ) : (
    <div className="admin-ok"><Icon name="check" size={14} />{ok}</div>
  );
}

function AdminScreen() {
  const companiesApi = useApi((signal) => window.VCApi.adminCompanies(signal));
  const usersApi = useApi((signal) => window.VCApi.adminUsers(signal));
  const companies = companiesApi.data || [];
  const users = usersApi.data || [];

  // ---- add-company form ----
  const [cName, setCName] = useState("");
  const [cType, setCType] = useState(ADMIN_COMPANY_TYPES[0][0]);
  const [cBusy, setCBusy] = useState(false);
  const [cErr, setCErr] = useState("");
  const [cOk, setCOk] = useState("");

  // ---- add-user form ----
  const [uName, setUName] = useState("");
  const [uEmail, setUEmail] = useState("");
  const [uPw, setUPw] = useState("");
  const [uShow, setUShow] = useState(false);
  const [uRole, setURole] = useState("user");
  const [uCompany, setUCompany] = useState("");
  const [uBusy, setUBusy] = useState(false);
  const [uErr, setUErr] = useState("");
  const [uOk, setUOk] = useState("");

  // Keep the user form's company select pointing at a real company once they load.
  useEffect(() => {
    if (!uCompany && companies.length) setUCompany(String(companies[0].companyId));
  }, [companies]);

  function refetchAll() {
    companiesApi.refetch(); // user counts change when a user is added too
    usersApi.refetch();
  }

  function addCompany(e) {
    e.preventDefault();
    setCErr(""); setCOk("");
    if (!cName.trim()) { setCErr("Enter the company name."); return; }
    setCBusy(true);
    window.VCApi.adminCreateCompany({ companyName: cName.trim(), companyType: cType })
      .then((created) => {
        setCBusy(false);
        setCName("");
        setCOk(`${created.companyName} onboarded (id ${created.companyId}).`);
        setUCompany(String(created.companyId)); // next user probably belongs to it
        refetchAll();
      })
      .catch((error) => {
        setCBusy(false);
        setCErr((error && error.message) || "Could not create the company.");
      });
  }

  function addUser(e) {
    e.preventDefault();
    setUErr(""); setUOk("");
    if (!uName.trim()) { setUErr("Enter a username."); return; }
    if (!uEmail.trim() || uEmail.indexOf("@") < 1) { setUErr("Enter a valid e-mail — the first-login code is sent there."); return; }
    { const pw = checkPasswordStrength(uPw, uName); if (pw) { setUErr(pw); return; } }
    if (!uCompany) { setUErr("Pick the company this user belongs to."); return; }
    setUBusy(true);
    window.VCApi.adminCreateUser({
      username: uName.trim(),
      email: uEmail.trim(),
      password: uPw,
      type: uRole,
      companyId: Number(uCompany),
    })
      .then((created) => {
        setUBusy(false);
        setUName(""); setUEmail(""); setUPw(""); setURole("user");
        setUOk(`${created.username} created — they'll get an e-mail code on first sign-in.`);
        refetchAll();
      })
      .catch((error) => {
        setUBusy(false);
        setUErr((error && error.message) || "Could not create the user.");
      });
  }

  const loading = companiesApi.loading || usersApi.loading;
  const loadError = companiesApi.error || usersApi.error;
  if (loading && !companiesApi.data && !usersApi.data)
    return <PageLoading label="Loading companies and users…" />;
  if (loadError && !companiesApi.data && !usersApi.data)
    return <PageError message={loadError} onRetry={refetchAll} />;

  return (
    <div className="page">
      <div className="page-head">
        <div>
          <span className="eyebrow">Administration</span>
          <h1 className="page-title">Companies &amp; users</h1>
          <p className="page-desc">Onboard a tenant company, then create the accounts that sign in to it. Analysts only ever see their own company's SBOM matches.</p>
        </div>
      </div>

      <div className="admin-grid">
        {/* ------------------------------------------------------ companies */}
        <section className="card admin-card">
          <div className="card-head">
            <h3><Icon name="layers" size={16} /> Companies</h3>
            <span className="card-sub">{companies.length} tenant{companies.length === 1 ? "" : "s"}</span>
          </div>

          <form className="admin-form" onSubmit={addCompany}>
            <label className="field admin-span2">
              <span className="field-lab">Company name</span>
              <div className="field-wrap">
                <Icon name="layers" size={16} className="field-ic" />
                <input value={cName} onChange={(e) => setCName(e.target.value)}
                  placeholder="e.g. Acme Motors" spellCheck="false" />
              </div>
            </label>
            <label className="field">
              <span className="field-lab">Type</span>
              <div className="field-wrap">
                <select value={cType} onChange={(e) => setCType(e.target.value)}>
                  {ADMIN_COMPANY_TYPES.map(([v, label]) => <option key={v} value={v}>{label}</option>)}
                </select>
              </div>
            </label>
            <div className="admin-form-act">
              <button className="btn btn-accent" type="submit" disabled={cBusy}>
                {cBusy ? <span className="btn-spin" /> : <><Icon name="plus" size={15} />Add company</>}
              </button>
            </div>
            <div className="admin-span2"><AdminNote err={cErr} ok={cOk} /></div>
          </form>

          {companies.length === 0 ? (
            <div className="empty">
              <Icon name="layers" size={24} />
              <p>No companies yet</p>
              <span className="empty-sub">Onboard the first tenant with the form above.</span>
            </div>
          ) : (
            <div className="admintbl-wrap">
              <table className="admintbl">
                <thead>
                  <tr><th>Company</th><th>Type</th><th className="admintbl-num">Users</th><th className="admintbl-num">ID</th></tr>
                </thead>
                <tbody>
                  {companies.map((c) => (
                    <tr key={c.companyId}>
                      <td>
                        <span className="admintbl-id">
                          <span className="entcard-avatar">{(c.companyName || "?").split(" ").map((w) => w[0]).join("").slice(0, 2)}</span>
                          <strong>{c.companyName || "—"}</strong>
                        </span>
                      </td>
                      <td>{companyTypeLabel(c.companyType)}</td>
                      <td className="admintbl-num">{c.userCount}</td>
                      <td className="admintbl-num mono">{c.companyId}</td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          )}
        </section>

        {/* ---------------------------------------------------------- users */}
        <section className="card admin-card">
          <div className="card-head">
            <h3><Icon name="user" size={16} /> Users</h3>
            <span className="card-sub">{users.length} account{users.length === 1 ? "" : "s"}</span>
          </div>

          <form className="admin-form" onSubmit={addUser}>
            <label className="field">
              <span className="field-lab">Username</span>
              <div className="field-wrap">
                <Icon name="user" size={16} className="field-ic" />
                <input value={uName} onChange={(e) => setUName(e.target.value)}
                  placeholder="analyst handle" spellCheck="false" autoComplete="off" />
              </div>
            </label>
            <label className="field">
              <span className="field-lab">E-mail</span>
              <div className="field-wrap">
                <Icon name="bell" size={16} className="field-ic" />
                <input type="email" value={uEmail} onChange={(e) => setUEmail(e.target.value)}
                  placeholder="name@company.com" spellCheck="false" autoComplete="off" />
              </div>
            </label>
            <label className="field">
              <span className="field-lab">Temporary password</span>
              <div className="field-wrap">
                <Icon name="shield" size={16} className="field-ic" />
                <input type={uShow ? "text" : "password"} value={uPw} onChange={(e) => setUPw(e.target.value)}
                  placeholder="8+ chars, upper, lower, number & symbol" autoComplete="new-password" />
                <button type="button" className="field-toggle" onClick={() => setUShow((s) => !s)}>
                  {uShow ? "Hide" : "Show"}
                </button>
              </div>
            </label>
            <label className="field">
              <span className="field-lab">Role</span>
              <div className="field-wrap">
                <select value={uRole} onChange={(e) => setURole(e.target.value)}>
                  <option value="user">Security analyst</option>
                  <option value="admin">Administrator (all tenants)</option>
                </select>
              </div>
            </label>
            <label className="field">
              <span className="field-lab">Company</span>
              <div className="field-wrap">
                <select value={uCompany} onChange={(e) => setUCompany(e.target.value)} disabled={!companies.length}>
                  {!companies.length && <option value="">Onboard a company first</option>}
                  {companies.map((c) => (
                    <option key={c.companyId} value={String(c.companyId)}>{c.companyName || `Company ${c.companyId}`}</option>
                  ))}
                </select>
              </div>
            </label>
            <div className="admin-form-act">
              <button className="btn btn-accent" type="submit" disabled={uBusy || !companies.length}>
                {uBusy ? <span className="btn-spin" /> : <><Icon name="plus" size={15} />Add user</>}
              </button>
            </div>
            <div className="admin-span2"><AdminNote err={uErr} ok={uOk} /></div>
          </form>

          {users.length === 0 ? (
            <div className="empty">
              <Icon name="user" size={24} />
              <p>No users yet</p>
              <span className="empty-sub">Create the first account with the form above.</span>
            </div>
          ) : (
            <div className="admintbl-wrap">
              <table className="admintbl">
                <thead>
                  <tr><th>User</th><th>Role</th><th>Company</th><th>Status</th><th className="admintbl-num">Created</th></tr>
                </thead>
                <tbody>
                  {users.map((u) => (
                    <tr key={u.userId}>
                      <td>
                        <span className="admintbl-user">
                          <strong>{u.username}</strong>
                          <span className="admintbl-sub">{u.email || "—"}</span>
                        </span>
                      </td>
                      <td>
                        <span className={"admin-role" + (u.type === "admin" ? " is-admin" : "")}>
                          {u.type === "admin" ? "Admin" : "Analyst"}
                        </span>
                      </td>
                      <td>{u.companyName || `#${u.companyId}`}</td>
                      <td>
                        <span className={"admin-verify" + (u.isVerified ? " is-ok" : "")}>
                          <span className="chip-dot" />{u.isVerified ? "Verified" : "Awaiting OTP"}
                        </span>
                      </td>
                      <td className="admintbl-num mono">{u.createdAt || "—"}</td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          )}
        </section>
      </div>

      <TaxonomyManager />
    </div>
  );
}

// ---------------------------------------------------------------------------
// Classification taxonomy editor. The OEM/vendor derivation rules, triage
// statuses and CVSS severity bands used to be hard-coded in the backend; they now
// live in ThreatCore tables and are edited here. Every save re-attributes the
// catalog (server evicts its cache), so the ledger/browse pivots reflect edits.
// ---------------------------------------------------------------------------
const TAX_TABS = [
  ["oems", "OEM marques"],
  ["vendors", "Vendor rules"],
  ["statuses", "Triage statuses"],
  ["bands", "Severity bands"],
];

// Comma-separated text <-> string[] for the needle/token list fields.
const csvToList = (s) => (s || "").split(",").map((x) => x.trim()).filter(Boolean);
const listToCsv = (a) => (a || []).join(", ");

function TaxonomyManager() {
  const taxApi = useApi((signal) => window.VCApi.adminTaxonomy(signal));
  const tax = taxApi.data || { oems: [], vendors: [], statuses: [], severityBands: [] };
  const [tab, setTab] = useState("oems");
  const [msg, setMsg] = useState(null); // { err } | { ok }
  const [busy, setBusy] = useState(false);

  // Run a write, surface the outcome, refetch on success. Resolves true on success
  // and false on failure so callers only clear their form when the save took.
  function run(promise, okMsg) {
    setBusy(true); setMsg(null);
    return promise
      .then(() => { setBusy(false); setMsg({ ok: okMsg }); taxApi.refetch(); return true; })
      .catch((e) => { setBusy(false); setMsg({ err: (e && e.message) || "Save failed." }); return false; });
  }
  function del(promise, okMsg) {
    setMsg(null);
    return promise
      .then(() => { setMsg({ ok: okMsg }); taxApi.refetch(); })
      .catch((e) => { setMsg({ err: (e && e.message) || "Delete failed." }); });
  }

  if (taxApi.loading && !taxApi.data) return (
    <section className="card admin-card admin-span-full"><PageLoading label="Loading taxonomy…" /></section>
  );

  return (
    <section className="card admin-card admin-span-full">
      <div className="card-head">
        <h3><Icon name="sparkle" size={16} /> Classification taxonomy</h3>
        <span className="card-sub">OEM &amp; vendor rules, statuses, severity bands</span>
      </div>
      <p className="page-desc" style={{ marginTop: 0 }}>
        Edit how findings are classified — no redeploy. Changes re-attribute the whole
        catalog on the next load. OEM/vendor needles match the (lower-cased) company or
        product text: a needle with a space or hyphen is matched as a substring, a single
        word is matched as a whole token.
      </p>

      <div className="tax-tabs">
        {TAX_TABS.map(([id, label]) => (
          <button key={id} type="button"
            className={"tax-tab" + (tab === id ? " is-on" : "")}
            onClick={() => { setTab(id); setMsg(null); }}>
            {label}
            <span className="tax-tab-count">{((id === "bands" ? tax.severityBands : tax[id]) || []).length}</span>
          </button>
        ))}
      </div>

      <div className="admin-span-full"><AdminNote err={msg && msg.err} ok={msg && msg.ok} /></div>

      {tab === "oems" && <OemEditor oems={tax.oems} busy={busy} run={run} del={del} />}
      {tab === "vendors" && <VendorEditor vendors={tax.vendors} busy={busy} run={run} del={del} />}
      {tab === "statuses" && <StatusEditor statuses={tax.statuses} busy={busy} run={run} del={del} />}
      {tab === "bands" && <BandEditor bands={tax.severityBands} busy={busy} run={run} del={del} />}
    </section>
  );
}

// ---- OEM marque editor ------------------------------------------------------
function OemEditor({ oems, busy, run, del }) {
  const blank = { canonical: "", needles: "", disqualifiers: "", supersedes: "", proseUnsafe: false, sortOrder: 0 };
  const [f, setF] = useState(blank);
  const set = (k, v) => setF((p) => ({ ...p, [k]: v }));
  const editing = oems.some((o) => o.canonical === f.canonical);

  function loadRow(o) {
    setF({
      canonical: o.canonical,
      needles: listToCsv(o.needles),
      disqualifiers: listToCsv(o.disqualifiers),
      supersedes: listToCsv(o.supersedes),
      proseUnsafe: !!o.proseUnsafe,
      sortOrder: o.sortOrder || 0,
    });
  }
  function save(e) {
    e.preventDefault();
    if (!f.canonical.trim() || csvToList(f.needles).length === 0) return;
    run(window.VCApi.adminUpsertOem({
      canonical: f.canonical.trim(),
      needles: csvToList(f.needles),
      disqualifiers: csvToList(f.disqualifiers),
      supersedes: csvToList(f.supersedes),
      proseUnsafe: !!f.proseUnsafe,
      sortOrder: Number(f.sortOrder) || 0,
    }), `Saved marque “${f.canonical.trim()}”.`).then((ok) => { if (ok) setF(blank); });
  }

  return (
    <div className="tax-panel">
      <form className="admin-form tax-form" onSubmit={save}>
        <label className="field">
          <span className="field-lab">Canonical name</span>
          <div className="field-wrap"><input value={f.canonical} onChange={(e) => set("canonical", e.target.value)} placeholder="e.g. Porsche" spellCheck="false" /></div>
        </label>
        <label className="field">
          <span className="field-lab">Sort order</span>
          <div className="field-wrap"><input type="number" value={f.sortOrder} onChange={(e) => set("sortOrder", e.target.value)} /></div>
        </label>
        <label className="field admin-span2">
          <span className="field-lab">Needles (comma-separated)</span>
          <div className="field-wrap"><input value={f.needles} onChange={(e) => set("needles", e.target.value)} placeholder="porsche, 911" spellCheck="false" /></div>
        </label>
        <label className="field">
          <span className="field-lab">Disqualifiers</span>
          <div className="field-wrap"><input value={f.disqualifiers} onChange={(e) => set("disqualifiers", e.target.value)} placeholder="design, consulting" spellCheck="false" /></div>
        </label>
        <label className="field">
          <span className="field-lab">Supersedes</span>
          <div className="field-wrap"><input value={f.supersedes} onChange={(e) => set("supersedes", e.target.value)} placeholder="another marque" spellCheck="false" /></div>
        </label>
        <label className="field tax-check">
          <input type="checkbox" checked={f.proseUnsafe} onChange={(e) => set("proseUnsafe", e.target.checked)} />
          <span className="field-lab">Prose-unsafe (skip in free-text matching)</span>
        </label>
        <div className="admin-form-act">
          {editing && <button type="button" className="btn btn-ghost" onClick={() => setF(blank)}>Cancel</button>}
          <button className="btn btn-accent" type="submit" disabled={busy}>
            {busy ? <span className="btn-spin" /> : <><Icon name="check" size={15} />{editing ? "Update" : "Add"} marque</>}
          </button>
        </div>
      </form>

      <div className="admintbl-wrap">
        <table className="admintbl">
          <thead><tr><th>Marque</th><th>Needles</th><th>Flags</th><th className="admintbl-num"></th></tr></thead>
          <tbody>
            {oems.map((o) => (
              <tr key={o.canonical} className={f.canonical === o.canonical ? "is-editing" : ""}>
                <td><strong>{o.canonical}</strong></td>
                <td><span className="tax-needles">{listToCsv(o.needles)}</span>
                  {(o.disqualifiers.length > 0 || o.supersedes.length > 0) &&
                    <span className="tax-sub">{o.disqualifiers.length > 0 ? "not: " + listToCsv(o.disqualifiers) : ""}{o.supersedes.length > 0 ? "  ⤳ " + listToCsv(o.supersedes) : ""}</span>}
                </td>
                <td>{o.proseUnsafe ? <span className="tax-flag">prose-unsafe</span> : ""}</td>
                <td className="admintbl-num tax-actions">
                  <button className="btn btn-ghost btn-sm" onClick={() => loadRow(o)} type="button"><Icon name="pencil" size={14} /></button>
                  <button className="btn btn-ghost btn-sm tax-del" onClick={() => del(window.VCApi.adminDeleteOem(o.canonical), `Deleted “${o.canonical}”.`)} type="button"><Icon name="trash" size={14} /></button>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

// ---- Vendor rule editor -----------------------------------------------------
function VendorEditor({ vendors, busy, run, del }) {
  const blank = { vendor: "", needles: "", sortOrder: 0 };
  const [f, setF] = useState(blank);
  const set = (k, v) => setF((p) => ({ ...p, [k]: v }));
  const editing = vendors.some((v) => v.vendor === f.vendor);

  function save(e) {
    e.preventDefault();
    if (!f.vendor.trim() || csvToList(f.needles).length === 0) return;
    run(window.VCApi.adminUpsertVendor({
      vendor: f.vendor.trim(), needles: csvToList(f.needles), sortOrder: Number(f.sortOrder) || 0,
    }), `Saved vendor “${f.vendor.trim()}”.`).then((ok) => { if (ok) setF(blank); });
  }

  return (
    <div className="tax-panel">
      <p className="tax-note"><Icon name="alert" size={13} /> Rules apply first-match-wins in sort order — put distinctive product names above broad ones.</p>
      <form className="admin-form tax-form" onSubmit={save}>
        <label className="field">
          <span className="field-lab">Vendor</span>
          <div className="field-wrap"><input value={f.vendor} onChange={(e) => set("vendor", e.target.value)} placeholder="e.g. Bosch" spellCheck="false" /></div>
        </label>
        <label className="field">
          <span className="field-lab">Sort order</span>
          <div className="field-wrap"><input type="number" value={f.sortOrder} onChange={(e) => set("sortOrder", e.target.value)} /></div>
        </label>
        <label className="field admin-span2">
          <span className="field-lab">Needles (comma-separated)</span>
          <div className="field-wrap"><input value={f.needles} onChange={(e) => set("needles", e.target.value)} placeholder="bosch, esitronic" spellCheck="false" /></div>
        </label>
        <div className="admin-form-act">
          {editing && <button type="button" className="btn btn-ghost" onClick={() => setF(blank)}>Cancel</button>}
          <button className="btn btn-accent" type="submit" disabled={busy}>
            {busy ? <span className="btn-spin" /> : <><Icon name="check" size={15} />{editing ? "Update" : "Add"} vendor</>}
          </button>
        </div>
      </form>
      <div className="admintbl-wrap">
        <table className="admintbl">
          <thead><tr><th className="admintbl-num">#</th><th>Vendor</th><th>Needles</th><th className="admintbl-num"></th></tr></thead>
          <tbody>
            {vendors.map((v) => (
              <tr key={v.vendor} className={f.vendor === v.vendor ? "is-editing" : ""}>
                <td className="admintbl-num mono">{v.sortOrder}</td>
                <td><strong>{v.vendor}</strong></td>
                <td><span className="tax-needles">{listToCsv(v.needles)}</span></td>
                <td className="admintbl-num tax-actions">
                  <button className="btn btn-ghost btn-sm" onClick={() => setF({ vendor: v.vendor, needles: listToCsv(v.needles), sortOrder: v.sortOrder || 0 })} type="button"><Icon name="pencil" size={14} /></button>
                  <button className="btn btn-ghost btn-sm tax-del" onClick={() => del(window.VCApi.adminDeleteVendor(v.vendor), `Deleted “${v.vendor}”.`)} type="button"><Icon name="trash" size={14} /></button>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

// ---- Triage status editor ---------------------------------------------------
function StatusEditor({ statuses, busy, run, del }) {
  const blank = { key: "", label: "", dot: "var(--st-new)", sortOrder: 0, selectable: true };
  const [f, setF] = useState(blank);
  const set = (k, v) => setF((p) => ({ ...p, [k]: v }));
  const editing = statuses.some((s) => s.key === f.key);

  function save(e) {
    e.preventDefault();
    if (!f.key.trim()) return;
    run(window.VCApi.adminUpsertStatus({
      key: f.key.trim(), label: f.label.trim() || f.key.trim(), dot: f.dot.trim() || "var(--st-new)",
      sortOrder: Number(f.sortOrder) || 0, selectable: !!f.selectable,
    }), `Saved status “${f.key.trim()}”.`).then((ok) => { if (ok) setF(blank); });
  }

  return (
    <div className="tax-panel">
      <p className="tax-note"><Icon name="alert" size={13} /> The <code>dot</code> is a CSS colour (e.g. <code>var(--st-new)</code> or <code>#5b8def</code>). Non-selectable states (like auto_enriched) show on findings but aren't offered in the triage menu.</p>
      <form className="admin-form tax-form" onSubmit={save}>
        <label className="field">
          <span className="field-lab">Key</span>
          <div className="field-wrap"><input value={f.key} onChange={(e) => set("key", e.target.value)} placeholder="in-review" spellCheck="false" /></div>
        </label>
        <label className="field">
          <span className="field-lab">Label</span>
          <div className="field-wrap"><input value={f.label} onChange={(e) => set("label", e.target.value)} placeholder="In review" /></div>
        </label>
        <label className="field">
          <span className="field-lab">Dot colour</span>
          <div className="field-wrap"><input value={f.dot} onChange={(e) => set("dot", e.target.value)} spellCheck="false" /></div>
        </label>
        <label className="field">
          <span className="field-lab">Sort order</span>
          <div className="field-wrap"><input type="number" value={f.sortOrder} onChange={(e) => set("sortOrder", e.target.value)} /></div>
        </label>
        <label className="field tax-check">
          <input type="checkbox" checked={f.selectable} onChange={(e) => set("selectable", e.target.checked)} />
          <span className="field-lab">User-selectable</span>
        </label>
        <div className="admin-form-act">
          {editing && <button type="button" className="btn btn-ghost" onClick={() => setF(blank)}>Cancel</button>}
          <button className="btn btn-accent" type="submit" disabled={busy}>
            {busy ? <span className="btn-spin" /> : <><Icon name="check" size={15} />{editing ? "Update" : "Add"} status</>}
          </button>
        </div>
      </form>
      <div className="admintbl-wrap">
        <table className="admintbl">
          <thead><tr><th className="admintbl-num">#</th><th>Status</th><th>Selectable</th><th className="admintbl-num"></th></tr></thead>
          <tbody>
            {statuses.map((s) => (
              <tr key={s.key} className={f.key === s.key ? "is-editing" : ""}>
                <td className="admintbl-num mono">{s.sortOrder}</td>
                <td><span className="admintbl-id"><span className="statuspill-dot" style={{ background: s.dot }} /><strong>{s.label}</strong><span className="tax-sub mono">{s.key}</span></span></td>
                <td>{s.selectable ? "Yes" : <span className="tax-sub">pipeline-only</span>}</td>
                <td className="admintbl-num tax-actions">
                  <button className="btn btn-ghost btn-sm" onClick={() => setF({ key: s.key, label: s.label, dot: s.dot, sortOrder: s.sortOrder || 0, selectable: s.selectable !== false })} type="button"><Icon name="pencil" size={14} /></button>
                  <button className="btn btn-ghost btn-sm tax-del" onClick={() => del(window.VCApi.adminDeleteStatus(s.key), `Deleted “${s.key}”.`)} type="button"><Icon name="trash" size={14} /></button>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

// ---- Severity band editor ---------------------------------------------------
function BandEditor({ bands, busy, run, del }) {
  const blank = { name: "", minCvss: 0, sortOrder: 0 };
  const [f, setF] = useState(blank);
  const set = (k, v) => setF((p) => ({ ...p, [k]: v }));
  const editing = bands.some((b) => b.name === f.name);

  function save(e) {
    e.preventDefault();
    if (!f.name.trim()) return;
    run(window.VCApi.adminUpsertBand({
      name: f.name.trim(), minCvss: Number(f.minCvss) || 0, sortOrder: Number(f.sortOrder) || 0,
    }), `Saved band “${f.name.trim()}”.`).then((ok) => { if (ok) setF(blank); });
  }

  return (
    <div className="tax-panel">
      <p className="tax-note"><Icon name="alert" size={13} /> A score gets the highest band whose minimum it meets. The four defaults (9 / 7 / 4 / 0) are the CVSS v3 standard — changing them re-labels findings everywhere.</p>
      <form className="admin-form tax-form" onSubmit={save}>
        <label className="field">
          <span className="field-lab">Name</span>
          <div className="field-wrap"><input value={f.name} onChange={(e) => set("name", e.target.value)} placeholder="Critical" spellCheck="false" /></div>
        </label>
        <label className="field">
          <span className="field-lab">Min CVSS</span>
          <div className="field-wrap"><input type="number" step="0.1" min="0" max="10" value={f.minCvss} onChange={(e) => set("minCvss", e.target.value)} /></div>
        </label>
        <label className="field">
          <span className="field-lab">Sort order</span>
          <div className="field-wrap"><input type="number" value={f.sortOrder} onChange={(e) => set("sortOrder", e.target.value)} /></div>
        </label>
        <div className="admin-form-act">
          {editing && <button type="button" className="btn btn-ghost" onClick={() => setF(blank)}>Cancel</button>}
          <button className="btn btn-accent" type="submit" disabled={busy}>
            {busy ? <span className="btn-spin" /> : <><Icon name="check" size={15} />{editing ? "Update" : "Add"} band</>}
          </button>
        </div>
      </form>
      <div className="admintbl-wrap">
        <table className="admintbl">
          <thead><tr><th className="admintbl-num">#</th><th>Band</th><th className="admintbl-num">Min CVSS</th><th className="admintbl-num"></th></tr></thead>
          <tbody>
            {bands.map((b) => (
              <tr key={b.name} className={f.name === b.name ? "is-editing" : ""}>
                <td className="admintbl-num mono">{b.sortOrder}</td>
                <td><span className={"sevtag sev-" + b.name.toLowerCase().replace(/\s+/g, "-") + " sevtag-sm"}><span className="sevtag-dot" /><span className="sevtag-label">{b.name}</span></span></td>
                <td className="admintbl-num mono">{b.minCvss.toFixed(1)}</td>
                <td className="admintbl-num tax-actions">
                  <button className="btn btn-ghost btn-sm" onClick={() => setF({ name: b.name, minCvss: b.minCvss, sortOrder: b.sortOrder || 0 })} type="button"><Icon name="pencil" size={14} /></button>
                  <button className="btn btn-ghost btn-sm tax-del" onClick={() => del(window.VCApi.adminDeleteBand(b.name), `Deleted “${b.name}”.`)} type="button"><Icon name="trash" size={14} /></button>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}
window.AdminScreen = AdminScreen;
