/* page-extras.jsx — role-aware pages: Vault, Tiering, Commissions, Training, Recruiting, Calls, Book.
   Each page exports a single component that branches on `role` so a single sidebar entry
   serves rep / manager / owner with the right density.

   Conventions:
     - All money in dollars in display (the underlying domain is cents in Supabase).
     - Hardcoded demo state lives in module scope; real pages read from Supabase. */

const Money = ({ v, dim }) => (
  <span className="tabular" style={{ color: dim ? "var(--text-tertiary)" : undefined, fontWeight: dim ? 400 : 500 }}>
    ${Math.abs(v).toLocaleString()}
  </span>
);

/* ─────────────────────────────────────────────────────────────────────────
   1. Compliance Vault — auditable artifact store (recordings, SOAs, consent)
   ───────────────────────────────────────────────────────────────────────── */
function PageVault({ role = "owner" }) {
  const { RECORDINGS, REPS } = AppData;
  const repById = Object.fromEntries(REPS.map(r => [r.id, r]));
  const [tab, setTab] = React.useState("artifacts");
  const [q, setQ] = React.useState("");
  const [uploadOpen, setUploadOpen] = React.useState(false);
  const [extras, setExtras] = React.useState([]);
  const [retentionEdits, setRetentionEdits] = React.useState({});

  const updateRetention = async (id, retention) => {
    setRetentionEdits(s => ({ ...s, [id]: retention }));
    try {
      await AppData.mutate.vaultRetentionUpdate(id, retention);
      window.toast && window.toast(`Retention updated to ${retention}${AppData.LIVE ? "" : " (demo)"}`, "success");
    } catch (_e) {}
  };

  // Synthesized SOAs + consent receipts to back the page
  const SEED_ARTIFACTS = [
    { id: "soa-1", kind: "SOA",        lead: "Cheryl Hampton", repId: "marc", date: "Today, 11:14a", retention: "10y",  status: "scheduled" },
    { id: "soa-2", kind: "SOA",        lead: "Robert Mendez",  repId: "dani", date: "Today, 9:02a",  retention: "10y",  status: "captured"  },
    { id: "lid-1", kind: "LeadiD",     lead: "Cheryl Hampton", repId: "marc", date: "Today, 11:01a", retention: "13mo", status: "captured"  },
    { id: "tf-1",  kind: "TrustedForm",lead: "Robert Mendez",  repId: "dani", date: "Today, 8:48a",  retention: "13mo", status: "captured"  },
    { id: "rec-1", kind: "Recording",  lead: "Cheryl Hampton", repId: "marc", date: "Today, 11:14a", retention: "10y",  status: "complete"  },
    { id: "rec-2", kind: "Recording",  lead: "Robert Mendez",  repId: "dani", date: "Today, 9:02a",  retention: "10y",  status: "complete"  },
    { id: "con-1", kind: "Consent",    lead: "Linda Cho",      repId: "marc", date: "Yesterday",     retention: "13mo", status: "captured"  },
    { id: "tpmo-1",kind: "TPMO disc.", lead: "Cheryl Hampton", repId: "marc", date: "Today, 11:14a", retention: "10y",  status: "captured"  },
  ];

  const ARTIFACTS = [...extras, ...SEED_ARTIFACTS].map(a => retentionEdits[a.id] ? { ...a, retention: retentionEdits[a.id] } : a);
  const filtered = ARTIFACTS.filter(a => !q || (a.lead + " " + a.kind).toLowerCase().includes(q.toLowerCase()));

  const submitUpload = async (form) => {
    const newRow = {
      id: "vault-" + Date.now(),
      kind: form.kind, lead_name: form.lead, lead: form.lead, repId: form.repId, rep_id: form.repId,
      date: "Just added", retention: form.retention, status: "captured",
    };
    try {
      await AppData.mutate.vaultArtifactInsert({
        kind: form.kind, lead_name: form.lead, rep_id: form.repId,
        retention: form.retention, status: "captured"
      });
    } catch (_e) {}
    setExtras(es => [newRow, ...es]);
    setUploadOpen(false);
    window.toast && window.toast(`Vault entry added · ${form.kind} for ${form.lead}`, "success");
  };

  return (
    <div className="page-pad">
      <div className="page-h">
        <div>
          <div className="page-title">Compliance Vault</div>
          <div className="page-sub">Auditor-ready · recordings, SOAs, consent · retention timer per artifact</div>
        </div>
        <div style={{ marginLeft: "auto", display: "flex", gap: 8 }}>
          <input className="text-input" style={{ width: 220 }} placeholder="Search lead or kind..." value={q} onChange={(e) => setQ(e.target.value)}/>
          <button className="btn btn-primary" onClick={() => setUploadOpen(true)}><Icons.Plus size={13}/> Upload artifact</button>
          <button className="btn" onClick={() => {
            const html = `
              <h1>Compliance Audit Pack</h1>
              <div class="meta">Atlas Insurance Group · Generated ${new Date().toLocaleDateString()}</div>
              <p><strong>14,820 artifacts retained</strong> across the trailing 13 months. SOA capture rate 98.2%, TPMO compliance 100%, zero violations.</p>
              <table>
                <thead><tr><th>Kind</th><th>Lead</th><th>Producer</th><th>Captured</th><th>Status</th><th>Retention</th></tr></thead>
                <tbody>
                ${ARTIFACTS.map(a => { const rep = repById[a.repId]; return `<tr><td>${a.kind}</td><td>${a.lead}</td><td>${rep?.name || ""}</td><td>${a.date}</td><td>${a.status}</td><td>${a.retention}</td></tr>`; }).join("")}
                </tbody>
              </table>`;
            window.exportPDF && window.exportPDF("Compliance Audit Pack", html);
          }}><Icons.ArrowUpRight size={13}/> Export audit pack</button>
        </div>
      </div>

      <div className="kpi-row">
        <Shared.KpiCard label="Artifacts retained" value="14,820" sub="last 13 months"/>
        <Shared.KpiCard label="SOA capture" value="98.2%" sub="goal 95%" trend="up"/>
        <Shared.KpiCard label="TPMO compliance" value="100%" sub="zero violations" trend="up"/>
        <Shared.KpiCard label="Open chargebacks" value="2" sub="docs in review"/>
      </div>

      <div className="panel">
        <div className="panel-h">
          <h3>Artifacts</h3>
          <div style={{ marginLeft: "auto", display: "flex", gap: 4 }}>
            {["artifacts", "policies", "scrub"].map(t => (
              <button key={t} onClick={() => setTab(t)} className="btn btn-ghost" style={{ padding: "3px 10px", background: tab === t ? "var(--bg-raised)" : "transparent", color: tab === t ? "var(--text-primary)" : "var(--text-tertiary)" }}>
                {t === "artifacts" ? "Artifacts" : t === "policies" ? "Retention policy" : "Auto-scrub policy"}
              </button>
            ))}
          </div>
        </div>
        {tab === "artifacts" && (
          <div className="list">
            <div className="list-h" style={{ gridTemplateColumns: "120px 1fr 1fr 1fr 100px 100px 30px" }}>
              <div>Kind</div><div>Lead</div><div>Producer</div><div>Captured</div><div>Status</div><div>Retention</div><div></div>
            </div>
            {filtered.map(a => (
              <div key={a.id} className="row" style={{ gridTemplateColumns: "120px 1fr 1fr 1fr 100px 100px 30px" }}>
                <div><span className="chip">{a.kind}</span></div>
                <div className="cell-truncate" style={{ fontWeight: 500 }}>{a.lead}</div>
                <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
                  <Shared.Avatar rep={repById[a.repId]} size={18}/>
                  <span style={{ fontSize: 11.5, color: "var(--text-tertiary)" }}>{repById[a.repId]?.name.split(" ")[0]}</span>
                </div>
                <div style={{ color: "var(--text-tertiary)", fontSize: 11.5 }}>{a.date}</div>
                <div><span className={`chip ${a.status === "captured" || a.status === "complete" ? "chip-money" : "chip-status"}`}>{a.status}</span></div>
                <div>
                  <Shared.Select value={a.retention} onChange={(v) => updateRetention(a.id, v)} options={[{ v: "13mo", l: "13mo" }, { v: "10y", l: "10y" }, { v: "indef", l: "indefinite" }]}/>
                </div>
                <button className="icon-btn"><Icons.ArrowUpRight size={12}/></button>
              </div>
            ))}
          </div>
        )}
        {tab === "policies" && (
          <div style={{ padding: 14, display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
            {[
              { k: "Recording (sales)", v: "10 years post-issue", d: "CMS / state insurance dept aligned" },
              { k: "SOA",                v: "10 years",            d: "Captured via TwentyHours, vault on issue" },
              { k: "TPMO disclaimer",    v: "10 years",            d: "Auto-clipped from recording, indexed" },
              { k: "LeadiD",             v: "13 months",            d: "Jornaya certificate per inbound form" },
              { k: "TrustedForm",        v: "13 months",            d: "Certificate per outbound or web form" },
              { k: "Consent receipt",    v: "13 months",            d: "Express consent for telemarketing under TCPA" },
            ].map((p, i) => (
              <div key={i} className="panel" style={{ padding: 12 }}>
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
                  <strong style={{ fontSize: 13 }}>{p.k}</strong>
                  <span className="chip">{p.v}</span>
                </div>
                <div style={{ color: "var(--text-tertiary)", fontSize: 12, marginTop: 6 }}>{p.d}</div>
              </div>
            ))}
          </div>
        )}
        {tab === "scrub" && (
          <div style={{ padding: 14, display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
            {[
              { k: "DNC scrub",         v: "Every dial",  d: "State + federal DNC + Atlas internal opt-out checked before route" },
              { k: "License gate",      v: "Realtime",     d: "Producer cannot dial leads in states they're not licensed in" },
              { k: "Carrier appointment", v: "Realtime",   d: "Validated against the lead's state — pre-qual at the dialer" },
              { k: "TPMO disclaimer",   v: "Within 8s",    d: "Auto-fires on every Med Supp connect, captured in recording" },
              { k: "Litigator screen",  v: "Pre-dial",     d: "Known TCPA litigator history blocks the dial" },
              { k: "Audit log",         v: "All scrubs",   d: "Result + timestamp + producer ID logged for trailing audit" },
            ].map((p, i) => (
              <div key={i} className="panel" style={{ padding: 12 }}>
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
                  <strong style={{ fontSize: 13 }}>{p.k}</strong>
                  <span className="chip chip-money">{p.v}</span>
                </div>
                <div style={{ color: "var(--text-tertiary)", fontSize: 12, marginTop: 6 }}>{p.d}</div>
              </div>
            ))}
            <div style={{ gridColumn: "span 2", padding: 12, background: "var(--bg-raised)", borderRadius: 6, fontSize: 12, color: "var(--text-secondary)", display: "flex", alignItems: "center", gap: 10 }}>
              <Icons.Shield size={14} style={{ color: "var(--accent-money)" }}/>
              <div style={{ flex: 1 }}>Need to test a specific number, age, or zip? The interactive scrub tool lives in <strong style={{ color: "var(--text-primary)" }}>Resources</strong>.</div>
              <button className="btn btn-ghost" onClick={() => window.dispatchEvent(new CustomEvent("nav:goto", { detail: { page: "resources" }}))}>
                <Icons.ArrowUpRight size={11}/> Open scrubber
              </button>
            </div>
          </div>
        )}
      </div>

      {uploadOpen && <VaultUploadModal onClose={() => setUploadOpen(false)} onSubmit={submitUpload}/>}
    </div>
  );
}

function VaultUploadModal({ onClose, onSubmit }) {
  const [form, setForm] = React.useState({ kind: "SOA", lead: "", repId: AppData.REPS[0]?.id, retention: "10y" });
  const valid = form.lead.trim().length > 0;
  return (
    <Shared.Modal title="Upload artifact" width={460} onClose={onClose} actions={
      <>
        <button className="btn btn-ghost" onClick={onClose}>Cancel</button>
        <button className="btn btn-primary" onClick={() => valid && onSubmit(form)} disabled={!valid}><Icons.Plus size={11}/> Upload</button>
      </>
    }>
      <Shared.Field label="Kind">
        <Shared.Select value={form.kind} onChange={(v) => setForm({ ...form, kind: v })} options={["SOA","Recording","LeadiD","TrustedForm","Consent","TPMO disc.","App","Other"].map(k => ({ v: k, l: k }))}/>
      </Shared.Field>
      <Shared.Field label="Lead name">
        <input className="text-input" value={form.lead} onChange={(e) => setForm({ ...form, lead: e.target.value })} placeholder="Cheryl Hampton" autoFocus/>
      </Shared.Field>
      <Shared.Field label="Producer">
        <Shared.Select value={form.repId} onChange={(v) => setForm({ ...form, repId: v })} options={AppData.REPS.map(r => ({ v: r.id, l: r.name }))}/>
      </Shared.Field>
      <Shared.Field label="Retention">
        <Shared.Select value={form.retention} onChange={(v) => setForm({ ...form, retention: v })} options={[{ v: "13mo", l: "13mo" }, { v: "10y", l: "10y" }, { v: "indef", l: "indefinite" }]}/>
      </Shared.Field>
      <div style={{ padding: 10, border: "1px dashed var(--border-strong)", borderRadius: 6, color: "var(--text-tertiary)", fontSize: 11.5, textAlign: "center" }}>
        File-drop placeholder · binary upload would route to Supabase Storage in the multi-tenant build
      </div>
    </Shared.Modal>
  );
}

/* ─────────────────────────────────────────────────────────────────────────
   2. Tiering Console — owner power: who decides who's Diamond?
   ───────────────────────────────────────────────────────────────────────── */
function PageTiering() {
  const { REPS } = AppData;
  const TIER_ORDER = ["bronze","silver","gold","platinum","diamond"];

  // Initial rules — editable
  const [rules, setRules] = React.useState({
    bronze:   { mtd: 0,     persistency: 0  },
    silver:   { mtd: 15000, persistency: 70 },
    gold:     { mtd: 25000, persistency: 80 },
    platinum: { mtd: 35000, persistency: 85 },
    diamond:  { mtd: 50000, persistency: 90 },
  });
  // Per-rep overrides
  const [overrides, setOverrides] = React.useState({});
  const [history, setHistory] = React.useState([
    { who: "Tony Park",   from: "gold",     to: "platinum", reason: "Lost a lead to no fault — protect tier",    when: "Apr 28" },
    { who: "Remy Chen",   from: "silver",   to: "bronze",   reason: "Persistency drift, 6-mo cohort",            when: "Apr 21" },
  ]);

  const persFor = (rep) => 88 + (rep.streak % 7); // synthesized

  const calcTier = (rep) => {
    const p = persFor(rep);
    let t = "bronze";
    for (const k of TIER_ORDER) {
      if (rep.mtd >= rules[k].mtd && p >= rules[k].persistency) t = k;
    }
    return t;
  };

  const setOverride = async (id, t) => {
    const rep = REPS.find(r => r.id === id);
    const auto = calcTier(rep);
    if (t === auto) {
      const n = { ...overrides }; delete n[id]; setOverrides(n);
    } else {
      setOverrides({ ...overrides, [id]: t });
      setHistory([{ who: rep.name, from: rep.tier, to: t, reason: "Manual override", when: "now" }, ...history]);
    }
    try { await AppData.mutate.tieringOverride(id, t); window.toast && window.toast(`${rep.name} → ${t.toUpperCase()}${AppData.LIVE ? " · saved" : ""}`, "success"); }
    catch (_e) {}
  };

  return (
    <div className="page-pad">
      <div className="page-h">
        <div>
          <div className="page-title">Tiering Console</div>
          <div className="page-sub">Define tier rules. Override per-rep when judgment beats numbers. Audit log included.</div>
        </div>
      </div>

      <div className="tiering-grid" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
        <div className="panel">
          <div className="panel-h"><Icons.Award size={13}/><h3>Tier rules</h3><span className="meta">all conditions AND</span></div>
          <div style={{ padding: 12, display: "flex", flexDirection: "column", gap: 8 }}>
            {TIER_ORDER.map(t => (
              <div key={t} style={{ display: "grid", gridTemplateColumns: "120px 1fr 1fr", gap: 10, alignItems: "center" }}>
                <div><Shared.TierChip tier={t}/></div>
                <Shared.Field label={`MTD ≥ $${rules[t].mtd.toLocaleString()}`}>
                  <input type="range" min={0} max={70000} step={1000} value={rules[t].mtd} onChange={(e) => setRules({ ...rules, [t]: { ...rules[t], mtd: +e.target.value } })}/>
                </Shared.Field>
                <Shared.Field label={`Persistency ≥ ${rules[t].persistency}%`}>
                  <input type="range" min={0} max={100} value={rules[t].persistency} onChange={(e) => setRules({ ...rules, [t]: { ...rules[t], persistency: +e.target.value } })}/>
                </Shared.Field>
              </div>
            ))}
          </div>
        </div>

        <div className="panel">
          <div className="panel-h"><Icons.Users size={13}/><h3>Per-rep tier · auto vs override</h3></div>
          <div className="list">
            <div className="list-h" style={{ gridTemplateColumns: "1.4fr 80px 90px 100px 1fr" }}>
              <div>Producer</div>
              <div className="tabular" style={{ textAlign: "right" }}>MTD</div>
              <div className="tabular" style={{ textAlign: "right" }}>Persist.</div>
              <div>Auto</div>
              <div>Effective</div>
            </div>
            {REPS.map(r => {
              const auto = calcTier(r);
              const eff = overrides[r.id] || auto;
              return (
                <div key={r.id} className="row" style={{ gridTemplateColumns: "1.4fr 80px 90px 100px 1fr" }}>
                  <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                    <Shared.Avatar rep={r} size={20}/>
                    <span style={{ fontWeight: 500 }}>{r.name}</span>
                  </div>
                  <div className="tabular" style={{ textAlign: "right", color: "var(--text-tertiary)" }}>${(r.mtd/1000).toFixed(1)}k</div>
                  <div className="tabular" style={{ textAlign: "right", color: "var(--text-tertiary)" }}>{persFor(r)}%</div>
                  <div><Shared.TierChip tier={auto} compact/></div>
                  <div>
                    <Shared.Select value={eff} onChange={(v) => setOverride(r.id, v)} options={TIER_ORDER.map(t => ({ v: t, l: t.toUpperCase() + (t === auto ? " (auto)" : "") }))}/>
                  </div>
                </div>
              );
            })}
          </div>
        </div>
      </div>

      <div className="panel" style={{ marginTop: 14 }}>
        <div className="panel-h"><Icons.Activity size={13}/><h3>Override audit log</h3><span className="meta">{history.length}</span></div>
        <div className="list">
          <div className="list-h" style={{ gridTemplateColumns: "1fr 100px 100px 1.6fr 100px" }}>
            <div>Producer</div><div>From</div><div>To</div><div>Reason</div><div>When</div>
          </div>
          {history.map((h, i) => (
            <div key={i} className="row" style={{ gridTemplateColumns: "1fr 100px 100px 1.6fr 100px" }}>
              <div style={{ fontWeight: 500 }}>{h.who}</div>
              <div><Shared.TierChip tier={h.from} compact/></div>
              <div><Shared.TierChip tier={h.to} compact/></div>
              <div style={{ color: "var(--text-secondary)", fontSize: 12.5 }}>{h.reason}</div>
              <div style={{ color: "var(--text-tertiary)", fontSize: 11.5 }}>{h.when}</div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

/* ─────────────────────────────────────────────────────────────────────────
   3. Commissions — rep statement / mgr team rollup / owner override pool
   ───────────────────────────────────────────────────────────────────────── */
function PageCommissions({ role = "rep" }) {
  if (role === "manager") return <CommissionsManager/>;
  if (role === "owner")   return <CommissionsOwner/>;
  return <CommissionsRep/>;
}

const STATEMENT = [
  { date: "Today",      lead: "Cheryl Hampton", carrier: "Aetna SRC",     product: "Plan G",    ap: 1840, pct: 50, expected: 920,  paid: 920,  status: "advance"  },
  { date: "Today",      lead: "Robert Mendez",  carrier: "UHC",            product: "FE $15K",   ap: 1320, pct: 50, expected: 660,  paid: 660,  status: "advance"  },
  { date: "Yesterday",  lead: "Henry Akins",    carrier: "F&G Annuities",  product: "Annuity",   ap: 4250, pct: 10, expected: 425,  paid: 0,    status: "as-earned"},
  { date: "Apr 26",     lead: "Linda Cho",      carrier: "Humana Vantage", product: "Plan N",    ap: 1490, pct: 50, expected: 745,  paid: 0,    status: "NIGO · sigs missing" },
  { date: "Apr 24",     lead: "Don Phelps",     carrier: "Aetna SRC",      product: "FE $10K",   ap: 0,    pct: 0,  expected: 0,    paid: -480, status: "Chargeback" },
  { date: "Apr 22",     lead: "Naomi Reese",    carrier: "Aetna SRC",      product: "Plan G",    ap: 1780, pct: 50, expected: 890,  paid: 890,  status: "paid"     },
  { date: "Apr 19",     lead: "Patricia Volker",carrier: "UHC",            product: "Plan G",    ap: 2120, pct: 50, expected: 1060, paid: 1060, status: "paid"     },
];

// ─── Account-based commission calculator ───────────────────────────────────
// Single source of truth: each row in policies carries comp_rate_pct +
// expected_commission (set by deal-write). PAID amounts come from the
// commissions ledger (advances / earned / trails). This makes the rep,
// manager, and owner views all derive from the same data — change a comp%
// at deal entry and every downstream number moves.
function buildStatement({ repId } = {}) {
  const policies = AppData.POLICIES || [];
  const commissions = AppData.COMMISSIONS || [];
  const pipeline = AppData.PIPELINE || [];
  const carriers = AppData.CARRIERS || [];
  const clawbacks = AppData.CLAWBACKS || [];
  const carrierById = new Map(carriers.map(c => [c.id, c]));
  const leadById   = new Map(pipeline.map(l => [l.id, l]));

  const fmtDate = (iso) => {
    if (!iso) return "—";
    const d = new Date(iso); if (isNaN(d)) return iso;
    const today = new Date(); today.setHours(0,0,0,0);
    const day = new Date(d); day.setHours(0,0,0,0);
    const diff = Math.round((today - day) / 86400000);
    if (diff === 0) return "Today";
    if (diff === 1) return "Yesterday";
    if (diff < 14)  return `${diff}d ago`;
    return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
  };

  const rows = policies
    .filter(p => !repId || p.owner === repId)
    .map(p => {
      // Sum any paid commissions tied to this policy
      const paidForPolicy = commissions
        .filter(c => c.policyId === p.id)
        .reduce((a, c) => a + (c.amount || 0), 0);
      // expected: prefer stored expected, else AP × comp%
      const base = p.targetPremium || p.ap || 0;
      const pct  = p.compRatePct != null ? p.compRatePct : 0;
      const expected = p.expectedCommission != null ? p.expectedCommission : Math.round(base * pct / 100);
      const lead     = p.leadId ? leadById.get(p.leadId) : null;
      const carrier  = carrierById.get(p.carrierId);
      // Status mapping
      const status = p.status === "issued" || p.status === "active" ? (paidForPolicy > 0 ? "paid" : "pending payout")
                    : p.status === "submitted" || p.status === "app_in" ? "submitted"
                    : p.status === "declined" || p.status === "withdrawn" ? p.status
                    : p.status || "—";
      return {
        policyId: p.id,
        date: fmtDate(p.submissionDate || p.issuedAt),
        lead: lead?.lead || (p.policyNumber ? `Policy ${p.policyNumber}` : "—"),
        carrier: carrier?.name || p.carrierId || "—",
        product: p.product || "—",
        ap: p.ap || 0,
        pct,
        expected,
        paid: paidForPolicy,
        status,
      };
    });

  // Append chargebacks (negative paid)
  clawbacks
    .filter(cb => !repId || cb.repId === repId)
    .forEach(cb => rows.push({
      policyId: cb.policyId,
      date: fmtDate(cb.recordedAt),
      lead: "(chargeback)",
      carrier: "—", product: "—", ap: 0, pct: 0,
      expected: 0, paid: -(cb.amount || 0), status: "Chargeback",
    }));

  return rows;
}

function CommissionsRep() {
  // Always recompute from policies + commissions ledger so any deal entered
  // anywhere by this rep flows through immediately.
  const repId = AppData.REPS && AppData.REPS[0] && AppData.REPS[0].id;
  const liveRows = buildStatement({ repId });
  const ROWS = liveRows && liveRows.length ? liveRows : STATEMENT;
  const total = ROWS.reduce((a, r) => a + r.expected, 0);
  const paid  = ROWS.reduce((a, r) => a + r.paid, 0);
  const inClearing = total - Math.max(0, paid);
  const charge = ROWS.filter(r => r.paid < 0).reduce((a, r) => a + r.paid, 0);
  return (
    <div className="page-pad">
      <div className="page-h">
        <div>
          <div className="page-title">Commissions · Me</div>
          <div className="page-sub">Statement · advances vs as-earned · NIGO and chargeback alerts</div>
        </div>
        <button className="btn" style={{ marginLeft: "auto" }} onClick={() => {
          const html = `
            <h1>Statement · April</h1>
            <div class="meta">Marcus Avila · Atlas Insurance Group · ${new Date().toLocaleDateString()}</div>
            <table>
              <thead><tr><th>Date</th><th>Lead</th><th>Carrier</th><th>Product</th><th style="text-align:right">AP</th><th style="text-align:right">Comp %</th><th style="text-align:right">Expected</th><th style="text-align:right">Paid</th><th>Status</th></tr></thead>
              <tbody>
              ${ROWS.map(r => `<tr><td>${r.date}</td><td>${r.lead}</td><td>${r.carrier}</td><td>${r.product}</td><td style="text-align:right">$${(r.ap || 0).toLocaleString()}</td><td style="text-align:right">${r.pct}%</td><td style="text-align:right">$${r.expected.toLocaleString()}</td><td style="text-align:right">$${r.paid.toLocaleString()}</td><td>${r.status}</td></tr>`).join("")}
              </tbody>
            </table>`;
          window.exportPDF && window.exportPDF("Statement · April", html);
        }}><Icons.ArrowUpRight size={13}/> Statement PDF</button>
        <button className="btn" style={{ marginLeft: 8 }} onClick={() => window.AppData.exportCsv(ROWS, "commissions-statement",
          [
            { k: "date",     l: "Date" },
            { k: "lead",     l: "Lead" },
            { k: "carrier",  l: "Carrier" },
            { k: "product",  l: "Product" },
            { k: "ap",       l: "AP",       fmt: (v) => v || 0 },
            { k: "pct",      l: "Comp %" },
            { k: "expected", l: "Expected", fmt: (v) => v || 0 },
            { k: "paid",     l: "Paid",     fmt: (v) => v || 0 },
            { k: "status",   l: "Status" },
          ])}><Icons.ArrowDown size={13}/> Export CSV</button>
      </div>

      <div className="kpi-row">
        <Shared.KpiCard hero label="Expected MTD" prefix="$" value={total.toLocaleString()} sub="across 7 issues" trend="up"/>
        <Shared.KpiCard label="Paid MTD" prefix="$" value={Math.max(0, paid).toLocaleString()} sub="advances + as-earned"/>
        <Shared.KpiCard label="In clearing" prefix="$" value={inClearing.toLocaleString()} sub="2 NIGO"/>
        <Shared.KpiCard label="Chargebacks" prefix="$" value={Math.abs(charge).toLocaleString()} sub="last 30d" neg/>
      </div>

      <div className="panel">
        <div className="panel-h"><Icons.Wallet size={13}/><h3>Statement</h3><span className="meta">{ROWS.length} rows · this month</span></div>
        <div className="list">
          <div className="list-h" style={{ gridTemplateColumns: "100px 1.4fr 1fr 1fr 80px 60px 90px 90px 1fr" }}>
            <div>Date</div><div>Lead</div><div>Carrier</div><div>Product</div>
            <div className="tabular" style={{ textAlign: "right" }}>AP</div>
            <div className="tabular" style={{ textAlign: "right" }}>%</div>
            <div className="tabular" style={{ textAlign: "right" }}>Expected</div>
            <div className="tabular" style={{ textAlign: "right" }}>Paid</div>
            <div>Status</div>
          </div>
          {ROWS.map((r, i) => (
            <div key={i} className="row" style={{ gridTemplateColumns: "100px 1.4fr 1fr 1fr 80px 60px 90px 90px 1fr" }}>
              <div style={{ color: "var(--text-tertiary)", fontSize: 11.5 }}>{r.date}</div>
              <div className="cell-truncate" style={{ fontWeight: 500 }}>{r.lead}</div>
              <div className="cell-truncate" style={{ color: "var(--text-tertiary)" }}>{r.carrier}</div>
              <div className="cell-truncate" style={{ color: "var(--text-tertiary)" }}>{r.product}</div>
              <div className="tabular" style={{ textAlign: "right" }}>{r.ap ? `$${r.ap.toLocaleString()}` : "—"}</div>
              <div className="tabular" style={{ textAlign: "right", color: "var(--text-tertiary)" }}>{r.pct}%</div>
              <div className="tabular" style={{ textAlign: "right" }}><Money v={r.expected}/></div>
              <div className="tabular" style={{ textAlign: "right", color: r.paid < 0 ? "var(--state-danger)" : undefined }}><Money v={r.paid}/></div>
              <div><span className={`chip ${
                r.status === "paid" || r.status === "advance" ? "chip-money" :
                r.status === "as-earned" ? "chip-info" :
                r.status.startsWith("Chargeback") ? "chip-danger" : "chip-status"
              }`}>{r.status}</span></div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

function CommissionsManager() {
  const { REPS } = AppData;
  // Aggregate buildStatement per rep — same comp% input flows up
  const perRep = REPS.map(r => {
    const rows = buildStatement({ repId: r.id });
    const issued = rows.filter(x => x.status === "paid" || x.status === "pending payout").length;
    const ap     = rows.reduce((a, x) => a + (x.ap || 0), 0);
    const expected = rows.reduce((a, x) => a + (x.expected || 0), 0);
    const paid    = rows.reduce((a, x) => a + Math.max(0, x.paid || 0), 0);
    const charge  = rows.filter(x => (x.paid || 0) < 0).reduce((a, x) => a + x.paid, 0);
    return { rep: r, issued, ap, expected, paid, ic: Math.max(0, expected - paid), charge };
  });
  const teamAp       = perRep.reduce((a, x) => a + x.ap, 0);
  const teamExpected = perRep.reduce((a, x) => a + x.expected, 0);
  const teamPaid     = perRep.reduce((a, x) => a + x.paid, 0);
  const teamIc       = Math.max(0, teamExpected - teamPaid);
  const teamCharge   = perRep.reduce((a, x) => a + x.charge, 0);

  // Fall back to demo numbers if no policies have been written yet
  const isEmpty = teamAp === 0 && teamExpected === 0;
  const display = isEmpty
    ? { ap: 295000, expected: 184260, paid: 142080, ic: 42180, charge: -11420 }
    : { ap: teamAp, expected: teamExpected, paid: teamPaid, ic: teamIc, charge: teamCharge };

  return (
    <div className="page-pad">
      <div className="page-h">
        <div>
          <div className="page-title">Commissions · Team rollup</div>
          <div className="page-sub">Per-producer ledger · computed from rep-entered comp % at deal-write</div>
        </div>
      </div>

      <div className="kpi-row">
        <Shared.KpiCard hero label="Team expected MTD" prefix="$" value={display.expected.toLocaleString()} sub={`across ${perRep.reduce((a, x) => a + x.issued, 0) || 14} issues`} trend="up"/>
        <Shared.KpiCard label="Team paid MTD" prefix="$" value={display.paid.toLocaleString()} sub="advances + as-earned"/>
        <Shared.KpiCard label="In clearing" prefix="$" value={display.ic.toLocaleString()} sub={isEmpty ? "14 apps" : "expected − paid"}/>
        <Shared.KpiCard label="Chargebacks" prefix="$" value={Math.abs(display.charge).toLocaleString()} sub="last 30d" neg/>
      </div>

      <div className="panel">
        <div className="panel-h"><h3>Producers · this month</h3><span className="meta">click rep to drill</span></div>
        <div className="list">
          <div className="list-h" style={{ gridTemplateColumns: "1.6fr 70px 100px 110px 100px 100px" }}>
            <div>Producer</div>
            <div className="tabular" style={{ textAlign: "right" }}>Issued</div>
            <div className="tabular" style={{ textAlign: "right" }}>AP</div>
            <div className="tabular" style={{ textAlign: "right" }}>Expected</div>
            <div className="tabular" style={{ textAlign: "right" }}>Paid</div>
            <div className="tabular" style={{ textAlign: "right" }}>In-clearing</div>
          </div>
          {perRep.map(({ rep, issued, ap, expected, paid, ic }) => {
            // Synthesize numbers when no real policies yet so the page isn't empty
            const fakeAp = rep.mtd;
            const fakePaid = Math.round(rep.mtd * 0.62);
            const showAp = isEmpty ? fakeAp : ap;
            const showExpected = isEmpty ? Math.round(rep.mtd * 0.5) : expected;
            const showPaid = isEmpty ? fakePaid : paid;
            const showIc = isEmpty ? Math.max(0, showExpected - showPaid) : ic;
            const showIssued = isEmpty ? Math.round(rep.mtd / 1800) : issued;
            return (
              <div key={rep.id} className="row" style={{ gridTemplateColumns: "1.6fr 70px 100px 110px 100px 100px" }}>
                <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                  <Shared.Avatar rep={rep} size={20}/>
                  <span style={{ fontWeight: 500 }}>{rep.name}</span>
                  <Shared.TierChip tier={rep.tier} compact/>
                </div>
                <div className="tabular" style={{ textAlign: "right" }}>{showIssued}</div>
                <div className="tabular" style={{ textAlign: "right" }}>${showAp.toLocaleString()}</div>
                <div className="tabular" style={{ textAlign: "right", fontWeight: 500 }}>${showExpected.toLocaleString()}</div>
                <div className="tabular" style={{ textAlign: "right", color: "var(--accent-money)" }}>${showPaid.toLocaleString()}</div>
                <div className="tabular" style={{ textAlign: "right", color: "var(--text-tertiary)" }}>${showIc.toLocaleString()}</div>
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
}

function CommissionsOwner() {
  // Account-wide pool: union of every rep's deals → producer commissions →
  // implied override slice. Owner sets the override % below; everything moves.
  const { REPS } = AppData;
  const [overridePct, setOverridePct] = React.useState(20);  // owner's slice on top of producer comp
  const allRows = buildStatement();   // all reps
  const issued = allRows.filter(r => r.status === "paid" || r.status === "pending payout").length;
  const totalAp       = allRows.reduce((a, r) => a + (r.ap || 0), 0);
  const totalExpected = allRows.reduce((a, r) => a + (r.expected || 0), 0);
  const totalPaid     = allRows.reduce((a, r) => a + Math.max(0, r.paid || 0), 0);
  const overridePool  = Math.round(totalAp * overridePct / 100);
  const isEmpty = totalAp === 0;

  // Region split — rough: first 5 reps = Atlanta, rest = Tampa
  const regionRows = ["Atlanta region", "Tampa region"].map((name, i) => {
    const reps = REPS.slice(i === 0 ? 0 : 5, i === 0 ? 5 : undefined);
    const ids = new Set(reps.map(r => r.id));
    const rows = allRows.filter(r => {
      const pol = (AppData.POLICIES || []).find(p => p.id === r.policyId);
      return pol && ids.has(pol.owner);
    });
    const ap = rows.reduce((a, r) => a + (r.ap || 0), 0);
    const ovr = Math.round(ap * overridePct / 100);
    return { name, reps: reps.length, ap, ovr };
  });

  // Fallback display when no real deals
  const display = isEmpty
    ? { pool: 258420, net: 104700, paidOut: 412300, totalAp: 731000 }
    : { pool: overridePool, net: Math.round(overridePool * 0.4), paidOut: totalPaid, totalAp };

  return (
    <div className="page-pad">
      <div className="page-h">
        <div>
          <div className="page-title">Commissions · Override pool</div>
          <div className="page-sub">Account-wide rollup · {issued || 14} issues this period · override % set by you below</div>
        </div>
      </div>
      <div className="kpi-row">
        <Shared.KpiCard hero label="Override pool · MTD" prefix="$" value={display.pool.toLocaleString()} sub={`${overridePct}% of $${display.totalAp.toLocaleString()} AP`} trend="up"/>
        <Shared.KpiCard label="Net to owner" prefix="$" value={display.net.toLocaleString()} sub="after lead spend + NIGO" trend="up"/>
        <Shared.KpiCard label="Paid to producers" prefix="$" value={display.paidOut.toLocaleString()} sub={`${REPS.length} producers`}/>
        <Shared.KpiCard label="Coverage" value={`${(display.pool / 100000).toFixed(2)}x`} sub="vs $100k goal" trend={display.pool >= 100000 ? "up" : "down"}/>
      </div>

      <div className="panel" style={{ marginBottom: 14 }}>
        <div className="panel-h"><Icons.Calculator size={13}/><h3>Owner override %</h3><span className="meta">applies to all producer AP</span></div>
        <div style={{ padding: "12px 14px" }}>
          <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", marginBottom: 4 }}>
            <span style={{ fontSize: 12, color: "var(--text-tertiary)" }}>Override slice</span>
            <span className="tabular" style={{ fontSize: 14, fontWeight: 600 }}>{overridePct}%</span>
          </div>
          <input type="range" min={5} max={40} step={1} value={overridePct} onChange={(e) => setOverridePct(+e.target.value)} style={{ width: "100%" }}/>
          <div style={{ marginTop: 8, fontSize: 11.5, color: "var(--text-tertiary)" }}>
            At {overridePct}%, every $1k of producer AP returns ${(overridePct * 10).toFixed(0)} to the owner pool. Rep comp % is set per-deal at write time.
          </div>
        </div>
      </div>

      <div className="panel">
        <div className="panel-h"><h3>By region</h3></div>
        <div className="list">
          <div className="list-h" style={{ gridTemplateColumns: "1.4fr 100px 110px 110px 1fr" }}>
            <div>Region</div>
            <div className="tabular" style={{ textAlign: "right" }}>Producers</div>
            <div className="tabular" style={{ textAlign: "right" }}>Total AP</div>
            <div className="tabular" style={{ textAlign: "right" }}>Override</div>
            <div></div>
          </div>
          {regionRows.map((r, i) => {
            const showAp  = isEmpty ? [412800, 318200][i] : r.ap;
            const showOvr = isEmpty ? [92420, 71390][i]   : r.ovr;
            const max     = Math.max(...regionRows.map(x => isEmpty ? Math.max(92420, 71390) : x.ovr), 1);
            return (
              <div key={i} className="row" style={{ gridTemplateColumns: "1.4fr 100px 110px 110px 1fr" }}>
                <div style={{ fontWeight: 500 }}>{r.name}</div>
                <div className="tabular" style={{ textAlign: "right" }}>{r.reps}</div>
                <div className="tabular" style={{ textAlign: "right" }}>${showAp.toLocaleString()}</div>
                <div className="tabular" style={{ textAlign: "right", color: "var(--accent-money)" }}>${showOvr.toLocaleString()}</div>
                <div style={{ height: 5, background: "var(--bg-raised)", borderRadius: 2, marginLeft: 12, overflow: "hidden" }}>
                  <div style={{ width: `${(showOvr / max) * 100}%`, height: "100%", background: "var(--accent-money)" }}></div>
                </div>
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
}

/* ─────────────────────────────────────────────────────────────────────────
   4. Training — rep / mgr / owner
   ───────────────────────────────────────────────────────────────────────── */
/* ─── ProductTraining store ───────────────────────────────────────────────
   Persists three things to localStorage and broadcasts a "training:changed"
   event so every Training pane stays in sync after edits:
     • courses        — owner-authored library (sections + lessons)
     • progress       — per-rep, per-course completedLessons + completedAt
     • assignments    — manager assigns courseId → repIds with optional dueDate
   Status is derived (not stored) so the source of truth is always progress. */
const ProductTraining = (() => {
  const K_COURSES     = "repflow:product_training_courses";
  const K_PROGRESS    = "repflow:product_training_progress";
  const K_ASSIGNMENTS = "repflow:product_training_assignments";

  function seedCourses() {
    return (AppData.COURSES || []).map((c) => ({
      ...c,
      required: c.required ?? false,  // owners explicitly flag required after authoring lessons
      description: c.description || "",
      sections: c.sections || [],
    }));
  }
  function loadJSON(key, fallback) {
    try { const raw = localStorage.getItem(key); if (raw) return JSON.parse(raw); } catch (_e) {}
    return fallback;
  }
  function saveJSON(key, val) {
    try { localStorage.setItem(key, JSON.stringify(val)); } catch (_e) {}
  }
  function broadcast() {
    window.dispatchEvent(new CustomEvent("training:changed"));
  }
  function loadCourses()     { return loadJSON(K_COURSES, seedCourses()); }
  function loadProgress()    { return loadJSON(K_PROGRESS, {}); }
  function loadAssignments() { return loadJSON(K_ASSIGNMENTS, []); }

  function totalLessons(course) {
    return (course.sections || []).reduce((a, s) => a + (s.lessons?.length || 0), 0);
  }
  function lessonIds(course) {
    const ids = [];
    (course.sections || []).forEach((s, si) => (s.lessons || []).forEach((_, li) => ids.push(`${si}.${li}`)));
    return ids;
  }
  function getProgress(progress, repId, courseId) {
    return (progress[repId] && progress[repId][courseId]) || { completedLessons: [], completedAt: null };
  }
  function deriveStatus(course, prog, assignment) {
    // Pure derivation from lessons + assignments. Ignore the legacy
    // course.status field — seed data sets it but it conflicts with the
    // progress-driven model (e.g. status:"complete" + zero lessons).
    const total = totalLessons(course);
    const done  = prog.completedLessons.length;
    if (total > 0 && done >= total) return "complete";
    if (done > 0) return "in-progress";
    if (assignment?.dueDate) {
      const today = new Date().toISOString().slice(0, 10);
      if (assignment.dueDate < today) return "overdue";
    }
    if (assignment) return "assigned";
    if (course.required) return "due";  // required courses are implicitly assigned
    return "assigned";
  }
  function statusFor(repId, course, progress, assignments) {
    const prog = getProgress(progress, repId, course.id);
    const a    = assignments.find(x => x.courseId === course.id && (x.repIds || []).includes(repId));
    return deriveStatus(course, prog, a);
  }
  function percentFor(repId, course, progress) {
    // Progress is derived from lessons completed, period. Seed courses with
    // a legacy `status: "complete"` field but zero lessons must NOT report
    // 100% — that creates a "fully filled bar but status: due" mismatch.
    const total = totalLessons(course);
    if (total === 0) return 0;
    return Math.round((getProgress(progress, repId, course.id).completedLessons.length / total) * 100);
  }
  function isComplete(repId, course, progress) {
    return statusFor(repId, course, progress, []) === "complete";
  }

  // React hooks — every Training pane subscribes via these and re-renders on change.
  function useStore() {
    const [, force] = React.useState(0);
    React.useEffect(() => {
      const onChange = () => force(n => n + 1);
      window.addEventListener("training:changed", onChange);
      return () => window.removeEventListener("training:changed", onChange);
    }, []);
    return {
      courses: loadCourses(),
      progress: loadProgress(),
      assignments: loadAssignments(),
      saveCourses: (next) => {
        const v = typeof next === "function" ? next(loadCourses()) : next;
        saveJSON(K_COURSES, v); broadcast();
      },
      saveProgress: (next) => {
        const v = typeof next === "function" ? next(loadProgress()) : next;
        saveJSON(K_PROGRESS, v); broadcast();
      },
      saveAssignments: (next) => {
        const v = typeof next === "function" ? next(loadAssignments()) : next;
        saveJSON(K_ASSIGNMENTS, v); broadcast();
      },
    };
  }

  function markLessonComplete(repId, courseId, lessonId) {
    const all = loadProgress();
    const repProg = all[repId] || {};
    const cur = repProg[courseId] || { completedLessons: [], completedAt: null };
    if (!cur.completedLessons.includes(lessonId)) {
      cur.completedLessons = [...cur.completedLessons, lessonId];
    }
    repProg[courseId] = cur;
    all[repId] = repProg;

    // Auto-flag completedAt when all lessons done.
    const courses = loadCourses();
    const course = courses.find(c => c.id === courseId);
    if (course) {
      const total = totalLessons(course);
      if (total > 0 && cur.completedLessons.length >= total && !cur.completedAt) {
        cur.completedAt = new Date().toISOString();
        all[repId][courseId] = cur;
      }
    }
    saveJSON(K_PROGRESS, all); broadcast();
  }

  function unmarkLessonComplete(repId, courseId, lessonId) {
    const all = loadProgress();
    const cur = (all[repId] || {})[courseId];
    if (!cur) return;
    cur.completedLessons = cur.completedLessons.filter(x => x !== lessonId);
    cur.completedAt = null;
    all[repId][courseId] = cur;
    saveJSON(K_PROGRESS, all); broadcast();
  }

  // Required course = required flag OR explicit assignment. Open = not yet complete.
  function requiredCoursesFor(repId, courses, progress, assignments) {
    return courses.filter(c => {
      if (c.required) return true;
      return assignments.some(a => a.courseId === c.id && (a.repIds || []).includes(repId));
    });
  }
  function openRequiredCount(repId, courses, progress, assignments) {
    return requiredCoursesFor(repId, courses, progress, assignments)
      .filter(c => totalLessons(c) > 0)  // ignore empty courses still being authored
      .filter(c => statusFor(repId, c, progress, assignments) !== "complete")
      .length;
  }

  return {
    useStore, totalLessons, lessonIds, getProgress, statusFor, percentFor, isComplete,
    requiredCoursesFor, openRequiredCount, markLessonComplete, unmarkLessonComplete,
  };
})();

/* ─── Embed helpers — accept Loom / YouTube / Vimeo / Wistia / direct mp4 ─ */
function toEmbedSrc(url = "") {
  const u = String(url).trim();
  if (!u) return "";
  const loom = u.match(/loom\.com\/share\/([a-z0-9]+)/i);
  if (loom) return `https://www.loom.com/embed/${loom[1]}`;
  const yt = u.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/|youtube\.com\/shorts\/)([\w-]+)/);
  if (yt) return `https://www.youtube.com/embed/${yt[1]}`;
  const vm = u.match(/vimeo\.com\/(\d+)/);
  if (vm) return `https://player.vimeo.com/video/${vm[1]}`;
  const wist = u.match(/(?:wistia\.com\/(?:medias|embed)|wi\.st\/)\/?([a-z0-9]+)/i);
  if (wist) return `https://fast.wistia.net/embed/iframe/${wist[1]}`;
  return u;
}
function isDirectVideo(url = "") {
  return /\.(mp4|webm|ogg)(\?|$)/i.test(url) || url.startsWith("data:video/");
}
/* Pull a thumbnail from a YouTube URL when we can — Vimeo/Loom/Wistia thumbnails
   require an API call so we let those fall through to a placeholder. */
function thumbFromUrl(url = "") {
  const u = String(url).trim();
  const yt = u.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/|youtube\.com\/shorts\/)([\w-]+)/);
  if (yt) return `https://i.ytimg.com/vi/${yt[1]}/hqdefault.jpg`;
  return "";
}
function detectVideoSourceLabel(url = "") {
  const u = String(url).toLowerCase();
  if (/youtube\.com|youtu\.be/.test(u)) return "YouTube";
  if (/vimeo\.com/.test(u))             return "Vimeo";
  if (/loom\.com/.test(u))              return "Loom";
  if (/wistia\.com|wi\.st/.test(u))     return "Wistia";
  if (isDirectVideo(u))                  return "Direct";
  return "Embed";
}

const COURSE_TRACKS = ["Onboarding", "FE", "Med Supp", "AEP", "Life", "Annuity", "Compliance"];

/* ─────────────────────────────────────────────────────────────────────────
   4. Training — unified hub: Call Coaching · Call Library · Product Training
   The legacy /coaching route in index.html now lands here with defaultTab="coaching".
   ───────────────────────────────────────────────────────────────────────── */
function PageTraining({ role = "rep", defaultTab = "coaching" }) {
  const [tab, setTab] = React.useState(defaultTab);
  const store = ProductTraining.useStore();
  const meId = AppData.REPS[0].id;
  const requiredOpen = ProductTraining.openRequiredCount(meId, store.courses, store.progress, store.assignments);

  const tabs = [
    { k: "coaching", l: "Call Coaching",    icon: "Activity" },
    { k: "library",  l: "Call Library",     icon: "Headset" },
    { k: "product",  l: "Product Training", icon: "Book", badge: role === "rep" && requiredOpen > 0 ? requiredOpen : undefined },
  ];

  return (
    <div className="page-pad">
      <div className="page-h">
        <div>
          <div className="page-title">Training</div>
          <div className="page-sub">
            {tab === "coaching" && "Coaching cards · scorecards · drill replays"}
            {tab === "library"  && "Recorded calls · waveform · AI scoring"}
            {tab === "product"  && (role === "owner" ? "Course library · authoring · required onboarding" : "Courses · videos · scripts · cert progress")}
          </div>
        </div>
      </div>

      <div className="training-tabs section-pill">
        {tabs.map(t => {
          const Ic = Icons[t.icon];
          return (
            <button key={t.k} className={tab === t.k ? "active" : ""} onClick={() => setTab(t.k)}>
              <Ic size={12} style={{ marginRight: 6, verticalAlign: "middle" }}/>
              {t.l}
              {t.badge != null && <span className="badge tabular" style={{ marginLeft: 6, fontSize: 10 }}>{t.badge}</span>}
            </button>
          );
        })}
      </div>

      {tab === "coaching" && <CoachingPane role={role}/>}
      {tab === "library"  && <CallLibraryPane role={role}/>}
      {tab === "product"  && <ProductTrainingPane role={role} store={store} meId={meId} requiredOpen={requiredOpen}/>}
    </div>
  );
}

/* Defer to the existing PageCoaching — it already handles all three roles.
   We strip its outer page-pad since we're already inside one. */
function CoachingPane({ role }) {
  // Render the role-specific inner component (CoachingRep / CoachingManager /
  // CoachingOwner) directly. The .training-embed class hides the duplicate
  // page-h title AND the manager's inner dashboard SectionPill (which would
  // otherwise surface unrelated nav links: Floor / NIGO / Dispatch).
  const Inner = role === "manager" ? window.CoachingManager
              : role === "owner"   ? window.CoachingOwner
              : window.CoachingRep;
  const Fallback = window.PageCoaching;
  if (!Inner && !Fallback) return <div style={{ padding: 30, color: "var(--text-tertiary)" }}>Coaching module loading…</div>;
  return (
    <div className="training-embed">
      {Inner ? <Inner/> : <Fallback role={role}/>}
    </div>
  );
}

function CallLibraryPane({ role }) {
  const { RECORDINGS, REPS } = AppData;
  const meId = REPS[0].id;
  const visible = role === "rep" ? RECORDINGS.filter(r => !r.repId || r.repId === meId) : RECORDINGS;

  const [selId, setSelId] = React.useState(visible[0]?.id);
  const [q, setQ]         = React.useState("");
  const filtered = visible.filter(r => !q || r.lead.toLowerCase().includes(q.toLowerCase()));
  const sel = filtered.find(r => r.id === selId) || filtered[0];

  return (
    <div className="calls-grid" style={{ display: "grid", gridTemplateColumns: "320px 1fr", gap: 14 }}>
      <div className="panel">
        <div className="panel-h">
          <h3>Recordings</h3>
          <span className="meta">{filtered.length}</span>
          <input className="text-input" style={{ width: 140, marginLeft: "auto", fontSize: 11.5 }} placeholder="Search lead…" value={q} onChange={(e) => setQ(e.target.value)}/>
        </div>
        <div style={{ padding: 8, display: "flex", flexDirection: "column", gap: 6, maxHeight: 520, overflowY: "auto" }}>
          {filtered.map(r => (
            <button key={r.id} onClick={() => setSelId(r.id)} className="btn btn-ghost" style={{ justifyContent: "flex-start", padding: 10, background: sel?.id === r.id ? "var(--bg-overlay)" : "var(--bg-raised)", border: "1px solid var(--border-subtle)", flexDirection: "column", alignItems: "stretch", gap: 4 }}>
              <div style={{ display: "flex", justifyContent: "space-between", width: "100%" }}>
                <strong style={{ fontSize: 12.5 }}>{r.lead}</strong>
                <span className="tabular" style={{ color: r.score >= 80 ? "var(--accent-money)" : r.score >= 60 ? "var(--state-warning)" : "var(--state-danger)", fontSize: 11.5 }}>{r.score}</span>
              </div>
              <div style={{ display: "flex", justifyContent: "space-between", color: "var(--text-tertiary)", fontSize: 11 }}>
                <span>{r.date}</span>
                <span className="mono">{Math.floor(r.durSec / 60)}:{String(r.durSec % 60).padStart(2, "0")}</span>
              </div>
            </button>
          ))}
          {filtered.length === 0 && <div style={{ padding: 20, color: "var(--text-tertiary)", fontSize: 12, textAlign: "center" }}>No recordings match.</div>}
        </div>
      </div>

      {sel && (
        <div className="panel">
          <div className="panel-h">
            <Icons.Headset size={13}/>
            <h3>{sel.lead} · score {sel.score}</h3>
            <div style={{ marginLeft: "auto", display: "flex", gap: 6 }}>
              <button className="btn btn-ghost" onClick={() => window.dispatchEvent(new CustomEvent("ai:ask", { detail: { prompt: `Summarize the call with ${sel.lead} and grade my open-ended question rate`, context: "Call · " + sel.lead }}))}><Icons.Sparkles size={11}/> Analyze</button>
            </div>
          </div>
          <div style={{ padding: 14 }}>
            <div style={{ display: "flex", alignItems: "center", gap: 8, color: "var(--text-tertiary)", fontSize: 11 }}>
              <span className="mono">00:00</span>
              <div style={{ flex: 1, height: 36, position: "relative", background: "var(--bg-raised)", borderRadius: 4, overflow: "hidden" }}>
                <svg width="100%" height="36" viewBox="0 0 240 36" preserveAspectRatio="none">
                  {Array.from({ length: 80 }).map((_, i) => {
                    const h = 4 + Math.abs(Math.sin(i * 0.5 + (sel.id?.length || 0))) * 26 + (i % 7 === 0 ? 4 : 0);
                    return <rect key={i} x={i * 3} y={(36 - h) / 2} width="1.6" height={h} fill={i < 48 ? "var(--accent-money)" : "var(--text-quaternary)"}/>;
                  })}
                </svg>
              </div>
              <span className="mono">{Math.floor(sel.durSec / 60)}:{String(sel.durSec % 60).padStart(2, "0")}</span>
            </div>
            <div style={{ marginTop: 12, display: "flex", gap: 6, flexWrap: "wrap" }}>
              <span className={`chip ${sel.talkRatio < 50 ? "chip-money" : "chip-status"}`}>Talk: {sel.talkRatio}%</span>
              <span className="chip">Open Q: {sel.openQ}</span>
              <span className={`chip ${sel.flags?.tpmo === "ok" ? "chip-money" : "chip-status"}`}>TPMO {sel.flags?.tpmo === "ok" ? "✓" : "?"}</span>
              <span className={`chip ${sel.flags?.soa === "captured" || sel.flags?.soa === "scheduled" ? "chip-money" : ""}`}>SOA {sel.flags?.soa}</span>
            </div>
            <div style={{ marginTop: 14, padding: 12, background: "var(--bg-raised)", borderRadius: 6, fontSize: 13, color: "var(--text-secondary)", lineHeight: 1.55 }}>
              <strong style={{ color: "var(--text-primary)" }}>AI summary —</strong> {sel.ai}
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

function ProductTrainingPane({ role, store, meId, requiredOpen }) {
  if (role === "owner")   return <ProductTrainingOwner store={store}/>;
  if (role === "manager") return <ProductTrainingManager store={store}/>;
  return <ProductTrainingRep store={store} meId={meId} requiredOpen={requiredOpen}/>;
}

/* ─── Default video library + scripts library ─────────────────────────────
   Both seed lists. The user's library is `seeds + localStorage extras`,
   merged at render time. Owner can edit via TrainingOwner authoring view. */
const DEFAULT_VIDEOS = [
  { id: "v-medg",  title: "Med Supp · Plan G — opening + objections",  cat: "Med Supp",      durMin: 12, src: "https://www.youtube.com/embed/dQw4w9WgXcQ", thumb: "https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg" },
  { id: "v-fe",     title: "Final Expense — empathy & emotional setup", cat: "Final Expense", durMin: 18, src: "https://www.youtube.com/embed/dQw4w9WgXcQ", thumb: "https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg" },
  { id: "v-aep",    title: "AEP — fast switch reasons that close",       cat: "AEP",           durMin: 9,  src: "https://www.youtube.com/embed/dQw4w9WgXcQ", thumb: "https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg" },
  { id: "v-iul",    title: "IUL — target premium vs annual premium",      cat: "Life",          durMin: 22, src: "https://www.youtube.com/embed/dQw4w9WgXcQ", thumb: "https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg" },
  { id: "v-tpmo",   title: "TPMO disclosure — verbatim walkthrough",      cat: "Compliance",    durMin: 6,  src: "https://www.youtube.com/embed/dQw4w9WgXcQ", thumb: "https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg" },
  { id: "v-cross",  title: "Cross-sell — Med Supp → FE in one call",      cat: "Med Supp",      durMin: 14, src: "https://www.youtube.com/embed/dQw4w9WgXcQ", thumb: "https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg" },
];

const DEFAULT_SCRIPTS = [
  { id: "s-medg",   title: "Med Supp — Plan G open",       cat: "Open",       version: "v3.1", updated: "2d ago", body: `Hi {{lead_name}}, this is {{rep_first}} with Atlas. The reason for my call is to make sure your Medicare Supplement gives you the same Plan G coverage at a lower rate. Quick question — when you turn the page on next year's premium, are you most concerned about the monthly cost or the network freedom?` },
  { id: "s-fe",     title: "Final Expense — empathy",       cat: "Open",       version: "v2.4", updated: "1w ago", body: `Most of my clients tell me the hardest part isn't paying for a policy, it's the thought of leaving the people they love with a bill on top of grief. Can I ask — if something happened tomorrow, who would you not want to leave that burden on?` },
  { id: "s-tpmo",   title: "TPMO disclosure (verbatim)",   cat: "Compliance", version: "v1.0", updated: "3w ago", body: `We do not offer every plan available in your area. Currently we represent {{n_orgs}} organizations which offer {{n_plans}} products in your area. Please contact Medicare.gov or 1-800-MEDICARE to get information on all of your options.` },
  { id: "s-annuity",title: "Annuity — fact-find",           cat: "Discovery",  version: "v1.7", updated: "5d ago", body: `Before I quote anything, I need to understand your timeline. The money you're considering — is this for income within the next 5 years, or is it cushion for ten-plus years out?` },
  { id: "s-xsell",  title: "Cross-sell — FE → Med Supp",   cat: "Cross-sell", version: "v2.0", updated: "1d ago", body: `Now that we've taken care of the final expense piece, the other coverage gap I usually see is on the medical side. With Plan G, your Medicare-approved costs after deductible would be zero. Want me to pull a quick rate?` },
  { id: "s-aep",    title: "AEP — switch reasons",          cat: "Open",       version: "v4.2", updated: "Today",   body: `Three reasons people switch during AEP: (1) the drug list changed, (2) their doctor dropped, (3) the premium jumped. Which of those is hitting you hardest this year?` },
];

const VIDEO_CATS  = ["All", "Med Supp", "Final Expense", "AEP", "Life", "Compliance"];
const SCRIPT_CATS = ["All", "Open", "Discovery", "Cross-sell", "Compliance"];

function useLocalArray(key, seed) {
  const [items, setItems] = React.useState(() => {
    try {
      const raw = localStorage.getItem(key);
      if (raw) return JSON.parse(raw);
    } catch (_e) {}
    return seed;
  });
  React.useEffect(() => {
    try { localStorage.setItem(key, JSON.stringify(items)); } catch (_e) {}
  }, [items]);
  return [items, setItems];
}

function VideoLibrary({ canEdit = true }) {
  // Resource data is now agency-shared via AppData.VIDEOS (migration 0010);
  // fall back to seed when nothing has been added yet so the page never
  // renders empty for fresh agencies.
  const [, force] = React.useState(0);
  React.useEffect(() => {
    const fn = () => force(n => n + 1);
    window.addEventListener("data:hydrated", fn);
    window.addEventListener("data:mutated", fn);
    window.addEventListener("data:realtime", fn);
    return () => {
      window.removeEventListener("data:hydrated", fn);
      window.removeEventListener("data:mutated", fn);
      window.removeEventListener("data:realtime", fn);
    };
  }, []);
  const live   = (window.AppData && window.AppData.VIDEOS) || [];
  const videos = live.length > 0 ? live : (window.isDemoAgency && window.isDemoAgency() ? DEFAULT_VIDEOS : []);
  const [cat, setCat]             = React.useState("All");
  const [q, setQ]                 = React.useState("");
  const [sel, setSel]             = React.useState(null);
  const [editing, setEditing]     = React.useState(null);  // {id?, title, cat, durMin, url}
  const filtered = videos.filter(v =>
    (cat === "All" || v.cat === cat) &&
    (!q || v.title.toLowerCase().includes(q.toLowerCase()))
  );

  const startNew  = () => setEditing({ id: null, title: "", cat: "Med Supp", durMin: "", url: "" });
  const startEdit = (v) => {
    const guess = v.src && v.src.includes("/embed/")
      ? v.src.replace("youtube.com/embed/", "youtube.com/watch?v=").replace("player.vimeo.com/video/", "vimeo.com/")
      : v.sourceUrl || v.src;
    setEditing({ id: v.id, title: v.title, cat: v.cat, durMin: v.durMin || "", url: guess || "" });
  };
  const saveVideo = async () => {
    const url = (editing.url || "").trim();
    if (!editing.title.trim() || !url) return;
    const src = toEmbedSrc(url);
    const thumb = thumbFromUrl(url) || editing.thumb || "";
    try {
      await window.AppData.mutate.videoUpsert({
        id: editing.id,
        title: editing.title.trim(),
        cat: editing.cat,
        durMin: +editing.durMin || 0,
        src, thumb,
        sourceUrl: url,
        sourceLabel: detectVideoSourceLabel(url),
      });
      window.toast && window.toast(editing.id ? "Video updated" : "Video added", "success");
      setEditing(null);
    } catch (_e) {
      // toast already raised by mutator
    }
  };
  const removeVideo = async (id) => {
    if (sel?.id === id) setSel(null);
    try { await window.AppData.mutate.videoDelete(id); window.toast && window.toast("Video removed", "info"); }
    catch (_e) {}
  };

  return (
    <div className="panel">
      <div className="panel-h">
        <Icons.Video size={13}/>
        <h3>Video library</h3>
        <span className="meta">{filtered.length} of {videos.length}</span>
        <input className="text-input" style={{ width: 220, marginLeft: "auto" }} placeholder="Search videos…" value={q} onChange={(e) => setQ(e.target.value)}/>
        {canEdit && (
          <button className="btn btn-primary" onClick={startNew}><Icons.Plus size={12}/> Add video</button>
        )}
      </div>
      <div style={{ padding: "10px 14px 0", display: "flex", gap: 4, flexWrap: "wrap" }}>
        {VIDEO_CATS.map(c => (
          <button key={c} className="btn btn-ghost" onClick={() => setCat(c)}
            style={{ padding: "4px 10px", fontSize: 11.5, background: cat === c ? "var(--bg-raised)" : "transparent", color: cat === c ? "var(--text-primary)" : "var(--text-tertiary)" }}>
            {c}
          </button>
        ))}
      </div>
      <div style={{ padding: 14, display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(240px, 1fr))", gap: 12 }}>
        {filtered.map(v => (
          <div key={v.id} style={{ background: "var(--bg-raised)", borderRadius: 8, overflow: "hidden", border: "1px solid var(--border-subtle)", position: "relative" }}>
            <div onClick={() => setSel(v)} style={{ position: "relative", paddingTop: "56.25%", background: "var(--bg-overlay)", cursor: "pointer" }}>
              {v.thumb && <img src={v.thumb} alt="" style={{ position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover" }}/>}
              <div style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", background: "rgba(0,0,0,0.25)" }}>
                <div style={{ width: 40, height: 40, borderRadius: 999, background: "rgba(0,0,0,0.6)", display: "flex", alignItems: "center", justifyContent: "center" }}>
                  <Icons.Play size={16} style={{ color: "white", marginLeft: 2 }}/>
                </div>
              </div>
              {v.durMin > 0 && <div style={{ position: "absolute", bottom: 6, right: 6, padding: "2px 6px", background: "rgba(0,0,0,0.7)", borderRadius: 3, fontSize: 10.5, color: "white" }}>{v.durMin}m</div>}
              {v.sourceLabel && <div style={{ position: "absolute", top: 6, left: 6, padding: "2px 6px", background: "rgba(0,0,0,0.55)", borderRadius: 3, fontSize: 9.5, color: "white", textTransform: "uppercase", letterSpacing: "0.05em" }}>{v.sourceLabel}</div>}
            </div>
            <div style={{ padding: 10, display: "flex", alignItems: "center", gap: 6 }}>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontWeight: 500, fontSize: 12.5 }} className="cell-truncate">{v.title}</div>
                <div style={{ marginTop: 4 }}><span className="chip">{v.cat}</span></div>
              </div>
              {canEdit && (
                <>
                  <button className="icon-btn" onClick={(e) => { e.stopPropagation(); startEdit(v); }} title="Edit"><Icons.Edit size={11}/></button>
                  <button className="icon-btn" onClick={(e) => { e.stopPropagation(); removeVideo(v.id); }} title="Remove" style={{ color: "var(--state-danger)" }}><Icons.X size={11}/></button>
                </>
              )}
            </div>
          </div>
        ))}
        {filtered.length === 0 && (
          <div style={{ gridColumn: "1 / -1", padding: 30, textAlign: "center", color: "var(--text-tertiary)", fontSize: 12.5 }}>
            No videos match your filter. {canEdit && <span>Click <strong style={{ color: "var(--text-secondary)" }}>Add video</strong> to paste a YouTube / Vimeo / Loom / Wistia URL.</span>}
          </div>
        )}
      </div>

      {sel && (
        <Shared.Modal title={sel.title} width={800} onClose={() => setSel(null)}>
          {isDirectVideo(sel.src) ? (
            <video src={sel.src} controls autoPlay style={{ width: "100%", borderRadius: 6, background: "black" }}/>
          ) : (
            <div style={{ position: "relative", paddingTop: "56.25%", background: "black", borderRadius: 6, overflow: "hidden" }}>
              <iframe src={sel.src} title={sel.title} allow="accelerometer; encrypted-media; picture-in-picture" allowFullScreen
                style={{ position: "absolute", inset: 0, width: "100%", height: "100%", border: 0 }}/>
            </div>
          )}
          <div style={{ marginTop: 10, display: "flex", gap: 8, alignItems: "center", color: "var(--text-tertiary)", fontSize: 12 }}>
            <Icons.Clock size={11}/> {sel.durMin || 0} min · <span className="chip">{sel.cat}</span>
            {sel.sourceLabel && <span className="chip" style={{ fontSize: 9.5 }}>{sel.sourceLabel}</span>}
          </div>
        </Shared.Modal>
      )}

      {editing && (
        <Shared.Modal title={editing.id ? "Edit video" : "Add video to library"} width={560} onClose={() => setEditing(null)}>
          <div style={{ display: "grid", gridTemplateColumns: "1fr", gap: 10 }}>
            <Shared.Field label="Video URL (YouTube / Vimeo / Loom / Wistia / direct .mp4)">
              <input className="text-input" value={editing.url} onChange={(e) => setEditing({ ...editing, url: e.target.value })}
                placeholder="https://www.youtube.com/watch?v=… or https://vimeo.com/… etc."
                autoFocus={!editing.id}/>
            </Shared.Field>
            <Shared.Field label="Title">
              <input className="text-input" value={editing.title} onChange={(e) => setEditing({ ...editing, title: e.target.value })} placeholder="Plan G — opening line walkthrough"/>
            </Shared.Field>
            <div style={{ display: "grid", gridTemplateColumns: "1fr 100px", gap: 10 }}>
              <Shared.Field label="Category">
                <Shared.Select value={editing.cat} onChange={(v) => setEditing({ ...editing, cat: v })}
                  options={VIDEO_CATS.filter(c => c !== "All").map(c => ({ v: c, l: c }))}/>
              </Shared.Field>
              <Shared.Field label="Length (min)">
                <input className="text-input" type="number" value={editing.durMin} onChange={(e) => setEditing({ ...editing, durMin: e.target.value })} placeholder="12"/>
              </Shared.Field>
            </div>
            {editing.url && (
              <div style={{ padding: 10, background: "var(--bg-raised)", borderRadius: 6, fontSize: 11, color: "var(--text-tertiary)" }}>
                <strong style={{ color: "var(--text-secondary)" }}>{detectVideoSourceLabel(editing.url)}</strong> · embed src: <code style={{ wordBreak: "break-all" }}>{toEmbedSrc(editing.url)}</code>
              </div>
            )}
          </div>
          <div style={{ marginTop: 14, display: "flex", gap: 8 }}>
            <button className="btn btn-primary" disabled={!editing.title.trim() || !editing.url.trim()} onClick={saveVideo}>
              {editing.id ? "Save" : "Add to library"}
            </button>
            <button className="btn btn-ghost" onClick={() => setEditing(null)}>Cancel</button>
          </div>
        </Shared.Modal>
      )}
    </div>
  );
}

function ScriptsLibrary() {
  // Agency-shared via AppData.SCRIPTS_LIB (migration 0010); seed fallback for
  // empty agencies so the page renders content immediately.
  const [, force] = React.useState(0);
  React.useEffect(() => {
    const fn = () => force(n => n + 1);
    window.addEventListener("data:hydrated", fn);
    window.addEventListener("data:mutated", fn);
    window.addEventListener("data:realtime", fn);
    return () => {
      window.removeEventListener("data:hydrated", fn);
      window.removeEventListener("data:mutated", fn);
      window.removeEventListener("data:realtime", fn);
    };
  }, []);
  const live    = (window.AppData && window.AppData.SCRIPTS_LIB) || [];
  const scripts = live.length > 0 ? live : (window.isDemoAgency && window.isDemoAgency() ? DEFAULT_SCRIPTS : []);
  const [cat, setCat]             = React.useState("All");
  const [q, setQ]                 = React.useState("");
  const [openId, setOpenId]       = React.useState(null);
  const [editing, setEditing]     = React.useState(null);   // {id?, title, cat, body}
  const [copyToast, setCopyToast] = React.useState(null);

  const filtered = scripts.filter(s =>
    (cat === "All" || s.cat === cat) &&
    (!q || s.title.toLowerCase().includes(q.toLowerCase()) || s.body.toLowerCase().includes(q.toLowerCase()))
  );
  const open = openId ? scripts.find(s => s.id === openId) : null;

  const startNew  = () => setEditing({ id: null, title: "", cat: "Open", body: "" });
  const startEdit = (s) => setEditing({ id: s.id, title: s.title, cat: s.cat, body: s.body });
  const save = async () => {
    if (!editing.title.trim() || !editing.body.trim()) return;
    try {
      await window.AppData.mutate.scriptUpsert({
        id: editing.id,
        title: editing.title.trim(),
        cat: editing.cat,
        body: editing.body,
      });
      window.toast && window.toast(editing.id ? "Script updated" : "Script added", "success");
      setEditing(null);
    } catch (_e) {}
  };
  const remove = async (id) => {
    if (openId === id) setOpenId(null);
    try { await window.AppData.mutate.scriptDelete(id); window.toast && window.toast("Script removed", "info"); }
    catch (_e) {}
  };
  const copyBody = async (s) => {
    try {
      await navigator.clipboard.writeText(s.body);
      setCopyToast(s.id);
      setTimeout(() => setCopyToast(null), 1400);
    } catch (_e) {
      window.toast && window.toast("Copy blocked by browser", "warn");
    }
  };

  return (
    <div className="panel">
      <div className="panel-h">
        <Icons.FileText size={13}/>
        <h3>Scripts library</h3>
        <span className="meta">{filtered.length} of {scripts.length}</span>
        <input className="text-input" style={{ width: 200, marginLeft: "auto" }} placeholder="Search title or body…" value={q} onChange={(e) => setQ(e.target.value)}/>
        <button className="btn btn-primary" onClick={startNew}><Icons.Plus size={12}/> New</button>
      </div>
      <div style={{ padding: "10px 14px 0", display: "flex", gap: 4, flexWrap: "wrap" }}>
        {SCRIPT_CATS.map(c => (
          <button key={c} className="btn btn-ghost" onClick={() => setCat(c)}
            style={{ padding: "4px 10px", fontSize: 11.5, background: cat === c ? "var(--bg-raised)" : "transparent", color: cat === c ? "var(--text-primary)" : "var(--text-tertiary)" }}>
            {c}
          </button>
        ))}
      </div>

      <div style={{ display: "grid", gridTemplateColumns: open ? "1fr 1.4fr" : "1fr", gap: 0 }}>
        <div className="list" style={{ borderRight: open ? "1px solid var(--border-subtle)" : "none" }}>
          {filtered.map(s => (
            <div key={s.id} className="row" style={{ gridTemplateColumns: "1.4fr 90px 80px 90px", height: 40, cursor: "pointer", background: openId === s.id ? "var(--bg-raised)" : undefined }}
              onClick={() => setOpenId(s.id)}>
              <div>
                <div style={{ fontWeight: 500, fontSize: 12.5 }}>{s.title}</div>
                <div style={{ fontSize: 10.5, color: "var(--text-tertiary)" }}>{s.version} · {s.updated}</div>
              </div>
              <div><span className="chip">{s.cat}</span></div>
              <div style={{ fontSize: 11, color: "var(--text-tertiary)" }}>{s.body.split(" ").length}w</div>
              <div style={{ display: "flex", gap: 4, justifyContent: "flex-end" }}>
                <button className="icon-btn" onClick={(e) => { e.stopPropagation(); copyBody(s); }} title="Copy">
                  {copyToast === s.id ? <Icons.Check size={11} style={{ color: "var(--accent-money)" }}/> : <Icons.Copy size={11}/>}
                </button>
                <button className="icon-btn" onClick={(e) => { e.stopPropagation(); startEdit(s); }} title="Edit"><Icons.Edit size={11}/></button>
              </div>
            </div>
          ))}
          {filtered.length === 0 && (
            <div style={{ padding: 30, textAlign: "center", color: "var(--text-tertiary)", fontSize: 12.5 }}>
              No scripts match your filter.
            </div>
          )}
        </div>

        {open && (
          <div style={{ padding: 16, background: "var(--bg-elevated)" }}>
            <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", marginBottom: 8 }}>
              <strong style={{ fontSize: 14 }}>{open.title}</strong>
              <span className="meta" style={{ fontSize: 11 }}>{open.version} · {open.updated}</span>
            </div>
            <div style={{ marginBottom: 12 }}><span className="chip">{open.cat}</span></div>
            <div style={{ padding: 14, background: "var(--bg-raised)", borderRadius: 6, fontSize: 13.5, lineHeight: 1.7, color: "var(--text-primary)", whiteSpace: "pre-wrap" }}>
              {open.body}
            </div>
            <div style={{ marginTop: 10, fontSize: 11, color: "var(--text-tertiary)" }}>
              Variables: <code style={{ fontSize: 11 }}>{`{{lead_name}}`}</code> · <code style={{ fontSize: 11 }}>{`{{rep_first}}`}</code> · <code style={{ fontSize: 11 }}>{`{{n_orgs}}`}</code> are filled at speak-time on the dialer.
            </div>
            <div style={{ marginTop: 14, display: "flex", gap: 8, justifyContent: "flex-end" }}>
              <button className="btn btn-ghost" onClick={() => remove(open.id)} style={{ color: "var(--state-danger)" }}>
                <Icons.X size={11}/> Delete
              </button>
              <button className="btn" onClick={() => copyBody(open)}>
                {copyToast === open.id ? <><Icons.Check size={11}/> Copied</> : <><Icons.Copy size={11}/> Copy</>}
              </button>
              <button className="btn btn-primary" onClick={() => startEdit(open)}>
                <Icons.Edit size={11}/> Edit
              </button>
            </div>
          </div>
        )}
      </div>

      {editing && (
        <Shared.Modal title={editing.id ? "Edit script" : "New script"} width={620} onClose={() => setEditing(null)} actions={
          <>
            <button className="btn btn-ghost" onClick={() => setEditing(null)}>Cancel</button>
            <button className="btn btn-primary" onClick={save} disabled={!editing.title.trim() || !editing.body.trim()}>
              <Icons.Check size={11}/> {editing.id ? "Save" : "Add"}
            </button>
          </>
        }>
          <Shared.Field label="Title">
            <input className="text-input" value={editing.title} onChange={(e) => setEditing({ ...editing, title: e.target.value })} placeholder="Med Supp · Plan G open" autoFocus/>
          </Shared.Field>
          <Shared.Field label="Category">
            <Shared.Select value={editing.cat} onChange={(v) => setEditing({ ...editing, cat: v })} options={SCRIPT_CATS.filter(c => c !== "All").map(c => ({ v: c, l: c }))}/>
          </Shared.Field>
          <Shared.Field label="Body">
            <textarea className="text-input" rows={10} value={editing.body} onChange={(e) => setEditing({ ...editing, body: e.target.value })}
              placeholder={`Hi {{lead_name}}, this is {{rep_first}} with Atlas...`}
              style={{ width: "100%", lineHeight: 1.6, fontFamily: "var(--font-ui)" }}/>
          </Shared.Field>
          <div style={{ fontSize: 11, color: "var(--text-tertiary)" }}>
            Use <code style={{ fontSize: 11 }}>{`{{lead_name}}`}</code> / <code style={{ fontSize: 11 }}>{`{{rep_first}}`}</code> for runtime substitution.
          </div>
        </Shared.Modal>
      )}
    </div>
  );
}

/* ─── Status chip helper used across rep/manager/owner views ─────────── */
const STATUS_CHIP_CLASS = {
  "complete":    "chip-money",
  "in-progress": "chip-info",
  "due":         "chip-status",
  "overdue":     "chip-status",
  "assigned":    "",
};
function StatusChip({ status }) {
  return <span className={`chip ${STATUS_CHIP_CLASS[status] || ""}`} style={status === "overdue" ? { color: "var(--state-danger)", borderColor: "var(--state-danger)" } : undefined}>{status}</span>;
}

/* ─── Reusable course list with real progress bars ────────────────────── */
function CourseList({ courses, store, repId, onOpen, showRequiredFlag }) {
  return (
    <div className="list">
      <div className="list-h" style={{ gridTemplateColumns: "1.6fr 100px 90px 1fr 110px 110px" }}>
        <div>Course</div><div>Track</div><div className="tabular" style={{ textAlign: "right" }}>Min</div><div>Progress</div><div>Status</div><div></div>
      </div>
      {courses.map(c => {
        const status = ProductTraining.statusFor(repId, c, store.progress, store.assignments);
        const pct    = ProductTraining.percentFor(repId, c, store.progress);
        const cta    = status === "complete" ? "Review" : (pct > 0 ? "Resume" : "Start");
        return (
          <div key={c.id} className="row" style={{ gridTemplateColumns: "1.6fr 100px 90px 1fr 110px 110px" }}>
            <div>
              <div style={{ fontWeight: 500 }}>{c.title}</div>
              {showRequiredFlag && c.required && <div style={{ fontSize: 10.5, color: "var(--accent-status)", marginTop: 2 }}>required</div>}
            </div>
            <div><span className="chip">{c.track}</span></div>
            <div className="tabular" style={{ textAlign: "right", color: "var(--text-tertiary)" }}>{c.durMin}</div>
            <div style={{ display: "flex", alignItems: "center", gap: 8, paddingRight: 12 }}>
              <div style={{ flex: 1, height: 5, background: "var(--bg-raised)", borderRadius: 2, overflow: "hidden" }}>
                <div style={{ width: `${pct}%`, height: "100%", background: pct === 100 ? "var(--accent-money)" : "var(--accent-status)" }}></div>
              </div>
              <span className="tabular" style={{ fontSize: 11, color: "var(--text-tertiary)", minWidth: 30, textAlign: "right" }}>{pct}%</span>
            </div>
            <div><StatusChip status={status}/></div>
            <div><button className="btn btn-ghost" onClick={() => onOpen(c)}><Icons.Play size={11}/> {cta}</button></div>
          </div>
        );
      })}
      {courses.length === 0 && (
        <div style={{ padding: 20, textAlign: "center", color: "var(--text-tertiary)", fontSize: 12.5 }}>No courses here.</div>
      )}
    </div>
  );
}

/* ─── Course viewer (rep) — walks sections + lessons, marks complete ──── */
function CourseViewerModal({ course, repId, store, onClose }) {
  const sections = course.sections || [];
  const lessons = sections.flatMap((s, si) => (s.lessons || []).map((l, li) => ({ ...l, _sec: s.title, _i: `${si}.${li}` })));
  const total = lessons.length;
  const prog  = ProductTraining.getProgress(store.progress, repId, course.id);

  // Resume at first incomplete lesson, else 0.
  const initial = Math.max(0, lessons.findIndex(l => !prog.completedLessons.includes(l._i)));
  const [idx, setIdx] = React.useState(initial === -1 ? 0 : initial);
  const lesson = lessons[idx];
  const isDone = lesson ? prog.completedLessons.includes(lesson._i) : false;
  const completedCount = prog.completedLessons.length;
  const pct = total ? Math.round((completedCount / total) * 100) : 0;

  const toggle = () => {
    if (!lesson) return;
    if (isDone) ProductTraining.unmarkLessonComplete(repId, course.id, lesson._i);
    else        ProductTraining.markLessonComplete(repId,   course.id, lesson._i);
    if (!isDone && idx < lessons.length - 1) setIdx(idx + 1);  // auto-advance on complete
  };

  return (
    <Shared.Modal title={course.title} width={920} onClose={onClose}>
      {total === 0 ? (
        <div style={{ padding: 30, textAlign: "center", color: "var(--text-tertiary)", fontSize: 13 }}>
          This course doesn't have any lessons yet.
        </div>
      ) : (
        <>
          <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 12, fontSize: 12, color: "var(--text-tertiary)" }}>
            <div style={{ flex: 1, height: 5, background: "var(--bg-raised)", borderRadius: 2, overflow: "hidden" }}>
              <div style={{ width: `${pct}%`, height: "100%", background: pct === 100 ? "var(--accent-money)" : "var(--accent-status)" }}></div>
            </div>
            <span className="tabular">{completedCount} of {total} complete · {pct}%</span>
          </div>

          <div style={{ display: "grid", gridTemplateColumns: "240px 1fr", gap: 14, minHeight: 420 }}>
            <div style={{ borderRight: "1px solid var(--border-subtle)", paddingRight: 12, maxHeight: 460, overflowY: "auto" }}>
              {sections.map((s, si) => (
                <div key={si} style={{ marginBottom: 10 }}>
                  <div style={{ fontSize: 11, fontWeight: 600, textTransform: "uppercase", letterSpacing: 0.4, color: "var(--text-tertiary)", marginBottom: 4 }}>{s.title}</div>
                  {(s.lessons || []).map((l, li) => {
                    const lid = `${si}.${li}`;
                    const flat = lessons.findIndex(x => x._i === lid);
                    const done = prog.completedLessons.includes(lid);
                    return (
                      <button key={li} onClick={() => setIdx(flat)} className="btn btn-ghost"
                        style={{ display: "flex", justifyContent: "flex-start", width: "100%", padding: "6px 8px", fontSize: 12, background: flat === idx ? "var(--bg-raised)" : "transparent", marginBottom: 2, gap: 6 }}>
                        {done
                          ? <Icons.Check size={11} style={{ color: "var(--accent-money)" }}/>
                          : <Icons.Play size={10} style={{ color: "var(--text-tertiary)" }}/>}
                        <span style={{ flex: 1, textAlign: "left", color: done ? "var(--text-tertiary)" : "var(--text-primary)" }}>{l.title}</span>
                      </button>
                    );
                  })}
                </div>
              ))}
            </div>

            <div>
              {lesson && (
                <>
                  <div style={{ fontSize: 11, color: "var(--text-tertiary)", marginBottom: 4 }}>{lesson._sec}</div>
                  <div style={{ fontSize: 16, fontWeight: 600, marginBottom: 12 }}>{lesson.title}</div>
                  {lesson.videoUrl ? (
                    isDirectVideo(lesson.videoUrl) ? (
                      <video src={lesson.videoUrl} controls style={{ width: "100%", borderRadius: 6, background: "black" }}/>
                    ) : (
                      <div style={{ position: "relative", paddingTop: "56.25%", background: "black", borderRadius: 6, overflow: "hidden" }}>
                        <iframe src={toEmbedSrc(lesson.videoUrl)} title={lesson.title} allow="accelerometer; encrypted-media; picture-in-picture" allowFullScreen
                          style={{ position: "absolute", inset: 0, width: "100%", height: "100%", border: 0 }}/>
                      </div>
                    )
                  ) : (
                    <div style={{ padding: 30, textAlign: "center", background: "var(--bg-raised)", borderRadius: 6, color: "var(--text-tertiary)", fontSize: 13 }}>
                      No video on this lesson yet.
                    </div>
                  )}
                  {lesson.description && (
                    <div style={{ marginTop: 12, padding: 12, background: "var(--bg-raised)", borderRadius: 6, fontSize: 13, lineHeight: 1.55, color: "var(--text-secondary)", whiteSpace: "pre-wrap" }}>
                      {lesson.description}
                    </div>
                  )}
                  <div style={{ marginTop: 12, display: "flex", gap: 8, justifyContent: "space-between", alignItems: "center" }}>
                    <button className="btn" disabled={idx === 0} onClick={() => setIdx(i => Math.max(0, i - 1))}>
                      <Icons.ArrowRight size={11} style={{ transform: "rotate(180deg)" }}/> Previous
                    </button>
                    <span style={{ fontSize: 11.5, color: "var(--text-tertiary)" }}>Lesson {idx + 1} of {total}</span>
                    <div style={{ display: "flex", gap: 6 }}>
                      <button className={isDone ? "btn" : "btn btn-primary"} onClick={toggle}>
                        {isDone ? <><Icons.X size={11}/> Mark incomplete</> : <><Icons.Check size={11}/> Mark complete</>}
                      </button>
                      <button className="btn" disabled={idx === lessons.length - 1} onClick={() => setIdx(i => Math.min(lessons.length - 1, i + 1))}>
                        Next <Icons.ArrowRight size={11}/>
                      </button>
                    </div>
                  </div>
                </>
              )}
            </div>
          </div>
        </>
      )}
    </Shared.Modal>
  );
}

/* ─── Rep · Product Training ──────────────────────────────────────────── */
function ProductTrainingRep({ store, meId, requiredOpen }) {
  const [tab, setTab] = React.useState("courses");
  const [openCourse, setOpenCourse] = React.useState(null);

  const required = ProductTraining.requiredCoursesFor(meId, store.courses, store.progress, store.assignments);
  const optional = store.courses.filter(c => !required.includes(c));
  const activeCount = store.courses.filter(c => ProductTraining.statusFor(meId, c, store.progress, store.assignments) !== "complete").length;

  return (
    <>
      {requiredOpen > 0 && (
        <div style={{ marginBottom: 12, padding: 12, background: "color-mix(in oklch, var(--accent-status) 10%, transparent)", border: "1px solid var(--accent-status)", borderRadius: 6, display: "flex", alignItems: "center", gap: 10, fontSize: 13 }}>
          <Icons.Bell size={14} style={{ color: "var(--accent-status)" }}/>
          <div style={{ flex: 1 }}>
            <strong>{requiredOpen}</strong> required onboarding course{requiredOpen === 1 ? "" : "s"} remaining. Complete these before taking your first live calls.
          </div>
        </div>
      )}

      <div style={{ display: "flex", background: "var(--bg-elevated)", border: "1px solid var(--border-subtle)", borderRadius: 6, padding: 2, width: "fit-content", marginBottom: 12 }}>
        {[
          { k: "courses", l: "Courses",  icon: "Book" },
          { k: "videos",  l: "Videos",   icon: "Video" },
          { k: "scripts", l: "Scripts",  icon: "FileText" },
        ].map(t => {
          const Ic = Icons[t.icon];
          return (
            <button key={t.k} onClick={() => setTab(t.k)} className="btn btn-ghost"
              style={{ padding: "4px 12px", display: "flex", alignItems: "center", gap: 6, background: tab === t.k ? "var(--bg-raised)" : "transparent", color: tab === t.k ? "var(--text-primary)" : "var(--text-tertiary)" }}>
              <Ic size={12}/> {t.l}
            </button>
          );
        })}
      </div>

      <div className="kpi-row">
        <Shared.KpiCard label="Required remaining" value={requiredOpen} sub={requiredOpen === 0 ? "onboarding complete" : "must finish"}/>
        <Shared.KpiCard label="Active courses" value={activeCount}/>
        <Shared.KpiCard label="Cert progress" value="62%" sub="AEP 2026 cert" trend="up"/>
        <Shared.KpiCard label="CE hours · YTD" value="14.5"/>
      </div>

      {tab === "courses" && (
        <>
          {required.length > 0 && (
            <div className="panel" style={{ marginBottom: 12 }}>
              <div className="panel-h">
                <Icons.Shield size={13} style={{ color: "var(--accent-status)" }}/>
                <h3>Required onboarding</h3>
                <span className="meta">{required.filter(c => ProductTraining.statusFor(meId, c, store.progress, store.assignments) === "complete").length} of {required.length} complete</span>
              </div>
              <CourseList courses={required} store={store} repId={meId} onOpen={setOpenCourse}/>
            </div>
          )}
          <div className="panel">
            <div className="panel-h"><Icons.Book size={13}/><h3>My courses</h3></div>
            <CourseList courses={optional} store={store} repId={meId} onOpen={setOpenCourse}/>
          </div>
        </>
      )}

      {tab === "videos"  && <VideoLibrary canEdit={role !== "rep"}/>}
      {tab === "scripts" && <ScriptsLibrary/>}

      {openCourse && <CourseViewerModal course={openCourse} repId={meId} store={store} onClose={() => setOpenCourse(null)}/>}
    </>
  );
}

/* ─── Manager · Product Training ─────────────────────────────────────── */
function ProductTrainingManager({ store }) {
  const { REPS } = AppData;
  const [showAssign, setShowAssign] = React.useState(false);

  // Per-rep: # required courses overdue or stuck.
  const atRisk = REPS.map(r => {
    const required = ProductTraining.requiredCoursesFor(r.id, store.courses, store.progress, store.assignments);
    const overdue  = required.filter(c => ProductTraining.statusFor(r.id, c, store.progress, store.assignments) === "overdue");
    const open     = required.filter(c => ProductTraining.statusFor(r.id, c, store.progress, store.assignments) !== "complete");
    return { rep: r, overdue, open };
  }).filter(x => x.overdue.length > 0 || (x.open.length >= 2));

  // Avg completion rate column per rep across all courses.
  const repAvg = (rep) => {
    if (store.courses.length === 0) return 0;
    const sum = store.courses.reduce((a, c) => a + ProductTraining.percentFor(rep.id, c, store.progress), 0);
    return Math.round(sum / store.courses.length);
  };

  return (
    <>
      <div style={{ display: "flex", justifyContent: "flex-end", marginBottom: 8, gap: 6 }}>
        <button className="btn btn-primary" onClick={() => setShowAssign(true)}><Icons.Plus size={13}/> Assign course</button>
      </div>

      {atRisk.length > 0 && (
        <div className="panel" style={{ marginBottom: 12 }}>
          <div className="panel-h">
            <Icons.Bell size={13} style={{ color: "var(--state-danger)" }}/>
            <h3>At-risk producers</h3>
            <span className="meta">{atRisk.length} need attention</span>
          </div>
          <div className="list">
            <div className="list-h" style={{ gridTemplateColumns: "1.4fr 1fr 100px 100px 140px" }}>
              <div>Producer</div><div>Concern</div><div className="tabular" style={{ textAlign: "right" }}>Overdue</div><div className="tabular" style={{ textAlign: "right" }}>Open req.</div><div></div>
            </div>
            {atRisk.map(({ rep, overdue, open }) => (
              <div key={rep.id} className="row" style={{ gridTemplateColumns: "1.4fr 1fr 100px 100px 140px" }}>
                <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                  <Shared.Avatar rep={rep} size={20}/>
                  <span style={{ fontWeight: 500 }}>{rep.name}</span>
                </div>
                <div style={{ fontSize: 12, color: "var(--text-tertiary)" }}>
                  {overdue.length > 0 ? overdue.map(c => c.title).slice(0, 2).join(" · ") : "Multiple open required courses"}
                </div>
                <div className="tabular" style={{ textAlign: "right", color: overdue.length > 0 ? "var(--state-danger)" : "var(--text-tertiary)" }}>{overdue.length}</div>
                <div className="tabular" style={{ textAlign: "right" }}>{open.length}</div>
                <div style={{ display: "flex", justifyContent: "flex-end" }}>
                  <button className="btn btn-ghost" onClick={() => window.toast && window.toast(`Check-in sent to ${rep.name.split(" ")[0]}`, "success")}><Icons.MessageSquare size={11}/> Check in</button>
                </div>
              </div>
            ))}
          </div>
        </div>
      )}

      <div className="panel">
        <div className="panel-h"><h3>Enrollment matrix</h3><span className="meta">{REPS.length} producers × {store.courses.length} courses</span></div>
        <div className="list">
          <div className="list-h" style={{ gridTemplateColumns: `1.4fr repeat(${store.courses.length}, 1fr) 80px` }}>
            <div>Producer</div>
            {store.courses.map(c => <div key={c.id} className="cell-truncate" style={{ fontSize: 11 }} title={c.title}>{c.title}</div>)}
            <div className="tabular" style={{ textAlign: "right" }}>Avg %</div>
          </div>
          {REPS.map(rep => (
            <div key={rep.id} className="row" style={{ gridTemplateColumns: `1.4fr repeat(${store.courses.length}, 1fr) 80px` }}>
              <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                <Shared.Avatar rep={rep} size={20}/>
                <span style={{ fontWeight: 500 }}>{rep.name}</span>
              </div>
              {store.courses.map(c => {
                const status = ProductTraining.statusFor(rep.id, c, store.progress, store.assignments);
                const pct    = ProductTraining.percentFor(rep.id, c, store.progress);
                return (
                  <div key={c.id} title={`${c.title} · ${pct}%`}>
                    <span className={`chip ${STATUS_CHIP_CLASS[status] || ""}`} style={status === "overdue" ? { color: "var(--state-danger)", borderColor: "var(--state-danger)" } : undefined}>
                      {pct > 0 && pct < 100 ? `${pct}%` : status}
                    </span>
                  </div>
                );
              })}
              <div className="tabular" style={{ textAlign: "right", color: repAvg(rep) >= 80 ? "var(--accent-money)" : repAvg(rep) >= 50 ? "var(--text-secondary)" : "var(--state-warning)" }}>{repAvg(rep)}%</div>
            </div>
          ))}
        </div>
      </div>

      {showAssign && <AssignCourseModal store={store} onClose={() => setShowAssign(false)}/>}
    </>
  );
}

/* ─── Manager · Assign Course modal ───────────────────────────────────── */
function AssignCourseModal({ store, onClose }) {
  const { REPS } = AppData;
  const [courseId, setCourseId] = React.useState(store.courses[0]?.id || "");
  const [repIds, setRepIds]     = React.useState([]);
  const [dueDate, setDueDate]   = React.useState("");
  const toggle = (id) => setRepIds(rs => rs.includes(id) ? rs.filter(x => x !== id) : [...rs, id]);

  const save = () => {
    if (!courseId || repIds.length === 0) return;
    const a = {
      id: "asgn-" + Date.now(),
      courseId,
      repIds,
      dueDate: dueDate || null,
      assignedAt: new Date().toISOString(),
    };
    store.saveAssignments(prev => [...prev, a]);
    window.toast && window.toast(`Assigned to ${repIds.length} producer${repIds.length === 1 ? "" : "s"}`, "success");
    onClose();
  };

  return (
    <Shared.Modal title="Assign course" width={560} onClose={onClose} actions={
      <>
        <button className="btn btn-ghost" onClick={onClose}>Cancel</button>
        <button className="btn btn-primary" onClick={save} disabled={!courseId || repIds.length === 0}>
          <Icons.Check size={11}/> Assign
        </button>
      </>
    }>
      <Shared.Field label="Course">
        <Shared.Select value={courseId} onChange={setCourseId} options={store.courses.map(c => ({ v: c.id, l: c.title }))}/>
      </Shared.Field>
      <Shared.Field label="Due date (optional)">
        <input className="text-input" type="date" value={dueDate} onChange={(e) => setDueDate(e.target.value)}/>
      </Shared.Field>
      <div className="field-l" style={{ marginTop: 8 }}>Producers · {repIds.length} selected</div>
      <div style={{ marginTop: 6, maxHeight: 240, overflowY: "auto", border: "1px solid var(--border-subtle)", borderRadius: 6 }}>
        {REPS.map(r => (
          <label key={r.id} style={{ display: "flex", alignItems: "center", gap: 10, padding: "6px 10px", cursor: "pointer", borderBottom: "1px solid var(--border-subtle)", fontSize: 12.5 }}>
            <input type="checkbox" checked={repIds.includes(r.id)} onChange={() => toggle(r.id)}/>
            <Shared.Avatar rep={r} size={20}/>
            <span style={{ flex: 1 }}>{r.name}</span>
            <span className="meta" style={{ fontSize: 11 }}>{r.handle}</span>
          </label>
        ))}
      </div>
    </Shared.Modal>
  );
}

/* ─── Owner · Product Training authoring (Course Builder) ────────────── */
function ProductTrainingOwner({ store }) {
  const { REPS } = AppData;
  const [editing, setEditing] = React.useState(null);

  const newCourse = () => setEditing({
    id: "c-" + Date.now(),
    title: "",
    track: "Onboarding",
    durMin: 0,
    status: "assigned",
    required: false,
    description: "",
    sections: [],
    _isNew: true,
  });
  const editCourse = (c) => setEditing({ ...c, sections: (c.sections || []).map(s => ({ ...s, lessons: [...(s.lessons || [])] })) });
  const removeCourse = (id) => {
    if (!confirm("Delete this course? This can't be undone.")) return;
    store.saveCourses(cs => cs.filter(c => c.id !== id));
    window.toast && window.toast("Course deleted", "info");
  };
  const saveCourse = (course) => {
    const { _isNew, ...c } = course;
    if (_isNew) store.saveCourses(cs => [...cs, c]);
    else        store.saveCourses(cs => cs.map(x => x.id === c.id ? c : x));
    window.toast && window.toast(_isNew ? "Course created" : "Course saved", "success");
    setEditing(null);
  };
  const toggleRequired = (id) => {
    store.saveCourses(cs => cs.map(c => c.id === id ? { ...c, required: !c.required } : c));
  };

  // Owner library row stats: enrollment + completion rate.
  const enrolledCount = (course) => REPS.filter(r => {
    if (course.required) return true;
    return store.assignments.some(a => a.courseId === course.id && (a.repIds || []).includes(r.id));
  }).length;
  const completionRate = (course) => {
    const enrolled = REPS.filter(r => course.required || store.assignments.some(a => a.courseId === course.id && (a.repIds || []).includes(r.id)));
    if (enrolled.length === 0) return 0;
    const done = enrolled.filter(r => ProductTraining.statusFor(r.id, course, store.progress, store.assignments) === "complete").length;
    return Math.round((done / enrolled.length) * 100);
  };

  return (
    <>
      <div style={{ display: "flex", gap: 8, justifyContent: "flex-end", marginBottom: 8 }}>
        <button className="btn"><Icons.ArrowUpRight size={13}/> Audit trail</button>
        <button className="btn btn-primary" onClick={newCourse}><Icons.Plus size={13}/> New course</button>
      </div>

      <div className="panel">
        <div className="panel-h"><h3>Course library</h3><span className="meta">{store.courses.length}</span></div>
        <div className="list">
          <div className="list-h" style={{ gridTemplateColumns: "1.6fr 100px 80px 80px 90px 90px 110px 100px" }}>
            <div>Course</div><div>Track</div><div className="tabular" style={{ textAlign: "right" }}>Sec.</div><div className="tabular" style={{ textAlign: "right" }}>Min</div><div className="tabular" style={{ textAlign: "right" }}>Enrolled</div><div className="tabular" style={{ textAlign: "right" }}>Complete %</div><div>Required</div><div></div>
          </div>
          {store.courses.map(c => {
            const lessonCount = (c.sections || []).reduce((a, s) => a + (s.lessons?.length || 0), 0);
            const enrolled = enrolledCount(c);
            const completed = completionRate(c);
            return (
              <div key={c.id} className="row" style={{ gridTemplateColumns: "1.6fr 100px 80px 80px 90px 90px 110px 100px" }}>
                <div>
                  <div style={{ fontWeight: 500 }}>{c.title || <span style={{ color: "var(--text-tertiary)" }}>Untitled</span>}</div>
                  <div style={{ fontSize: 11, color: "var(--text-tertiary)", marginTop: 2 }}>{lessonCount} lesson{lessonCount === 1 ? "" : "s"}</div>
                </div>
                <div><span className="chip">{c.track}</span></div>
                <div className="tabular" style={{ textAlign: "right", color: "var(--text-tertiary)" }}>{(c.sections || []).length}</div>
                <div className="tabular" style={{ textAlign: "right", color: "var(--text-tertiary)" }}>{c.durMin}</div>
                <div className="tabular" style={{ textAlign: "right" }}>{enrolled}</div>
                <div className="tabular" style={{ textAlign: "right", color: completed >= 80 ? "var(--accent-money)" : completed >= 50 ? "var(--text-secondary)" : "var(--state-warning)" }}>{completed}%</div>
                <div>
                  <label style={{ display: "flex", alignItems: "center", gap: 6, fontSize: 12, cursor: "pointer" }}>
                    <input type="checkbox" checked={!!c.required} onChange={() => toggleRequired(c.id)}/>
                    {c.required ? <span style={{ color: "var(--accent-status)" }}>required</span> : <span style={{ color: "var(--text-tertiary)" }}>optional</span>}
                  </label>
                </div>
                <div style={{ display: "flex", gap: 4, justifyContent: "flex-end" }}>
                  <button className="icon-btn" onClick={() => editCourse(c)} title="Edit"><Icons.Edit size={11}/></button>
                  <button className="icon-btn" onClick={() => removeCourse(c.id)} title="Delete"><Icons.X size={11}/></button>
                </div>
              </div>
            );
          })}
          {store.courses.length === 0 && (
            <div style={{ padding: 30, textAlign: "center", color: "var(--text-tertiary)", fontSize: 13 }}>
              No courses yet. Click <strong>New course</strong> to start building.
            </div>
          )}
        </div>
      </div>

      {editing && <CourseBuilderModal course={editing} setCourse={setEditing} onSave={saveCourse} onCancel={() => setEditing(null)}/>}
    </>
  );
}

/* ─── Course Builder modal — sections, lessons, video upload/embed ───── */
function CourseBuilderModal({ course, setCourse, onSave, onCancel }) {
  const c = course;
  const update = (patch) => setCourse({ ...c, ...patch });
  const updateSection = (si, patch) => update({ sections: c.sections.map((s, i) => i === si ? { ...s, ...patch } : s) });
  const updateLesson = (si, li, patch) => update({
    sections: c.sections.map((s, i) => i !== si ? s : ({ ...s, lessons: s.lessons.map((l, j) => j === li ? { ...l, ...patch } : l) })),
  });
  const addSection = () => update({ sections: [...c.sections, { title: `Section ${c.sections.length + 1}`, lessons: [] }] });
  const removeSection = (si) => update({ sections: c.sections.filter((_, i) => i !== si) });
  const moveSection = (si, dir) => {
    const ns = [...c.sections]; const j = si + dir;
    if (j < 0 || j >= ns.length) return;
    [ns[si], ns[j]] = [ns[j], ns[si]];
    update({ sections: ns });
  };
  const addLesson = (si) => update({
    sections: c.sections.map((s, i) => i === si ? { ...s, lessons: [...s.lessons, { title: "New lesson", videoUrl: "", description: "" }] } : s),
  });
  const removeLesson = (si, li) => update({
    sections: c.sections.map((s, i) => i === si ? { ...s, lessons: s.lessons.filter((_, j) => j !== li) } : s),
  });
  const moveLesson = (si, li, dir) => {
    update({
      sections: c.sections.map((s, i) => {
        if (i !== si) return s;
        const ls = [...s.lessons]; const j = li + dir;
        if (j < 0 || j >= ls.length) return s;
        [ls[li], ls[j]] = [ls[j], ls[li]];
        return { ...s, lessons: ls };
      }),
    });
  };
  const onUploadVideo = (si, li, file) => {
    if (!file) return;
    if (file.size > 6 * 1024 * 1024) {
      window.toast && window.toast("Files >6MB won't persist in browser storage — paste a Loom link instead", "warn");
    }
    const reader = new FileReader();
    reader.onload = () => updateLesson(si, li, { videoUrl: reader.result });
    reader.readAsDataURL(file);
  };

  const canSave = !!c.title.trim();

  return (
    <Shared.Modal title={c._isNew ? "New course" : "Edit course"} width={860} onClose={onCancel} actions={
      <>
        <button className="btn btn-ghost" onClick={onCancel}>Cancel</button>
        <button className="btn btn-primary" onClick={() => onSave(c)} disabled={!canSave}>
          <Icons.Check size={11}/> {c._isNew ? "Create course" : "Save changes"}
        </button>
      </>
    }>
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
        <Shared.Field label="Title">
          <input className="text-input" value={c.title} onChange={(e) => update({ title: e.target.value })} placeholder="Final Expense Closing 101" autoFocus/>
        </Shared.Field>
        <Shared.Field label="Track">
          <Shared.Select value={c.track} onChange={(v) => update({ track: v })} options={COURSE_TRACKS.map(t => ({ v: t, l: t }))}/>
        </Shared.Field>
      </div>
      <Shared.Field label="Description">
        <textarea className="text-input" rows={2} value={c.description} onChange={(e) => update({ description: e.target.value })}
          placeholder="What this course teaches and who should take it" style={{ width: "100%", lineHeight: 1.55 }}/>
      </Shared.Field>
      <div style={{ display: "grid", gridTemplateColumns: "120px 1fr", gap: 12, alignItems: "center" }}>
        <Shared.Field label="Duration (min)">
          <input className="text-input" type="number" value={c.durMin} onChange={(e) => update({ durMin: +e.target.value || 0 })}/>
        </Shared.Field>
        <label style={{ display: "flex", alignItems: "center", gap: 8, fontSize: 13, marginTop: 18 }}>
          <input type="checkbox" checked={!!c.required} onChange={(e) => update({ required: e.target.checked })}/>
          <span>Required for new reps · must be completed before first live calls</span>
        </label>
      </div>

      <div style={{ marginTop: 14, paddingTop: 14, borderTop: "1px solid var(--border-subtle)" }}>
        <div style={{ display: "flex", alignItems: "center", marginBottom: 10 }}>
          <strong style={{ fontSize: 13 }}>Sections</strong>
          <span className="meta" style={{ marginLeft: 8 }}>{c.sections.length}</span>
          <button className="btn btn-ghost" style={{ marginLeft: "auto" }} onClick={addSection}><Icons.Plus size={11}/> Add section</button>
        </div>

        {c.sections.length === 0 && (
          <div style={{ padding: 20, textAlign: "center", background: "var(--bg-raised)", borderRadius: 6, color: "var(--text-tertiary)", fontSize: 12.5 }}>
            No sections yet. Click <strong>Add section</strong> to start.
          </div>
        )}

        {c.sections.map((s, si) => (
          <div key={si} style={{ marginBottom: 10, border: "1px solid var(--border-subtle)", borderRadius: 6, background: "var(--bg-raised)" }}>
            <div style={{ display: "flex", alignItems: "center", gap: 6, padding: "8px 10px", borderBottom: "1px solid var(--border-subtle)" }}>
              <span style={{ fontSize: 11, color: "var(--text-tertiary)", minWidth: 22 }}>#{si + 1}</span>
              <input className="text-input" value={s.title} onChange={(e) => updateSection(si, { title: e.target.value })} placeholder="Section title" style={{ flex: 1 }}/>
              <button className="icon-btn" onClick={() => moveSection(si, -1)} disabled={si === 0} title="Move up"><Icons.ArrowRight size={11} style={{ transform: "rotate(-90deg)" }}/></button>
              <button className="icon-btn" onClick={() => moveSection(si,  1)} disabled={si === c.sections.length - 1} title="Move down"><Icons.ArrowRight size={11} style={{ transform: "rotate(90deg)" }}/></button>
              <button className="icon-btn" onClick={() => removeSection(si)} title="Remove section"><Icons.X size={11}/></button>
            </div>

            <div style={{ padding: 10, display: "flex", flexDirection: "column", gap: 8 }}>
              {s.lessons.map((l, li) => (
                <div key={li} style={{ padding: 10, background: "var(--bg-elevated)", borderRadius: 6, border: "1px solid var(--border-subtle)" }}>
                  <div style={{ display: "flex", gap: 6, alignItems: "center", marginBottom: 6 }}>
                    <span style={{ fontSize: 10.5, color: "var(--text-tertiary)", minWidth: 28 }}>L{si + 1}.{li + 1}</span>
                    <input className="text-input" value={l.title} onChange={(e) => updateLesson(si, li, { title: e.target.value })} placeholder="Lesson title" style={{ flex: 1 }}/>
                    <button className="icon-btn" onClick={() => moveLesson(si, li, -1)} disabled={li === 0} title="Move up"><Icons.ArrowRight size={11} style={{ transform: "rotate(-90deg)" }}/></button>
                    <button className="icon-btn" onClick={() => moveLesson(si, li,  1)} disabled={li === s.lessons.length - 1} title="Move down"><Icons.ArrowRight size={11} style={{ transform: "rotate(90deg)" }}/></button>
                    <button className="icon-btn" onClick={() => removeLesson(si, li)} title="Remove lesson"><Icons.X size={11}/></button>
                  </div>
                  <div style={{ display: "grid", gridTemplateColumns: "1fr auto", gap: 6, alignItems: "center" }}>
                    <input className="text-input"
                      value={l.videoUrl?.startsWith("data:") ? "(uploaded file)" : (l.videoUrl || "")}
                      readOnly={l.videoUrl?.startsWith("data:")}
                      onChange={(e) => updateLesson(si, li, { videoUrl: e.target.value })}
                      placeholder="Paste Loom / YouTube / Vimeo link or upload →"/>
                    <label className="btn btn-ghost" style={{ cursor: "pointer", whiteSpace: "nowrap" }}>
                      <Icons.ArrowUpRight size={11}/> Upload
                      <input type="file" accept="video/*" style={{ display: "none" }} onChange={(e) => onUploadVideo(si, li, e.target.files?.[0])}/>
                    </label>
                  </div>
                  <textarea className="text-input" rows={2} value={l.description} onChange={(e) => updateLesson(si, li, { description: e.target.value })}
                    placeholder="What this lesson covers (optional)" style={{ width: "100%", marginTop: 6, lineHeight: 1.5 }}/>
                  {l.videoUrl && !l.videoUrl.startsWith("data:") && (
                    <div style={{ fontSize: 10.5, color: "var(--text-tertiary)", marginTop: 4 }}>
                      Embed: <code style={{ fontSize: 10.5 }}>{toEmbedSrc(l.videoUrl).slice(0, 70)}{toEmbedSrc(l.videoUrl).length > 70 ? "…" : ""}</code>
                    </div>
                  )}
                </div>
              ))}
              <button className="btn btn-ghost" style={{ alignSelf: "flex-start" }} onClick={() => addLesson(si)}><Icons.Plus size={11}/> Add lesson</button>
            </div>
          </div>
        ))}
      </div>
    </Shared.Modal>
  );
}


/* ─────────────────────────────────────────────────────────────────────────
   6. Calls — Gong-style cards with waveform, transcript, AI score
   ───────────────────────────────────────────────────────────────────────── */
function PageCalls({ role = "rep" }) {
  const { RECORDINGS, REPS } = AppData;
  const repById = Object.fromEntries(REPS.map(r => [r.id, r]));
  // GAP-D1 — resolve the actual signed-in viewer instead of REPS[0]=Marcus.
  const meIdent = (typeof window !== "undefined" && window.me && window.me()) || null;
  const meId = meIdent?.rep_id || (REPS[0] && REPS[0].id);
  // Manager view scopes to downline; rep to self; owner sees fleet.
  const scopeIds = (typeof window !== "undefined" && window.scopeRepIds && window.scopeRepIds()) || null;
  const visible = role === "rep"
    ? RECORDINGS.filter(r => !r.repId || r.repId === meId)
    : role === "manager" && scopeIds
      ? RECORDINGS.filter(r => !r.repId || scopeIds.includes(r.repId))
      : RECORDINGS;

  const [selId, setSelId] = React.useState(visible[0]?.id);
  const sel = visible.find(r => r.id === selId) || visible[0];

  return (
    <div className="page-pad">
      <div className="page-h">
        <div>
          <div className="page-title">Calls</div>
          <div className="page-sub">{role === "rep" ? "My calls" : "All recorded calls"} · waveform · talk ratio · AI score</div>
        </div>
      </div>

      <div className="calls-grid" style={{ display: "grid", gridTemplateColumns: "320px 1fr", gap: 14 }}>
        <div className="panel">
          <div className="panel-h"><h3>Recordings</h3><span className="meta">{visible.length}</span></div>
          <div style={{ padding: 8, display: "flex", flexDirection: "column", gap: 6 }}>
            {visible.map(r => (
              <button key={r.id} onClick={() => setSelId(r.id)} className="btn btn-ghost" style={{ justifyContent: "flex-start", padding: 10, background: sel?.id === r.id ? "var(--bg-overlay)" : "var(--bg-raised)", border: "1px solid var(--border-subtle)", flexDirection: "column", alignItems: "stretch", gap: 4 }}>
                <div style={{ display: "flex", justifyContent: "space-between", width: "100%" }}>
                  <strong style={{ fontSize: 12.5 }}>{r.lead}</strong>
                  <span className="tabular" style={{ color: r.score >= 80 ? "var(--accent-money)" : r.score >= 60 ? "var(--state-warning)" : "var(--state-danger)", fontSize: 11.5 }}>{r.score}</span>
                </div>
                <div style={{ display: "flex", justifyContent: "space-between", color: "var(--text-tertiary)", fontSize: 11 }}>
                  <span>{r.date}</span>
                  <span className="mono">{Math.floor(r.durSec / 60)}:{String(r.durSec % 60).padStart(2, "0")}</span>
                </div>
              </button>
            ))}
            {visible.length === 0 && (
              <div style={{ padding: 24, textAlign: "center", color: "var(--text-tertiary)", fontSize: 12.5 }}>
                {role === "rep" ? "No calls logged yet — make your first dial from the Floor." : "No recorded calls in scope."}
              </div>
            )}
          </div>
        </div>

        <div className="panel">
          <div className="panel-h">
            <Icons.Headset size={13}/>
            <h3>{sel?.lead} · score {sel?.score}</h3>
            <div style={{ marginLeft: "auto", display: "flex", gap: 6 }}>
              <button className="btn btn-ghost" onClick={() => sel && window.dispatchEvent(new CustomEvent("ai:ask", { detail: { prompt: `Summarize the call with ${sel.lead} and grade my open-ended question rate`, context: "Call · " + sel.lead }}))}><Icons.Sparkles size={11}/> Analyze</button>
              <button className="btn btn-ghost" onClick={() => sel && AppData.mutate.vaultArtifactInsert({ kind: "Recording", lead_name: sel.lead, rep_id: sel.repId, retention: "10y", status: "complete" }).then(() => window.toast && window.toast(`Sent ${sel.lead}'s recording to Vault`, "success"))}><Icons.Shield size={11}/> Send to vault</button>
              <button className="btn btn-ghost" onClick={() => window.dispatchEvent(new CustomEvent("nav:goto", { detail: { page: "vault" }}))}><Icons.ArrowUpRight size={11}/> Open Vault</button>
            </div>
          </div>
          <div style={{ padding: 14 }}>
            <div style={{ display: "flex", alignItems: "center", gap: 8, color: "var(--text-tertiary)", fontSize: 11 }}>
              <span className="mono">00:00</span>
              <div style={{ flex: 1, height: 36, position: "relative", background: "var(--bg-raised)", borderRadius: 4, overflow: "hidden" }}>
                <svg width="100%" height="36" viewBox="0 0 240 36" preserveAspectRatio="none">
                  {Array.from({ length: 80 }).map((_, i) => {
                    const h = 4 + Math.abs(Math.sin(i * 0.5 + (sel?.id?.length || 0))) * 26 + (i % 7 === 0 ? 4 : 0);
                    return <rect key={i} x={i * 3} y={(36 - h) / 2} width="1.6" height={h} fill={i < 48 ? "var(--accent-money)" : "var(--text-quaternary)"}/>;
                  })}
                </svg>
              </div>
              <span className="mono">{Math.floor((sel?.durSec || 0) / 60)}:{String((sel?.durSec || 0) % 60).padStart(2, "0")}</span>
            </div>
            <div style={{ marginTop: 12, display: "flex", gap: 6, flexWrap: "wrap" }}>
              <span className={`chip ${sel?.talkRatio < 50 ? "chip-money" : "chip-status"}`}>Talk: {sel?.talkRatio}%</span>
              <span className="chip">Open Q: {sel?.openQ}</span>
              <span className={`chip ${sel?.flags?.tpmo === "ok" ? "chip-money" : "chip-status"}`}>TPMO {sel?.flags?.tpmo === "ok" ? "✓" : "?"}</span>
              <span className={`chip ${sel?.flags?.soa === "captured" || sel?.flags?.soa === "scheduled" ? "chip-money" : ""}`}>SOA {sel?.flags?.soa}</span>
            </div>
            <div style={{ marginTop: 14, padding: 12, background: "var(--bg-raised)", borderRadius: 6, fontSize: 13, color: "var(--text-secondary)", lineHeight: 1.55 }}>
              <strong style={{ color: "var(--text-primary)" }}>AI summary —</strong> {sel?.ai || <span style={{ color: "var(--text-tertiary)" }}>processing…</span>}
            </div>

            {/* Whisper transcript when available — falls back to a hint when the
                transcribe pipeline hasn't run yet for this recording. */}
            {sel && (
              <div style={{ marginTop: 14 }}>
                <div style={{ fontSize: 11, color: "var(--text-tertiary)", textTransform: "uppercase", letterSpacing: "0.06em", fontWeight: 500, marginBottom: 6, display: "flex", alignItems: "center", gap: 6 }}>
                  <Icons.FileText size={11}/> Transcript
                </div>
                {window.PostCallTranscript
                  ? (() => { const T = window.PostCallTranscript; return <T recordingId={sel.id}/>; })()
                  : <div style={{ fontSize: 11.5, color: "var(--text-tertiary)" }}>Transcript module loading…</div>}
              </div>
            )}
          </div>
        </div>
      </div>
    </div>
  );
}

/* ─────────────────────────────────────────────────────────────────────────
   7. Book Analytics — owner
   ───────────────────────────────────────────────────────────────────────── */
function PageBook() {
  const [period, setPeriod] = React.useState("13mo");
  const [drill, setDrill]   = React.useState(null);
  const [view, setView]     = React.useState("mix");

  const exportBook = () => {
    const blob = new Blob([JSON.stringify({ period, generated_at: new Date().toISOString() }, null, 2)], { type: "application/json" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a"); a.href = url; a.download = `book-${period}-${new Date().toISOString().slice(0,10)}.json`; a.click(); URL.revokeObjectURL(url);
    window.toast && window.toast(`Book export ready`, "success");
  };

  return (
    <div className="page-pad">
      <div className="page-h">
        <div>
          <div className="page-title">Book Analytics</div>
          <div className="page-sub">Persistency · lapse · cross-sell pathway · carrier mix</div>
        </div>
        <div style={{ marginLeft: "auto", display: "flex", gap: 8 }}>
          <Shared.SectionPill items={[{k:"3mo",l:"3mo"},{k:"13mo",l:"13mo"},{k:"24mo",l:"24mo"}]} value={period} onChange={setPeriod} dense/>
          <button className="btn" onClick={exportBook}><Icons.ArrowUpRight size={13}/> Export</button>
        </div>
      </div>

      <Shared.SectionPill items={[{k:"mix",l:"Carrier mix"},{k:"cohorts",l:"Cohorts"},{k:"crosssell",l:"Cross-sell"}]} value={view} onChange={setView}/>

      <div className="kpi-row">
        <Shared.KpiCard hero label="In-force AP" prefix="$" value="6.84M" sub="+9.4% YoY" trend="up"/>
        <Shared.KpiCard label={`Persistency · ${period}`} value="91.4%" sub="goal 90%" trend="up"/>
        <Shared.KpiCard label="Lapse rate" value="4.2%" sub="-0.6 WoW" neg trend="up"/>
        <Shared.KpiCard label="Cross-sell rate" value="22%" sub="FE → Med Supp"/>
      </div>

      <div className="book-grid" style={{ display: "grid", gridTemplateColumns: "1.4fr 1fr", gap: 14 }}>
        <div className="panel">
          <div className="panel-h"><h3>Carrier mix · in-force</h3></div>
          <div className="list">
            <div className="list-h" style={{ gridTemplateColumns: "1.4fr 100px 100px 1fr" }}>
              <div>Carrier</div>
              <div className="tabular" style={{ textAlign: "right" }}>Apps</div>
              <div className="tabular" style={{ textAlign: "right" }}>AP</div>
              <div></div>
            </div>
            {[
              { n: "UHC",            a: 184, p: 1842000, w: 100 },
              { n: "Humana Vantage", a: 132, p: 1320000, w: 72  },
              { n: "Aetna SRC",      a: 124, p: 1108000, w: 60  },
              { n: "F&G Annuities",  a:  42,  p: 1860000, w: 100 },
              { n: "Mutual of Omaha",a:  88,  p:  708000, w: 38  },
            ].map((r, i) => (
              <div key={i} className="row" style={{ gridTemplateColumns: "1.4fr 100px 100px 1fr", cursor: "pointer", background: drill === r.n ? "var(--bg-raised)" : undefined }} onClick={() => setDrill(drill === r.n ? null : r.n)}>
                <div style={{ fontWeight: 500 }}>{r.n}</div>
                <div className="tabular" style={{ textAlign: "right", color: "var(--text-tertiary)" }}>{r.a}</div>
                <div className="tabular" style={{ textAlign: "right" }}>${r.p.toLocaleString()}</div>
                <div style={{ height: 5, background: "var(--bg-raised)", borderRadius: 2, marginLeft: 14, overflow: "hidden" }}>
                  <div style={{ width: `${r.w}%`, height: "100%", background: "var(--accent-money)" }}></div>
                </div>
              </div>
            ))}
            {drill && (
              <div style={{ padding: 14, background: "var(--bg-raised)", borderTop: "1px solid var(--border-subtle)", display: "flex", flexDirection: "column", gap: 8 }}>
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
                  <strong style={{ fontSize: 13 }}>{drill}</strong>
                  <button className="icon-btn" onClick={() => setDrill(null)}><Icons.X size={11}/></button>
                </div>
                <div style={{ fontSize: 12, color: "var(--text-secondary)", lineHeight: 1.55 }}>
                  Persistency: {drill === "F&G Annuities" ? 96 : drill === "UHC" ? 94 : 87}% over {period}.
                  Top product: {drill === "F&G Annuities" ? "Annuity $50K" : "Plan G"}.
                  NIGO rate: {drill === "Aetna SRC" ? "3.1% (high)" : "1.4%"}.
                </div>
                <div style={{ display: "flex", gap: 6 }}>
                  <button className="btn btn-ghost" onClick={() => window.dispatchEvent(new CustomEvent("ai:ask", { detail: { prompt: `Break down ${drill}: top contributors, NIGO drivers, persistency drift this ${period}`, context: "Book · " + drill }}))}>
                    <Icons.Sparkles size={11}/> Ask the Book
                  </button>
                  <button className="btn btn-ghost" onClick={() => window.dispatchEvent(new CustomEvent("nav:goto", { detail: { page: "carriers" }}))}>Open in Carriers</button>
                </div>
              </div>
            )}
          </div>
        </div>

        <div className="panel">
          <div className="panel-h"><h3>{period} persistency · cohorts</h3></div>
          <div style={{ padding: 14 }}>
            {[
              { l: "Med Supp · UHC",      v: 94 },
              { l: "Med Supp · Humana",   v: 92 },
              { l: "FE · UHC",            v: 88 },
              { l: "FE · Mutual of Omaha",v: 78 },
              { l: "Annuity · F&G",       v: 96 },
            ].map((r, i) => (
              <div key={i} style={{ display: "grid", gridTemplateColumns: "1.4fr 60px 1fr", padding: "5px 0", alignItems: "center", fontSize: 12 }}>
                <span style={{ color: "var(--text-secondary)" }}>{r.l}</span>
                <span className="tabular" style={{ textAlign: "right", fontWeight: 500 }}>{r.v}%</span>
                <div style={{ height: 5, background: "var(--bg-raised)", borderRadius: 2, marginLeft: 14, overflow: "hidden" }}>
                  <div style={{ width: `${r.v}%`, height: "100%", background: r.v >= 90 ? "var(--accent-money)" : r.v >= 80 ? "var(--state-warning)" : "var(--state-danger)" }}></div>
                </div>
              </div>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
}

/* ─────────────────────────────────────────────────────────────────────────
   8. Settings — role-aware (org / billing / integrations / API / routing /
      notifications). Owner sees everything, mgr sees team-relevant
      sections, rep sees only their profile.
   ───────────────────────────────────────────────────────────────────────── */
function PageSettings({ role = "owner" }) {
  const TABS = role === "owner"
    ? [["org","Organization"],["team","Team & invites"],["carriers","Carriers"],["billing","Billing"],["integrations","Integrations"],["api","API keys"],["routing","Routing rules"],["calling","Calling"],["notifications","Notifications"],["profile","Profile"]]
    : role === "manager"
      ? [["team","Team & invites"],["carriers","Carriers"],["routing","Routing rules"],["calling","Calling"],["notifications","Notifications"],["profile","Profile"]]
      : [["calling","Calling"],["profile","Profile"],["notifications","Notifications"]];
  // Allow other pages to deeplink into a specific tab via sessionStorage
  // (e.g. Resources → "Manage carriers" jumps here with carriers preselected).
  const initialTab = (() => {
    try {
      const stash = sessionStorage.getItem("repflow.settings.tab");
      if (stash) {
        sessionStorage.removeItem("repflow.settings.tab");
        if (TABS.some(([k]) => k === stash)) return stash;
      }
    } catch {}
    return TABS[0][0];
  })();
  const [tab, setTab] = React.useState(initialTab);

  return (
    <div className="page-pad">
      <div className="page-h">
        <div>
          <div className="page-title">Settings</div>
          <div className="page-sub">{role === "owner" ? "Organization, team, carriers, billing, integrations, API, routing" : role === "manager" ? "Team, carriers, routing rules and notifications" : "Your profile and notifications"}</div>
        </div>
      </div>

      <div className="settings-grid" style={{ display: "grid", gridTemplateColumns: "200px 1fr", gap: 14 }}>
        <div className="panel" style={{ padding: 6 }}>
          {TABS.map(([k, l]) => (
            <button key={k} onClick={() => setTab(k)} className="btn btn-ghost" style={{ width: "100%", justifyContent: "flex-start", padding: "8px 10px", background: tab === k ? "var(--bg-raised)" : "transparent", color: tab === k ? "var(--text-primary)" : "var(--text-secondary)", fontWeight: tab === k ? 500 : 400 }}>{l}</button>
          ))}
        </div>

        <div>
          {tab === "org"          && <SettingsOrg/>}
          {tab === "billing"      && <SettingsBilling/>}
          {tab === "integrations" && <SettingsIntegrations/>}
          {tab === "api"          && <SettingsApi/>}
          {tab === "routing"      && <SettingsRouting/>}
          {tab === "calling"      && (() => { const C = window.CallingSetup; return C ? <C/> : null; })()}
          {tab === "team"          && (() => { const T = window.SettingsTeam;  return T ? <T/> : null; })()}
          {tab === "carriers"      && (() => { const C = window.SettingsCarriers; return C ? <C canEdit={role === "owner"}/> : null; })()}
          {tab === "notifications"&& <SettingsNotifications/>}
          {tab === "profile"      && <SettingsProfile role={role}/>}
        </div>
      </div>
    </div>
  );
}

function SettingsOrg() {
  const [name, setName]     = React.useState(window.AppData?.ORG_SETTINGS?.name || "Atlas Insurance Group");
  const [legal, setLegal]   = React.useState(window.AppData?.ORG_SETTINGS?.legal || "Atlas IMO LLC");
  const [domain, setDomain] = React.useState(window.AppData?.ORG_SETTINGS?.domain || "atlasimo.com");
  const [npn, setNpn]       = React.useState(window.AppData?.ORG_SETTINGS?.npn || "19384726");
  const [saving, setSaving] = React.useState(false);
  const save = async () => {
    setSaving(true);
    try {
      await window.AppData.mutate.orgSettingsSave({ name, legal, domain, npn });
      window.toast && window.toast(`Organization saved${AppData.LIVE ? "" : " (demo only — sign in for persistence)"}`, "success");
    } catch (_e) {} finally { setSaving(false); }
  };
  return (
    <div className="panel" style={{ padding: 16 }}>
      <h3 style={{ margin: 0, marginBottom: 12 }}>Organization</h3>
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
        <Shared.Field label="Display name"><input className="text-input" value={name} onChange={(e) => setName(e.target.value)}/></Shared.Field>
        <Shared.Field label="Legal entity"><input className="text-input" value={legal} onChange={(e) => setLegal(e.target.value)}/></Shared.Field>
        <Shared.Field label="Domain"><input className="text-input" value={domain} onChange={(e) => setDomain(e.target.value)}/></Shared.Field>
        <Shared.Field label="NPN"><input className="text-input" value={npn} onChange={(e) => setNpn(e.target.value)}/></Shared.Field>
      </div>
      <div className="divider"></div>
      <h3 style={{ margin: 0, marginBottom: 8 }}>Operating states</h3>
      <OperatingStatesEditor/>
      <div className="divider"></div>
      <button className="btn btn-primary" onClick={save} disabled={saving}><Icons.Check size={12}/> {saving ? "Saving..." : "Save organization"}</button>
    </div>
  );
}

const ALL_US_STATES = ["AL","AK","AZ","AR","CA","CO","CT","DE","FL","GA","HI","ID","IL","IN","IA","KS","KY","LA","ME","MD","MA","MI","MN","MS","MO","MT","NE","NV","NH","NJ","NM","NY","NC","ND","OH","OK","OR","PA","RI","SC","SD","TN","TX","UT","VT","VA","WA","WV","WI","WY","DC"];

function OperatingStatesEditor() {
  const initial = (window.AppData?.ORG_SETTINGS?.operating_states) || ["TX","FL","CA","NY","GA","NV","AZ","OH","PA","MI","NC","WI","WA"];
  const [states, setStates] = React.useState(initial);
  const [picking, setPicking] = React.useState(false);
  const [busy, setBusy]       = React.useState(false);

  const persist = async (next) => {
    setStates(next);
    if (window.AppData?.ORG_SETTINGS) window.AppData.ORG_SETTINGS.operating_states = next;
    if (window.AppData?.mutate?.orgSettingsSave) {
      setBusy(true);
      try {
        await window.AppData.mutate.orgSettingsSave({ operating_states: next });
      } catch (_e) {} finally { setBusy(false); }
    }
  };

  const remove = (s) => persist(states.filter(x => x !== s));
  const toggle = (s) => persist(states.includes(s) ? states.filter(x => x !== s) : [...states, s].sort());

  const available = ALL_US_STATES.filter(s => !states.includes(s));

  return (
    <div>
      <div style={{ display: "flex", gap: 6, flexWrap: "wrap", alignItems: "center" }}>
        {states.map(s => (
          <span key={s} className="chip chip-money" style={{ display: "inline-flex", alignItems: "center", gap: 4 }}>
            {s}
            <button onClick={() => remove(s)} className="icon-btn" style={{ width: 14, height: 14, padding: 0, opacity: 0.6 }} title={`Remove ${s}`}>
              <Icons.X size={9}/>
            </button>
          </span>
        ))}
        <button className="btn btn-ghost" style={{ padding: "3px 10px" }} onClick={() => setPicking(p => !p)} disabled={busy}>
          <Icons.Plus size={11}/> Add{busy && " · saving…"}
        </button>
      </div>
      {picking && (
        <div style={{ marginTop: 10, padding: 10, background: "var(--bg-raised)", borderRadius: 6 }}>
          <div style={{ fontSize: 11, color: "var(--text-tertiary)", marginBottom: 6 }}>{available.length} states available</div>
          <div style={{ display: "flex", flexWrap: "wrap", gap: 4 }}>
            {available.map(s => (
              <button key={s} onClick={() => toggle(s)} className="chip" style={{ cursor: "pointer", border: 0 }}>
                {s}
              </button>
            ))}
            {available.length === 0 && <div style={{ fontSize: 11, color: "var(--text-tertiary)" }}>All 51 states + DC already operating.</div>}
          </div>
        </div>
      )}
    </div>
  );
}

function SettingsBilling() {
  const goBilling = () => {
    if (window.gotoPage) window.gotoPage("billing");
    else window.toast && window.toast("Billing page not yet wired", "info");
  };
  const updatePayment = () => {
    // Stripe-hosted billing portal — env-gated. If no portal URL set, surface
    // a friendly notice rather than the dead button it was before.
    const url = window.AppData?.ORG_SETTINGS?.stripe_portal_url;
    if (url) { window.open(url, "_blank", "noopener,noreferrer"); return; }
    window.toast && window.toast("Add STRIPE_PORTAL_URL to update payment method", "info");
  };
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
      <div className="panel" style={{ padding: 16 }}>
        <h3 style={{ margin: 0, marginBottom: 8 }}>Plan</h3>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
          <div>
            <div style={{ fontSize: 16, fontWeight: 500 }}>Network · Annual</div>
            <div style={{ color: "var(--text-tertiary)", fontSize: 12.5, marginTop: 2 }}>Up to 25 producers · all integrations · 24h support</div>
          </div>
          <button className="btn btn-ghost" onClick={goBilling}>Manage plan</button>
        </div>
      </div>
      <div className="panel" style={{ padding: 16 }}>
        <h3 style={{ margin: 0, marginBottom: 8 }}>Usage this month</h3>
        {[
          { l: "Active producers", v: "9 / 25",  w: 36 },
          { l: "Voice AI minutes", v: "12,480 / 50,000", w: 25 },
          { l: "Lead enrichment",  v: "1,840 / 5,000",   w: 37 },
          { l: "Storage",           v: "412 GB / 1 TB",   w: 41 },
        ].map((r, i) => (
          <div key={i} style={{ display: "grid", gridTemplateColumns: "1fr 120px 200px", padding: "8px 0", alignItems: "center", borderBottom: i < 3 ? "1px solid var(--border-subtle)" : 0, fontSize: 12.5 }}>
            <span style={{ color: "var(--text-secondary)" }}>{r.l}</span>
            <span className="tabular" style={{ textAlign: "right", fontWeight: 500 }}>{r.v}</span>
            <div style={{ height: 5, background: "var(--bg-raised)", borderRadius: 2, marginLeft: 14, overflow: "hidden" }}>
              <div style={{ width: `${r.w}%`, height: "100%", background: "var(--accent-money)" }}></div>
            </div>
          </div>
        ))}
      </div>
      <div className="panel" style={{ padding: 16 }}>
        <h3 style={{ margin: 0, marginBottom: 8 }}>Payment method</h3>
        <div style={{ display: "flex", gap: 8, alignItems: "center", color: "var(--text-secondary)" }}>
          <span className="chip">VISA</span><span className="mono" style={{ fontSize: 12.5 }}>**** 4419</span><span style={{ color: "var(--text-tertiary)", fontSize: 12.5 }}>· expires 09/27</span>
          <button className="btn btn-ghost" style={{ marginLeft: "auto" }} onClick={updatePayment}>Update</button>
        </div>
      </div>
    </div>
  );
}

function SettingsIntegrations() {
  const { CONNECTIONS } = AppData;
  const [testing, setTesting] = React.useState(null);
  const [twilioOpen, setTwilioOpen]   = React.useState(false);
  const [genericOpen, setGenericOpen] = React.useState(null);  // connector id

  const test = async (c) => {
    setTesting(c.id);
    // Simulated test — flip status briefly to "warn" then back to ok via mutate.connectionStatus
    await new Promise(r => setTimeout(r, 600));
    try {
      await AppData.mutate.connectionStatus(c.id, "ok", c.meta + " · last test " + new Date().toLocaleTimeString());
      window.toast && window.toast(`${c.name}: connection healthy`, "success");
    } catch (_e) {}
    setTesting(null);
  };

  return (
    <div className="panel">
      <div className="panel-h"><h3>Connected services</h3><span className="meta">{CONNECTIONS.length} configured</span></div>
      <div className="list">
        <div className="list-h" style={{ gridTemplateColumns: "1.4fr 1fr 100px 1.6fr 140px" }}>
          <div>Service</div><div>Category</div><div>Status</div><div>Detail</div><div></div>
        </div>
        {CONNECTIONS.map(c => (
          <div key={c.id} className="row" style={{ gridTemplateColumns: "1.4fr 1fr 100px 1.6fr 140px" }}>
            <div style={{ fontWeight: 500 }}>{c.name}</div>
            <div style={{ color: "var(--text-tertiary)" }}>{c.category}</div>
            <div><span className={`chip ${c.status === "ok" ? "chip-money" : c.status === "warn" ? "chip-status" : "chip-danger"}`}>{c.status === "ok" ? "Connected" : c.status === "warn" ? "Action needed" : "Down"}</span></div>
            <div style={{ color: "var(--text-tertiary)", fontSize: 12 }}>{c.meta}</div>
            <div style={{ display: "flex", gap: 4, justifyContent: "flex-end" }}>
              <button className="btn btn-ghost" onClick={() => test(c)} disabled={testing === c.id}>{testing === c.id ? "Testing..." : "Test"}</button>
              <button className="btn btn-ghost" onClick={() => { if (c.id === "twilio") setTwilioOpen(true); else if (window.CONNECTOR_SCHEMAS && window.CONNECTOR_SCHEMAS[c.id]) setGenericOpen(c.id); else window.toast && window.toast(`No config schema for ${c.name} yet`, "info"); }}>{c.status === "ok" ? "Configure" : "Reconnect"}</button>
            </div>
          </div>
        ))}
      </div>
      {twilioOpen && window.TwilioConfigModal && (() => { const M = window.TwilioConfigModal; return <M onClose={() => setTwilioOpen(false)}/>; })()}
      {genericOpen && window.ConnectorConfigModal && (() => { const M = window.ConnectorConfigModal; return <M connectorId={genericOpen} onClose={() => setGenericOpen(null)}/>; })()}
    </div>
  );
}

function SettingsApi() {
  const [revealed, setRevealed] = React.useState(false);
  // Generate a deterministic-looking but session-local key. Real key issuance
  // would call /api/keys/* — we surface a clear message when that endpoint
  // doesn't exist rather than silently failing.
  const [key, setKey] = React.useState(() => {
    try {
      const stash = sessionStorage.getItem("repflow.api_key");
      if (stash) return stash;
    } catch {}
    return "rfk_live_eyJhbGciOiJIUzI1NiJ9...QzfBn4xT2";
  });
  const newKey = () => {
    const fresh = "rfk_live_" + Math.random().toString(36).slice(2, 12) + Math.random().toString(36).slice(2, 12);
    setKey(fresh);
    setRevealed(true);
    try { sessionStorage.setItem("repflow.api_key", fresh); } catch {}
    window.toast && window.toast("New API key generated · save it now, you won't see it again", "success");
  };
  const rotate = () => {
    if (!confirm("Rotate the API key? Existing integrations will stop working until updated with the new value.")) return;
    newKey();
  };
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
      <div className="panel" style={{ padding: 16 }}>
        <h3 style={{ margin: 0, marginBottom: 8 }}>API keys</h3>
        <div style={{ color: "var(--text-tertiary)", fontSize: 12.5, marginBottom: 12 }}>Use this key to push leads or pull pipeline state via REST. Never commit keys to source control.</div>
        <div style={{ display: "flex", gap: 8, alignItems: "center", padding: 10, background: "var(--bg-raised)", borderRadius: 6, fontSize: 12.5 }}>
          <span className="mono" style={{ flex: 1, color: "var(--text-secondary)" }}>{revealed ? key : key.slice(0, 12) + "•••••••••••••••••••"}</span>
          <button className="btn btn-ghost" onClick={() => setRevealed(r => !r)}>{revealed ? "Hide" : "Reveal"}</button>
          <button className="btn btn-ghost" onClick={() => navigator.clipboard.writeText(key).then(() => window.toast && window.toast("API key copied to clipboard", "success"))}><Icons.Copy size={12}/> Copy</button>
        </div>
        <div style={{ display: "flex", gap: 8, marginTop: 12 }}>
          <button className="btn btn-primary" onClick={newKey}><Icons.Plus size={12}/> Create new key</button>
          <button className="btn" onClick={rotate}>Rotate</button>
        </div>
      </div>
      <div className="panel" style={{ padding: 16 }}>
        <h3 style={{ margin: 0, marginBottom: 8 }}>Webhooks</h3>
        <div className="list" style={{ marginTop: 8 }}>
          {[
            { url: "https://atlas.zapier.com/leads",      events: "lead.new · lead.assigned",        last: "2m ago" },
            { url: "https://atlas.n8n.io/issued",         events: "deal.issued",                       last: "14m ago" },
            { url: "https://atlas.app.n8n.cloud/nigo",    events: "deal.nigo",                          last: "yesterday" },
          ].map((w, i) => (
            <div key={i} className="row" style={{ gridTemplateColumns: "1.4fr 1fr 100px 100px" }}>
              <div className="cell-truncate mono" style={{ fontSize: 11.5, color: "var(--text-secondary)" }}>{w.url}</div>
              <div style={{ fontSize: 11.5, color: "var(--text-tertiary)" }}>{w.events}</div>
              <div style={{ fontSize: 11.5, color: "var(--text-tertiary)" }}>{w.last}</div>
              <button className="btn btn-ghost">Edit</button>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

function SettingsRouting() {
  const [rules, setRules] = React.useState([
    { id: 1, src: "FB Lead Form · T65", route: "Med Supp specialists", weight: 60 },
    { id: 2, src: "Inbound < 30s",      route: "Tier ≥ Gold",          weight: 90 },
    { id: 3, src: "Annuity",             route: "Certified producer",    weight: 100 },
    { id: 4, src: "Spanish",             route: "Bilingual round-robin", weight: 50 },
  ]);
  return (
    <div className="panel" style={{ padding: 16 }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }}>
        <h3 style={{ margin: 0 }}>Routing rules</h3>
        <button className="btn btn-primary"><Icons.Plus size={12}/> New rule</button>
      </div>
      <div className="list">
        <div className="list-h" style={{ gridTemplateColumns: "1.4fr 1.4fr 1fr 60px" }}>
          <div>Source / trigger</div><div>Route to</div><div>Priority</div><div></div>
        </div>
        {rules.map(r => (
          <div key={r.id} className="row" style={{ gridTemplateColumns: "1.4fr 1.4fr 1fr 60px" }}>
            <div style={{ fontWeight: 500 }}>{r.src}</div>
            <div style={{ color: "var(--text-secondary)" }}>{r.route}</div>
            <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
              <input type="range" min={0} max={100} value={r.weight} onChange={(e) => setRules(rs => rs.map(x => x.id === r.id ? { ...x, weight: +e.target.value } : x))} style={{ flex: 1 }}/>
              <span className="tabular" style={{ width: 30, fontSize: 11.5, color: "var(--text-tertiary)" }}>{r.weight}</span>
            </div>
            <button className="btn btn-ghost"><Icons.X size={11}/></button>
          </div>
        ))}
      </div>
    </div>
  );
}

function SettingsNotifications() {
  const [prefs, setPrefs] = React.useState({
    leadNew: true, leadStuck: true, dealIssued: true, nigo: true, coachingNew: false, recruitingNew: true, dailyDigest: true,
  });
  const update = (k, v) => {
    const next = { ...prefs, [k]: v };
    setPrefs(next);
    window.AppData.mutate.notificationPrefsSave("me", next).catch(() => {});
  };
  const t = (k, l, sub) => (
    <label style={{ display: "grid", gridTemplateColumns: "auto 1fr 80px", gap: 12, padding: "10px 0", borderBottom: "1px solid var(--border-subtle)", alignItems: "center" }}>
      <span style={{ display: "inline-block", width: 32 }}>
        <input type="checkbox" checked={prefs[k]} onChange={(e) => update(k, e.target.checked)}/>
      </span>
      <div>
        <div style={{ fontWeight: 500, fontSize: 13 }}>{l}</div>
        <div style={{ color: "var(--text-tertiary)", fontSize: 11.5, marginTop: 1 }}>{sub}</div>
      </div>
      <span style={{ textAlign: "right", color: "var(--text-tertiary)", fontSize: 11.5 }}>{prefs[k] ? "Email + push" : "off"}</span>
    </label>
  );
  return (
    <div className="panel" style={{ padding: 16 }}>
      <h3 style={{ margin: 0 }}>Notifications</h3>
      <div style={{ marginTop: 8 }}>
        {t("leadNew",       "New lead in my queue",         "Push within 30s of routing")}
        {t("leadStuck",     "Lead stuck > 3 days in stage", "Daily")}
        {t("dealIssued",    "Deal issued",                   "Push immediately")}
        {t("nigo",          "NIGO returned",                  "Push + email + escalate to mgr")}
        {t("coachingNew",   "New coaching card for me",      "Daily digest")}
        {t("recruitingNew", "New applicant in funnel",        "Daily")}
        {t("dailyDigest",   "Daily digest",                    "8am · weekdays")}
      </div>
    </div>
  );
}
function SettingsNotifications_OLD() {
  const [prefs, setPrefs] = React.useState({
    leadNew: true, leadStuck: true, dealIssued: true, nigo: true, coachingNew: false, recruitingNew: true, dailyDigest: true,
  });
  const t = (k, l, sub) => (
    <label style={{ display: "grid", gridTemplateColumns: "auto 1fr 80px", gap: 12, padding: "10px 0", borderBottom: "1px solid var(--border-subtle)", alignItems: "center" }}>
      <span style={{ display: "inline-block", width: 32 }}>
        <input type="checkbox" checked={prefs[k]} onChange={(e) => setPrefs({ ...prefs, [k]: e.target.checked })}/>
      </span>
      <div>
        <div style={{ fontWeight: 500, fontSize: 13 }}>{l}</div>
        <div style={{ color: "var(--text-tertiary)", fontSize: 11.5, marginTop: 1 }}>{sub}</div>
      </div>
      <span style={{ textAlign: "right", color: "var(--text-tertiary)", fontSize: 11.5 }}>{prefs[k] ? "Email + push" : "off"}</span>
    </label>
  );
  return (
    <div className="panel" style={{ padding: 16 }}>
      <h3 style={{ margin: 0 }}>Notifications</h3>
      <div style={{ marginTop: 8 }}>
        {t("leadNew",       "New lead in my queue",         "Push within 30s of routing")}
        {t("leadStuck",     "Lead stuck > 3 days in stage", "Daily")}
        {t("dealIssued",    "Deal issued",                   "Push immediately")}
        {t("nigo",          "NIGO returned",                  "Push + email + escalate to mgr")}
        {t("coachingNew",   "New coaching card for me",      "Daily digest")}
        {t("recruitingNew", "New applicant in funnel",        "Daily")}
        {t("dailyDigest",   "Daily digest",                    "8am · weekdays")}
      </div>
    </div>
  );
}

function SettingsProfile({ role }) {
  const me = AppData.REPS[0];
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
      <div className="panel" style={{ padding: 16 }}>
        <h3 style={{ margin: 0 }}>Profile</h3>
        <div style={{ display: "flex", alignItems: "center", gap: 14, marginTop: 12 }}>
          <Shared.Avatar rep={me} size={48}/>
          <div>
            <div style={{ fontSize: 16, fontWeight: 500 }}>{me.name}</div>
            <div style={{ color: "var(--text-tertiary)", fontSize: 12 }}>{me.handle} · Atlanta · {role}</div>
          </div>
          <button className="btn btn-ghost" style={{ marginLeft: "auto" }}>Change avatar</button>
        </div>
        <div className="divider"></div>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
          <Shared.Field label="Display name"><input className="text-input" defaultValue={me.name}/></Shared.Field>
          <Shared.Field label="Email"><input className="text-input" defaultValue="marcus@atlasimo.com"/></Shared.Field>
          <Shared.Field label="Phone"><input className="text-input" defaultValue="+1 (404) 555-0142"/></Shared.Field>
          <Shared.Field label="Time zone"><Shared.Select value="ET" onChange={() => {}} options={[{ v: "ET", l: "Eastern" }, { v: "CT", l: "Central" }, { v: "MT", l: "Mountain" }, { v: "PT", l: "Pacific" }]}/></Shared.Field>
        </div>
      </div>

      <div className="panel" style={{ padding: 16 }}>
        <h3 style={{ margin: 0 }}>Session</h3>
        <div style={{ marginTop: 10, display: "flex", gap: 8, alignItems: "center" }}>
          <button className="btn" onClick={() => window.signOut && window.signOut()}><Icons.X size={12}/> Sign out</button>
          <span style={{ color: "var(--text-tertiary)", fontSize: 11.5 }}>Ends your Supabase session and clears demo flag.</span>
        </div>
      </div>

      <div className="panel" style={{ padding: 16 }}>
        <h3 style={{ margin: 0 }}>Licenses + appointments</h3>
        <div style={{ display: "flex", gap: 6, flexWrap: "wrap", marginTop: 10 }}>
          {["TX","FL","GA","NV","AZ"].map(s => <span key={s} className="chip chip-money">{s} · active</span>)}
          {["NY"].map(s => <span key={s} className="chip chip-status">{s} · pending</span>)}
        </div>
        <div className="divider"></div>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(160px, 1fr))", gap: 8 }}>
          {["UHC","Humana","Aetna SRC","Mutual of Omaha","F&G Annuities"].map(c => (
            <div key={c} className="chip">{c}</div>
          ))}
        </div>
      </div>
    </div>
  );
}

/* ─────────────────────────────────────────────────────────────────────────
   9. Notifications panel (slide-out from Bell icon)
   ───────────────────────────────────────────────────────────────────────── */
function NotificationsPanel({ open, onClose, goto }) {
  if (!open) return null;
  const FALLBACK = [
    { kind: "lead",     t: "Hot inbound · Cheryl Hampton",    d: "14s",       sub: "FB T65 · score 92 · TX",                page: "queue" },
    { kind: "issued",   t: "Deal issued · Naomi Reese",        d: "8m",        sub: "Aetna SRC Plan G · $1,780 AP",          page: "commissions" },
    { kind: "nigo",     t: "NIGO returned · Linda Cho",         d: "1h",        sub: "Sigs missing · Plan N",                  page: "calls" },
    { kind: "coaching", t: "New coaching card",                  d: "2h",        sub: "Open-ended Q drill assigned",            page: "coaching" },
    { kind: "anomaly",  t: "Persistency drift · Tampa",          d: "3h",        sub: "FE 13-mo cohort -3.2pts WoW",           page: "book" },
    { kind: "recruit",  t: "New applicant · Stacy V",            d: "yesterday", sub: "Already licensed in TX",                  page: "recruiting" },
  ];
  // Live notifications: AppData.NOTIFICATIONS, mapped onto the panel shape.
  // Sort unread first, then most recent. Fallback to FALLBACK if empty.
  const fmtDelta = (iso) => {
    if (!iso) return "";
    const ms = Date.now() - new Date(iso).getTime();
    const s = Math.floor(ms / 1000);
    if (s < 60) return `${s}s`;
    const m = Math.floor(s / 60);
    if (m < 60) return `${m}m`;
    const h = Math.floor(m / 60);
    if (h < 24) return `${h}h`;
    const d = Math.floor(h / 24);
    if (d < 7) return `${d}d`;
    return new Date(iso).toLocaleDateString("en-US", { month: "short", day: "numeric" });
  };
  const linkToPage = (link) => {
    if (!link) return null;
    const m = String(link).match(/page=([a-z-]+)/);
    return m ? m[1] : null;
  };
  const live = (AppData.NOTIFICATIONS || []).map(n => ({
    kind: n.kind,
    t: n.title,
    d: fmtDelta(n.createdAt),
    sub: n.body || "",
    page: linkToPage(n.link),
    unread: !n.readAt,
    id: n.id,
  })).sort((a, b) => (a.unread === b.unread) ? 0 : (a.unread ? -1 : 1));
  const items = live.length > 0 ? live : FALLBACK;
  const unreadCount = live.length > 0 ? live.filter(i => i.unread).length : items.length;
  const colorOf = (k) => k === "lead_assigned" || k === "lead" ? "var(--accent-money)" :
                       k === "commission_paid" || k === "issued" ? "var(--accent-money)" :
                       k === "nigo" ? "var(--state-danger)" :
                       k === "tier_promo" ? "var(--accent-money)" :
                       k === "anomaly" ? "var(--state-warning)" :
                       "var(--accent-status)";
  const markAllRead = async () => {
    const sb = window.getSupabase && window.getSupabase();
    if (!sb || live.length === 0) { onClose(); return; }
    const ids = live.filter(i => i.unread).map(i => i.id);
    if (ids.length === 0) { onClose(); return; }
    await sb.from("notifications").update({ read_at: new Date().toISOString() }).in("id", ids);
    window.hydrateFromSupabase && window.hydrateFromSupabase();
    onClose();
  };
  return (
    <div className="slideout-overlay" onClick={onClose}>
      <aside className="slideout" onClick={(e) => e.stopPropagation()} style={{ width: 380 }}>
        <div className="slideout-h">
          <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
            <Icons.Bell size={14}/>
            <div style={{ fontSize: 14, fontWeight: 500 }}>Notifications</div>
            <span className="chip chip-money">{unreadCount}</span>
          </div>
          <div style={{ display: "flex", gap: 4 }}>
            <button className="btn btn-ghost" onClick={markAllRead}>Mark read</button>
            <button className="icon-btn" onClick={onClose}><Icons.X size={14}/></button>
          </div>
        </div>
        <div className="slideout-body" style={{ padding: 0 }}>
          {items.map((n, i) => (
            <div key={i} onClick={() => { goto && goto(n.page); onClose(); }} style={{ display: "flex", gap: 10, padding: "12px 14px", borderBottom: "1px solid var(--border-subtle)", cursor: "pointer" }}>
              <span className="dot" style={{ background: colorOf(n.kind), marginTop: 6 }}></span>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 13, fontWeight: 500 }}>{n.t}</div>
                <div style={{ color: "var(--text-tertiary)", fontSize: 11.5, marginTop: 2 }}>{n.sub}</div>
              </div>
              <span style={{ color: "var(--text-quaternary)", fontSize: 11 }}>{n.d}</span>
            </div>
          ))}
        </div>
      </aside>
    </div>
  );
}

/* ─────────────────────────────────────────────────────────────────────────
   10. Keyboard shortcuts help (?)
   ───────────────────────────────────────────────────────────────────────── */
function ShortcutsHelp({ open, onClose }) {
  if (!open) return null;
  const groups = [
    { title: "Global", items: [
      ["⌘K / Ctrl+K", "Command palette"],
      ["?",            "Shortcut help"],
      ["Esc",           "Close any overlay"],
    ]},
    { title: "Navigation (in palette)", items: [
      ["↑ ↓",  "Move selection"],
      ["Enter", "Open page or run action"],
    ]},
    { title: "On a call", items: [
      ["M",     "Mute / unmute"],
      ["S",     "Send SOA"],
      ["Space", "Pause transcript"],
    ]},
    { title: "Pipeline", items: [
      ["F",   "Filter"],
      ["N",   "New lead"],
      ["1-5", "Move selected lead to stage"],
    ]},
  ];
  return (
    <Shared.Modal title="Keyboard shortcuts" width={520} onClose={onClose} actions={
      <button className="btn btn-primary" onClick={onClose}>Got it</button>
    }>
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 18 }}>
        {groups.map(g => (
          <div key={g.title}>
            <div className="field-l" style={{ marginBottom: 6 }}>{g.title}</div>
            {g.items.map(([k, l]) => (
              <div key={k} style={{ display: "flex", justifyContent: "space-between", padding: "5px 0", fontSize: 12.5 }}>
                <span style={{ color: "var(--text-secondary)" }}>{l}</span>
                <span className="kbd mono">{k}</span>
              </div>
            ))}
          </div>
        ))}
      </div>
    </Shared.Modal>
  );
}

/* Stub fallback retained for unknown page IDs */
function PageStub({ title, sub }) {
  return (
    <div className="page-pad">
      <div className="page-h">
        <div>
          <div className="page-title">{title}</div>
          <div className="page-sub">{sub}</div>
        </div>
      </div>
      <div className="panel" style={{ padding: 36, textAlign: "center", color: "var(--text-tertiary)" }}>
        <Icons.Sparkles size={20} style={{ color: "var(--accent-money)" }}/>
        <div style={{ marginTop: 8, fontSize: 14, fontWeight: 500 }}>Page coming online</div>
        <div style={{ fontSize: 12, marginTop: 4 }}>This view is wired in the data layer; UI ships in the next build.</div>
      </div>
    </div>
  );
}

window.PageVault          = PageVault;
window.PageTiering        = PageTiering;
window.PageCommissions    = PageCommissions;
window.PageTraining       = PageTraining;
/* PageRecruiting moved to page-recruiting.jsx */
window.PageCalls          = PageCalls;
window.PageBook           = PageBook;
window.PageSettings       = PageSettings;
window.PageStub           = PageStub;
window.NotificationsPanel = NotificationsPanel;
window.ShortcutsHelp      = ShortcutsHelp;
