// One-Off Report — the CR Report's sibling, scoped to the One-Off Deals universe.
// Universe: one_off_pipeline (scraper v1.40, browse dir 526 / buyer 1 / project
// 52279 "One-Off Deals"), cross-referenced at render time against the PREVIOUS
// BUYERS layer (one_off_conflicts by harvey_id) — rows built from Harvey events
// labeled "Conflict Check", i.e. buyers who already looked at this target.
// Brian ruling 7/23: the DB/scraper keep the conflict_* spelling; NO UI string
// says "conflict" — it reads Previous Buyers everywhere.
// Per-company Action / Owner / Due persist in one_off_picks (one row per
// harvey_id); Exclude rides on the pick so it survives every re-import.
// Visual system: "Vault Sleek Fintech Light" — same as CR Report / Action Dashboard.

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

const OV = {
  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 OV_GRAD = `linear-gradient(180deg,${OV.accent},${OV.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})`;
}

// Action vocabulary (Brian 7/23): the CR three, plus the five team asks.
const ACTION_META = {
  "High Importance":          { color: "#D6456A" },
  "Standard Approach":        { color: "#2E77C2" },
  "Tradeshow":                { color: "#7C5CD0" },
  "Send to One-Off Team":     { color: "#0FA3A3" },
  "Flip to New Buyer":        { color: "#1F9D57" },
  "Follow-Up with Owner":     { color: "#B07D0A" },
  "Follow-Up with Client":    { color: "#C2571F" },
  "Get Additional Information": { color: "#64748B" },
};
const ACTION_KEYS = Object.keys(ACTION_META);
const ACTION_ORDER = ACTION_KEYS.reduce((m, k, i) => { m[k] = i; return m; }, { "": ACTION_KEYS.length });
// The five team asks group under one pill so the filter row stays scannable.
const TEAM_ASKS = ACTION_KEYS.slice(3);

const COLUMNS = [
  { key: "company",        label: "Company",          align: "left"  },
  { key: "prevBuyers",     label: "Prev Buyers",      align: "center" },
  { 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: "action",         label: "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";
}
// m/d/yy — one-off targets can carry very old last-action dates
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) ? OV.muted : n >= 8 ? OV.good : n >= 6.5 ? OV.accent : OV.muted;
  return (
    <span style={{ fontFamily: OV.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>
  );
}

// Previous Buyers cell: count badge; click opens the buyer/date/by list.
function PrevBuyersCell({ rows }) {
  const [open, setOpen] = useState(false);
  const n = rows ? rows.length : 0;
  useEffect(() => {
    if (!open) return;
    const close = () => setOpen(false);
    document.addEventListener("mousedown", close);
    return () => document.removeEventListener("mousedown", close);
  }, [open]);
  if (n === 0) {
    return <div style={{ textAlign: "center", fontFamily: OV.mono, fontSize: 12, color: "rgba(14,42,71,.25)" }}>—</div>;
  }
  const color = "#7C5CD0";
  return (
    <div style={{ position: "relative", display: "flex", justifyContent: "center" }} onMouseDown={e => e.stopPropagation()}>
      <button onClick={() => setOpen(o => !o)}
        title={n + " previous buyer" + (n === 1 ? "" : "s") + " — click for detail"}
        style={{ fontFamily: OV.mono, fontSize: 11, fontWeight: 600, color,
          background: hexA(color, .1), border: "1px solid " + hexA(color, .3),
          borderRadius: 999, padding: "2px 9px", cursor: "pointer" }}>
        {n}
      </button>
      {open && (
        <div style={{ position: "absolute", top: "calc(100% + 6px)", left: "50%", transform: "translateX(-50%)",
          width: 260, maxHeight: 260, overflowY: "auto", zIndex: 30,
          background: "#FFFFFF", border: "1px solid rgba(14,42,71,.12)", borderRadius: 12,
          boxShadow: "0 12px 32px rgba(14,42,71,.16)", padding: "10px 12px", textAlign: "left" }}>
          <div style={{ ...OV.kickerStyle, marginBottom: 7 }}>Previous Buyers</div>
          {rows.map((b, i) => (
            <div key={i} style={{ display: "flex", alignItems: "baseline", gap: 8, padding: "4px 0",
              borderTop: i ? "1px solid rgba(14,42,71,.06)" : "none" }}>
              <span style={{ flex: 1, fontSize: 12.5, color: OV.ink, minWidth: 0 }}>{b.prevBuyer || "—"}</span>
              <span style={{ fontFamily: OV.mono, fontSize: 10.5, color: OV.muted, whiteSpace: "nowrap" }}>
                {b.prevBuyerDate ? fmtMonDayYr(b.prevBuyerDate) : ""}
              </span>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

// Action cell: button + popover with the full vocabulary + clear.
function ActionCell({ pick, onSet, readOnly }) {
  const [open, setOpen] = useState(false);
  const meta = pick && pick.oneOffAction ? ACTION_META[pick.oneOffAction] : null;
  const color = meta ? meta.color : OV.muted;
  const isDone = !!(pick && pick.done);
  const label = pick && pick.oneOffAction ? (isDone ? "✓ " : "") + pick.oneOffAction : "— 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 ? OV.ink : OV.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: OV.muted, flexShrink: 0 }}>▾</span>}
      </button>
      {open && (
        <div style={{ position: "absolute", top: "calc(100% + 6px)", left: 0, width: 246, 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 }}>
          {ACTION_KEYS.map((a, i) => (
            <React.Fragment key={a}>
              {i === 3 && (
                <div style={{ ...OV.kickerStyle, padding: "7px 10px 3px", borderTop: "1px solid rgba(14,42,71,.07)", marginTop: 3 }}>Team Asks</div>
              )}
              <button onClick={() => { onSet(a, true); setOpen(false); }}
                style={{ display: "flex", alignItems: "center", gap: 9, border: "none",
                  background: pick && pick.oneOffAction === a ? hexA(ACTION_META[a].color, .08) : "transparent",
                  borderRadius: 8, padding: "8px 10px", cursor: "pointer", fontFamily: "inherit", fontSize: 12.5, color: OV.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>
            </React.Fragment>
          ))}
          <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: OV.muted, textAlign: "left" }}>
            <span style={{ width: 8, height: 8, borderRadius: "50%", flexShrink: 0, background: OV.muted }}/>
            — Unassigned
          </button>
        </div>
      )}
    </div>
  );
}

// Owner cell: app-wide Avatar; click to reassign.
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: OV.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: OV.mono, fontSize: 9, fontWeight: 600, color: OV.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(OV.over, .45) : "rgba(14,42,71,.16)"),
          background: overdue ? hexA(OV.over, .05) : "#FFFFFF",
          fontFamily: OV.mono, fontSize: 11, color: overdue ? OV.over : (dueDate ? OV.ink : OV.muted),
          fontWeight: overdue ? 700 : 400, cursor: readOnly ? "default" : "pointer" }}/>
      {overdue && <span style={{ fontFamily: OV.mono, fontSize: 9, letterSpacing: ".08em", color: OV.over }}>OVERDUE</span>}
    </div>
  );
}

function OneOffReportScreen({ user }) {
  const F = window.VAULT_FIRM;
  const PEOPLE_BY_ID = (F && F.PEOPLE_BY_ID) || {};
  const [rows, setRows] = useState([]);
  const [picks, setPicks] = useState([]);
  const [prevBuyers, setPrevBuyers] = 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 | <action> | teamasks | unassigned | excluded
  const [ownerFilter, setOwnerFilter] = useState("all");  // all | person id | "none"
  const [search, setSearch] = useState("");
  // One-shot focus handoff from the Action Dashboard.
  useEffect(() => {
    try {
      const raw = window.sessionStorage.getItem("vault:focus-company");
      if (!raw) return;
      const f = JSON.parse(raw);
      if (f && f.view === "oneoff" && f.q) {
        setSearch(f.q);
        window.sessionStorage.removeItem("vault:focus-company");
      }
    } catch (e) {}
  }, []);
  // Building sorts: click = primary, shift-click = stack a level.
  const [sortStack, setSortStack] = useState([{ key: "sales", dir: "desc" }]);
  const [colFilters, setColFilters] = useState({});
  const [openFilter, setOpenFilter] = useState(null);
  const filterPopRef = 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, prevBuyers: 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 || "—";
    if (key === "prevBuyers") return String((r.prev || []).length);
    return null;
  };
  const [colWidths, setColWidths] = useState({
    company: 200, prevBuyers: 84, sales: 78, ebitda: 78, status: 78, highStatus: 84,
    lastAction: 104, lastActionDate: 96, action: 190, owner: 56, due: 100,
  });
  const rzRef = useRef(null);
  const today = localTodayStr();

  // Column resize (56px floor, same as CR)
  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, pb] = await Promise.all([
          window.VaultAPI.listOneOffPipeline(),
          window.VaultAPI.listOneOffPicks().catch(() => []),
          window.VaultAPI.listOneOffPreviousBuyers().catch(() => []),
        ]);
        if (cancelled) return;
        setRows(univ);
        setPicks(pk);
        setPrevBuyers(pb);
      } 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 prevById = useMemo(() => {
    const m = new Map();
    prevBuyers.forEach(b => {
      const k = String(b.harveyId);
      const a = m.get(k) || [];
      a.push(b);
      m.set(k, a);
    });
    // newest first inside each company
    m.forEach(a => a.sort((x, y) => String(y.prevBuyerDate || "").localeCompare(String(x.prevBuyerDate || ""))));
    return m;
  }, [prevBuyers]);

  const modulePeople = useMemo(() => {
    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;
  }, []);

  const enriched = useMemo(() => rows.map((r, i) => {
    const pick = pickById.get(String(r.harveyId)) || null;
    const prev = prevById.get(String(r.harveyId)) || [];
    const raw = (r.analystName || "").trim();
    const ownerKey = (pick && pick.ownerId)
      || (PEOPLE_BY_ID[raw] ? raw : null)
      || (PEOPLE_BY_ID[raw.toLowerCase()] ? raw.toLowerCase() : null)
      || personIdByName.get(raw.toLowerCase())
      || null;
    return { ...r, pick, prev, ownerKey, _idx: i };
  }), [rows, pickById, prevById, personIdByName]);

  // Excluded companies leave the working universe entirely and live on the
  // "Excluded" view until restored. The flag rides on one_off_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]);

  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, teamasks: 0, unassigned: 0, excluded: 0 };
    ACTION_KEYS.forEach(k => { c[k] = 0; });
    ownerPool.forEach(r => {
      const a = r.pick && r.pick.oneOffAction;
      if (a && c[a] != null) {
        c[a] += 1;
        if (TEAM_ASKS.indexOf(a) >= 0) c.teamasks += 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 withPrevBuyers = useMemo(() => ownerPool.filter(r => r.prev && r.prev.length > 0).length, [ownerPool]);

  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.oneOffAction));
    else if (filter === "teamasks") out = out.filter(r => r.pick && TEAM_ASKS.indexOf(r.pick.oneOffAction) >= 0);
    else if (filter !== "all" && filter !== "excluded") out = out.filter(r => r.pick && r.pick.oneOffAction === 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 "prevBuyers": return (r.prev || []).length;
        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 "action": return ACTION_ORDER[(r.pick && r.pick.oneOffAction) || ""];
        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) => {
    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, oneOffAction: null, ownerId: null, dueDate: null, ...patch }];
    });
    try { await window.VaultAPI.upsertOneOffPick(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: "Team Asks", val: "teamasks" },
    { label: "Unassigned", val: "unassigned" },
    { label: "Excluded", val: "excluded" },
  ];

  return (
    <div style={{ padding: "20px 28px 60px", minHeight: "100vh", color: OV.body,
      background: "radial-gradient(1100px 620px at 100% -12%, rgba(46,119,194,.07), transparent 58%), " + OV.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: OV.ink }}>One-Off Report</h1>
          <div style={{ marginTop: 7, fontFamily: OV.mono, fontSize: 12, letterSpacing: ".02em", color: OV.sec }}>
            One-Off Deals Universe · Cross-referenced with Previous Buyers
          </div>
        </div>
      </div>

      {loading && <div style={{ padding: 40, textAlign: "center", fontFamily: OV.mono, fontSize: 12, color: OV.muted, ...OV.panel }}>Loading One-Off universe…</div>}
      {error && (
        <div style={{ padding: "22px 24px", ...OV.panel }}>
          <div style={{ fontSize: 14, fontWeight: 600, color: OV.over }}>Couldn't load the One-Off universe.</div>
          <div style={{ fontFamily: OV.mono, fontSize: 11, color: OV.muted, marginTop: 6 }}>{error}</div>
          <div style={{ fontSize: 12.5, color: OV.sec, marginTop: 10 }}>
            If <span style={{ fontFamily: OV.mono }}>one_off_pipeline</span> or <span style={{ fontFamily: OV.mono }}>one_off_picks</span> doesn't
            exist yet, run the DDL, scrape with v1.40, and import both CSVs via Data Uploads.
          </div>
          <button onClick={reload} style={{ marginTop: 12, border: "1px solid rgba(14,42,71,.12)", background: "#FFFFFF",
            color: OV.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: "One-Off Universe", value: ownerPool.length, color: OV.accent },
            { kicker: "With Prev Buyers", value: withPrevBuyers, color: "#7C5CD0" },
            { kicker: "Team Asks", value: counts.teamasks, color: "#0FA3A3" },
            { kicker: "Unassigned", value: counts.unassigned, color: OV.muted },
          ].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: OV.mono, fontWeight: 600, fontSize: 24, color: OV.ink, lineHeight: 1 }}>{k.value}</div>
                <div style={{ ...OV.kickerStyle, letterSpacing: ".12em", marginTop: 6 }}>{k.kicker}</div>
              </div>
            </div>
          ))}
        </div>

        {/* Owner filter pills */}
        <div style={{ display: "flex", flexWrap: "wrap", gap: 8, marginBottom: 10 }}>
          {(() => {
            const pillBase = (on) => on
              ? { 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: OV_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: OV.sec, padding: "5px 13px 5px 6px", borderRadius: 999,
                  background: "#FFFFFF", border: "1px solid rgba(14,42,71,.12)" };
            const countBadge = (on) => ({ fontFamily: OV.mono, fontSize: 10, padding: "1px 6px", borderRadius: 6,
              color: on ? "rgba(255,255,255,.85)" : OV.muted,
              background: on ? "rgba(255,255,255,.2)" : "rgba(14,42,71,.05)" });
            return (
              <>
                <button onClick={() => setOwnerFilter("all")}
                  style={{ ...pillBase(ownerFilter === "all"), padding: "7px 13px" }}>
                  All Owners
                  <span style={countBadge(ownerFilter === "all")}>{enriched.length}</span>
                </button>
                {modulePeople.map(p => {
                  const on = ownerFilter === p.id;
                  return (
                    <button key={p.id} onClick={() => setOwnerFilter(on ? "all" : p.id)} style={pillBase(on)}>
                      <window.Avatar id={p.id}/>
                      {p.name.split(" ")[0]}
                      <span style={countBadge(on)}>{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>

        {/* Action 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 on = filter === f.val;
              return (
                <button key={f.val} onClick={() => setFilter(f.val)}
                  title={f.val === "teamasks" ? TEAM_ASKS.join(" · ") : ""}
                  style={on
                    ? { 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: OV_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: OV.sec, padding: "7px 13px", borderRadius: 999,
                        background: "#FFFFFF", border: "1px solid rgba(14,42,71,.12)" }}>
                  {f.label}
                  <span style={{ fontFamily: OV.mono, fontSize: 10, padding: "1px 6px", borderRadius: 6,
                    color: on ? "rgba(255,255,255,.85)" : OV.muted,
                    background: on ? "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: OV.mono, fontSize: 12, color: OV.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={{ ...OV.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: OV.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={{ ...OV.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: OV.mono, fontSize: 11,
                  color: OV.accent, background: hexA(OV.accent, .08), border: "1px solid " + hexA(OV.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: OV.mono, fontSize: 10, color: OV.muted }}>Shift-click headers to stack</span>
          </div>
        )}

        {/* Table */}
        <div className="tbl-wrap" style={{ ...OV.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;
              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: OV.mono, fontSize: 10, fontWeight: 600, letterSpacing: ".1em",
                      textTransform: "uppercase", color: sortOn ? OV.accent : OV.kicker,
                      justifyContent: c.align === "right" ? "flex-end" : c.align === "center" ? "center" : "flex-start" }}>
                    {c.label}
                    <span style={sortOn
                      ? { fontSize: 8, color: OV.accent, textShadow: "0 0 6px rgba(46,119,194,.6)" }
                      : { fontSize: 8, color: "#B7C0CC" }}>{arrow}</span>
                    {sortOn && sortStack.length > 1 && (
                      <span style={{ fontFamily: OV.mono, fontSize: 8, color: OV.accent, background: hexA(OV.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(OV.accent, .12) : "transparent",
                          color: has ? OV.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={{ ...OV.kickerStyle }}>{c.label}</span>
                          {sel.length > 0 && (
                            <span onClick={() => setColFilters(prev => ({ ...prev, [c.key]: [] }))}
                              style={{ fontFamily: OV.mono, fontSize: 10, color: OV.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: OV.body }}>
                            <input type="checkbox" checked={sel.includes(v)} onChange={() => toggle(v)}
                              style={{ accentColor: OV.accent, width: 13, height: 13, cursor: "pointer" }}/>
                            <span style={{ flex: 1, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{v}</span>
                            <span style={{ fontFamily: OV.mono, fontSize: 10, color: OV.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: OV.mono, fontSize: 11, color: OV.muted }}>
              {enriched.length === 0 ? "The universe is empty — scrape with v1.40 and import to one_off_pipeline." : "Nothing matches this filter."}
            </div>
          )}

          {visible.map((r) => (
            <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>
              </div>
              <PrevBuyersCell rows={r.prev}/>
              <div style={{ fontFamily: OV.mono, fontSize: 13, color: OV.ink, textAlign: "right" }}>{fmtM(r.revenue) || "—"}</div>
              <div style={{ fontFamily: OV.mono, fontSize: 13, color: r.ebitda != null ? OV.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: OV.body, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", minWidth: 0 }}>{r.lastAction || "—"}</div>
              <div style={{ fontFamily: OV.mono, fontSize: 12, color: OV.body, textAlign: "center" }}>{r.lastActionDate ? fmtMonDayYr(r.lastActionDate) : "—"}</div>
              <ActionCell pick={r.pick} onSet={(a, reopen) => patchPick(r.harveyId, reopen ? { oneOffAction: a, done: false, doneDate: null } : { oneOffAction: 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 One-Off universe"
                  style={{ border: "1px solid " + hexA(OV.accent, .3), background: hexA(OV.accent, .07), color: OV.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: OV.mono, fontSize: 11, letterSpacing: ".04em", color: OV.muted, margin: "16px 4px 0" }}>
          {visible.length} companies · universe of {active.length} active · {excludedRows.length} excluded · {withPrevBuyers} with previous buyers
        </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={{ ...OV.kickerStyle, flexShrink: 0, marginTop: 2 }}>Universe</span>
          <div style={{ fontSize: 12.5, color: OV.sec, lineHeight: 1.55, maxWidth: 820 }}>
            The One-Off universe is the set of Targets sitting in Harvey's One-Off Deals project — companies that came in
            outside a buyer engagement and need a home. The Prev Buyers column shows which buyers have already looked at
            each target, so a flip goes to someone new. Assign an action and owner here and it lands on that person's
            Action Dashboard; Exclude drops a company from the universe permanently and survives every re-import.
          </div>
        </div>
      </>)}
    </div>
  );
}

window.OneOffReportScreen = OneOffReportScreen;
})();