// CR Report — the Pipeline Report's sibling, scoped to re-engagement.
// Universe: cr_pipeline (scraper v1.38 "CR Pipeline": HS 5.0–9.0, CS 2.0–9.0,
// Private, $10M+ sales), cross-referenced at render time against crs (CR
// touch history) by harvey_target_id. Per-company CR Action / Owner / Due
// persist in cr_picks (one row per harvey_id).
// Visual system: "Vault Sleek Fintech Light" — matches the Action Dashboard
// re-brand (2026-07-16 design) and the CR Report design concept (2026-07-17).

(function () {
const { useState, useEffect, useMemo, useCallback, useRef } = React;

const CV = {
  canvas: "#EDF1F6", ink: "#142B44", body: "#33445A", sec: "#5E6E82",
  muted: "#8493A6", kicker: "#94A3B5",
  accent: "#2E77C2", accentDeep: "#1C5C9E", over: "#D6456A", good: "#1F9D57",
  mono: "var(--f-mono)",
  panel: { background: "#FFFFFF", border: "1px solid rgba(14,42,71,.08)", borderRadius: 16,
           boxShadow: "0 1px 2px rgba(14,42,71,.05), 0 8px 24px rgba(14,42,71,.04)" },
  kickerStyle: { fontFamily: "var(--f-mono)", fontSize: 10, letterSpacing: ".14em",
                 textTransform: "uppercase", color: "#94A3B5" },
};
const CV_GRAD = `linear-gradient(180deg,${CV.accent},${CV.accentDeep})`;
function hexA(hex, a) {
  const h = hex.replace("#", "");
  return `rgba(${parseInt(h.slice(0, 2), 16)},${parseInt(h.slice(2, 4), 16)},${parseInt(h.slice(4, 6), 16)},${a})`;
}

const ACTION_META = {
  "High Importance":   { color: "#D6456A" },
  "Standard Approach": { color: "#2E77C2" },
  "Tradeshow":         { color: "#7C5CD0" },
};
const ACTION_ORDER = { "High Importance": 0, "Standard Approach": 1, "Tradeshow": 2, "": 3 };

const COLUMNS = [
  { key: "company",        label: "Company",          align: "left"  },
  { key: "sales",          label: "Sales",            align: "right" },
  { key: "ebitda",         label: "EBITDA",           align: "right" },
  { key: "status",         label: "Status",           align: "right" },
  { key: "highStatus",     label: "High Status",      align: "right" },
  { key: "lastAction",     label: "Last Action",      align: "left"  },
  { key: "lastActionDate", label: "Last Action Date", align: "center" },
  { key: "crAction",       label: "CR Action",        align: "left"  },
  { key: "owner",          label: "Owner",            align: "left"  },
  { key: "due",            label: "Due",              align: "right" },
];

function fmtM(n) {
  if (n == null || isNaN(n)) return null;
  const m = n >= 100000 ? n / 1e6 : n; // normalize raw dollars vs $M
  return "$" + (m % 1 === 0 ? m.toFixed(0) : m.toFixed(1)) + "M";
}
function fmtMonDay(d) {
  if (!d) return "";
  return new Date(String(d).slice(0, 10) + "T00:00:00Z")
    .toLocaleDateString("en-US", { month: "short", day: "numeric", timeZone: "UTC" }).toUpperCase();
}
// m/d/yy — last-action dates can be a year+ old; month+day alone misreads.
function fmtMonDayYr(d) {
  const m = String(d || "").match(/^(\d{4})-(\d{2})-(\d{2})/);
  if (!m) return "";
  return `${parseInt(m[2], 10)}/${parseInt(m[3], 10)}/${m[1].slice(2)}`;
}
function localTodayStr() {
  const d = new Date();
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
}
function initialsOf(name) {
  return String(name || "?").replace(/[^A-Za-z ]/g, "").split(/\s+/).filter(Boolean)
    .slice(0, 2).map(w => w[0].toUpperCase()).join("") || "?";
}

function StatusChip({ value }) {
  const n = parseFloat(value);
  const c = isNaN(n) ? CV.muted : n >= 8 ? CV.good : n >= 6.5 ? CV.accent : CV.muted;
  return (
    <span style={{ fontFamily: CV.mono, fontSize: 12, fontWeight: 500, color: c,
      background: hexA(c, .1), border: "1px solid " + hexA(c, .28), padding: "2px 8px", borderRadius: 6 }}>
      {isNaN(n) ? "—" : n.toFixed(2)}
    </span>
  );
}

// CR Action cell: button + popover with the three actions + clear.
function CRActionCell({ pick, onSet, readOnly }) {
  const [open, setOpen] = useState(false);
  const meta = pick && pick.crAction ? ACTION_META[pick.crAction] : null;
  const color = meta ? meta.color : CV.muted;
  const isDone = !!(pick && pick.done);
  const label = pick && pick.crAction ? (isDone ? "✓ " : "") + pick.crAction : "— Set action";
  useEffect(() => {
    if (!open) return;
    const close = () => setOpen(false);
    document.addEventListener("mousedown", close);
    return () => document.removeEventListener("mousedown", close);
  }, [open]);
  return (
    <div style={{ position: "relative", minWidth: 0 }} onMouseDown={e => e.stopPropagation()}>
      <button onClick={() => !readOnly && setOpen(o => !o)}
        style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8, width: "100%",
          border: "1px solid " + (meta ? hexA(color, .35) : "rgba(14,42,71,.12)"),
          background: meta ? hexA(color, .07) : "#FFFFFF",
          borderRadius: 9, padding: "6px 10px", cursor: readOnly ? "default" : "pointer",
          fontFamily: "inherit", fontSize: 12, color: meta ? CV.ink : CV.muted, minWidth: 0,
          opacity: isDone ? 0.55 : 1, textDecoration: isDone ? "line-through" : "none" }}
        title={isDone ? "Worked — checked off on the Action Dashboard. Pick an action to re-open." : ""}>
        <span style={{ display: "flex", alignItems: "center", gap: 7, minWidth: 0 }}>
          <span style={{ width: 8, height: 8, borderRadius: "50%", flexShrink: 0, background: color,
            boxShadow: meta ? "0 0 8px " + hexA(color, .55) : "none" }}/>
          <span style={{ whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{label}</span>
        </span>
        {!readOnly && <span style={{ fontSize: 9, color: CV.muted, flexShrink: 0 }}>▾</span>}
      </button>
      {open && (
        <div style={{ position: "absolute", top: "calc(100% + 6px)", left: 0, width: 220, zIndex: 20,
          background: "#FFFFFF", border: "1px solid rgba(14,42,71,.12)", borderRadius: 12,
          boxShadow: "0 12px 32px rgba(14,42,71,.16)", padding: 6, display: "flex", flexDirection: "column", gap: 2 }}>
          {Object.keys(ACTION_META).map(a => (
            <button key={a} onClick={() => { onSet(a, true); setOpen(false); }}
              style={{ display: "flex", alignItems: "center", gap: 9, border: "none", background: pick && pick.crAction === a ? hexA(ACTION_META[a].color, .08) : "transparent",
                borderRadius: 8, padding: "8px 10px", cursor: "pointer", fontFamily: "inherit", fontSize: 12.5, color: CV.ink, textAlign: "left" }}>
              <span style={{ width: 8, height: 8, borderRadius: "50%", flexShrink: 0, background: ACTION_META[a].color,
                boxShadow: "0 0 8px " + hexA(ACTION_META[a].color, .55) }}/>
              {a}
            </button>
          ))}
          <button onClick={() => { onSet(null, true); setOpen(false); }}
            style={{ display: "flex", alignItems: "center", gap: 9, border: "none", background: "transparent",
              borderRadius: 8, padding: "8px 10px", cursor: "pointer", fontFamily: "inherit", fontSize: 12.5, color: CV.muted, textAlign: "left" }}>
            <span style={{ width: 8, height: 8, borderRadius: "50%", flexShrink: 0, background: CV.muted }}/>
            — Unassigned
          </button>
        </div>
      )}
    </div>
  );
}

// Owner cell: the app-wide Avatar (same tones/initials as every other page);
// click to reassign via a select. resolvedId = pick owner, else the Harvey
// analyst name matched to a Vault person.
function OwnerCell({ ownerId, resolvedId, fallbackName, people, onSet, readOnly }) {
  const [editing, setEditing] = useState(false);
  const F = window.VAULT_FIRM;
  const displayId = ownerId || resolvedId || null;
  const person = displayId && F.PEOPLE_BY_ID ? F.PEOPLE_BY_ID[displayId] : null;
  if (editing && !readOnly) {
    return (
      <select autoFocus value={ownerId || ""}
        onBlur={() => setEditing(false)}
        onChange={e => { onSet(e.target.value || null); setEditing(false); }}
        style={{ width: "100%", padding: "5px 6px", border: "1px solid rgba(14,42,71,.14)", borderRadius: 8,
          fontFamily: "inherit", fontSize: 12, background: "#FFFFFF", color: CV.ink }}>
        <option value="">{fallbackName ? "(Harvey: " + fallbackName + ")" : "—"}</option>
        {people.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}
      </select>
    );
  }
  return (
    <div onClick={() => !readOnly && setEditing(true)}
      title={(person ? person.name : (fallbackName || "")) + (readOnly ? "" : " — click to reassign")}
      style={{ display: "flex", alignItems: "center", justifyContent: "center", minWidth: 0, cursor: readOnly ? "default" : "pointer" }}>
      {person ? (
        <window.Avatar id={displayId}/>
      ) : (
        <span style={{ width: 24, height: 24, borderRadius: "50%", flexShrink: 0, background: "rgba(14,42,71,.08)",
          display: "flex", alignItems: "center", justifyContent: "center",
          fontFamily: CV.mono, fontSize: 9, fontWeight: 600, color: CV.muted }}>{initialsOf(fallbackName)}</span>
      )}
    </div>
  );
}

// Due cell: an always-visible date input — the affordance IS the control.
function DueCell({ dueDate, onSet, readOnly, today }) {
  const overdue = dueDate && dueDate < today;
  return (
    <div style={{ display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 2 }}>
      <input type="date" value={dueDate || ""} disabled={readOnly}
        onChange={e => onSet(e.target.value || null)}
        title={dueDate ? "Due date" : "Set a due date"}
        style={{ width: "100%", padding: "4px 7px", borderRadius: 8,
          border: "1px solid " + (overdue ? hexA(CV.over, .45) : "rgba(14,42,71,.16)"),
          background: overdue ? hexA(CV.over, .05) : "#FFFFFF",
          fontFamily: CV.mono, fontSize: 11, color: overdue ? CV.over : (dueDate ? CV.ink : CV.muted),
          fontWeight: overdue ? 700 : 400, cursor: readOnly ? "default" : "pointer" }}/>
      {overdue && <span style={{ fontFamily: CV.mono, fontSize: 9, letterSpacing: ".08em", color: CV.over }}>OVERDUE</span>}
    </div>
  );
}

function CRReportScreen({ user }) {
  const F = window.VAULT_FIRM;
  const PEOPLE_BY_ID = (F && F.PEOPLE_BY_ID) || {};
  const [rows, setRows] = useState([]);
  const [picks, setPicks] = useState([]);
  const [touches, setTouches] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [refreshKey, setRefreshKey] = useState(0);
  const reload = useCallback(() => setRefreshKey(k => k + 1), []);

  const [filter, setFilter] = useState("all");   // all | High Importance | Standard Approach | Tradeshow | unassigned
  const [ownerFilter, setOwnerFilter] = useState("all"); // all | person id | "none"
  const [search, setSearch] = useState("");
  // Building sorts: ordered levels, e.g. [Sales desc, Last Action Date asc].
  // Click a header = set primary; SHIFT-click = stack another level.
  const [sortStack, setSortStack] = useState([{ key: "sales", dir: "desc" }]);
  // Per-column VALUE filters (distinct-value checklists; ported from Pipeline).
  // {key: [values]} — absent/empty = no filter. These refine the view like
  // search does; KPI tiles + pills stay owner-scoped only.
  const [colFilters, setColFilters] = useState({});
  const [openFilter, setOpenFilter] = useState(null);
  const filterPopRef = React.useRef(null);
  useEffect(() => {
    if (!openFilter) return;
    const onDoc = (e) => { if (filterPopRef.current && !filterPopRef.current.contains(e.target)) setOpenFilter(null); };
    document.addEventListener("mousedown", onDoc);
    return () => document.removeEventListener("mousedown", onDoc);
  }, [openFilter]);
  const FILTERABLE = { status: true, highStatus: true, lastAction: true };
  const colFilterVal = (r, key) => {
    if (key === "status") return r.currentStatus != null ? Number(r.currentStatus).toFixed(2) : "—";
    if (key === "highStatus") return r.highStatus != null ? Number(r.highStatus).toFixed(2) : "—";
    if (key === "lastAction") return r.lastAction || "—";
    return null;
  };
  const [colWidths, setColWidths] = useState({
    company: 200, sales: 78, ebitda: 78, status: 78, highStatus: 84, lastAction: 104,
    lastActionDate: 96, crAction: 168, owner: 56, due: 100,
  });
  const [crModalOpen, setCrModalOpen] = useState(false);
  const rzRef = useRef(null);
  const today = localTodayStr();

  // Column resize (design behavior: drag handles, 56px floor)
  useEffect(() => {
    const onMove = (e) => {
      const rz = rzRef.current;
      if (!rz) return;
      const w = Math.max(56, rz.startW + (e.clientX - rz.startX));
      setColWidths(s => ({ ...s, [rz.key]: w }));
    };
    const onUp = () => { if (rzRef.current) { rzRef.current = null; document.body.style.userSelect = ""; } };
    window.addEventListener("mousemove", onMove);
    window.addEventListener("mouseup", onUp);
    return () => { window.removeEventListener("mousemove", onMove); window.removeEventListener("mouseup", onUp); };
  }, []);
  const startResize = (key) => (e) => {
    e.preventDefault(); e.stopPropagation();
    rzRef.current = { key, startX: e.clientX, startW: colWidths[key] };
    document.body.style.userSelect = "none";
  };

  useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        setError(null);
        const [univ, pk, tch] = await Promise.all([
          window.VaultAPI.listCRPipeline(),
          window.VaultAPI.listCRPicks().catch(() => []),
          window.VaultAPI.listCRTouchesByTarget().catch(() => []),
        ]);
        if (cancelled) return;
        setRows(univ);
        setPicks(pk);
        setTouches(tch);
      } catch (e) {
        if (!cancelled) setError(e.message || String(e));
      } finally {
        if (!cancelled) setLoading(false);
      }
    })();
    return () => { cancelled = true; };
  }, [refreshKey]);

  const pickById = useMemo(() => new Map(picks.map(p => [String(p.harveyId), p])), [picks]);
  const crHistById = useMemo(() => {
    const m = new Map();
    touches.forEach(t => {
      const cur = m.get(t.harveyTargetId) || { count: 0, last: null };
      cur.count += 1;
      if (t.date && (!cur.last || t.date > cur.last)) cur.last = t.date;
      m.set(t.harveyTargetId, cur);
    });
    return m;
  }, [touches]);

  const modulePeople = useMemo(() => {
    // Same team derivation the Action Dashboard uses: team, else subteam sans "st-".
    const myId = ((user && user.personId) || "").replace(/^p_/, "").replace(/_gmail$/, "");
    const me = PEOPLE_BY_ID[myId] || null;
    const teamOf = (p) => p.team || (p.subteam ? p.subteam.replace(/^st-/, "") : null);
    const myTeam = me ? teamOf(me) : null;
    const list = ((F && F.PEOPLE) || Object.values(PEOPLE_BY_ID)).filter(p => p && (!myTeam || teamOf(p) === myTeam));
    return list.sort((a, b) => (a.name || "").localeCompare(b.name || ""));
  }, []);

  // Harvey analyst name -> Vault person id, so unassigned rows still attribute
  const personIdByName = useMemo(() => {
    const m = new Map();
    Object.values(PEOPLE_BY_ID).forEach(p => { if (p && p.name) m.set(p.name.trim().toLowerCase(), p.id); });
    return m;
  }, []);

  // Enrich + filter + sort
  const enriched = useMemo(() => rows.map((r, i) => {
    const pick = pickById.get(String(r.harveyId)) || null;
    const hist = crHistById.get(String(r.harveyId)) || null;
    const raw = (r.analystName || "").trim();
    const ownerKey = (pick && pick.ownerId)
      || (PEOPLE_BY_ID[raw] ? raw : null)                               // Harvey username == Vault id (e.g. "jscheftz")
      || (PEOPLE_BY_ID[raw.toLowerCase()] ? raw.toLowerCase() : null)
      || personIdByName.get(raw.toLowerCase())                          // full-name fallback
      || null;
    return { ...r, pick, hist, ownerKey, _idx: i };
  }), [rows, pickById, crHistById, personIdByName]);

  // Excluded companies leave the working universe entirely (KPIs, pills,
  // counts) and live on the "Excluded" view until restored. The flag rides on
  // cr_picks, so it survives every master re-import.
  const active = useMemo(() => enriched.filter(r => !(r.pick && r.pick.excluded)), [enriched]);
  const excludedRows = useMemo(() => enriched.filter(r => r.pick && r.pick.excluded), [enriched]);

  // Pool for KPI tiles + action-pill counts: respects the OWNER filter (so
  // "View Grant" shows Grant's figures), but not the action filter itself
  // (the tiles ARE the action breakdown) nor transient search.
  const ownerPool = useMemo(() => {
    if (ownerFilter === "none") return active.filter(r => !r.ownerKey);
    if (ownerFilter !== "all") return active.filter(r => r.ownerKey === ownerFilter);
    return active;
  }, [active, ownerFilter]);

  const counts = useMemo(() => {
    const c = { all: ownerPool.length, "High Importance": 0, "Standard Approach": 0, "Tradeshow": 0, unassigned: 0, excluded: 0 };
    ownerPool.forEach(r => {
      const a = r.pick && r.pick.crAction;
      if (a && c[a] != null) c[a] += 1; else c.unassigned += 1;
    });
    c.excluded = excludedRows.filter(r =>
      ownerFilter === "all" ? true : ownerFilter === "none" ? !r.ownerKey : r.ownerKey === ownerFilter
    ).length;
    return c;
  }, [ownerPool, excludedRows, ownerFilter]);

  const ownerCounts = useMemo(() => {
    const c = new Map();
    let none = 0;
    active.forEach(r => { if (r.ownerKey) c.set(r.ownerKey, (c.get(r.ownerKey) || 0) + 1); else none += 1; });
    return { byId: c, none };
  }, [active]);

  const visible = useMemo(() => {
    let out = filter === "excluded" ? excludedRows : active;
    if (ownerFilter === "none") out = out.filter(r => !r.ownerKey);
    else if (ownerFilter !== "all") out = out.filter(r => r.ownerKey === ownerFilter);
    if (filter === "unassigned") out = out.filter(r => !(r.pick && r.pick.crAction));
    else if (filter !== "all" && filter !== "excluded") out = out.filter(r => r.pick && r.pick.crAction === filter);
    for (const [k, vals] of Object.entries(colFilters)) {
      if (!vals || !vals.length) continue;
      out = out.filter(r => vals.includes(colFilterVal(r, k)));
    }
    const q = search.trim().toLowerCase();
    if (q) out = out.filter(r => (r.target || "").toLowerCase().includes(q));
    const val = (r, sortKey) => {
      switch (sortKey) {
        case "company": return (r.target || "").toLowerCase();
        case "sales": return r.revenue != null ? Number(r.revenue) : null;
        case "ebitda": return r.ebitda != null ? Number(r.ebitda) : null;
        case "status": return r.currentStatus != null ? r.currentStatus : null;
        case "highStatus": return r.highStatus != null ? r.highStatus : null;
        case "lastAction": return (r.lastAction || "").toLowerCase() || null;
        case "lastActionDate": return r.lastActionDate || null;
        case "crAction": return ACTION_ORDER[(r.pick && r.pick.crAction) || ""];
        case "owner": {
          const p = r.pick && r.pick.ownerId ? PEOPLE_BY_ID[r.pick.ownerId] : null;
          return ((p ? p.name : r.analystName) || "").toLowerCase();
        }
        case "due": return (r.pick && r.pick.dueDate) || "9999-12-31";
        default: return 0;
      }
    };
    return [...out].sort((a, b) => {
      for (const lvl of sortStack) {
        const dir = lvl.dir === "asc" ? 1 : -1;
        const va = val(a, lvl.key), vb = val(b, lvl.key);
        if (va == null && vb == null) continue;
        if (va == null) return 1;   // missing values sink, both directions
        if (vb == null) return -1;
        if (va < vb) return -1 * dir;
        if (va > vb) return 1 * dir;
      }
      return 0;
    });
  }, [active, excludedRows, filter, ownerFilter, search, sortStack, colFilters]);

  const defaultDirFor = (key) => (key === "company" || key === "lastAction" || key === "owner" ? "asc" : "desc");
  const setSort = (key, additive) => {
    setSortStack(prev => {
      const i = prev.findIndex(l => l.key === key);
      if (additive) {
        if (i >= 0) { const n = [...prev]; n[i] = { ...n[i], dir: n[i].dir === "asc" ? "desc" : "asc" }; return n; }
        return [...prev, { key, dir: defaultDirFor(key) }];
      }
      if (i === 0) return [{ key, dir: prev[0].dir === "asc" ? "desc" : "asc" }, ...prev.slice(1)];
      return [{ key, dir: defaultDirFor(key) }];
    });
  };

  const patchPick = useCallback(async (harveyId, patch) => {
    // Optimistic local merge, then persist; reload on failure.
    setPicks(prev => {
      const i = prev.findIndex(p => String(p.harveyId) === String(harveyId));
      if (i >= 0) { const next = [...prev]; next[i] = { ...next[i], ...patch }; return next; }
      return [...prev, { harveyId, crAction: null, ownerId: null, dueDate: null, ...patch }];
    });
    try { await window.VaultAPI.upsertCRPick(harveyId, patch); }
    catch (e) { window.VaultUI && window.VaultUI.toast("error", "Failed to save: " + (e.message || e)); reload(); }
  }, [reload]);

  const gridCols = COLUMNS.map(c => colWidths[c.key] + "px").join(" ") + " 34px";
  const filterDefs = [
    { label: "All", val: "all" },
    { label: "High Importance", val: "High Importance" },
    { label: "Standard Approach", val: "Standard Approach" },
    { label: "Tradeshow", val: "Tradeshow" },
    { label: "Unassigned", val: "unassigned" },
    { label: "Excluded", val: "excluded" },
  ];

  return (
    <div style={{ padding: "20px 28px 60px", minHeight: "100vh", color: CV.body,
      background: "radial-gradient(1100px 620px at 100% -12%, rgba(46,119,194,.07), transparent 58%), " + CV.canvas }}>

      {/* Header */}
      <div style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 24, marginBottom: 24, flexWrap: "wrap" }}>
        <div>
          <h1 style={{ margin: 0, fontSize: 27, fontWeight: 600, letterSpacing: "-.01em", color: CV.ink }}>CR Report</h1>
          <div style={{ marginTop: 7, fontFamily: CV.mono, fontSize: 12, letterSpacing: ".02em", color: CV.sec }}>
            Custom Re-approach Universe · High-Status 5.0–9.0 · Private · $10M+ Sales
          </div>
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: 12, flexShrink: 0 }}>
          <button onClick={() => setCrModalOpen(true)}
            style={{ border: "none", cursor: "pointer", fontFamily: "inherit", fontSize: 13, fontWeight: 600,
              color: "#EAF3FF", padding: "10px 16px", borderRadius: 11, background: CV_GRAD,
              boxShadow: "0 6px 16px rgba(28,92,158,.3)", whiteSpace: "nowrap" }}>+ New CR</button>
        </div>
      </div>

      {loading && <div style={{ padding: 40, textAlign: "center", fontFamily: CV.mono, fontSize: 12, color: CV.muted, ...CV.panel }}>Loading CR universe…</div>}
      {error && (
        <div style={{ padding: "22px 24px", ...CV.panel }}>
          <div style={{ fontSize: 14, fontWeight: 600, color: CV.over }}>Couldn't load the CR universe.</div>
          <div style={{ fontFamily: CV.mono, fontSize: 11, color: CV.muted, marginTop: 6 }}>{error}</div>
          <div style={{ fontSize: 12.5, color: CV.sec, marginTop: 10 }}>
            If the <span style={{ fontFamily: CV.mono }}>cr_pipeline</span> table doesn't exist yet, run the DDL, scrape with v1.38, and import via Data Uploads.
          </div>
          <button onClick={reload} style={{ marginTop: 12, border: "1px solid rgba(14,42,71,.12)", background: "#FFFFFF",
            color: CV.sec, borderRadius: 9, cursor: "pointer", fontSize: 12, padding: "7px 14px", fontFamily: "inherit" }}>Retry</button>
        </div>
      )}

      {!loading && !error && (<>
        {/* KPI tiles */}
        <div className="kpi-grid" style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 12, marginBottom: 20 }}>
          {[
            { kicker: "CR Universe", value: ownerPool.length, color: CV.accent },
            { kicker: "High Importance", value: counts["High Importance"], color: "#D6456A" },
            { kicker: "Standard Approach", value: counts["Standard Approach"], color: "#2E77C2" },
            { kicker: "Tradeshow", value: counts["Tradeshow"], color: "#7C5CD0" },
          ].map(k => (
            <div key={k.kicker} style={{ padding: "16px 18px", borderRadius: 14, background: "#FFFFFF",
              border: "1px solid rgba(14,42,71,.08)", boxShadow: "0 1px 2px rgba(14,42,71,.05)",
              display: "flex", alignItems: "center", gap: 13 }}>
              <span style={{ width: 9, height: 9, borderRadius: "50%", background: k.color, boxShadow: "0 0 9px " + hexA(k.color, .55), flexShrink: 0 }}/>
              <div>
                <div style={{ fontFamily: CV.mono, fontWeight: 600, fontSize: 24, color: CV.ink, lineHeight: 1 }}>{k.value}</div>
                <div style={{ ...CV.kickerStyle, letterSpacing: ".12em", marginTop: 6 }}>{k.kicker}</div>
              </div>
            </div>
          ))}
        </div>

        {/* Owner filter pills — module people with rows, same avatars as everywhere else */}
        <div style={{ display: "flex", flexWrap: "wrap", gap: 8, marginBottom: 10 }}>
          {(() => {
            const pillBase = (active) => active
              ? { display: "inline-flex", alignItems: "center", gap: 7, border: "none", cursor: "pointer",
                  fontFamily: "inherit", fontSize: 12, fontWeight: 600, color: "#EAF3FF", padding: "5px 13px 5px 6px",
                  borderRadius: 999, background: CV_GRAD, boxShadow: "0 4px 12px rgba(46,119,194,.35)" }
              : { display: "inline-flex", alignItems: "center", gap: 7, cursor: "pointer", fontFamily: "inherit",
                  fontSize: 12, fontWeight: 500, color: CV.sec, padding: "5px 13px 5px 6px", borderRadius: 999,
                  background: "#FFFFFF", border: "1px solid rgba(14,42,71,.12)" };
            const countBadge = (active) => ({ fontFamily: CV.mono, fontSize: 10, padding: "1px 6px", borderRadius: 6,
              color: active ? "rgba(255,255,255,.85)" : CV.muted,
              background: active ? "rgba(255,255,255,.2)" : "rgba(14,42,71,.05)" });
            const owners = modulePeople; // standard team pills — exist with or without rows/assignments
            return (
              <>
                <button onClick={() => setOwnerFilter("all")}
                  style={{ ...pillBase(ownerFilter === "all"), padding: "7px 13px" }}>
                  All Owners
                  <span style={countBadge(ownerFilter === "all")}>{enriched.length}</span>
                </button>
                {owners.map(p => {
                  const active = ownerFilter === p.id;
                  return (
                    <button key={p.id} onClick={() => setOwnerFilter(active ? "all" : p.id)} style={pillBase(active)}>
                      <window.Avatar id={p.id}/>
                      {p.name.split(" ")[0]}
                      <span style={countBadge(active)}>{ownerCounts.byId.get(p.id) || 0}</span>
                    </button>
                  );
                })}
                {ownerCounts.none > 0 && (
                  <button onClick={() => setOwnerFilter(ownerFilter === "none" ? "all" : "none")}
                    style={{ ...pillBase(ownerFilter === "none"), padding: "7px 13px" }}>
                    Unowned
                    <span style={countBadge(ownerFilter === "none")}>{ownerCounts.none}</span>
                  </button>
                )}
              </>
            );
          })()}
        </div>

        {/* Filter pills + search */}
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16, marginBottom: 16, flexWrap: "wrap" }}>
          <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
            {filterDefs.map(f => {
              const active = filter === f.val;
              return (
                <button key={f.val} onClick={() => setFilter(f.val)}
                  style={active
                    ? { display: "inline-flex", alignItems: "center", gap: 7, border: "none", cursor: "pointer",
                        fontFamily: "inherit", fontSize: 12, fontWeight: 600, color: "#EAF3FF", padding: "7px 13px",
                        borderRadius: 999, background: CV_GRAD, boxShadow: "0 4px 12px rgba(46,119,194,.35)" }
                    : { display: "inline-flex", alignItems: "center", gap: 7, cursor: "pointer", fontFamily: "inherit",
                        fontSize: 12, fontWeight: 500, color: CV.sec, padding: "7px 13px", borderRadius: 999,
                        background: "#FFFFFF", border: "1px solid rgba(14,42,71,.12)" }}>
                  {f.label}
                  <span style={{ fontFamily: CV.mono, fontSize: 10, padding: "1px 6px", borderRadius: 6,
                    color: active ? "rgba(255,255,255,.85)" : CV.muted,
                    background: active ? "rgba(255,255,255,.2)" : "rgba(14,42,71,.05)" }}>{counts[f.val]}</span>
                </button>
              );
            })}
          </div>
          <input value={search} onChange={e => setSearch(e.target.value)} placeholder="Search companies…"
            style={{ width: 240, background: "#FFFFFF", border: "1px solid rgba(14,42,71,.12)", borderRadius: 10,
              padding: "9px 14px", fontFamily: CV.mono, fontSize: 12, color: CV.ink, outline: "none" }}/>
        </div>

        {/* Active column-filter chips */}
        {Object.entries(colFilters).some(([, v]) => v && v.length) && (
          <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 10, flexWrap: "wrap" }}>
            <span style={{ ...CV.kickerStyle }}>Filter</span>
            {Object.entries(colFilters).filter(([, v]) => v && v.length).map(([k, vals]) => {
              const col = COLUMNS.find(c => c.key === k);
              return (
                <span key={k} style={{ display: "inline-flex", alignItems: "center", gap: 6, fontFamily: CV.mono, fontSize: 11,
                  color: "#1F9D57", background: "rgba(31,157,87,.08)", border: "1px solid rgba(31,157,87,.28)",
                  borderRadius: 999, padding: "4px 9px" }}>
                  <span>{(col ? col.label : k)}: {vals.length <= 2 ? vals.join(", ") : vals.length + " values"}</span>
                  <span style={{ cursor: "pointer", opacity: .6 }} title="Clear this filter"
                    onClick={() => setColFilters(prev => ({ ...prev, [k]: [] }))}>×</span>
                </span>
              );
            })}
          </div>
        )}

        {/* Building-sort chips */}
        {sortStack.length > 1 && (
          <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 10, flexWrap: "wrap" }}>
            <span style={{ ...CV.kickerStyle }}>Sort</span>
            {sortStack.map((l, i) => {
              const col = COLUMNS.find(c => c.key === l.key);
              return (
                <span key={l.key} style={{ display: "inline-flex", alignItems: "center", gap: 6, fontFamily: CV.mono, fontSize: 11,
                  color: CV.accent, background: hexA(CV.accent, .08), border: "1px solid " + hexA(CV.accent, .25),
                  borderRadius: 999, padding: "4px 9px" }}>
                  <span style={{ opacity: .6 }}>{i + 1}</span>
                  <span style={{ cursor: "pointer" }} title="Flip direction"
                    onClick={() => setSortStack(prev => prev.map(x => x.key === l.key ? { ...x, dir: x.dir === "asc" ? "desc" : "asc" } : x))}>
                    {(col ? col.label : l.key)} {l.dir === "asc" ? "▲" : "▼"}
                  </span>
                  <span style={{ cursor: "pointer", opacity: .6 }} title="Remove level"
                    onClick={() => setSortStack(prev => prev.length > 1 ? prev.filter(x => x.key !== l.key) : prev)}>×</span>
                </span>
              );
            })}
            <span style={{ fontFamily: CV.mono, fontSize: 10, color: CV.muted }}>Shift-click headers to stack</span>
          </div>
        )}

        {/* Table */}
        <div className="tbl-wrap" style={{ ...CV.panel, padding: "6px 16px 14px", overflowX: "auto" }}>
          <div style={{ display: "grid", gridTemplateColumns: gridCols, columnGap: 12, alignItems: "center",
            padding: "14px 4px", borderBottom: "1px solid rgba(14,42,71,.1)", minWidth: "fit-content" }}>
            {COLUMNS.map(c => {
              const lvlIdx = sortStack.findIndex(l => l.key === c.key);
              const sortOn = lvlIdx >= 0;   // renamed from `active` — was shadowing the universe memo
              const arrow = sortOn ? (sortStack[lvlIdx].dir === "asc" ? "▲" : "▼") : "↕";
              return (
                <div key={c.key} style={{ position: "relative", minWidth: 0, justifySelf: "stretch" }}>
                  <button onClick={(e) => setSort(c.key, e.shiftKey)} title="Click: sort · Shift-click: add as a sort level"
                    style={{ display: "flex", alignItems: "center", gap: 5, width: "100%", border: "none", background: "transparent",
                      cursor: "pointer", fontFamily: CV.mono, fontSize: 10, fontWeight: 600, letterSpacing: ".1em",
                      textTransform: "uppercase", color: sortOn ? CV.accent : CV.kicker,
                      justifyContent: c.align === "right" ? "flex-end" : c.align === "center" ? "center" : "flex-start" }}>
                    {c.label}
                    <span style={sortOn
                      ? { fontSize: 8, color: CV.accent, textShadow: "0 0 6px rgba(46,119,194,.6)" }
                      : { fontSize: 8, color: "#B7C0CC" }}>{arrow}</span>
                    {sortOn && sortStack.length > 1 && (
                      <span style={{ fontFamily: CV.mono, fontSize: 8, color: CV.accent, background: hexA(CV.accent, .12), borderRadius: 4, padding: "0 3px" }}>{lvlIdx + 1}</span>
                    )}
                  </button>
                  {FILTERABLE[c.key] && (() => {
                    const sel = colFilters[c.key] || [];
                    const has = sel.length > 0;
                    return (
                      <button onClick={(e) => { e.stopPropagation(); setOpenFilter(f => f === c.key ? null : c.key); }}
                        title={"Filter " + c.label}
                        style={{ position: "absolute", top: "50%", transform: "translateY(-50%)",
                          right: c.align === "right" ? "auto" : 2, left: c.align === "right" ? -4 : "auto",
                          border: "none", background: has ? hexA(CV.accent, .12) : "transparent",
                          color: has ? CV.accent : "#B7C0CC", borderRadius: 5, width: 16, height: 16,
                          cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", padding: 0 }}>
                        <svg width="9" height="9" viewBox="0 0 12 12" fill="currentColor"><path d="M1 2h10l-4 5v3l-2 1V7L1 2z"/></svg>
                      </button>
                    );
                  })()}
                  {openFilter === c.key && (() => {
                    const opts = (() => {
                      const m = new Map();
                      active.forEach(r => { const v = colFilterVal(r, c.key); m.set(v, (m.get(v) || 0) + 1); });
                      const arr = [...m.entries()];
                      const numeric = c.key !== "lastAction";
                      arr.sort((a, b) => numeric
                        ? (parseFloat(b[0]) || -1) - (parseFloat(a[0]) || -1)
                        : String(a[0]).localeCompare(String(b[0])));
                      return arr;
                    })();
                    const sel = colFilters[c.key] || [];
                    const toggle = (v) => setColFilters(prev => {
                      const cur = prev[c.key] || [];
                      const next = cur.includes(v) ? cur.filter(x => x !== v) : [...cur, v];
                      return { ...prev, [c.key]: next };
                    });
                    return (
                      <div ref={filterPopRef} style={{ position: "absolute", top: "calc(100% + 10px)",
                        [c.align === "right" ? "right" : "left"]: 0, zIndex: 40, minWidth: 190, maxHeight: 300, overflowY: "auto",
                        background: "#FFFFFF", borderRadius: 14, border: "1px solid rgba(14,42,71,.1)",
                        boxShadow: "0 12px 34px rgba(14,42,71,.16)", padding: "10px 12px" }}>
                        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 7 }}>
                          <span style={{ ...CV.kickerStyle }}>{c.label}</span>
                          {sel.length > 0 && (
                            <span onClick={() => setColFilters(prev => ({ ...prev, [c.key]: [] }))}
                              style={{ fontFamily: CV.mono, fontSize: 10, color: CV.accent, cursor: "pointer" }}>Clear</span>
                          )}
                        </div>
                        {opts.map(([v, n]) => (
                          <label key={v} style={{ display: "flex", alignItems: "center", gap: 8, padding: "4px 2px",
                            cursor: "pointer", fontSize: 12.5, color: CV.body }}>
                            <input type="checkbox" checked={sel.includes(v)} onChange={() => toggle(v)}
                              style={{ accentColor: CV.accent, width: 13, height: 13, cursor: "pointer" }}/>
                            <span style={{ flex: 1, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{v}</span>
                            <span style={{ fontFamily: CV.mono, fontSize: 10, color: CV.muted }}>{n}</span>
                          </label>
                        ))}
                      </div>
                    );
                  })()}
                  <div onMouseDown={startResize(c.key)} title="Drag to resize"
                    style={{ position: "absolute", top: -14, right: -7, height: "calc(100% + 28px)", width: 13,
                      display: "flex", alignItems: "center", justifyContent: "center", cursor: "col-resize", zIndex: 6 }}>
                    <span style={{ width: 2, height: 15, borderRadius: 2, background: "rgba(14,42,71,.16)" }}/>
                  </div>
                </div>
              );
            })}
            <div/>
          </div>

          {visible.length === 0 && (
            <div style={{ padding: "34px 8px", textAlign: "center", fontFamily: CV.mono, fontSize: 11, color: CV.muted }}>
              {enriched.length === 0 ? "The universe is empty — scrape with v1.38 and import to cr_pipeline." : "Nothing matches this filter."}
            </div>
          )}

          {visible.map((r) => {
            const overdueDue = r.pick && r.pick.dueDate && r.pick.dueDate < today;
            return (
              <div key={r.harveyId} style={{ display: "grid", gridTemplateColumns: gridCols, columnGap: 12,
                alignItems: "center", padding: "13px 4px", borderBottom: "1px solid rgba(14,42,71,.06)", minWidth: "fit-content" }}>
                <div style={{ display: "flex", alignItems: "center", gap: 8, minWidth: 0 }}>
                  <window.HarveyLink companyId={r.harveyId} companyName={r.target}
                    style={{ fontSize: 14, fontWeight: 600, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", minWidth: 0 }}>
                    {r.target}
                  </window.HarveyLink>
                  {r.hist && r.hist.count > 0 && (
                    <span title={r.hist.count + " CR touch" + (r.hist.count !== 1 ? "es" : "") + (r.hist.last ? " · last " + fmtMonDayYr(r.hist.last) : "")}
                      style={{ flexShrink: 0, fontFamily: CV.mono, fontSize: 9, letterSpacing: ".1em", padding: "2px 6px", borderRadius: 6,
                        color: "#1993C4", background: "rgba(25,147,196,.12)", border: "1px solid rgba(25,147,196,.3)" }}>
                      CR{r.hist.count > 1 ? " ×" + r.hist.count : ""}
                    </span>
                  )}
                </div>
                <div style={{ fontFamily: CV.mono, fontSize: 13, color: CV.ink, textAlign: "right" }}>{fmtM(r.revenue) || "—"}</div>
                <div style={{ fontFamily: CV.mono, fontSize: 13, color: r.ebitda != null ? CV.ink : "rgba(14,42,71,.25)", textAlign: "right" }}>{fmtM(r.ebitda) || "—"}</div>
                <div style={{ textAlign: "right" }}><StatusChip value={r.currentStatus}/></div>
                <div style={{ textAlign: "right" }} title="High-water status"><StatusChip value={r.highStatus}/></div>
                <div style={{ fontSize: 13, color: CV.body, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", minWidth: 0 }}>{r.lastAction || "—"}</div>
                <div style={{ fontFamily: CV.mono, fontSize: 12, color: CV.body, textAlign: "center" }}>{r.lastActionDate ? fmtMonDayYr(r.lastActionDate) : "—"}</div>
                <CRActionCell pick={r.pick} onSet={(a, reopen) => patchPick(r.harveyId, reopen ? { crAction: a, done: false, doneDate: null } : { crAction: a })}/>
                <OwnerCell ownerId={r.pick && r.pick.ownerId} resolvedId={r.ownerKey} fallbackName={r.analystName}
                  people={modulePeople}
                  onSet={(pid) => patchPick(r.harveyId, { ownerId: pid })}/>
                <DueCell dueDate={r.pick && r.pick.dueDate} today={today}
                  onSet={(d) => patchPick(r.harveyId, { dueDate: d })}/>
                {filter === "excluded" ? (
                  <button onClick={() => patchPick(r.harveyId, { excluded: false })}
                    title="Restore to the CR universe"
                    style={{ border: "1px solid " + hexA(CV.accent, .3), background: hexA(CV.accent, .07), color: CV.accent,
                      borderRadius: 8, width: 26, height: 26, cursor: "pointer", fontSize: 13, lineHeight: 1,
                      display: "flex", alignItems: "center", justifyContent: "center", justifySelf: "end" }}>↩</button>
                ) : (
                  <button onClick={() => patchPick(r.harveyId, { excluded: true })}
                    title="Exclude — removes from the universe and all counts; review under the Excluded pill. Survives future data pulls."
                    style={{ border: "1px solid rgba(214,69,106,.25)", background: "transparent", color: "#B9C2CE",
                      borderRadius: 8, width: 26, height: 26, cursor: "pointer", fontSize: 13, lineHeight: 1,
                      display: "flex", alignItems: "center", justifyContent: "center", justifySelf: "end" }}
                    onMouseEnter={e => { e.currentTarget.style.color = "#D6456A"; e.currentTarget.style.background = "rgba(214,69,106,.07)"; }}
                    onMouseLeave={e => { e.currentTarget.style.color = "#B9C2CE"; e.currentTarget.style.background = "transparent"; }}>×</button>
                )}
              </div>
            );
          })}
        </div>

        <div style={{ fontFamily: CV.mono, fontSize: 11, letterSpacing: ".04em", color: CV.muted, margin: "16px 4px 0" }}>
          {visible.length} companies · universe of {active.length} active · {excludedRows.length} excluded · Cross-referenced with CR history
        </div>

        <div style={{ marginTop: 14, padding: "16px 18px", borderRadius: 14, background: "#F4F7FB",
          border: "1px solid rgba(14,42,71,.07)", display: "flex", gap: 12, alignItems: "flex-start" }}>
          <span style={{ ...CV.kickerStyle, flexShrink: 0, marginTop: 2 }}>Universe</span>
          <div style={{ fontSize: 12.5, color: CV.sec, lineHeight: 1.55, maxWidth: 820 }}>
            The CR Universe is the set of privately-held Targets with a High Status of 5.0–9.0, a Current Status of 2.0–4.6
            (anything above lives on the Pipeline Report),
            and $10M+ in sales, cross-referenced against prior custom re-approach (CR) history. Targets re-enter the universe
            when a new CR touch is logged or their status shifts — this is the sibling feed to the Pipeline Report, scoped
            to re-engagement rather than active deals.
          </div>
        </div>
      </>)}

      {crModalOpen && window.CRModal && (
        <window.CRModal viewer={user && { id: (user.personId || "").replace(/^p_/, "").replace(/_gmail$/, ""), name: user.name }}
          crPeople={modulePeople}
          onClose={() => setCrModalOpen(false)}
          onCreated={() => { setCrModalOpen(false); reload(); }}/>
      )}
    </div>
  );
}

window.CRReportScreen = CRReportScreen;
})();