/* Shared atomic components for Repflow */
const { useState, useEffect, useRef, useMemo } = React;

const TierChip = ({ tier, compact }) => (
  <span className={`tier tier-${tier}`}>
    <span className="gem"></span>
    {!compact && AppData.TIER_LABELS[tier]}
  </span>
);

const Avatar = ({ rep, size = 22 }) => {
  const initials = rep.name.split(" ").map(s => s[0]).slice(0, 2).join("");
  return (
    <span className="avatar-xs" style={{ width: size, height: size, fontSize: size * 0.42, background: rep.color }}>
      {initials}
    </span>
  );
};

const Sparkline = ({ data, width = 70, height = 28, color = "var(--accent-money)", neg }) => {
  const max = Math.max(...data), min = Math.min(...data);
  const range = Math.max(1, max - min);
  const pts = data.map((v, i) => {
    const x = (i / (data.length - 1)) * width;
    const y = height - ((v - min) / range) * (height - 4) - 2;
    return [x, y];
  });
  const d = pts.map(([x, y], i) => `${i === 0 ? "M" : "L"} ${x.toFixed(1)} ${y.toFixed(1)}`).join(" ");
  const fill = `${d} L ${width} ${height} L 0 ${height} Z`;
  return (
    <svg className="kpi-spark" width={width} height={height} viewBox={`0 0 ${width} ${height}`}>
      <path d={fill} fill={neg ? "var(--state-danger)" : color} opacity="0.10"/>
      <path d={d} stroke={neg ? "var(--state-danger)" : color} strokeWidth="1.5" fill="none" strokeLinecap="round"/>
    </svg>
  );
};

const KpiCard = ({ label, value, prefix, suffix, sub, trend, hero, spark, neg }) => (
  <div className={`kpi ${hero ? "hero" : ""}`}>
    <div className="kpi-label">{label}</div>
    <div className={`kpi-val tabular money`}>
      {prefix}{value}{suffix && <span style={{ fontSize: "0.55em", color: "var(--text-tertiary)", fontWeight: 500, marginLeft: 4 }}>{suffix}</span>}
    </div>
    {sub && (
      <div className="kpi-meta">
        {trend === "up" && <span className="up tabular"><Icons.TrendingUp size={12}/> {sub}</span>}
        {trend === "dn" && <span className="dn tabular"><Icons.TrendingDown size={12}/> {sub}</span>}
        {!trend && <span className="tabular">{sub}</span>}
      </div>
    )}
    {spark && <Sparkline data={spark} width={hero ? 130 : 70} height={hero ? 56 : 28} neg={neg}/>}
  </div>
);

/* ───── Sidebar ─────
   Pages shared across roles render role-aware variants (driven by `role` prop).
   The NAV map decides which role sees which page in their sidebar. */
const NAV = {
  // Each role's nav is graded by frequency × depth. Daily glance items live
  // at top; workspaces (commissions deep dive, training, etc.) get folded in.
  // Commissions is now context-rebound:
  //   rep    → folded into Today as a Pay tab (glance only)
  //   manager→ standalone "Pay" workspace (downline payouts + override review)
  //   owner  → folded into P&L as a Comp tab (aggregate line item)
  rep: [
    { id: "today",       label: "Today",        icon: "Home" },
    { id: "floor",       label: "Floor",        icon: "Phone",    badge: "47" },
    { id: "messages",    label: "Messages",     icon: "MessageSquare" },
    { id: "leaderboard", label: "Leaderboard",  icon: "Trophy" },
    { id: "library",     label: "Library",      icon: "Book" },
  ],
  manager: [
    { id: "today",       label: "Today",        icon: "Home" },
    { id: "floor",       label: "Floor",        icon: "Phone",  badge: "184" },
    { id: "crm",         label: "CRM",          icon: "Users" },
    { id: "messages",    label: "Messages",     icon: "MessageSquare" },
    { id: "team",        label: "Team",         icon: "Users" },
    { id: "nigo",        label: "NIGO Queue",   icon: "Bell" },
    { id: "recruiting",  label: "Recruiting",   icon: "ArrowUpRight" },
    { id: "pay",         label: "Pay",          icon: "Wallet" },
    { id: "library",     label: "Library",      icon: "Book" },
  ],
  owner: [
    { id: "admin",       label: "Admin",        icon: "Shield" },
    { id: "pnl",         label: "P&L",          icon: "TrendingUp" },
    { id: "org",         label: "Org",          icon: "Users" },
    { id: "book",        label: "Book",         icon: "Activity" },
    { id: "floor",       label: "Floor",        icon: "Phone" },
    { id: "crm",         label: "CRM",          icon: "Users" },
    { id: "quote",       label: "Quote Tool",   icon: "Sparkles" },
    { id: "auto-quoter", label: "Auto Quoter",  icon: "Bolt" },
    { id: "recruiting",  label: "Recruiting",   icon: "ArrowUpRight" },
    { id: "compliance",  label: "Compliance",   icon: "Shield" },
    { id: "resources",   label: "Resources",    icon: "Folder" },
    { id: "library",     label: "Training",     icon: "Book" },
  ],
  ops: [
    { id: "connections", label: "Connections",  icon: "Plug" },
  ],
};

const Sidebar = ({ role, setRole, page, setPage, openCmdK }) => {
  const items = NAV[role];
  return (
    <nav className="sidebar">
      <div className="sb-brand">
        <div className="sb-brand-mark">R</div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div className="sb-brand-name">Repflow</div>
          <div className="sb-brand-meta">Atlas Insurance Group</div>
        </div>
      </div>

      <div className="role-switch">
        {["rep","manager","owner"].map(r => (
          <button key={r} className={role === r ? "active" : ""} onClick={() => setRole(r)}>
            {r === "rep" ? "Rep" : r === "manager" ? "Mgr" : "Owner"}
          </button>
        ))}
      </div>

      <div className="sb-section">Workspace</div>
      <div className="sb-nav">
        {items.map(it => {
          const Ico = Icons[it.icon];
          return (
            <button key={it.id} className={`sb-item ${page === it.id ? "active" : ""}`} onClick={() => setPage(it.id)}>
              <Ico size={15}/>
              <span>{it.label}</span>
              {it.badge && <span className="badge tabular">{it.badge}</span>}
            </button>
          );
        })}
      </div>

      <div className="sb-section">Operations</div>
      <div className="sb-nav">
        {NAV.ops.map(it => {
          const Ico = Icons[it.icon];
          return (
            <button key={it.id} className={`sb-item ${page === it.id ? "active" : ""}`} onClick={() => setPage(it.id)}>
              <Ico size={15}/>
              <span>{it.label}</span>
            </button>
          );
        })}
      </div>

      <div className="sb-spacer"/>

      <div style={{ padding: "0 8px 8px" }}>
        <button className="sb-item" onClick={openCmdK}>
          <Icons.Search size={15}/>
          <span>Command</span>
          <span className="kbd">⌘K</span>
        </button>
      </div>

      <SidebarUser setPage={setPage}/>
    </nav>
  );
};

/* Resolve the actual signed-in viewer instead of hardcoding Marcus Avila /
   Atlanta / platinum. Falls through to REPS[0] only when me() hasn't loaded
   yet (first paint), then re-renders on the me:loaded event. */
const SidebarUser = ({ setPage }) => {
  const [, force] = useState(0);
  useEffect(() => {
    const fn = () => force(n => n + 1);
    window.addEventListener("me:loaded", fn);
    window.addEventListener("data:hydrated", fn);
    return () => {
      window.removeEventListener("me:loaded", fn);
      window.removeEventListener("data:hydrated", fn);
    };
  }, []);
  const meIdent = (typeof window !== "undefined" && window.me && window.me()) || null;
  const repRow = (meIdent?.rep_id && AppData.REPS?.find(r => r.id === meIdent.rep_id))
              || AppData.REPS?.[0]
              || { name: "—", color: "var(--text-tertiary)" };
  const name = meIdent?.full_name || repRow.name || "—";
  const tier = meIdent?.tier || repRow.tier || "bronze";
  const cityChip = meIdent?.agency_name ? `· ${meIdent.agency_name}` : (repRow.city ? `· ${repRow.city}` : "");
  return (
    <div className="sb-user">
      <Avatar rep={repRow} size={26}/>
      <div className="sb-user-info">
        <div className="sb-user-name">{name}</div>
        <div className="sb-user-role">
          <TierChip tier={tier} compact/>
          {cityChip && <span>{cityChip}</span>}
        </div>
      </div>
      <button className="icon-btn" onClick={() => setPage("settings")} title="Settings"><Icons.Settings size={14}/></button>
    </div>
  );
};

/* ───── Topbar ───── */
const LiveBadge = () => {
  const live = AppData.LIVE;
  return (
    <span className={`live-badge ${live ? "on" : "off"}`} title={live ? "Reading live data from Supabase" : "Showing demo data — Supabase not connected or empty"}>
      <span className="dot"></span>
      {live ? "live" : "demo"}
    </span>
  );
};

const AccountChip = () => {
  const [open, setOpen] = React.useState(false);
  const [me, setMe]     = React.useState(typeof window !== "undefined" && window.me ? window.me() : null);
  React.useEffect(() => {
    const onLoad = (e) => setMe(e.detail || (window.me && window.me()));
    window.addEventListener("me:loaded", onLoad);
    return () => window.removeEventListener("me:loaded", onLoad);
  }, []);
  const inDemo = (() => { try { return sessionStorage.getItem("repflow.demo") === "1"; } catch { return false; } })();
  const isAuthed = !!(me && me.authenticated && !me.is_demo);
  const label = isAuthed
    ? (me.full_name || me.handle || me.agency_name || "Account")
    : (inDemo ? "Demo" : "Guest");
  const sub = isAuthed ? me.agency_name : (inDemo ? "Read-only · Atlas seed" : "Not signed in");

  const tone = isAuthed ? "var(--accent-money)" : (inDemo ? "var(--accent-status)" : "var(--text-tertiary)");
  return (
    <div style={{ position: "relative" }}>
      <button
        className="lb-pill"
        title={`${label} · ${sub}`}
        onClick={() => setOpen(o => !o)}
        style={{ display: "inline-flex", alignItems: "center", gap: 6, color: tone, borderColor: `color-mix(in oklch, ${tone} 35%, transparent)` }}
      >
        <span style={{ width: 6, height: 6, borderRadius: 999, background: tone, display: "inline-block" }}></span>
        <span style={{ maxWidth: 140, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{label}</span>
        <Icons.ChevronRight size={11} style={{ transform: open ? "rotate(90deg)" : "rotate(0)", transition: "transform 120ms" }}/>
      </button>
      {open && (
        <div
          onMouseLeave={() => setOpen(false)}
          style={{
            position: "absolute", top: "calc(100% + 6px)", right: 0, minWidth: 240,
            background: "var(--bg-raised)", border: "1px solid var(--border-subtle)",
            borderRadius: 8, padding: 10, zIndex: 50,
            boxShadow: "0 12px 32px color-mix(in oklch, black 35%, transparent)"
          }}>
          <div style={{ fontSize: 12, color: "var(--text-secondary)", fontWeight: 600 }}>{label}</div>
          <div style={{ fontSize: 11, color: "var(--text-tertiary)", marginTop: 2 }}>{sub}</div>
          {isAuthed && me.role && (
            <div style={{ marginTop: 6, fontSize: 10.5 }}>
              <span className="chip">{me.role}</span>
              {me.tier && <span className="chip" style={{ marginLeft: 4 }}>{me.tier}</span>}
            </div>
          )}
          <div style={{ borderTop: "1px solid var(--border-subtle)", margin: "10px -10px 0", padding: "8px 10px 0" }}>
            {isAuthed ? (
              <button className="btn btn-ghost" style={{ width: "100%", justifyContent: "flex-start", fontSize: 12 }} onClick={() => window.signOut && window.signOut()}>
                <Icons.X size={11}/> Sign out
              </button>
            ) : (
              <button className="btn btn-primary" style={{ width: "100%", justifyContent: "center", fontSize: 12 }} onClick={() => {
                try { sessionStorage.removeItem("repflow.demo"); } catch {}
                window.location.reload();
              }}>
                <Icons.Send size={11}/> Sign in
              </button>
            )}
          </div>
        </div>
      )}
    </div>
  );
};

const Topbar = ({ crumbs, aep, openCmdK, toggleRail, railOn, openMobile, openNotifications, openSettings, notifCount }) => (
  <div className="topbar">
    <div className="crumbs">
      {crumbs.map((c, i) => (
        <React.Fragment key={i}>
          {i > 0 && <span className="sep"><Icons.ChevronRight size={12}/></span>}
          <span className={i === crumbs.length - 1 ? "here" : ""}>{c}</span>
        </React.Fragment>
      ))}
    </div>
    <LiveBadge/>
    <AccountChip/>
    {window.AgencySwitcher && (() => { const A = window.AgencySwitcher; return <A/>; })()}
    <div className="topbar-spacer"/>
    {aep && (
      <div className="aep-pill"><span className="dot"></span>AEP SURGE · Day 14 / 54</div>
    )}
    <button className="cmdk-trigger" onClick={openCmdK}>
      <Icons.Search size={13}/>
      <span>Search or run a command</span>
      <span className="kbd">⌘K</span>
    </button>
    <button className="lb-pill">
      <Icons.Trophy size={13} style={{ color: "var(--accent-status)" }}/>
      <span className="rank tabular">#3</span>
      <span className="delta-up tabular"><Icons.ArrowUp size={10}/>2</span>
    </button>
    <button className="icon-btn" onClick={openMobile} title="Open rep mobile prototype">
      <Icons.Phone size={15}/>
    </button>
    <button className="icon-btn" onClick={toggleRail} title="Toggle AI co-pilot">
      <Icons.Sparkles size={15} style={{ color: railOn ? "var(--accent-money)" : undefined }}/>
    </button>
    <button className="icon-btn" onClick={openNotifications} title="Notifications" style={{ position: "relative" }}>
      <Icons.Bell size={15}/>
      {notifCount > 0 && <span style={{ position: "absolute", top: 4, right: 4, width: 7, height: 7, borderRadius: "50%", background: "var(--accent-heat)", boxShadow: "0 0 0 2px var(--bg-base)" }}></span>}
    </button>
    {openSettings && (
      <button className="icon-btn" onClick={openSettings} title="Settings">
        <Icons.Settings size={15}/>
      </button>
    )}
  </div>
);

/* ───── Cmd K ───── */
const CMD_ITEMS = {
  Actions: [
    { label: "Dial next lead in queue",                       kbd: "D", icon: "Phone",    nav: "queue" },
    { label: "Send SOA to current lead",                      kbd: "S", icon: "Shield",   nav: "vault" },
    { label: "Log a sale",                                    kbd: "L", icon: "Wallet",   nav: "commissions" },
    { label: "Schedule callback",                                       icon: "Calendar", nav: "today" },
    { label: "Draft rebuttal: 'I already have coverage'",               icon: "Sparkles" },
  ],
  Navigate: [
    { label: "Today",              icon: "Home",         nav: "today" },
    { label: "Pipeline",           icon: "Pipeline",     nav: "pipeline" },
    { label: "Dial Queue",         icon: "Phone",        nav: "queue" },
    { label: "Calls",              icon: "Headset",      nav: "calls" },
    { label: "Leaderboard",        icon: "Trophy",       nav: "leaderboard" },
    { label: "Performance · standings + tiering + forecast", icon: "Trophy", nav: "performance" },
    { label: "Commissions",        icon: "Wallet",       nav: "commissions" },
    { label: "Training",           icon: "Book",         nav: "training" },
    { label: "Compliance Vault",   icon: "Shield",       nav: "vault" },
    { label: "Resources · scrub tool + carriers + links", icon: "Folder", nav: "resources" },
    { label: "Recruiting Funnel",  icon: "ArrowUpRight", nav: "recruiting" },
    { label: "P&L",                icon: "TrendingUp",   nav: "pnl" },
    { label: "Org Tree",           icon: "Users",        nav: "tree" },
    { label: "Book Analytics",     icon: "Activity",     nav: "book" },
    { label: "Lead Vendors · ROI", icon: "Wallet",       nav: "attribution" },
    { label: "NIGO Queue",         icon: "Bell",         nav: "nigo" },
    { label: "Connections",        icon: "Plug",         nav: "connections" },
    { label: "Admin",              icon: "Shield",       nav: "admin" },
    { label: "Settings",           icon: "Settings",     nav: "settings" },
  ],
  "Ask Repflow": [
    { label: "Show leads I haven't touched in 7 days",            icon: "Sparkles", nav: "pipeline" },
    { label: "Compare my conversion vs Tony's, last month",       icon: "Sparkles", nav: "leaderboard" },
    { label: "Why did Cheryl Hampton's policy charge back?",      icon: "Sparkles", nav: "calls" },
  ],
};

const CmdK = ({ open, onClose, goto }) => {
  const [q, setQ] = useState("");
  const [sel, setSel] = useState(0);
  const inputRef = useRef();
  useEffect(() => { if (open) { setQ(""); setSel(0); setTimeout(() => inputRef.current?.focus(), 60); } }, [open]);

  // Unified search: builds a virtual dataset from leads + scripts + docs +
  // quick links + reps + carriers — a query "Cheryl" returns Cheryl Hampton's
  // pipeline row + every doc/script that mentions her. Pages stay top of list.
  const dataset = useMemo(() => {
    const items = [];
    Object.entries(CMD_ITEMS).forEach(([sec, list]) => {
      list.forEach(i => items.push({ ...i, sec, _kind: "page" }));
    });
    if (q && q.length >= 2) {
      const ql = q.toLowerCase();
      const safeStartsWith = (s) => (s || "").toLowerCase().includes(ql);
      // Leads
      (window.AppData?.PIPELINE || []).slice(0, 200).forEach(p => {
        if (safeStartsWith(p.lead) || safeStartsWith(p.product) || safeStartsWith(p.source) || safeStartsWith(p.state)) {
          items.push({ label: `${p.lead} · ${p.product || "—"}`, sec: "Leads", icon: "Phone", _kind: "lead", _payload: p,
            sub: `${p.stage} · ${p.state || "—"} · owner ${p.owner || "—"}` });
        }
      });
      // Reps
      (window.AppData?.REPS || []).forEach(r => {
        if (safeStartsWith(r.name) || safeStartsWith(r.handle) || safeStartsWith(r.tier)) {
          items.push({ label: r.name, sec: "Reps", icon: "Users", _kind: "rep", _payload: r,
            sub: `${r.handle} · ${r.tier?.toUpperCase()}` });
        }
      });
      // Scripts
      (window.AppData?.SCRIPTS_LIB || []).forEach(s => {
        if (safeStartsWith(s.title) || safeStartsWith(s.body) || safeStartsWith(s.cat)) {
          items.push({ label: s.title, sec: "Scripts", icon: "FileText", _kind: "script", _payload: s,
            sub: `${s.cat} · ${s.version || ""}` });
        }
      });
      // Docs
      (window.AppData?.DOCS || []).forEach(d => {
        if (safeStartsWith(d.title) || safeStartsWith(d.cat) || safeStartsWith(d.text)) {
          items.push({ label: d.title, sec: "Docs", icon: "Folder", _kind: "doc", _payload: d,
            sub: `${d.cat} · ${d.kind || "link"}` });
        }
      });
      // Quick links
      (window.AppData?.QUICK_LINKS || []).forEach(l => {
        if (safeStartsWith(l.label) || safeStartsWith(l.cat) || safeStartsWith(l.url)) {
          items.push({ label: l.label, sec: "Links", icon: "Bookmark", _kind: "link", _payload: l,
            sub: l.cat });
        }
      });
      // Carriers
      (window.AppData?.CARRIERS || []).forEach(c => {
        if (safeStartsWith(c.name) || safeStartsWith(c.category)) {
          items.push({ label: c.name, sec: "Carriers", icon: "Shield", _kind: "carrier", _payload: c,
            sub: c.category });
        }
      });
    }
    return items;
  }, [q]);

  const flat = useMemo(() => {
    if (!q) return dataset.filter(i => i._kind === "page");
    const ql = q.toLowerCase();
    return dataset.filter(i => i.label.toLowerCase().includes(ql) || (i.sub || "").toLowerCase().includes(ql)).slice(0, 50);
  }, [dataset, q]);

  const run = (it) => {
    if (!it) return onClose();
    if (it._kind === "page" && it.nav && goto) { goto(it.nav); onClose(); return; }
    if (it._kind === "lead")    { goto && goto("crm");     window.dispatchEvent(new CustomEvent("crm:focusLead", { detail: it._payload })); onClose(); return; }
    if (it._kind === "rep")     { goto && goto("team");                                                                                    onClose(); return; }
    if (it._kind === "script")  { goto && goto("library"); window.dispatchEvent(new CustomEvent("library:openScript", { detail: it._payload })); onClose(); return; }
    if (it._kind === "doc")     { if (it._payload?.url) window.open(it._payload.url, "_blank"); else { goto && goto("library"); } onClose(); return; }
    if (it._kind === "link")    { if (it._payload?.url) window.open(it._payload.url, "_blank"); onClose(); return; }
    if (it._kind === "carrier") { goto && goto("library"); onClose(); return; }
    if (it.nav && goto)         { goto(it.nav); }
    onClose();
  };

  useEffect(() => {
    if (!open) return;
    const onKey = (e) => {
      if (e.key === "ArrowDown") { e.preventDefault(); setSel(s => Math.min(flat.length - 1, s + 1)); }
      else if (e.key === "ArrowUp") { e.preventDefault(); setSel(s => Math.max(0, s - 1)); }
      else if (e.key === "Enter") { e.preventDefault(); run(flat[sel]); }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [open, flat, sel]);

  if (!open) return null;
  const grouped = flat.reduce((acc, it) => { (acc[it.sec] ||= []).push(it); return acc; }, {});

  return (
    <div className="cmdk-overlay" onClick={onClose}>
      <div className="cmdk" onClick={(e) => e.stopPropagation()}>
        <input ref={inputRef} className="cmdk-input" value={q} onChange={(e) => { setQ(e.target.value); setSel(0); }} placeholder="Type a command, ask anything..." onKeyDown={(e) => e.key === "Escape" && onClose()}/>
        <div style={{ maxHeight: "52vh", overflowY: "auto" }}>
          {Object.entries(grouped).map(([sec, items]) => (
            <div key={sec} className="cmdk-section">
              <div className="cmdk-section-title">{sec}</div>
              {items.map((it, i) => {
                const Ico = Icons[it.icon] || Icons.ArrowRight;
                const idx = flat.indexOf(it);
                return (
                  <div key={i} className={`cmdk-item ${idx === sel ? "sel" : ""}`} onMouseEnter={() => setSel(idx)} onClick={() => run(it)}>
                    <Ico size={14} style={{ color: "var(--text-tertiary)" }}/>
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div style={{ fontSize: 12.5, fontWeight: 500 }}>{it.label}</div>
                      {it.sub && <div style={{ fontSize: 10.5, color: "var(--text-tertiary)", marginTop: 1 }}>{it.sub}</div>}
                    </div>
                    {it.kbd && <span className="kbd">{it.kbd}</span>}
                  </div>
                );
              })}
            </div>
          ))}
          {flat.length === 0 && <div style={{ padding: "20px", color: "var(--text-tertiary)", textAlign: "center", fontSize: 12.5 }}>No matches</div>}
        </div>
      </div>
    </div>
  );
};

/* ───── AI Rail (functional — calls /api/copilot which proxies to Gemini) ───── */
const SUGGESTIONS_BY_PAGE = {
  pnl:          ["Which downline is dragging persistency below 80%?", "What's my biggest leak in the P&L this month?", "If I cut the worst-performing lead source, what's the net impact?"],
  pipeline:     ["Show me leads I haven't touched in 7 days", "Which deals are most likely to close this week?", "Why is this deal stuck in 'App In'?"],
  queue:        ["Which lead in the queue should I dial first and why?", "Draft a 30-second opener for the top scored lead", "Which producers are hottest right now?"],
  leaderboard:  ["Compare my conversion vs Tony's last month", "What's the gap between #1 and #2 this month?"],
  performance:  ["Who would qualify for Diamond if MTD threshold dropped to $45k?", "Which producers are most at risk of missing tier this month?", "What's our 30-day weighted forecast vs goal?"],
  team:         ["Who's at risk of missing tier this month?", "Which producer needs a coaching nudge today?"],
  coaching:     ["Top 3 issues across all producer calls this week", "Which coaching theme is moving the needle most?"],
  vault:        ["Are any artifacts approaching retention expiry?", "Audit pack for Aetna SRC — what's missing?"],
  resources:    ["Which carrier portal needs the credentials reset this month?", "Add a quick link for the new training URL"],
  recruiting:   ["Which campaign has the lowest cost per producer?", "Draft a follow-up DM for {{handle}} based on their reply"],
  commissions:  ["Where's my biggest variance vs carrier statements this month?"],
  book:          ["Which carrier mix segment has the best persistency?"],
  default:       ["Summarize what's on this page", "What should I focus on right now?", "What changed since yesterday?"],
};

function pageKeyFromContext(context) {
  if (!context) return "default";
  const c = String(context).toLowerCase();
  if (c.includes("p&l") || c.includes("pnl")) return "pnl";
  if (c.includes("pipeline")) return "pipeline";
  if (c.includes("queue") || c.includes("dispatch")) return "queue";
  if (c.includes("performance") || c.includes("standings")) return "performance";
  if (c.includes("leaderboard")) return "leaderboard";
  if (c.includes("team")) return "team";
  if (c.includes("coaching")) return "coaching";
  if (c.includes("vault")) return "vault";
  if (c.includes("resources")) return "resources";
  if (c.includes("tiering")) return "performance";
  if (c.includes("forecast")) return "performance";
  if (c.includes("recruit")) return "recruiting";
  if (c.includes("commission")) return "commissions";
  if (c.includes("book")) return "book";
  return "default";
}

const AIRail = ({ context }) => {
  const [val, setVal]       = useState("");
  const [history, setHist]  = useState([]); // [{role, text, ms}]
  const [busy, setBusy]     = useState(false);
  const bottomRef            = useRef();

  useEffect(() => { bottomRef.current?.scrollIntoView({ behavior: "smooth" }); }, [history.length, busy]);

  // External pages can dispatch CustomEvent('ai:ask', { detail: { prompt, context }})
  // to seed the rail with a prompt and auto-fire it.
  useEffect(() => {
    const onAsk = (e) => { const p = e.detail?.prompt; if (p) ask(p); };
    window.addEventListener("ai:ask", onAsk);
    return () => window.removeEventListener("ai:ask", onAsk);
  }, [busy]);

  const ask = async (prompt) => {
    if (!prompt.trim() || busy) return;
    setHist(h => [...h, { role: "user", text: prompt }]);
    setVal("");
    setBusy(true);
    try {
      // If signed in, forward the Supabase JWT so the Edge fn can fetch live data
      // under authenticated RLS. Demo mode just sends no token.
      let jwt = null;
      const sb = window.getSupabase && window.getSupabase();
      if (sb) {
        const { data } = await sb.auth.getSession();
        jwt = data?.session?.access_token || null;
      }
      const headers = { "content-type": "application/json" };
      if (jwt) headers["x-supabase-auth"] = `Bearer ${jwt}`;
      // Pass last 3 turns so the copilot has short-term memory for vague
      // follow-ups like "what do you need?" / "??". Each turn = {q, a}.
      const recent = [];
      for (let i = hist.length - 1; i >= 0 && recent.length < 3; i--) {
        const m = hist[i];
        if (m.role === "assistant" && i > 0 && hist[i-1]?.role === "user") {
          recent.unshift({ q: hist[i-1].text || "", a: m.text || "" });
        }
      }
      const resp = await fetch("/api/copilot", {
        method: "POST",
        headers,
        body: JSON.stringify({ prompt, context, history: recent })
      });
      const j = await resp.json();
      if (!resp.ok) throw new Error(j.error + (j.detail ? " — " + j.detail.slice(0, 200) : ""));
      setHist(h => [...h, { role: "assistant", text: j.text, ms: j.ms, model: j.model, tools: j.tools_used }]);
    } catch (e) {
      setHist(h => [...h, { role: "assistant", text: "Couldn't reach the model. " + (e.message || ""), ms: 0, err: true }]);
    } finally {
      setBusy(false);
    }
  };

  const suggestions = SUGGESTIONS_BY_PAGE[pageKeyFromContext(context)] || SUGGESTIONS_BY_PAGE.default;

  return (
    <aside className="airail">
      <div className="airail-h">
        <Icons.Sparkles size={14} style={{ color: "var(--accent-money)" }}/>
        <span className="title">Co-pilot</span>
        <span className="meta">{context}</span>
        {history.length > 0 && <button className="icon-btn" onClick={() => setHist([])} title="Clear"><Icons.X size={12}/></button>}
      </div>
      <div className="airail-body">
        {history.length === 0 && (
          <>
            <div style={{ padding: 14, fontSize: 12, color: "var(--text-tertiary)", lineHeight: 1.5 }}>
              Ask anything about <strong style={{ color: "var(--text-primary)" }}>{context}</strong>. I see your current page and can pull from your data.
            </div>
            <div style={{ padding: "0 14px 14px", display: "flex", flexDirection: "column", gap: 6 }}>
              {suggestions.map((s, i) => (
                <button key={i} className="btn btn-ghost" style={{ justifyContent: "flex-start", padding: "8px 10px", fontSize: 12, textAlign: "left", whiteSpace: "normal", height: "auto", lineHeight: 1.4 }} onClick={() => ask(s)}>
                  <Icons.Sparkles size={11} style={{ color: "var(--accent-money)", flex: "0 0 auto" }}/>
                  <span>{s}</span>
                </button>
              ))}
            </div>
          </>
        )}
        {history.map((m, i) => (
          <div key={i} className={`ai-msg ${m.role === "assistant" ? "assistant" : ""}`}>
            <div className="who">
              {m.role === "user" ? <><Avatar rep={AppData.REPS[0]} size={16}/> You</> : <><Icons.Sparkles size={11} style={{ color: "var(--accent-money)" }}/> Repflow{m.ms ? ` · ${(m.ms/1000).toFixed(1)}s` : ""}{m.tools?.length ? ` · queried ${m.tools.join(", ")}` : ""}</>}
            </div>
            <div className="body" style={{ whiteSpace: "pre-wrap", color: m.err ? "var(--state-danger)" : undefined }}>{m.text}</div>
          </div>
        ))}
        {busy && (
          <div className="ai-msg assistant">
            <div className="who"><Icons.Sparkles size={11} style={{ color: "var(--accent-money)" }}/> Repflow · thinking...</div>
            <div className="body" style={{ display: "flex", gap: 4 }}>
              <span className="ai-dot"></span><span className="ai-dot"></span><span className="ai-dot"></span>
            </div>
          </div>
        )}
        <div ref={bottomRef}></div>
      </div>
      <div className="airail-foot">
        <div style={{ display: "flex", gap: 6 }}>
          <input
            className="airail-input"
            value={val}
            onChange={(e) => setVal(e.target.value)}
            placeholder="Ask anything, or hold ⌥ to dictate"
            onKeyDown={(e) => e.key === "Enter" && !e.shiftKey && (e.preventDefault(), ask(val))}
            disabled={busy}
          />
          <button className="icon-btn" onClick={() => ask(val)} disabled={busy || !val.trim()} style={{ background: "var(--bg-raised)" }}><Icons.Send size={14}/></button>
        </div>
      </div>
    </aside>
  );
};

/* ───── Modal + form primitives (used by Pipeline filter, New-lead, Bulk-assign) ───── */
const Modal = ({ title, children, onClose, actions, width = 460 }) => {
  useEffect(() => {
    const onKey = (e) => { if (e.key === "Escape") onClose && onClose(); };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [onClose]);
  return (
    <div className="cmdk-overlay" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()} style={{ width }}>
        <div className="modal-h">
          <div className="modal-t">{title}</div>
          <button className="icon-btn" onClick={onClose}><Icons.X size={14}/></button>
        </div>
        <div className="modal-body">{children}</div>
        {actions && <div className="modal-foot">{actions}</div>}
      </div>
    </div>
  );
};

const Field = ({ label, children, hint }) => (
  <label className="field">
    <span className="field-l">{label}</span>
    {children}
    {hint && <span className="field-h">{hint}</span>}
  </label>
);

const Select = ({ value, onChange, options }) => (
  <select className="text-input" value={value} onChange={(e) => onChange(e.target.value)}>
    {options.map((o, i) => <option key={i} value={o.v ?? o.value}>{o.l ?? o.label}</option>)}
  </select>
);

/* Section pill — horizontal liquid-glass tabs for combining related pages
   within one screen. Use as: <SectionPill items={[{k:"a",l:"All"},...]}
   value={tab} onChange={setTab}/> */
const SectionPill = ({ items, value, onChange, dense }) => (
  <div className="section-pill" style={dense ? { margin: "0 0 8px" } : undefined}>
    {items.map(it => (
      <button key={it.k} className={value === it.k ? "active" : ""} onClick={() => onChange(it.k)}>
        {it.icon && Icons[it.icon] ? React.createElement(Icons[it.icon], { size: 11, style: { marginRight: 4, verticalAlign: "middle" } }) : null}
        {it.l}
        {it.badge != null && <span className="badge tabular" style={{ marginLeft: 6, fontSize: 9.5 }}>{it.badge}</span>}
      </button>
    ))}
  </div>
);

/* Validation helpers — every form should reach for these instead of trusting
   the input element. Phone uses a permissive E.164 (10-15 digits, optional +).
   Age clamps to 0-120 to catch typos like 999. ZIP is 5 or 5+4. */
const Validate = {
  phone(v) {
    if (!v) return { ok: true,  msg: "" };
    const cleaned = String(v).replace(/[\s\-().]/g, "");
    return /^\+?[1-9]\d{9,14}$/.test(cleaned)
      ? { ok: true, msg: "" }
      : { ok: false, msg: "Phone must be E.164 (10-15 digits, optional +)" };
  },
  age(v) {
    if (v === "" || v == null) return { ok: true, msg: "" };
    const n = Number(v);
    if (!Number.isFinite(n))   return { ok: false, msg: "Age must be a number" };
    if (n < 0 || n > 120)      return { ok: false, msg: "Age must be 0-120" };
    return { ok: true, msg: "" };
  },
  zip(v) {
    if (!v) return { ok: true, msg: "" };
    return /^\d{5}(-\d{4})?$/.test(String(v).trim())
      ? { ok: true, msg: "" }
      : { ok: false, msg: "ZIP must be 5 digits (or 5+4)" };
  },
  state(v) {
    if (!v) return { ok: true, msg: "" };
    return /^[A-Z]{2}$/.test(String(v).trim().toUpperCase())
      ? { ok: true, msg: "" }
      : { ok: false, msg: "State must be 2-letter code" };
  },
  money(v) {
    if (v === "" || v == null) return { ok: true, msg: "" };
    const n = Number(String(v).replace(/[^0-9.-]/g, ""));
    if (!Number.isFinite(n)) return { ok: false, msg: "Must be a number" };
    if (n < 0)               return { ok: false, msg: "Cannot be negative" };
    return { ok: true, msg: "" };
  },
};

/* ValidatedInput — text-input that shows inline error tone + message when the
   value fails its kind's check. Use as drop-in: <ValidatedInput kind="phone"
   value={...} onChange={...}/>. */
const ValidatedInput = ({ kind, value, onChange, className = "text-input", ...rest }) => {
  const v = Validate[kind] ? Validate[kind](value) : { ok: true, msg: "" };
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 2 }}>
      <input className={className} value={value || ""} onChange={onChange} {...rest}
        style={{ ...(rest.style || {}), borderColor: !v.ok && value ? "var(--state-danger)" : undefined }}/>
      {!v.ok && value && (
        <span style={{ fontSize: 10.5, color: "var(--state-danger)" }}>{v.msg}</span>
      )}
    </div>
  );
};

/* React class error boundary — wraps page content so a single throwing
   component doesn't blank the whole app. Logs to console + offers a reset. */
class ErrorBoundary extends React.Component {
  constructor(props) { super(props); this.state = { err: null }; }
  static getDerivedStateFromError(err) { return { err }; }
  componentDidCatch(err, info) {
    console.error("[ErrorBoundary]", err, info?.componentStack);
    if (window.toast) window.toast(`UI error: ${err?.message || err}`, "error");
  }
  render() {
    if (!this.state.err) return this.props.children;
    return (
      <div style={{ padding: 24, margin: 14, background: "var(--bg-raised)", border: "1px solid color-mix(in oklch, var(--state-danger) 35%, transparent)", borderRadius: 8 }}>
        <div style={{ fontSize: 13, fontWeight: 600, color: "var(--state-danger)", marginBottom: 8 }}>This panel hit an error.</div>
        <div style={{ fontSize: 11.5, color: "var(--text-tertiary)", fontFamily: "var(--font-mono, monospace)", marginBottom: 12, whiteSpace: "pre-wrap" }}>{String(this.state.err?.message || this.state.err)}</div>
        <button className="btn" onClick={() => this.setState({ err: null })}>Try again</button>
      </div>
    );
  }
}

/* Skeleton row — drop-in for any list/table during initial Supabase hydrate.
   Animates a shimmer; honors prefers-reduced-motion. */
const Skeleton = ({ height = 14, width = "100%", radius = 4, count = 1, gap = 8 }) => {
  const item = (
    <div style={{
      height, width, borderRadius: radius,
      background: "linear-gradient(90deg, var(--bg-raised) 0%, var(--bg-overlay) 50%, var(--bg-raised) 100%)",
      backgroundSize: "200% 100%",
      animation: "shimmer 1.4s ease-in-out infinite",
    }}/>
  );
  if (count === 1) return item;
  return (
    <div style={{ display: "flex", flexDirection: "column", gap }}>
      {Array.from({ length: count }).map((_, i) => <div key={i}>{item}</div>)}
    </div>
  );
};

/* Agency timezone resolver — every page that displays "Today" should use this
   instead of `new Date()` so distributed teams don't see different totals.
   Reads me().agency_timezone (column added by future migration when needed),
   falls back to the agency's first-rep state mapping, finally browser local.
   Also exposes formatInTz(date) for formatting rolled-up calendar dates. */
const AgencyTime = (() => {
  const STATE_TO_TZ = {
    AL: "America/Chicago",     AK: "America/Anchorage",  AZ: "America/Phoenix",
    AR: "America/Chicago",     CA: "America/Los_Angeles",CO: "America/Denver",
    CT: "America/New_York",    DE: "America/New_York",   FL: "America/New_York",
    GA: "America/New_York",    HI: "Pacific/Honolulu",   ID: "America/Boise",
    IL: "America/Chicago",     IN: "America/Indianapolis",IA: "America/Chicago",
    KS: "America/Chicago",     KY: "America/New_York",   LA: "America/Chicago",
    ME: "America/New_York",    MD: "America/New_York",   MA: "America/New_York",
    MI: "America/Detroit",     MN: "America/Chicago",    MS: "America/Chicago",
    MO: "America/Chicago",     MT: "America/Denver",     NE: "America/Chicago",
    NV: "America/Los_Angeles", NH: "America/New_York",   NJ: "America/New_York",
    NM: "America/Denver",      NY: "America/New_York",   NC: "America/New_York",
    ND: "America/Chicago",     OH: "America/New_York",   OK: "America/Chicago",
    OR: "America/Los_Angeles", PA: "America/New_York",   RI: "America/New_York",
    SC: "America/New_York",    SD: "America/Chicago",    TN: "America/Chicago",
    TX: "America/Chicago",     UT: "America/Denver",     VT: "America/New_York",
    VA: "America/New_York",    WA: "America/Los_Angeles",WV: "America/New_York",
    WI: "America/Chicago",     WY: "America/Denver",
  };
  function resolve() {
    const m = window.me && window.me();
    if (m?.agency_timezone)   return m.agency_timezone;
    if (m?.agency_state && STATE_TO_TZ[m.agency_state]) return STATE_TO_TZ[m.agency_state];
    return Intl.DateTimeFormat().resolvedOptions().timeZone || "America/New_York";
  }
  return {
    resolve,
    todayStr() {
      try { return new Date().toLocaleDateString("en-CA", { timeZone: resolve() }); }
      catch { return new Date().toISOString().slice(0, 10); }
    },
    format(d, opts) {
      try { return new Date(d).toLocaleString("en-US", { timeZone: resolve(), ...(opts || {}) }); }
      catch { return new Date(d).toLocaleString(); }
    },
  };
})();

/* Demo agency guard — every fallback to seed/sample data must check this so
   a real signed-in agency never sees Atlas / Cheryl / Marcus content.
   Real agencies hit empty states with import/add CTAs instead. */
const DEMO_AGENCY_ID = "e0a68c9f-cf48-47b0-bef7-dba3f27db0b9";
const isDemoAgency = () => {
  const m = window.me && window.me();
  if (!m) return true;                                  // pre-auth = treat as demo
  if (m.is_demo === true) return true;
  if (m.agency_id && String(m.agency_id) === DEMO_AGENCY_ID) return true;
  return false;
};

window.Shared = { TierChip, Avatar, Sparkline, KpiCard, Sidebar, Topbar, CmdK, AIRail, NAV, Modal, Field, Select, SectionPill, Validate, ValidatedInput, ErrorBoundary, Skeleton, AgencyTime, isDemoAgency, DEMO_AGENCY_ID };
window.isDemoAgency = isDemoAgency;
window.AgencyTime = AgencyTime;
