// screens-project-health.jsx — unified project page (single scroll, Lifetime/Current toggle):
//   rings + Deal Fees / Contact Rate / Historical Lead Rate, Closed Deals table,
//   activity counts, outreach-over-time, conversion funnel + days-since-last.
//
// Score components: window.VaultProjectScore.scoreBoth(ctx)
// Per-project economics: window.VaultOffersEV.forProject(nameHints, year)
// Activity: VaultAPI.getProjectWeekly (per project)
// Real firm terms: Universe (No Interest), Stale Leads, Historical Lead Rate, Contact Rate.

const { useState, useEffect, useMemo } = React;

function _money(n) {
  if (n == null) return "—"; if (n === 0) return "$0";
  const a = Math.abs(n);
  if (a >= 1e6) return `$${(n / 1e6).toFixed(2)}M`;
  if (a >= 1e3) return `$${(n / 1e3).toFixed(0)}K`;
  return `$${Math.round(n).toLocaleString()}`;
}
function _pct(n) { return n == null ? "—" : `${Math.round(n)}%`; }
function _n0(n) { return n == null ? "—" : Math.round(n); }
function _personName(id) {
  const F = window.VAULT_FIRM;
  const p = F && F.PEOPLE_BY_ID ? F.PEOPLE_BY_ID[id] : null;
  return p ? p.name : (id || "—");
}
function _band(score) {
  if (score == null) return { c: "#93A1B0", l: "#F4F7FA", t: "—" };
  if (score >= 80) return { c: "#1E7A1E", l: "#E3F0D8", t: "Top Project" };
  if (score >= 65) return { c: "#3B6D11", l: "#EAF3DE", t: "Prioritize" };
  if (score >= 50) return { c: "#0C447C", l: "#E3EEF6", t: "Standard" };
  if (score >= 35) return { c: "#854F0B", l: "#FAEEDA", t: "Struggling" };
  return { c: "#A32D2D", l: "#FCEBEB", t: "De-Prioritize" };
}

// Surfaces the 1–3 pillars dragging the score most, as plain-language reasons.
// "Drag" = pillar weight × (100 − pillar score), so a heavily-weighted weak pillar
// outranks a lightly-weighted one. Only meaningful drags (>= 8) are shown.
function _diagnostics(cur) {
  if (!cur) return [];
  const W = { economics: 0.40, funnel: 0.30, momentum: 0.18, coverage: 0.12 };
  const ranked = Object.keys(W)
    .map(k => ({ k, s: cur[k] && cur[k].score, d: (cur[k] && cur[k].score != null) ? W[k] * (100 - cur[k].score) : 0 }))
    .filter(x => x.d >= 8)
    .sort((a, b) => b.d - a.d);
  const out = [];
  for (const { k } of ranked) {
    if (k === "economics") {
      const e = cur.economics || {};
      if (!(e.realizedFee > 0) && !(e.signedFee > 0) && !(e.weightedEV > 0)) out.push("No closed deals or pipeline");
      else if (!(e.signedFee > 0) && !(e.weightedEV > 0)) out.push("Little late-stage pipeline");
      else out.push("Light economics");
    } else if (k === "funnel") {
      const r = (cur.funnel && cur.funnel.rates) || {};
      if (cur.funnel && cur.funnel.stub) out.push("No approval-list activity");
      else if ((r.offer || 0) < 0.02 && (r.visit || 0) < 0.05) out.push("Little late-stage progression");
      else out.push("Weak funnel conversion");
    } else if (k === "momentum") {
      const pp = (cur.momentum && cur.momentum.parts) || {};
      const stale = key => { const p = pp[key]; return !p || p.days == null || p.days > 90; };
      out.push(stale("offer") && stale("cc") && stale("visit") ? "Late-stage activity gone quiet" : "Momentum has stalled");
    } else if (k === "coverage") {
      out.push("Thin universe coverage");
    }
  }
  return out.slice(0, 3);
}
const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

function Ring({ score, label, size }) {
  const S = size || 116, R = (S - 22) / 2, C = 2 * Math.PI * R, pct = (score || 0) / 100;
  const b = _band(score);
  return (
    <div style={{ position: "relative", width: S, height: S, flex: "0 0 auto" }}>
      <svg width={S} height={S} style={{ transform: "rotate(-90deg)" }}>
        <circle cx={S / 2} cy={S / 2} r={R} fill="none" stroke="#EEF2F6" strokeWidth="10" />
        <circle cx={S / 2} cy={S / 2} r={R} fill="none" stroke={b.c} strokeWidth="10" strokeLinecap="round"
          strokeDasharray={C} strokeDashoffset={C * (1 - pct)} />
      </svg>
      <div style={{ position: "absolute", inset: 0, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center" }}>
        <span style={{ fontSize: S <= 96 ? 22 : 30, fontWeight: 600, lineHeight: 1, color: "#1A2733" }}>{score == null ? "—" : score}</span>
        <span style={{ fontSize: 10, color: "#5A6B7B", marginTop: 4, textTransform: "uppercase", letterSpacing: ".4px", fontWeight: 600 }}>{label}</span>
      </div>
    </div>
  );
}

const CARD = { background: "#fff", border: "1px solid #E4E9F0", borderRadius: 10, padding: "14px 16px" };
const PANEL = { background: "#fff", border: "1px solid #E4E9F0", borderRadius: 14, padding: "20px 22px" };
const SECTION_LABEL = { fontSize: 11, fontWeight: 700, letterSpacing: ".6px", textTransform: "uppercase", color: "#93A1B0", margin: "26px 0 12px", display: "flex", alignItems: "center", gap: 8 };

function WeightTag({ pct }) {
  return <span style={{ fontSize: 10, fontWeight: 600, color: "#196EA7", background: "#E6F1FB", padding: "2px 8px", borderRadius: 5 }}>{pct}% of score</span>;
}

function LineChart({ labels, series, height, animKey }) {
  const w = 760, h = height || 220, pad = { l: 38, r: 14, t: 14, b: 26 };
  const iw = w - pad.l - pad.r, ih = h - pad.t - pad.b;
  const maxV = Math.max(1, ...series.flatMap(s => s.data));
  const top = maxV <= 6 ? Math.max(1, Math.ceil(maxV)) : maxV;
  const x = (i) => pad.l + (labels.length <= 1 ? iw / 2 : (i / (labels.length - 1)) * iw);
  const y = (v) => pad.t + ih - (v / top) * ih;
  const ticks = top <= 6 ? Array.from({ length: top + 1 }, (_, k) => k) : [0, Math.round(top / 2), top];
  return (
    <svg key={animKey} viewBox={`0 0 ${w} ${h}`} style={{ width: "100%", height: "auto", display: "block" }}>
      {ticks.map((tv, i) => (<g key={i}>
        <line x1={pad.l} x2={w - pad.r} y1={y(tv)} y2={y(tv)} stroke="#EEF2F6" />
        <text x={pad.l - 6} y={y(tv) + 3} fontSize="10" fill="#93A1B0" textAnchor="end">{Math.round(tv)}</text>
      </g>))}
      {labels.map((lab, i) => <text key={i} x={x(i)} y={h - 8} fontSize="9.5" fill={lab === "Jan" ? "#5A6B7B" : "#93A1B0"} fontWeight={lab === "Jan" ? 700 : 400} textAnchor="middle">{lab}</text>)}
      {series.map((s, si) => (
        <g key={si}>
          <polyline className="ph-line" pathLength="1" style={{ animationDelay: `${100 + si * 120}ms` }}
            points={s.data.map((v, i) => `${x(i)},${y(v)}`).join(" ")} fill="none" stroke={s.color} strokeWidth="2.5" strokeLinejoin="round" strokeLinecap="round" />
          {s.data.map((v, i) => <circle key={i} className="ph-dot" style={{ animationDelay: `${300 + si * 120 + i * 25}ms` }} cx={x(i)} cy={y(v)} r="2.5" fill={s.color} />)}
        </g>
      ))}
    </svg>
  );
}
function BarPairChart({ labels, series, height, animKey }) {
  const w = 420, h = height || 220, pad = { l: 30, r: 10, t: 14, b: 26 };
  const iw = w - pad.l - pad.r, ih = h - pad.t - pad.b;
  const maxV = Math.max(1, ...series.flatMap(s => s.data));
  const top = maxV <= 6 ? Math.max(1, Math.ceil(maxV)) : maxV;
  const groupW = iw / Math.max(1, labels.length), barW = Math.min(14, (groupW - 6) / 2);
  const ticks = top <= 6 ? Array.from({ length: top + 1 }, (_, k) => k) : [0, Math.round(top / 2), top];
  return (
    <svg key={animKey} viewBox={`0 0 ${w} ${h}`} preserveAspectRatio="none" style={{ width: "100%", height: h, display: "block" }}>
      {ticks.map((tv, i) => { const yy = pad.t + ih - (tv / top) * ih; return (<g key={i}>
        <line x1={pad.l} x2={w - pad.r} y1={yy} y2={yy} stroke="#EEF2F6" />
        <text x={pad.l - 6} y={yy + 3} fontSize="10" fill="#93A1B0" textAnchor="end">{Math.round(tv)}</text>
      </g>); })}
      {labels.map((lab, gi) => {
        const gx = pad.l + gi * groupW + groupW / 2;
        return (<g key={gi}>
          {series.map((s, si) => {
            const v = s.data[gi] || 0, bh = (v / top) * ih, bx = gx - barW + si * barW;
            return <rect key={si} className="ph-bar" style={{ animationDelay: `${100 + gi * 40 + si * 20}ms` }} x={bx} y={pad.t + ih - bh} width={barW - 2} height={bh} fill={s.color} rx="1.5" />;
          })}
          <text x={gx} y={h - 8} fontSize="9.5" fill={lab === "Jan" ? "#5A6B7B" : "#93A1B0"} fontWeight={lab === "Jan" ? 700 : 400} textAnchor="middle">{lab}</text>
        </g>);
      })}
    </svg>
  );
}

// windowed paging hook: show `size` most-recent periods, arrows to scroll
function usePager(total, size) {
  const [end, setEnd] = useState(total);  // exclusive end index
  useEffect(() => { setEnd(total); }, [total]);
  const e = Math.min(end, total), s = Math.max(0, e - size);
  const canPrev = s > 0, canNext = e < total;
  return {
    start: s, end: e,
    prev: () => canPrev && setEnd(Math.max(size, e - size)),
    next: () => canNext && setEnd(Math.min(total, e + size)),
    canPrev, canNext,
  };
}
function PagerArrows({ pager, label }) {
  const btn = (on, dir, fn) => (
    <button onClick={fn} disabled={!on} style={{
      border: "1px solid #E4E9F0", background: on ? "#fff" : "#F8FAFC", color: on ? "#196EA7" : "#C7D0DA",
      width: 26, height: 26, borderRadius: 7, cursor: on ? "pointer" : "default", fontSize: 13, lineHeight: 1,
      display: "inline-flex", alignItems: "center", justifyContent: "center", fontFamily: "inherit",
    }}>{dir}</button>
  );
  return (
    <div style={{ display: "inline-flex", alignItems: "center", gap: 6 }}>
      {label && <span style={{ fontSize: 11, color: "#93A1B0", marginRight: 2 }}>{label}</span>}
      {btn(pager.canPrev, "‹", pager.prev)}
      {btn(pager.canNext, "›", pager.next)}
    </div>
  );
}

const METRICS = [
  { key: "calls", label: "Calls", color: "#196EA7" },
  { key: "mailings", label: "Mailings", color: "#E0A92E" },
  { key: "leads", label: "Leads", color: "#5FA0CF" },
  { key: "confCalls", label: "Conf Calls", color: "#0C447C" },
  { key: "visits", label: "Visits", color: "#6FAE54" },
  { key: "offers", label: "Offers", color: "#C77DBB" },
  { key: "closed", label: "Closed", color: "#3B6D11" },
];

function ProjectHealthScreen(props) {
  const forcedId = props && props.projectId != null ? props.projectId : null;
  const viewerUser = (props && props.user) || null;
  const forcedName = props && props.projectName ? props.projectName : null;
  const embedded = !!(props && props.embedded);
  const isMobile = window.useIsMobile();

  // Viewer's module team — used to attribute closed deals ("just our fees"). A close
  // counts for a project only if a member of the viewer's module worked it (dm1/2/3).
  // This keeps shared-platform closes (e.g. Citadel) scoped to the viewer's own team.
  const viewerTeam = useMemo(() => {
    const F = window.VAULT_FIRM;
    if (!F) return [];
    const raw = (viewerUser && (viewerUser.personId || viewerUser.id)) || "";
    const head = String(raw).replace(/^p_/, "").replace(/_gmail$/, "");
    if (!head) return [];
    // everyone whose subteam rolls up to this module head
    const roster = (F.PEOPLE || []).filter(p => p.subteam === ("st-" + head)).map(p => p.id);
    return roster.length ? roster : [head];
  }, [viewerUser]);
  const [projects, setProjects] = useState(null);
  const [pid, setPid] = useState(forcedId);
  const [bundle, setBundle] = useState(null);
  const [loading, setLoading] = useState(false);
  const [err, setErr] = useState(null);
  const [view, setView] = useState("life");
  const [tab, setTab] = useState("score");
  const [metric, setMetric] = useState("calls");
  const [closedVer, setClosedVer] = useState(0);  // bumps when live closed deals swap in (data-closed-loader)

  // Recompute realized fees when the closed-deals loader replaces window.HARVEY_CLOSED.
  useEffect(() => {
    const h = () => setClosedVer(v => v + 1);
    window.addEventListener("vault:closed-updated", h);
    return () => window.removeEventListener("vault:closed-updated", h);
  }, []);

  // keep pid in sync if the parent switches the forced project
  useEffect(() => { if (forcedId != null) setPid(forcedId); }, [forcedId]);

  useEffect(() => {
    if (forcedId != null) return;   // embedded: no picker needed
    (async () => {
      try {
        const list = await window.VaultAPI.listBookProjects();
        const sorted = (list || []).slice().sort((a, b) =>
          (a.project_id === 47701 ? -1 : b.project_id === 47701 ? 1 : (b.universe_size || 0) - (a.universe_size || 0)));
        setProjects(sorted);
        if (pid == null) setPid(sorted.length ? sorted[0].project_id : null);
      } catch (e) { setErr(String(e.message || e)); }
    })();
  }, [forcedId]);

  useEffect(() => {
    if (pid == null) return;
    setLoading(true); setErr(null); setBundle(null);
    (async () => {
      try { setBundle(await window.VaultAPI.getProjectHealthBundle(pid)); }
      catch (e) { setErr(String(e.message || e)); }
      finally { setLoading(false); }
    })();
  }, [pid]);

  const meta = bundle && bundle.meta ? bundle.meta : {};
  const projName = (meta.project_name) || forcedName || (projects && (projects.find(p => p.project_id === pid) || {}).project_name) || `Project ${pid}`;

  const result = useMemo(() => {
    if (!bundle) return null;
    const baseName = String(projName).replace(/\s*\([^)]*\)\s*$/, "").trim();
    const buyerId = bundle.meta ? bundle.meta.buyer_id : null;
    const projTeam = bundle.meta ? [bundle.meta.director, bundle.meta.analyst, bundle.meta.dm2].filter(Boolean) : [];
    const teamForClose = viewerTeam.length ? viewerTeam : projTeam;
    const closedAll = (window.VaultOffersEV && window.VaultOffersEV.closedForProject)
      ? window.VaultOffersEV.closedForProject(pid, baseName, teamForClose, null).deals : [];
    const ctx = {
      universeRows: bundle.universe || [],
      scopeModule: "BrianScott",
      lastByType: bundle.last_actions || {},
      lists: bundle.lists || [],
      asOf: new Date(),
      economicsHints: [baseName].filter(Boolean),
      economicsBuyerId: buyerId,
      closed: closedAll,
    };
    try { return window.VaultProjectScore.scoreBoth(ctx); }
    catch (e) { console.error("score error", e); return null; }
  }, [bundle, projName, pid, viewerTeam, closedVer]);

  const econ = useMemo(() => {
    if (!bundle) return null;
    const baseName = String(projName).replace(/\s*\([^)]*\)\s*$/, "").trim();
    const buyerId = bundle.meta ? bundle.meta.buyer_id : null;
    const yr = view === "cur" ? new Date().getFullYear() : null;
    const ev = (window.VaultOffersEV && window.VaultOffersEV.forProject)
      ? window.VaultOffersEV.forProject([baseName], yr, buyerId) : null;
    if (!ev) return null;
    // Realized fees from closed deals (strongest economic signal).
    const projTeam = bundle.meta ? [bundle.meta.director, bundle.meta.analyst, bundle.meta.dm2].filter(Boolean) : [];
    const teamForClose = viewerTeam.length ? viewerTeam : projTeam;
    const closed = (window.VaultOffersEV && window.VaultOffersEV.closedForProject)
      ? window.VaultOffersEV.closedForProject(pid, baseName, teamForClose, yr)
      : { count: 0, fee: 0, deals: [] };
    return Object.assign({}, ev, { closed });
  }, [bundle, projName, view, pid, viewerTeam, closedVer]);

  const cur = result ? (view === "life" ? result.lifetime : result.current) : null;

  const universeHealth = useMemo(() => {
    if (!bundle) return null;
    const rows = (bundle.universe || []).filter(r => (r.module || "") === "BrianScott");
    const approved = rows.filter(r => r.approved);
    const nApproved = approved.length;
    const everLead = rows.filter(r => Number(r.high_status) >= 4.7);
    const nowLead = rows.filter(r => Number(r.current_status) >= 4.7);
    const staleLeads = everLead.filter(r => Number(r.current_status) < 4.7);
    const noInterest = approved.filter(r => Number(r.high_status) < 4.7);
    // Contact rate off UNIQUE universe rows — summed list counts double-count companies
    // that appear on multiple lists and can exceed 100%. "Of approved, how many we've ever
    // contacted (high_status >= 4.0)." Bounded 0–100 by construction.
    const contacted = approved.filter(r => Number(r.high_status) >= 4.0);
    const contactRate = nApproved > 0 ? (contacted.length / nApproved) * 100 : null;
    const histLeadRate = nApproved > 0 ? (everLead.length / nApproved) * 100 : null;
    return {
      universeCount: rows.length, contactRate, histLeadRate,
      staleLeads: staleLeads.length, noInterest: noInterest.length, nowLead: nowLead.length,
    };
  }, [bundle]);

  const [weekly, setWeekly] = useState([]);
  const [loadingWk, setLoadingWk] = useState(false);
  useEffect(() => {
    if (pid == null) return;   // fetch eagerly when project loads, so the Activity
    // tab is already populated when switched to (prevents a height-collapse + scroll jump)
    let cancel = false; setLoadingWk(true);
    window.VaultAPI.getProjectWeekly(pid, "2023-01-01", "2027-12-31")
      .then(rows => { if (!cancel) { setWeekly(rows || []); setLoadingWk(false); } })
      .catch(() => { if (!cancel) setLoadingWk(false); });
    return () => { cancel = true; };
  }, [pid]);
  const monthly = useMemo(() => {
    const yr = view === "cur" ? String(new Date().getFullYear()) : null;
    const m = {};
    weekly.forEach(w => {
      if (yr && (w.week || "").slice(0, 4) !== yr) return;
      const key = (w.week || "").slice(0, 7); if (!key) return;
      if (!m[key]) m[key] = { calls: 0, mailings: 0, leads: 0, confCalls: 0, visits: 0, offers: 0, closed: 0 };
      ["calls", "mailings", "leads", "confCalls", "visits", "offers", "closed"].forEach(k => m[key][k] += w[k] || 0);
    });
    const keys = Object.keys(m).sort();
    return {
      keys,
      labels: keys.map(k => MONTHS[parseInt(k.slice(5, 7), 10) - 1]),
      years: keys.map(k => k.slice(0, 4)),
      data: keys.map(k => m[k]),
    };
  }, [weekly, view]);
  const tot = useMemo(() => {
    const yr = view === "cur" ? String(new Date().getFullYear()) : null;
    const t = { calls: 0, mailings: 0, leads: 0, confCalls: 0, visits: 0, offers: 0, closed: 0 };
    weekly.forEach(w => { if (yr && (w.week || "").slice(0, 4) !== yr) return; Object.keys(t).forEach(k => t[k] += w[k] || 0); });
    return t;
  }, [weekly, view]);

  // windowed paging — show the last PAGE_SIZE months, arrows to scroll history
  const PAGE_SIZE = 12;
  const pager = usePager(monthly.labels.length, PAGE_SIZE);
  const win = useMemo(() => {
    const years = monthly.years.slice(pager.start, pager.end);
    const uniq = Array.from(new Set(years));
    return {
      labels: monthly.labels.slice(pager.start, pager.end),
      data: monthly.data.slice(pager.start, pager.end),
      yearSpan: uniq.join(" · "),
      key: `${pager.start}-${pager.end}`,
    };
  }, [monthly, pager.start, pager.end]);

  return (
    <div style={{ maxWidth: embedded ? "100%" : 1120, margin: "0 auto", padding: embedded ? "4px 0 32px" : "24px 30px 64px" }}>
      <style>{`
        @keyframes phBarRise { from { transform: scaleY(0); } to { transform: scaleY(1); } }
        @keyframes phLineDraw { to { stroke-dashoffset: 0; } }
        @keyframes phFade { from { opacity: 0; } to { opacity: 1; } }
        @keyframes phGrow { from { transform: scaleX(0); } to { transform: scaleX(1); } }
        .ph-bar { transform-box: fill-box; transform-origin: bottom; animation: phBarRise .6s cubic-bezier(.22,1,.36,1) both; }
        .ph-line { stroke-dasharray: 1; stroke-dashoffset: 1; animation: phLineDraw .8s ease-out both; }
        .ph-dot { opacity: 0; animation: phFade .3s ease-out both; }
        .ph-grow { transform-origin: left; animation: phGrow .7s cubic-bezier(.22,1,.36,1) both; }
      `}</style>
      <window.PageToolbar
        title={projName}
        subtitle={"Lead: " + _personName(meta.director) + (meta.dm2 ? " · " + _personName(meta.dm2) : "") + " · Project " + pid}>
        {!embedded && projects && (
          <select value={pid || ""} onChange={e => setPid(Number(e.target.value))}
            style={{ padding: "9px 13px", borderRadius: 9, border: "1px solid #E4E9F0", fontSize: 13, color: "#1A2733", background: "#fff", maxWidth: 320 }}>
            {projects.map(p => (
              <option key={p.project_id} value={p.project_id}>
                {(p.project_name || `Project ${p.project_id}`)}{p.has_lists ? "" : " (no funnel)"}
              </option>
            ))}
          </select>
        )}
      </window.PageToolbar>

      <div style={{ display: "flex", justifyContent: "flex-end", alignItems: "center", flexWrap: "wrap", gap: 12, marginBottom: 16 }}>
        <div style={{ display: "inline-flex", background: "#fff", border: "1px solid #E4E9F0", borderRadius: 9, padding: 3, gap: 2 }}>
          {[["life", "Lifetime"], ["cur", `${new Date().getFullYear()}`]].map(([k, l]) => (
            <button key={k} onClick={() => setView(k)} style={{
              border: 0, background: view === k ? "#196EA7" : "transparent", color: view === k ? "#fff" : "#5A6B7B",
              fontSize: 12, fontWeight: 500, padding: "7px 15px", borderRadius: 7, cursor: "pointer", fontFamily: "inherit",
            }}>{l}</button>
          ))}
        </div>
      </div>

      {err && <div style={{ ...CARD, color: "#A32D2D", borderColor: "#FCEBEB", background: "#FCEBEB" }}>Error: {err}</div>}
      {loading && <div style={{ ...PANEL, color: "#5A6B7B" }}>Scoring {projName}…</div>}

      {result && !loading && (() => {
        const comp = cur.composite, b = _band(comp.score);
        const traj = result.trajectory;
        const tB = traj == null ? null : (traj >= 0 ? { bg: "#EAF3DE", c: "#3B6D11", a: "▲" } : { bg: "#FCEBEB", c: "#A32D2D", a: "▼" });
        const deals = (econ && econ.closed && econ.closed.deals)
          ? econ.closed.deals.slice().sort((x, y) => String(y.closeDate || "").localeCompare(String(x.closeDate || "")))
          : [];
        const reasons = _diagnostics(cur);
        return (
          <>
            <div className="kpi-grid one-col" style={{ ...PANEL, display: "grid", gridTemplateColumns: "auto 1fr", gap: 28, alignItems: "center", marginBottom: 16 }}>
              <div style={{ display: "flex", gap: 22, alignItems: "center", flexWrap: "wrap" }}>
                <Ring score={result.lifetime.composite.score} label="Lifetime" size={isMobile ? 84 : 116} />
                <Ring score={result.current.composite.score} label={`${new Date().getFullYear()}`} size={isMobile ? 84 : 116} />
                <div style={{ display: "flex", flexDirection: "column", gap: 8, maxWidth: 230 }}>
                  <div style={{ display: "flex", alignItems: "center", gap: 6, flexWrap: "wrap" }}>
                    <span style={{ fontSize: 11, fontWeight: 700, letterSpacing: ".4px", textTransform: "uppercase", padding: "4px 10px", borderRadius: 6, background: b.l, color: b.c }}>{b.t}</span>
                    {tB && <span style={{ fontSize: 12, fontWeight: 500, padding: "4px 10px", borderRadius: 7, background: tB.bg, color: tB.c }}>{tB.a} {traj >= 0 ? "+" : ""}{traj} vs life</span>}
                  </div>
                  {reasons.length > 0 && (
                    <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
                      {reasons.map((rsn, i) => (
                        <span key={i} style={{ fontSize: 10.5, fontWeight: 500, color: "#5A6B7B", background: "#F4F7FA", border: "1px solid #E9EEF3", padding: "3px 8px", borderRadius: 6 }}>{rsn}</span>
                      ))}
                    </div>
                  )}
                </div>
              </div>
              <div className="kpi-grid" style={{ display: "grid", gridTemplateColumns: "repeat(3,1fr)", gap: 12, borderLeft: isMobile ? "none" : "1px solid #E4E9F0", paddingLeft: isMobile ? 0 : 28 }}>
                <div style={{ background: "#F8FAFC", borderRadius: 9, padding: "14px 16px" }}>
                  <div style={{ fontSize: 11.5, color: "#5A6B7B", fontWeight: 500 }}>Deal Fees</div>
                  <div style={{ fontSize: 22, fontWeight: 600, marginTop: 6, color: "#0C447C" }}>{econ && econ.closed.fee > 0 ? _money(econ.closed.fee) : "—"}</div>
                  <div style={{ fontSize: 10.5, color: "#93A1B0", marginTop: 4 }}>{econ ? `${econ.closed.count} Closed` : ""}{econ && econ.signed.fee > 0 ? ` · ${_money(econ.signed.fee)} signed` : ""}{econ && econ.active.fee > 0 ? ` · ${_money(econ.active.fee)} pipeline` : ""}</div>
                </div>
                <div style={{ background: "#F8FAFC", borderRadius: 9, padding: "14px 16px" }}>
                  <div style={{ fontSize: 11.5, color: "#5A6B7B", fontWeight: 500 }}>Contact Rate <span style={{ fontSize: 9.5, fontWeight: 600, color: "#93A1B0", textTransform: "uppercase", letterSpacing: ".3px" }}>· lifetime</span></div>
                  <div style={{ fontSize: 22, fontWeight: 600, marginTop: 6, color: "#1A2733" }}>{universeHealth ? _pct(universeHealth.contactRate) : "—"}</div>
                  <div style={{ fontSize: 10.5, color: "#93A1B0", marginTop: 4 }}>Of Approved Companies Contacted</div>
                </div>
                <div style={{ background: "#F8FAFC", borderRadius: 9, padding: "14px 16px" }}>
                  <div style={{ fontSize: 11.5, color: "#5A6B7B", fontWeight: 500 }}>Historical Lead Rate <span style={{ fontSize: 9.5, fontWeight: 600, color: "#93A1B0", textTransform: "uppercase", letterSpacing: ".3px" }}>· lifetime</span></div>
                  <div style={{ fontSize: 22, fontWeight: 600, marginTop: 6, color: "#1A2733" }}>{universeHealth ? _pct(universeHealth.histLeadRate) : "—"}</div>
                  <div style={{ fontSize: 10.5, color: "#93A1B0", marginTop: 4 }}>Of Approved Became a Lead</div>
                </div>
              </div>
            </div>

            {deals.length > 0 && (
              <details style={{ ...PANEL, marginBottom: 16 }}>
                <summary style={{ fontSize: 14, fontWeight: 600, color: "#1A2733", cursor: "pointer" }}>Closed Deals ({deals.length})</summary>
                <div className="tbl-wrap"><table style={{ width: "100%", borderCollapse: "collapse", marginTop: 12 }}>
                  <thead>
                    <tr style={{ fontSize: 11, color: "#93A1B0", textTransform: "uppercase", letterSpacing: ".4px" }}>
                      <th style={{ textAlign: "left", padding: "0 0 10px", fontWeight: 600 }}>Target</th>
                      <th style={{ textAlign: "left", padding: "0 0 10px", fontWeight: 600 }}>Buyer</th>
                      <th style={{ textAlign: "left", padding: "0 0 10px", fontWeight: 600 }}>Type</th>
                      <th style={{ textAlign: "right", padding: "0 0 10px", fontWeight: 600 }}>TEV</th>
                      <th style={{ textAlign: "right", padding: "0 0 10px", fontWeight: 600 }}>Fee</th>
                      <th style={{ textAlign: "right", padding: "0 0 10px", fontWeight: 600 }}>Close Date</th>
                    </tr>
                  </thead>
                  <tbody>
                    {deals.map((d, i) => (
                      <tr key={d.id || i}>
                        <td style={{ fontSize: 13, padding: "9px 0", borderTop: "1px solid #EEF2F6", color: "#1A2733" }}>
                          {d.harveyTargetId
                            ? <a href={`https://db.harveyllc.com/company/${d.harveyTargetId}/target`} target="_blank" rel="noopener noreferrer" style={{ color: "#196EA7", textDecoration: "none", cursor: "pointer" }}>{d.name || "—"}</a>
                            : (d.name || "—")}
                        </td>
                        <td style={{ fontSize: 13, padding: "9px 0", borderTop: "1px solid #EEF2F6", color: "#5A6B7B" }}>{d.buyer || "—"}</td>
                        <td style={{ fontSize: 13, padding: "9px 0", borderTop: "1px solid #EEF2F6", color: "#5A6B7B" }}>{d.type || "—"}</td>
                        <td style={{ fontSize: 13, padding: "9px 0", borderTop: "1px solid #EEF2F6", textAlign: "right", color: "#5A6B7B" }}>{d.tev != null ? `$${d.tev}M` : "—"}</td>
                        <td style={{ fontSize: 13, padding: "9px 0", borderTop: "1px solid #EEF2F6", textAlign: "right", fontWeight: 600, color: "#0C447C" }}>{d.fee != null ? _money(d.fee) : "—"}</td>
                        <td style={{ fontSize: 13, padding: "9px 0", borderTop: "1px solid #EEF2F6", textAlign: "right", color: "#5A6B7B" }}>{d.closeDate || "—"}</td>
                      </tr>
                    ))}
                  </tbody>
                </table></div>
              </details>
            )}

            {loadingWk && <div style={{ ...PANEL, color: "#5A6B7B" }}>Loading outreach…</div>}
            {!loadingWk && weekly.length > 0 && (
              <>
                <div className="kpi-grid" style={{ display: "grid", gridTemplateColumns: "repeat(7,1fr)", gap: 10, marginBottom: 16 }}>
                  {METRICS.map(m => {
                    const on = metric === m.key;
                    return (
                      <div key={m.key} onClick={() => setMetric(m.key)} title={`Show ${m.label} over time`}
                        style={{ ...CARD, cursor: "pointer", background: on ? "var(--accent-soft)" : "#fff", boxShadow: on ? `inset 0 0 0 1.5px ${m.color}` : undefined }}>
                        <div style={{ fontSize: 11, color: "#5A6B7B", display: "flex", alignItems: "center", gap: 5 }}>
                          <span style={{ width: 8, height: 8, borderRadius: 4, background: m.color, display: "inline-block" }} />{m.label}
                        </div>
                        <div style={{ fontSize: 21, fontWeight: 600, marginTop: 5, color: "#1A2733" }}>{tot[m.key]}</div>
                      </div>
                    );
                  })}
                </div>
                {(() => {
                  const sel = METRICS.find(m => m.key === metric) || METRICS[0];
                  return (
                    <div style={{ ...PANEL, marginBottom: 16 }}>
                      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }}>
                        <div style={{ display: "flex", alignItems: "baseline", gap: 10 }}>
                          <h3 style={{ fontSize: 14, fontWeight: 600, color: "#1A2733" }}>{sel.label} Over Time</h3>
                          <span style={{ fontSize: 12, fontWeight: 600, color: "#93A1B0", letterSpacing: ".3px" }}>{win.yearSpan}</span>
                        </div>
                        <div style={{ display: "flex", gap: 16, alignItems: "center", fontSize: 12, color: "#5A6B7B" }}>
                          <span style={{ display: "flex", alignItems: "center", gap: 6 }}><span style={{ width: 10, height: 10, borderRadius: 5, background: sel.color }} />{sel.label} {tot[sel.key]}</span>
                          {monthly.labels.length > PAGE_SIZE && <PagerArrows pager={pager} />}
                        </div>
                      </div>
                      <LineChart animKey={win.key + sel.key} labels={win.labels} series={[
                        { color: sel.color, data: win.data.map(d => d[sel.key] || 0) },
                      ]} />
                    </div>
                  );
                })()}
              </>
            )}

            <div className="kpi-grid one-col" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16, alignItems: "start" }}>
              <div style={PANEL}>
                <h3 style={{ fontSize: 14, fontWeight: 600, color: "#1A2733", marginBottom: 14 }}>Conversion Funnel</h3>
                {cur.funnel.stub ? (
                  <div style={{ fontSize: 13, color: "#93A1B0" }}>No approval-list data for this project.</div>
                ) : (
                  <div className="tbl-wrap"><table style={{ width: "100%", borderCollapse: "collapse" }}>
                    <thead>
                      <tr style={{ fontSize: 11, color: "#93A1B0", textTransform: "uppercase", letterSpacing: ".4px" }}>
                        <th style={{ textAlign: "left", padding: "0 0 10px", fontWeight: 600 }}>Stage</th>
                        <th style={{ textAlign: "right", padding: "0 0 10px", fontWeight: 600 }}>Rate</th>
                      </tr>
                    </thead>
                    <tbody>
                      {[["Approved", "approved"], ["Contacted", "contacted"], ["Leads (4.7+)", "leads"], ["Conf Calls", "cc"], ["Visits", "visit"], ["Offers", "offer"]].map(([lbl, k]) => {
                        const rate = cur.funnel.rates && cur.funnel.rates[k];
                        return (
                          <tr key={k}>
                            <td style={{ fontSize: 13, padding: "9px 0", borderTop: "1px solid #EEF2F6", color: "#1A2733" }}>{lbl}</td>
                            <td style={{ fontSize: 13, padding: "9px 0", borderTop: "1px solid #EEF2F6", textAlign: "right", color: "#5A6B7B" }}>{rate == null ? "—" : _pct(Math.min(rate, 1) * 100)}</td>
                          </tr>
                        );
                      })}
                    </tbody>
                  </table></div>
                )}
              </div>

              <div style={PANEL}>
                <h3 style={{ fontSize: 14, fontWeight: 600, color: "#1A2733", marginBottom: 12 }}>Days Since Last</h3>
                <div style={{ display: "grid", gap: 8 }}>
                  {[["Last list", "list"], ["Last lead", "lead"], ["Last conf call", "cc"], ["Last visit", "visit"], ["Last offer", "offer"]].map(([lbl, k]) => {
                    const p = (cur.momentum.parts && cur.momentum.parts[k]) || {};
                    const col = (p.score || 0) >= 60 ? "#3B6D11" : (p.score || 0) >= 30 ? "#854F0B" : "#A32D2D";
                    return (
                      <div key={k} style={{ ...CARD, display: "flex", justifyContent: "space-between", alignItems: "center", padding: "10px 14px" }}>
                        <span style={{ fontSize: 12.5, color: "#5A6B7B" }}>{lbl}</span>
                        <span style={{ fontSize: 16, fontWeight: 600, color: col }}>{p.days == null ? "—" : `${p.days}d`}</span>
                      </div>
                    );
                  })}
                </div>
              </div>
            </div>
          </>
        );
      })()}
    </div>
  );
}

window.ProjectHealthScreen = ProjectHealthScreen;