/* MetadataDesk — Content Operations' queue for metadata changes proposed by machines.
   Assigns window.LedgerMetadataDesk. See specs/content-ops-metadata-desk-v1.md.

   Two producers today, one queue (the `metadata_queue` view, 0130):
     title  — tools/matter-titles split a legacy title into `ST v Name` + summary + note (0109)
     fact   — the Trial Tracker read an article and proposes a court date, a status, or a person (0112)
     document — the A3 backfill read page 1 of a copied document and proposes its type and title (0146)
     agency   — tools/agency-cleanup places an agency in its county, or proposes merging two duplicates (0148);
                0149 moves statewide links to the state; tools/agency-details proposes contacts with a role and
                how the agency works (pickup, release, turnaround) read from its notes (0150)
                and tools/courthouse-seed proposes courthouse buildings from court dates (0151)
   Nothing here touches a matter until a person approves, and approving goes through an
   RPC that applies and records in one transaction. The RPC is the real gate
   (can_decide_metadata); this screen only decides what to draw. */
(function () {
  const DS = window.LawCrimeDesignSystem_e18d68;
  const T = window.titleize || ((s) => String(s || "").replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()));
  const icons = () => setTimeout(() => window.lucide && window.lucide.createIcons(), 30);

  // Every icon sits in a span React owns and is keyed on its name, so a conditional or
  // renamed icon can never leave React holding an <i> lucide already swapped out.
  const Ic = ({ n }) => <span className="ic-wrap" key={n}><i data-lucide={n} /></span>;

  const KINDS = [
    { id: "all",    label: "All" },
    { id: "title",  label: "Titles" },
    { id: "event",  label: "Court dates" },
    { id: "status", label: "Status" },
    { id: "person", label: "People" },
    { id: "document", label: "Documents" },
    { id: "agency", label: "Agencies" },
  ];
  const KIND_LABEL = { title: "Title", event: "Court date", status: "Status", person: "Person", document: "Document", agency: "Agency" };
  // Agencies (0148). Vocabularies mirror decide_agency_proposal's checks.
  const SCOPES = ["local", "state", "federal", "national"];
  const SCOPE_LABEL = { local: "Local", state: "Statewide", federal: "Federal", national: "National" };
  const COUNTY_KINDS = ["county", "parish", "borough", "independent_city"];
  const placeText = (p, state) => {
    if (!p) return "Not placed";
    if (p.county) return p.county + (p.county_kind === "independent_city" ? " (independent city)" : " " + T(p.county_kind || "county")) + (state ? ", " + state : "");
    return p.scope ? SCOPE_LABEL[p.scope] + (p.scope === "state" && state ? " · " + state : "") : "Not placed";
  };
  // State links (0149): a statewide "agency" that is really a case search or directory.
  const RES_KINDS = ["case_search", "court_directory", "live_stream_directory", "foia_portal", "statute", "other"];
  const resKindLabel = (k) => (window.LedgerAgencyPlaces ? window.LedgerAgencyPlaces.KIND_LABEL[k] : null) || T(k || "other");
  // Contacts and quirks (0150). Vocabularies live in config.js (window.AGENCY_*); the RPC re-checks them.
  const AG_SUBS = [
    { id: "all", label: "All" }, { id: "location", label: "Location" }, { id: "merge", label: "Duplicates" },
    { id: "state_resource", label: "State links" }, { id: "contact", label: "Contacts" }, { id: "quirk", label: "Records requests" },
    // 0151: courthouse buildings. RLS shows these to Content Ops only, which is everyone on this desk.
    { id: "courthouse", label: "Courthouses" },
  ];
  const AG_SUB_LABEL = { location: "Location", merge: "Duplicate", state_resource: "State link", contact: "Contact", quirk: "Records requests",
                         courthouse: "Courthouse" };
  const quirkText = (q) => {
    const chips = window.agencyQuirkChips ? window.agencyQuirkChips(q || {}) : [];
    const none = q && q.pickup === "any" ? ["Any pickup"] : [];
    const open = q && q.release_condition === "none" ? ["Released while open"] : [];
    return chips.map((c) => c.text).concat(none, open).join(" · ") || "Nothing recorded";
  };
  const contactText = (c) => [c.name, c.title, c.email, c.phone].filter(Boolean).join(" · ");
  const DOC_SOURCES = ["court", "agency", "law_enforcement", "freelancer", "internal", "other"];
  /** The pick list for a document's record type (cached by api-attachments). */
  function useDocTypes(entity) {
    const [types, setTypes] = React.useState([]);
    React.useEffect(() => {
      if (!entity || !window.LedgerAPI.att) return;
      window.LedgerAPI.att.docTypes(entity).then(setTypes).catch(() => setTypes([]));
    }, [entity]);
    return types;
  }
  const typeLabel = (types, key) => ((types.find((t) => t.key === key) || {}).label || T(key || ""));

  // Vocabularies mirror the RPC's allow-lists (0130). The server re-checks every one.
  const EVENT_TYPES = ["hearing", "arraignment", "bail_hearing", "motion_hearing", "trial_start", "trial_day",
    "change_of_plea", "verdict", "sentencing", "appeal", "press_conference", "arrest", "other"];
  const STATUS_VALUES = {
    legal_status: ["incident", "arrested", "investigation", "charged", "pre_trial", "trial", "verdict",
                   "sentencing", "appeal", "closed", "cold_case"],
    verdict: ["guilty", "not_guilty", "mistrial", "plea", "pending"],
  };
  const ROLES = ["defendant", "victim", "judge", "prosecutor", "defense_attorney", "witness"];

  const FLAG_HELP = {
    truncated_source: "The original title is cut off (an unbalanced bracket). The missing text is gone upstream: don't invent it. Approve only the part that is certain, or reject.",
    ambiguous_boundary: "The name run stopped at a connector, which is exactly where a co-defendant can go missing. Check the original for a name that didn't make it into the alias.",
    no_defendant: "Nothing name-shaped was found before the description started.",
    ops_note_only: "The only thing appended to the name was a production instruction.",
    not_in_source: "Part of this isn't in the agency's notes or its contacts on file, so the model may have made it up. Check it against the notes below.",
    generic_name: "The court dates only say a generic building (\"Judicial Center\", \"Main Courthouse\"), so the county was put in front. Check it is one building, and give it its real name if you know it.",
    multi_county: "The court dates using this name come from matters in more than one county. Check which county the building is in.",
    check_name: "The name is initials (\"H.C. Courthouse\"). Spell it out before approving.",
    shared_contact: "The same email was proposed for more than one agency. Either the agencies are duplicates, or the note is about another office (Orlando PD's note describes the Sheriff's PIO inbox). Approve only if this agency really uses it.",
  };

  const ALIAS_RE = /^[A-Z]{2} v \S/;
  const conf = (r) => (r.confidence == null ? null : Number(r.confidence));
  // Confidence bands (operator 2026-10-12: filter by confidence to speed review). The
  // thresholds are the Conf pill's tones, so a chip and the pills under it agree.
  // Unscored proposals sit with "Under 75%": nothing vouches for them either.
  const BANDS = [
    { id: "all",  label: "Any confidence" },
    { id: "high", label: "95%+",     test: (c) => c != null && c >= 0.95 },
    { id: "mid",  label: "75–94%",   test: (c) => c != null && c >= 0.75 && c < 0.95 },
    { id: "low",  label: "Under 75%", test: (c) => c == null || c < 0.75 },
  ];
  const inBand = (r, band) => band === "all" || BANDS.find((b) => b.id === band).test(conf(r));
  const isStale = (r) => r.queue === "title" && !!(r.detail && r.detail.stale);
  // What "select all" is allowed to pick up. Flagged, stale or under 0.9 need a person
  // to look, so they can be ticked one by one but never swept in by a bulk select.
  const bulkEligible = (r) => (conf(r) || 0) >= 0.9 && !r.flag && !isStale(r);
  const hostLabel = (u) => { try { return new URL(u).hostname.replace(/^www\./, ""); } catch (e) { return "source"; } };
  const fmtWhen = (iso) => {
    if (!iso) return "";
    const d = new Date(iso);
    return d.toLocaleString("en-US", { month: "short", day: "numeric", hour: "numeric", minute: "2-digit", timeZone: "America/New_York" });
  };
  const who = (email) => (email || "").split("@")[0] || "—";

  function Conf({ value }) {
    if (value == null) return null;
    const v = Number(value);
    const tone = v >= 0.95 ? "high" : v >= 0.75 ? "mid" : "low";
    return <span className={"tr-conf tr-conf--" + tone} title="Model confidence">{v.toFixed(2)}</span>;
  }
  function Flag({ flag }) {
    if (!flag) return null;
    return <span className={"tr-flag tr-flag--" + flag.replace(/_/g, "-")} title={FLAG_HELP[flag] || flag}><Ic n="alert-triangle" /> {T(flag)}</span>;
  }

  // The editable draft for a row, seeded from what was proposed.
  function initialDraft(r) {
    const d = r.detail || {};
    if (r.queue === "title") return { alias: d.alias || "", summary: d.summary || "", notes: d.notes || "" };
    if (r.kind === "event") return { event_type: d.event_type || "other", event_date: d.event_date || "",
                                     event_time: d.event_time || "", courthouse: d.courthouse || "" };
    if (r.kind === "status") return { value: d.value || "" };
    if (r.queue === "document") return { doc_type_key: d.doc_type_key || "", title: d.title || "",
                                         document_date: d.document_date || "", source: d.source || "" };
    if (r.queue === "agency" && r.sub_kind === "merge") return { keep: d.keep || "" };
    if (r.queue === "agency" && r.sub_kind === "contact") return { role: d.role || "other", name: d.name || "", title: d.title || "",
                                                                   email: d.email || "", phone: d.phone || "", notes: d.notes || "" };
    if (r.queue === "agency" && r.sub_kind === "quirk") return { pickup: d.pickup || "", release_condition: d.release_condition || "",
      release_condition_days: d.release_condition_days == null ? "" : String(d.release_condition_days),
      typical_turnaround_days: d.typical_turnaround_days == null ? "" : String(d.typical_turnaround_days) };
    if (r.queue === "agency" && r.sub_kind === "courthouse") return { name: d.name || "", county: d.county || "", state: d.state || "",
      city: d.city || "", address: d.address || "", court_entity_id: d.court_entity_id || "" };
    if (r.queue === "agency" && r.sub_kind === "state_resource") return { kind: d.kind || "other", label: d.label || "",
                                                                          url: d.url || "", retire: !!d.retire };
    if (r.queue === "agency") return { county: d.county || "", county_kind: d.county_kind || "county",
                                       scope: d.scope || "local", state: (d.current && d.current.state) || "" };
    return { name: d.name || "", role: d.role || "witness" };
  }
  // Only the keys that actually changed go to the server. For facts, an unchanged draft
  // sends nothing, so the proposal is recorded as accepted-as-proposed.
  function changedKeys(r, draft) {
    const base = initialDraft(r);
    const out = {};
    Object.keys(draft).forEach((k) => { if ((draft[k] || "") !== (base[k] || "")) out[k] = draft[k]; });
    return out;
  }

  function EditFields({ r, draft, set, types }) {
    const { Input } = DS;
    const up = (k) => (e) => set({ ...draft, [k]: e.target.value });
    if (r.queue === "agency" && r.sub_kind === "merge") {
      const d = r.detail || {};
      return (
        <div className="md-edit">
          <div className="field-label">Which one stays?</div>
          {[d.a, d.b].filter(Boolean).map((c) => (
            <label key={c.entity_id} className="md-keep">
              <input type="radio" name={"keep-" + r.proposal_id} checked={draft.keep === c.entity_id}
                onChange={() => set({ ...draft, keep: c.entity_id })} /> {c.name}
            </label>
          ))}
        </div>
      );
    }
    if (r.queue === "agency" && r.sub_kind === "contact") {
      return (
        <div className="md-edit md-edit--grid">
          <label className="field-label">Role
            <select className="lc-input" value={draft.role} onChange={up("role")}>
              {window.AGENCY_CONTACT_ROLES.map(([v, l]) => <option key={v} value={v}>{l}</option>)}
            </select>
          </label>
          <label className="field-label">Name<input className="lc-input" value={draft.name} onChange={up("name")} /></label>
          <label className="field-label">Title<input className="lc-input" value={draft.title} onChange={up("title")} /></label>
          <label className="field-label">Email<input className="lc-input" value={draft.email} onChange={up("email")} /></label>
          <label className="field-label">Phone<input className="lc-input" value={draft.phone} onChange={up("phone")} /></label>
          <label className="field-label">Note<input className="lc-input" value={draft.notes} onChange={up("notes")} /></label>
        </div>
      );
    }
    if (r.queue === "agency" && r.sub_kind === "quirk") {
      return (
        <div className="md-edit md-edit--grid">
          <label className="field-label">Pickup
            <select className="lc-input" value={draft.pickup} onChange={up("pickup")}>
              <option value="">Not stated</option>
              {window.AGENCY_PICKUPS.map(([v, l]) => <option key={v} value={v}>{l}</option>)}
            </select>
          </label>
          <label className="field-label">Release
            <select className="lc-input" value={draft.release_condition} onChange={up("release_condition")}>
              <option value="">Not stated</option>
              {window.AGENCY_RELEASES.map(([v, l]) => <option key={v} value={v}>{l}</option>)}
            </select>
          </label>
          {draft.release_condition === "after_adjudication_days" &&
            <label className="field-label">Days after adjudication<input className="lc-input" value={draft.release_condition_days} onChange={up("release_condition_days")} /></label>}
          <label className="field-label">Turnaround, days <span className="fnote">staff estimate</span>
            <input className="lc-input" value={draft.typical_turnaround_days} onChange={up("typical_turnaround_days")} /></label>
        </div>
      );
    }
    if (r.queue === "agency" && r.sub_kind === "courthouse") {
      // Courts to choose from: the one proposed, plus every court on file in that county.
      const d = r.detail || {};
      const opts = (d.court_entity_id ? [{ entity_id: d.court_entity_id, name: d.court_name || d.entity_label || "Proposed court" }] : [])
        .concat((d.court_options || []).filter((c) => c.entity_id !== d.court_entity_id));
      return (
        <div className="md-edit md-edit--grid">
          <label className="field-label">Building name<input className="lc-input" value={draft.name} onChange={up("name")} /></label>
          <label className="field-label">County <span className="fnote">without "County"</span>
            <input className="lc-input" value={draft.county} onChange={up("county")} /></label>
          <label className="field-label">State<input className="lc-input" value={draft.state} maxLength={2}
            onChange={(e) => set({ ...draft, state: e.target.value.toUpperCase() })} /></label>
          <label className="field-label">City<input className="lc-input" value={draft.city} onChange={up("city")} /></label>
          <label className="field-label">Court
            <select className="lc-input" value={draft.court_entity_id} onChange={up("court_entity_id")}>
              <option value="">— not linked (set it later on the courthouse) —</option>
              {opts.map((c) => <option key={c.entity_id} value={c.entity_id}>{c.name}</option>)}
            </select>
          </label>
        </div>
      );
    }
    if (r.queue === "agency" && r.sub_kind === "state_resource") {
      const a = (r.detail && r.detail.a) || {};
      const linked = (a.requests || 0) + (a.matters || 0) + (a.documents || 0) > 0;
      return (
        <div className="md-edit md-edit--grid">
          <label className="field-label">Kind
            <select className="lc-input" value={draft.kind} onChange={up("kind")}>
              {RES_KINDS.map((x) => <option key={x} value={x}>{resKindLabel(x)}</option>)}
            </select>
          </label>
          <label className="field-label">Label<input className="lc-input" value={draft.label} onChange={up("label")} /></label>
          <label className="field-label">Link<input className="lc-input" value={draft.url} onChange={up("url")} placeholder="https://" /></label>
          <label className="md-keep" title={linked ? "Something links to it, so it stays in the directory" : ""}>
            <input type="checkbox" checked={draft.retire && !linked} disabled={linked}
              onChange={(e) => set({ ...draft, retire: e.target.checked })} /> Hide the agency row once the link is added
          </label>
        </div>
      );
    }
    if (r.queue === "agency") {
      return (
        <div className="md-edit md-edit--grid">
          <label className="field-label">Scope
            <select className="lc-input" value={draft.scope} onChange={up("scope")}>
              {SCOPES.map((x) => <option key={x} value={x}>{SCOPE_LABEL[x]}</option>)}
            </select>
          </label>
          <label className="field-label">County <span className="fnote">without "County"</span>
            <input className="lc-input" value={draft.county} onChange={up("county")} disabled={draft.scope !== "local"} />
          </label>
          <label className="field-label">Kind
            <select className="lc-input" value={draft.county_kind} onChange={up("county_kind")} disabled={draft.scope !== "local"}>
              {COUNTY_KINDS.map((x) => <option key={x} value={x}>{T(x)}</option>)}
            </select>
          </label>
          <label className="field-label">State<input className="lc-input" value={draft.state} maxLength={2}
            onChange={(e) => set({ ...draft, state: e.target.value.toUpperCase() })} /></label>
        </div>
      );
    }
    if (r.queue === "document") {
      return (
        <div className="md-edit md-edit--grid">
          <label className="field-label">Document type
            <select className="lc-input" value={draft.doc_type_key} onChange={up("doc_type_key")}>
              {types.map((t) => <option key={t.key} value={t.key}>{t.label}</option>)}
            </select>
          </label>
          <label className="field-label">Title<input className="lc-input" value={draft.title} onChange={up("title")} /></label>
          <label className="field-label">Document date<input className="lc-input" type="date" value={draft.document_date} onChange={up("document_date")} /></label>
          <label className="field-label">Source
            <select className="lc-input" value={draft.source} onChange={up("source")}>
              <option value="">—</option>
              {DOC_SOURCES.map((x) => <option key={x} value={x}>{T(x)}</option>)}
            </select>
          </label>
        </div>
      );
    }
    if (r.queue === "title") {
      const cur = (r.detail && r.detail.current) || {};
      return (
        <div className="md-edit">
          <Input label="Display alias" value={draft.alias} onChange={up("alias")} />
          {!ALIAS_RE.test(draft.alias.trim()) && <div className="tr-badalias">Must read “ST v Name”.</div>}
          <label className="field-label">One-line summary</label>
          <textarea className="lc-input ledger-textarea tr-ta" value={draft.summary} onChange={up("summary")} placeholder="(none)" />
          <label className="field-label">Editorial note <span className="fnote">production instruction; appended to the existing note</span></label>
          <textarea className="lc-input ledger-textarea tr-ta" value={draft.notes} onChange={up("notes")} placeholder="(none)" />
          {cur.editorial_notes && <div className="md-existing"><b>Existing note:</b> {cur.editorial_notes}</div>}
        </div>
      );
    }
    if (r.kind === "event") {
      return (
        <div className="md-edit md-edit--grid">
          <label className="field-label">Type
            <select className="lc-input" value={draft.event_type} onChange={up("event_type")}>
              {EVENT_TYPES.map((t) => <option key={t} value={t}>{T(t)}</option>)}
            </select>
          </label>
          <label className="field-label">Date<input className="lc-input" type="date" value={draft.event_date} onChange={up("event_date")} /></label>
          <label className="field-label">Time <span className="fnote">court-local</span><input className="lc-input" type="time" value={draft.event_time} onChange={up("event_time")} /></label>
          <label className="field-label">Courthouse<input className="lc-input" value={draft.courthouse} onChange={up("courthouse")} /></label>
        </div>
      );
    }
    if (r.kind === "status") {
      const field = (r.detail && r.detail.field) || "legal_status";
      return (
        <div className="md-edit">
          <label className="field-label">{T(field)}
            <select className="lc-input" value={draft.value} onChange={up("value")}>
              {(STATUS_VALUES[field] || []).map((v) => <option key={v} value={v}>{T(v)}</option>)}
            </select>
          </label>
        </div>
      );
    }
    return (
      <div className="md-edit md-edit--grid">
        <label className="field-label">Name<input className="lc-input" value={draft.name} onChange={up("name")} /></label>
        <label className="field-label">Role
          <select className="lc-input" value={draft.role} onChange={up("role")}>
            {ROLES.map((x) => <option key={x} value={x}>{T(x)}</option>)}
          </select>
        </label>
      </div>
    );
  }

  /** What an agency proposal is based on. A merge shows both records side by side, with
   *  what each carries, so the reviewer can see which one is worth keeping. */
  function AgencyEvidence({ r, keep }) {
    const d = r.detail || {};
    const agencyLink = (
      <button type="button" className="md-mlink" onClick={() => window.LedgerOpenAgency && window.LedgerOpenAgency(d.entity_id)}>{d.entity_label}</button>
    );
    const notes = d.agency_notes && (
      <details className="md-notes"><summary>The agency's notes</summary><div className="ag-notes">{d.agency_notes}</div></details>
    );
    if (r.sub_kind === "contact") {
      const of = d.on_file;
      return (
        <div className="md-ev">
          <div><span className="md-evlabel">Proposed</span> <span className="chip ag-rolemini">{window.agencyRoleLabel(d.role)}</span> {contactText(d)}</div>
          {d.notes && <div className="md-sub">Note: {d.notes}</div>}
          <div><span className="md-evlabel">{of ? "On file" : "Agency"}</span>{" "}
            {of ? <span><span className="chip ag-rolemini">{window.agencyRoleLabel(of.role)}</span> {contactText(of)} · on {agencyLink}</span>
                : <span>{agencyLink} · approving adds a contact</span>}</div>
          {of && r.status === "pending" && <div className="md-sub">Approving sets its role{d.title ? " and title" : ""} and fills anything it lacks. Nothing typed is overwritten.</div>}
          {r.evidence && <div className="md-sub">{r.evidence}</div>}
          {notes}
        </div>
      );
    }
    if (r.sub_kind === "quirk") {
      return (
        <div className="md-ev">
          <div><span className="md-evlabel">Proposed</span> {quirkText(d)}</div>
          <div><span className="md-evlabel">Now</span> {quirkText(d.quirks)} · {agencyLink}</div>
          {r.evidence && <div className="md-quote">{r.evidence}</div>}
          <div className="md-sub">Approving sets only what is stated; it never clears a value. Turnaround is the desk's estimate.</div>
          {notes}
        </div>
      );
    }
    if (r.sub_kind === "courthouse") {
      const place = [d.city, d.county && d.county + " County", d.state].filter(Boolean).join(", ");
      return (
        <div className="md-ev">
          <div><span className="md-evlabel">Building</span> {d.name}{place ? " · " + place : ""}</div>
          <div><span className="md-evlabel">Court</span>{" "}
            {d.court_entity_id
              ? <button type="button" className="md-mlink" onClick={() => window.LedgerOpenAgency && window.LedgerOpenAgency(d.court_entity_id)}>{d.court_name || d.entity_label}</button>
              : (d.court_options && d.court_options.length ? `${d.court_options.length} courts on file in the county: pick one when editing, or leave it` : "None on file yet")}</div>
          <div><span className="md-evlabel">Court dates</span> {d.events || 0}{d.upcoming ? ` (${d.upcoming} upcoming)` : ""}
            {d.source_texts && d.source_texts.length > 0 && <span className="md-sub"> · written as {d.source_texts.map((s) => `“${s}”`).join(", ")}</span>}</div>
          {r.evidence && <div className="md-sub">{r.evidence}</div>}
          <div className="md-sub">{r.status === "pending"
            ? "Approving adds the building (or reuses one with the same name) and links these court dates to it. Production notes are added on the courthouse page."
            : d.courthouse_id
              ? <button type="button" className="md-mlink" onClick={() => window.LedgerOpenCourthouse && window.LedgerOpenCourthouse(d.courthouse_id)}>Open the courthouse</button>
              : null}</div>
        </div>
      );
    }
    if (r.sub_kind === "state_resource") {
      const a = d.a || {};
      return (
        <div className="md-ev">
          <div><span className="md-evlabel">Link</span> {resKindLabel(d.kind)} · {d.label}
            {d.url ? <> · <a href={d.url} target="_blank" rel="noreferrer">{String(d.url).replace(/^https?:\/\//, "").slice(0, 60)}</a></> : " · no link on file"}</div>
          <div><span className="md-evlabel">Agency row</span>{" "}
            <button type="button" className="md-mlink" onClick={() => window.LedgerOpenAgency && window.LedgerOpenAgency(d.entity_id)}>{d.entity_label}</button>
            {" · "}{a.requests || 0} request{a.requests === 1 ? "" : "s"} · {a.matters || 0} matter{a.matters === 1 ? "" : "s"} · {a.documents || 0} document{a.documents === 1 ? "" : "s"}</div>
          <div className="md-sub">{r.status === "pending"
            ? (d.retire ? "Approving adds the link to the state and hides this row (nothing links to it)."
                        : "Approving adds the link to the state. The row stays as a statewide agency, because requests or matters point at it.")
            : (d.retire ? "Added to the state; the row was hidden." : "Added to the state; the row stayed.")}</div>
        </div>
      );
    }
    if (r.sub_kind !== "merge") {
      return (
        <div className="md-ev">
          <div><span className="md-evlabel">Proposed</span> {placeText(d, d.current && d.current.state)}</div>
          <div><span className="md-evlabel">Now</span> {placeText(d.current && d.current.scope ? d.current : null, d.current && d.current.state)}
            {d.current && d.current.type ? " · " + T(d.current.type) : ""}</div>
          {r.evidence && <div className="md-sub">{r.evidence}</div>}
        </div>
      );
    }
    const card = (c) => c && (
      <div key={c.entity_id} className={"md-agcard" + (c.entity_id === keep ? " md-agcard--keep" : "")}>
        <div className="md-agcard-h">{c.entity_id === keep ? "Stays" : "Merged in, then hidden"}</div>
        <button type="button" className="md-mlink" onClick={() => window.LedgerOpenAgency && window.LedgerOpenAgency(c.entity_id)}>{c.name}</button>
        <div className="md-sub">{[c.type && T(c.type), c.county ? c.county + " County" : null, c.state].filter(Boolean).join(" · ")}</div>
        <div className="md-sub">{c.requests} request{c.requests === 1 ? "" : "s"} · {c.matters} matter{c.matters === 1 ? "" : "s"} · {c.documents} document{c.documents === 1 ? "" : "s"}</div>
        {(c.contact || c.email || c.phone) && <div className="md-sub">{[c.contact, c.email, c.phone].filter(Boolean).join(" · ")}</div>}
      </div>
    );
    return (
      <div className="md-ev">
        <div className="md-agpair">{card(d.a)}{card(d.b)}</div>
        {r.evidence && <div className="md-sub">{r.evidence}</div>}
        <div className="md-sub">Requests, matters and documents move to the one that stays; its empty fields are filled from the other. A merge can be undone from Decided.</div>
      </div>
    );
  }

  /** One proposal: summary, evidence, and approve / edit / reject. */
  function Row({ r, selected, onToggle, onDone, onError }) {
    const { Button, Input } = DS;
    const [editing, setEditing] = React.useState(false);
    const [draft, setDraft] = React.useState(() => initialDraft(r));
    const [rejecting, setRejecting] = React.useState(false);
    const [why, setWhy] = React.useState("");
    const [staleOk, setStaleOk] = React.useState(false);
    const [busy, setBusy] = React.useState(false);
    React.useEffect(() => { icons(); }, [editing, rejecting]);

    const d = r.detail || {};
    const types = useDocTypes(r.queue === "document" ? r.sub_kind : null);
    const stale = isStale(r);
    const aliasBad = r.queue === "title" && editing && !ALIAS_RE.test((draft.alias || "").trim());

    async function approve() {
      setBusy(true);
      try {
        if (r.queue === "title") {
          await window.LedgerAPI.decideTitle(r.proposal_id, "approve", editing ? draft : {}, { confirmStale: stale && staleOk });
        } else if (r.queue === "document") {
          const edits = editing ? changedKeys(r, draft) : {};
          await window.LedgerAPI.decideDocument(r.proposal_id, "approve", Object.keys(edits).length ? edits : null);
        } else if (r.queue === "agency") {
          const edits = editing ? changedKeys(r, draft) : {};
          await window.LedgerAPI.decideAgency(r.proposal_id, "approve", Object.keys(edits).length ? edits : null);
        } else {
          const edits = editing ? changedKeys(r, draft) : {};
          await window.LedgerAPI.decideFact(r.proposal_id, "accept", Object.keys(edits).length ? edits : null);
        }
        onDone(r, editing && Object.keys(changedKeys(r, draft)).length ? "edited" : "approved");
      } catch (e) { onError(e.message || String(e)); setBusy(false); }
    }
    async function reject() {
      setBusy(true);
      try {
        if (r.queue === "title") await window.LedgerAPI.decideTitle(r.proposal_id, "reject", {}, { note: why });
        else if (r.queue === "document") await window.LedgerAPI.decideDocument(r.proposal_id, "reject", null, why);
        else if (r.queue === "agency") await window.LedgerAPI.decideAgency(r.proposal_id, "reject", null, why);
        else await window.LedgerAPI.decideFact(r.proposal_id, "dismiss", null, why);
        onDone(r, "rejected");
      } catch (e) { onError(e.message || String(e)); setBusy(false); }
    }

    return (
      <div className={"md-row" + (selected ? " md-row--sel" : "")}>
        <label className="md-check" title={bulkEligible(r) ? "Select" : "Needs a look: flagged, stale or under 0.90. Can still be selected by hand."}>
          <input type="checkbox" checked={selected} onChange={() => onToggle(r)} disabled={busy} />
        </label>
        <div className="md-body">
          <div className="md-top">
            <span className={"md-kind md-kind--" + r.kind}>{KIND_LABEL[r.kind] || r.kind}</span>
            {r.sub_kind && r.queue === "fact" && <span className="md-sub">{T(r.sub_kind)}</span>}
            {r.queue === "agency" && <span className="md-sub">{AG_SUB_LABEL[r.sub_kind] || "Location"}{d.method === "ai" ? " · AI" : " · rule"}</span>}
            <span className="md-sum">{r.summary}</span>
            <span className="tr-meta"><Conf value={r.confidence} /><Flag flag={r.flag} /></span>
          </div>

          {r.queue === "title" ? (
            <div className="md-ev">
              <span className="md-evlabel">Title now</span> {d.source_title}
              {(d.summary || d.notes) && !editing && (
                <div className="md-split">
                  {d.summary && <div><span className="md-evlabel">Summary</span> {d.summary}</div>}
                  {d.notes && <div><span className="md-evlabel">Note</span> {d.notes}</div>}
                </div>
              )}
              {stale && (
                <div className="tr-warn">
                  <Ic n="alert-circle" /> The title has changed since this was proposed. It now reads <b>{d.current && d.current.working_title}</b>.
                  <label className="md-staleok"><input type="checkbox" checked={staleOk} onChange={(e) => setStaleOk(e.target.checked)} /> I've checked it against the new title</label>
                </div>
              )}
            </div>
          ) : r.queue === "agency" ? (
            <AgencyEvidence r={r} keep={editing && r.sub_kind === "merge" ? draft.keep : d.keep} />
          ) : r.queue === "document" ? (
            <div className="md-ev">
              <div><span className="md-evlabel">Proposed</span> {typeLabel(types, d.doc_type_key)}
                {d.document_date ? ` · ${window.LedgerTime ? window.LedgerTime.fmtDate(d.document_date) : d.document_date}` : ""}
                {d.source ? ` · ${T(d.source)}` : ""}</div>
              <div><span className="md-evlabel">Now</span> {typeLabel(types, d.current && d.current.doc_type_key)} · {d.current && d.current.title}</div>
              {d.entity_label && <div><span className="md-evlabel">On</span> {d.entity_label}</div>}
              {r.source_url && (
                <a className="md-src" href={r.source_url} target="_blank" rel="noopener noreferrer" title="Open the document in Drive">
                  <Ic n="file-text" /> {r.source_title}
                </a>
              )}
              {r.evidence && <div className="md-sub">{r.evidence}</div>}
            </div>
          ) : (
            <div className="md-ev">
              {r.kind === "status" && d.current != null && <div><span className="md-evlabel">Currently</span> {T(d.current)}</div>}
              {r.evidence && <div className="md-quote">&ldquo;{r.evidence}&rdquo;</div>}
              {r.source_url && (
                <a className="md-src" href={r.source_url} target="_blank" rel="noopener noreferrer" title={r.source_title || r.source_url}>
                  <Ic n="external-link" /> {hostLabel(r.source_url)}
                </a>
              )}
            </div>
          )}

          {editing && <EditFields r={r} draft={draft} set={setDraft} types={types} />}

          <div className="md-actions">
            {rejecting ? (
              <React.Fragment>
                <Input value={why} onChange={(e) => setWhy(e.target.value)} placeholder="Why? (optional, kept on the record)" />
                <button type="button" className="link-btn" onClick={() => setRejecting(false)} disabled={busy}>Cancel</button>
                <Button size="sm" variant="secondary" icon="x" onClick={reject} disabled={busy}>{busy ? "…" : "Confirm reject"}</Button>
              </React.Fragment>
            ) : (
              <React.Fragment>
                <button type="button" className="link-btn" onClick={() => setRejecting(true)} disabled={busy}>Reject</button>
                <button type="button" className="link-btn" disabled={busy}
                  onClick={() => { if (editing) setDraft(initialDraft(r)); setEditing(!editing); }}>
                  {editing ? "Cancel edit" : "Edit"}
                </button>
                <Button size="sm" variant="primary" icon="check" onClick={approve}
                  disabled={busy || aliasBad || (stale && !staleOk)}>
                  {busy ? "Applying…" : editing ? "Approve edited" : "Approve"}
                </Button>
              </React.Fragment>
            )}
          </div>
        </div>
      </div>
    );
  }

  // A card is one matter. Its header checkbox selects every pending row in it (flagged
  // and stale included: picking a matter is a deliberate choice, unlike the global
  // "select eligible"), and its own Approve/Reject act on just that matter's selection.
  function MatterCard({ g, selected, onToggle, onSelectGroup, onBulk, bulkBusy, onDone, onError, onOpenMatter }) {
    const { Button } = DS;
    const [armed, setArmed] = React.useState(null); // "approve" | "reject" | null
    const boxRef = React.useRef(null);
    const sel = g.rows.filter((r) => selected.has(r.proposal_id));
    const all = sel.length === g.rows.length;
    const some = sel.length > 0 && !all;
    React.useEffect(() => { if (boxRef.current) boxRef.current.indeterminate = some; }, [some]);
    React.useEffect(() => { if (!sel.length) setArmed(null); }, [sel.length]);
    const needsLook = sel.filter((r) => !bulkEligible(r)).length;

    async function go() {
      await onBulk(armed, sel);
      setArmed(null);
    }

    return (
      <DS.Card style={{ marginBottom: 12 }}><DS.Card.Body>
        <div className="md-mhead">
          <label className="md-check md-gcheck" title={all ? "Clear this matter" : "Select everything pending for this matter"}>
            <input ref={boxRef} type="checkbox" checked={all} disabled={bulkBusy}
              onChange={() => onSelectGroup(g.rows, !all)} />
          </label>
          {g.matter_id ? (
            <button type="button" className="md-mlink" onClick={() => onOpenMatter && onOpenMatter(g.matter_id)}>
              {g.label || "Untitled matter"}
            </button>
          ) : g.agency_id ? (
            <button type="button" className="md-mlink" onClick={() => window.LedgerOpenAgency && window.LedgerOpenAgency(g.agency_id)}>
              {g.label || "Agency"}
            </button>
          ) : <b className="md-mlink md-mlink--plain">{g.label || "No matter"}</b>}
          <span className="md-mmeta">{[g.display_id, g.state, g.lane && T(g.lane)].filter(Boolean).join(" · ")}</span>
          {sel.length === 0 ? (
            <span className="md-mcount">{g.rows.length} pending</span>
          ) : (
            <span className="md-gact">
              <span className="md-gsel">{sel.length} of {g.rows.length} selected
                {needsLook > 0 && <span className="md-glook"> · {needsLook} need a look</span>}</span>
              {armed ? (
                <React.Fragment>
                  <button type="button" className="link-btn" onClick={() => setArmed(null)} disabled={bulkBusy}>Cancel</button>
                  <Button size="sm" variant={armed === "approve" ? "primary" : "secondary"} icon={armed === "approve" ? "check-check" : "x"}
                    onClick={go} disabled={bulkBusy}>
                    {bulkBusy ? "Applying…" : `Yes, ${armed} ${sel.length}`}
                  </Button>
                </React.Fragment>
              ) : (
                <React.Fragment>
                  <button type="button" className="link-btn" onClick={() => setArmed("reject")} disabled={bulkBusy}>Reject {sel.length}</button>
                  <Button size="sm" variant="primary" icon="check-check" onClick={() => setArmed("approve")} disabled={bulkBusy}>
                    Approve {sel.length}
                  </Button>
                </React.Fragment>
              )}
            </span>
          )}
        </div>
        {g.rows.map((r) => (
          <Row key={r.proposal_id} r={r} selected={selected.has(r.proposal_id)}
               onToggle={onToggle} onDone={onDone} onError={onError} />
        ))}
      </DS.Card.Body></DS.Card>
    );
  }

  function Decided({ rows, onOpenMatter, onChanged }) {
    const [person, setPerson] = React.useState("");
    const [undoing, setUndoing] = React.useState(null);   // merge_id being undone
    const [armed, setArmed] = React.useState(null);       // merge_id awaiting "Yes, undo"
    const [err, setErr] = React.useState(null);
    async function undo(mergeId) {
      setUndoing(mergeId); setErr(null);
      try { await window.LedgerAPI.undoAgencyMerge(mergeId); onChanged && onChanged(); }
      catch (e) { setErr(e.message || String(e)); }
      setUndoing(null); setArmed(null);
    }
    const people = Array.from(new Set(rows.map((r) => r.decided_email).filter(Boolean))).sort();
    const shown = person ? rows.filter((r) => r.decided_email === person) : rows;
    const edited = (r) => {
      const o = r.detail && r.detail.original;
      if (!o) return false;
      if (r.queue === "title") return (o.alias || null) !== (r.detail.alias || null) ||
        (o.summary || null) !== (r.detail.summary || null) || (o.notes || null) !== (r.detail.notes || null);
      if (r.queue === "document") return ["doc_type_key", "title", "document_date", "source"].some((k) => (o[k] || null) !== (r.detail[k] || null));
      if (r.queue === "agency" && r.sub_kind === "state_resource")
        return ["kind", "label", "url"].some((k) => (o[k] || null) !== (r.detail[k] || null));
      if (r.queue === "agency" && r.sub_kind === "courthouse")
        return ["name", "county", "state", "city", "address", "court_entity_id"].some((k) => (o[k] || null) !== (r.detail[k] || null));
      if (r.queue === "agency" && r.sub_kind === "contact")
        return ["role", "name", "title", "email", "phone", "notes"].some((k) => (o[k] || null) !== (r.detail[k] || null));
      if (r.queue === "agency" && r.sub_kind === "quirk")
        return ["pickup", "release_condition", "release_condition_days", "typical_turnaround_days"].some((k) => (o[k] == null ? null : String(o[k])) !== (r.detail[k] == null ? null : String(r.detail[k])));
      if (r.queue === "agency") return r.sub_kind === "merge" ? (o.keep || null) !== (r.detail.keep || null)
        : ["county", "county_kind", "scope"].some((k) => (o[k] || null) !== (r.detail[k] || null));
      return true; // facts carry `original` only when a reviewer changed something
    };
    return (
      <div>
        <div className="md-filters">
          <label className="field-label md-inline">Decided by
            <select className="lc-input" value={person} onChange={(e) => setPerson(e.target.value)}>
              <option value="">Everyone</option>
              {people.map((p) => <option key={p} value={p}>{p}</option>)}
            </select>
          </label>
          <span className="tr-done">Last 7 days · {shown.length}</span>
        </div>
        {err && <div className="login__err" style={{ marginBottom: 12 }}>{err}</div>}
        {shown.length === 0 ? (
          <div className="ledger-empty"><Ic n="inbox" /><b>No decisions yet</b><span>Approvals and rejections from the last 7 days show here.</span></div>
        ) : (
          <table className="md-table">
            <thead><tr><th>When</th><th>Who</th><th>Matter</th><th>Change</th><th>Decision</th></tr></thead>
            <tbody>
              {shown.map((r) => (
                <tr key={r.queue + r.proposal_id}>
                  <td className="md-nowrap">{fmtWhen(r.decided_at)}</td>
                  <td>{who(r.decided_email)}</td>
                  <td>{r.matter_id
                    ? <button type="button" className="md-mlink" onClick={() => onOpenMatter && onOpenMatter(r.matter_id)}>{r.matter_label}</button>
                    : r.matter_label}</td>
                  <td><span className={"md-kind md-kind--" + r.kind}>{KIND_LABEL[r.kind]}</span> {r.summary}
                    {edited(r) && <span className="md-edited" title="Changed by the reviewer before approving">edited</span>}
                    {r.decision_note && <div className="md-note">{r.decision_note}</div>}
                    {r.queue === "agency" && r.sub_kind === "merge" && r.status === "approved" && r.detail && r.detail.merge_id && (
                      r.detail.undone ? <div className="md-note">Undone</div>
                      : armed === r.detail.merge_id ? (
                        <div className="md-note">Split them apart again?{" "}
                          <button type="button" className="link-btn" onClick={() => setArmed(null)} disabled={!!undoing}>Cancel</button>{" "}
                          <button type="button" className="link-btn" onClick={() => undo(r.detail.merge_id)} disabled={!!undoing}>
                            {undoing === r.detail.merge_id ? "Undoing…" : "Yes, undo"}</button>
                        </div>
                      ) : <div><button type="button" className="link-btn" onClick={() => setArmed(r.detail.merge_id)}>Undo merge</button></div>
                    )}</td>
                  <td><span className={"md-dec md-dec--" + r.status}>{r.status === "approved" ? "Approved" : "Rejected"}</span></td>
                </tr>
              ))}
            </tbody>
          </table>
        )}
      </div>
    );
  }

  const PAGE = 40; // matter cards per page; 398 at once is a long scroll and a slow paint

  function MetadataDesk({ onOpenMatter }) {
    const { Button } = DS;
    const [tab, setTab] = React.useState("pending");
    const [rows, setRows] = React.useState(null);
    const [decided, setDecided] = React.useState(null);
    const [counts, setCounts] = React.useState(null);
    const [kind, setKind] = React.useState("all");
    const [agSub, setAgSub] = React.useState("all"); // inside Agencies: location, merge, state_resource, contact, quirk
    const [flagged, setFlagged] = React.useState(false);
    const [band, setBand] = React.useState("all");
    const [q, setQ] = React.useState("");
    const [shown, setShown] = React.useState(PAGE);
    const [selected, setSelected] = React.useState(() => new Set());
    const [err, setErr] = React.useState(null);
    const [msg, setMsg] = React.useState(null);
    const [armed, setArmed] = React.useState(null); // "approve" | "reject" | null
    const [bulkBusy, setBulkBusy] = React.useState(false);
    const [partial, setPartial] = React.useState(false);   // first pages drawn, the rest still loading
    // Decided while later pages were still arriving: a page must not bring them back.
    const decidedIds = React.useRef(new Set());

    const refreshCounts = React.useCallback(() => {
      window.LedgerAPI.metadataCounts().then(setCounts).catch(() => {});
    }, []);
    const load = React.useCallback(() => {
      setErr(null);
      const keep = (r) => r.filter((x) => !decidedIds.current.has(x.proposal_id));
      window.LedgerAPI.metadataCounts().then(setCounts).catch(() => {});
      window.LedgerAPI.metadataQueue({ status: "pending", onPage: (r) => { setRows(keep(r)); setPartial(true); } })
        .then((r) => { setRows(keep(r)); setPartial(false); })
        .catch((e) => { setErr(e.message || String(e)); setPartial(false); });
    }, []);
    React.useEffect(() => { load(); }, [load]);
    React.useEffect(() => {
      if (tab !== "decided") return;
      setDecided(null);
      window.LedgerAPI.metadataQueue({ status: "decided" }).then(setDecided).catch((e) => setErr(e.message || String(e)));
    }, [tab]);
    React.useEffect(() => { icons(); });

    // Everything but the confidence band, so each band chip can count what it would show.
    const unbanded = React.useMemo(() => {
      const needle = q.trim().toLowerCase();
      return (rows || []).filter((r) =>
        (kind === "all" || r.kind === kind) &&
        (kind !== "agency" || agSub === "all" || r.sub_kind === agSub) &&
        (!flagged || !!r.flag || isStale(r)) &&
        (!needle || (r.matter_label || "").toLowerCase().includes(needle) ||
                    (r.display_id || "").toLowerCase().includes(needle) ||
                    (r.summary || "").toLowerCase().includes(needle)));
    }, [rows, kind, agSub, flagged, q]);
    const filtered = React.useMemo(() => unbanded.filter((r) => inBand(r, band)), [unbanded, band]);
    const bandCount = (id) => (rows ? unbanded.filter((r) => inBand(r, id)).length : null);

    // Grouped by matter: a case is judged once, with everything proposed for it together.
    const groups = React.useMemo(() => {
      const by = new Map();
      filtered.forEach((r) => {
        // A document with no matter (an agency's form) groups under its own record.
        const key = r.matter_id || ("rec:" + ((r.detail && r.detail.entity_id) || r.proposal_id));
        let g = by.get(key);
        if (!g) {
          g = { key, matter_id: r.matter_id, label: r.matter_label, display_id: r.display_id, state: r.state, lane: r.lane, rows: [], newest: r.created_at,
                agency_id: r.queue === "agency" ? (r.detail && r.detail.entity_id) : null };
          by.set(key, g);
        }
        g.rows.push(r);
        if (r.created_at > g.newest) g.newest = r.created_at;
      });
      return Array.from(by.values()).sort((a, b) => (b.rows.length - a.rows.length) || (b.newest > a.newest ? 1 : -1));
    }, [filtered]);

    React.useEffect(() => { setShown(PAGE); }, [kind, agSub, flagged, band, q]);

    const eligible = filtered.filter(bulkEligible);
    const selRows = (rows || []).filter((r) => selected.has(r.proposal_id));

    function toggle(r) {
      setSelected((s) => { const n = new Set(s); n.has(r.proposal_id) ? n.delete(r.proposal_id) : n.add(r.proposal_id); return n; });
      setArmed(null);
    }
    function removeRows(ids) {
      ids.forEach((id) => decidedIds.current.add(id));
      const drop = new Set(ids);
      setRows((rs) => (rs || []).filter((r) => !drop.has(r.proposal_id)));
      setSelected((s) => { const n = new Set(s); ids.forEach((id) => n.delete(id)); return n; });
    }
    function done(r, how) {
      removeRows([r.proposal_id]);
      // An edited approval applied something other than the summary line, so don't echo it.
      setMsg(how === "edited" ? "Approved with your edits: " + (r.matter_label || "matter")
             : (how === "approved" ? "Approved: " : "Rejected: ") + r.summary);
      // A merge closes the dropped agency's other proposals on the server; reload so they go.
      // Hiding a row moved to the state's links closes its other proposals the same way.
      if (r.queue === "agency" && (r.sub_kind === "merge" || r.sub_kind === "state_resource") && how !== "rejected") load();
      else refreshCounts();
    }

    function selectGroup(groupRows, on) {
      setSelected((s) => { const n = new Set(s); groupRows.forEach((r) => (on ? n.add(r.proposal_id) : n.delete(r.proposal_id))); return n; });
      setArmed(null);
    }

    // `target` defaults to the whole selection (the bar at the top); a matter card
    // passes just its own selected rows.
    async function runBulk(decision, target) {
      const list = target || selRows;
      setBulkBusy(true); setErr(null); setMsg(null);
      try {
        const res = await window.LedgerAPI.decideBulk(list.map((r) => ({ queue: r.queue, id: r.proposal_id, decision })));
        const ok = res.filter((x) => x.ok).map((x) => x.id);
        // An agency proposal overtaken by a merge earlier in the same batch is closed, not failed.
        const gone = res.filter((x) => !x.ok && x.superseded).map((x) => x.id);
        const bad = res.filter((x) => !x.ok && !x.superseded);
        removeRows(ok.concat(gone));
        setMsg(`${ok.length} ${decision === "approve" ? "approved" : "rejected"}.` +
               (gone.length ? ` ${gone.length} closed because the agency had just been merged.` : ""));
        if (bad.length) setErr(`${bad.length} not applied: ` + Array.from(new Set(bad.map((x) => x.error))).slice(0, 3).join("; "));
      } catch (e) { setErr(e.message || String(e)); }
      setBulkBusy(false); setArmed(null);
      refreshCounts();
    }

    const kindCount = (k) => (counts ? (k === "all" ? counts.pending : counts[k]) : null);
    const selKinds = Array.from(new Set(selRows.map((r) => KIND_LABEL[r.kind]))).join(", ");

    return (
      <div className="md-desk">
        <div className="page-head">
          <div>
            <div className="eyebrow">Content Operations</div>
            <h1 className="page-title">Metadata<window.LedgerComment section="Metadata desk" /></h1>
            <div className="page-lede">
              Changes that machines propose: title fixes; court dates, status and people the Trial Tracker read
              in coverage; the type and title of documents copied in from old Drive folders; and the county of an
              agency, two agency rows that are the same agency, an agency's contacts and how it works,
              read from its notes, and courthouse buildings named in court dates. Nothing changes
              until someone here approves it, and every decision is recorded with your name.
            </div>
          </div>
          {counts && (
            <div className="md-stats">
              <div><b>{counts.pending}</b><span>pending</span></div>
              <div><b>{counts.decided_today_me || 0}</b><span>by you today</span></div>
              <div><b>{counts.decided_today || 0}</b><span>team today</span></div>
            </div>
          )}
        </div>

        <div className="md-tabs">
          <button type="button" className={"tagopt" + (tab === "pending" ? " tagopt--on" : "")} onClick={() => setTab("pending")}>Needs a decision</button>
          <button type="button" className={"tagopt" + (tab === "decided" ? " tagopt--on" : "")} onClick={() => setTab("decided")}>Decided</button>
        </div>

        {err && <div className="login__err" style={{ marginBottom: 12 }}>{err}</div>}
        {msg && <div className="ledger-ok" style={{ marginBottom: 12 }}>{msg}</div>}

        {tab === "decided" ? (
          decided === null ? <div className="md-loading">Loading…</div>
            : <Decided rows={decided} onOpenMatter={onOpenMatter}
                onChanged={() => window.LedgerAPI.metadataQueue({ status: "decided" }).then(setDecided).catch((e) => setErr(e.message || String(e)))} />
        ) : (
          <React.Fragment>
            <div className="md-filters">
              <div className="tr-bands">
                {KINDS.map((k) => (
                  <button key={k.id} type="button" className={"tagopt" + (kind === k.id ? " tagopt--on" : "")} onClick={() => setKind(k.id)}>
                    {k.label}{kindCount(k.id) != null && <b className="tr-n">{kindCount(k.id)}</b>}
                  </button>
                ))}
                <button type="button" className={"tagopt" + (flagged ? " tagopt--on" : "")} onClick={() => setFlagged((v) => !v)}
                  title="Flagged by the extractor, or the title changed since the proposal">
                  Needs a look{counts && <b className="tr-n">{counts.flagged}</b>}
                </button>
              </div>
              <div className="md-filters-r">
                <input className="lc-input md-search" value={q} onChange={(e) => setQ(e.target.value)} placeholder={kind === "agency" ? "Find an agency…" : "Find a matter…"} />
              </div>
            </div>

            {kind === "agency" && (
              <div className="tr-bands md-conf" role="group" aria-label="Kind of agency proposal">
                {AG_SUBS.map((x) => {
                  const n = x.id === "all" ? null : (rows || []).filter((r) => r.queue === "agency" && r.sub_kind === x.id).length;
                  if (x.id !== "all" && !n && agSub !== x.id) return null;
                  return (
                    <button key={x.id} type="button" className={"tagopt" + (agSub === x.id ? " tagopt--on" : "")} onClick={() => setAgSub(x.id)}>
                      {x.label}{n != null && <b className="tr-n">{n}</b>}
                    </button>
                  );
                })}
              </div>
            )}

            <div className="tr-bands md-conf" role="group" aria-label="Confidence">
              {BANDS.map((b) => (
                <button key={b.id} type="button" className={"tagopt" + (band === b.id ? " tagopt--on" : "")} onClick={() => setBand(b.id)}
                  title={b.id === "all" ? "Every confidence" : b.id === "low" ? "Below 0.75, or no score" : `Model confidence ${b.label}`}>
                  {b.label}{b.id !== "all" && bandCount(b.id) != null && <b className="tr-n">{bandCount(b.id)}</b>}
                </button>
              ))}
            </div>

            <div className="tr-bulk md-bulk">
              <div>
                {selRows.length > 0
                  ? <span><b>{selRows.length}</b> selected ({selKinds}). <button type="button" className="link-btn" onClick={() => { setSelected(new Set()); setArmed(null); }}>Clear</button></span>
                  : partial
                    ? <span>Loading the queue: <b>{(rows || []).length.toLocaleString()}</b> of {counts ? counts.pending.toLocaleString() : "…"}. Select-all waits until it is complete.</span>
                    : <span><b>{eligible.length}</b> in this filter are ≥ 0.90 with no flag and can be selected together.</span>}
              </div>
              <span className="tr-bulk-arm">
                {selRows.length === 0 ? (
                  <Button size="sm" variant="secondary" icon="check-square" disabled={!eligible.length || partial}
                    onClick={() => setSelected(new Set(eligible.map((r) => r.proposal_id)))}>
                    Select {eligible.length}
                  </Button>
                ) : armed ? (
                  <React.Fragment>
                    <button type="button" className="link-btn" onClick={() => setArmed(null)} disabled={bulkBusy}>Cancel</button>
                    <Button size="sm" variant={armed === "approve" ? "primary" : "secondary"} icon={armed === "approve" ? "check-check" : "x"}
                      onClick={() => runBulk(armed)} disabled={bulkBusy}>
                      {bulkBusy ? "Applying…" : `Yes, ${armed} ${selRows.length}`}
                    </Button>
                  </React.Fragment>
                ) : (
                  <React.Fragment>
                    <button type="button" className="link-btn" onClick={() => setArmed("reject")}>Reject selected</button>
                    <Button size="sm" variant="primary" icon="check-check" onClick={() => setArmed("approve")}>Approve selected ({selRows.length})</Button>
                  </React.Fragment>
                )}
              </span>
            </div>

            {rows === null && <div className="md-loading">Loading…</div>}
            {rows && groups.length === 0 && (
              <div className="ledger-empty"><Ic n="check-check" /><b>Nothing waiting here</b>
                <span>Every proposal in this filter has been decided.</span></div>
            )}
            {groups.slice(0, shown).map((g) => (
              <MatterCard key={g.key} g={g} selected={selected} onToggle={toggle}
                onSelectGroup={selectGroup} onBulk={runBulk} bulkBusy={bulkBusy}
                onDone={done} onError={setErr} onOpenMatter={onOpenMatter} />
            ))}
            {groups.length > shown && (
              <div className="md-more">
                <Button size="sm" variant="secondary" onClick={() => setShown((n) => n + PAGE)}>
                  Show more ({groups.length - shown} matters left)
                </Button>
              </div>
            )}
          </React.Fragment>
        )}
      </div>
    );
  }

  window.LedgerMetadataDesk = MetadataDesk;
})();
