// Nexa — Internal compliance dashboard (rebuild)
// Single-file React app wired to https://api.usenexa.io

const { useState, useEffect, useMemo, useCallback } = React;

const API_BASE = window.location.origin;
const RESET_KEY = "3F8869A9-609D-4892-B380-D0933E0C263B";

// ── Formatters ─────────────────────────────────────────────────────────
const fmtMoney = (amt, ccy) => {
  if (amt == null) return "—";
  const sym = { GBP: "£", USD: "$", EUR: "€", JPY: "¥", CAD: "C$", CHF: "CHF ", CNY: "¥" }[ccy] || "";
  const n = Number(amt).toLocaleString("en-GB", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
  return `${sym}${n}${sym ? "" : " " + (ccy || "")}`;
};
const fmtCompact = (n) => {
  if (n == null) return "—";
  if (Math.abs(n) >= 1e9) return (n / 1e9).toFixed(1) + "B";
  if (Math.abs(n) >= 1e6) return (n / 1e6).toFixed(1) + "M";
  if (Math.abs(n) >= 1e3) return (n / 1e3).toFixed(0) + "k";
  return String(n);
};
const fmtDateTime = (iso) => {
  if (!iso) return "—";
  const d = new Date(iso);
  if (isNaN(d)) return iso;
  const date = d.toLocaleDateString("en-GB", { day: "2-digit", month: "short", timeZone: "UTC" });
  const time = d.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit", timeZone: "UTC" });
  return `${date} · ${time}`;
};
const fmtDate = (iso) => {
  if (!iso) return "—";
  const d = new Date(iso);
  if (isNaN(d)) return iso;
  return d.toLocaleDateString("en-GB", { day: "2-digit", month: "short", year: "numeric", timeZone: "UTC" });
};
const fmtRelative = (iso) => {
  if (!iso) return "—";
  const d = new Date(iso);
  if (isNaN(d)) return iso;
  const diff = (d - Date.now()) / 1000;
  const abs = Math.abs(diff);
  const sign = diff < 0 ? "ago" : "from now";
  if (abs < 60) return `${Math.round(abs)}s ${sign}`;
  if (abs < 3600) return `${Math.round(abs / 60)}m ${sign}`;
  if (abs < 86400) return `${Math.round(abs / 3600)}h ${sign}`;
  return `${Math.round(abs / 86400)}d ${sign}`;
};

// ── API helpers ────────────────────────────────────────────────────────
async function apiGet(path) {
  const headers = {};
  if (path === "/internal/state" || path.startsWith("/internal/state?")) {
    headers["X-Internal-Key"] = RESET_KEY;
  }
  const r = await fetch(`${API_BASE}${path}`, { headers });
  if (!r.ok) throw new Error(`${r.status} ${r.statusText}`);
  return r.json();
}
async function apiPost(path, body) {
  const headers = { "Content-Type": "application/json" };
  if (path.startsWith("/internal/")) {
    headers["X-Internal-Key"] = RESET_KEY;
  }
  const r = await fetch(`${API_BASE}${path}`, {
    method: "POST",
    headers,
    body: body ? JSON.stringify(body) : undefined,
  });
  if (!r.ok) throw new Error(`${r.status} ${r.statusText}`);
  const text = await r.text();
  return text ? JSON.parse(text) : null;
}
async function apiReset() {
  const r = await fetch(`${API_BASE}/internal/reset-data`, {
    method: "POST",
    headers: { "X-Internal-Key": RESET_KEY },
  });
  if (!r.ok) throw new Error(`${r.status} ${r.statusText}`);
  return r.json();
}

// ── Toast bus ──────────────────────────────────────────────────────────
let toastSetter = null;
function toast(msg, opts = {}) {
  if (!toastSetter) return;
  toastSetter({ msg, error: !!opts.error, key: Date.now() });
  setTimeout(() => toastSetter && toastSetter(null), 2400);
}

// ── Common cells ───────────────────────────────────────────────────────
const Badge = ({ kind, children }) => (
  <span className={`tc-badge is-${kind}`}><span className="tc-badge-dot" />{children}</span>
);
const RiskBadge = ({ r }) => {
  const label = { mandatory_high: "mandatory high" }[r] || r || "—";
  return <span className={`tc-risk is-${r}`}><span className="tc-risk-dot" />{label}</span>;
};
const TxStatusBadge = ({ s }) => {
  const label = { pending_approval: "pending approval" }[s] || s;
  return <Badge kind={s}>{label}</Badge>;
};
const RfiStatusBadge = ({ status, overdue }) => {
  const label = overdue && status === "open" ? "open · overdue" : status;
  return <Badge kind={status}>{label}</Badge>;
};
const StageBadge = ({ s }) => {
  const label = { kyb_review: "kyb review", edd_required: "edd required", documents_pending: "docs pending", hard_blocked: "hard blocked" }[s] || s || "—";
  return <span className={`tc-stage is-${s}`}>{label}</span>;
};
const FlagPills = ({ flags }) => {
  if (!flags || flags.length === 0) return <span className="tc-muted tc-mono-sm">—</span>;
  return (
    <div style={{ display: "flex", flexWrap: "wrap", gap: 4 }}>
      {flags.map((f, i) => {
        const label = typeof f === "string" ? f : f.label || f.type || "flag";
        const crit = (typeof f !== "string" && (f.type === "sanctions_near_miss" || f.type === "agent_auto_suspended")) ? "is-critical" : "";
        return <span key={i} className={`tc-flag-pill ${crit}`}>{label}</span>;
      })}
    </div>
  );
};

const BizCell = ({ id, businesses }) => {
  const b = businesses?.[id];
  if (!b) return <span className="tc-mono-sm tc-muted">{id}</span>;
  return (
    <div className="tc-biz-cell">
      <span className="tc-biz-cell-n">{b.name}</span>
      <span className="tc-biz-cell-s">{b.country || "—"} · {b.industry || "—"}</span>
    </div>
  );
};

// ── Side nav ───────────────────────────────────────────────────────────
const NAV_ITEMS = [
  { id: "overview",   label: "Overview",          group: "Platform" },
  { id: "monitoring", label: "Transactions",      group: "Monitoring" },
  { id: "rfis",       label: "RFIs",              group: "Monitoring" },
  { id: "alerts",     label: "Alerts",            group: "Monitoring" },
  { id: "businesses", label: "Businesses",        group: "KYB" },
  { id: "onboarding", label: "Onboarding queue",  group: "KYB" },
  { id: "reviews",    label: "Periodic reviews",  group: "KYB" },
  { id: "audit",      label: "Audit log",         group: "Platform" },
];
const NavIcon = ({ name }) => {
  const p = { className: "tc-nav-item-icon", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.4, strokeLinecap: "round", strokeLinejoin: "round" };
  switch (name) {
    case "overview":   return <svg {...p}><rect x="2" y="2" width="5" height="5" rx="1"/><rect x="9" y="2" width="5" height="5" rx="1"/><rect x="2" y="9" width="5" height="5" rx="1"/><rect x="9" y="9" width="5" height="5" rx="1"/></svg>;
    case "monitoring": return <svg {...p}><path d="M2 9l3-4 3 4 3-3 3 4"/><path d="M2 13h12"/></svg>;
    case "rfis":       return <svg {...p}><path d="M3 3h10v8H7l-3 3v-3H3z"/><path d="M6 6h4M6 8h3"/></svg>;
    case "alerts":     return <svg {...p}><path d="M8 2l6 11H2z"/><path d="M8 6v3M8 11v.4"/></svg>;
    case "businesses": return <svg {...p}><path d="M3 13V5l5-3 5 3v8"/><path d="M6 13V9h4v4"/></svg>;
    case "onboarding": return <svg {...p}><path d="M2 4h12v8H2z"/><path d="M2 7h12"/></svg>;
    case "reviews":    return <svg {...p}><path d="M2 3h12v10H2z"/><path d="M5 7h6M5 9.5h4"/><path d="M12 6l-4 4-2-2"/></svg>;
    case "audit":      return <svg {...p}><path d="M3 2h7l3 3v9H3z"/><path d="M5.5 7h5M5.5 9.5h5M5.5 12h3"/></svg>;
    default: return null;
  }
};

function SideNav({ page, onNav, counts, you }) {
  const groups = ["Platform", "Monitoring", "KYB"];
  const alertish = new Set(["rfis", "alerts", "onboarding", "monitoring", "reviews"]);
  return (
    <aside className="tc-side">
      <div className="tc-side-brand">
        <span className="tc-brand-mark" aria-hidden="true">
          <svg width="22" height="22" viewBox="0 0 48 48" fill="none">
            <rect x="6" y="14" width="2.4" height="20" fill="currentColor" />
            <rect x="8.4" y="22.8" width="26" height="2.4" fill="currentColor" />
            <circle cx="38.5" cy="24" r="4.2" fill="currentColor" />
          </svg>
        </span>
        <span className="tc-brand-text">tenor</span>
        <span className="tc-brand-tag">INTERNAL</span>
      </div>
      <div className="tc-side-org">
        <span className="tc-org-k">Compliance Operations</span>
        <span className="tc-org-v">Nexa · Trust ops</span>
      </div>
      <nav className="tc-side-nav" aria-label="Primary">
        {groups.map(g => (
          <React.Fragment key={g}>
            <span className="tc-side-nav-h">{g}</span>
            {NAV_ITEMS.filter(n => n.group === g).map(n => {
              const c = counts[n.id];
              const alert = alertish.has(n.id) && c > 0;
              return (
                <button
                  key={n.id}
                  className={`tc-nav-item ${page === n.id ? "is-active" : ""} ${alert ? "is-alert" : ""}`}
                  onClick={() => onNav(n.id)}
                >
                  <NavIcon name={n.id} />
                  <span className="tc-nav-item-label">{n.label}</span>
                  {c ? <span className="tc-nav-item-count">{c}</span> : null}
                </button>
              );
            })}
          </React.Fragment>
        ))}
      </nav>
      <div className="tc-side-foot">
        <div className="tc-side-avatar">{you.initials}</div>
        <div style={{ display: "flex", flexDirection: "column", gap: 1, minWidth: 0 }}>
          <span className="tc-side-foot-l">{you.name}</span>
          <span className="tc-side-foot-s">{you.role}</span>
        </div>
      </div>
    </aside>
  );
}

// ── Topbar ─────────────────────────────────────────────────────────────
const PAGE_TITLES = {
  overview:   { eyebrow: "Platform · live",         h: "Compliance overview" },
  monitoring: { eyebrow: "Monitoring · 48h window", h: "Transaction monitor" },
  rfis:       { eyebrow: "Monitoring",              h: "Requests for information" },
  alerts:     { eyebrow: "Monitoring",              h: "Alerts feed" },
  businesses: { eyebrow: "KYB",                     h: "Onboarded businesses" },
  onboarding: { eyebrow: "KYB",                     h: "Onboarding queue" },
  reviews:    { eyebrow: "KYB · ongoing monitoring", h: "Periodic client reviews" },
  audit:      { eyebrow: "Platform",                h: "Audit log" },
};
function TopBar({ page, lastFetch, onRefresh }) {
  const t = PAGE_TITLES[page] || { eyebrow: "", h: page };
  return (
    <header className="tc-topbar">
      <div className="tc-topbar-l">
        <span className="tc-eyebrow">{t.eyebrow}</span>
        <span className="tc-topbar-h">{t.h}</span>
      </div>
      <div className="tc-topbar-r">
        <span className="tc-mono-sm tc-muted" style={{ fontSize: 11 }}>
          <span className="tc-livedot" style={{ marginRight: 6 }} />
          last fetch · {lastFetch ? fmtRelative(lastFetch) : "—"}
        </span>
        <button className="tc-btn tc-btn-ghost" onClick={onRefresh}>Refresh</button>
      </div>
    </header>
  );
}

// ── Pages ──────────────────────────────────────────────────────────────
function OverviewPage({ state, onNav, onOpen }) {
  const { businesses = [], biz_by_id = {}, transactions = [], rfis = [], alerts = [], applications = [] } = state;
  const today = new Date().toISOString().slice(0, 10);

  const activeBiz = businesses.filter(b => b.status === "active").length;
  const openRfis = rfis.filter(r => r.status === "open" || r.status === "responded").length;
  const overdueRfis = rfis.filter(r => r.overdue && r.status !== "resolved").length;
  const pendingApprovals = transactions.filter(t => t.status === "pending_approval").length;
  const flaggedToday = transactions.filter(t => (t.flags?.length > 0) && (t.ts || "").startsWith(today)).length;

  // Pipeline funnel
  const onboarding = applications.length ? applications : businesses.filter(b => b.status === "onboarding").map(b => ({
    id: b.id, name: b.name, onboardingStage: b.onboardingStage, daysInQueue: b.daysInQueue, riskRating: b.riskRating, country: b.country, industry: b.industry, submittedDate: b.submittedDate, assignedAnalyst: b.assignedAnalyst,
  }));
  const stages = [
    { k: "submitted", label: "Submitted" },
    { k: "kyb_review", label: "KYB review" },
    { k: "edd_required", label: "EDD required", flag: "is-edd" },
    { k: "documents_pending", label: "Docs pending" },
    { k: "hard_blocked", label: "Hard blocked", flag: "is-blocked" },
  ];
  const stageCount = (k) => onboarding.filter(a => a.onboardingStage === k).length;

  const recentAlerts = [...alerts].sort((a, b) => (b.ts || "").localeCompare(a.ts || "")).slice(0, 6);
  const recentTx = [...transactions].sort((a, b) => (b.ts || "").localeCompare(a.ts || "")).slice(0, 8);

  return (
    <>
      <div className="tc-kpis">
        <div className="tc-kpi"><span className="tc-kpi-k">Active businesses</span><span className="tc-kpi-v">{activeBiz}</span><span className="tc-kpi-meta">{businesses.length} total</span></div>
        <div className={`tc-kpi ${openRfis ? "is-warn" : ""}`}><span className="tc-kpi-k">Open RFIs</span><span className="tc-kpi-v">{openRfis}</span><span className="tc-kpi-meta">awaiting / responded</span></div>
        <div className={`tc-kpi ${overdueRfis ? "is-alert" : ""}`}><span className="tc-kpi-k">Overdue RFIs</span><span className="tc-kpi-v">{overdueRfis}</span><span className="tc-kpi-meta">past due date</span></div>
        <div className={`tc-kpi ${pendingApprovals ? "is-warn" : ""}`}><span className="tc-kpi-k">Pending approvals</span><span className="tc-kpi-v">{pendingApprovals}</span><span className="tc-kpi-meta">awaiting decision</span></div>
        <div className={`tc-kpi ${flaggedToday ? "is-alert" : ""}`}><span className="tc-kpi-k">Flagged today</span><span className="tc-kpi-v">{flaggedToday}</span><span className="tc-kpi-meta">transactions</span></div>
      </div>

      <div className="tc-card">
        <div className="tc-card-h">
          <h3>Onboarding pipeline</h3>
          <span className="tc-card-h-meta" onClick={() => onNav("onboarding")} style={{ cursor: "pointer" }}>view queue →</span>
        </div>
        <div className="tc-card-body-flush">
          <div className="tc-funnel">
            {stages.map(s => (
              <div key={s.k} className={`tc-funnel-step ${s.flag || ""}`}>
                <span className="tc-funnel-step-k">{s.label}</span>
                <span className="tc-funnel-step-v">{stageCount(s.k)}</span>
                <span className="tc-funnel-step-meta">applications</span>
              </div>
            ))}
          </div>
        </div>
      </div>

      <div className="tc-grid-2">
        <div className="tc-card">
          <div className="tc-card-h">
            <h3>Recent alerts</h3>
            <span className="tc-card-h-meta" onClick={() => onNav("alerts")} style={{ cursor: "pointer" }}>all alerts →</span>
          </div>
          <div className="tc-card-body-flush">
            <table className="tc-tbl">
              <thead><tr><th>Severity</th><th>Trigger</th><th>Business</th><th>When</th></tr></thead>
              <tbody>
                {recentAlerts.length === 0 && <tr><td colSpan="4" className="tc-tbl-empty">no alerts</td></tr>}
                {recentAlerts.map(a => (
                  <tr key={a.id}>
                    <td><span className={`tc-sev is-${a.severity}`}>{a.severity}</span></td>
                    <td style={{ maxWidth: 260 }}>{a.trigger}</td>
                    <td><BizCell id={a.bizId} businesses={biz_by_id} /></td>
                    <td className="tc-mono-sm tc-muted">{fmtRelative(a.ts)}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </div>

        <div className="tc-card">
          <div className="tc-card-h">
            <h3>Recent transactions</h3>
            <span className="tc-card-h-meta" onClick={() => onNav("monitoring")} style={{ cursor: "pointer" }}>monitor →</span>
          </div>
          <div className="tc-card-body-flush">
            <table className="tc-tbl">
              <thead><tr><th>Time</th><th>Counterparty</th><th className="tc-num">Amount</th><th>Status</th></tr></thead>
              <tbody>
                {recentTx.length === 0 && <tr><td colSpan="4" className="tc-tbl-empty">no transactions</td></tr>}
                {recentTx.map(t => (
                  <tr key={t.id}>
                    <td className="tc-mono-sm tc-muted">{fmtRelative(t.ts)}</td>
                    <td>{t.counterparty || "—"}</td>
                    <td className="tc-num">{fmtMoney(t.amount, t.ccy)}</td>
                    <td><TxStatusBadge s={t.status} /></td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </div>
      </div>
    </>
  );
}

function MonitoringPage({ state, refresh, onOpenBiz }) {
  const { transactions = [], biz_by_id = {} } = state;
  const [filter, setFilter] = useState("all");
  const [search, setSearch] = useState("");
  const [busy, setBusy] = useState(null);

  const rows = useMemo(() => {
    return transactions
      .filter(t => filter === "all" || t.status === filter)
      .filter(t => !search || (t.counterparty || "").toLowerCase().includes(search.toLowerCase()) || (t.id || "").toLowerCase().includes(search.toLowerCase()))
      .sort((a, b) => (b.ts || "").localeCompare(a.ts || ""));
  }, [transactions, filter, search]);

  async function doApproval(t, action) {
    setBusy(t.id);
    try {
      // Find approval id for this tx
      const list = await apiGet(`/approvals?business_id=${encodeURIComponent(t.bizId)}`);
      const approval = (Array.isArray(list) ? list : list?.approvals || []).find(a => a.tx_id === t.id || a.transaction_id === t.id || a.id === t.id);
      const apId = approval?.id || approval?.approval_id || t.id;
      await apiPost(`/approvals/${encodeURIComponent(apId)}/${action}`);
      toast(`${action === "approve" ? "Approved" : "Rejected"} ${t.id}`);
      refresh();
    } catch (e) {
      toast(`${action} failed: ${e.message}`, { error: true });
    } finally {
      setBusy(null);
    }
  }

  return (
    <div className="tc-card">
      <div className="tc-filterbar">
        <span className="tc-filter-input">
          <svg width="13" height="13" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.4"><circle cx="6" cy="6" r="4"/><path d="M9 9l3 3"/></svg>
          <input value={search} onChange={e => setSearch(e.target.value)} placeholder="Search counterparty or tx id…" />
        </span>
        <span className="tc-filter-divider" />
        <select className="tc-filter-sel" value={filter} onChange={e => setFilter(e.target.value)}>
          <option value="all">All statuses</option>
          <option value="settled">Settled</option>
          <option value="pending_approval">Pending approval</option>
          <option value="rejected">Rejected</option>
        </select>
        <span className="tc-spacer" />
        <span className="tc-mono-sm tc-muted">{rows.length} of {transactions.length}</span>
      </div>
      <div className="tc-card-body-flush">
        <table className="tc-tbl">
          <thead>
            <tr>
              <th>Time (UTC)</th>
              <th>Business</th>
              <th>Agent</th>
              <th>Op</th>
              <th>Counterparty</th>
              <th className="tc-num">Amount</th>
              <th>Status</th>
              <th>Flags</th>
              <th></th>
            </tr>
          </thead>
          <tbody>
            {rows.length === 0 && <tr><td colSpan="9" className="tc-tbl-empty">no transactions match filters</td></tr>}
            {rows.map(t => (
              <tr key={t.id}>
                <td className="tc-mono-sm">{fmtDateTime(t.ts)}<div className="tc-id">{t.id}</div></td>
                <td onClick={() => onOpenBiz?.(t.bizId)} style={{ cursor: "pointer" }}><BizCell id={t.bizId} businesses={biz_by_id} /></td>
                <td className="tc-mono-sm">{t.agent || "—"}</td>
                <td className="tc-mono-sm">{t.op || "—"}</td>
                <td>{t.counterparty || "—"}<div className="tc-id">{t.reason}</div></td>
                <td className="tc-num">{fmtMoney(t.amount, t.ccy)}</td>
                <td><TxStatusBadge s={t.status} /></td>
                <td><FlagPills flags={t.flags} /></td>
                <td>
                  {t.status === "pending_approval" && (
                    <div style={{ display: "flex", gap: 4, justifyContent: "flex-end" }}>
                      <button className={`tc-btn tc-btn-sm tc-btn-primary ${busy === t.id ? "is-busy" : ""}`} disabled={busy === t.id} onClick={() => doApproval(t, "approve")}>Approve</button>
                      <button className={`tc-btn tc-btn-sm tc-btn-danger ${busy === t.id ? "is-busy" : ""}`} disabled={busy === t.id} onClick={() => doApproval(t, "reject")}>Reject</button>
                    </div>
                  )}
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

function RfisPage({ state, onOpenRfi }) {
  const { rfis = [], biz_by_id = {} } = state;
  const [filter, setFilter] = useState("all");

  const rows = useMemo(() => {
    return rfis
      .filter(r => filter === "all" || r.status === filter || (filter === "overdue" && r.overdue))
      .sort((a, b) => {
        // overdue first, then by createdAt desc
        if (a.overdue !== b.overdue) return a.overdue ? -1 : 1;
        return (b.createdAt || "").localeCompare(a.createdAt || "");
      });
  }, [rfis, filter]);

  return (
    <div className="tc-card">
      <div className="tc-filterbar">
        <select className="tc-filter-sel" value={filter} onChange={e => setFilter(e.target.value)}>
          <option value="all">All RFIs</option>
          <option value="open">Open</option>
          <option value="responded">Responded</option>
          <option value="escalated">Escalated</option>
          <option value="resolved">Resolved</option>
          <option value="overdue">Overdue only</option>
        </select>
        <span className="tc-spacer" />
        <span className="tc-mono-sm tc-muted">{rows.length} of {rfis.length}</span>
      </div>
      <div className="tc-card-body-flush">
        <table className="tc-tbl">
          <thead>
            <tr>
              <th>RFI</th>
              <th>Business</th>
              <th>Status</th>
              <th>Urgency</th>
              <th>Created</th>
              <th>Due</th>
              <th></th>
            </tr>
          </thead>
          <tbody>
            {rows.length === 0 && <tr><td colSpan="7" className="tc-tbl-empty">no RFIs</td></tr>}
            {rows.map(r => (
              <tr key={r.id} className="is-clickable" onClick={() => onOpenRfi(r.id)}>
                <td className="tc-mono-sm">{r.id}<div className="tc-id">{(r.questions || []).length} question{(r.questions || []).length === 1 ? "" : "s"}</div></td>
                <td><BizCell id={r.bizId} businesses={biz_by_id} /></td>
                <td><RfiStatusBadge status={r.status} overdue={r.overdue} /></td>
                <td><span className={`tc-urg is-${r.urgency || "standard"}`}>{r.urgency || "standard"}</span></td>
                <td className="tc-mono-sm">{fmtDateTime(r.createdAt)}</td>
                <td className="tc-mono-sm">{fmtDate(r.dueAt)}{r.overdue && <span className="tc-flag-pill is-overdue" style={{ marginLeft: 6 }}>overdue</span>}</td>
                <td className="tc-mono-sm tc-muted">→</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

function AlertsPage({ state, onOpenRfi }) {
  const { alerts = [], biz_by_id = {} } = state;
  const [sev, setSev] = useState("all");

  const rows = useMemo(() => alerts
    .filter(a => sev === "all" || a.severity === sev)
    .sort((a, b) => (b.ts || "").localeCompare(a.ts || "")), [alerts, sev]);

  return (
    <div className="tc-card">
      <div className="tc-filterbar">
        <select className="tc-filter-sel" value={sev} onChange={e => setSev(e.target.value)}>
          <option value="all">All severities</option>
          <option value="info">Info</option>
          <option value="warning">Warning</option>
          <option value="critical">Critical</option>
        </select>
        <span className="tc-spacer" />
        <span className="tc-mono-sm tc-muted">{rows.length} of {alerts.length}</span>
      </div>
      <div className="tc-card-body-flush">
        <table className="tc-tbl">
          <thead><tr><th>Alert</th><th>Severity</th><th>Trigger</th><th>Business</th><th>RFI</th><th>When</th></tr></thead>
          <tbody>
            {rows.length === 0 && <tr><td colSpan="6" className="tc-tbl-empty">no alerts</td></tr>}
            {rows.map(a => (
              <tr key={a.id}>
                <td className="tc-mono-sm">{a.id}<div className="tc-id">{a.type}</div></td>
                <td><span className={`tc-sev is-${a.severity}`}>{a.severity}</span></td>
                <td style={{ maxWidth: 360 }}>{a.trigger}</td>
                <td><BizCell id={a.bizId} businesses={biz_by_id} /></td>
                <td>{a.relatedRfi ? <a className="tc-mono-sm" style={{ color: "var(--info)", cursor: "pointer" }} onClick={() => onOpenRfi(a.relatedRfi)}>{a.relatedRfi}</a> : <span className="tc-muted tc-mono-sm">—</span>}</td>
                <td className="tc-mono-sm">{fmtDateTime(a.ts)}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

function BusinessesPage({ state, onOpenBiz }) {
  const { businesses = [] } = state;
  const [search, setSearch] = useState("");
  const [risk, setRisk] = useState("all");
  const [status, setStatus] = useState("all");

  const rows = useMemo(() => businesses
    .filter(b => b.status !== "onboarding")
    .filter(b => risk === "all" || b.riskRating === risk)
    .filter(b => status === "all" || b.status === status)
    .filter(b => !search || (b.name || "").toLowerCase().includes(search.toLowerCase()))
    .sort((a, b) => (b.volume30d || 0) - (a.volume30d || 0)), [businesses, search, risk, status]);

  return (
    <div className="tc-card">
      <div className="tc-filterbar">
        <span className="tc-filter-input">
          <svg width="13" height="13" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.4"><circle cx="6" cy="6" r="4"/><path d="M9 9l3 3"/></svg>
          <input value={search} onChange={e => setSearch(e.target.value)} placeholder="Search businesses…" />
        </span>
        <span className="tc-filter-divider" />
        <select className="tc-filter-sel" value={risk} onChange={e => setRisk(e.target.value)}>
          <option value="all">All risk</option><option value="low">Low</option><option value="medium">Medium</option><option value="high">High</option><option value="mandatory_high">Mandatory high</option>
        </select>
        <select className="tc-filter-sel" value={status} onChange={e => setStatus(e.target.value)}>
          <option value="all">All status</option><option value="active">Active</option><option value="suspended">Suspended</option><option value="under_review">Under review</option>
        </select>
        <span className="tc-spacer" />
        <span className="tc-mono-sm tc-muted">{rows.length} of {businesses.length}</span>
      </div>
      <div className="tc-card-body-flush">
        <table className="tc-tbl">
          <thead>
            <tr><th>Business</th><th>Status</th><th>Risk</th><th className="tc-num">Active agents</th><th className="tc-num">30d volume</th><th className="tc-num">Open RFIs</th><th></th></tr>
          </thead>
          <tbody>
            {rows.length === 0 && <tr><td colSpan="7" className="tc-tbl-empty">no businesses</td></tr>}
            {rows.map(b => (
              <tr key={b.id} className="is-clickable" onClick={() => onOpenBiz(b.id)}>
                <td><div className="tc-biz-cell"><span className="tc-biz-cell-n">{b.name}</span><span className="tc-biz-cell-s">{b.id} · {b.country || "—"} · {b.industry || "—"}</span></div></td>
                <td><Badge kind={b.status}>{b.status?.replace("_", " ")}</Badge></td>
                <td><RiskBadge r={b.riskRating} /></td>
                <td className="tc-num">{b.activeAgents ?? 0}{b.suspendedAgents ? <span className="tc-muted"> +{b.suspendedAgents} susp</span> : null}</td>
                <td className="tc-num">{fmtMoney(b.volume30d, b.volumeCcy)}</td>
                <td className="tc-num">{b.openRfis || 0}</td>
                <td className="tc-mono-sm tc-muted">→</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

function OnboardingPage({ state, onOpenBiz }) {
  const { applications = [], businesses = [], analysts = [] } = state;
  const apps = applications.length ? applications : businesses.filter(b => b.status === "onboarding");
  const analystName = (id) => analysts.find(a => a.id === id)?.name || (id ? id : "unassigned");

  const rows = [...apps].sort((a, b) => (b.daysInQueue || 0) - (a.daysInQueue || 0));

  return (
    <div className="tc-card">
      <div className="tc-card-h">
        <h3>Onboarding queue</h3>
        <span className="tc-card-h-meta">{rows.length} applications</span>
      </div>
      <div className="tc-card-body-flush">
        <table className="tc-tbl">
          <thead>
            <tr><th>Application</th><th>Stage</th><th>Risk</th><th>Country</th><th className="tc-num">Days in queue</th><th>Submitted</th><th>Analyst</th></tr>
          </thead>
          <tbody>
            {rows.length === 0 && <tr><td colSpan="7" className="tc-tbl-empty">no applications in queue</td></tr>}
            {rows.map(a => (
              <tr key={a.id} className="is-clickable" onClick={() => onOpenBiz(a.id)}>
                <td><div className="tc-biz-cell"><span className="tc-biz-cell-n">{a.name}</span><span className="tc-biz-cell-s">{a.id} · {a.industry || "—"}</span></div></td>
                <td><StageBadge s={a.onboardingStage} /></td>
                <td><RiskBadge r={a.riskRating} /></td>
                <td className="tc-mono-sm">{a.country || "—"}</td>
                <td className="tc-num">{a.daysInQueue ?? "—"}</td>
                <td className="tc-mono-sm">{fmtDate(a.submittedDate)}</td>
                <td className="tc-mono-sm">{analystName(a.assignedAnalyst)}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

function AuditPage({ state }) {
  const { audit = [], biz_by_id = {} } = state;
  const [search, setSearch] = useState("");
  const rows = useMemo(() => audit
    .filter(e => !search || (e.type || "").includes(search.toLowerCase()) || (e.details || "").toLowerCase().includes(search.toLowerCase()))
    .sort((a, b) => (b.ts || "").localeCompare(a.ts || "")), [audit, search]);
  return (
    <div className="tc-card">
      <div className="tc-filterbar">
        <span className="tc-filter-input">
          <svg width="13" height="13" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.4"><circle cx="6" cy="6" r="4"/><path d="M9 9l3 3"/></svg>
          <input value={search} onChange={e => setSearch(e.target.value)} placeholder="Filter by type or detail…" />
        </span>
        <span className="tc-spacer" />
        <span className="tc-mono-sm tc-muted">{rows.length} of {audit.length}</span>
      </div>
      <div className="tc-card-body-flush">
        <table className="tc-tbl">
          <thead><tr><th>Time (UTC)</th><th>Event</th><th>Principal</th><th>Business</th><th>Detail</th></tr></thead>
          <tbody>
            {rows.length === 0 && <tr><td colSpan="5" className="tc-tbl-empty">no audit entries</td></tr>}
            {rows.map(e => (
              <tr key={e.id}>
                <td className="tc-mono-sm">{fmtDateTime(e.ts)}<div className="tc-id">{e.id}</div></td>
                <td className="tc-mono-sm">{e.type}</td>
                <td className="tc-mono-sm">{e.principal || "—"}</td>
                <td><BizCell id={e.bizId} businesses={biz_by_id} /></td>
                <td style={{ maxWidth: 480 }}>{e.details || "—"}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

function ReviewsPage({ state, refresh }) {
  const { reviews_due = [], reviews_pending = [], biz_by_id = {} } = state;
  const [tab, setTab] = useState("pending");
  const [reviewDrawer, setReviewDrawer] = useState(null);

  const pendingWithClient = reviews_pending.map(r => ({
    ...r,
    company_name: (() => {
      // look up company name via client_id or business_id
      const cf = Object.values(biz_by_id).find(b => b.client_id === r.client_id) || biz_by_id[r.client_id];
      return cf ? cf.name : r.client_id;
    })(),
  }));

  return (
    <div>
      <div className="tc-filterbar" style={{ marginBottom: 16 }}>
        <div className="tc-tab-group">
          <button className={`tc-tab ${tab === "pending" ? "is-active" : ""}`} onClick={() => setTab("pending")}>
            In progress {reviews_pending.length > 0 && <span className="tc-nav-item-count">{reviews_pending.length}</span>}
          </button>
          <button className={`tc-tab ${tab === "due" ? "is-active" : ""}`} onClick={() => setTab("due")}>
            Due / overdue {reviews_due.length > 0 && <span className="tc-nav-item-count">{reviews_due.length}</span>}
          </button>
        </div>
      </div>

      {tab === "pending" && (
        <div className="tc-card">
          <div className="tc-card-body-flush">
            <table className="tc-tbl">
              <thead><tr><th>Client</th><th>Review ID</th><th>Type</th><th>Opened</th><th>Assigned to</th><th>Progress</th><th></th></tr></thead>
              <tbody>
                {pendingWithClient.length === 0 && <tr><td colSpan="7" className="tc-tbl-empty">no in-progress periodic reviews</td></tr>}
                {pendingWithClient.map(r => {
                  const total = r.checklist.length;
                  const done = r.checklist.filter(i => i.checked).length;
                  return (
                    <tr key={r.review_id} style={{ cursor: "pointer" }} onClick={() => setReviewDrawer(r)}>
                      <td><strong>{r.company_name}</strong><div className="tc-id">{r.client_id}</div></td>
                      <td className="tc-mono-sm">{r.review_id}</td>
                      <td><span className="tc-badge">{r.review_type}</span></td>
                      <td className="tc-mono-sm">{fmtDateTime(r.opened_at)}</td>
                      <td>{r.assigned_to || <span className="tc-muted">unassigned</span>}</td>
                      <td>
                        <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                          <div style={{ flex: 1, height: 4, background: "var(--rule-2)", borderRadius: 2, minWidth: 80 }}>
                            <div style={{ height: "100%", width: `${total ? (done / total) * 100 : 0}%`, background: done === total ? "var(--green)" : "var(--info)", borderRadius: 2 }} />
                          </div>
                          <span className="tc-mono-sm tc-muted">{done}/{total}</span>
                        </div>
                      </td>
                      <td><button className="tc-btn tc-btn-ghost tc-btn-xs" onClick={e => { e.stopPropagation(); setReviewDrawer(r); }}>Open →</button></td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
        </div>
      )}

      {tab === "due" && (
        <div className="tc-card">
          <div className="tc-card-body-flush">
            <table className="tc-tbl">
              <thead><tr><th>Client</th><th>Risk rating</th><th>Review date</th><th>Days overdue</th></tr></thead>
              <tbody>
                {reviews_due.length === 0 && <tr><td colSpan="4" className="tc-tbl-empty">no clients currently due for review</td></tr>}
                {reviews_due.map(c => {
                  const overdue = c.next_review_date ? Math.max(0, Math.floor((Date.now() - new Date(c.next_review_date)) / 86400000)) : null;
                  return (
                    <tr key={c.client_id}>
                      <td><strong>{c.company_name}</strong><div className="tc-id">{c.client_id}</div></td>
                      <td><RiskBadge r={c.risk_rating} /></td>
                      <td className="tc-mono-sm">{fmtDate(c.next_review_date)}</td>
                      <td className="tc-mono-sm">{overdue != null ? <span style={{ color: overdue > 30 ? "var(--crit)" : "var(--warn)" }}>{overdue}d</span> : "—"}</td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
        </div>
      )}

      {reviewDrawer && (
        <ReviewDrawer
          review={reviewDrawer}
          onClose={() => { setReviewDrawer(null); refresh(); }}
          refresh={refresh}
        />
      )}
    </div>
  );
}

function ReviewDrawer({ review, onClose, refresh }) {
  const [checklist, setChecklist] = useState(() => (review.checklist || []).map(i => ({ ...i })));
  const [decision, setDecision] = useState("");
  const [notes, setNotes] = useState("");
  const [nextDate, setNextDate] = useState("");
  const [ratingAfter, setRatingAfter] = useState("");
  const [busy, setBusy] = useState(false);

  const total = checklist.length;
  const done = checklist.filter(i => i.checked).length;

  async function saveChecklist(updated) {
    setChecklist(updated);
    try {
      await apiPost(`/internal/clients/${encodeURIComponent(review.client_id)}/reviews/${encodeURIComponent(review.review_id)}/checklist`, { items: updated });
    } catch (e) {
      toast(`Checklist save failed: ${e.message}`, { error: true });
    }
  }

  function toggleItem(idx) {
    const updated = checklist.map((item, i) => i === idx ? { ...item, checked: !item.checked } : item);
    saveChecklist(updated);
  }

  async function completeReview() {
    if (!decision) { toast("Select a decision", { error: true }); return; }
    setBusy(true);
    try {
      await apiPost(`/internal/clients/${encodeURIComponent(review.client_id)}/reviews/${encodeURIComponent(review.review_id)}/complete`, {
        decision,
        notes,
        risk_rating_after: ratingAfter || null,
        next_review_date: nextDate || null,
      });
      toast("Review completed");
      onClose();
    } catch (e) {
      toast(`Complete failed: ${e.message}`, { error: true });
    } finally {
      setBusy(false);
    }
  }

  return (
    <>
      <div className="tc-scrim" onClick={onClose} />
      <aside className="tc-drawer" role="dialog" aria-modal="true">
        <header className="tc-drawer-h">
          <div>
            <span className="tc-drawer-eyebrow">Periodic review · {review.review_type}</span>
            <h2>{review.company_name || review.client_id}</h2>
            <div className="tc-drawer-meta">Review ID: {review.review_id} · opened {fmtDateTime(review.opened_at)}</div>
          </div>
          <button className="tc-drawer-close" onClick={onClose} aria-label="Close">
            <svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"><path d="M3 3l6 6M9 3l-6 6"/></svg>
          </button>
        </header>
        <div className="tc-drawer-body">
          <section className="tc-dsec">
            <header className="tc-dsec-h">
              <h3>Review checklist</h3>
              <span className="tc-dsec-meta">{done}/{total} complete</span>
            </header>
            <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
              {checklist.map((item, idx) => (
                <label key={item.id || idx} style={{ display: "flex", alignItems: "flex-start", gap: 10, cursor: "pointer", padding: "8px 0", borderBottom: idx < checklist.length - 1 ? "1px solid var(--border)" : "none" }}>
                  <input
                    type="checkbox"
                    checked={item.checked}
                    onChange={() => toggleItem(idx)}
                    style={{ marginTop: 2, flexShrink: 0 }}
                  />
                  <span style={{ fontSize: 13, lineHeight: 1.5, color: item.checked ? "var(--fg-3)" : "var(--fg-1)", textDecoration: item.checked ? "line-through" : "none" }}>
                    {item.label}
                  </span>
                </label>
              ))}
            </div>
          </section>

          <section className="tc-dsec">
            <header className="tc-dsec-h"><h3>Complete review</h3></header>
            <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
              <div>
                <label className="tc-label">Decision *</label>
                <select className="tc-select" value={decision} onChange={e => setDecision(e.target.value)}>
                  <option value="">— select —</option>
                  <option value="approved">Approved — no changes</option>
                  <option value="approved_with_conditions">Approved with conditions</option>
                  <option value="escalated">Escalated — EDD required</option>
                  <option value="suspended">Suspend account</option>
                </select>
              </div>
              <div>
                <label className="tc-label">Risk rating after review</label>
                <select className="tc-select" value={ratingAfter} onChange={e => setRatingAfter(e.target.value)}>
                  <option value="">— unchanged —</option>
                  <option value="low">Low</option>
                  <option value="medium">Medium</option>
                  <option value="high">High</option>
                  <option value="mandatory_high">Mandatory high</option>
                </select>
              </div>
              <div>
                <label className="tc-label">Next review date</label>
                <input type="date" className="tc-input" value={nextDate} onChange={e => setNextDate(e.target.value)} />
              </div>
              <div>
                <label className="tc-label">Notes</label>
                <textarea className="tc-textarea" rows={3} value={notes} onChange={e => setNotes(e.target.value)} placeholder="Decision rationale, conditions, actions taken…" />
              </div>
            </div>
          </section>
        </div>
        <footer className="tc-drawer-foot">
          <button className="tc-btn tc-btn-ghost" onClick={onClose}>Cancel</button>
          <button className="tc-btn tc-btn-primary" disabled={busy || !decision} onClick={completeReview}>
            {busy ? "Completing…" : "Complete review"}
          </button>
        </footer>
      </aside>
    </>
  );
}

// ── Drawers ────────────────────────────────────────────────────────────
function Drawer({ open, onClose, eyebrow, title, meta, children, footer }) {
  useEffect(() => {
    if (!open) return;
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    document.addEventListener("keydown", onKey);
    document.body.style.overflow = "hidden";
    return () => { document.removeEventListener("keydown", onKey); document.body.style.overflow = ""; };
  }, [open, onClose]);
  if (!open) return null;
  return (
    <>
      <div className="tc-scrim" onClick={onClose} />
      <aside className="tc-drawer" role="dialog" aria-modal="true">
        <header className="tc-drawer-h">
          <div>
            <span className="tc-drawer-eyebrow">{eyebrow}</span>
            <h2>{title}</h2>
            {meta && <div className="tc-drawer-meta">{meta}</div>}
          </div>
          <button className="tc-drawer-close" onClick={onClose} aria-label="Close">
            <svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"><path d="M3 3l6 6M9 3l-6 6"/></svg>
          </button>
        </header>
        <div className="tc-drawer-body">{children}</div>
        {footer && <footer className="tc-drawer-foot">{footer}</footer>}
      </aside>
    </>
  );
}

const DSec = ({ title, children, meta }) => (
  <section className="tc-dsec">
    <header className="tc-dsec-h"><h3>{title}</h3>{meta && <span className="tc-dsec-meta">{meta}</span>}</header>
    <div>{children}</div>
  </section>
);

function BusinessDrawer({ id, state, onClose, refresh }) {
  const b = state.biz_by_id?.[id];
  const [busy, setBusy] = useState(null);
  const [reviewerId, setReviewerId] = useState("");
  const [approveJustification, setApproveJustification] = useState("");
  const [rejectReason, setRejectReason] = useState("");
  const [docs, setDocs] = useState([]);
  const [docsLoading, setDocsLoading] = useState(false);
  const [verifications, setVerifications] = useState([]);

  useEffect(() => {
    if (!b?.client_id) { setDocs([]); setVerifications([]); return; }
    setDocsLoading(true);
    apiGet(`/internal/clients/${encodeURIComponent(b.client_id)}/documents`)
      .then(d => setDocs(d.documents || []))
      .catch(() => setDocs([]))
      .finally(() => setDocsLoading(false));
    apiGet(`/internal/clients/${encodeURIComponent(b.client_id)}/verifications`)
      .then(d => setVerifications(d.verifications || []))
      .catch(() => setVerifications([]));
  }, [id, b?.client_id]);

  if (!b) return null;
  const isOnboarding = b.status === "onboarding";
  const txForBiz = (state.transactions || []).filter(t => t.bizId === id).slice(0, 5);

  // Synthesise agent list from transactions if not provided directly
  const agents = useMemo(() => {
    const m = new Map();
    (state.transactions || []).filter(t => t.bizId === id).forEach(t => {
      if (!t.agent) return;
      if (!m.has(t.agent)) m.set(t.agent, { id: t.agent, lastTx: t.ts, status: "active" });
      else if ((t.ts || "") > m.get(t.agent).lastTx) m.get(t.agent).lastTx = t.ts;
    });
    return [...m.values()];
  }, [state.transactions, id]);

  async function toggleAgent(agentId, action) {
    setBusy(agentId);
    try {
      await apiPost(`/agents/${encodeURIComponent(agentId)}/${action}`);
      toast(`Agent ${action}d`);
      refresh();
    } catch (e) {
      toast(`${action} failed: ${e.message}`, { error: true });
    } finally {
      setBusy(null);
    }
  }

  async function manualApproveOnboarding() {
    const who = reviewerId.trim();
    const notes = approveJustification.trim();
    if (!who || !notes) return;
    setBusy("approve-onb");
    try {
      await apiPost(`/internal/onboarding/${encodeURIComponent(id)}/approve`, {
        notes,
        approved_by: who,
      });
      toast("Application approved · manual override logged");
      await refresh();
      onClose();
    } catch (e) {
      toast(`Approve failed: ${e.message}`, { error: true });
    } finally {
      setBusy(null);
    }
  }

  async function manualRejectOnboarding() {
    const who = reviewerId.trim();
    const reason = rejectReason.trim();
    if (!who || !reason) return;
    setBusy("reject-onb");
    try {
      await apiPost(`/internal/onboarding/${encodeURIComponent(id)}/reject`, {
        reason,
        rejected_by: who,
      });
      toast("Application rejected · logged");
      await refresh();
      onClose();
    } catch (e) {
      toast(`Reject failed: ${e.message}`, { error: true });
    } finally {
      setBusy(null);
    }
  }

  return (
    <Drawer
      open={true}
      onClose={onClose}
      eyebrow={isOnboarding ? `Onboarding · ${b.id}` : `Business · ${b.id}`}
      title={b.name}
      meta={<>
        <Badge kind={b.status || "active"}>{(b.status || "active").replace("_", " ")}</Badge>
        <RiskBadge r={b.riskRating} />
        <span className="tc-mono-sm tc-muted">{b.country || "—"} · {b.industry || "—"}</span>
      </>}
    >
      {isOnboarding && b.customer_update ? (
        <DSec title="Suggested customer wording" meta="may share externally — no internal codes">
          <p style={{ fontSize: 13.5, lineHeight: 1.55, color: "var(--fg-2)", margin: 0 }}>{b.customer_update}</p>
        </DSec>
      ) : null}

      {isOnboarding && (
        <DSec title="Manual compliance decision" meta="audit-logged · clears wallet gate when approved">
          <p style={{ fontSize: 12.5, color: "var(--fg-3)", lineHeight: 1.5, margin: "0 0 14px" }}>
            Use when EDD or KYB queue requires an explicit ops decision. Provide your reviewer identity and a written justification (approve) or reason (reject).
          </p>
          <div style={{ marginBottom: 12 }}>
            <div className="tc-mono-sm tc-muted" style={{ marginBottom: 6 }}>Reviewer identifier</div>
            <input
              type="text"
              style={{ width: "100%", maxWidth: 420, padding: "8px 10px", borderRadius: 6, border: "1px solid var(--rule)", background: "var(--surface)", color: "var(--fg)", fontSize: 13 }}
              value={reviewerId}
              onChange={(e) => setReviewerId(e.target.value)}
              placeholder="e.g. dc@compliance.internal"
              autoComplete="off"
            />
          </div>
          <div style={{ marginBottom: 12 }}>
            <div className="tc-mono-sm tc-muted" style={{ marginBottom: 6 }}>Approve · justification (required)</div>
            <textarea
              className="tc-textarea"
              value={approveJustification}
              onChange={(e) => setApproveJustification(e.target.value)}
              placeholder="Why are you clearing this case despite engine outcome (EDD signals resolved, docs verified, etc.)?"
              rows={4}
            />
          </div>
          <div style={{ display: "flex", gap: 8, flexWrap: "wrap", marginBottom: 16 }}>
            <button
              type="button"
              className={`tc-btn tc-btn-accent ${busy === "approve-onb" ? "is-busy" : ""}`}
              disabled={busy === "approve-onb" || !reviewerId.trim() || !approveJustification.trim()}
              onClick={manualApproveOnboarding}
            >
              Approve (manual override)
            </button>
          </div>
          <div style={{ marginBottom: 12 }}>
            <div className="tc-mono-sm tc-muted" style={{ marginBottom: 6 }}>Reject · reason (required)</div>
            <textarea
              className="tc-textarea"
              value={rejectReason}
              onChange={(e) => setRejectReason(e.target.value)}
              placeholder="Document why onboarding is being declined."
              rows={3}
            />
          </div>
          <button
            type="button"
            className={`tc-btn tc-btn-danger ${busy === "reject-onb" ? "is-busy" : ""}`}
            disabled={busy === "reject-onb" || !reviewerId.trim() || !rejectReason.trim()}
            onClick={manualRejectOnboarding}
          >
            Reject application
          </button>
        </DSec>
      )}

      <DSec title="Profile">
        <div className="tc-kv-grid">
          <span className="tc-kv-k">Legal</span><span>{b.legal || b.name}</span>
          <span className="tc-kv-k">Reg. number</span><span className="tc-mono-sm">{b.regNumber || "—"}</span>
          <span className="tc-kv-k">Jurisdiction</span><span>{b.jurisdiction || b.countryName || b.country || "—"}</span>
          <span className="tc-kv-k">Onboarded</span><span className="tc-mono-sm">{fmtDate(b.onboardedDate)}</span>
          <span className="tc-kv-k">30d volume</span><span className="tc-mono-sm">{fmtMoney(b.volume30d, b.volumeCcy)}</span>
          <span className="tc-kv-k">Open RFIs</span><span className="tc-mono-sm">{b.openRfis || 0}</span>
        </div>
        {b.profile?.summary && <p style={{ marginTop: 12, fontSize: 12.5, color: "var(--ink-2)" }}>{b.profile.summary}</p>}
      </DSec>

      <DSec title={`Agents (${agents.length})`}>
        {agents.length === 0 && <span className="tc-mono-sm tc-muted">No agents observed in recent transactions.</span>}
        {agents.map(a => (
          <div key={a.id} style={{ display: "flex", alignItems: "center", gap: 10, padding: "8px 0", borderBottom: "1px dashed var(--rule)" }}>
            <span className="tc-mono-sm" style={{ flex: 1 }}>{a.id}</span>
            <span className="tc-mono-sm tc-muted">last tx · {fmtRelative(a.lastTx)}</span>
            <button className={`tc-btn tc-btn-sm tc-btn-danger ${busy === a.id ? "is-busy" : ""}`} disabled={busy === a.id} onClick={() => toggleAgent(a.id, "suspend")}>Suspend</button>
            <button className={`tc-btn tc-btn-sm ${busy === a.id ? "is-busy" : ""}`} disabled={busy === a.id} onClick={() => toggleAgent(a.id, "activate")}>Activate</button>
          </div>
        ))}
      </DSec>

      <DSec title="Recent transactions" meta={`${txForBiz.length} of latest`}>
        {txForBiz.length === 0 && <span className="tc-mono-sm tc-muted">none</span>}
        {txForBiz.map(t => (
          <div key={t.id} style={{ display: "grid", gridTemplateColumns: "1fr auto auto", gap: 10, padding: "6px 0", borderBottom: "1px dashed var(--rule)", alignItems: "center" }}>
            <div>
              <div style={{ fontSize: 12.5 }}>{t.counterparty || "—"}</div>
              <div className="tc-id">{fmtDateTime(t.ts)} · {t.op}</div>
            </div>
            <div className="tc-mono" style={{ fontSize: 12.5 }}>{fmtMoney(t.amount, t.ccy)}</div>
            <TxStatusBadge s={t.status} />
          </div>
        ))}
      </DSec>

      {verifications.length > 0 && (
        <DSec title="Director identity verification">
          {verifications.map(v => {
            const statusColor = v.status === "completed" ? "var(--ok)" : v.status === "failed" ? "var(--crit)" : v.status === "expired" ? "var(--ink-3)" : "var(--warn)";
            const statusIcon = v.status === "completed" ? "✓" : v.status === "failed" ? "✗" : v.status === "expired" ? "—" : "○";
            return (
              <div key={v.request_id} style={{ padding: "10px 0", borderBottom: "1px dashed var(--rule)", display: "flex", alignItems: "flex-start", gap: 12 }}>
                {v.selfie_url && (
                  <a href={`${API_BASE}${v.selfie_url}`} target="_blank" rel="noreferrer" style={{ flexShrink: 0 }}>
                    <img src={`${API_BASE}${v.selfie_url}`} alt="Selfie" style={{ width: 48, height: 48, borderRadius: 6, objectFit: "cover", border: "1px solid var(--rule-2)" }} />
                  </a>
                )}
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                    <span style={{ fontSize: 13, fontWeight: 500, color: "var(--fg)" }}>{v.director_name}</span>
                    <span style={{ fontSize: 11, fontWeight: 600, color: statusColor, fontFamily: "monospace", textTransform: "uppercase", letterSpacing: "0.05em" }}>{statusIcon} {v.status}</span>
                    {v.match_score != null && (
                      <span style={{ fontSize: 11, color: "var(--ink-3)", fontFamily: "monospace" }}>score {(v.match_score * 100).toFixed(0)}%</span>
                    )}
                  </div>
                  {v.status === "pending" && v.verification_url && (
                    <div style={{ marginTop: 4, display: "flex", alignItems: "center", gap: 8 }}>
                      <span className="tc-mono-sm tc-muted" style={{ wordBreak: "break-all", fontSize: 11 }}>{v.verification_url}</span>
                      <button className="tc-btn-xs" onClick={() => { navigator.clipboard.writeText(v.verification_url); toast("Link copied"); }}>Copy</button>
                    </div>
                  )}
                  {v.completed_at && (
                    <div className="tc-id" style={{ marginTop: 2 }}>{fmtDateTime(v.completed_at)}</div>
                  )}
                </div>
              </div>
            );
          })}
        </DSec>
      )}

      <DSec title={`Documents (${docs.length})`} meta={b.client_id ? `client · ${b.client_id}` : "no client file"}>
        {docsLoading && <span className="tc-mono-sm tc-muted">Loading…</span>}
        {!docsLoading && !b.client_id && <span className="tc-mono-sm tc-muted">No client file linked to this business.</span>}
        {!docsLoading && b.client_id && docs.length === 0 && <span className="tc-mono-sm tc-muted">No documents on file.</span>}
        {docs.map(d => (
          <div key={d.doc_id} style={{ padding: "8px 0", borderBottom: "1px dashed var(--rule)" }}>
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 12 }}>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 13, fontWeight: 450, color: "var(--fg)" }}>{d.doc_type || "Document"}</div>
                <div className="tc-id" style={{ marginTop: 2 }}>
                  {d.file_name}
                  {d.uploaded_by ? ` · uploaded by ${d.uploaded_by}` : ""}
                  {" · "}{fmtDate(d.uploaded_at)}
                  {d.file_size ? ` · ${d.file_size < 1048576 ? (d.file_size/1024).toFixed(1)+"KB" : (d.file_size/1048576).toFixed(1)+"MB"}` : ""}
                </div>
                {d.extracted_data && Object.keys(d.extracted_data).length > 0 && (
                  <div style={{ marginTop: 6, display: "flex", flexWrap: "wrap", gap: "2px 14px" }}>
                    {Object.entries(d.extracted_data).slice(0, 6).map(([k, v]) => (
                      <span key={k} style={{ fontSize: 11, color: "var(--ink-3)" }}>
                        <span style={{ color: "var(--ink-4)", fontFamily: "monospace", textTransform: "uppercase", letterSpacing: "0.04em", fontSize: 10 }}>{k.replace(/_/g, " ")}</span>
                        {" "}{String(v).slice(0, 60)}
                      </span>
                    ))}
                  </div>
                )}
              </div>
              <div style={{ display: "flex", gap: 6, flexShrink: 0 }}>
                <span className="tc-mono-sm" style={{ color: "var(--ink-4)", textTransform: "uppercase", fontSize: 10.5, letterSpacing: "0.04em", alignSelf: "center" }}>{d.category}</span>
                {d.has_file && (
                  <a
                    href={`${API_BASE}/documents/${encodeURIComponent(d.doc_id)}/download`}
                    target="_blank"
                    rel="noreferrer"
                    className="tc-btn tc-btn-sm"
                    style={{ fontSize: 12 }}
                  >
                    Download
                  </a>
                )}
              </div>
            </div>
          </div>
        ))}
      </DSec>
    </Drawer>
  );
}

function RfiDrawer({ id, state, onClose, refresh }) {
  const r = (state.rfis || []).find(x => x.id === id);
  const [response, setResponse] = useState("");
  const [escReason, setEscReason] = useState("");
  const [busy, setBusy] = useState(null);
  if (!r) return null;

  async function act(action, body) {
    setBusy(action);
    try {
      await apiPost(`/rfis/${encodeURIComponent(r.id)}/${action}`, body);
      toast(`RFI ${action}`);
      setResponse(""); setEscReason("");
      refresh();
      onClose();
    } catch (e) {
      toast(`${action} failed: ${e.message}`, { error: true });
    } finally {
      setBusy(null);
    }
  }

  return (
    <Drawer
      open={true}
      onClose={onClose}
      eyebrow={`RFI · ${r.id}`}
      title={`Request for information`}
      meta={<>
        <RfiStatusBadge status={r.status} overdue={r.overdue} />
        <span className={`tc-urg is-${r.urgency || "standard"}`}>{r.urgency || "standard"}</span>
        <span className="tc-mono-sm tc-muted">due · {fmtDate(r.dueAt)}</span>
      </>}
      footer={
        r.status !== "resolved" && (
          <>
            {r.status === "responded" && (
              <button className={`tc-btn tc-btn-primary ${busy === "resolve" ? "is-busy" : ""}`} onClick={() => act("resolve")}>Resolve</button>
            )}
            <button
              className={`tc-btn tc-btn-danger ${busy === "escalate" ? "is-busy" : ""}`}
              disabled={!escReason}
              onClick={() => act("escalate", { reason: escReason })}
            >Escalate</button>
            <button
              className={`tc-btn tc-btn-primary ${busy === "respond" ? "is-busy" : ""}`}
              disabled={!response}
              onClick={() => act("respond", { response })}
            >Submit response</button>
          </>
        )
      }
    >
      <DSec title="Business">
        <BizCell id={r.bizId} businesses={state.biz_by_id || {}} />
        <div style={{ marginTop: 8 }} className="tc-mono-sm tc-muted">
          Created {fmtDateTime(r.createdAt)} · {r.respondedAt ? `responded ${fmtRelative(r.respondedAt)}` : "no response yet"}
        </div>
      </DSec>

      <DSec title={`Questions (${(r.questions || []).length})`}>
        {(r.questions || []).map((q, i) => (
          <div key={i} className="tc-question">
            <span className="tc-question-n">{String(i + 1).padStart(2, "0")}</span>
            <span>{q}</span>
          </div>
        ))}
      </DSec>

      {r.response && (
        <DSec title="Existing response" meta={fmtDateTime(r.response.ts)}>
          <p style={{ fontSize: 12.5, margin: 0 }}>{r.response.body}</p>
        </DSec>
      )}

      {r.status !== "resolved" && (
        <>
          <DSec title="Submit response">
            <textarea className="tc-textarea" value={response} onChange={e => setResponse(e.target.value)} placeholder="Type the response on behalf of the business…" />
          </DSec>
          <DSec title="Escalate (optional reason)">
            <textarea className="tc-textarea" value={escReason} onChange={e => setEscReason(e.target.value)} placeholder="Why is this being escalated?" />
          </DSec>
        </>
      )}
    </Drawer>
  );
}

// ── Dev toolbar ────────────────────────────────────────────────────────
function DevBar({ onReset, lastFetch }) {
  const [busy, setBusy] = useState(false);
  async function reset() {
    if (busy) return;
    if (!confirm("Reset all server data to seed fixtures?")) return;
    setBusy(true);
    try {
      await apiReset();
      toast("Data reset · refreshing");
      await onReset();
    } catch (e) {
      toast(`Reset failed: ${e.message}`, { error: true });
    } finally {
      setBusy(false);
    }
  }
  return (
    <div className="tc-devbar">
      <span className="tc-devbar-tag">DEV</span>
      <span style={{ color: "#b8b3a3" }}>API · {API_BASE.replace("https://", "")}</span>
      <button className={`tc-devbar-btn ${busy ? "is-busy" : ""}`} onClick={reset}>Reset data</button>
    </div>
  );
}

// ── App shell ──────────────────────────────────────────────────────────
function App() {
  const [page, setPage] = useState("overview");
  const [state, setState] = useState({});
  const [boot, setBoot] = useState({ phase: "loading", error: null });
  const [lastFetch, setLastFetch] = useState(null);
  const [bizDrawer, setBizDrawer] = useState(null);
  const [rfiDrawer, setRfiDrawer] = useState(null);
  const [toastMsg, setToastMsg] = useState(null);
  toastSetter = setToastMsg;

  const refresh = useCallback(async () => {
    setBoot({ phase: "loading", error: null });
    try {
      const d = await apiGet("/internal/state");
      setState(d);
      setLastFetch(new Date().toISOString());
      setBoot({ phase: "ready", error: null });
    } catch (e) {
      const msg = e.message || String(e);
      setBoot({ phase: "error", error: msg });
      toast(`Fetch failed: ${msg}`, { error: true });
    }
  }, []);

  useEffect(() => { refresh(); }, [refresh]);

  // Esc closes drawers
  useEffect(() => {
    const k = (e) => { if (e.key === "Escape") { setBizDrawer(null); setRfiDrawer(null); } };
    window.addEventListener("keydown", k);
    return () => window.removeEventListener("keydown", k);
  }, []);

  const counts = useMemo(() => {
    const tx = state.transactions || [];
    const r = state.rfis || [];
    const a = state.alerts || [];
    const apps = state.applications && state.applications.length ? state.applications : (state.businesses || []).filter(b => b.status === "onboarding");
    return {
      monitoring: tx.filter(t => t.status === "pending_approval").length,
      rfis: r.filter(x => x.status === "open" || x.status === "responded" || x.status === "escalated").length,
      alerts: a.filter(x => x.status === "new").length,
      onboarding: apps.length,
      reviews: (state.reviews_pending || []).length,
    };
  }, [state]);

  const you = (state.analysts || [])[0] || { name: "Diana Castellanos", role: "Senior compliance officer", initials: "DC" };

  function openBiz(id) { setRfiDrawer(null); setBizDrawer(id); }
  function openRfi(id) { setBizDrawer(null); setRfiDrawer(id); }

  return (
    <div className="tc-shell">
      <SideNav page={page} onNav={setPage} counts={counts} you={you} />
      <div className="tc-main">
        <TopBar page={page} lastFetch={lastFetch} onRefresh={refresh} />
        <main className="tc-content">
          {boot.phase === "loading" && <div className="tc-loading">LOADING /internal/state …</div>}
          {boot.phase === "error" && (
            <div className="tc-card" style={{ maxWidth: 520, margin: "48px auto", padding: "24px 28px" }}>
              <h3 style={{ margin: "0 0 12px", fontSize: 18 }}>Cannot load dashboard</h3>
              <p style={{ margin: "0 0 10px", fontSize: 13, color: "var(--fg-2)", lineHeight: 1.5 }}>
                <span className="tc-mono-sm">GET /internal/state</span> failed: <strong>{boot.error}</strong>
              </p>
              <ul style={{ margin: "12px 0 16px", paddingLeft: 18, fontSize: 12.5, color: "var(--fg-3)", lineHeight: 1.55 }}>
                <li><strong>404</strong> — redeploy the API so <span className="tc-mono-sm">internal_api.py</span> includes the <span className="tc-mono-sm">/internal/state</span> route.</li>
                <li><strong>401</strong> — set Railway <span className="tc-mono-sm">TENOR_INTERNAL_KEY</span> to match <span className="tc-mono-sm">RESET_KEY</span> in this page&apos;s script.</li>
              </ul>
              <button type="button" className="tc-btn tc-btn-primary" onClick={() => refresh()}>Retry</button>
            </div>
          )}
          {boot.phase === "ready" && page === "overview"   && <OverviewPage   state={state} onNav={setPage} onOpen={openBiz} />}
          {boot.phase === "ready" && page === "monitoring" && <MonitoringPage state={state} refresh={refresh} onOpenBiz={openBiz} />}
          {boot.phase === "ready" && page === "rfis"       && <RfisPage       state={state} onOpenRfi={openRfi} />}
          {boot.phase === "ready" && page === "alerts"     && <AlertsPage     state={state} onOpenRfi={openRfi} />}
          {boot.phase === "ready" && page === "businesses" && <BusinessesPage state={state} onOpenBiz={openBiz} />}
          {boot.phase === "ready" && page === "onboarding" && <OnboardingPage state={state} onOpenBiz={openBiz} />}
          {boot.phase === "ready" && page === "reviews"    && <ReviewsPage    state={state} refresh={refresh} />}
          {boot.phase === "ready" && page === "audit"      && <AuditPage      state={state} />}
        </main>
      </div>
      {bizDrawer && <BusinessDrawer id={bizDrawer} state={state} onClose={() => setBizDrawer(null)} refresh={refresh} />}
      {rfiDrawer && <RfiDrawer id={rfiDrawer} state={state} onClose={() => setRfiDrawer(null)} refresh={refresh} />}
      <DevBar onReset={refresh} lastFetch={lastFetch} />
      {toastMsg && <div className={`tc-toast ${toastMsg.error ? "is-error" : ""}`}>{toastMsg.msg}</div>}
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
