/* RequestKit — shared UI + helpers for the Asset Requests worklist (migrations
   0048–0055). Consumed by the matter Asset Requests tab (Matters.jsx) and the
   cross-matter Ops queue (OpsQueue.jsx) so status tone, content-kind chips, the
   agency-playbook panel, and the agency picker stay identical and config-driven.
   Assigns window.LedgerRequestKit. */
(function () {
  const DS = window.LawCrimeDesignSystem_e18d68;
  const T = window.titleize;
  const V = window.LEDGER_VOCAB;
  const icons = () => setTimeout(() => window.lucide && window.lucide.createIcons(), 30);

  // ---- Lifecycle tone -------------------------------------------------------
  // Terminal states close a request; everything else is "in flight" (on the desk).
  const REQUEST_TERMINAL = (window.LEDGER_VOCAB && window.LEDGER_VOCAB.request_terminal)
    || ["received", "released", "unavailable", "cancelled"];
  const REQUEST_TONE = {
    received: "rq-done", released: "rq-done",
    awaiting_approval: "rq-warn", paid_awaiting: "rq-warn", quoted: "rq-warn", pending_checkback: "rq-warn",
    unavailable: "rq-dead", cancelled: "rq-dead",
  };
  const requestTone = (s) => REQUEST_TONE[s] || "rq-active";
  const isOpen = (s) => !REQUEST_TERMINAL.includes(s);
  const daysUntil = (d) => (d ? Math.ceil((new Date(d).getTime() - Date.now()) / 86400000) : null);
  // Overdue = a due date in the past on a still-open request.
  const isOverdue = (r) => r && r.due_date && isOpen(r.status) && daysUntil(r.due_date) < 0;

  function StatusPill({ status }) {
    if (!status) return null;
    return <span className={"pill pill--" + requestTone(status)}>{T(status)}</span>;
  }

  // Show label from the (admin-managed) show_taxonomy, falling back to titleize.
  function showLabelMap(shows) {
    const map = {};
    (shows || []).forEach((s) => { map[s.show_key] = s.label; });
    return (key) => (key ? (map[key] || T(key)) : null);
  }

  // Content-kind selector (multi). Read-only when onToggle is omitted.
  function ContentKindChips({ value = [], onToggle }) {
    React.useEffect(() => { icons(); });
    return (
      <div className="ck-chips">
        {V.content_kind.map((k) => {
          const on = value.includes(k);
          if (!onToggle) return on ? <span key={k} className="chip">{T(k)}</span> : null;
          return (
            <button type="button" key={k} className={"ck-chip" + (on ? " ck-chip--on" : "")}
              onClick={() => onToggle(k)}>{on ? "✓ " : ""}{T(k)}</button>
          );
        })}
      </div>
    );
  }

  // Shows picker (0159): one request can be for several shows, primary first.
  // Chips for what's picked (× removes), a select to add another.
  function ShowPicker({ value = [], shows, onChange, label = "Which shows" }) {
    const { Select } = DS;
    const name = showLabelMap(shows);
    const rest = (shows || []).filter((s) => !value.includes(s.show_key))
      .sort((a, b) => (a.label || "").localeCompare(b.label || ""))
      .map((s) => ({ value: s.show_key, label: s.label }));
    return (
      <div className="show-pick">
        <label className="field-label">{label}</label>
        <div className="show-pick-row">
          {value.map((k, i) => (
            <span key={k} className="chip show-pick-chip" title={i === 0 && value.length > 1 ? "Primary show" : undefined}>
              {name(k)}
              <button type="button" aria-label={"Remove " + name(k)} onClick={() => onChange(value.filter((x) => x !== k))}>×</button>
            </span>
          ))}
          {rest.length > 0 && (
            <Select value="" onChange={(e) => { if (e.target.value) onChange(value.concat(e.target.value)); }}
              options={[{ value: "", label: value.length ? "+ add a show" : "— which show —" }].concat(rest)} />
          )}
        </div>
      </div>
    );
  }

  // Agency picker options: this matter's agencies first (from matter_agencies +
  // involvement), then the full directory. Header rows are inert (value __hdr*).
  function buildAgencyOptions(matterAgencies, entities) {
    const maIds = new Set((matterAgencies || []).map((a) => a.entity_id));
    // Name · County, ST (0148): two Orange Counties must never look alike in a picker.
    const maOpts = (matterAgencies || []).map((a) => ({ value: a.entity_id, label: window.agencyLabel(a) }));
    const restOpts = (entities || []).filter((e) => !maIds.has(e.entity_id))
      .map((e) => ({ value: e.entity_id, label: window.agencyLabel(e) }));
    const out = [{ value: "", label: "— select agency —" }];
    if (maOpts.length) { out.push({ value: "__hdr_m", label: "──  This matter's agencies  ──" }); out.push(...maOpts); }
    if (restOpts.length) { out.push({ value: "__hdr_all", label: "──  All agencies  ──" }); out.push(...restOpts); }
    return out;
  }
  const isHeaderVal = (v) => typeof v === "string" && v.startsWith("__hdr");

  // The agency FOIA-filing playbook (entities, migration 0049) pulled forward on a
  // request so Content Center can process without hunting, plus the state residency
  // banner (state_foia_rules, migration 0050) that routes to an outside requester.
  function AgencyPanel({ playbook, stateRule, requestState, requestCounty }) {
    React.useEffect(() => { icons(); }, [playbook, stateRule]);
    const p = playbook || {};
    const rows = [
      ["Filing method", p.foia_method ? window.vocabLabel("foia_method", p.foia_method) : null],
      ["Records portal", p.foia_portal_url || p.website || null, "url"],
      ["Records email", p.foia_email || p.email || null, "email"],
      ["Request form", p.foia_forms_url || null, "url"],
      ["Typical turnaround", p.typical_turnaround_days != null ? `${p.typical_turnaround_days} days` : null],
    ].filter((r) => r[1]);
    const notes = p.foia_notes || p.notes || null;
    return (
      <div className="rq-play">
        {stateRule && stateRule.residency_required && (
          <div className="rq-resid">
            <i data-lucide="alert-triangle" />
            <div>
              <b>{requestState} requires residency</b> to file public-records requests
              {stateRule.statute_ref ? <span> ({stateRule.statute_ref})</span> : null}. File via a
              resident <b>outside requester</b> (freelancer tagged as a FOIA requester).
              {stateRule.notes ? <div className="rq-resid-note">{stateRule.notes}</div> : null}
            </div>
          </div>
        )}
        <div className="rq-play-head"><i data-lucide="book-open" /> Agency playbook</div>
        {(requestState || requestCounty) && (
          <div className="rq-play-loc"><i data-lucide="map-pin" /> {[requestCounty, requestState].filter(Boolean).join(", ")}</div>
        )}
        {rows.length === 0 && !notes && (
          <div className="rq-play-empty">No filing playbook on file for this agency yet — add records-request details on the Agencies tab.</div>
        )}
        {rows.length > 0 && (
          <dl className="rq-play-grid">
            {rows.map(([label, val, kind], i) => (
              <div className="rq-play-row" key={i}>
                <dt>{label}</dt>
                <dd>
                  {kind === "url" ? <a href={val} target="_blank" rel="noreferrer">{String(val).replace(/^https?:\/\//, "")}</a>
                    : kind === "email" ? <a href={"mailto:" + val}>{val}</a>
                    : val}
                </dd>
              </div>
            ))}
          </dl>
        )}
        {notes && <div className="rq-play-notes">{notes}</div>}
      </div>
    );
  }

  // ==== Phase 2: deliveries + cost/approval (migrations 0058–0061) ============
  const opts = (arr) => (arr || []).map((v) => ({ value: v, label: T(v) }));
  const money = (n) => (n == null || n === "" ? null
    : "$" + Number(n).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }));

  // Canonical, deterministic EVO folder name from state/city/culprit/crime (spec §5.4).
  // Sanitized to [A-Za-z0-9_], state upper-cased, capped ≤260 chars. Single source of
  // truth for the slug (client-side; the copy button + stored evo_path both use this).
  function evoSlug({ state, city, culprit, crime } = {}) {
    const clean = (s) => String(s || "").trim().replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
    const parts = [clean(state).toUpperCase(), clean(city), clean(culprit), clean(crime)].filter(Boolean);
    let slug = parts.join("_").replace(/_{2,}/g, "_");
    if (slug.length > 260) slug = slug.slice(0, 260).replace(/_+$/, "");
    return slug;
  }

  function CopyButton({ text, small }) {
    const [done, setDone] = React.useState(false);
    React.useEffect(() => { icons(); }, [done]);
    if (!text) return null;
    return (
      <button type="button" className={"rq-copy" + (small ? " rq-copy--sm" : "")}
        onClick={(e) => { e.stopPropagation();
          try { navigator.clipboard.writeText(text); setDone(true); setTimeout(() => setDone(false), 1400); } catch (_) {} }}>
        <i data-lucide={done ? "check" : "copy"} /> {done ? "Copied" : "Copy"}
      </button>
    );
  }

  function DeliveryReviewPill({ state }) {
    const tone = state === "released" ? "rq-done" : "rq-warn";
    return <span className={"pill pill--" + tone}>{state === "released" ? "Released" : "Pending review"}</span>;
  }

  // Small self-contained armed-delete (RequestKit doesn't share Matters' DeleteLink).
  function MiniDelete({ onDelete }) {
    const [armed, setArmed] = React.useState(false);
    const [busy, setBusy] = React.useState(false);
    React.useEffect(() => { icons(); }, [armed]);
    if (!armed) return (
      <button type="button" className="rq-del" title="Delete"
        onClick={(e) => { e.stopPropagation(); setArmed(true); }}><i data-lucide="trash-2" /></button>
    );
    return (
      <span className="rq-del-confirm" onClick={(e) => e.stopPropagation()}>
        <button type="button" className="rq-del rq-del--yes" disabled={busy}
          onClick={async () => { setBusy(true); try { await onDelete(); } finally { setBusy(false); } }}>Delete</button>
        <button type="button" className="rq-del rq-del--no" onClick={() => setArmed(false)}>Cancel</button>
      </span>
    );
  }

  // The per-request operations panel: deliveries (log / release / list) + the cost &
  // approval trail. Rendered inside a request's expanded detail (Matters AssetRequestsTab).
  // onChanged is called after any write so the parent reloads the request row (its
  // delivery_location / cost / approval_state are denormalized by DB triggers).
  function RequestOpsPanel({ request, onChanged }) {
    const { Button, Input, Select } = DS;
    const rid = request.id;
    const [dels, setDels] = React.useState(null);
    const [costs, setCosts] = React.useState(null);
    const [threshold, setThreshold] = React.useState(500);
    const [showDForm, setShowDForm] = React.useState(false);
    const [showCForm, setShowCForm] = React.useState(false);
    const [docsId, setDocsId] = React.useState(null);   // cost event whose documents are open (B5)
    const [docCounts, setDocCounts] = React.useState({});
    const [busy, setBusy] = React.useState(false);
    const [err, setErr] = React.useState(null);

    const today = () => new Date().toISOString().slice(0, 10);
    const blankD = () => ({ received_at: today(), delivery_method: "", evo_path: "",
      slug_city: "", slug_culprit: "", slug_crime: "",
      iconik_asset_ids: "", item_count: "", size_gb: "", review_state: "released", notes: "" });
    const blankC = () => ({ event_type: "quoted", amount: "", note: "" });
    const [df, setDf] = React.useState(blankD);
    const [cf, setCf] = React.useState(blankC);

    const load = React.useCallback(() => {
      window.LedgerAPI.listDeliveries(rid).then(setDels).catch(() => setDels([]));
      window.LedgerAPI.listCostEvents(rid).then((cs) => {
        setCosts(cs);
        const att = window.LedgerAPI.att;
        if (att && cs.length) att.counts("asset_cost_event", cs.map((c) => c.id)).then(setDocCounts).catch(() => {});
      }).catch(() => setCosts([]));
    }, [rid]);
    React.useEffect(() => { load(); window.LedgerAPI.approvalThreshold().then(setThreshold).catch(() => {}); }, [load]);
    React.useEffect(() => { icons(); }, [dels, costs, showDForm, showCForm, df.evo_path, docsId, docCounts]);

    const setD = (k) => (e) => setDf((s) => ({ ...s, [k]: e.target.value }));
    const setC = (k) => (e) => setCf((s) => ({ ...s, [k]: e.target.value }));
    const genSlug = () => setDf((s) => ({ ...s,
      evo_path: evoSlug({ state: request.state, city: s.slug_city, culprit: s.slug_culprit, crime: s.slug_crime }) }));

    async function saveDelivery() {
      if (!df.delivery_method) { setErr("Pick how it arrived."); return; }
      setBusy(true); setErr(null);
      try {
        const ids = df.iconik_asset_ids.split(",").map((x) => x.trim()).filter(Boolean);
        await window.LedgerAPI.logDelivery({
          request_id: rid,
          received_at: df.received_at ? new Date(df.received_at).toISOString() : null,
          delivery_method: df.delivery_method,
          evo_path: df.evo_path || null,
          iconik_asset_ids: ids,
          item_count: df.item_count === "" ? null : parseInt(df.item_count, 10),
          size_gb: df.size_gb === "" ? null : parseFloat(df.size_gb),
          review_state: df.review_state,
          notes: df.notes || null,
        });
        setDf(blankD()); setShowDForm(false); load(); onChanged && onChanged();
      } catch (e) { setErr(e.message || String(e)); }
      finally { setBusy(false); }
    }
    async function release(id) {
      setBusy(true); setErr(null);
      try { await window.LedgerAPI.releaseDelivery(id); load(); onChanged && onChanged(); }
      catch (e) { setErr(e.message || String(e)); } finally { setBusy(false); }
    }
    async function saveCost() {
      const needsAmount = !["approval_requested", "approved", "rejected"].includes(cf.event_type);
      if (needsAmount && cf.amount === "") { setErr("Enter an amount."); return; }
      setBusy(true); setErr(null);
      try {
        const ev = await window.LedgerAPI.addCostEvent({ request_id: rid, event_type: cf.event_type,
          amount: cf.amount === "" ? null : parseFloat(cf.amount), note: cf.note || null });
        // Open the new event's documents so the invoice or receipt goes on straight away.
        setCf(blankC()); setShowCForm(false); if (ev && window.LedgerAttachments) setDocsId(ev.id);
        load(); onChanged && onChanged();
      } catch (e) { setErr(e.message || String(e)); } finally { setBusy(false); }
    }

    const overThreshold = request.estimated_cost != null && Number(request.estimated_cost) >= threshold;
    const apprTone = { approved: "rq-done", rejected: "rq-dead", required: "rq-warn" }[request.approval_state] || "rq-active";

    return (
      <div className="rq-ops" onClick={(e) => e.stopPropagation()}>
        {err && <div className="login__err" style={{ margin: "4px 0 10px" }}>{err}</div>}

        {/* ---- Deliveries (Media Operations) ---- */}
        <div className="rq-ops-head">
          <span><i data-lucide="package" /> Deliveries{dels && dels.length ? ` (${dels.length})` : ""}<span className="rq-ops-team">Media Operations</span></span>
          <Button size="sm" variant="secondary" icon="plus"
            onClick={() => { setErr(null); setShowDForm((v) => !v); }}>{showDForm ? "Close" : "Log delivery"}</Button>
        </div>

        {showDForm && (
          <div className="rq-ops-form">
            <div className="form-grid">
              <Input type="date" label="Received" value={df.received_at} onChange={setD("received_at")} />
              <Select label="How it arrived" value={df.delivery_method} onChange={setD("delivery_method")}
                options={[{ value: "", label: "— method —" }].concat(opts(V.delivery_method))} />
              <Input type="number" label="Item count" value={df.item_count} onChange={setD("item_count")} placeholder="e.g. 3" />
              <Input type="number" label="Size (GB)" value={df.size_gb} onChange={setD("size_gb")} placeholder="e.g. 12.5" />
            </div>
            <div className="rq-slug">
              <div className="rq-slug-head"><i data-lucide="folder" /> Canonical EVO folder</div>
              <div className="form-grid">
                <Input label="City" value={df.slug_city} onChange={setD("slug_city")} placeholder="e.g. Nashville" />
                <Input label="Culprit" value={df.slug_culprit} onChange={setD("slug_culprit")} placeholder="e.g. Doe" />
                <Input label="Crime" value={df.slug_crime} onChange={setD("slug_crime")} placeholder="e.g. homicide" />
              </div>
              <div className="rq-slug-row">
                <Button size="sm" variant="ghost" icon="wand-2" onClick={genSlug}>Generate</Button>
                <input className="lc-input rq-slug-path" value={df.evo_path} onChange={setD("evo_path")}
                  placeholder="state_city_culprit_crime (editable)" />
                <CopyButton text={df.evo_path} />
              </div>
              <div className="rq-slug-hint">State (<b>{request.state || "—"}</b>) prefixes the slug · sanitized · ≤260 chars · editable.</div>
            </div>
            <div className="form-grid">
              <Input label="Iconik asset IDs" value={df.iconik_asset_ids} onChange={setD("iconik_asset_ids")} placeholder="comma-separated (optional)" />
              <Select label="Release" value={df.review_state} onChange={setD("review_state")}
                options={[{ value: "released", label: "Release now (notify requester)" }, { value: "pending_review", label: "Hold for review (embargo)" }]} />
              <div className="full">
                <label className="field-label">Notes</label>
                <textarea className="lc-input ledger-textarea" value={df.notes} onChange={setD("notes")} />
              </div>
            </div>
            <div className="form-actions">
              <Button variant="primary" icon="check" onClick={saveDelivery} disabled={busy}>{busy ? "Saving…" : "Log delivery"}</Button>
            </div>
          </div>
        )}

        {dels && dels.length > 0 && (
          <ul className="rq-deliv">
            {dels.map((d) => (
              <li key={d.id} className="rq-deliv-item">
                <div className="rq-deliv-main">
                  <div className="rq-deliv-top">
                    <DeliveryReviewPill state={d.review_state} />
                    {d.delivery_method && <span className="chip">{T(d.delivery_method)}</span>}
                    {d.received_at && <span className="rq-deliv-date">{String(d.received_at).slice(0, 10)}</span>}
                    {d.item_count != null && <span className="rq-deliv-meta">{d.item_count} items</span>}
                    {d.size_gb != null && <span className="rq-deliv-meta">{d.size_gb} GB</span>}
                  </div>
                  {d.evo_path && <div className="rq-deliv-path"><code>{d.evo_path}</code><CopyButton text={d.evo_path} small /></div>}
                  {d.notes && <div className="rq-deliv-notes">{d.notes}</div>}
                </div>
                <div className="rq-deliv-actions">
                  {d.review_state === "pending_review" && (
                    <Button size="sm" variant="primary" icon="send" onClick={() => release(d.id)} disabled={busy}>Release</Button>
                  )}
                  <MiniDelete onDelete={async () => { await window.LedgerAPI.deleteDelivery(d.id); load(); onChanged && onChanged(); }} />
                </div>
              </li>
            ))}
          </ul>
        )}
        {dels && dels.length === 0 && !showDForm && (
          <div className="rq-ops-empty">No deliveries yet. Log one when media arrives — releasing notifies the requester.</div>
        )}

        {/* ---- Cost & approval (Content Center) ---- */}
        <div className="rq-ops-head rq-ops-head--cost">
          <span><i data-lucide="dollar-sign" /> Cost &amp; approval<span className="rq-ops-team">Content Center</span></span>
          <Button size="sm" variant="secondary" icon="plus"
            onClick={() => { setErr(null); setShowCForm((v) => !v); }}>{showCForm ? "Close" : "Add cost event"}</Button>
        </div>
        <div className="rq-cost-read">
          <span><b>Estimated</b> {money(request.estimated_cost) || "—"}</span>
          <span><b>Paid</b> {money(request.actual_cost) || "—"}</span>
          <span className={"pill pill--" + apprTone}>{T(request.approval_state || "none")}</span>
          {overThreshold && request.approval_state !== "approved" && (
            <span className="rq-cost-flag"><i data-lucide="alert-triangle" /> ≥ ${threshold} — approval required</span>
          )}
        </div>

        {showCForm && (
          <div className="rq-ops-form">
            <div className="form-grid">
              <Select label="Event" value={cf.event_type} onChange={setC("event_type")} options={opts(V.cost_event_type)} />
              <Input type="number" label="Amount ($)" value={cf.amount} onChange={setC("amount")} placeholder="e.g. 540.00" />
              <div className="full">
                <label className="field-label">Note</label>
                <textarea className="lc-input ledger-textarea" value={cf.note} onChange={setC("note")} placeholder="e.g. negotiated down from $1,530" />
              </div>
            </div>
            <div className="form-actions">
              <Button variant="primary" icon="check" onClick={saveCost} disabled={busy}>{busy ? "Saving…" : "Add event"}</Button>
            </div>
          </div>
        )}
        {costs && costs.length > 0 && (
          <ul className="rq-cost-trail">
            {costs.map((c) => (
              <li key={c.id} className="rq-cost-item">
                <div className="rq-cost-ev">
                  <span className={"rq-cost-dot rq-cost-dot--" + c.event_type} />
                  <span className="rq-cost-type">{T(c.event_type)}</span>
                  {c.amount != null && <span className="rq-cost-amt">{money(c.amount)}</span>}
                  {c.note && <span className="rq-cost-note">{c.note}</span>}
                  {c.occurred_at && <span className="rq-cost-when">{String(c.occurred_at).slice(0, 10)}</span>}
                  {window.LedgerAttachments && (
                    <button type="button" className="link-btn rq-cost-docs-btn" onClick={() => setDocsId(docsId === c.id ? null : c.id)}
                      title="Invoice, quote, receipt or approval for this cost. Kept apart from the request's documents.">
                      <span className="ic-wrap" key="paperclip"><i data-lucide="paperclip" /></span>
                      {docsId === c.id ? "Hide documents" : docCounts[c.id] ? `Documents (${docCounts[c.id]})` : "Add invoice / receipt"}
                    </button>
                  )}
                </div>
                {docsId === c.id && (
                  <div className="rq-cost-docs">
                    <window.LedgerAttachments entityType="asset_cost_event" entityId={c.id}
                      onCount={(n) => setDocCounts((m) => (m[c.id] === n ? m : { ...m, [c.id]: n }))} />
                  </div>
                )}
              </li>
            ))}
          </ul>
        )}
      </div>
    );
  }

  // Full editable request detail — the desk work surface (Content Center + Media Ops
  // work a request from here, not by opening the matter). Editable fields up top (the
  // requestor may have miskeyed), then the agency playbook, then the cost (Content
  // Center) + deliveries (Media Operations) ops panel. Self-loading: pulls the shows /
  // staff / agency directory / state rules / matter-agencies / playbook it needs.
  function RequestDetail({ request, onBack, onChanged, onOpenMatter }) {
    const { Button, Input, Select } = DS;
    const [shows, setShows] = React.useState([]);
    const [staff, setStaff] = React.useState([]);
    const [entities, setEntities] = React.useState([]);
    const [stateRules, setStateRules] = React.useState([]);
    const [matterAgencies, setMatterAgencies] = React.useState([]);
    const [playbook, setPlaybook] = React.useState(null);
    const [busy, setBusy] = React.useState(false);
    const [saved, setSaved] = React.useState(false);
    const [err, setErr] = React.useState(null);

    const seed = React.useCallback(() => ({
      content_kind: request.content_kind || [],
      request_type: request.request_type || "",
      requesting_shows: (request.requesting_shows && request.requesting_shows.length) ? request.requesting_shows
        : (request.requesting_show ? [request.requesting_show] : []),
      status: request.status || "new",
      assignee: request.assignee || "",
      agency_id: request.agency_id || "",
      state: request.state || "", county: request.county || "",
      due_date: request.due_date || "", slug: request.slug || "",
      expecting_more: !!request.expecting_more,
      details: request.details || "",
    }), [request]);
    const [f, setF] = React.useState(seed);
    // Reseed only when a *different* request is opened — preserves in-progress edits
    // across the parent's post-write reloads (same id → new object, but keep the form).
    React.useEffect(() => { setF(seed()); }, [request.id]);
    // Sync just the status field when the server advances it out from under us (e.g. a
    // logged delivery auto-marks the request 'received', migration 0062) — without
    // clobbering other in-progress edits, and so Save can't regress it to the stale value.
    const lastStatus = React.useRef(request.status);
    React.useEffect(() => {
      if (request.status !== lastStatus.current) { lastStatus.current = request.status; setF((s) => ({ ...s, status: request.status })); }
    }, [request.status]);

    React.useEffect(() => {
      window.LedgerAPI.showTaxonomy().then(setShows).catch(() => {});
      // Assignee picker = Content Center staff marked Assignable (0137), never requesters.
      window.LedgerAPI.listAssignableStaff().then(setStaff).catch(() => {});
      window.LedgerAPI.listEntities().then(setEntities).catch(() => {});
      window.LedgerAPI.stateFoiaRules().then(setStateRules).catch(() => {});
      if (request.matter_id) window.LedgerAPI.listMatterAgencies(request.matter_id).then(setMatterAgencies).catch(() => {});
      else setMatterAgencies([]);
    }, [request.matter_id]);
    React.useEffect(() => {
      if (f.agency_id && !isHeaderVal(f.agency_id)) window.LedgerAPI.getAgencyPlaybook(f.agency_id).then(setPlaybook).catch(() => setPlaybook({}));
      else setPlaybook(null);
    }, [f.agency_id]);
    React.useEffect(() => { icons(); });

    const set = (k) => (e) => setF((s) => ({ ...s, [k]: e.target.value }));
    const toggleKind = (k) => setF((s) => ({ ...s, content_kind: s.content_kind.includes(k) ? s.content_kind.filter((x) => x !== k) : s.content_kind.concat(k) }));
    const stateRuleFor = (st) => (stateRules || []).find((r) => r.state === st) || null;
    const agencyOpts = buildAgencyOptions(matterAgencies, entities);
    const staffOpts = [{ value: "", label: "— unassigned —" }]
      .concat((staff || []).map((s) => ({ value: s.staff_id, label: `${s.first_name || ""} ${s.last_name || ""}`.trim() })));
    // A request still held by someone who is no longer assignable keeps showing them.
    if (f.assignee && !staffOpts.some((o) => o.value === f.assignee)) {
      staffOpts.push({ value: f.assignee, label: (request.assignee_name || "Current assignee") + " (not assignable)" });
    }

    async function save() {
      setBusy(true); setErr(null); setSaved(false);
      try {
        const agencyId = f.agency_id && !isHeaderVal(f.agency_id) ? f.agency_id : null;
        // Picking an agency links it to the matter so future requests inherit it (§3.7).
        if (agencyId && request.matter_id) { try { await window.LedgerAPI.addMatterAgency({ matter_id: request.matter_id, entity_id: agencyId, role: "records_custodian" }); } catch (_) {} }
        // A new due date goes through the re-date RPC (0138) so it is logged as a
        // chase, the same as a push from the Asset Requests calendar. The RPC compares with the
        // stored date, so saving twice records it once.
        if ((f.due_date || null) !== (request.due_date || null)) {
          await window.LedgerAPI.redateAssetRequest(request.id, f.due_date || null);
        }
        await window.LedgerAPI.updateAssetRequest(request.id, {
          content_kind: f.content_kind,
          request_type: f.request_type || null,
          requesting_shows: f.requesting_shows,
          status: f.status,
          assignee: f.assignee || null,
          agency_id: agencyId,
          state: f.state || null, county: f.county || null,
          due_date: f.due_date || null, slug: f.slug || null,
          expecting_more: f.expecting_more,
          details: f.details || null,
        });
        setSaved(true); setTimeout(() => setSaved(false), 1600);
        onChanged && onChanged();
      } catch (e) { setErr(e.message || String(e)); }
      finally { setBusy(false); }
    }

    // Ops panel reads the live form's state/county for the slug prefix + residency.
    const liveReq = { ...request, state: f.state, county: f.county };
    const showAgencyCard = (f.agency_id && !isHeaderVal(f.agency_id)) || (stateRuleFor(f.state) && stateRuleFor(f.state).residency_required);

    return (
      <div className="rqd">
        <button type="button" className="crumb" onClick={onBack}><i data-lucide="arrow-left" /> Back to queue</button>
        <div className="rqd-head">
          <div className="rqd-head-l">
            <span className="rqd-id">{request.display_id}</span>
            <StatusPill status={f.status} />
            {request.expecting_more && <span className="rq-more">expecting more</span>}
          </div>
          {request.matter_id && (
            <button type="button" className="link-btn rqd-matter" onClick={() => onOpenMatter && onOpenMatter(request.matter_id)}>
              <i data-lucide="folder" /> {request.matter_title || request.matter_display_id || "Open matter"}
            </button>
          )}
        </div>

        {err && <div className="login__err" style={{ margin: "6px 0 12px" }}>{err}</div>}

        {window.LedgerHoldBanner && <window.LedgerHoldBanner matterIds={[request.matter_id]} requestIds={[request.id]} />}
        {window.LedgerUsedIn && <window.LedgerUsedIn request={{ ...request, status: f.status }} />}

        {/* Editable request fields */}
        <DS.Card><DS.Card.Body>
          <div className="rqd-sec-head"><i data-lucide="pencil" /> Request details <span className="rqd-hint">editable — fix anything the requestor miskeyed</span></div>
          <label className="field-label">What's needed</label>
          <ContentKindChips value={f.content_kind} onToggle={toggleKind} />
          <div className="form-grid" style={{ marginTop: 14 }}>
            <ShowPicker value={f.requesting_shows} shows={shows} onChange={(v) => setF((s) => ({ ...s, requesting_shows: v }))} />
            <Select label="Rights basis" value={f.request_type} onChange={set("request_type")} options={[{ value: "", label: "— basis —" }].concat(opts(V.request_type))} />
            <Select label="Status" value={f.status} onChange={set("status")} options={opts(V.request_status)} />
            <Select label="Assignee" value={f.assignee} onChange={set("assignee")} options={staffOpts} />
            <Select label="Agency" value={f.agency_id}
              onChange={(e) => { if (!isHeaderVal(e.target.value)) set("agency_id")(e); }} options={agencyOpts} />
            <Input type="date" label="Need by" value={f.due_date} onChange={set("due_date")} />
            <Input label="State" value={f.state} onChange={set("state")} placeholder="e.g. FL" />
            <Input label="County" value={f.county} onChange={set("county")} />
            <Input label="Slug / label" value={f.slug} onChange={set("slug")} />
            <label className="rqd-check"><input type="checkbox" checked={f.expecting_more} onChange={(e) => setF((s) => ({ ...s, expecting_more: e.target.checked }))} /> Expecting more deliveries</label>
            <div className="full">
              <label className="field-label">Notes</label>
              <textarea className="lc-input ledger-textarea" value={f.details} onChange={set("details")} />
            </div>
          </div>
          <div className="form-actions">
            <Button variant="primary" icon="check" onClick={save} disabled={busy}>{busy ? "Saving…" : (saved ? "Saved ✓" : "Save changes")}</Button>
          </div>
        </DS.Card.Body></DS.Card>

        {/* Agency playbook + residency */}
        {showAgencyCard && (
          <DS.Card style={{ marginTop: 12 }}><DS.Card.Body>
            <AgencyPanel playbook={playbook || {}} stateRule={stateRuleFor(f.state)} requestState={f.state} requestCounty={f.county} />
          </DS.Card.Body></DS.Card>
        )}

        {/* Documents: FOIA letters, fee estimates, release letters (attachments A1) */}
        {window.LedgerAttachments && (
          <DS.Card style={{ marginTop: 12 }}><DS.Card.Body>
            <window.LedgerAttachments entityType="asset_request" entityId={request.id} />
          </DS.Card.Body></DS.Card>
        )}

        {/* Cost (Content Center) + Deliveries (Media Operations) */}
        <DS.Card style={{ marginTop: 12 }}><DS.Card.Body>
          <RequestOpsPanel request={liveReq} onChanged={onChanged} />
        </DS.Card.Body></DS.Card>
      </div>
    );
  }

  window.LedgerRequestKit = {
    REQUEST_TERMINAL, requestTone, isOpen, daysUntil, isOverdue,
    StatusPill, ContentKindChips, AgencyPanel,
    showLabelMap, ShowPicker, buildAgencyOptions, isHeaderVal,
    evoSlug, RequestOpsPanel, RequestDetail,
  };
})();
