// Case index — master register of all FOI cases with faceted advanced search.

function CaseIndex({ go }) {
  const all = window.REQUESTS;
  // Deep-link: the Dashboard sets window.__openCaseId before navigating here, so a
  // clicked request opens straight to its detail. One-shot — cleared on read.
  const [sel, setSel] = React.useState(() => {
    const id = window.__openCaseId; window.__openCaseId = null;
    return id ? (window.REQUESTS || []).find((x) => x.id === id) || null : null;
  });

  // Status lane (the triage tabs, carried over from the old Tracker) + advanced filters.
  const [lane, setLane] = React.useState("all");
  const [q, setQ] = React.useState("");
  const [types, setTypes] = React.useState([]);       // recipientType
  const [statuses, setStatuses] = React.useState([]); // status
  const [cats, setCats] = React.useState([]);          // category
  const [channels, setChannels] = React.useState([]);
  const [owner, setOwner] = React.useState("any");
  const [deadlineState, setDeadlineState] = React.useState("any"); // any|overdue|due-soon|on-track|closed
  const [from, setFrom] = React.useState("");
  const [to, setTo] = React.useState("");
  const [sort, setSort] = React.useState("newest");

  const toggle = (list, setList, v) =>
    setList(list.includes(v) ? list.filter((x) => x !== v) : [...list, v]);

  const clearAll = () => {
    setQ(""); setTypes([]); setStatuses([]); setCats([]); setChannels([]);
    setOwner("any"); setDeadlineState("any"); setFrom(""); setTo("");
  };

  const deadlineOf = (r) => {
    if (["fulfilled", "refused", "partial"].includes(r.status)) return "closed";
    if (!r.sentOn) return "draft";
    const left = (r.deadlineDays || 20) - r.workingDaysElapsed;
    if (left < 0) return "overdue";
    if (left <= 3) return "due-soon";
    return "on-track";
  };

  // Status-lane membership (the triage tabs carried over from the Tracker).
  const inLane = (r) => {
    if (lane === "all") return true;
    if (lane === "active") return !["fulfilled", "refused", "draft"].includes(r.status);
    if (lane === "overdue") return r.status === "overdue";
    if (lane === "closed") return ["fulfilled", "refused"].includes(r.status);
    return r.status === lane;   // internal-review
  };

  const results = React.useMemo(() => {
    let rows = all.filter((r) => {
      if (!inLane(r)) return false;
      if (q) {
        const hay = `${r.id} ${r.title} ${r.recipientName} ${(r.tags || []).join(" ")} ${r.category} ${r.owner}`.toLowerCase();
        if (!hay.includes(q.toLowerCase())) return false;
      }
      if (types.length && !types.includes(r.recipientType)) return false;
      if (statuses.length && !statuses.includes(r.status)) return false;
      if (cats.length && !cats.includes(r.category)) return false;
      if (channels.length && !channels.includes(r.targetLevel)) return false;
      if (owner !== "any" && r.owner !== owner) return false;
      if (deadlineState !== "any" && deadlineOf(r) !== deadlineState) return false;
      if (from && r.sentOn && r.sentOn < from) return false;
      if (to && r.sentOn && r.sentOn > to) return false;
      if ((from || to) && !r.sentOn) return false;
      return true;
    });
    rows = [...rows].sort((a, b) => {
      if (sort === "newest") return (b.sentOn || "0") < (a.sentOn || "0") ? -1 : 1;
      if (sort === "oldest") return (a.sentOn || "9") < (b.sentOn || "9") ? -1 : 1;
      if (sort === "deadline") return (a.workingDaysElapsed ? 20 - a.workingDaysElapsed : 99) - (b.workingDaysElapsed ? 20 - b.workingDaysElapsed : 99);
      if (sort === "replies") return b.replies - a.replies;
      if (sort === "title") return a.title.localeCompare(b.title);
      return 0;
    });
    return rows;
  }, [all, lane, q, types, statuses, cats, channels, owner, deadlineState, from, to, sort]);

  const activeCount = types.length + statuses.length + cats.length + channels.length +
    (owner !== "any" ? 1 : 0) + (deadlineState !== "any" ? 1 : 0) + (from ? 1 : 0) + (to ? 1 : 0) + (q ? 1 : 0);

  if (sel) return <RequestDetail r={sel} back={() => setSel(null)} />;

  // Facet counts (against everything, classic GOV.UK style)
  const countBy = (key, val, valueGetter) => all.filter((r) => (valueGetter ? valueGetter(r) : r[key]) === val).length;

  return (
    <div>
      <SectionTitle sub="Track, search and open every FOI case — to trusts, councils and individual schools." action={
        <div style={{ display: "flex", gap: 8 }}>
          <Button variant="secondary" icon={<Ico.Doc/>}>Export results</Button>
          <Button onClick={() => go("compose")} icon={<Ico.Plus/>}>New case</Button>
        </div>
      }>Cases</SectionTitle>

      {/* Status lanes — quick triage tabs (carried over from the Tracker), compose with the filters below */}
      <div style={{ display: "flex", gap: 4, marginBottom: 16, borderBottom: "1px solid var(--rule)" }}>
        {[
          { id: "all", label: "All", count: all.length },
          { id: "active", label: "Active", count: all.filter((r) => !["fulfilled", "refused", "draft"].includes(r.status)).length },
          { id: "overdue", label: "Overdue", count: all.filter((r) => r.status === "overdue").length },
          { id: "internal-review", label: "Review", count: all.filter((r) => r.status === "internal-review").length },
          { id: "closed", label: "Closed", count: all.filter((r) => ["fulfilled", "refused"].includes(r.status)).length },
        ].map((t) => (
          <button key={t.id} onClick={() => setLane(t.id)} style={{
            background: "none", border: "none", padding: "10px 14px",
            fontSize: 13, fontFamily: "inherit",
            fontWeight: lane === t.id ? 600 : 500,
            color: lane === t.id ? "var(--text)" : "var(--muted)",
            borderBottom: lane === t.id ? "2px solid var(--accent)" : "2px solid transparent",
            cursor: "pointer", display: "inline-flex", alignItems: "center", gap: 8, marginBottom: -1
          }}>
            {t.label}
            <span style={{ fontSize: 11, fontFamily: "var(--font-mono)", color: "var(--muted)", background: "var(--surface-2)", padding: "0 6px", borderRadius: 2 }}>{t.count}</span>
          </button>
        ))}
      </div>

      <div style={{ display: "grid", gridTemplateColumns: "248px 1fr", gap: 20, alignItems: "start" }}>
        {/* ── Filter rail ── */}
        <aside style={{ position: "sticky", top: 68 }}>
          <Card pad={0}>
            <div style={{ padding: "12px 16px", borderBottom: "1px solid var(--rule)", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
              <span style={{ fontWeight: 700, fontSize: 14, display: "flex", alignItems: "center", gap: 8 }}><Ico.Filter/> Filters</span>
              {activeCount > 0 && (
                <button onClick={clearAll} style={{ background: "none", border: "none", color: "var(--accent)", fontSize: 12, cursor: "pointer", fontFamily: "inherit", fontWeight: 500 }}>Clear ({activeCount})</button>
              )}
            </div>

            <div style={{ padding: 16, borderBottom: "1px solid var(--rule)" }}>
              <div style={{ position: "relative" }}>
                <div style={{ position: "absolute", left: 10, top: 0, bottom: 0, display: "flex", alignItems: "center", color: "var(--muted)" }}><Ico.Search/></div>
                <input value={q} onChange={(e) => setQ(e.target.value)} placeholder="Keyword, ref, tag…"
                  style={{ width: "100%", padding: "8px 10px 8px 32px", fontSize: 13, fontFamily: "inherit", border: "1px solid var(--rule)", background: "var(--surface)", borderRadius: 2, outline: "none" }} />
              </div>
            </div>

            <FacetGroup title="Progress">
              {Object.keys(window.STATUS_META).filter((s) => all.some((r) => r.status === s)).map((s) => (
                <FacetCheck key={s} checked={statuses.includes(s)} onChange={() => toggle(statuses, setStatuses, s)}
                  label={window.STATUS_META[s].label} count={countBy("status", s)} tone={window.STATUS_META[s].tone} />
              ))}
            </FacetGroup>

            <FacetGroup title="Recipient type">
              {window.RECIPIENT_TYPES.filter((t) => all.some((r) => r.recipientType === t)).map((t) => (
                <FacetCheck key={t} checked={types.includes(t)} onChange={() => toggle(types, setTypes, t)}
                  label={t} count={countBy("recipientType", t)} />
              ))}
            </FacetGroup>

            <FacetGroup title="Category" defaultOpen={false}>
              {window.CATEGORIES.filter((c) => all.some((r) => r.category === c)).map((c) => (
                <FacetCheck key={c} checked={cats.includes(c)} onChange={() => toggle(cats, setCats, c)}
                  label={c} count={countBy("category", c)} />
              ))}
            </FacetGroup>

            <FacetGroup title="Deadline" >
              {[
                { v: "overdue", l: "Overdue" },
                { v: "due-soon", l: "Due ≤ 3 days" },
                { v: "on-track", l: "On track" },
                { v: "closed", l: "Closed" },
                { v: "draft", l: "Not sent" }
              ].map((d) => (
                <FacetRadioRow key={d.v} checked={deadlineState === d.v} onChange={() => setDeadlineState(deadlineState === d.v ? "any" : d.v)}
                  label={d.l} count={all.filter((r) => deadlineOf(r) === d.v).length} />
              ))}
            </FacetGroup>

            <FacetGroup title="Target level" defaultOpen={false}>
              {window.TARGET_LEVELS.map((c) => (
                <FacetCheck key={c} checked={channels.includes(c)} onChange={() => toggle(channels, setChannels, c)}
                  label={c} count={countBy("targetLevel", c)} />
              ))}
            </FacetGroup>

            <FacetGroup title="Owner" defaultOpen={false}>
              <select value={owner} onChange={(e) => setOwner(e.target.value)}
                style={{ width: "100%", padding: "7px 10px", fontSize: 13, fontFamily: "inherit", border: "1px solid var(--rule)", background: "var(--surface)", borderRadius: 2 }}>
                <option value="any">Anyone</option>
                {window.OWNERS.filter((o) => all.some((r) => r.owner === o)).map((o) => <option key={o} value={o}>{o}</option>)}
              </select>
            </FacetGroup>

            <FacetGroup title="Date sent" defaultOpen={false}>
              <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
                <label style={{ fontSize: 11, color: "var(--muted)" }}>From
                  <input type="date" value={from} onChange={(e) => setFrom(e.target.value)} style={{ width: "100%", padding: "6px 8px", fontSize: 12, fontFamily: "var(--font-mono)", border: "1px solid var(--rule)", background: "var(--surface)", borderRadius: 2, marginTop: 3 }} />
                </label>
                <label style={{ fontSize: 11, color: "var(--muted)" }}>To
                  <input type="date" value={to} onChange={(e) => setTo(e.target.value)} style={{ width: "100%", padding: "6px 8px", fontSize: 12, fontFamily: "var(--font-mono)", border: "1px solid var(--rule)", background: "var(--surface)", borderRadius: 2, marginTop: 3 }} />
                </label>
              </div>
            </FacetGroup>
          </Card>
        </aside>

        {/* ── Results ── */}
        <div>
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }}>
            <div style={{ fontSize: 13, color: "var(--muted)" }}>
              <strong style={{ color: "var(--text)", fontVariantNumeric: "tabular-nums" }}>{results.length}</strong> of {all.length} cases
            </div>
            <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
              <span style={{ fontSize: 12, color: "var(--muted)" }}>Sort</span>
              <select value={sort} onChange={(e) => setSort(e.target.value)}
                style={{ padding: "6px 10px", fontSize: 13, fontFamily: "inherit", border: "1px solid var(--rule)", background: "var(--surface)", borderRadius: 2 }}>
                <option value="newest">Newest sent</option>
                <option value="oldest">Oldest sent</option>
                <option value="deadline">Deadline (soonest)</option>
                <option value="replies">Most replies</option>
                <option value="title">Title A–Z</option>
              </select>
            </div>
          </div>

          {/* Active filter chips */}
          {activeCount > 0 && (
            <div style={{ display: "flex", flexWrap: "wrap", gap: 6, marginBottom: 12 }}>
              {q && <Chip label={`“${q}”`} onClear={() => setQ("")} />}
              {types.map((t) => <Chip key={t} label={t} onClear={() => toggle(types, setTypes, t)} />)}
              {statuses.map((s) => <Chip key={s} label={window.STATUS_META[s].label} onClear={() => toggle(statuses, setStatuses, s)} />)}
              {cats.map((c) => <Chip key={c} label={c} onClear={() => toggle(cats, setCats, c)} />)}
              {channels.map((c) => <Chip key={c} label={c} onClear={() => toggle(channels, setChannels, c)} />)}
              {owner !== "any" && <Chip label={owner} onClear={() => setOwner("any")} />}
              {deadlineState !== "any" && <Chip label={deadlineState} onClear={() => setDeadlineState("any")} />}
              {from && <Chip label={`from ${from}`} onClear={() => setFrom("")} />}
              {to && <Chip label={`to ${to}`} onClear={() => setTo("")} />}
            </div>
          )}

          <Card pad={0}>
            <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 13 }}>
              <thead style={{ background: "var(--surface-2)" }}>
                <tr>
                  <Th width={48} num>#</Th>
                  <Th>Case</Th>
                  <Th width={110}>Type</Th>
                  <Th width={120}>Progress</Th>
                  <Th width={150}>Deadline</Th>
                  <Th width={70} num>Replies</Th>
                </tr>
              </thead>
              <tbody>
                {results.map((r) => {
                  const ds = deadlineOf(r);
                  const budget = r.deadlineDays || 20;
                  const left = budget - r.workingDaysElapsed;
                  const pct = r.workingDaysElapsed ? (r.workingDaysElapsed / budget) * 100 : 0;
                  const tone = ds === "overdue" ? "danger" : ds === "due-soon" ? "warn" : "info";
                  return (
                    <tr key={r.id} onClick={() => setSel(r)} style={{ cursor: "pointer", borderBottom: "1px solid var(--rule)" }}
                        onMouseEnter={(e) => e.currentTarget.style.background = "var(--surface-2)"}
                        onMouseLeave={(e) => e.currentTarget.style.background = "transparent"}>
                      <td style={{ padding: "12px", textAlign: "right", fontFamily: "var(--font-mono)", fontSize: 12, color: "var(--muted)", fontVariantNumeric: "tabular-nums" }}>{String(r.caseIndex).padStart(2, "0")}</td>
                      <td style={{ padding: "12px" }}>
                        <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                          <span style={{ fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--muted)" }}>{r.id}</span>
                          <span style={{ fontSize: 10.5, color: "var(--muted)", border: "1px solid var(--rule)", padding: "0 6px", borderRadius: 999 }}>{r.category}</span>
                        </div>
                        <div style={{ fontWeight: 500, color: "var(--text)", marginTop: 3 }}>{r.title}</div>
                        <div style={{ fontSize: 11.5, color: "var(--muted)", marginTop: 2 }}>{r.recipientName} · {r.owner}</div>
                      </td>
                      <td style={{ padding: "12px", fontSize: 12, color: "var(--text)" }}>{r.recipientType}</td>
                      <td style={{ padding: "12px" }}><StatusPill status={r.status} /></td>
                      <td style={{ padding: "12px" }}>
                        {r.sentOn ? (
                          ds === "closed" ? <span style={{ fontSize: 12, color: "var(--muted)" }}>Closed</span> : (
                            <>
                              <div style={{ fontSize: 12, color: tone === "danger" ? "var(--danger-fg)" : "var(--text)", fontWeight: tone === "danger" ? 600 : 500, fontVariantNumeric: "tabular-nums" }}>
                                {left >= 0 ? `${left}d left` : `${Math.abs(left)}d over`}
                              </div>
                              <div style={{ width: 110, marginTop: 5 }}><Bar pct={Math.min(100, pct)} tone={tone} /></div>
                            </>
                          )
                        ) : <span style={{ fontSize: 12, color: "var(--muted)" }}>—</span>}
                      </td>
                      <td style={{ padding: "12px", textAlign: "right", fontVariantNumeric: "tabular-nums" }}>{r.replies}</td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
            {results.length === 0 && (
              <div style={{ padding: 48, textAlign: "center", color: "var(--muted)" }}>
                <div style={{ fontSize: 14, fontWeight: 600, marginBottom: 4 }}>No cases match your filters</div>
                <div style={{ fontSize: 13 }}>Try removing a filter or <button onClick={clearAll} style={{ background: "none", border: "none", color: "var(--accent)", cursor: "pointer", fontFamily: "inherit", fontSize: 13, padding: 0 }}>clear all</button>.</div>
              </div>
            )}
          </Card>
        </div>
      </div>
    </div>
  );
}

function FacetGroup({ title, children, defaultOpen = true }) {
  const [open, setOpen] = React.useState(defaultOpen);
  return (
    <div style={{ borderBottom: "1px solid var(--rule)" }}>
      <button onClick={() => setOpen(!open)} style={{
        width: "100%", display: "flex", justifyContent: "space-between", alignItems: "center",
        padding: "10px 16px", background: "none", border: "none", cursor: "pointer",
        fontFamily: "inherit", fontSize: 12, fontWeight: 600, color: "var(--text)",
        textTransform: "uppercase", letterSpacing: "0.05em"
      }}>
        {title}
        <span style={{ color: "var(--muted)", transform: open ? "rotate(90deg)" : "none", transition: "transform 150ms", fontSize: 10 }}>▶</span>
      </button>
      {open && <div style={{ padding: "0 16px 14px" }}>{children}</div>}
    </div>
  );
}

function FacetCheck({ checked, onChange, label, count, tone }) {
  return (
    <label style={{ display: "flex", alignItems: "center", gap: 8, padding: "4px 0", cursor: "pointer", fontSize: 13 }}>
      <input type="checkbox" checked={checked} onChange={onChange} style={{ accentColor: "var(--accent)", margin: 0 }} />
      {tone && <span style={{ width: 7, height: 7, borderRadius: "50%", background: `var(--pill-${tone === "neutral" ? "fg" : tone + "-fg"})`, flexShrink: 0 }} />}
      <span style={{ flex: 1, color: "var(--text)" }}>{label}</span>
      <span style={{ fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--muted)" }}>{count}</span>
    </label>
  );
}

function FacetRadioRow({ checked, onChange, label, count }) {
  return (
    <label style={{ display: "flex", alignItems: "center", gap: 8, padding: "4px 0", cursor: "pointer", fontSize: 13 }}>
      <input type="checkbox" checked={checked} onChange={onChange} style={{ accentColor: "var(--accent)", margin: 0 }} />
      <span style={{ flex: 1, color: "var(--text)" }}>{label}</span>
      <span style={{ fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--muted)" }}>{count}</span>
    </label>
  );
}

function Chip({ label, onClear }) {
  return (
    <span style={{ display: "inline-flex", alignItems: "center", gap: 6, background: "var(--nav-active-bg)", border: "1px solid var(--rule)", padding: "3px 6px 3px 10px", borderRadius: 999, fontSize: 12, color: "var(--text)" }}>
      {label}
      <button onClick={onClear} style={{ background: "var(--track)", border: "none", width: 16, height: 16, borderRadius: "50%", cursor: "pointer", color: "var(--muted)", fontSize: 11, lineHeight: 1, display: "grid", placeItems: "center", padding: 0 }}>×</button>
    </span>
  );
}

// Case detail — deadline, real timeline, two-way conversation, and status actions.
// (Moved here from the former Tracker screen during consolidation.)
function RequestDetail({ r, back }) {
  // Full two-way conversation for this case ref — our sent request(s) + inbound replies.
  const [thread, setThread] = React.useState([]);
  React.useEffect(() => {
    fetch(`/api/thread?ref=${encodeURIComponent(r.id || "")}`)
      .then((res) => (res.ok ? res.json() : Promise.reject(res.status)))
      .then((d) => setThread(d.messages || []))
      .catch(() => setThread([]));
  }, [r.id]);
  const replies = thread.filter((m) => !m.outbound);   // genuine inbound replies (drive the count + timeline)

  // Manual status override — wires the detail action buttons to /api/cases/{id}/status.
  const [localStatus, setLocalStatus] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  const [openReply, setOpenReply] = React.useState(null);   // id of the expanded reply row
  const curStatus = localStatus || r.status;
  const setStatus = async (s) => {
    setBusy(true);
    try {
      const resp = await fetch(`/api/cases/${encodeURIComponent(r.id)}/status`, {
        method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ status: s }),
      });
      if (resp.ok) {
        if (window.refreshCases) { try { await window.refreshCases(); } catch (_) {} }
        const updated = (window.REQUESTS || []).find((x) => x.id === r.id);
        setLocalStatus(updated ? updated.status : (s || r.status));
      }
    } finally { setBusy(false); }
  };

  const budget = r.deadlineDays || 20;
  const pct = r.workingDaysElapsed ? (r.workingDaysElapsed / budget) * 100 : 0;
  const tone = curStatus === "overdue" ? "danger" : pct > 70 ? "warn" : "info";
  const daysLeft = budget - r.workingDaysElapsed;

  // Timeline driven by real signals: the dispatch, the first actual reply (from the
  // matched inbox messages), the statutory deadline, and the current (override-aware) status.
  const replyTimes = replies.map((m) => new Date(m.date)).filter((d) => !isNaN(d.getTime()));
  const firstReply = replyTimes.length ? new Date(Math.min(...replyTimes.map((d) => d.getTime()))) : null;
  const hasReply = replies.length > 0;
  const answered = ["fulfilled", "partial", "refused"].includes(curStatus);
  const timeline = [
    { date: r.sentOn, label: "Dispatched", desc: `Sent via ${r.channel} to ${r.recipientName}`, ok: !!r.sentOn },
    { date: firstReply ? firstReply.toLocaleDateString("en-CA") : null, label: "Reply received",
      desc: hasReply ? `${replies.length} message${replies.length === 1 ? "" : "s"} on this thread` : "Awaiting first reply", ok: hasReply },
    { date: r.deadline, label: "Substantive response due", desc: "Statutory FOIA deadline", ok: answered },
    { date: null, label: "Internal review", desc: "If refused or partial", ok: curStatus === "internal-review" }
  ];

  return (
    <div>
      <button onClick={back} style={{ background: "none", border: "none", color: "var(--muted)", fontSize: 13, cursor: "pointer", fontFamily: "inherit", marginBottom: 10, padding: 0 }}>← Back to cases</button>

      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 20, gap: 24 }}>
        <div>
          <div style={{ fontFamily: "var(--font-mono)", fontSize: 12, color: "var(--muted)", marginBottom: 4 }}>{r.id}</div>
          <h2 style={{ margin: 0, fontSize: 22, fontWeight: 700, letterSpacing: "-0.01em", color: "var(--text)" }}>{r.title}</h2>
          <div style={{ fontSize: 13, color: "var(--muted)", marginTop: 6, maxWidth: 600 }}>{r.summary}</div>
        </div>
        <div style={{ display: "flex", gap: 8, flexWrap: "wrap", justifyContent: "flex-end", maxWidth: 360 }}>
          {curStatus !== "fulfilled" && <Button variant="secondary" onClick={() => setStatus("fulfilled")} disabled={busy} icon={<Ico.Check/>}>Mark fulfilled</Button>}
          {curStatus !== "refused" && <Button variant="secondary" onClick={() => setStatus("refused")} disabled={busy}>Mark refused</Button>}
          {curStatus !== "internal-review" && <Button variant="secondary" onClick={() => setStatus("internal-review")} disabled={busy} icon={<Ico.Warn/>}>Internal review</Button>}
          <Button variant="secondary" onClick={() => setStatus("")} disabled={busy}>Reset to auto</Button>
        </div>
      </div>

      <div style={{ display: "grid", gridTemplateColumns: "2fr 1fr", gap: 16 }}>
        <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
          {r.sentOn && (
            <Card>
              <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 14 }}>
                <div>
                  <div style={{ fontWeight: 600, fontSize: 14 }}>Response deadline</div>
                  <div style={{ fontSize: 12, color: "var(--muted)", marginTop: 2 }}>FOIA · {budget} working days{(r.recipientType === "MAT" || r.recipientType === "School") ? " (school-day rule)" : ""}</div>
                </div>
                <div style={{ textAlign: "right" }}>
                  <div style={{ fontSize: 28, fontWeight: 700, color: tone === "danger" ? "var(--danger-fg)" : "var(--text)", letterSpacing: "-0.02em", fontVariantNumeric: "tabular-nums" }}>
                    {daysLeft >= 0 ? `${daysLeft}` : `+${Math.abs(daysLeft)}`}
                  </div>
                  <div style={{ fontSize: 11, color: "var(--muted)", textTransform: "uppercase", letterSpacing: "0.06em", fontWeight: 600 }}>
                    {daysLeft >= 0 ? "days left" : "days overdue"}
                  </div>
                </div>
              </div>
              <Bar pct={Math.min(100, pct)} tone={tone} />
              <div style={{ display: "flex", justifyContent: "space-between", fontSize: 11, fontFamily: "var(--font-mono)", color: "var(--muted)", marginTop: 8 }}>
                <span>Sent {r.sentOn}</span>
                <span>Due {r.deadline}</span>
              </div>
            </Card>
          )}

          <Card pad={0}>
            <div style={{ padding: "14px 18px", borderBottom: "1px solid var(--rule)", fontWeight: 600, fontSize: 14 }}>Timeline</div>
            <div style={{ padding: "8px 18px 18px" }}>
              {timeline.map((step, i) => (
                <div key={i} style={{ display: "grid", gridTemplateColumns: "20px 1fr auto", gap: 14, padding: "12px 0", borderBottom: i < timeline.length - 1 ? "1px dashed var(--rule)" : "none", alignItems: "flex-start" }}>
                  <div style={{
                    width: 16, height: 16, borderRadius: "50%",
                    background: step.ok ? "var(--ok-bg)" : "var(--surface-2)",
                    border: `1px solid ${step.ok ? "var(--ok-bd)" : "var(--rule)"}`,
                    color: "var(--ok-fg)",
                    display: "grid", placeItems: "center",
                    marginTop: 2
                  }}>
                    {step.ok && <svg width="8" height="8" viewBox="0 0 8 8" fill="none" stroke="currentColor" strokeWidth="1.75"><path d="M1.5 4l1.5 1.5L6.5 2"/></svg>}
                  </div>
                  <div>
                    <div style={{ fontWeight: step.ok ? 600 : 500, fontSize: 13, color: step.ok ? "var(--text)" : "var(--muted)" }}>{step.label}</div>
                    <div style={{ fontSize: 12, color: "var(--muted)", marginTop: 2 }}>{step.desc}</div>
                  </div>
                  <div style={{ fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--muted)" }}>{step.date || "—"}</div>
                </div>
              ))}
            </div>
          </Card>

          {thread.length > 0 && (
            <Card pad={0}>
              <div style={{ padding: "14px 18px", borderBottom: "1px solid var(--rule)", display: "flex", justifyContent: "space-between", alignItems: "baseline", gap: 8 }}>
                <div style={{ fontWeight: 600, fontSize: 14 }}>Conversation</div>
                <span style={{ fontFamily: "var(--font-mono)", fontSize: 12, color: "var(--muted)" }}>
                  {thread.length} message{thread.length === 1 ? "" : "s"} · {replies.length} repl{replies.length === 1 ? "y" : "ies"}
                </span>
              </div>
              {thread.map((m) => {
                const fm = (m.from || "").match(/^\s*"?([^"<]*?)"?\s*<([^>]+)>/);
                const fromName = (fm ? (fm[1] || fm[2]) : (m.from || "")).trim() || "Unknown sender";
                const open = openReply === m.id;
                const tone = m.outbound ? "var(--accent)" : "var(--ok-fg)";
                return (
                  <div key={m.id} style={{ borderBottom: "1px solid var(--rule)", borderLeft: `3px solid ${tone}` }}>
                    <div onClick={() => setOpenReply(open ? null : m.id)}
                      onMouseEnter={(e) => e.currentTarget.style.background = "var(--surface-2)"}
                      onMouseLeave={(e) => e.currentTarget.style.background = "transparent"}
                      style={{ padding: "12px 18px", cursor: "pointer" }}>
                      <div style={{ display: "flex", justifyContent: "space-between", gap: 8, alignItems: "center" }}>
                        <div style={{ display: "flex", alignItems: "center", gap: 8, minWidth: 0 }}>
                          <span style={{ fontSize: 9.5, fontWeight: 700, letterSpacing: "0.04em", padding: "1px 6px", borderRadius: 2, color: "#fff", background: tone, whiteSpace: "nowrap" }}>{m.outbound ? "SENT" : "REPLY"}</span>
                          <div style={{ fontSize: 13, fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{fromName}</div>
                        </div>
                        <div style={{ fontSize: 11, fontFamily: "var(--font-mono)", color: "var(--muted)", whiteSpace: "nowrap" }}>{(m.date || "").slice(0, 16)}</div>
                      </div>
                      <div style={{ display: "flex", justifyContent: "space-between", gap: 8, alignItems: "center", marginTop: 4 }}>
                        <div style={{ fontSize: 12, color: "var(--muted)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{m.subject || "(no subject)"}</div>
                        <span style={{ fontSize: 11, color: "var(--accent)", fontWeight: 500, whiteSpace: "nowrap" }}>{open ? "Hide ▲" : "View ▼"}</span>
                      </div>
                    </div>
                    {open && (
                      <div style={{ padding: "0 18px 14px" }}>
                        <div style={{ border: "1px solid var(--rule)", background: "var(--surface-2)", borderRadius: 2, padding: 12, maxHeight: 300, overflow: "auto", fontSize: 12.5, lineHeight: 1.6, whiteSpace: "pre-wrap", color: "var(--text)", fontFamily: "var(--font-mono)" }}>
                          {(m.body || m.snippet || "(no message body)").trim()}
                        </div>
                      </div>
                    )}
                  </div>
                );
              })}
            </Card>
          )}
        </div>

        <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
          <Card>
            <div style={{ fontWeight: 600, fontSize: 14, marginBottom: 12 }}>Details</div>
            <div style={{ display: "grid", gridTemplateColumns: "auto 1fr", gap: "8px 14px", fontSize: 13 }}>
              <span style={{ color: "var(--muted)" }}>Status</span><span><StatusPill status={curStatus}/></span>
              <span style={{ color: "var(--muted)" }}>Recipient</span><span>{r.recipientName}</span>
              <span style={{ color: "var(--muted)" }}>Channel</span><span>{r.channel}</span>
              <span style={{ color: "var(--muted)" }}>Sent</span><span style={{ fontFamily: "var(--font-mono)" }}>{r.sentOn || "—"}</span>
              <span style={{ color: "var(--muted)" }}>Owner</span><span>{r.owner}</span>
              <span style={{ color: "var(--muted)" }}>Replies</span><span style={{ fontVariantNumeric: "tabular-nums" }}>{replies.length}</span>
            </div>
          </Card>

          <Card>
            <div style={{ fontWeight: 600, fontSize: 14, marginBottom: 8 }}>Compliance checks</div>
            <ul style={{ paddingLeft: 0, margin: 0, listStyle: "none", fontSize: 13 }}>
              <li style={{ display: "flex", gap: 8, padding: "6px 0", borderBottom: "1px dashed var(--rule)" }}>
                <span style={{ color: "var(--ok-fg)" }}>✓</span><span>Within s.12 cost limit</span>
              </li>
              <li style={{ display: "flex", gap: 8, padding: "6px 0", borderBottom: "1px dashed var(--rule)" }}>
                <span style={{ color: "var(--ok-fg)" }}>✓</span><span>No vexatious-history flags</span>
              </li>
              <li style={{ display: "flex", gap: 8, padding: "6px 0", borderBottom: "1px dashed var(--rule)" }}>
                <span style={{ color: r.status === "overdue" ? "var(--danger-fg)" : "var(--ok-fg)" }}>{r.status === "overdue" ? "!" : "✓"}</span>
                <span>{r.status === "overdue" ? "Past 20-working-day deadline" : "Within 20-working-day window"}</span>
              </li>
              <li style={{ display: "flex", gap: 8, padding: "6px 0" }}>
                <span style={{ color: "var(--ok-fg)" }}>✓</span><span>Redaction pass complete</span>
              </li>
            </ul>
          </Card>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { CaseIndex, RequestDetail });
