// Deal Dashboard — Offers & Closed Deals.
//
// CLOSED DEALS: live + real (getClosedDeals → fee, tev, type, dealmakers).
// OPEN OFFERS / LOIs: from the HARVEY_OFFERS *static snapshot* (data-offers.js).
//   - TEV is best-effort parsed from the free-text `amount` field.
//   - Estimated fee = Standard Fee Formula (VaultFees) on parsed TEV + type.
//   - Two scenarios: Potential (100% — all close) and Realistic (stage-weighted).
//   NOTE: snapshot, not live. When the offers data lands in Supabase, swap the
//   HARVEY_OFFERS source for an RPC and the page becomes live.

const { useState, useEffect, useMemo } = React;

// stage definitions + weights now live in the shared helper (single source).
const STAGE_COLOR = { "LOI Executed": "#0D3E5C", "LOI Submitted": "#196EA7", "Verbal Offer Submitted": "#3D8FC4", "IOI Submitted": "#84BBDE" };

function _person(pid) { return (window.VAULT_FIRM.PEOPLE_BY_ID[pid] || {}); }
function moduleNameOf(pid) {
  const p = _person(pid); if (!p.subteam) return "—";
  const st = (window.VAULT_FIRM.SUBTEAMS || []).find(s => s.id === p.subteam);
  return st ? String(st.label).replace(/^.*—\s*/, "").trim() : "—";
}
function subteamNameOf(pid) {
  const F = window.VAULT_FIRM, p = _person(pid);
  const sst = (F.SUBSUBTEAMS || []).find(s => s.id === p.subSubteam) || (F.SUBSUBTEAMS || []).find(s => s.lead === pid);
  return sst ? String(sst.label).replace(/\s*(Sub-?team|Team)\s*$/i, "").trim() : "—";
}
function nameOf(pid) { return (window.VAULT_FIRM.PEOPLE_BY_ID[pid] || {}).name || pid || "—"; }

function _cmp(a, b) {
  if (a == null && b == null) return 0;
  if (a == null) return 1; if (b == null) return -1;
  if (typeof a === "number" && typeof b === "number") return a - b;
  return String(a).localeCompare(String(b), undefined, { numeric: true });
}
function sortRows(rows, key, dir, val) {
  const s = rows.slice().sort((x, y) => _cmp(val(x, key), val(y, key)));
  return dir === "desc" ? s.reverse() : s;
}
const OC_OFFER_W = { count: 40, stage: 112, target: 190, buyer: 150, type: 78, tev: 82, fee: 90, offerDate: 96, projClose: 96, module: 88, subteam: 96, analyst: 104, act: 152 };
const OC_CLOSED_W = { count: 40, name: 200, buyer: 168, type: 82, tev: 82, fee: 92, closeDate: 100, module: 88, subteam: 96, analyst: 104, act: 100 };
function SortTh({ col, label, sort, setSort, w, sortable = true, num }) {
  const active = sort.key === col;
  const onClick = sortable ? () => setSort(active ? { key: col, dir: sort.dir === "asc" ? "desc" : "asc" } : { key: col, dir: "asc" }) : undefined;
  return React.createElement(window.ResizableTh, {
    col, widths: w.widths, setWidth: w.setWidth,
    className: num ? "num" : undefined,
    onClick,
    style: { cursor: sortable ? "pointer" : "default", userSelect: "none" },
  }, label, active ? (sort.dir === "asc" ? " ▲" : " ▼") : "");
}

function _m(n) {
  if (n == null) return "—"; if (n === 0) return "$0";
  const a = Math.abs(n);
  if (a >= 1000000) return `$${(n / 1000000).toFixed(2)}M`;
  if (a >= 1000) return `$${(n / 1000).toFixed(0)}K`;
  return `$${Math.round(n).toLocaleString()}`;
}
function _tevM(n) { return n == null ? "—" : "$" + (Math.round(Number(n) * 10) / 10) + "M"; }
function _d10(s) { return (s == null ? "" : String(s)).slice(0, 10); }
function _inRange(d, from, to) { const x = _d10(d); return x && x >= from && x <= to; }
function _mondayRange(weeks) {
  if (!weeks || !weeks.length) return null;
  const toMon = (f) => { const d = new Date(f + "T00:00:00Z"); d.setUTCDate(d.getUTCDate() - 4); return d.toISOString().slice(0, 10); };
  const m = weeks.map(toMon).sort(); return { from: m[0], to: m[m.length - 1] };
}
function _calRange(period, year) {
  const P = (window.VAULT_FIRM.PERIODS || []).find(p => p.id === period);
  const today = new Date().toISOString().slice(0, 10);
  if (P && P.quarter) { const q = P.quarter; const st = ["01-01", "04-01", "07-01", "10-01"][q - 1], en = ["03-31", "06-30", "09-30", "12-31"][q - 1]; return { from: `${year}-${st}`, to: `${year}-${en}` }; }
  if (P && P.weeksBack) { // trailing windows are relative to today
    const d = new Date(); d.setUTCDate(d.getUTCDate() - P.weeksBack * 7);
    return { from: d.toISOString().slice(0, 10), to: today };
  }
  if (P && P.id === "ytd") { const en = `${year}-12-31`; return { from: `${year}-01-01`, to: today < en ? today : en }; }
  return { from: `${year}-01-01`, to: `${year}-12-31` };
}


function _shortLbl(s) { return String(s || "").replace(/\s*(Sub-?team|Team)\s*$/i, "").replace(/^.*— /, "").trim() || s; }
function resolveUserScope(user) {
  const F = window.VAULT_FIRM;
  const pid = user && (user.personId || user.id);
  const all = (F.DEAL_PEOPLE || F.PEOPLE || (F.PEOPLE_BY_ID ? Object.values(F.PEOPLE_BY_ID) : []));
  let me = (pid && F.PEOPLE_BY_ID) ? F.PEOPLE_BY_ID[pid] : null;
  if (!me && pid) me = F.PEOPLE_BY_ID[pid.replace(/^p_/, "")] || null;
  if (!me) me = all.find(p => p && (
        p.id === pid || p.id === (pid || "").replace(/^p_/, "") ||
        (user && user.email && p.email && p.email.toLowerCase() === user.email.toLowerCase()) ||
        (user && user.name && p.name === user.name))) || null;
  const meId = (me && me.id) || pid || null;
  const moduleId = me ? me.subteam : null;
  const moduleLabel = ((F.SUBTEAMS.find(s => s.id === moduleId) || {}).label || "My module").replace(/^.*— /, "");
  const mySubteams = (F.SUBSUBTEAMS || []).filter(s => s.parentSubteam === moduleId)
    .map(s => ({ id: s.id, label: _shortLbl(s.label), memberIds: s.memberIds || [] }));
  const indIds = new Set();
  mySubteams.forEach(s => s.memberIds.forEach(id => indIds.add(id)));
  if (meId) indIds.add(meId);
  const myIndividuals = [...indIds].map(id => ({ id, label: (F.PEOPLE_BY_ID[id] && F.PEOPLE_BY_ID[id].name) || id })).sort((a, b) => a.label.localeCompare(b.label));
  return { moduleId, moduleLabel, mySubteams, myIndividuals, meId };
}
function scopeParams(grain, id, U) {
  const none = { modules: null, subteams: null, individuals: null, scope: { subteam: [], subSubteam: [], individuals: [] } };
  if (grain === "all") return none;  // firm-wide: null person set → dealInScope/forScope include everyone
  if (grain === "subteam") { if (!id) return none; return { modules: null, subteams: [id], individuals: null, scope: { subteam: [], subSubteam: [id], individuals: [] } }; }
  if (!U.moduleId) return none;
  return { modules: [U.moduleId.replace(/^st-/, "")], subteams: null, individuals: null, scope: { subteam: [U.moduleId], subSubteam: [], individuals: [] } };
}

function OffersClosedScreen({ period, setPeriod, year, setYear, user }) {
  const VS = { resolveUserScope, scopeParams };
  const OC_STAGE_MAP = (window.VaultOffersEV || {}).STAGE_MAP || {};
  const B = window.VAULT_BHAG;
  const U = useMemo(() => VS.resolveUserScope(user), [user && (user.personId || user.id)]);
  // Destructive writes (delete offer / closed deal) are limited to Vault co-admins.
  const canDelete = ["bscott", "ayacoel"].includes(String(U.meId || "").replace(/^p_/, "").replace(/_gmail$/, ""));

  const [grain, setGrain] = useState("module");
  const [pickId, setPickId] = useState(null);
  const [offersVer, setOffersVer] = useState(0);
  useEffect(() => { const h = () => setOffersVer(v => v + 1); window.addEventListener("vault:offers-updated", h); return () => window.removeEventListener("vault:offers-updated", h); }, []);
  const [form, setForm] = useState(null);     // null = closed; else offer object
  const [saving, setSaving] = useState(false);
  const modulePeople = useMemo(() => {
    const ids = new Set();
    U.mySubteams.forEach(s => (s.memberIds || []).forEach(id => ids.add(id)));
    if (U.meId) ids.add(U.meId);
    return [...ids].map(id => ({ id, name: (window.VAULT_FIRM.PEOPLE_BY_ID[id] || {}).name || id })).sort((a, b) => a.name.localeCompare(b.name));
  }, [U]);
  const refreshOffers = async () => {
    try { const rows = await window.VaultAPI.getOffers(); if (rows && rows.length) { window.HARVEY_OFFERS = rows; window.dispatchEvent(new CustomEvent("vault:offers-updated")); } } catch (e) {}
  };
  const saveOffer = async (o) => {
    setSaving(true);
    try { await window.VaultAPI.upsertOffer(o); await refreshOffers(); setForm(null); }
    catch (e) { window.VaultUI.toast("error", "Save failed: " + (e.message || e)); }
    finally { setSaving(false); }
  };
  const removeOffer = async (o) => {
    if (!o.id || !(await window.VaultUI.confirm({ message: "Delete this offer?", danger: true }))) return;
    try { await window.VaultAPI.deleteOffer(o.id); await refreshOffers(); }
    catch (e) { window.VaultUI.toast("error", "Delete failed: " + (e.message || e)); }
  };
  const editPrefill = (o) => ({ id: o.id, dm: o.dm || "", aa: o.aa || "", analyst: o.analyst || "", stage: o.stage || "LOI Submitted", type: o.type || "Add-on", target: o.target || "", buyer: o.buyer || "", amount: o.amount || "", offer_date: o.offerDate || "", projected_close: (o.projectedClose && !OC_STAGE_MAP[o.projectedClose]) ? o.projectedClose : "" });
  const blankOffer = () => ({ dm: U.meId || "", aa: "", analyst: "", stage: "LOI Submitted", type: "Add-on", target: "", buyer: "", amount: "", offer_date: "", projected_close: "" });

  // Closed-deal create/edit/delete + promote-from-offer
  const [closedForm, setClosedForm] = useState(null);
  const [showRecon, setShowRecon] = useState(false);
  const [reconBusy, setReconBusy] = useState(null);
  const removeReconRow = async (id) => {
    setReconBusy(id);
    try { await window.VaultAPI.deleteClosedDeal(id); setClosedVer(v => v + 1); }
    catch (e) { showToast && showToast("error", "Couldn't remove: " + (e.message || e)); }
    finally { setReconBusy(null); }
  };
  const [savingClosed, setSavingClosed] = useState(false);
  const [closedVer, setClosedVer] = useState(0);
  const [offSort, setOffSort] = useState({ key: "stage", dir: "asc" });
  const [collapsedStages, setCollapsedStages] = useState({}); // stageKey -> true when collapsed
  const toggleStage = k => setCollapsedStages(m => ({ ...m, [k]: !m[k] }));
  const [clSort, setClSort] = useState({ key: "closeDate", dir: "asc" });
  const offW = window.useColumnWidths("oc-offers", OC_OFFER_W);
  const clW = window.useColumnWidths("oc-closed", OC_CLOSED_W);
  const allPeople = useMemo(() => (window.VAULT_FIRM.PEOPLE || []).slice().sort((a, b) => a.name.localeCompare(b.name)).map(p => ({ id: p.id, name: p.name })), []);
  const today10 = new Date().toISOString().slice(0, 10);
  const saveClosedDeal = async (deal) => {
    setSavingClosed(true);
    const clean = Object.assign({}, deal, {
      tev: (deal.tev !== "" && deal.tev != null) ? parseFloat(deal.tev) : null,
      fee: (deal.fee !== "" && deal.fee != null) ? parseFloat(deal.fee) : null,
    });
    try {
      if (clean.id) await window.VaultAPI.updateClosedDeal(clean.id, clean);
      else await window.VaultAPI.createClosedDeal(Object.assign({}, clean, { id: "C-NEW-" + Date.now().toString(36), status: "closed" }));
      if (deal._sourceOfferId) { await window.VaultAPI.upsertOffer({ id: deal._sourceOfferId, stage: "Closed" }); await refreshOffers(); }
      setClosedVer(v => v + 1); setClosedForm(null);
    } catch (e) { window.VaultUI.toast("error", "Save failed: " + (e.message || e)); }
    finally { setSavingClosed(false); }
  };
  const removeClosedDeal = async (d) => {
    if (!d.id || !(await window.VaultUI.confirm({ message: "Delete this closed deal?", danger: true }))) return;
    try { await window.VaultAPI.deleteClosedDeal(d.id); setClosedVer(v => v + 1); }
    catch (e) { window.VaultUI.toast("error", "Delete failed: " + (e.message || e)); }
  };
  const closedEditPrefill = (d) => ({ id: d.id, name: d.name || "", buyer: d.buyer || "", dm1: d.dm1 || "", dm2: d.dm2 || "", dm3: d.dm3 || "", tev: d.tev != null ? String(d.tev) : "", fee: d.fee != null ? String(d.fee) : "", closeDate: d.closeDate || today10, type: d.typeRaw || d.type || "add-on" });
  const blankClosed = () => ({ name: "", buyer: "", dm1: U.meId || "", dm2: "", dm3: "", tev: "", fee: "", closeDate: today10, type: "add-on" });
  const promotePrefill = (o) => ({ _sourceOfferId: o.id, name: o.target || "", buyer: o.buyer || "", dm1: o.dm || "", dm2: o.aa || "", dm3: o.analyst || "", tev: o.tevM != null ? String(o.tevM) : "", fee: o.fee != null ? String(Math.round(o.fee)) : "", closeDate: today10, type: (String(o.type || "").toLowerCase() === "platform") ? "platform" : "add-on" });
  const activeId = grain === "subteam" ? (pickId || (U.mySubteams[0] && U.mySubteams[0].id)) : (U.moduleId || "module");
  const sp = VS.scopeParams(grain, activeId, U);
  const scopeSet = useMemo(() => B ? B.scopePersonSet(sp.scope) : null, [grain, activeId]);

  const cr = _calRange(period, year);   // calendar range for closed-deal filtering

  const [closed, setClosed] = useState([]);
  const [unlinkedOnly, setUnlinkedOnly] = useState(false);
  const isLinked = d => !!(d.harveyTargetId || d.harveyProjectId);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    let cancel = false; setLoading(true);
    window.VaultAPI.getClosedDeals().then(all => {
      if (cancel) return;
      const rows = (all || [])
        .filter(d => window.VAULT_BHAG.dealInScope(d, scopeSet))
        .filter(d => _inRange(d.closeDate, cr.from, cr.to))
        .sort((a, b) => _d10(a.closeDate) < _d10(b.closeDate) ? 1 : -1);
      setClosed(rows); setLoading(false);
    }).catch(() => { if (!cancel) setLoading(false); });
    return () => { cancel = true; };
  }, [grain, activeId, cr.from, cr.to, closedVer]);

  // open offers + EV from the shared helper (attributes by dm OR analyst aa)
  const ev = useMemo(() => (window.VaultOffersEV ? window.VaultOffersEV.forScope(scopeSet, year) : { offers: [], potential: 0, signed: { count: 0, fee: 0 }, active: { count: 0, fee: 0 }, byStage: [], parsedCount: 0 }), [scopeSet, offersVer, year]);
  const offers = ev.offers;
  const offVal = (o, k) => ({ stage: (window.VaultOffersEV.STAGE_MAP[o.stage] || {}).order, target: o.target, buyer: o.buyer, type: o.type, tev: o.tevM, fee: o.fee, offerDate: _d10(o.offerDate), module: moduleNameOf(o.dm), subteam: subteamNameOf(o.aa), analyst: o.analyst ? nameOf(o.analyst) : "" }[k]);
  const offersSorted = sortRows(offers, offSort.key, offSort.dir, offVal);
  // Group into distinct, collapsible stage sections in canonical funnel order
  // (LOI Executed → LOI Submitted → Verbal Offer → IOI). Sort within a group
  // still honors the active column sort.
  const stageGroups = useMemo(() => {
    const order = (window.VaultOffersEV && window.VaultOffersEV.STAGES) ? window.VaultOffersEV.STAGES.map(s => s.key) : Object.keys(STAGE_COLOR);
    return order.map(key => {
      const rows = offersSorted.filter(o => o.stage === key);
      const fee = rows.reduce((s, o) => s + (Number(o.fee) || 0), 0);
      return { key, label: (OC_STAGE_MAP[key] || {}).label || key, rows, fee };
    }).filter(g => g.rows.length > 0);
  }, [offersSorted]);
  const OFFER_COLSPAN = 14;

  const closedFees = useMemo(() => closed.reduce((s, d) => s + (Number(d.fee) || 0), 0), [closed]);
  const clVal = (d, k) => ({ name: d.name, buyer: d.buyer, type: d.type, tev: d.tev, fee: d.fee, closeDate: _d10(d.closeDate), module: moduleNameOf(d.dm1), subteam: subteamNameOf(d.dm2), analyst: d.dm3 ? nameOf(d.dm3) : "" }[k]);
  const closedSortedAll = sortRows(closed, clSort.key, clSort.dir, clVal);
  const closedSorted = unlinkedOnly ? closedSortedAll.filter(d => !isLinked(d)) : closedSortedAll;
  const unlinkedCount = closed.filter(d => !isLinked(d)).length;
  // Likely-duplicate closes: same buyer + fee + close date entered twice (e.g.
  // one deal logged under two analysts). Flag for review — never auto-dropped.
  // Reconciliation: detect duplicate closed-deal pairs and recommend which row
  // to keep. A row carrying a Harvey event id is authoritative (scraped); its
  // id-less twin is the stale manual copy. Two match tiers:
  //   exact — same buyer + fee + close date
  //   soft  — same name + buyer + fee, different date (manual-entry date drift)
  const nrm = s => String(s || "").trim().toLowerCase().replace(/[^a-z0-9]/g, "");
  const dupInfo = useMemo(() => {
    const bucket = (keyFn, kind) => {
      const m = new Map();
      closed.forEach(d => {
        if (!(Number(d.fee) || 0)) return;
        const k = keyFn(d); if (!k) return;
        if (!m.has(k)) m.set(k, []); m.get(k).push(d);
      });
      return [...m.values()].filter(v => v.length > 1).map(rows => ({ kind, rows }));
    };
    const exact = bucket(d => d.closeDate ? [nrm(d.buyer), Number(d.fee) || 0, String(d.closeDate).slice(0, 10)].join("|") : null, "exact");
    const exactIds = new Set(exact.flatMap(g => g.rows.map(r => r.id)));
    // soft groups exclude rows already caught as exact dups
    const soft = bucket(d => exactIds.has(d.id) ? null : [nrm(d.name), nrm(d.buyer), Number(d.fee) || 0].join("|"), "soft");
    const groups = [...exact, ...soft].map(g => {
      // recommend keeping the event-id row; if exactly one lacks an id, that's the remove.
      const withEid = g.rows.filter(r => r.harveyEventId);
      const noEid   = g.rows.filter(r => !r.harveyEventId);
      let keepId = null, removeIds = [], decidable = false;
      if (withEid.length === 1 && noEid.length >= 1) { keepId = withEid[0].id; removeIds = noEid.map(r => r.id); decidable = true; }
      else if (withEid.length === 0 && g.rows.length === 2) { keepId = g.rows[0].id; removeIds = [g.rows[1].id]; decidable = true; } // both manual: keep first, remove second (still your click)
      return { ...g, keepId, removeIds, decidable };
    });
    const flagged = new Set(groups.flatMap(g => g.rows.map(r => r.id)));
    const extra = groups.reduce((n, g) => n + g.rows.length - 1, 0);
    return { groups, flagged, extra };
  }, [closed]);
  const potential = ev.potential;
  const signed = ev.signed;
  const active = ev.active;
  const parsedCount = ev.parsedCount;

  const bhagKey = B ? B.resolveKey(sp.scope) : null;
  const target = (B && bhagKey && Number(year) === B.YEAR) ? B.annualTarget(bhagKey, "conservative", "fees") : null;

  // stage rollup (from shared helper)
  const byStage = ev.byStage;

  const periodLabel = ((window.VAULT_FIRM.PERIODS.find(p => p.id === period)) || {}).label || period;

  return (
    <div className="deal-ov" style={{ padding: "20px 28px 60px" }}>
      <style>{`
        .deal-ov .card{ box-shadow: var(--shadow-lg); }
        .deal-ov .seclabel{ position:relative; padding-left:11px; }
        .deal-ov .seclabel::before{ content:""; position:absolute; left:0; top:50%; transform:translateY(-50%); width:3px; height:12px; border-radius:2px; background:var(--accent); }
        .deal-ov .tintcard{ background:var(--accent-soft); border-color:var(--accent-soft-2); }
        .deal-ov .gbtn{ padding:5px 12px; font-size:12.5px; border:0; background:var(--surface); color:var(--muted); cursor:pointer; }
        .deal-ov .gbtn.on{ background:var(--accent); color:var(--accent-ink); }
        .deal-ov .snap{ font-size:9px; letter-spacing:.05em; padding:1px 6px; border-radius:4px; background:var(--accent-soft); color:var(--accent); border:1px solid var(--accent-soft-2); }
        .deal-ov table.oc{ width:100%; border-collapse:collapse; font-size:12.5px; table-layout:fixed; }
        .deal-ov table.oc th{ text-align:left; font-size:10px; letter-spacing:.04em; text-transform:uppercase; color:var(--muted); font-weight:600; padding:7px 10px; border-bottom:1px solid var(--line); white-space:nowrap; overflow:hidden; }
        .deal-ov table.oc td{ padding:8px 10px; border-bottom:1px solid var(--line-2); color:var(--ink-2); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
        .deal-ov table.oc td.num, .deal-ov table.oc th.num{ text-align:right; font-family:var(--f-mono); }
        .deal-ov table.oc tr:hover td{ background:var(--surface-2); }
        .deal-ov .pill-st{ font-size:10.5px; padding:2px 8px; border-radius:999px; color:#fff; font-weight:600; }
        .deal-ov .oc-op{ display:flex; align-items:center; font-size:22px; font-weight:600; color:var(--muted); flex:0 0 auto; }
      `}</style>

      {/* scope + period (PageToolbar) */}
      <window.PageToolbar
        title="Offers & Closed Deals"
        subtitle={(grain === "all" ? "Firm-wide" : grain === "subteam" ? "Sub-team scope" : U.moduleLabel + " module") + " · " + periodLabel}>
        <div className="row" style={{ gap: 0, borderRadius: 8, overflow: "hidden", border: "1px solid var(--line)" }}>
          {[["all", "All"], ["module", U.moduleLabel], ["subteam", "Sub-team"]].map(([g, lbl]) => (
            <button key={g} className={"gbtn" + (grain === g ? " on" : "")} onClick={() => { setGrain(g); setPickId(null); }}>{lbl}</button>
          ))}
        </div>
        {grain === "subteam" && (
          <select value={activeId || ""} onChange={e => setPickId(e.target.value)} style={{ fontSize: 12.5, padding: "5px 9px" }}>
            {U.mySubteams.map(s => <option key={s.id} value={s.id}>{s.label}</option>)}
          </select>
        )}
        <window.VaultPeriod.PeriodSelector value={period} onChange={setPeriod} year={year} onYearChange={setYear}/>
      </window.PageToolbar>

      {/* KPI row */}
      <div style={{ display: "flex", alignItems: "stretch", gap: 8, marginBottom: 16 }}>
        <div style={{ flex: 1, background: "var(--accent)", borderRadius: "var(--radius-lg)", padding: "16px 18px", color: "var(--accent-ink)", boxShadow: "var(--shadow-md)" }}>
          <span style={{ fontSize: 12.5, opacity: .85 }}>Closed Fees · {periodLabel}</span>
          <div className="display mono" style={{ fontSize: 26, lineHeight: 1.1, margin: "5px 0 4px" }}>{_m(closedFees)}</div>
          <span style={{ fontSize: 11.5, opacity: .8 }}>{closed.length} closed deals</span>
        </div>
        <div className="oc-op">+</div>
        <div className="card" style={{ flex: 1, padding: "16px 18px" }}>
          <div className="micro">Signed LOIs</div>
          <div className="display mono" style={{ fontSize: 26, lineHeight: 1.1, margin: "5px 0 4px", color: "var(--accent)" }}>{_m(signed.fee)}</div>
          <span style={{ fontSize: 11.5, color: "var(--muted)" }}>{signed.count} signed</span>
        </div>
        <div className="oc-op">+</div>
        <div className="card tintcard" style={{ flex: 1, padding: "16px 18px" }}>
          <div className="micro">Active offers · potential</div>
          <div className="display mono" style={{ fontSize: 26, lineHeight: 1.1, margin: "5px 0 4px" }}>{_m(active.fee)}</div>
          <span style={{ fontSize: 11.5, color: "var(--muted)" }}>{active.count} offers in play</span>
        </div>
        <div className="oc-op">=</div>
        <div className="card" style={{ flex: 1, padding: "16px 18px", display: "flex", flexDirection: "column", justifyContent: "center" }}>
          <div className="micro" style={{ marginBottom: 4 }}>Projected total fees</div>
          <div className="display mono" style={{ fontSize: 22, lineHeight: 1.1 }}>{_m(closedFees + potential)}</div>
          <span style={{ fontSize: 11, color: "var(--muted)", marginTop: 3 }}>closed + open pipeline{target != null ? ` · ${Math.round(((closedFees + potential) / target) * 100)}% of goal` : ""}</span>
        </div>
      </div>

      {/* fees outlook bar */}
      <div className="card card-pad" style={{ marginBottom: 26 }}>
        <div className="row" style={{ justifyContent: "space-between", alignItems: "center", marginBottom: 10, flexWrap: "wrap", gap: 8 }}>
          <span className="micro seclabel">Fees outlook · closed + pipeline {target != null ? "vs annual goal" : ""}</span>
          <span className="row" style={{ gap: 14, fontSize: 11, color: "var(--ink-2)" }}>
            <span className="row" style={{ gap: 5, alignItems: "center" }}><span style={{ width: 10, height: 10, borderRadius: 2, background: "var(--accent)" }}/>Closed {_m(closedFees)}</span>
            <span className="row" style={{ gap: 5, alignItems: "center" }}><span style={{ width: 10, height: 10, borderRadius: 2, background: "#5AA4D2" }}/>Signed {_m(signed.fee)}</span>
            <span className="row" style={{ gap: 5, alignItems: "center" }}><span style={{ width: 10, height: 10, borderRadius: 2, background: "#9CC3EA" }}/>Active {_m(active.fee)}</span>
          </span>
        </div>
        {(() => {
          const ceil = Math.max(target || 0, closedFees + potential, 1);
          const closedPct = closedFees / ceil, signedPct = signed.fee / ceil, activePct = active.fee / ceil;
          return (
            <div>
              <div style={{ position: "relative", display: "flex", height: 26, borderRadius: 8, background: "var(--surface-2)", overflow: "hidden" }}>
                <div style={{ width: `${closedPct * 100}%`, background: "var(--accent)" }} title={`Closed ${_m(closedFees)}`}/>
                <div style={{ width: `${signedPct * 100}%`, background: "#5AA4D2" }} title={`Signed LOIs ${_m(signed.fee)}`}/>
                <div style={{ width: `${activePct * 100}%`, background: "#9CC3EA" }} title={`Active offers ${_m(active.fee)}`}/>
                {target != null && <div style={{ position: "absolute", top: -3, bottom: -3, left: `${Math.min(100, (target / ceil) * 100)}%`, width: 2, background: "var(--ink)" }} title={`Goal ${_m(target)}`}/>}
              </div>
              <div className="row" style={{ justifyContent: "space-between", marginTop: 6 }}>
                <span className="muted small mono">$0</span>
                {target != null && <span className="mono" style={{ fontSize: 11.5, fontWeight: 600, color: "var(--ink)" }}>Goal {_m(target)}</span>}
                <span className="muted small mono">Potential {_m(closedFees + potential)}</span>
              </div>
            </div>
          );
        })()}
      </div>

      {/* open offers & LOIs */}
      <div className="card card-pad" style={{ marginBottom: 26 }}>
        <div className="row" style={{ justifyContent: "space-between", alignItems: "center", marginBottom: 6, flexWrap: "wrap", gap: 8 }}>
          <h2 className="display seclabel" style={{ margin: 0, fontSize: 18, fontWeight: 400, display: "inline-block" }}>Open Offers &amp; LOIs</h2>
          <div className="row" style={{ gap: 10, alignItems: "center" }}>
            <span className="snap">{parsedCount}/{offers.length} amounts parsed</span>
            <span className="micro" title="Offers sync from the Harvey offers report. Manual add was retired to keep the funnel matching Harvey." style={{ color: "var(--muted)", textTransform: "uppercase", letterSpacing: ".05em" }}>Offers sync from Harvey</span>
          </div>
        </div>
        <div className="row" style={{ gap: 12, flexWrap: "wrap", marginBottom: 10, alignItems: "center" }}>
          <span className="muted" style={{ fontSize: 10.5 }}>Funnel · deals at or past each stage:</span>
          {byStage.map(s => (
            <span key={s.key} className="row" style={{ gap: 6, alignItems: "center", fontSize: 11.5, color: "var(--ink-2)" }}>
              <span className="pill-st" style={{ background: STAGE_COLOR[s.key] }}>{s.label}</span>
              {s.count} · {_m(s.fee)}
            </span>
          ))}
        </div>
        {offers.length === 0
          ? <window.EmptyState title="No open offers or LOIs" hint="Nothing in this scope yet. Offers sync from the Harvey offers report."/>
          : <div style={{ overflowX: "auto" }}>
              <table className="oc freeze-last" style={{ tableLayout: "fixed", width: "100%", minWidth: Object.values(offW.widths).reduce((a, b) => a + (Number(b) || 0), 0) }}>
                <thead><tr>
                  <SortTh col="count" label="#" sort={offSort} setSort={setOffSort} w={offW} sortable={false} num/>
                  <SortTh col="stage" label="Stage" sort={offSort} setSort={setOffSort} w={offW}/>
                  <SortTh col="target" label="Target" sort={offSort} setSort={setOffSort} w={offW}/>
                  <SortTh col="buyer" label="Buyer" sort={offSort} setSort={setOffSort} w={offW}/>
                  <SortTh col="type" label="Type" sort={offSort} setSort={setOffSort} w={offW}/>
                  <SortTh col="tev" label="TEV" sort={offSort} setSort={setOffSort} w={offW} num/>
                  <SortTh col="fee" label="Est. fee" sort={offSort} setSort={setOffSort} w={offW} num/>
                  <SortTh col="offerDate" label="Offer date" sort={offSort} setSort={setOffSort} w={offW}/>
                  <SortTh col="projClose" label="Proj. close" sort={offSort} setSort={setOffSort} w={offW} sortable={false}/>
                  <SortTh col="module" label="Module" sort={offSort} setSort={setOffSort} w={offW}/>
                  <SortTh col="subteam" label="Sub-team" sort={offSort} setSort={setOffSort} w={offW}/>
                  <SortTh col="analyst" label="Analyst" sort={offSort} setSort={setOffSort} w={offW}/>
                  <th aria-hidden="true" style={{ padding: 0 }} />
                  <SortTh col="act" label="" sort={offSort} setSort={setOffSort} w={offW} sortable={false}/>
                </tr></thead>
                <tbody>
                  {stageGroups.map(g => {
                    const collapsed = !!collapsedStages[g.key];
                    return (
                    <React.Fragment key={g.key}>
                      <tr className="stage-group-row" onClick={() => toggleStage(g.key)} style={{ cursor: "pointer" }}>
                        <td colSpan={OFFER_COLSPAN} style={{ padding: 0 }}>
                          <div style={{ display: "flex", alignItems: "center", gap: 10, padding: "7px 10px", borderLeft: `4px solid ${STAGE_COLOR[g.key]}`, background: "var(--canvas, #f4f7fa)" }}>
                            <span className="mono" style={{ fontSize: 11, color: "var(--muted)", width: 12 }}>{collapsed ? "▸" : "▾"}</span>
                            <span style={{ fontWeight: 700, fontSize: 12.5 }}>{g.label}</span>
                            <span className="pill" style={{ fontSize: 10.5 }}>{g.rows.length}</span>
                            <span className="muted" style={{ fontSize: 11.5, marginLeft: "auto" }}>{_m(g.fee)} est. fee</span>
                          </div>
                        </td>
                      </tr>
                      {!collapsed && g.rows.map((o, i) => (
                    <tr key={o.id != null ? o.id : i}>
                      <td className="num" style={{ color: "var(--muted)" }}>{i + 1}</td>
                      <td><span className="pill-st" style={{ background: STAGE_COLOR[o.stage] }}>{OC_STAGE_MAP[o.stage].label}</span></td>
                      <td style={{ maxWidth: 200, overflow: "hidden", textOverflow: "ellipsis" }}>{window.HarveyLink ? <window.HarveyLink companyId={o.targetId} projectId={o.projectId} companyName={o.target}>{o.target}</window.HarveyLink> : o.target}</td>
                      <td style={{ maxWidth: 160, overflow: "hidden", textOverflow: "ellipsis", color: "var(--muted)" }}>{o.buyer}</td>
                      <td>{o.type}</td>
                      <td className="num">{o.tevM != null ? `$${o.tevM.toFixed(1)}M` : <span style={{ color: "var(--muted)" }}>—</span>}</td>
                      <td className="num">{_m(o.fee)}</td>
                      <td style={{ color: "var(--muted)" }}>{o.offerDate || "—"}</td>
                      <td style={{ color: "var(--muted)" }}>{o.projectedClose && !OC_STAGE_MAP[o.projectedClose] ? o.projectedClose : "—"}</td>
                      <td style={{ color: "var(--muted)" }}>{moduleNameOf(o.dm)}</td>
                      <td style={{ color: "var(--ink-2)" }}>{subteamNameOf(o.aa)}</td>
                      <td style={{ color: "var(--ink-2)" }}>{o.analyst ? nameOf(o.analyst) : "—"}</td>
                      <td aria-hidden="true" style={{ padding: 0 }} />
                      <td style={{ whiteSpace: "nowrap", textAlign: "right" }}>{o.id != null && <span className="row" style={{ gap: 4, justifyContent: "flex-end" }}>
                        <button title="Mark as Closed" onClick={() => setClosedForm(promotePrefill(o))} style={{ border: "1px solid var(--accent)", background: "var(--accent-soft)", color: "var(--accent)", borderRadius: 6, padding: "2px 7px", fontSize: 11, cursor: "pointer", fontWeight: 600 }}>Close ✓</button>
                        <button title="Edit" onClick={() => setForm(editPrefill(o))} style={{ border: "1px solid var(--line)", background: "var(--surface)", borderRadius: 6, padding: "2px 7px", fontSize: 11, cursor: "pointer", color: "var(--ink-2)" }}>Edit</button>
                        {canDelete && <button title="Delete" onClick={() => removeOffer(o)} style={{ border: "1px solid var(--line)", background: "var(--surface)", borderRadius: 6, padding: "2px 7px", fontSize: 11, cursor: "pointer", color: "var(--risk)" }}>✕</button>}
                      </span>}</td>
                    </tr>
                      ))}
                    </React.Fragment>
                    );
                  })}
                </tbody>
              </table>
            </div>}
        <div className="muted" style={{ fontSize: 10.5, marginTop: 10, lineHeight: 1.5 }}>
          Est. fee = Standard Fee Formula on the offer's TEV (full value, if it closes). Signed = LOI Executed; Active = IOI / Verbal / LOI Submitted. TEV is best-effort parsed from free text; rows without a parseable amount show "—".
        </div>
      </div>

      {/* closed deals */}
      <div className="card card-pad">
        <div className="row" style={{ justifyContent: "space-between", alignItems: "center", marginBottom: 12 }}>
          <h2 className="display seclabel" style={{ margin: 0, fontSize: 18, fontWeight: 400 }}>Closed Deals · {periodLabel}</h2>
          <div className="row" style={{ gap: 8, alignItems: "center", marginLeft: "auto" }}>
            {dupInfo.extra > 0 && (
              <button onClick={() => setShowRecon(true)} className="pill warn" style={{ cursor: "pointer", border: 0 }}
                title="Review duplicate closed deals and remove extras">
                <span className="dot"/>{dupInfo.extra} likely duplicate{dupInfo.extra > 1 ? "s" : ""} — {closed.length - dupInfo.extra} unique · Review
              </button>
            )}
            {unlinkedCount > 0 && (
              <button onClick={() => setUnlinkedOnly(v => !v)} className={"pill " + (unlinkedOnly ? "warn" : "")}
                style={{ cursor: "pointer", border: unlinkedOnly ? 0 : "1px solid var(--line)", background: unlinkedOnly ? undefined : "var(--surface)", color: unlinkedOnly ? undefined : "var(--muted)" }}
                title="Rows with no Harvey link are legacy manual entries — the likely duplicate / mis-dated rows. Toggle to isolate them for review or deletion.">
                <span className="dot"/>{unlinkedOnly ? "Showing " + unlinkedCount + " unlinked · show all" : unlinkedCount + " unlinked"}
              </button>
            )}
          <span className="micro" title="Closed deals now sync from Harvey via Data Uploads → Import from Harvey CSV, which dedupes on Harvey event id. Manual entry was retired because it created duplicate and mis-dated rows." style={{ color: "var(--muted)", textTransform: "uppercase", letterSpacing: ".05em" }}>Closes sync from Harvey import</span>
          </div>
        </div>
        {loading ? <window.Skeleton rows={6} height={15} gap={12} style={{ margin: "14px 0" }}/>
          : closed.length === 0 ? <window.EmptyState title="No closed deals" hint="Nothing closed in this scope/period."/>
          : <div style={{ overflowX: "auto" }}>
              <table className="oc freeze-last" style={{ tableLayout: "fixed", width: "100%", minWidth: 44 + Object.values(clW.widths).reduce((a, b) => a + (Number(b) || 0), 0) }}>
                <thead><tr>
                  <SortTh col="count" label="#" sort={clSort} setSort={setClSort} w={clW} sortable={false} num/>
                  <SortTh col="name" label="Company" sort={clSort} setSort={setClSort} w={clW}/>
                  <th style={{ width: 44, textAlign: "center" }} title="Harvey link">↗</th>
                  <SortTh col="buyer" label="Buyer" sort={clSort} setSort={setClSort} w={clW}/>
                  <SortTh col="type" label="Type" sort={clSort} setSort={setClSort} w={clW}/>
                  <SortTh col="tev" label="TEV" sort={clSort} setSort={setClSort} w={clW} num/>
                  <SortTh col="fee" label="Fee" sort={clSort} setSort={setClSort} w={clW} num/>
                  <SortTh col="closeDate" label="Close date" sort={clSort} setSort={setClSort} w={clW}/>
                  <SortTh col="module" label="Module" sort={clSort} setSort={setClSort} w={clW}/>
                  <SortTh col="subteam" label="Sub-team" sort={clSort} setSort={setClSort} w={clW}/>
                  <SortTh col="analyst" label="Analyst" sort={clSort} setSort={setClSort} w={clW}/>
                  <th aria-hidden="true" style={{ padding: 0 }} />
                  <SortTh col="act" label="" sort={clSort} setSort={setClSort} w={clW} sortable={false}/>
                </tr></thead>
                <tbody>
                  {closedSorted.map((d, i) => (
                    <tr key={d.id != null ? d.id : i} style={dupInfo.flagged.has(d.id) ? { background: "var(--warn-soft, #fdf6e3)" } : undefined}
                      title={dupInfo.flagged.has(d.id) ? "Possible duplicate — same buyer, fee, and close date as another row" : undefined}>
                      <td className="num" style={{ color: "var(--muted)" }}>{i + 1}</td>
                      <td style={{ maxWidth: 210, overflow: "hidden", textOverflow: "ellipsis" }}>{window.HarveyLink ? <window.HarveyLink companyId={d.harveyTargetId} projectId={d.harveyProjectId} companyName={d.name}>{d.name}</window.HarveyLink> : d.name}</td>
                      <td style={{ textAlign: "center" }} title={isLinked(d) ? "Linked to Harvey" : "No Harvey link — legacy manual row"}>
                        {isLinked(d) ? <span style={{ color: "var(--accent, #196EA7)", fontWeight: 700 }}>↗</span> : <span style={{ color: "var(--risk, #b3402e)", opacity: .55 }}>—</span>}
                      </td>
                      <td style={{ maxWidth: 160, overflow: "hidden", textOverflow: "ellipsis", color: "var(--muted)" }}>{d.buyer}</td>
                      <td>{d.type}</td>
                      <td className="num">{d.tev != null ? _tevM(d.tev) : <span style={{ color: "var(--muted)" }}>—</span>}</td>
                      <td className="num" style={{ fontWeight: 600 }}>{_m(d.fee)}</td>
                      <td style={{ color: "var(--muted)" }}>{_d10(d.closeDate)}</td>
                      <td style={{ color: "var(--muted)" }}>{moduleNameOf(d.dm1)}</td>
                      <td style={{ color: "var(--ink-2)" }}>{subteamNameOf(d.dm2)}</td>
                      <td style={{ color: "var(--ink-2)" }}>{d.dm3 ? nameOf(d.dm3) : "—"}</td>
                      <td aria-hidden="true" style={{ padding: 0 }} />
                      <td style={{ whiteSpace: "nowrap", textAlign: "right" }}><span className="row" style={{ gap: 4, justifyContent: "flex-end" }}>
                        <button title="Edit" onClick={() => setClosedForm(closedEditPrefill(d))} style={{ border: "1px solid var(--line)", background: "var(--surface)", borderRadius: 6, padding: "2px 7px", fontSize: 11, cursor: "pointer", color: "var(--ink-2)" }}>Edit</button>
                        {canDelete && <button title="Delete" onClick={() => removeClosedDeal(d)} style={{ border: "1px solid var(--line)", background: "var(--surface)", borderRadius: 6, padding: "2px 7px", fontSize: 11, cursor: "pointer", color: "var(--risk)" }}>✕</button>}
                      </span></td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>}
      </div>

      {form && <OfferForm value={form} people={modulePeople} saving={saving} onChange={setForm} onSave={saveOffer} onClose={() => setForm(null)} />}
      {closedForm && <ClosedDealForm value={closedForm} people={allPeople} saving={savingClosed} onChange={setClosedForm} onSave={saveClosedDeal} onClose={() => setClosedForm(null)} />}
      {showRecon && <ReconPanel groups={dupInfo.groups} onRemove={removeReconRow} busy={reconBusy} canDelete={canDelete} nameOf={nameOf} moduleNameOf={moduleNameOf} onClose={() => setShowRecon(false)} />}
    </div>
  );
}

// Module-level so React keeps a stable component identity across form re-renders.
// (Defining Field INSIDE the form remounts every input on each keystroke -> focus loss.)
function Field({ label, children }) { return (<label style={{ display: "block" }}><div className="micro" style={{ marginBottom: 4 }}>{label}</div>{children}</label>); }
function mdyToISO(v) { const s = String(v || "").trim(); if (!s) return ""; if (/^\d{4}-\d{2}-\d{2}$/.test(s)) return s; const m = s.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/); return m ? m[3] + "-" + m[1].padStart(2, "0") + "-" + m[2].padStart(2, "0") : ""; }
function isoToMDY(v) { const m = String(v || "").match(/^(\d{4})-(\d{2})-(\d{2})$/); return m ? m[2] + "/" + m[3] + "/" + m[1] : (v || ""); }

function OfferForm({ value, people, saving, onChange, onSave, onClose }) {
  const set = (k, v) => onChange(Object.assign({}, value, { [k]: v }));
  const inp = { width: "100%", padding: "7px 9px", fontSize: 13, border: "1px solid var(--line)", borderRadius: 7, background: "var(--surface)", color: "var(--ink)" };
  return (
    <div onClick={onClose} style={{ position: "fixed", inset: 0, background: "rgba(15,30,50,.35)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 60 }}>
      <div onClick={e => e.stopPropagation()} className="card" style={{ width: 470, maxWidth: "92vw", maxHeight: "88vh", overflow: "auto", padding: 20 }}>
        <h2 className="display" style={{ margin: "0 0 14px", fontSize: 18, fontWeight: 600 }}>{value.id ? "Edit offer" : "Add offer"}</h2>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
          <Field label="Target company"><input style={inp} value={value.target} onChange={e => set("target", e.target.value)}/></Field>
          <Field label="Buyer"><input style={inp} value={value.buyer} onChange={e => set("buyer", e.target.value)}/></Field>
          <Field label="Module lead"><select style={inp} value={value.dm} onChange={e => set("dm", e.target.value)}><option value="">—</option>{people.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}</select></Field>
          <Field label="Sub-team lead"><select style={inp} value={value.aa} onChange={e => set("aa", e.target.value)}><option value="">—</option>{people.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}</select></Field>
          <Field label="Analyst"><select style={inp} value={value.analyst} onChange={e => set("analyst", e.target.value)}><option value="">—</option>{people.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}</select></Field>
          <Field label="Stage"><select style={inp} value={value.stage} onChange={e => set("stage", e.target.value)}>{window.VaultOffersEV.STAGES.map(s => <option key={s.key} value={s.key}>{s.label}</option>)}</select></Field>
          <Field label="Type"><select style={inp} value={value.type} onChange={e => set("type", e.target.value)}><option>Add-on</option><option>Platform</option></select></Field>
          <Field label="Amount (TEV, e.g. $8.5M)"><input style={inp} placeholder="$8.5M" value={value.amount} onChange={e => set("amount", e.target.value)}/></Field>
          <Field label="Offer date"><input style={inp} type="date" value={mdyToISO(value.offer_date)} onChange={e => set("offer_date", isoToMDY(e.target.value))}/></Field>
          <Field label="Projected close"><input style={inp} type="date" value={mdyToISO(value.projected_close)} onChange={e => set("projected_close", isoToMDY(e.target.value))}/></Field>
        </div>
        <div className="row" style={{ justifyContent: "flex-end", gap: 8, marginTop: 16 }}>
          <button onClick={onClose} style={{ padding: "7px 14px", border: "1px solid var(--line)", borderRadius: 8, background: "var(--surface)", color: "var(--ink-2)", fontSize: 13, cursor: "pointer" }}>Cancel</button>
          <button disabled={saving} onClick={() => onSave(value)} style={{ padding: "7px 16px", border: 0, borderRadius: 8, background: "var(--accent)", color: "var(--accent-ink)", fontSize: 13, fontWeight: 600, cursor: saving ? "default" : "pointer", opacity: saving ? .6 : 1 }}>{saving ? "Saving…" : (value.id ? "Save" : "Add")}</button>
        </div>
      </div>
    </div>
  );
}

// Reconciliation panel — lists duplicate closed-deal groups and lets the user
// remove the stale copy one click at a time. Recommends the event-id row as
// the keeper; every deletion is an explicit per-row action (no batch delete).
function ReconPanel({ groups, onRemove, busy, canDelete, nameOf, moduleNameOf, onClose }) {
  const fmtFee = v => v == null ? "—" : "$" + (Number(v) >= 1000 ? Math.round(Number(v) / 1000) + "K" : Number(v));
  const remaining = groups.filter(g => g.rows.length > 1);
  return (
    <div onClick={onClose} style={{ position: "fixed", inset: 0, background: "rgba(15,30,45,.35)", zIndex: 500, display: "flex", justifyContent: "center", alignItems: "flex-start", overflowY: "auto", padding: "40px 16px" }}>
      <div onClick={e => e.stopPropagation()} style={{ background: "var(--surface, #fff)", borderRadius: 12, width: "min(860px, 100%)", boxShadow: "0 20px 60px rgba(0,0,0,.25)", overflow: "hidden" }}>
        <div className="row" style={{ padding: "14px 18px", borderBottom: "1px solid var(--line-2)", alignItems: "center" }}>
          <div>
            <div className="display" style={{ fontSize: 17, fontWeight: 600 }}>Review Duplicate Closed Deals</div>
            <div className="micro" style={{ marginTop: 2, textTransform: "uppercase", letterSpacing: ".05em" }}>{groups.length} group{groups.length !== 1 ? "s" : ""} · keep the Harvey-linked row, remove the manual copy</div>
          </div>
          <button onClick={onClose} style={{ marginLeft: "auto", border: "none", background: "transparent", fontSize: 20, cursor: "pointer", color: "var(--muted)" }}>×</button>
        </div>
        <div style={{ maxHeight: "64vh", overflowY: "auto", padding: "8px 0" }}>
          {groups.length === 0 && <div className="muted small" style={{ padding: "24px 18px", textAlign: "center" }}>No duplicate groups remaining. 🎉</div>}
          {groups.map((g, gi) => (
            <div key={gi} style={{ padding: "10px 18px", borderBottom: "1px solid var(--line-2)" }}>
              <div className="row" style={{ gap: 8, marginBottom: 6, alignItems: "center" }}>
                <span className="pill" style={{ fontSize: 10, background: g.kind === "exact" ? "var(--danger-soft, #fdecea)" : "var(--warn-soft, #fdf6e3)", color: g.kind === "exact" ? "var(--danger, #b3402e)" : "var(--warn, #9a7213)" }}>
                  {g.kind === "exact" ? "EXACT DUP" : "SAME DEAL · DIFF DATE"}
                </span>
                <span style={{ fontWeight: 700, fontSize: 13 }}>{g.rows[0].name}</span>
                <span className="muted small">{g.rows[0].buyer} · {fmtFee(g.rows[0].fee)}</span>
              </div>
              <table className="dt" style={{ fontSize: 12, width: "100%" }}>
                <tbody>
                  {g.rows.map(r => {
                    const isKeep = r.id === g.keepId;
                    const isRemoveRec = g.removeIds.includes(r.id);
                    return (
                      <tr key={r.id} style={isKeep ? { background: "var(--ok-soft, #eaf6ef)" } : undefined}>
                        <td style={{ width: 90 }}>
                          {isKeep ? <span className="pill ok" style={{ fontSize: 10 }}><span className="dot"/>KEEP</span>
                            : isRemoveRec ? <span className="pill warn" style={{ fontSize: 10 }}><span className="dot"/>REMOVE</span>
                            : <span className="muted micro">review</span>}
                        </td>
                        <td>{r.closeDate}</td>
                        <td className="mono" style={{ color: "var(--muted)", fontSize: 11 }}>{r.harveyEventId ? "eid ✓" : "manual"}</td>
                        <td style={{ color: "var(--muted)" }}>{r.analyst ? nameOf(r.analyst) : (r.dm2 ? nameOf(r.dm2) : "—")}</td>
                        <td className="mono" style={{ fontSize: 10.5, color: "var(--muted)" }}>{String(r.id).slice(0, 16)}</td>
                        <td style={{ textAlign: "right", width: 90 }}>
                          {canDelete && !isKeep && (
                            <button disabled={busy === r.id} onClick={() => onRemove(r.id)}
                              style={{ border: "1px solid var(--risk, #b3402e)", background: isRemoveRec ? "var(--risk, #b3402e)" : "var(--surface)", color: isRemoveRec ? "#fff" : "var(--risk, #b3402e)", borderRadius: 6, padding: "3px 10px", fontSize: 11, fontWeight: 600, cursor: "pointer", opacity: busy === r.id ? 0.5 : 1 }}>
                              {busy === r.id ? "…" : "Remove"}
                            </button>
                          )}
                        </td>
                      </tr>
                    );
                  })}
                </tbody>
              </table>
              {!g.decidable && <div className="micro" style={{ marginTop: 4, color: "var(--warn, #9a7213)" }}>Neither row is Harvey-linked — verify in Harvey before removing either.</div>}
            </div>
          ))}
        </div>
        <div className="row" style={{ padding: "12px 18px", borderTop: "1px solid var(--line-2)" }}>
          <span className="muted small">Removals are permanent and take effect immediately. The Harvey-linked (eid ✓) row is the authoritative record.</span>
          <button onClick={onClose} style={{ marginLeft: "auto", background: "var(--accent)", color: "var(--accent-ink, #fff)", border: 0, borderRadius: 8, padding: "6px 14px", fontSize: 12.5, fontWeight: 600, cursor: "pointer" }}>Done</button>
        </div>
      </div>
    </div>
  );
}

window.OffersClosedScreen = OffersClosedScreen;

function ClosedDealForm({ value, people, saving, onChange, onSave, onClose }) {
  const set = (k, v) => onChange(Object.assign({}, value, { [k]: v }));
  const inp = { width: "100%", padding: "7px 9px", fontSize: 13, border: "1px solid var(--line)", borderRadius: 7, background: "var(--surface)", color: "var(--ink)" };
  const canSave = String(value.name || "").trim() && value.dm1;
  const isPromote = !!value._sourceOfferId;
  return (
    <div onClick={onClose} style={{ position: "fixed", inset: 0, background: "rgba(15,30,50,.35)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 60 }}>
      <div onClick={e => e.stopPropagation()} className="card" style={{ width: 500, maxWidth: "92vw", maxHeight: "88vh", overflow: "auto", padding: 20 }}>
        <h2 className="display" style={{ margin: "0 0 4px", fontSize: 18, fontWeight: 600 }}>{value.id ? "Edit closed deal" : (isPromote ? "Mark offer as Closed" : "Record closed deal")}</h2>
        {isPromote && <div className="muted small" style={{ marginBottom: 12, lineHeight: 1.45 }}>Pre-filled from the offer. Fee is the Standard-Formula estimate — adjust to the booked fee. The offer is kept and marked Closed (drops out of the open list &amp; EV).</div>}
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12, marginTop: isPromote ? 0 : 12 }}>
          <Field label="Target company *"><input style={inp} value={value.name} onChange={e => set("name", e.target.value)}/></Field>
          <Field label="Buyer"><input style={inp} value={value.buyer} onChange={e => set("buyer", e.target.value)}/></Field>
          <Field label="TEV ($M)"><input style={inp} type="number" step="0.1" value={value.tev} onChange={e => set("tev", e.target.value)}/></Field>
          <Field label="Fee (USD)"><input style={inp} type="number" step="1000" value={value.fee} onChange={e => set("fee", e.target.value)}/></Field>
          <Field label="Close date"><input style={inp} type="date" value={value.closeDate} onChange={e => set("closeDate", e.target.value)}/></Field>
          <Field label="Type"><select style={inp} value={value.type} onChange={e => set("type", e.target.value)}><option value="add-on">Add-on</option><option value="platform">Platform</option><option value="placement">Placement</option><option value="earnout">Earnout</option></select></Field>
          <Field label="Module lead (DM1) *"><select style={inp} value={value.dm1} onChange={e => set("dm1", e.target.value)}><option value="">—</option>{people.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}</select></Field>
          <Field label="Sub-team lead (DM2)"><select style={inp} value={value.dm2} onChange={e => set("dm2", e.target.value)}><option value="">—</option>{people.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}</select></Field>
          <Field label="Analyst (DM3)"><select style={inp} value={value.dm3} onChange={e => set("dm3", e.target.value)}><option value="">—</option>{people.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}</select></Field>
        </div>
        <div className="row" style={{ justifyContent: "flex-end", gap: 8, marginTop: 16 }}>
          <button onClick={onClose} style={{ padding: "7px 14px", border: "1px solid var(--line)", borderRadius: 8, background: "var(--surface)", color: "var(--ink-2)", fontSize: 13, cursor: "pointer" }}>Cancel</button>
          <button disabled={!canSave || saving} onClick={() => onSave(value)} style={{ padding: "7px 16px", border: 0, borderRadius: 8, background: "var(--accent)", color: "var(--accent-ink)", fontSize: 13, fontWeight: 600, cursor: (!canSave || saving) ? "default" : "pointer", opacity: (!canSave || saving) ? .6 : 1 }}>{saving ? "Saving…" : (value.id ? "Save" : (isPromote ? "Mark Closed" : "Record"))}</button>
        </div>
      </div>
    </div>
  );
}