/* Agencies — the global directory of every organization the team works with
   (courts, police/sheriff, prosecutor offices, records custodians, vendors).
   The Content Center does a lot of FOIA work with the same agencies, so this is
   a first-class top-level tab: find an agency, see its contact set and every
   matter + FOIA request tied to it. Assigns window.LedgerAgencies. */
(function () {
  const DS = window.LawCrimeDesignSystem_e18d68;
  const T = window.titleize;
  const V = window.LEDGER_VOCAB;
  const icons = () => setTimeout(() => window.lucide && window.lucide.createIcons(), 30);
  const opts = (arr, withBlank) => (withBlank ? [{ value: "", label: "—" }] : []).concat((arr || []).map((v) => ({ value: v, label: T(v) })));

  const Ic = ({ n }) => <span className="ic-wrap" key={n}><i data-lucide={n} /></span>;
  const fmtDay = (iso) => (iso ? new Date(iso).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", timeZone: "America/New_York" }) : "");
  const who = (email) => (email || "").split("@")[0];
  const checkedToday = (iso) => !!iso && fmtDay(iso) === fmtDay(new Date().toISOString());
  const ROLE_OPTS = () => window.AGENCY_CONTACT_ROLES.map(([value, label]) => ({ value, label }));

  // ---- Contacts with a role (0150, G3) --------------------------------------
  function ContactForm({ initial, entityId, onSaved, onCancel }) {
    const { Button, Input, Select } = DS;
    const [f, setF] = React.useState(() => ({ role: "other", name: "", title: "", email: "", phone: "", notes: "", ...(initial || {}) }));
    const [busy, setBusy] = React.useState(false);
    const [err, setErr] = React.useState(null);
    const set = (k) => (e) => setF((s) => ({ ...s, [k]: e.target.value }));
    async function save() {
      if (![f.name, f.email, f.phone].some((v) => (v || "").trim())) { setErr("Give a name, an email or a phone."); return; }
      setBusy(true); setErr(null);
      try {
        await window.LedgerAPI.saveAgencyContact({
          contact_id: f.contact_id || null, entity_id: entityId, role: f.role,
          name: f.name || null, title: f.title || null, email: f.email || null, phone: f.phone || null, notes: f.notes || null });
        onSaved();
      } catch (e) { setErr(e.message || String(e)); setBusy(false); }
    }
    return (
      <div className="pl-form" style={{ marginTop: 8 }}>
        {err && <div className="login__err" style={{ marginBottom: 10 }}>{err}</div>}
        <div className="form-grid">
          <Select label="Role" value={f.role} onChange={set("role")} options={ROLE_OPTS()} />
          <Input label="Name" value={f.name || ""} onChange={set("name")} placeholder="Person, or leave blank for an inbox" />
          <Input label="Title" value={f.title || ""} onChange={set("title")} placeholder="e.g. Public Information Officer" />
          <Input label="Email" value={f.email || ""} onChange={set("email")} />
          <Input label="Phone" value={f.phone || ""} onChange={set("phone")} />
          <Input label="Note" value={f.notes || ""} onChange={set("notes")} placeholder="Direct line, very helpful…" />
        </div>
        <div className="form-actions">
          <Button variant="ghost" onClick={onCancel} disabled={busy}>Cancel</Button>
          <Button variant="primary" icon="check" onClick={save} disabled={busy}>{busy ? "Saving…" : "Save contact"}</Button>
        </div>
      </div>
    );
  }

  function ContactsPanel({ entityId, readOnly, onChanged }) {
    const [rows, setRows] = React.useState(null);
    const [editing, setEditing] = React.useState(null); // contact_id | "new"
    const [err, setErr] = React.useState(null);
    const [busy, setBusy] = React.useState(null);
    const load = React.useCallback(() => {
      window.LedgerAPI.listAgencyContacts(entityId).then(setRows).catch((e) => setErr(e.message || String(e)));
    }, [entityId]);
    React.useEffect(() => { setRows(null); setEditing(null); load(); }, [load]);
    React.useEffect(() => { icons(); }, [rows, editing]);
    const changed = () => { setEditing(null); load(); onChanged && onChanged(); };
    async function act(id, fn) {
      setBusy(id); setErr(null);
      try { await fn(); changed(); } catch (e) { setErr(e.message || String(e)); }
      finally { setBusy(null); }
    }
    if (rows === null) return <div className="pl-empty">{err || "Loading contacts…"}</div>;
    return (
      <div className="ag-people">
        {err && <div className="login__err" style={{ marginBottom: 10 }}>{err}</div>}
        {rows.length === 0 && editing !== "new" && <div className="pl-empty">No contacts yet. Add the PIO, court administrator or records inbox.</div>}
        {rows.map((c) => (editing === c.contact_id ? (
          <ContactForm key={c.contact_id} entityId={entityId} initial={c} onCancel={() => setEditing(null)} onSaved={changed} />
        ) : (
          <div key={c.contact_id} className={"ag-person" + (c.is_primary ? " ag-person--primary" : "")}>
            <span className={"chip ag-role ag-role--" + c.role}>{window.agencyRoleLabel(c.role)}</span>
            <div className="ag-person-main">
              <div className="ag-person-name">
                {c.name ? <b>{c.name}</b> : <b className="ag-inbox">{c.email ? "Inbox" : "Phone"}</b>}
                {c.title && <span className="ag-person-title">{c.title}</span>}
                {c.is_primary && <span className="ag-primary" title="Shown on cards and the request panel">Primary</span>}
              </div>
              <div className="ag-contact ag-contact--row">
                {c.email && <span><i data-lucide="mail" /> <a href={"mailto:" + c.email}>{c.email}</a></span>}
                {c.phone && <span><i data-lucide="phone" /> {c.phone}</span>}
              </div>
              {c.notes && c.notes.trim() && <div className="ag-person-note">{c.notes.trim()}</div>}
              <div className="ag-person-meta">
                {c.verified_at
                  ? <span className={checkedToday(c.verified_at) ? "ag-checked" : ""}>Checked by {who(c.verified_email)} · {checkedToday(c.verified_at) ? "today" : fmtDay(c.verified_at)}</span>
                  : <span>Not checked yet</span>}
                {c.source === "proposal" && <span> · from the agency notes</span>}
              </div>
            </div>
            {!readOnly && (
              <span className="pl-resact">
                {checkedToday(c.verified_at)
                  ? <span className="ag-checked" title={"Checked by " + who(c.verified_email) + " today"}><Ic n="check" /> Checked today</span>
                  : <button type="button" className="link-btn" disabled={!!busy} title="Mark as checked today"
                      onClick={() => act(c.contact_id, () => window.LedgerAPI.verifyAgencyContact(c.contact_id))}>{busy === c.contact_id ? "Saving…" : "Still right"}</button>}
                {!c.is_primary && <button type="button" className="link-btn" disabled={!!busy}
                  onClick={() => act(c.contact_id, () => window.LedgerAPI.saveAgencyContact({ ...c, is_primary: true }))}>Make primary</button>}
                <button type="button" className="link-btn" disabled={!!busy} onClick={() => setEditing(c.contact_id)}>Edit</button>
                <button type="button" className="link-btn" disabled={!!busy} onClick={() => {
                  if (window.confirm(`Remove ${c.name || c.email || c.phone}?`)) act(c.contact_id, () => window.LedgerAPI.removeAgencyContact(c.contact_id));
                }}>Remove</button>
              </span>
            )}
          </div>
        )))}
        {!readOnly && (editing === "new"
          ? <ContactForm entityId={entityId} onCancel={() => setEditing(null)} onSaved={changed} />
          : <button type="button" className="link-btn pl-add" onClick={() => setEditing("new")}><Ic n="plus" /> Add a contact</button>)}
      </div>
    );
  }

  // ---- Records requests: how the agency handles them, structured quirks next to the free notes (0150, G3) ----
  function QuirksPanel({ e, readOnly, onSaved }) {
    const { Button, Input, Select } = DS;
    const [editing, setEditing] = React.useState(false);
    const [f, setF] = React.useState(null);
    const [busy, setBusy] = React.useState(false);
    const [err, setErr] = React.useState(null);
    React.useEffect(() => { icons(); }, [editing]);
    const chips = window.agencyQuirkChips(e);
    const start = () => {
      setF({ pickup: e.pickup || "", release_condition: e.release_condition || "",
             release_condition_days: e.release_condition_days == null ? "" : String(e.release_condition_days),
             typical_turnaround_days: e.typical_turnaround_days == null ? "" : String(e.typical_turnaround_days) });
      setErr(null); setEditing(true);
    };
    const set = (k) => (ev) => setF((s) => ({ ...s, [k]: ev.target.value }));
    async function save() {
      const days = f.release_condition === "after_adjudication_days" ? parseInt(f.release_condition_days, 10) : null;
      if (f.release_condition === "after_adjudication_days" && !(days >= 1)) { setErr("How many days after adjudication?"); return; }
      const turn = f.typical_turnaround_days.trim() === "" ? null : parseInt(f.typical_turnaround_days, 10);
      if (turn != null && !(turn >= 0)) { setErr("Turnaround is a number of days."); return; }
      setBusy(true); setErr(null);
      try {
        await window.LedgerAPI.updateEntity(e.entity_id, {
          pickup: f.pickup || null, release_condition: f.release_condition || null,
          release_condition_days: days, typical_turnaround_days: turn });
        setEditing(false); onSaved();
      } catch (ex) { setErr(ex.message || String(ex)); }
      finally { setBusy(false); }
    }
    if (editing) {
      return (
        <div className="pl-form">
          {err && <div className="login__err" style={{ marginBottom: 10 }}>{err}</div>}
          <div className="form-grid">
            <Select label="Pickup" value={f.pickup} onChange={set("pickup")}
              options={[{ value: "", label: "Not known" }].concat(window.AGENCY_PICKUPS.map(([value, label]) => ({ value, label })))} />
            <Select label="Release" value={f.release_condition} onChange={set("release_condition")}
              options={[{ value: "", label: "Not known" }].concat(window.AGENCY_RELEASES.map(([value, label]) => ({ value, label })))} />
            {f.release_condition === "after_adjudication_days" &&
              <Input label="Days after adjudication" value={f.release_condition_days} onChange={set("release_condition_days")} placeholder="e.g. 60" />}
            <Input label="Typical turnaround, days (staff estimate)" value={f.typical_turnaround_days} onChange={set("typical_turnaround_days")} placeholder="e.g. 45" />
          </div>
          <div className="form-actions">
            <Button variant="ghost" onClick={() => setEditing(false)} disabled={busy}>Cancel</Button>
            <Button variant="primary" icon="check" onClick={save} disabled={busy}>{busy ? "Saving…" : "Save"}</Button>
          </div>
        </div>
      );
    }
    return (
      <div>
        {chips.length
          ? <div className="chips-row">{chips.map((c) => <span key={c.k} className={"chip" + (c.warn ? " pl-chip--warn" : "")}>{c.text}</span>)}</div>
          : <div className="pl-empty">Nothing recorded about pickup, release or turnaround.</div>}
        {e.typical_turnaround_days != null && <div className="ag-person-meta" style={{ marginTop: 6 }}>Turnaround is the desk's estimate, not a measured time.</div>}
        {!readOnly && <button type="button" className="link-btn pl-add" onClick={start}><Ic n="pencil" /> Edit</button>}
      </div>
    );
  }

  // ---- One agency: contact + everything it touches -------------------------
  function AgencyProfile({ id, onBack, onOpenMatter, onOpenAgency, onOpenPlace, onOpenCourthouse }) {
    const { Button, Input, Select } = DS;
    const [data, setData] = React.useState(null);
    const [err, setErr] = React.useState(null);
    const [editing, setEditing] = React.useState(false);
    const [f, setF] = React.useState(null);
    const [busy, setBusy] = React.useState(false);
    const reload = React.useCallback(() => {
      window.LedgerAPI.getEntityProfile(id).then(setData).catch((e) => setErr(e.message || String(e)));
    }, [id]);
    React.useEffect(() => { setData(null); setErr(null); setEditing(false); reload(); }, [id, reload]);
    React.useEffect(() => { icons(); }, [data, editing]);

    const back = <div className="crumb" onClick={onBack}><i data-lucide="arrow-left" /> All agencies</div>;
    if (err) return <div>{back}<div className="login__err">{err}</div></div>;
    if (!data) return <div>{back}<div style={{ color: "var(--text-muted)", fontSize: 13 }}>Loading…</div></div>;

    const e = data.entity;
    const sub = [e.entity_type && T(e.entity_type), e.city, window.agencyPlace(e) || e.state].filter(Boolean).join(" · ");
    const foiaMatters = data.matters.filter((m) => (m.involvement || []).some((t) => /FOIA/i.test(t))).length;

    const startEdit = () => {
      setF({
        entity_name: e.entity_name || "", entity_type: e.entity_type || "other",
        city: e.city || "", state: e.state || "", county: e.county || "", website: e.website || "",
        notes: e.notes || "",
      });
      setErr(null); setEditing(true);
    };
    const set = (k) => (ev) => setF((s) => ({ ...s, [k]: ev.target.value }));
    async function saveEdit() {
      if (!f.entity_name.trim()) { setErr("Agency name is required."); return; }
      setBusy(true); setErr(null);
      // A county places the agency locally (0148). Clearing it leaves a statewide or
      // federal placement alone; those are set on the Metadata desk.
      const cty = f.county.trim().replace(/\s+(county|parish|borough)$/i, "");
      const place = cty
        ? { county: cty, county_kind: e.county_kind || "county", scope: "local" }
        : (e.county ? { county: null, county_kind: null } : {});
      try {
        await window.LedgerAPI.updateEntity(id, {
          entity_name: f.entity_name.trim(), entity_type: f.entity_type || null,
          city: f.city || null, state: (f.state || "").trim().toUpperCase() || null, website: f.website || null,
          notes: (f.notes || "").trim() || null,
          ...place,
        });
        setEditing(false); reload();
      } catch (ex) { setErr(ex.message || String(ex)); }
      finally { setBusy(false); }
    }

    return (
      <div>
        {back}
        <div className="page-head">
          <div>
            <div className="eyebrow">Organization{sub ? " · " + sub : ""}</div>
            <h1 className="page-title">{e.entity_name}<window.LedgerComment section="Agency profile" /></h1>
          </div>
          {!editing && !e.merged_into && !e.moved_to_resource && <Button variant="secondary" icon="pencil" onClick={startEdit}>Edit</Button>}
        </div>

        {e.merged_into && (
          <div className="tr-warn" style={{ marginBottom: 18 }}>
            <span className="ic-wrap" key="merge"><i data-lucide="git-merge" /></span> This was a duplicate and has been merged.
            Its requests, matters and documents are on the agency it was merged into.{" "}
            {onOpenAgency && <button type="button" className="link-btn" onClick={() => onOpenAgency(e.merged_into)}>Open that agency</button>}
          </div>
        )}

        {e.moved_to_resource && (
          <div className="tr-warn" style={{ marginBottom: 18 }}>
            <span className="ic-wrap" key="moved"><i data-lucide="map-pin" /></span> This was a statewide link, not an agency.
            It is now listed under {e.state ? (window.LEDGER_STATE_NAME[e.state] || e.state) : "its state"}'s links and hidden from the directory.{" "}
            {onOpenPlace && e.state && <button type="button" className="link-btn" onClick={() => onOpenPlace({ state: e.state })}>Open {e.state}</button>}
            {window.LedgerCaps && (window.LedgerCaps.isContentOps || window.LedgerCaps.isAdmin) && (
              <button type="button" className="link-btn" style={{ marginLeft: 12 }} disabled={busy} onClick={async () => {
                setBusy(true);
                try { await window.LedgerAPI.restoreMovedAgency(id); reload(); } catch (ex) { setErr(ex.message || String(ex)); }
                finally { setBusy(false); }
              }}>Put it back in the directory</button>
            )}
          </div>
        )}

        {editing ? (
          <DS.Card style={{ marginBottom: 22 }}><DS.Card.Body>
            <div className="form-grid">
              <Input label="Agency name *" value={f.entity_name} onChange={set("entity_name")} />
              <Select label="Agency type" value={f.entity_type} onChange={set("entity_type")} options={opts(V.entity_type)} />
              <Input label="City" value={f.city} onChange={set("city")} />
              <Input label="State" value={f.state} onChange={set("state")} placeholder="e.g. FL" />
              <Input label="County" value={f.county} onChange={set("county")} placeholder="e.g. Maricopa" />
              <Input label="Website / records portal" value={f.website} onChange={set("website")} placeholder="https://" />
              <div className="full">
                <label className="field-label">Notes</label>
                <textarea className="lc-input ledger-textarea" value={f.notes} onChange={set("notes")} placeholder="Portal quirks, typical turnaround, fee schedule, etc." />
              </div>
            </div>
            <div className="form-actions">
              <Button variant="ghost" onClick={() => { setEditing(false); setErr(null); }} disabled={busy}>Cancel</Button>
              <Button variant="primary" icon="check" onClick={saveEdit} disabled={busy}>{busy ? "Saving…" : "Save agency"}</Button>
            </div>
          </DS.Card.Body></DS.Card>
        ) : null}

        <div className="kpis" style={{ maxWidth: 520, marginBottom: 22 }}>
          <div className="cell"><DS.StatTile value={String(data.matters.length)} label="Matters" /></div>
          <div className="cell"><DS.StatTile value={String(foiaMatters)} label="Matters w/ FOIA" /></div>
        </div>

        {/* G3: contacts by role, then how the agency works. Notes stay beside them (spec D5). */}
        <div className="ag-detail-two">
          <div>
            <div className="form-subhead">Contacts</div>
            <DS.Card style={{ marginBottom: 20 }}><DS.Card.Body>
              <ContactsPanel entityId={id} readOnly={!!(e.merged_into || e.moved_to_resource)} onChanged={reload} />
            </DS.Card.Body></DS.Card>
          </div>
          <div>
            <div className="form-subhead">Records requests</div>
            <DS.Card style={{ marginBottom: 20 }}><DS.Card.Body>
              <QuirksPanel e={e} readOnly={!!(e.merged_into || e.moved_to_resource)} onSaved={reload} />
              {e.website && (
                <div className="ag-contact" style={{ marginTop: 12 }}>
                  <span><i data-lucide="globe" /> <a href={e.website} target="_blank" rel="noreferrer">{String(e.website).replace(/^https?:\/\//, "")}</a></span>
                </div>
              )}
              {e.notes && e.notes.trim() && <div className="ag-notes">{e.notes.trim()}</div>}
            </DS.Card.Body></DS.Card>
          </div>
        </div>

        {/* G4: a court's buildings and their production notes (Content Ops only, D12). */}
        {e.entity_type === "court" && !e.merged_into && window.LedgerCourthouses && onOpenCourthouse && (
          <window.LedgerCourthouses.Section courtId={id} state={e.state} county={e.county} onOpen={onOpenCourthouse} />
        )}

        {/* Documents: blank request forms, filing instructions, fee schedules (attachments A2) */}
        {window.LedgerAttachments && (
          <DS.Card style={{ marginBottom: 20 }}><DS.Card.Body>
            <window.LedgerAttachments entityType="agency" entityId={id} />
          </DS.Card.Body></DS.Card>
        )}

        <div className="form-subhead">Matters ({data.matters.length})</div>
        {data.matters.length === 0
          ? <div style={{ fontSize: 12.5, color: "var(--text-muted)" }}>Not linked to any matter yet.</div>
          : (
            <DS.Card><DS.Card.Body style={{ padding: 0 }}>
              <ul className="linklist">
                {data.matters.map((m) => (
                  <li key={m.matter_id} style={{ cursor: "pointer" }} onClick={() => onOpenMatter && onOpenMatter(m.matter_id)}>
                    <div className="li-main">
                      <b>{m.display_id ? m.display_id + " · " : ""}{m.working_title}</b>
                      {m.matter_summary && <span className="li-lede">{m.matter_summary}</span>}
                      <span>{window.vocabLabel("matter_type", m.matter_type)}{m.legal_status ? " · " + window.vocabLabel("legal_status", m.legal_status) : ""}</span>
                    </div>
                    {(m.involvement || []).map((t, i) => <span key={i} className="chip chip--inv">{t}</span>)}
                    <i data-lucide="chevron-right" style={{ width: 16, height: 16, color: "var(--lc-slate-300)" }} />
                  </li>
                ))}
              </ul>
            </DS.Card.Body></DS.Card>
          )}
      </div>
    );
  }

  // ---- Add a new agency to the directory -----------------------------------
  // G3 (spec D7, D19, D20): Ledger is where agencies get added, so this is the duplicate gate.
  // State is required; the county is suggested from the name; agencies with a similar name in
  // that state are listed before saving (a warning, never a block).
  const COUNTY_IN_NAME = /\b((?:St\.?|Saint|San|Santa|Los|Las|El|La|De|Del|Fort|Ft\.?|Port|Mount|Mt\.?|New|North|South|East|West|Upper|Lower)?\s?[A-Z][\w'’.-]*(?:\s[A-Z][\w'’.-]*)?)\s+(County|Parish|Borough)\b/;
  function suggestCounty(name) {
    const m = (name || "").match(COUNTY_IN_NAME);
    return m ? m[1].trim() : "";
  }

  function AddAgency({ onCancel, onCreated, onOpenAgency }) {
    const { Button, Input, Select } = DS;
    const blank = { entity_name: "", entity_type: "police_department", state: "", county: "", city: "", website: "",
                    role: "records_coordinator", contact_name: "", email: "", phone: "", notes: "" };
    const [f, setF] = React.useState(blank);
    const [countyTouched, setCountyTouched] = React.useState(false);
    const [alikes, setAlikes] = React.useState([]);
    const [busy, setBusy] = React.useState(false);
    const [err, setErr] = React.useState(null);
    const set = (k) => (e) => setF((s) => ({ ...s, [k]: e.target.value }));
    React.useEffect(() => { icons(); });
    // The county follows the name until someone types in it.
    React.useEffect(() => {
      if (!countyTouched) setF((s) => ({ ...s, county: suggestCounty(s.entity_name) }));
    }, [f.entity_name, countyTouched]);
    // Look-alikes, 400 ms after typing stops.
    React.useEffect(() => {
      const name = f.entity_name.trim(), st = f.state.trim().toUpperCase();
      if (name.length < 4) { setAlikes([]); return; }
      let live = true;
      const t = setTimeout(() => {
        window.LedgerAPI.agencyLookalikes(name, /^[A-Z]{2}$/.test(st) ? st : null)
          .then((r) => { if (live) setAlikes(r); }).catch(() => { if (live) setAlikes([]); });
      }, 400);
      return () => { live = false; clearTimeout(t); };
    }, [f.entity_name, f.state]);

    async function submit() {
      if (!f.entity_name.trim()) { setErr("Agency name is required."); return; }
      const st = (f.state || "").trim().toUpperCase();
      if (!/^[A-Z]{2}$/.test(st)) { setErr("State is required: the two-letter code, e.g. FL."); return; }
      setBusy(true); setErr(null);
      try {
        const ent = await window.LedgerAPI.createEntity({
          entity_name: f.entity_name.trim(), entity_type: f.entity_type || null,
          state: st, county: f.county || null, city: f.city || null, website: f.website || null, notes: (f.notes || "").trim() || null,
        });
        if ([f.contact_name, f.email, f.phone].some((v) => (v || "").trim())) {
          await window.LedgerAPI.saveAgencyContact({ entity_id: ent.entity_id, role: f.role, name: f.contact_name || null,
            email: f.email || null, phone: f.phone || null, is_primary: true });
        }
        onCreated(ent.entity_id);
      } catch (e) { setErr(e.message || String(e)); setBusy(false); }
    }
    return (
      <DS.Card style={{ marginBottom: 18 }}><DS.Card.Body>
        {err && <div className="login__err" style={{ marginBottom: 12 }}>{err}</div>}
        <div className="form-grid">
          <Input label="Agency name *" value={f.entity_name} onChange={set("entity_name")} placeholder="e.g. Miami-Dade Police Dept" />
          <Select label="Agency type" value={f.entity_type} onChange={set("entity_type")} options={opts(V.entity_type)} />
          <Input label="State *" value={f.state} onChange={(e) => setF((s) => ({ ...s, state: e.target.value.toUpperCase().slice(0, 2) }))} placeholder="e.g. FL" />
          <Input label={"County" + (!countyTouched && f.county ? " (from the name)" : "")} value={f.county}
            onChange={(e) => { setCountyTouched(true); set("county")(e); }} placeholder="e.g. Maricopa" />
          <Input label="City" value={f.city} onChange={set("city")} />
          <Input label="Website / records portal" value={f.website} onChange={set("website")} placeholder="https://" />
        </div>
        {alikes.length > 0 && (
          <div className="ag-alikes">
            <Ic n="copy" /> <b>Already in Ledger?</b> {alikes.length === 1 ? "An agency" : "Agencies"} with a similar name{/^[A-Z]{2}$/.test(f.state) ? " in " + f.state : ""}:
            <ul>
              {alikes.map((a) => (
                <li key={a.entity_id}>
                  <button type="button" className="link-btn" onClick={() => onOpenAgency && onOpenAgency(a.entity_id)}>{window.agencyLabel(a)}</button>
                  {a.entity_type ? <span className="ag-person-meta"> · {T(a.entity_type)}</span> : null}
                </li>
              ))}
            </ul>
            If one of these is it, open it instead. Adding a second copy splits its requests and contacts.
          </div>
        )}
        <div className="form-subhead" style={{ marginTop: 14 }}>First contact (optional)</div>
        <div className="form-grid">
          <Select label="Role" value={f.role} onChange={set("role")} options={ROLE_OPTS()} />
          <Input label="Name" value={f.contact_name} onChange={set("contact_name")} placeholder="Person, or blank for an inbox" />
          <Input label="Email" value={f.email} onChange={set("email")} />
          <Input label="Phone" value={f.phone} onChange={set("phone")} />
          <div className="full">
            <label className="field-label">Notes</label>
            <textarea className="lc-input ledger-textarea" value={f.notes} onChange={set("notes")} placeholder="Portal quirks, typical turnaround, fee schedule, etc." />
          </div>
        </div>
        <div className="form-actions">
          <Button variant="ghost" onClick={onCancel} disabled={busy}>Cancel</Button>
          <Button variant="primary" icon="check" onClick={submit} disabled={busy}>{busy ? "Saving…" : alikes.length ? "Add anyway" : "Add agency"}</Button>
        </div>
      </DS.Card.Body></DS.Card>
    );
  }

  // ---- Directory ------------------------------------------------------------
  function Agencies({ onOpenMatter, openIntent }) {
    const { Button, Input, Select } = DS;
    const [rows, setRows] = React.useState(null);
    const [q, setQ] = React.useState("");
    const [type, setType] = React.useState("all");
    const [stateFilter, setStateFilter] = React.useState("all");
    const [countyFilter, setCountyFilter] = React.useState("all");
    const [err, setErr] = React.useState(null);
    const [selected, setSelected] = React.useState(() => (openIntent && openIntent.id) || null); // entity_id
    // G4: a courthouse page (Content Ops). Reached from a county, a court, Search or a court date.
    const [courthouse, setCourthouse] = React.useState(() => (openIntent && openIntent.courthouse) || readPlace().courthouse || null);
    // Opened from elsewhere (an agency on a matter page): go straight to its profile.
    React.useEffect(() => {
      if (openIntent && openIntent.id) { setCourthouse(null); setSelected(openIntent.id); }
      if (openIntent && openIntent.courthouse) { setSelected(null); setCourthouse(openIntent.courthouse); }
    }, [openIntent]);
    const [adding, setAdding] = React.useState(false);
    // G2: "By place" groups agencies by state → county (AgencyPlaces.jsx). The place is in the
    // hash (#agencies?state=AZ&county=Maricopa) so a county page can be linked and reloaded.
    const [view, setView] = React.useState(() => (readPlace().view || "type"));
    const [place, setPlaceState] = React.useState(() => { const p = readPlace(); return { state: p.state, county: p.county }; });
    const setPlace = React.useCallback((p) => { setPlaceState(p || {}); setView("place"); }, []);
    React.useEffect(() => { writePlace(selected ? null : view, place, courthouse); }, [view, place, selected, courthouse]);
    const SortTh = window.LedgerSortKit.Th;
    // Default order is unchanged — alphabetical, which is how a directory is read.
    const { sort, toggle } = window.LedgerSortKit.useSort("entity_name", "asc");

    const load = React.useCallback(() => {
      setRows(null); setErr(null);
      window.LedgerAPI.listAgenciesDirectory().then(setRows).catch((e) => setErr(e.message || String(e)));
    }, []);
    React.useEffect(() => { load(); }, [load]);
    React.useEffect(() => { icons(); }, [rows, adding, type, stateFilter, sort]);

    // Back from a courthouse returns to the court profile or county it was opened from.
    if (courthouse) {
      return <window.LedgerCourthouses.Page id={courthouse} onOpenMatter={onOpenMatter}
        onOpenAgency={(aid) => { setCourthouse(null); setSelected(aid); }}
        onOpenPlace={(p) => { setCourthouse(null); setSelected(null); setPlace(p); }}
        onBack={() => { setCourthouse(null); icons(); }} />;
    }
    if (selected) {
      return <AgencyProfile id={selected} onOpenMatter={onOpenMatter} onOpenAgency={setSelected}
        onOpenPlace={(p) => { setSelected(null); setPlace(p); }}
        onOpenCourthouse={setCourthouse}
        onBack={() => { setSelected(null); icons(); }} />;
    }

    const needle = q.trim().toLowerCase();
    // CLIENT-SIDE SORT, and that is correct HERE specifically: this directory
    // fetches every agency (~676) and filters in the browser already, so the sort
    // sees the whole set. The same code on Matters or the Asset Requests desk would
    // be wrong — those page server-side, so a browser sort would order the 50 rows
    // already on screen and claim to have ordered 11,738. SortKit draws the same
    // header either way; only the backend differs.
    // ⚠️ If this directory is ever paged (known-issues.md), the sort must move to
    // Postgres in the SAME change, or it silently becomes that lie.
    const shown = window.LedgerSortKit.sortRows((rows || [])
      .filter((a) => type === "all" || a.entity_type === type)
      .filter((a) => stateFilter === "all" || a.state === stateFilter)
      .filter((a) => countyFilter === "all" || (countyFilter === "_none" ? window.agencyUnplaced(a) : a.county === countyFilter))
      .filter((a) => !needle || [a.entity_name, a.city, a.county, a.state, a.contact_name].filter(Boolean).join(" ").toLowerCase().includes(needle)),
      sort.key, sort.dir, {
        location: (a) => [a.state, a.county, a.city].filter(Boolean).join(" "),
        contact: (a) => a.contact_name || a.phone || a.email,
        matter_count: (a) => a.matter_count || 0,
        foia_count: (a) => a.foia_count || 0,
      });
    const typesPresent = [...new Set((rows || []).map((a) => a.entity_type).filter(Boolean))].sort();
    const statesPresent = [...new Set((rows || []).map((a) => a.state).filter(Boolean))].sort();
    const countiesPresent = stateFilter === "all" ? []
      : [...new Set((rows || []).filter((a) => a.state === stateFilter).map((a) => a.county).filter(Boolean))].sort();

    return (
      <div>
        <div className="page-head">
          <div>
            <div className="eyebrow">Directory</div>
            <h1 className="page-title">Agencies<window.LedgerComment section="Agencies directory" /></h1>
          </div>
          <div style={{ display: "flex", gap: 10, alignItems: "center" }}>
            <div className="cal-viewtoggle" role="tablist" aria-label="By type or by place">
              <button type="button" className={"cal-viewbtn" + (view === "type" ? " is-on" : "")} onClick={() => setView("type")}>By type</button>
              <button type="button" className={"cal-viewbtn" + (view === "place" ? " is-on" : "")} onClick={() => setView("place")}>By place</button>
            </div>
            <Button variant="primary" icon="circle-plus" onClick={() => setAdding((a) => !a)}>{adding ? "Close" : "Add agency"}</Button>
          </div>
        </div>

        {adding && <AddAgency onCancel={() => setAdding(false)}
          onOpenAgency={(id) => { setAdding(false); setSelected(id); }}
          onCreated={(id) => { setAdding(false); load(); setSelected(id); }} />}

        {view === "place" && rows && (
          <window.LedgerAgencyPlaces.LocationView rows={rows} place={place} setPlace={setPlace}
            onOpenAgency={setSelected} onOpenCourthouse={setCourthouse}
            onShowUnplaced={(st) => { setStateFilter(st); setCountyFilter("_none"); setView("type"); }} />
        )}
        {view === "place" && rows === null && !err && <div style={{ color: "var(--text-muted)", fontSize: 13 }}>Loading…</div>}

        {view === "type" && (<>
        <div style={{ display: "flex", gap: 12, alignItems: "flex-end", marginBottom: 18, maxWidth: 980, flexWrap: "wrap" }}>
          <div style={{ width: 200 }}>
            <Select label="Type" value={type} onChange={(e) => setType(e.target.value)}
              options={[{ value: "all", label: "All agency types" }].concat(opts(typesPresent))} />
          </div>
          <div style={{ width: 160 }}>
            <Select label="Location" value={stateFilter} onChange={(e) => { setStateFilter(e.target.value); setCountyFilter("all"); }}
              options={[{ value: "all", label: "All locations" }].concat(statesPresent.map((s) => ({ value: s, label: s })))} />
          </div>
          {stateFilter !== "all" && (
            <div style={{ width: 180 }}>
              <Select label="County" value={countyFilter} onChange={(e) => setCountyFilter(e.target.value)}
                options={[{ value: "all", label: "All counties" }].concat(countiesPresent.map((c) => ({ value: c, label: c })),
                  [{ value: "_none", label: "Not placed yet" }])} />
            </div>
          )}
          <div style={{ flex: 1, minWidth: 200 }}>
            <Input label="Find an agency" icon="search" placeholder="Name, county, city, contact…" value={q} onChange={(e) => setQ(e.target.value)} />
          </div>
        </div>

        {err && <div className="login__err" style={{ marginBottom: 16 }}>{err}</div>}
        {rows === null && <div style={{ color: "var(--text-muted)", fontSize: 13 }}>Loading…</div>}
        {rows && shown.length === 0 && (
          <div className="ledger-empty"><i data-lucide="building-2" /><b>No agencies{rows.length ? " match" : " yet"}</b>
            <span>{rows.length ? "Try a broader filter." : "Add the first agency, or they’ll appear as you link them to matters."}</span></div>
        )}
        {shown.length > 0 && (
          <DS.Card><DS.Card.Body style={{ padding: 0 }}>
            <table className="tbl">
              {/* Matters and FOIA open LARGEST first — "which agency do we deal with
                  most" is the question a count column is there to answer, and A–Z on
                  a number is nobody's first ask. */}
              <thead><tr>
                <SortTh k="entity_name" sort={sort} onSort={toggle}>Agency</SortTh>
                <SortTh k="entity_type" sort={sort} onSort={toggle}>Type</SortTh>
                <SortTh k="location" sort={sort} onSort={toggle}>Location</SortTh>
                <SortTh k="contact" sort={sort} onSort={toggle}>Contact</SortTh>
                <SortTh k="matter_count" sort={sort} onSort={toggle} firstDir="desc">Matters</SortTh>
                <SortTh k="foia_count" sort={sort} onSort={toggle} firstDir="desc">FOIA</SortTh>
              </tr></thead>
              <tbody>
                {shown.map((a) => (
                  <tr key={a.entity_id} onClick={() => setSelected(a.entity_id)}>
                    <td className="cname">{a.entity_name}</td>
                    <td className="cdef">{a.entity_type ? T(a.entity_type) : "—"}</td>
                    <td className="cdef">{window.agencyPlace(a) || [a.city, a.state].filter(Boolean).join(", ") || "—"}</td>
                    <td className="cdef">{a.primary_contact
                      ? <span><span className="ag-rolemini">{window.agencyRoleLabel(a.primary_contact.role)}</span> {a.primary_contact.name || a.primary_contact.email || a.primary_contact.phone}</span>
                      : (a.contact_name || a.phone || a.email || "—")}</td>
                    <td className="cdef" style={{ fontVariantNumeric: "tabular-nums" }}>{a.matter_count || 0}</td>
                    <td className="cdef" style={{ fontVariantNumeric: "tabular-nums" }}>
                      {a.foia_count ? <span className="ag-foia-badge">{a.foia_count}</span> : "—"}
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </DS.Card.Body></DS.Card>
        )}
        </>)}
      </div>
    );
  }

  // #agencies?view=place&state=AZ&county=Maricopa, #agencies?courthouse=<id> (G4).
  // Best effort: replaceState throws on an opaque origin.
  function readPlace() {
    const h = window.location.hash || "";
    if (!h.startsWith("#agencies")) return {};
    const q = new URLSearchParams(h.split("?")[1] || "");
    return { view: q.get("view") || (q.get("state") ? "place" : null), state: q.get("state") || undefined, county: q.get("county") || undefined,
             courthouse: q.get("courthouse") || undefined };
  }
  function writePlace(view, place, courthouse) {
    try {
      const bare = window.location.pathname + window.location.search;
      if (courthouse) {
        const next = "#agencies?courthouse=" + encodeURIComponent(courthouse);
        if (window.location.hash !== next) history.replaceState(null, "", bare + next);
        return;
      }
      // Leaving a courthouse for a profile: drop its id, or a reload would reopen the courthouse.
      if (!view) { if (/[?&]courthouse=/.test(window.location.hash)) history.replaceState(null, "", bare + "#agencies"); return; }
      const q = new URLSearchParams();
      if (view === "place") {
        q.set("view", "place");
        if (place && place.state) q.set("state", place.state);
        if (place && place.county) q.set("county", place.county);
      }
      const next = "#agencies" + (q.toString() ? "?" + q.toString() : "");
      if (window.location.hash !== next) history.replaceState(null, "", bare + next);
    } catch (e) { /* link state is a convenience */ }
  }

  window.LedgerAgencies = Agencies;
})();
