/* Leads tab — real HubSpot paid leads (Facebook + Google) with funnel, quality, attribution & HubSpot links.
   Reads window.__ADORE_LEADS (snapshot written by Claude from the HubSpot MCP).
   Optional live refresh via /api/hubspot-leads (gated on a server-side HUBSPOT_TOKEN). */

// Real HubSpot Lifecycle Stages (pulled from the contacts.lifecyclestage property), in funnel order.
const STAGE_ORDER = ["New Lead", "Open", "Contacted Meeting Scheduled", "Showroom Meeting", "Ongoing Follow Up",
  "On-Hold", "Site Assessment", "Proposal", "Contract", "Closed won", "Customer",
  "Multi-Family Dwelling", "Refer to Contractor", "Not Interested", "Dead Strong Leads",
  "Invalid Leads (for deletion)", "Not a lead"];
const STAGE_CLASS = {
  "New Lead": "neutral", "Open": "neutral",
  "Contacted Meeting Scheduled": "info", "Ongoing Follow Up": "info",
  "Showroom Meeting": "good", "Site Assessment": "good", "Proposal": "good", "Contract": "good", "Closed won": "good", "Customer": "good",
  "On-Hold": "warn", "Multi-Family Dwelling": "info", "Refer to Contractor": "neutral",
  "Not Interested": "bad", "Dead Strong Leads": "bad",
  "Invalid Leads (for deletion)": "warn", "Not a lead": "warn",
};
const QUALIFIED = ["Showroom Meeting", "Site Assessment", "Proposal", "Contract", "Closed won", "Customer"];
const WORKING = ["New Lead", "Open", "Contacted Meeting Scheduled", "Ongoing Follow Up", "On-Hold"];
const LOST = ["Not Interested", "Dead Strong Leads"];
const JUNK = ["Not a lead", "Invalid Leads (for deletion)"];
const isQual = r => QUALIFIED.includes(r.stage);
const isJunk = r => JUNK.includes(r.stage);
// Stages hidden from the Leads tab entirely (leads in these stages are excluded from all counts/funnels/table).
const HIDDEN_STAGES = new Set(["New Lead", "On-Hold", "Customer", "Multi-Family Dwelling", "Refer to Contractor", "Invalid Leads (for deletion)"]);

// furthest milestone reached (from the manual stage-date props), most-advanced first
const MS_STEPS = [["won", "Closed won", "good"], ["sent", "Contract sent", "good"], ["proposal", "Proposal", "good"],
  ["site", "Site assessment", "good"], ["showroom", "Showroom", "good"], ["intro", "Intro call", "info"]];
function furthestMs(ms) {
  if (!ms) return null;
  for (const [key, label, cls] of MS_STEPS) if (ms[key]) return { label, cls, date: ms[key] };
  return null;
}
function shortDate(iso) { const d = new Date(iso + "T00:00:00"); return isNaN(d) ? iso : d.toLocaleDateString(undefined, { month: "short", day: "numeric" }); }

function daysSince(iso) { return Math.floor((new Date() - new Date(iso)) / 86400000); }
function leadAgo(iso) {
  const d = new Date(iso), days = daysSince(iso);
  const date = d.toLocaleDateString(undefined, { month: "short", day: "numeric" });
  if (days <= 0) return "today"; if (days === 1) return "1d ago";
  if (days < 30) return days + "d ago"; return date;
}

function LeadsTab() {
  const L = window.__ADORE_LEADS;
  const HS = window.__ADORE_HUBSPOT;
  const [plat, setPlat] = React.useState("all");
  const [stage, setStage] = React.useState("all");
  const [q, setQ] = React.useState("");
  const [hideJunk, setHideJunk] = React.useState(false);
  const [funnelLead, setFunnelLead] = React.useState(null);
  const [stageView, setStageView] = React.useState(null);   // { key, label, leads, loading } — funnel drill-down
  const [live, setLive] = React.useState({ loading: false, msg: null });
  const [data, setData] = React.useState(L);
  const [range, setRange] = React.useState("30");   // "all" | "7" | "30" | "90"
  const [from, setFrom] = React.useState("");
  const [to, setTo] = React.useState("");

  if (!L) return <div className="card" style={{ padding: 20, color: "var(--t3)" }}>No leads loaded.</div>;

  // merge per-lead enrichment (offer / booked-call / last-activity)
  const allRows = React.useMemo(() => (data.rows || []).map(r => {
    const e = (data.enrich && data.enrich[r.id]) || {};
    return { ...r, offer: e.o ? (data.offerLabels[e.o] || "") : "", booked: !!e.b, act: e.act || r.created, ms: e.ms || {} };
  }), [data]);
  // date-range filter — drives ALL analytics + the table below
  const rows = React.useMemo(() => {
    const now = new Date();
    return allRows.filter(r => {
      if (HIDDEN_STAGES.has(r.stage)) return false;
      const t = new Date(r.created);
      if (from && t < new Date(from + "T00:00:00")) return false;
      if (to && t > new Date(to + "T23:59:59")) return false;
      if (!from && !to && range !== "all") { const d = Number(range); if (d && (now - t) > d * 86400000) return false; }
      return true;
    });
  }, [allRows, range, from, to]);

  // Live, WINDOWED pull from HubSpot for the chosen date range (falls back to snapshot on 503).
  async function refreshLive(rng, f, t) {
    const range_ = rng !== undefined ? rng : range;
    const from_ = f !== undefined ? f : from;
    const to_ = t !== undefined ? t : to;
    setLive({ loading: true, msg: null });
    try {
      const qs = new URLSearchParams({ range: range_ || "30" });
      if (from_) qs.set("from", from_);
      if (to_) qs.set("to", to_);
      qs.set("tz", String(new Date().getTimezoneOffset()));   // for correct "Today" in the user's local day
      qs.set("d", new Date().toLocaleDateString("en-CA"));     // local YYYY-MM-DD
      const res = await fetch("/api/hubspot-leads?" + qs.toString());
      const d = await res.json();
      if (d && d.ok && Array.isArray(d.rows)) {
        setData(prev => ({ ...prev, rows: d.rows, enrich: d.enrich || {}, asOf: d.asOf || prev.asOf, range: d.range || prev.range, total: d.total, wins: d.wins, funnelMs: d.funnelMs, branch: d.branch }));
        setLive({ loading: false, msg: `✓ ${d.count}${d.capped ? " of " + d.total : ""} leads · ${d.range}` });
      } else {
        setLive({ loading: false, msg: d && d.error ? d.error : "Live not configured — showing snapshot." });
      }
    } catch (e) { setLive({ loading: false, msg: "Live not available — showing snapshot." }); }
    setTimeout(() => setLive(s => ({ ...s, msg: null })), 6000);
  }

  // auto-pull live HubSpot leads for the current window on open + live Facebook spend (fills FB CPL / cost-per-qualified)
  React.useEffect(() => { refreshLive(range, from, to); /* eslint-disable-next-line */ }, []);
  React.useEffect(() => {
    fetch("/api/fb-spend?match=campbell").then(r => r.json()).then(d => {
      if (d && d.ok) {
        const fb = (d.matched && d.matched.spend) || d.spend;
        if (fb) setData(prev => ({ ...prev, spend: { ...(prev.spend || {}), facebook: fb } }));
      }
    }).catch(() => {});
  }, []);

  // ---- derived analytics ----
  const a = React.useMemo(() => {
    const n = pred => rows.filter(pred).length;
    const gSpend = (data.spend && data.spend.google) || null;
    const fSpend = (data.spend && data.spend.facebook) || null;
    const gQual = n(r => r.plat === "google" && isQual(r));
    const fQual = n(r => r.plat === "facebook" && isQual(r));
    const gLeads = n(r => r.plat === "google");

    // keyword quality (google) — group by detail (the converting search term)
    const kw = {};
    rows.filter(r => r.plat === "google").forEach(r => {
      const key = r.detail || "—";
      (kw[key] = kw[key] || { key, leads: 0, qual: 0, junk: 0 });
      kw[key].leads++; if (isQual(r)) kw[key].qual++; if (isJunk(r)) kw[key].junk++;
    });
    const keywords = Object.values(kw).sort((x, y) => y.qual - x.qual || y.leads - x.leads);

    // facebook campaign attribution
    const fc = {};
    rows.filter(r => r.plat === "facebook").forEach(r => {
      const key = r.detail || "—";
      (fc[key] = fc[key] || { key, leads: 0, qual: 0 });
      fc[key].leads++; if (isQual(r)) fc[key].qual++;
    });
    const fbCampaigns = Object.values(fc).sort((x, y) => y.leads - x.leads);

    // offer / landing page
    const of = {};
    rows.forEach(r => {
      const key = r.offer || "Phone / direct";
      (of[key] = of[key] || { key, leads: 0, qual: 0 });
      of[key].leads++; if (isQual(r)) of[key].qual++;
    });
    const offers = Object.values(of).sort((x, y) => y.leads - x.leads);

    // owner leaderboard
    const ow = {};
    rows.forEach(r => {
      const key = r.owner || "Unassigned";
      (ow[key] = ow[key] || { key, leads: 0, qual: 0 });
      ow[key].leads++; if (isQual(r)) ow[key].qual++;
    });
    const owners = Object.values(ow).sort((x, y) => y.qual - x.qual || y.leads - x.leads);

    // milestone funnel (manual stage-DATE properties) — how many of this cohort reached each step
    const MS = [["Intro call", "intro"], ["Showroom", "showroom"], ["Site assessment", "site"],
      ["Proposal", "proposal"], ["Contract sent", "sent"], ["Closed won", "won"]];
    const msFunnel = MS.map(([label, key]) => ({ label, key, count: rows.filter(r => r.ms && r.ms[key]).length }));
    const hasMs = msFunnel.some(s => s.count > 0);

    // weekly trend (5 buckets back from asOf)
    const asOf = new Date((data.asOf || rows[0].created) + "T23:59:59");
    const weeks = Array.from({ length: 5 }, (_, i) => ({ i, fb: 0, gg: 0 }));
    rows.forEach(r => {
      const wi = Math.floor((asOf - new Date(r.created)) / (7 * 86400000));
      if (wi >= 0 && wi < 5) weeks[wi][r.plat === "facebook" ? "fb" : "gg"]++;
    });
    const wkMax = Math.max(1, ...weeks.map(w => w.fb + w.gg));

    // stale follow-ups (working stage, no activity in 6+ days)
    const stale = rows.filter(r => WORKING.includes(r.stage) && daysSince(r.act) >= 6 && !isJunk(r)).length;

    // lifecycle funnel — ALL HubSpot stages in order, with counts (0 if none this window)
    const stageCount = {}; rows.forEach(r => { stageCount[r.stage] = (stageCount[r.stage] || 0) + 1; });
    const funnel = [...STAGE_ORDER, ...Object.keys(stageCount).filter(s => !STAGE_ORDER.includes(s))]
      .filter(s => !HIDDEN_STAGES.has(s))
      .map(s => ({ stage: s, n: stageCount[s] || 0 }));
    const funnelMax = Math.max(1, ...funnel.map(f => f.n));

    return {
      total: rows.length, fb: n(r => r.plat === "facebook"), gg: gLeads, org: n(r => r.plat === "google-organic"),
      qual: gQual + fQual, gQual, fQual, booked: n(r => r.booked),
      working: n(r => WORKING.includes(r.stage)), lost: n(r => LOST.includes(r.stage)),
      gSpend, fSpend,
      gCPL: gSpend && gLeads ? Math.round(gSpend / gLeads) : null,
      gCPQ: gSpend && gQual ? Math.round(gSpend / gQual) : null,
      fCPL: fSpend && n(r => r.plat === "facebook") ? Math.round(fSpend / n(r => r.plat === "facebook")) : null,
      fCPQ: fSpend && fQual ? Math.round(fSpend / fQual) : null,
      cpq: (gSpend || 0) + (fSpend || 0) && (gQual + fQual) ? Math.round(((gSpend || 0) + (fSpend || 0)) / (gQual + fQual)) : null,
      keywords, fbCampaigns, owners, offers, weeks, wkMax, stale, funnel, funnelMax, msFunnel, hasMs,
    };
  }, [rows, data]);

  // ---- filtered table rows ----
  const stageList = [...new Set(rows.map(r => r.stage))].sort((a, b) => {
    const ia = STAGE_ORDER.indexOf(a), ib = STAGE_ORDER.indexOf(b);
    return (ia < 0 ? 99 : ia) - (ib < 0 ? 99 : ib);
  });
  const ql = q.trim().toLowerCase();
  const visible = rows.filter(r => {
    if (plat !== "all" && r.plat !== plat) return false;
    if (stage !== "all" && r.stage !== stage) return false;
    if (hideJunk && isJunk(r)) return false;
    if (ql && !(`${r.name} ${r.email} ${r.phone} ${r.detail} ${r.loc} ${r.owner} ${r.offer}`.toLowerCase().includes(ql))) return false;
    return true;
  });

  const rangeLabel = (from || to)
    ? `${from || "start"} → ${to || "now"}`
    : (range === "all" ? data.range.toLowerCase() : `last ${range} days`);

  // window query string (mirrors refreshLive) for the funnel drill-down endpoint
  function winQS() {
    const q = new URLSearchParams();
    if (from || to) { if (from) q.set("from", from); if (to) q.set("to", to); } else q.set("range", range);
    q.set("tz", String(new Date().getTimezoneOffset())); q.set("d", new Date().toLocaleDateString("en-CA"));
    return q.toString();
  }
  async function openStage(key, label) {
    if (key === undefined) return;
    setStageView({ key, label, leads: [], loading: true });
    try {
      const r = await fetch("/api/hubspot-stage?key=" + key + "&" + winQS());
      const d = await r.json();
      setStageView({ key, label, leads: (d && d.ok && d.leads) || [], total: d && d.total, loading: false, err: !(d && d.ok) });
    } catch (e) { setStageView({ key, label, leads: [], loading: false, err: true }); }
  }

  const cards = [
    { label: "Total leads", val: (data.total && data.total > a.total) ? data.total : a.total, foot: rangeLabel + ((data.total && data.total > a.total) ? ` · ${a.total} detailed` : "") },
    { label: "Facebook", val: a.fb, foot: a.fCPL ? `$${a.fCPL} CPL` : "paid social", cls: "fb" },
    { label: "Google Paid", val: a.gg, foot: a.gCPL ? `$${a.gCPL} CPL` : "paid search", cls: "gg" },
    { label: "Google Organic", val: a.org, foot: "organic search / SEO", cls: "gg-org" },
    { label: "Reached showroom+", val: a.qual, foot: `FB ${a.fQual} · Google ${a.gQual}`, accent: true },
    { label: "Closed won (this period)", val: data.wins ? data.wins.count : "—", foot: "by close date · all sources", accent: !!(data.wins && data.wins.count) },
    { label: "Booked intro calls", val: a.booked, foot: "via Meetings link", accent: a.booked > 0 },
    { label: "Cost / qualified lead", val: a.cpq ? "$" + a.cpq.toLocaleString() : "—",
      foot: (a.gCPQ ? `Google $${a.gCPQ.toLocaleString()}` : "") + (a.fCPQ ? ` · FB $${a.fCPQ.toLocaleString()}` : "") || "needs spend" },
    { label: "Working", val: a.working, foot: a.stale ? `${a.stale} stale (6d+ no touch)` : "all touched recently" },
    { label: "Not interested / junk", val: a.lost, foot: "incl. call-tracking spam" },
  ];

  const wkLabels = ["This wk", "1 wk", "2 wk", "3 wk", "4 wk"];

  return (
    <div className="view">
      <div style={{ display: "flex", alignItems: "baseline", gap: 12, flexWrap: "wrap", marginBottom: 14 }}>
        <h2 className="serif" style={{ margin: 0, fontSize: 24 }}>Leads from paid ads</h2>
        <span style={{ font: "11px var(--mono)", color: "var(--t3)", letterSpacing: ".1em" }}>HUBSPOT · {data.range} · {data.asOf}</span>
        <button className="chip chip-ghost" style={{ marginLeft: "auto" }} onClick={() => refreshLive()} disabled={live.loading}>
          <Ico d={I.refresh} s={13} cls={live.loading ? "spin" : ""} /> {live.loading ? "Refreshing…" : "Refresh from HubSpot"}
        </button>
        {live.msg && <span style={{ font: "12px var(--mono)", color: "var(--t3)" }}>{live.msg}</span>}
      </div>

      {/* date range */}
      <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap", marginBottom: 16 }}>
        <span style={{ font: "11px var(--mono)", color: "var(--t3)", letterSpacing: ".08em", textTransform: "uppercase" }}>Date range</span>
        {[["today", "Today"], ["all", "All time"], ["7", "7 days"], ["30", "30 days"], ["90", "90 days"], ["120", "120 days"], ["150", "150 days"], ["180", "180 days"], ["365", "365 days"]].map(([v, lab]) => (
          <button key={v} className={"chip" + (range === v && !from && !to ? " on" : "")}
            onClick={() => { setRange(v); setFrom(""); setTo(""); refreshLive(v, "", ""); }}>{lab}</button>
        ))}
        <span style={{ color: "var(--t4)", margin: "0 2px" }}>·</span>
        <input type="date" className="input" value={from} max={to || undefined} onChange={e => { setFrom(e.target.value); refreshLive(range, e.target.value, to); }}
          title="From date" style={{ padding: "6px 9px", fontSize: 12, width: 150, flex: "none" }} />
        <span style={{ color: "var(--t3)" }}>→</span>
        <input type="date" className="input" value={to} min={from || undefined} onChange={e => { setTo(e.target.value); refreshLive(range, from, e.target.value); }}
          title="To date" style={{ padding: "6px 9px", fontSize: 12, width: 150, flex: "none" }} />
        {(from || to) && <button className="chip chip-ghost" onClick={() => { setFrom(""); setTo(""); refreshLive("30", "", ""); setRange("30"); }}>Clear dates</button>}
        <span style={{ marginLeft: "auto", font: "12px var(--mono)", color: "var(--t3)" }}>{rows.length} shown{data.total && data.total > rows.length ? ` · ${data.total} total` : ""}</span>
      </div>

      {/* summary cards */}
      <div className="leads-cards">
        {cards.map((c, i) => (
          <div key={i} className={"lead-card" + (c.accent ? " accent" : "") + (c.cls ? " " + c.cls : "")}>
            <div className="lc-label">{c.label}</div>
            <div className="lc-val">{c.val}</div>
            <div className="lc-foot">{c.foot}</div>
          </div>
        ))}
      </div>

      {/* insight panels */}
      <div className="leads-insights">
        {/* sales funnel — stages REACHED IN the window, by each stage's own date (all sources) */}
        {(() => {
          const fm = (data.funnelMs && data.funnelMs.length) ? data.funnelMs : null;
          const base = fm ? Math.max(1, fm[0].count) : 1;
          return (
            <div className="li-panel" style={{ gridColumn: "1 / -1" }}>
              <div className="li-title">Sales funnel — {data.range || "this window"} <span style={{ textTransform: "none", letterSpacing: 0, color: "var(--t4)" }}>· all sources, by stage date</span></div>
              {fm ? (
                <table className="li-tbl">
                  <thead><tr><th>Stage</th><th className="num">Reached</th><th className="num">vs Open</th><th style={{ width: "45%" }}></th></tr></thead>
                  <tbody>{fm.map((s, i) => (
                    <tr key={i} className={s.count ? "funnel-row" : ""} style={{ opacity: s.count ? 1 : 0.45 }}
                      onClick={() => s.count && openStage(s.key, s.label)} title={s.count ? "Click to see the leads" : ""}>
                      <td><b>{s.label}</b></td>
                      <td className="num"><b style={{ color: s.count ? "var(--ok)" : "var(--t3)" }}>{s.count || "—"}</b></td>
                      <td className="num">{i === 0 ? "—" : (base ? Math.round(s.count / base * 100) + "%" : "—")}</td>
                      <td><div style={{ height: 7, borderRadius: 4, width: (s.count / base * 100) + "%", minWidth: s.count ? 4 : 0, background: "var(--ok)" }}></div></td>
                    </tr>))}</tbody>
                </table>
              ) : (
                <div style={{ font: "12.5px var(--sans)", color: "var(--t3)", padding: "6px 2px" }}>
                  Funnel loads on live refresh — Open → Contacted Meeting Scheduled → Showroom Meeting → Site Assessment → Contract → Closed won, by each stage's date.
                </div>
              )}
              {fm && <div style={{ font: "11px var(--sans)", color: "var(--t4)", marginTop: 8 }}>Click any stage to see the leads that reached it → click a lead for its funnel.</div>}
            </div>
          );
        })()}

        {/* closed won — breakdown by Lead Source Verification (the team's manually-verified source) */}
        {data.wins && data.wins.rows && data.wins.rows.length > 0 && (() => {
          const by = {};
          data.wins.rows.forEach(w => { const k = (w.verified || "").trim() || "Not set"; by[k] = (by[k] || 0) + 1; });
          const arr = Object.entries(by).map(([k, v]) => ({ k, v })).sort((x, y) => y.v - x.v);
          const total = data.wins.rows.length, max = Math.max(1, ...arr.map(x => x.v));
          return (
            <div className="li-panel" style={{ gridColumn: "1 / -1" }}>
              <div className="li-title">Closed won by lead source <span style={{ textTransform: "none", letterSpacing: 0, color: "var(--t4)" }}>· verified source · {data.wins.count} won · {data.range}</span></div>
              <table className="li-tbl">
                <thead><tr><th>Lead Source Verification</th><th className="num">Won</th><th className="num">%</th><th style={{ width: "42%" }}></th></tr></thead>
                <tbody>{arr.map((s, i) => (
                  <tr key={i}>
                    <td><b>{s.k}</b></td>
                    <td className="num"><b style={{ color: "var(--ok)" }}>{s.v}</b></td>
                    <td className="num">{Math.round(s.v / total * 100)}%</td>
                    <td><div style={{ height: 7, borderRadius: 4, width: (s.v / max * 100) + "%", minWidth: 4, background: "var(--ok)" }}></div></td>
                  </tr>))}</tbody>
              </table>
            </div>
          );
        })()}
        {/* sales cycle — days from Open (created) to Closed won, computed from each win's stage dates */}
        {data.wins && data.wins.rows && data.wins.rows.length > 0 && (() => {
          const W = data.wins.rows;
          const dt = s => { const d = new Date(String(s || "").slice(0, 10) + "T00:00:00"); return isNaN(d) ? null : d; };
          const days = (a, b) => { const A = dt(a), B = dt(b); return (A && B) ? Math.round((B - A) / 86400000) : null; };
          const med = arr => { const s = [...arr].sort((x, y) => x - y), n = s.length; return !n ? 0 : (n % 2 ? s[(n - 1) / 2] : Math.round((s[n / 2 - 1] + s[n / 2]) / 2)); };
          const recs = W.map(w => { const ms = w.ms || {}, won = ms.won || w.date; return { total: days(w.created, won), iw: days(ms.intro, won), sw: days(ms.showroom, won), sentw: days(ms.sent, won) }; });
          const valid = recs.filter(r => r.total != null && r.total >= 0 && r.total <= 365);
          if (!valid.length) return null;
          const tot = valid.map(r => r.total);
          const b = { "< 30 days": 0, "30–60 days": 0, "60–90 days": 0, "90+ days": 0 };
          tot.forEach(n => b[n < 30 ? "< 30 days" : n < 60 ? "30–60 days" : n < 90 ? "60–90 days" : "90+ days"]++);
          const bmax = Math.max(1, ...Object.values(b));
          const iw = valid.map(r => r.iw).filter(x => x != null && x >= 0);
          const sw = valid.map(r => r.sw).filter(x => x != null && x >= 0);
          const sentw = valid.map(r => r.sentw).filter(x => x != null && x >= 0 && x <= 180);
          return (
            <div className="li-panel" style={{ gridColumn: "1 / -1" }}>
              <div className="li-title">Sales cycle — Open → Closed won <span style={{ textTransform: "none", letterSpacing: 0, color: "var(--t4)" }}>· {valid.length} won w/ valid dates · {data.range}</span></div>
              <div style={{ display: "flex", gap: 24, flexWrap: "wrap", alignItems: "baseline", margin: "2px 0 12px" }}>
                <div><span className="serif" style={{ fontSize: 27, color: "var(--ok)" }}>{med(tot)}</span> <span style={{ font: "12px var(--sans)", color: "var(--t3)" }}>days median</span></div>
                <div style={{ font: "12px var(--sans)", color: "var(--t3)" }}>avg {Math.round(tot.reduce((a, c) => a + c, 0) / tot.length)} · fastest {Math.min(...tot)} · slowest {Math.max(...tot)}</div>
              </div>
              <table className="li-tbl"><tbody>
                {Object.entries(b).map(([k, v], i) => (
                  <tr key={i}><td style={{ width: 90 }}>{k}</td><td className="num" style={{ width: 40 }}><b>{v}</b></td>
                    <td><div style={{ height: 7, borderRadius: 4, width: (v / bmax * 100) + "%", minWidth: v ? 4 : 0, background: "var(--acc)" }}></div></td></tr>
                ))}
              </tbody></table>
              <div style={{ font: "12px var(--sans)", color: "var(--t2)", marginTop: 10, display: "flex", gap: 18, flexWrap: "wrap" }}>
                {iw.length > 0 && <span>Intro call → Won <b>{med(iw)}d</b></span>}
                {sw.length > 0 && <span>Showroom → Won <b>{med(sw)}d</b></span>}
                {sentw.length > 0 && <span>Contract sent → Won <b>{med(sentw)}d</b></span>}
              </div>
            </div>
          );
        })()}
        {/* closed won this period (by close date, all sources) */}
        {data.wins && data.wins.rows && data.wins.rows.length > 0 && (
          <div className="li-panel" style={{ gridColumn: "1 / -1" }}>
            <div className="li-title">Closed won this period — {data.wins.count} by close date (all sources)</div>
            <table className="li-tbl">
              <thead><tr><th>Name</th><th>Source</th><th>Lead Source Verification</th><th className="num">Closed</th><th className="num"></th></tr></thead>
              <tbody>{data.wins.rows.map((w, i) => (
                <tr key={i}>
                  <td><b className="lead-name-btn" onClick={() => setFunnelLead(w)} title="View this lead's funnel">{w.name}</b></td>
                  <td><span className={"plat-chip " + w.plat}>{w.source}</span></td>
                  <td className="lead-muted">{w.verified || "—"}</td>
                  <td className="num">{w.date}</td>
                  <td className="num">
                    <a className="hs-btn" href={data.hubBase + w.id} target="_blank" rel="noopener noreferrer" title="Open in HubSpot">
                      Open <Ico d={I.ext} s={12} />
                    </a>
                  </td>
                </tr>))}</tbody>
            </table>
          </div>
        )}
        {/* keyword quality */}
        <div className="li-panel">
          <div className="li-title">Google keywords by quality</div>
          <table className="li-tbl">
            <thead><tr><th>Keyword</th><th className="num">Leads</th><th className="num">Qual.</th><th className="num">Junk</th></tr></thead>
            <tbody>{a.keywords.map((k, i) => (
              <tr key={i}>
                <td>{k.key}</td><td className="num">{k.leads}</td>
                <td className="num"><b style={{ color: k.qual ? "var(--ok)" : "var(--t3)" }}>{k.qual}</b></td>
                <td className="num" style={{ color: k.junk ? "var(--warn)" : "var(--t4)" }}>{k.junk || "—"}</td>
              </tr>))}</tbody>
          </table>
        </div>

        {/* facebook campaigns */}
        <div className="li-panel">
          <div className="li-title">Facebook campaigns</div>
          <table className="li-tbl">
            <thead><tr><th>Campaign</th><th className="num">Leads</th><th className="num">Qual.</th></tr></thead>
            <tbody>{a.fbCampaigns.map((c, i) => (
              <tr key={i}><td>{c.key || "—"}</td><td className="num">{c.leads}</td>
                <td className="num"><b style={{ color: c.qual ? "var(--ok)" : "var(--t3)" }}>{c.qual}</b></td></tr>))}</tbody>
          </table>
        </div>

        {/* offer / landing page */}
        <div className="li-panel">
          <div className="li-title">Offer / landing page</div>
          <table className="li-tbl">
            <thead><tr><th>Offer</th><th className="num">Leads</th><th className="num">Qual.</th><th className="num">Rate</th></tr></thead>
            <tbody>{a.offers.map((o, i) => (
              <tr key={i}><td>{o.key}</td><td className="num">{o.leads}</td>
                <td className="num"><b style={{ color: o.qual ? "var(--ok)" : "var(--t3)" }}>{o.qual}</b></td>
                <td className="num">{o.leads ? Math.round(o.qual / o.leads * 100) + "%" : "—"}</td></tr>))}</tbody>
          </table>
        </div>

        {/* weekly trend */}
        <div className="li-panel">
          <div className="li-title">Leads per week</div>
          <div className="trend">
            {a.weeks.map((w, i) => (
              <div key={i} className="trend-col">
                <div className="trend-bars">
                  <div className="trend-bar gg" style={{ height: (w.gg / a.wkMax * 70) + "px" }} title={`Google ${w.gg}`}></div>
                  <div className="trend-bar fb" style={{ height: (w.fb / a.wkMax * 70) + "px" }} title={`Facebook ${w.fb}`}></div>
                </div>
                <div className="trend-n">{w.fb + w.gg}</div>
                <div className="trend-lab">{wkLabels[i]}</div>
              </div>
            ))}
          </div>
          <div className="trend-legend"><i><span className="sw gg"></span>Google</i><i><span className="sw fb"></span>Facebook</i></div>
        </div>

        {/* OTHER OUTCOMES — branch stages, kept at the very bottom (not part of the funnel) */}
        {data.branch && data.branch.length > 0 && (
          <div className="li-panel" style={{ gridColumn: "1 / -1" }}>
            <div className="li-title">Other outcomes — {data.range || "this window"} <span style={{ textTransform: "none", letterSpacing: 0, color: "var(--t4)" }}>· current stage, created in window</span></div>
            <table className="li-tbl">
              <thead><tr><th>Stage</th><th className="num">Leads</th></tr></thead>
              <tbody>{data.branch.map((b, i) => (
                <tr key={i} style={{ opacity: b.count ? 1 : 0.45 }}>
                  <td><span className={"badge " + (STAGE_CLASS[b.label] || "neutral")}><span className="bd"></span>{b.label}</span></td>
                  <td className="num"><b>{b.count != null ? b.count : "—"}</b></td>
                </tr>))}</tbody>
            </table>
          </div>
        )}
      </div>

      {/* filters */}
      <div className="leads-filters">
        <div className="chiprow">
          {[["all", "All"], ["facebook", "Facebook"], ["google", "Google Paid"], ["google-organic", "Google Organic"]].map(([v, lab]) => (
            <button key={v} className={"chip" + (plat === v ? " on" : "")} onClick={() => setPlat(v)}>{lab}</button>
          ))}
        </div>
        <div className="chiprow">
          <button className={"chip" + (stage === "all" ? " on" : "")} onClick={() => setStage("all")}>All stages</button>
          {stageList.map(s => (
            <button key={s} className={"chip" + (stage === s ? " on" : "")} onClick={() => setStage(s)}>{s}</button>
          ))}
        </div>
        <button className={"chip chip-ghost" + (hideJunk ? " on" : "")} onClick={() => setHideJunk(v => !v)}>
          {hideJunk ? "Junk hidden" : "Hide junk"}
        </button>
        <input className="input lead-search" placeholder="Search name, email, keyword…" value={q} onChange={e => setQ(e.target.value)} />
      </div>

      <div className="lead-count">{visible.length} of {rows.length} leads</div>

      {/* table */}
      <div className="table-wrap">
        <table className="ads leads-tbl">
          <thead><tr>
            {["Lead", "Source", "Lifecycle Stage", "Lifecycle Status", "Progress", "Keyword / campaign", "Offer", "Location", "Owner", "Last activity", ""].map((h, i) =>
              <th key={i} className={i === 10 ? "num" : ""}>{h}</th>)}
          </tr></thead>
          <tbody>
            {visible.map(r => {
              const stale = WORKING.includes(r.stage) && daysSince(r.act) >= 6 && !isJunk(r);
              return (
                <tr key={r.id}>
                  <td>
                    <div className="lead-name lead-name-btn" onClick={() => setFunnelLead(r)} title="View this lead's funnel">{r.name}
                      {r.booked && <span className="call-tag book">★ call booked</span>}
                      {r.callOnly && !r.booked && <span className="call-tag">call</span>}
                    </div>
                    <div className="lead-contact">
                      {r.email ? <a href={"mailto:" + r.email} className="lead-link">{r.email}</a> : <span className="lead-muted">no email</span>}
                      {r.phone && <> · <a href={"tel:" + r.phone} className="lead-link">{r.phone}</a></>}
                    </div>
                  </td>
                  <td><span className={"plat-chip " + r.plat}>{r.plat === "facebook" ? "Facebook" : r.plat === "google-organic" ? "Google Organic" : "Google Paid"}</span></td>
                  <td><span className={"badge " + (STAGE_CLASS[r.stage] || "neutral")}><span className="bd"></span>{r.stage}</span></td>
                  <td className="lead-muted">{r.lcStatus || "—"}</td>
                  <td>{(() => { const m = furthestMs(r.ms); return m
                    ? <span className={"badge " + m.cls} title={"Reached " + m.label + " on " + m.date}><span className="bd"></span>{m.label} · {shortDate(m.date)}</span>
                    : <span className="lead-muted">—</span>; })()}</td>
                  <td className="lead-detail">{r.detail || "—"}</td>
                  <td className="lead-muted">{r.offer || "—"}</td>
                  <td className="lead-muted">{r.loc || "—"}</td>
                  <td className="lead-muted">{r.owner || "—"}</td>
                  <td>
                    <span title={r.act}>{leadAgo(r.act)}</span>
                    {stale && <span className="stale-dot" title="No activity in 6+ days"></span>}
                  </td>
                  <td className="num">
                    <a className="hs-btn" href={data.hubBase + r.id} target="_blank" rel="noopener noreferrer" title="Open in HubSpot">
                      Open <Ico d={I.ext} s={12} />
                    </a>
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
        {visible.length === 0 && <div className="empty" style={{ padding: 40 }}><div>No leads match these filters.</div></div>}
      </div>

      <div className="lead-note">
        Leads = HubSpot contacts whose original source is paid social (Facebook) or paid search (Google).
        <b> Reached showroom+</b> = reached a Showroom Meeting or Site Assessment (qualified). <b>Cost / qualified</b> divides ad spend by qualified leads —
        the number that actually reflects ROI. “call” = phone lead from call tracking (no email). <b>★ call booked</b> = booked an intro call via the Meetings link.
        Amber dot = working lead with no activity in 6+ days. No closed-won deals attributed in this window yet.
        Snapshot — “Refresh from HubSpot” pulls live once a HubSpot token is set server-side; otherwise ask Claude to “update HubSpot leads”.
      </div>

      {stageView && <StageLeads view={stageView} onPick={(lead) => setFunnelLead(lead)} onClose={() => setStageView(null)} />}
      {funnelLead && <LeadFunnel r={funnelLead} hubBase={data.hubBase} onClose={() => setFunnelLead(null)} />}
    </div>
  );
}

// Per-lead funnel timeline — click a lead's name to see which stages it reached + when.
function LeadFunnel({ r, hubBase, onClose }) {
  const ms = r.ms || {};
  const steps = [
    ["Open", r.created ? String(r.created).slice(0, 10) : ""],
    ["Contacted Meeting Scheduled", ms.intro],
    ["Showroom Meeting", ms.showroom],
    ["Site Assessment", ms.site],
    ["Contract", ms.sent],
    ["Closed won", ms.won],
  ];
  const platLabel = r.plat === "facebook" ? "Facebook" : r.plat === "google-organic" ? "Google Organic" : "Google Paid";
  return (
    <div className="overlay" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()} style={{ width: "min(440px, 94vw)" }}>
        <div className="modal-head">
          <div>
            <h3 className="serif">{r.name}</h3>
            <div className="mid">{platLabel} · now: {r.stage}{r.owner ? " · " + r.owner : ""}</div>
          </div>
          <button className="ico-btn" onClick={onClose}><Ico d={I.x} s={14} /></button>
        </div>
        <div className="modal-body">
          <div className="lead-funnel">
            {steps.map(([label, date], i) => {
              const reached = !!date;
              return (
                <div key={i} className={"lf-step" + (reached ? " on" : "")}>
                  <span className="lf-dot">{reached && <Ico d={I.check} s={11} />}</span>
                  <span className="lf-label">{label}</span>
                  <span className="lf-date">{reached ? shortDate(date) : "—"}</span>
                </div>
              );
            })}
          </div>
        </div>
        <div className="modal-foot">
          <span className="lead-muted" style={{ font: "11.5px var(--sans)" }}>{r.email || r.phone || ""}</span>
          <a className="btn btn-ghost btn-sm" href={hubBase + r.id} target="_blank" rel="noopener noreferrer"><Ico d={I.ext} s={13} /> Open in HubSpot</a>
        </div>
      </div>
    </div>
  );
}

// Funnel drill-down — leads that reached a stage; click one to open its funnel timeline.
function StageLeads({ view, onPick, onClose }) {
  const platLabel = p => p === "facebook" ? "Facebook" : p === "google-organic" ? "Google Organic" : p === "google" ? "Google Paid" : "Other";
  return (
    <div className="overlay" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()} style={{ width: "min(540px, 95vw)", maxHeight: "85vh", display: "flex", flexDirection: "column" }}>
        <div className="modal-head">
          <div>
            <h3 className="serif">Reached: {view.label}</h3>
            <div className="mid">{view.loading ? "loading…" : (view.total != null ? view.total + " leads" : view.leads.length + " leads")} · click a lead for its funnel</div>
          </div>
          <button className="ico-btn" onClick={onClose}><Ico d={I.x} s={14} /></button>
        </div>
        <div className="modal-body" style={{ overflowY: "auto", padding: 0 }}>
          {view.loading ? (
            <div style={{ padding: 30, textAlign: "center", color: "var(--t3)", font: "13px var(--sans)" }}>Loading leads…</div>
          ) : view.err ? (
            <div style={{ padding: 30, textAlign: "center", color: "var(--t3)", font: "13px var(--sans)" }}>Couldn’t load (needs live HubSpot).</div>
          ) : view.leads.length === 0 ? (
            <div style={{ padding: 30, textAlign: "center", color: "var(--t3)", font: "13px var(--sans)" }}>No leads in this window.</div>
          ) : (
            <table className="ads" style={{ width: "100%" }}>
              <tbody>{view.leads.map((l, i) => (
                <tr key={i} className="funnel-row" onClick={() => onPick(l)} title="View funnel">
                  <td style={{ padding: "10px 16px" }}><b>{l.name}</b><div className="lead-muted" style={{ font: "11px var(--mono)" }}>{l.email || l.phone || ""}</div></td>
                  <td style={{ padding: "10px 14px" }}><span className={"plat-chip " + l.plat}>{platLabel(l.plat)}</span></td>
                  <td style={{ padding: "10px 14px", font: "12px var(--sans)", color: "var(--t2)" }}>now: {l.stage}</td>
                  <td className="num" style={{ padding: "10px 16px", font: "11.5px var(--mono)", color: "var(--t3)" }}>{l.date}</td>
                </tr>))}</tbody>
            </table>
          )}
        </div>
      </div>
    </div>
  );
}

window.LeadsTab = LeadsTab;
