/* Conversions & marketing attribution.
   - ConversionsReport: revenue by channel/source/campaign under a chosen model.
   - OrderAttributionCard: the acquisition journey for one order (order detail).
   - CustomerAcquisitionCard: how a customer was acquired (customer profile).
   All three read the /api/attribution + /api/conversions endpoints. */

// Channel taxonomy → label + colour (kept in sync with attribution.js CHANNELS).
const CHANNEL_META = {
  paid_search:    { label: "Paid search",    color: "#2f6df6" },
  paid_social:    { label: "Paid social",    color: "#8b5cf6" },
  organic_search: { label: "Organic search", color: "#12b886" },
  organic_social: { label: "Organic social", color: "#f0842b" },
  email:          { label: "Email",          color: "#e64980" },
  referral:       { label: "Referral",       color: "#4dabf7" },
  direct:         { label: "Direct",         color: "#868e96" },
  other:          { label: "Other",          color: "#adb5bd" },
  unattributed:   { label: "Unattributed",   color: "#ced4da" },
};
const chanMeta = (c) => CHANNEL_META[c] || { label: c || "—", color: "#adb5bd" };
const gbp = (n) => (DTF && DTF.fmtGBP ? DTF.fmtGBP(n || 0) : "£" + (n || 0).toFixed(2));
const cvrDay = (ms) => ms ? new Date(ms).toLocaleDateString("en-GB", { day: "numeric", month: "short" }) : "—";
const cvrDayYr = (ms) => ms ? new Date(ms).toLocaleDateString("en-GB", { day: "numeric", month: "short", year: "numeric" }) : "—";

function SourceBadge({ channel, source, small }) {
  const m = chanMeta(channel);
  return (
    <span className="src-badge" style={{ fontSize: small ? 11 : 12 }} title={source ? (m.label + " · " + source) : m.label}>
      <span className="src-dot" style={{ background: m.color }} />
      {m.label}{source && source !== "direct" && !small ? <span className="dim" style={{ fontWeight: 400 }}> · {source}</span> : null}
    </span>
  );
}

const RANGES = [
  { key: "today", label: "Today",   days: 0 },
  { key: "7",     label: "7 days",  days: 7 },
  { key: "30",    label: "30 days", days: 30 },
  { key: "90",    label: "90 days", days: 90 },
  { key: "365",   label: "12 mo",   days: 365 },
];

function ConversionsReport({ go }) {
  const [rangeKey, setRangeKey] = useState("today");
  const [model, setModel] = useState("position");
  const [groupBy, setGroupBy] = useState("channel");
  const [meta, setMeta] = useState(null);
  const [rep, setRep] = useState(null);
  const [status, setStatus] = useState(null);
  const [loading, setLoading] = useState(true);
  const [syncing, setSyncing] = useState(false);
  const [err, setErr] = useState(null);
  const [drill, setDrill] = useState(null); // { key, label, groupBy } → orders modal
  const [expanded, setExpanded] = useState({}); // tree expand state (channel view)

  const range = RANGES.find(r => r.key === rangeKey) || RANGES[1];
  // Stable per range — recomputing Date.now() each render would re-fire the fetch
  // effect (which resets expand state) on every render.
  const from = useMemo(() => {
    if (range.days === 0) { const d = new Date(); d.setHours(0, 0, 0, 0); return d.getTime(); } // "Today" = local midnight
    return Date.now() - range.days * 86400000;
  }, [rangeKey]);
  // The Channel view is a grouped/expandable tree (channel → source → campaign);
  // Source/Campaign are flat rankings.
  const tree = groupBy === "channel";
  const endpoint = tree ? "/api/conversions/breakdown" : "/api/conversions";

  useEffect(() => { fetch("/api/conversions/meta").then(r => r.json()).then(setMeta).catch(() => {}); }, []);
  const loadStatus = () => fetch("/api/conversions/status").then(r => r.json()).then(setStatus).catch(() => {});
  useEffect(() => { loadStatus(); }, []);

  useEffect(() => {
    let alive = true; setLoading(true); setErr(null); setExpanded({});
    const q = `?from=${from}&model=${model}` + (tree ? "" : `&groupBy=${groupBy}`);
    fetch(endpoint + q).then(r => r.json()).then(j => {
      if (!alive) return;
      if (j && j.error) { setErr(j.error); setRep(null); } else setRep(j);
    }).catch(e => alive && setErr(e.message)).finally(() => alive && setLoading(false));
    return () => { alive = false; };
  }, [from, model, groupBy]);
  const toggle = (k) => setExpanded(e => ({ ...e, [k]: !e[k] }));

  async function sync() {
    setSyncing(true); setErr(null);
    try {
      await fetch("/api/conversions/sync?sinceDays=" + Math.max(range.days, 90), { method: "POST" }).then(r => r.json());
      await loadStatus();
      // re-pull the report
      const j = await fetch(`/api/conversions?from=${from}&model=${model}&groupBy=${groupBy}`).then(r => r.json());
      setRep(j);
    } catch (e) { setErr(e.message); }
    finally { setSyncing(false); }
  }

  const rows = (rep && (tree ? rep.channels : rep.rows)) || [];
  const totals = rep && rep.totals;
  const maxRev = rows.reduce((m, r) => Math.max(m, r.revenue), 0) || 1;
  const modelList = (meta && meta.models) || ["position", "last", "first", "linear"];
  const modelLabel = (m) => (meta && meta.modelLabels && meta.modelLabels[m]) || m;

  return (
    <div className="board cvr">
      <div className="toolbar cvr__bar">
        <div className="seg">
          {RANGES.map(r => (
            <button key={r.key} className={(rangeKey === r.key ? "is-active" : "")} onClick={() => setRangeKey(r.key)}>{r.label}</button>
          ))}
        </div>
        <div className="seg">
          {["channel", "source", "campaign", "keyword"].map(g => (
            <button key={g} className={(groupBy === g ? "is-active" : "")} onClick={() => setGroupBy(g)} title={g === "keyword" ? "Paid-search keywords (utm_term)" : undefined}>{g[0].toUpperCase() + g.slice(1)}</button>
          ))}
        </div>
        <div className="seg" title="Attribution model — how each order's revenue is split across its touchpoints">
          {modelList.map(m => (
            <button key={m} className={(model === m ? "is-active" : "")} onClick={() => setModel(m)} title={modelLabel(m)}>
              {m === "position" ? "Position" : m === "last" ? "Last click" : m === "first" ? "First click" : "Linear"}
            </button>
          ))}
        </div>
        <div className="right" style={{ display: "flex", alignItems: "center", gap: 10 }}>
          {status && status.syncedAt && <span className="kick dim">synced {cvrDay(status.syncedAt)}</span>}
          <button className="btn btn--ghost btn--sm" onClick={sync} disabled={syncing}>
            {syncing ? <><span className="spin spin--dark" /> Syncing…</> : <><Icon name="refresh" /> Sync from Shopify</>}
          </button>
        </div>
      </div>

      <div className="cvr__note dim">
        {modelLabel(model)} attribution · {cvrDayYr(from)} → today
        {rep && rep.origins ? <> · {rep.origins.shopify || 0} Shopify journeys, {rep.origins.pixel || 0} pixel, {rep.origins.none || 0} unattributed</> : null}
        {rep && rep.journeySupported === false && <span style={{ color: "#c0392b" }}> · Shopify journey scope unavailable — pixel-only</span>}
      </div>

      <HealthStrip />

      {err && <div className="empty"><Icon name="spark" /><div className="t">Couldn’t load report</div><div className="m">{err}</div></div>}

      {loading && !rep ? <SkeletonRows rows={6} /> : rep && (
        <>
          {/* KPI cards */}
          <div className="cvr__kpis">
            <div className="kpi"><div className="kpi__k">Revenue</div><div className="kpi__v">{gbp(totals.revenue)}</div></div>
            <div className="kpi"><div className="kpi__k">Orders</div><div className="kpi__v">{totals.orders}</div></div>
            <div className="kpi"><div className="kpi__k">AOV</div><div className="kpi__v">{gbp(totals.aov)}</div></div>
            <div className="kpi" title="Connect Google Ads to populate spend & ROAS">
              <div className="kpi__k">ROAS</div>
              <div className="kpi__v">{totals.roas != null ? totals.roas + "×" : <span className="dim" style={{ fontSize: 15 }}>— add spend</span>}</div>
            </div>
          </div>

          <div className="card" style={{ marginTop: 14 }}>
            <div className="card__head"><h3>Revenue by {groupBy}</h3><span className="right kick">{rows.length} {groupBy}s</span></div>
            <div className="card__body--flush tbl-wrap">
              <table className="tbl cvr__tbl">
                <thead>
                  <tr>
                    <th>{groupBy[0].toUpperCase() + groupBy.slice(1)}</th>
                    <th className="num">Orders</th>
                    <th className="num">Revenue</th>
                    <th className="num">AOV</th>
                    <th className="num">Share</th>
                    <th className="num" title="Ad spend — from the Google Ads connector">Spend</th>
                    <th className="num" title="Return on ad spend">ROAS</th>
                  </tr>
                </thead>
                <tbody>
                  {tree
                    ? rows.map(c => {
                        const cOpen = !!expanded[c.key];
                        const hasSrc = (c.sources || []).length > 0;
                        return (
                          <React.Fragment key={c.key}>
                            <tr className="cvr__row" style={{ cursor: "pointer" }}
                                onClick={() => setDrill({ key: c.key, label: c.label, groupBy: "channel" })}>
                              <td>
                                <div className="cvr__srccell">
                                  <button className="cvr__twist" onClick={e => { e.stopPropagation(); toggle(c.key); }} title={cOpen ? "Collapse" : "Expand sources"} style={{ visibility: hasSrc ? "visible" : "hidden" }}>
                                    <span style={{ display: "inline-block", transform: cOpen ? "rotate(90deg)" : "none" }}><Icon name="chevron" /></span>
                                  </button>
                                  <SourceBadge channel={c.key} />
                                </div>
                                <div className="cvr__revbar" style={{ marginLeft: 22 }}><span style={{ width: (c.revenue / maxRev * 100) + "%", background: chanMeta(c.channel).color }} /></div>
                              </td>
                              <td className="num">{c.orders}</td>
                              <td className="num"><RevCell row={c} /></td>
                              <td className="num">{gbp(c.aov)}</td>
                              <td className="num">{c.pct}%</td>
                              <td className="num dim">{c.spend != null ? gbp(c.spend) : "—"}</td>
                              <td className="num"><RoasCell row={c} /></td>
                            </tr>
                            {cOpen && (c.sources || []).map(s => {
                              const sk = c.key + ">" + s.key;
                              const sOpen = !!expanded[sk];
                              const camps = (s.campaigns || []).filter(cm => cm.key !== "(no campaign)" || (s.campaigns || []).length > 1);
                              const hasCamp = camps.length > 0;
                              return (
                                <React.Fragment key={sk}>
                                  <tr className="cvr__row cvr__row--sub" style={{ cursor: "pointer" }}
                                      onClick={() => setDrill({ key: s.key, label: s.label, groupBy: "source" })}>
                                    <td>
                                      <div className="cvr__srccell" style={{ paddingLeft: 22 }}>
                                        <button className="cvr__twist" onClick={e => { e.stopPropagation(); toggle(sk); }} style={{ visibility: hasCamp ? "visible" : "hidden" }}>
                                          <span style={{ display: "inline-block", transform: sOpen ? "rotate(90deg)" : "none" }}><Icon name="chevron" /></span>
                                        </button>
                                        <span className="cvr__srcname">{s.label === "(none)" ? <span className="dim">(no source)</span> : s.label}</span>
                                      </div>
                                    </td>
                                    <td className="num">{s.orders}</td>
                                    <td className="num">{gbp(s.revenue)}</td>
                                    <td className="num">{gbp(s.aov)}</td>
                                    <td className="num">{s.pct}%</td>
                                    <td className="num dim">—</td>
                                    <td className="num dim">—</td>
                                  </tr>
                                  {sOpen && camps.map(cm => (
                                    <tr key={sk + ">" + cm.key} className="cvr__row cvr__row--sub2" style={{ cursor: "pointer" }}
                                        onClick={() => setDrill({ key: cm.key, label: cm.label, groupBy: "campaign" })}>
                                      <td><div className="cvr__srccell" style={{ paddingLeft: 66 }}><span className="cvr__campname">{cm.label}</span></div></td>
                                      <td className="num">{cm.orders}</td>
                                      <td className="num">{gbp(cm.revenue)}</td>
                                      <td className="num">{gbp(cm.aov)}</td>
                                      <td className="num">{cm.pct}%</td>
                                      <td className="num dim">{cm.spend != null ? gbp(cm.spend) : "—"}</td>
                                      <td className="num"><RoasCell row={cm} /></td>
                                    </tr>
                                  ))}
                                </React.Fragment>
                              );
                            })}
                          </React.Fragment>
                        );
                      })
                    : rows.map(r => (
                    <tr key={r.key} className="cvr__row" onClick={() => setDrill({ key: r.key, label: r.label, groupBy })} style={{ cursor: "pointer" }}>
                      <td>
                        <div className="cvr__srccell">
                          <SourceBadge channel={r.channel} small /><span style={{ marginLeft: 6 }}>{r.label}</span>
                        </div>
                        <div className="cvr__revbar"><span style={{ width: (r.revenue / maxRev * 100) + "%", background: chanMeta(r.channel).color }} /></div>
                      </td>
                      <td className="num">{r.orders}</td>
                      <td className="num"><RevCell row={r} /></td>
                      <td className="num">{gbp(r.aov)}</td>
                      <td className="num">{r.pct}%</td>
                      <td className="num dim">{r.spend != null ? gbp(r.spend) : "—"}</td>
                      <td className="num"><RoasCell row={r} /></td>
                    </tr>
                  ))}
                  {groupBy !== "keyword" && rep.unattributed && rep.unattributed.orders > 0 && (
                    <tr className="cvr__row cvr__row--muted" onClick={() => setDrill({ key: "(unattributed)", label: "Unattributed", groupBy: "channel" })} style={{ cursor: "pointer" }}>
                      <td><div className="cvr__srccell"><SourceBadge channel="unattributed" /><span className="dim" style={{ marginLeft: 6, fontSize: 11 }}>no journey data</span></div></td>
                      <td className="num">{rep.unattributed.orders}</td>
                      <td className="num" style={{ fontWeight: 600 }}>{gbp(rep.unattributed.revenue)}</td>
                      <td className="num dim">—</td>
                      <td className="num dim">—</td>
                      <td className="num dim">—</td>
                      <td className="num dim">—</td>
                    </tr>
                  )}
                </tbody>
              </table>
            </div>
            {groupBy === "keyword" ? (
              <div className="card__body dim" style={{ fontSize: 12, borderTop: "1px solid var(--rule)" }}>
                <Icon name="spark" /> Paid-search keywords: CRM-attributed revenue vs. Google Ads keyword spend → per-keyword <b>ROAS</b> &amp; CPA. Rows with spend but no revenue are keywords not yet paying back. “(no keyword)” = paid clicks where no keyword was captured. KPI cards above stay blended (all revenue ÷ all spend).
              </div>
            ) : !rep.hasSpend && (
              <div className="card__body dim" style={{ fontSize: 12, borderTop: "1px solid var(--rule)" }}>
                <Icon name="spark" /> Spend & ROAS are empty until the Google Ads connector posts daily spend to <span className="mono">/api/adspend</span>. Attribution (left) works today.
              </div>
            )}
          </div>
          <LtvBySource from={from} />
        </>
      )}

      {drill && <ConversionDrill drill={drill} from={from} groupBy={drill.groupBy || groupBy} model={model} go={go} onClose={() => setDrill(null)} />}
    </div>
  );
}

// Orders credited to one bucket, under the current model — modal drill-down.
function ConversionDrill({ drill, from, groupBy, model, go, onClose }) {
  const [orders, setOrders] = useState(null);
  useEffect(() => {
    let alive = true; setOrders(null);
    const q = `?from=${from}&groupBy=${groupBy}&model=${model}&key=${encodeURIComponent(drill.key)}`;
    fetch("/api/conversions/orders" + q).then(r => r.json()).then(j => alive && setOrders((j && j.orders) || [])).catch(() => alive && setOrders([]));
    return () => { alive = false; };
  }, [drill.key, from, groupBy, model]);
  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal cvr__modal" onClick={e => e.stopPropagation()}>
        <div className="modal__head">
          <h3>{drill.label} · orders</h3>
          <button className="icon-btn" onClick={onClose}><Icon name="x" /></button>
        </div>
        <div className="modal__body" style={{ maxHeight: "60vh", overflow: "auto" }}>
          {orders === null ? <SkeletonRows rows={5} /> : orders.length === 0 ? (
            <div className="dim" style={{ padding: 16 }}>No orders in this window.</div>
          ) : (
            <table className="tbl">
              <thead><tr><th>Order</th><th>Date</th><th>Closed by</th><th className="num">Credit</th><th className="num">Revenue</th></tr></thead>
              <tbody>
                {orders.map(o => (
                  <tr key={o.legacyId} style={{ cursor: "pointer" }} onClick={() => { onClose(); go({ view: "order", id: o.name }); }}>
                    <td className="mono">{o.name}</td>
                    <td>{cvrDay(o.createdAt)}</td>
                    <td><SourceBadge channel={o.channel} source={o.source} small /></td>
                    <td className="num">{Math.round((o.credit || 0) * 100)}%</td>
                    <td className="num">{gbp(o.revenue)}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          )}
        </div>
      </div>
    </div>
  );
}

/* --------- Order detail: acquisition journey panel --------- */
function OrderAttributionCard({ legacyId }) {
  const [a, setA] = useState(null);
  const [open, setOpen] = useState(false);
  useEffect(() => {
    let alive = true; setA(null);
    if (!legacyId) return;
    fetch("/api/attribution/order/" + encodeURIComponent(legacyId)).then(r => r.json()).then(j => alive && setA(j)).catch(() => alive && setA({ origin: "none", touches: [] }));
    return () => { alive = false; };
  }, [legacyId]);

  if (!a) return null;
  const touches = a.touches || [];
  const posCredit = (a.models && a.models.position) || [];

  return (
    <div className="card">
      <div className="card__head"><h3>Attribution</h3>
        <span className="right kick dim">
          {a.origin === "shopify" ? "Shopify journey" : a.origin === "pixel" ? "Pixel sessions" : "No journey data"}
          {a.touchCount ? " · " + a.touchCount + " touch" + (a.touchCount === 1 ? "" : "es") : ""}
        </span>
      </div>
      <div className="card__body">
        {touches.length === 0 ? (
          <div className="dim" style={{ fontSize: 13 }}>No acquisition data for this order. Shopify didn’t record a journey and no pixel session matched — this fills in as the storefront pixel captures traffic.</div>
        ) : (
          <>
            <div className="attr-grid">
              <div>
                <div className="attr-k">First touch</div>
                {a.first ? <SourceBadge channel={a.first.channel} source={a.first.source} /> : <span className="dim">—</span>}
                {a.first && a.first.campaign && <div className="dim attr-camp">{a.first.campaign}</div>}
              </div>
              <div style={{ alignSelf: "center", color: "var(--txt-3)" }}><Icon name="chevron" /></div>
              <div>
                <div className="attr-k">Last (closing) touch</div>
                {a.last ? <SourceBadge channel={a.last.channel} source={a.last.source} /> : <span className="dim">—</span>}
                {a.last && a.last.campaign && <div className="dim attr-camp">{a.last.campaign}</div>}
              </div>
            </div>
            {(a.daysToConversion != null || a.orderIndex != null) && (
              <div className="dim" style={{ fontSize: 12, marginTop: 10 }}>
                {a.daysToConversion != null && <>{a.daysToConversion} day{a.daysToConversion === 1 ? "" : "s"} to convert</>}
                {a.daysToConversion != null && a.orderIndex != null && " · "}
                {a.orderIndex != null && <>order #{a.orderIndex} for this customer</>}
              </div>
            )}
            {posCredit.length > 0 && (
              <div className="attr-credit">
                <div className="attr-k">Position-based credit</div>
                <div className="attr-creditbar">
                  {posCredit.map(c => (
                    <span key={c.channel} title={chanMeta(c.channel).label + " · " + Math.round(c.credit * 100) + "%"}
                          style={{ width: (c.credit * 100) + "%", background: chanMeta(c.channel).color }} />
                  ))}
                </div>
                <div className="attr-creditkeys">
                  {posCredit.map(c => <span key={c.channel} className="attr-creditkey"><span className="src-dot" style={{ background: chanMeta(c.channel).color }} />{chanMeta(c.channel).label} {Math.round(c.credit * 100)}%</span>)}
                </div>
              </div>
            )}
            <button className="btn btn--ghost btn--sm" style={{ marginTop: 12 }} onClick={() => setOpen(o => !o)}>
              {open ? "Hide" : "Show"} full journey ({touches.length})
            </button>
            {open && (
              <div className="attr-timeline">
                {touches.map((t, i) => (
                  <div className="attr-tl" key={i}>
                    <span className="attr-tl__dot" style={{ background: chanMeta(t.channel).color }} />
                    <div style={{ minWidth: 0 }}>
                      <div className="attr-tl__src"><SourceBadge channel={t.channel} source={t.source} small />{t.campaign && <span className="dim"> · {t.campaign}</span>}{t.term && <span className="dim"> · “{t.term}”</span>}</div>
                      <div className="attr-tl__meta dim">
                        {t.ts ? cvrDayYr(t.ts) : ""}{t.landing ? " · " + String(t.landing).replace(/^https?:\/\/[^/]+/, "") : ""}
                        {t.gclid ? " · gclid" : ""}{t.fbclid ? " · fbclid" : ""}
                      </div>
                    </div>
                  </div>
                ))}
              </div>
            )}
          </>
        )}
      </div>
    </div>
  );
}

/* --------- Customer profile: acquisition card --------- */
function CustomerAcquisitionCard({ email }) {
  const [a, setA] = useState(null);
  useEffect(() => {
    let alive = true; setA(null);
    if (!email) return;
    fetch("/api/attribution/customer/" + encodeURIComponent(email)).then(r => r.json()).then(j => alive && setA(j)).catch(() => alive && setA(null));
    return () => { alive = false; };
  }, [email]);
  if (!a || (!a.acquired && (!a.channels || a.channels.length === 0))) return null;
  return (
    <div className="card">
      <div className="card__head"><h3>Acquisition</h3></div>
      <div className="card__body">
        {a.acquired ? (
          <div style={{ marginBottom: a.channels && a.channels.length ? 14 : 0 }}>
            <div className="attr-k">Acquired via</div>
            <SourceBadge channel={a.acquired.channel} source={a.acquired.source} />
            {a.acquired.campaign && <div className="dim attr-camp">{a.acquired.campaign}</div>}
            {a.acquired.ts && <div className="dim" style={{ fontSize: 11, marginTop: 3 }}>{cvrDayYr(a.acquired.ts)}</div>}
          </div>
        ) : <div className="dim" style={{ fontSize: 13 }}>No acquisition source recorded yet.</div>}
        {a.channels && a.channels.length > 0 && (
          <dl className="kv">
            {a.channels.map(c => (
              <React.Fragment key={c.channel}>
                <dt><SourceBadge channel={c.channel} small /></dt>
                <dd>{c.orders} order{c.orders === 1 ? "" : "s"} · {gbp(c.revenue)}</dd>
              </React.Fragment>
            ))}
          </dl>
        )}
      </div>
    </div>
  );
}

// ROAS cell: CRM-attributed ROAS, falling back to Google Ads' reported ROAS
// (marked with ~) when nothing is attributed to that row yet.
function RoasCell({ row }) {
  if (row.roas) return <strong>{row.roas}×</strong>;
  if (row.reportedRoas != null) return <span title="Google Ads reported conversions (until CRM attribution catches up)" style={{ color: "var(--txt-2)" }}>~{row.reportedRoas}×</span>;
  return <span className="dim">—</span>;
}
// Revenue cell: CRM-attributed revenue; if none yet but Google reports conversions,
// show the Google-reported figure (marked ~) so a spend row isn't a bare £0.
function RevCell({ row }) {
  if (row.revenue) return <span style={{ fontWeight: 600 }}>{gbp(row.revenue)}</span>;
  if (row.reportedRevenue) return <span title="Google Ads reported conversion value (CRM hasn't attributed these orders yet)" style={{ color: "var(--txt-2)" }}>~{gbp(row.reportedRevenue)}</span>;
  return <span style={{ fontWeight: 600 }}>{gbp(0)}</span>;
}

// Business-health strip: repeat-order rate over rolling weeks + a drop alert.
function HealthStrip() {
  const [h, setH] = useState(null);
  useEffect(() => { let a = true; fetch("/api/conversions/health?weeks=8").then(r => r.json()).then(j => a && setH(j)).catch(() => {}); return () => { a = false; }; }, []);
  if (!h || !h.weeks) return null;
  const max = h.weeks.reduce((m, w) => Math.max(m, w.orders), 0) || 1;
  const L = h.latest || {};
  return (
    <div className="cvr__health">
      {(h.alerts || []).map((al, i) => (
        <div key={i} className="cvr__alert"><Icon name="spark" /> {al.message}</div>
      ))}
      <div className="cvr__healthrow">
        <div className="cvr__hstat"><div className="cvr__hk">Orders / wk</div><div className="cvr__hv">{L.orders}{h.ordersDrop ? <span className={"cvr__delta " + (h.ordersDrop < 0 ? "neg" : "pos")}>{h.ordersDrop > 0 ? "+" : ""}{h.ordersDrop}%</span> : null}</div></div>
        <div className="cvr__hstat"><div className="cvr__hk">Repeat-order rate</div><div className="cvr__hv">{L.repeatRate}%{h.rateDrop ? <span className={"cvr__delta " + (h.rateDrop < 0 ? "neg" : "pos")}>{h.rateDrop > 0 ? "+" : ""}{h.rateDrop}pt</span> : null}</div></div>
        <div className="cvr__spark" title="Orders per week (last 8)">
          {h.weeks.map((w, i) => (
            <span key={i} className="cvr__bar2" style={{ height: Math.max(4, (w.orders / max) * 34) + "px" }}
                  title={cvrDay(w.weekStart) + " · " + w.orders + " orders · " + w.repeatRate + "% repeat"} />
          ))}
        </div>
      </div>
    </div>
  );
}

// LTV by acquisition source (cohort) — is a channel negative on first order but
// positive over lifetime? Shows CAC, first-order ROAS and LTV-ROAS side by side.
function LtvBySource({ from }) {
  const [d, setD] = useState(null);
  useEffect(() => { let a = true; setD(null); fetch("/api/conversions/ltv?from=" + from).then(r => r.json()).then(j => a && setD(j)).catch(() => {}); return () => { a = false; }; }, [from]);
  if (!d || !d.rows) return null;
  const rows = d.rows.filter(r => r.customers > 0 || r.spend);
  if (!rows.length) return null;
  return (
    <div className="card" style={{ marginTop: 14 }}>
      <div className="card__head"><h3>LTV by acquisition source</h3>
        <span className="right kick dim">customers acquired this window · lifetime value across tracked orders</span>
      </div>
      <div className="card__body--flush tbl-wrap">
        <table className="tbl cvr__tbl">
          <thead><tr>
            <th>Channel</th><th className="num">New custs</th><th className="num" title="Cost to acquire a customer">CAC</th>
            <th className="num" title="First-order revenue ÷ spend">1st-order ROAS</th>
            <th className="num">Avg LTV</th><th className="num">LTV</th>
            <th className="num" title="Lifetime value ÷ spend">LTV-ROAS</th><th className="num">Repeat</th>
          </tr></thead>
          <tbody>
            {rows.map(r => (
              <tr key={r.channel}>
                <td><SourceBadge channel={r.channel} /></td>
                <td className="num">{r.customers}</td>
                <td className="num dim">{r.cac != null ? gbp(r.cac) : "—"}</td>
                <td className="num">{r.firstRoas != null ? r.firstRoas + "×" : <span className="dim">—</span>}</td>
                <td className="num">{gbp(r.avgLtv)}</td>
                <td className="num" style={{ fontWeight: 600 }}>{gbp(r.ltv)}</td>
                <td className="num">{r.ltvRoas != null ? <strong className={r.ltvRoas >= 1 ? "" : "cvr__neg"}>{r.ltvRoas}×</strong> : <span className="dim">—</span>}</td>
                <td className="num">{r.repeatRate}%</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
      <div className="card__body dim" style={{ fontSize: 11.5, borderTop: "1px solid var(--rule)" }}>
        A channel with 1st-order ROAS below 1× but LTV-ROAS above 1× still pays back — just over repeat orders, not the first. {d.note}
      </div>
    </div>
  );
}

// Email funnel report — Sent → Opened → Clicked → Converted per campaign.
function EmailReport({ go }) {
  const [rangeKey, setRangeKey] = useState("30");
  const [d, setD] = useState(null);
  const [loading, setLoading] = useState(true);
  const range = RANGES.find(r => r.key === rangeKey) || RANGES[1];
  const from = useMemo(() => {
    if (range.days === 0) { const d = new Date(); d.setHours(0, 0, 0, 0); return d.getTime(); } // "Today" = local midnight
    return Date.now() - range.days * 86400000;
  }, [rangeKey]);
  useEffect(() => {
    let a = true; setLoading(true);
    fetch("/api/email/funnel?from=" + from).then(r => r.json()).then(j => { if (a) { setD(j); setLoading(false); } }).catch(() => a && setLoading(false));
    return () => { a = false; };
  }, [from]);
  const t = d && d.totals;
  const stages = t ? [
    { k: "Sent", v: t.sent, pct: 100 },
    { k: "Opened", v: t.opened, pct: t.sent ? Math.round(t.opened / t.sent * 100) : 0 },
    { k: "Clicked", v: t.clicked, pct: t.sent ? Math.round(t.clicked / t.sent * 100) : 0 },
    { k: "Converted", v: t.converted, pct: t.sent ? Math.round(t.converted / t.sent * 100) : 0 },
  ] : [];
  const colors = ["#2f6df6", "#12b886", "#f0842b", "#e64980"];
  return (
    <div className="board cvr">
      <div className="toolbar cvr__bar">
        <div className="seg">{RANGES.map(r => <button key={r.key} className={rangeKey === r.key ? "is-active" : ""} onClick={() => setRangeKey(r.key)}>{r.label}</button>)}</div>
        <div className="right kick dim">email performance · {cvrDayYr(from)} → today</div>
      </div>
      {loading && !d ? <SkeletonRows rows={6} /> : t && (
        <>
          <div className="cvr__kpis" style={{ gridTemplateColumns: "repeat(5,1fr)" }}>
            <div className="kpi"><div className="kpi__k">Sent</div><div className="kpi__v">{t.sent}</div></div>
            <div className="kpi"><div className="kpi__k">Open rate</div><div className="kpi__v">{t.openRate}%</div></div>
            <div className="kpi"><div className="kpi__k">CTR</div><div className="kpi__v">{t.ctr}%</div></div>
            <div className="kpi"><div className="kpi__k">Conv. rate</div><div className="kpi__v">{t.convRate}%</div></div>
            <div className="kpi"><div className="kpi__k">Revenue</div><div className="kpi__v">{gbp(t.revenue)}</div></div>
          </div>
          <div className="card" style={{ marginTop: 14 }}>
            <div className="card__head"><h3>Funnel</h3></div>
            <div className="card__body">
              <div className="efunnel">
                {stages.map((s, i) => (
                  <div key={s.k} className="efunnel__stage">
                    <div className="efunnel__bar" style={{ width: Math.max(6, s.pct) + "%", background: colors[i] }}>{s.v}</div>
                    <div className="efunnel__lbl">{s.k} · {s.pct}%</div>
                  </div>
                ))}
              </div>
            </div>
          </div>
          <div className="card" style={{ marginTop: 14 }}>
            <div className="card__head"><h3>By campaign</h3><span className="right kick">{d.rows.length}</span></div>
            <div className="card__body--flush tbl-wrap">
              <table className="tbl cvr__tbl">
                <thead><tr>
                  <th>Subject</th><th className="num">Sent</th><th className="num">Opened</th>
                  <th className="num">Clicked</th><th className="num" title="Click-to-open rate">CTOR</th>
                  <th className="num">Converted</th><th className="num">Revenue</th>
                </tr></thead>
                <tbody>
                  {d.rows.map(r => (
                    <tr key={r.context}>
                      <td style={{ maxWidth: 320, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }} title={r.subject}>{r.subject}</td>
                      <td className="num">{r.sent}</td>
                      <td className="num">{r.opened}{r.sent ? <span className="dim"> · {r.openRate}%</span> : null}</td>
                      <td className="num">{r.clicked}{r.sent ? <span className="dim"> · {r.ctr}%</span> : null}</td>
                      <td className="num dim">{r.ctor}%</td>
                      <td className="num">{r.converted ? <strong>{r.converted}</strong> : <span className="dim">0</span>}{r.sent ? <span className="dim"> · {r.convRate}%</span> : null}</td>
                      <td className="num" style={{ fontWeight: 600 }}>{gbp(r.revenue)}</td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
            {d.note && <div className="card__body dim" style={{ fontSize: 11.5, borderTop: "1px solid var(--rule)" }}><Icon name="spark" /> {d.note}</div>}
          </div>
        </>
      )}
    </div>
  );
}

Object.assign(window, { ConversionsReport, OrderAttributionCard, CustomerAcquisitionCard, SourceBadge, HealthStrip, LtvBySource, RoasCell, RevCell, EmailReport });
