/* Analytics Dashboard — sessions · visitors · orders · revenue · CR · customer growth · retention.
   Single API call per load. Smooth bezier line chart. Prev/next period nav. AI insights. */

const AN_RANGES = [
  { key: "today",  label: "Today",       days: 1,   unit: "day"   },
  { key: "7d",     label: "Last 7d",     days: 7,   unit: "day"   },
  { key: "30d",    label: "Last 30d",    days: 30,  unit: "day"   },
  { key: "90d",    label: "Last 90d",    days: 90,  unit: "week"  },
  { key: "12mo",   label: "12 months",   days: 365, unit: "month" },
  { key: "custom", label: "Custom",      days: 0,   unit: "day"   },
];

const METRICS = [
  { key: "sessions",           label: "Sessions",       fmt: "int", color: "#2f6df6" },
  { key: "visitors",           label: "Visitors",       fmt: "int", color: "#8b5cf6" },
  { key: "orders",             label: "Orders",         fmt: "int", color: "#18f4af" },
  { key: "revenue",            label: "Revenue",        fmt: "gbp", color: "#f0842b" },
  { key: "cr",                 label: "Conv. rate",     fmt: "pct1",color: "#12b886" },
  { key: "newCustomers",       label: "New customers",  fmt: "int", color: "#4dabf7" },
  { key: "returningCustomers", label: "Returning",      fmt: "int", color: "#e64980" },
  { key: "retentionRate",      label: "Retention",      fmt: "pct1",color: "#adb5bd" },
];

// GA4 series keys use "ga" prefix to distinguish from pixel data
const CHART_SERIES = [
  { key: "gaSessions", label: "Sessions (GA4·UK)", color: "#2f6df6", axis: "left",  gaOnly: true  },
  { key: "gaUsers",    label: "Users (GA4·UK)",    color: "#8b5cf6", axis: "left",  gaOnly: true  },
  { key: "sessions",   label: "Sessions (pixel)",  color: "#4a90d9", axis: "left",  pixelOnly: true },
  { key: "orders",     label: "Orders",            color: "#18f4af", axis: "right" },
  { key: "revenue",    label: "Revenue",           color: "#f0842b", axis: "right" },
];

function anFmt(v, type) {
  if (v == null) return "—";
  if (type === "gbp")  return DTF && DTF.fmtGBP ? DTF.fmtGBP(v) : "£" + (+v).toFixed(2);
  if (type === "pct1") return (+v).toFixed(1) + "%";
  if (type === "pct")  return (+v).toFixed(1) + "%";
  return Number(v).toLocaleString("en-GB");
}

function signStr(n) { return n > 0 ? "+" : ""; }
function deltaCol(pct) {
  if (pct == null) return "var(--txt-3)";
  return pct > 0 ? "#12b886" : pct < 0 ? "#e03131" : "var(--txt-3)";
}

// ---------------------------------------------------------------------------
// Period helpers
// ---------------------------------------------------------------------------
function periodBounds(rangeKey, offset = 0) {
  const r = AN_RANGES.find(x => x.key === rangeKey) || AN_RANGES[2];
  if (rangeKey === "custom") return null; // custom handled separately
  const span = r.days * 86400000;
  if (rangeKey === "today") {
    const s = new Date(); s.setHours(0, 0, 0, 0);
    const ms = s.getTime() + offset * span;
    return { from: ms, to: ms + span - 1 };
  }
  const now = Date.now();
  const end = now + offset * span;
  return { from: end - span, to: end };
}

function periodLabel(rangeKey, offset) {
  const b = periodBounds(rangeKey, offset);
  if (!b) return "";
  const { from, to } = b;
  const f = new Date(from), t = new Date(to);
  const o = { day: "numeric", month: "short" };
  const oy = { ...o, year: "numeric" };
  if (f.getFullYear() !== t.getFullYear())
    return f.toLocaleDateString("en-GB", oy) + " – " + t.toLocaleDateString("en-GB", oy);
  if (f.getMonth() === t.getMonth() && f.getDate() === t.getDate())
    return f.toLocaleDateString("en-GB", oy);
  return f.toLocaleDateString("en-GB", o) + " – " + t.toLocaleDateString("en-GB", o) + " " + t.getFullYear();
}

function msToDateInput(ms) {
  return new Date(ms).toISOString().slice(0, 10);
}

// ---------------------------------------------------------------------------
// Smooth bezier path (Catmull-Rom → cubic bezier)
// ---------------------------------------------------------------------------
function smoothPath(pts) {
  if (!pts || pts.length < 2) return "";
  let d = `M${pts[0].x.toFixed(1)},${pts[0].y.toFixed(1)}`;
  for (let i = 0; i < pts.length - 1; i++) {
    const p0 = pts[Math.max(0, i - 1)];
    const p1 = pts[i];
    const p2 = pts[i + 1];
    const p3 = pts[Math.min(pts.length - 1, i + 2)];
    const cp1x = p1.x + (p2.x - p0.x) / 6;
    const cp1y = p1.y + (p2.y - p0.y) / 6;
    const cp2x = p2.x - (p3.x - p1.x) / 6;
    const cp2y = p2.y - (p3.y - p1.y) / 6;
    d += ` C${cp1x.toFixed(1)},${cp1y.toFixed(1)} ${cp2x.toFixed(1)},${cp2y.toFixed(1)} ${p2.x.toFixed(1)},${p2.y.toFixed(1)}`;
  }
  return d;
}
function smoothArea(pts, baseline) {
  const line = smoothPath(pts);
  if (!line) return "";
  return `${line} L${pts[pts.length-1].x.toFixed(1)},${baseline} L${pts[0].x.toFixed(1)},${baseline} Z`;
}

// ---------------------------------------------------------------------------
// Line Chart
// ---------------------------------------------------------------------------
const DOW = ["Su","Mo","Tu","We","Th","Fr","Sa"];
function tooltipLabel(b) {
  if (!b) return "";
  if (/^\d{4}-\d{2}-\d{2}$/.test(b.key)) {
    const d = new Date(b.key + "T00:00:00Z");
    return DOW[d.getUTCDay()] + " " + b.label;
  }
  return b.label;
}

function AnLineChart({ buckets, visibleSeries, allSeries }) {
  const series = allSeries || CHART_SERIES;
  const W = 900, H = 240, PL = 0, PR = 0, PT = 20, PB = 40;
  const iW = W - PL - PR, iH = H - PT - PB;
  const [hover, setHover] = React.useState(null);

  if (!buckets || buckets.length < 2) return (
    <div className="an-chart--empty">Not enough data points</div>
  );

  const leftSeries  = series.filter(s => s.axis === "left"  && visibleSeries.includes(s.key));
  const rightSeries = series.filter(s => s.axis === "right" && visibleSeries.includes(s.key));
  const allActive   = [...leftSeries, ...rightSeries];

  const rawMaxL = Math.max(...buckets.flatMap(b => leftSeries.map(s  => b[s.key]  || 0)), 1);
  const rawMaxR = Math.max(...buckets.flatMap(b => rightSeries.map(s => b[s.key]  || 0)), 1);

  function niceMax(v) {
    const mag = Math.pow(10, Math.floor(Math.log10(v)));
    const f = [1, 1.5, 2, 2.5, 5, 10].map(x => x * mag).find(x => x >= v);
    return f || v;
  }
  const maxL = niceMax(rawMaxL), maxR = niceMax(rawMaxR);

  const xOf = (i) => PL + (i / (buckets.length - 1)) * iW;
  const yOf = (v, axis) => PT + iH - Math.min(1, v / (axis === "left" ? maxL : maxR)) * iH;

  const pts = (series) => buckets.map((b, i) => ({ x: xOf(i), y: yOf(b[series.key] || 0, series.axis) }));

  // Y grid lines (4 steps, left scale)
  const gridCount = 4;
  const gridVals = Array.from({ length: gridCount + 1 }, (_, i) => (maxL / gridCount) * i);

  // X tick labels — max 7
  const step = Math.max(1, Math.ceil(buckets.length / 7));
  const tickIdxs = buckets.map((_, i) => i).filter(i => i % step === 0 || i === buckets.length - 1);

  const hoverX = hover != null ? xOf(hover) : null;

  return (
    <div className="an-chart-wrap"
         onMouseLeave={() => setHover(null)}
         onMouseMove={e => {
           const rect = e.currentTarget.querySelector("svg").getBoundingClientRect();
           const mx = (e.clientX - rect.left) / rect.width * W;
           let best = 0, bestD = Infinity;
           buckets.forEach((_, i) => { const d = Math.abs(xOf(i) - mx); if (d < bestD) { bestD = d; best = i; } });
           setHover(best);
         }}>
      <svg viewBox={`0 0 ${W} ${H}`} className="an-chart-svg" preserveAspectRatio="none">
        <defs>
          {allActive.map(s => (
            <linearGradient key={s.key} id={`agr-${s.key}`} x1="0" y1="0" x2="0" y2="1">
              <stop offset="0%"   stopColor={s.color} stopOpacity="0.22" />
              <stop offset="75%"  stopColor={s.color} stopOpacity="0.04" />
              <stop offset="100%" stopColor={s.color} stopOpacity="0" />
            </linearGradient>
          ))}
          <clipPath id="an-clip"><rect x={PL} y={PT} width={iW} height={iH} /></clipPath>
        </defs>

        {/* Horizontal grid lines */}
        {gridVals.map((v, i) => (
          <line key={i} x1={PL} y1={yOf(v, "left")} x2={W - PR} y2={yOf(v, "left")}
                stroke={i === 0 ? "#2a2d35" : "#1e2128"} strokeWidth={i === 0 ? "1" : "1"} strokeDasharray={i > 0 ? "4 4" : ""} />
        ))}

        {/* Area fills (clipped) */}
        <g clipPath="url(#an-clip)">
          {allActive.map(s => (
            <path key={s.key + "-a"} d={smoothArea(pts(s), PT + iH)}
                  fill={`url(#agr-${s.key})`} />
          ))}
        </g>

        {/* Lines (clipped) */}
        <g clipPath="url(#an-clip)">
          {allActive.map(s => (
            <path key={s.key + "-l"} d={smoothPath(pts(s))}
                  fill="none" stroke={s.color} strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" />
          ))}
        </g>

        {/* Hover crosshair */}
        {hoverX != null && (
          <line x1={hoverX} y1={PT} x2={hoverX} y2={PT + iH}
                stroke="rgba(255,255,255,0.12)" strokeWidth="1" strokeDasharray="3 3" />
        )}

        {/* Hover dots */}
        {hover != null && allActive.map(s => {
          const p = pts(s)[hover];
          return (
            <circle key={s.key} cx={p.x} cy={p.y} r={5}
                    fill={s.color} stroke="#1a1c22" strokeWidth="2" />
          );
        })}

        {/* X-axis tick labels */}
        {tickIdxs.map(i => (
          <text key={i} x={xOf(i)} y={H - 6} textAnchor="middle" fontSize="11" fill="#4a5568">
            {buckets[i].label}
          </text>
        ))}

        {/* Y-axis left values (ghost, top of each grid line, outside chart) */}
        {gridVals.filter((_, i) => i > 0).map((v, i) => (
          <text key={i} x={PL + 4} y={yOf(v, "left") - 4} fontSize="10" fill="#3a4152">
            {v >= 1000 ? (v / 1000).toFixed(v % 1000 === 0 ? 0 : 1) + "k" : v}
          </text>
        ))}
      </svg>

      {/* Floating tooltip */}
      {hover != null && (
        <div className="an-tooltip" style={{
          left: `${Math.min(85, Math.max(10, (hover / (buckets.length - 1)) * 100))}%`,
          transform: hover > buckets.length * 0.6 ? "translateX(-110%)" : "translateX(8px)",
        }}>
          <div className="an-tooltip__label">{tooltipLabel(buckets[hover])}</div>
          {allActive.map(s => (
            <div key={s.key} className="an-tooltip__row">
              <span className="an-tooltip__dot" style={{ background: s.color }} />
              <span className="an-tooltip__name">{s.label}</span>
              <span className="an-tooltip__val">
                {s.key === "revenue" ? anFmt(buckets[hover][s.key], "gbp")
                  : s.key === "cr"  ? anFmt(buckets[hover][s.key], "pct1")
                  : anFmt(buckets[hover][s.key], "int")}
              </span>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

// ---------------------------------------------------------------------------
// KPI delta card
// ---------------------------------------------------------------------------
function DeltaCard({ metric, delta, active, onClick, gaTag }) {
  const pct = delta && delta.pct;
  const arrow = pct == null ? null : pct > 0 ? "▲" : pct < 0 ? "▼" : null;
  const col = deltaCol(pct);
  const isBtn = !!onClick;
  return (
    <div className={"an-kpi" + (active ? " is-active" : "") + (isBtn ? " an-kpi--btn" : "")}
         style={{ "--accent": metric.color }} onClick={onClick}>
      <div className="an-kpi__label">
        {metric.label}
        {gaTag && <span className="an-ga-pill">GA4</span>}
      </div>
      <div className="an-kpi__val">{delta ? anFmt(delta.current, metric.fmt) : "—"}</div>
      {delta && delta.previous != null && (
        <div className="an-kpi__delta" style={{ color: col }}>
          {arrow && <span>{arrow} </span>}
          {pct != null ? signStr(pct) + Math.abs(pct).toFixed(1) + "%" : "nc"}
          <span className="an-kpi__prev"> vs {anFmt(delta.previous, metric.fmt)}</span>
        </div>
      )}
      <div className="an-kpi__accent" style={{ background: metric.color }} />
    </div>
  );
}

// ---------------------------------------------------------------------------
// AI Insights
// ---------------------------------------------------------------------------
function AiInsights({ from, to, excludeRanges }) {
  const [data, setData]       = React.useState(null);
  const [loading, setLoading] = React.useState(false);
  const [err, setErr]         = React.useState(null);

  function load() {
    if (loading) return;
    setLoading(true); setErr(null);
    const p = new URLSearchParams({ from, to });
    if (excludeRanges && excludeRanges.length) p.set("excludeRanges", JSON.stringify(excludeRanges));
    fetch("/api/analytics/insights?" + p)
      .then(r => r.json())
      .then(j => { setLoading(false); j.error ? setErr(j.error) : setData(j); })
      .catch(e => { setLoading(false); setErr(e.message); });
  }

  if (!data && !loading) return (
    <div className="an-ai">
      <button className="btn btn--sm btn--ghost" onClick={load}>
        <Icon name="spark" /> AI insights
      </button>
    </div>
  );

  return (
    <div className="an-ai an-ai--open card" style={{ marginTop: 14 }}>
      <div className="card__head">
        <h3><Icon name="spark" style={{ marginRight: 6, verticalAlign: "middle" }} />AI insights</h3>
        <button className="btn btn--ghost btn--sm right" onClick={load} disabled={loading}>
          {loading ? <span className="spin spin--dark" /> : <Icon name="refresh" />}
        </button>
      </div>
      <div className="card__body">
        {loading && <div style={{ display:"flex", gap:8, alignItems:"center", color:"var(--txt-2)", fontSize:13 }}><span className="spin" /> Analysing…</div>}
        {err && <div style={{ color:"#c0392b", fontSize:13 }}>{err}</div>}
        {data && data.insights && (
          <div className="an-ai__text">
            {data.insights.split("\n").filter(Boolean).map((line, i) => <p key={i}>{line}</p>)}
          </div>
        )}
        {data && <div className="dim" style={{ fontSize:11, marginTop:8 }}>
          Generated {new Date(data.generatedAt).toLocaleTimeString("en-GB", { hour:"2-digit", minute:"2-digit" })}
        </div>}
      </div>
    </div>
  );
}

// ---------------------------------------------------------------------------
// Spam exclusion
// ---------------------------------------------------------------------------
function SpamExclusion({ excludeRanges, onChange }) {
  const [open, setOpen] = React.useState(false);
  const [from, setFrom] = React.useState("");
  const [to,   setTo]   = React.useState("");

  function add() {
    if (!from || !to) return;
    const f = new Date(from + "T00:00:00Z").getTime();
    const t = new Date(to   + "T23:59:59Z").getTime();
    if (f > t) return;
    onChange([...excludeRanges, { from: f, to: t, label: from + " – " + to }]);
    setFrom(""); setTo("");
  }

  return (
    <div className="an-spam">
      <button className={"btn btn--ghost btn--sm" + (excludeRanges.length ? " is-active" : "")}
              onClick={() => setOpen(o => !o)}>
        <Icon name="filter" />
        {excludeRanges.length > 0 ? ` ${excludeRanges.length} excluded` : " Exclude spam"}
      </button>
      {open && (
        <div className="an-spam__panel card">
          <div className="card__head">
            <h3>Exclude date ranges</h3>
            <button className="icon-btn right" onClick={() => setOpen(false)}><Icon name="x" /></button>
          </div>
          <div className="card__body">
            <p className="dim" style={{ fontSize:12, marginBottom:10 }}>Strip known spam periods (e.g. Sep 2024 bot traffic) from all metrics.</p>
            {excludeRanges.length > 0 && (
              <div className="an-spam__list">
                {excludeRanges.map((r, i) => (
                  <div key={i} className="an-spam__row">
                    <span style={{ fontSize:13 }}>{r.label || new Date(r.from).toLocaleDateString("en-GB") + " – " + new Date(r.to).toLocaleDateString("en-GB")}</span>
                    <button className="icon-btn" onClick={() => onChange(excludeRanges.filter((_, j) => j !== i))}><Icon name="x" /></button>
                  </div>
                ))}
              </div>
            )}
            <div className="an-spam__add">
              <input type="date" value={from} onChange={e => setFrom(e.target.value)} className="shop-lin" style={{ flex:1 }} />
              <span className="dim" style={{ fontSize:12 }}>to</span>
              <input type="date" value={to}   onChange={e => setTo(e.target.value)}   className="shop-lin" style={{ flex:1 }} />
              <button className="btn btn--sm" onClick={add} disabled={!from || !to}>Add</button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// ---------------------------------------------------------------------------
// Customer growth stacked bar
// ---------------------------------------------------------------------------
function CustomerGrowthChart({ buckets }) {
  if (!buckets || !buckets.length) return <div className="an-chart--empty dim" style={{padding:20,textAlign:"center"}}>No data</div>;
  const W = 600, H = 130, PL = 0, PR = 0, PT = 8, PB = 22;
  const iW = W - PL - PR, iH = H - PT - PB;
  const n = buckets.length;
  const gapRatio = n > 20 ? 0.08 : n > 8 ? 0.15 : 0.25;
  const barW = Math.max(2, (iW / n) * (1 - gapRatio));
  const maxT = Math.max(...buckets.map(b => (b.newCustomers || 0) + (b.returningCustomers || 0)), 1);
  const xOf = (i) => PL + i * (iW / n) + ((iW / n) - barW) / 2;
  const step = Math.max(1, Math.ceil(n / 6));
  const ticks = buckets.map((_, i) => i).filter(i => i % step === 0 || i === n - 1);
  return (
    <svg viewBox={`0 0 ${W} ${H}`} className="an-chart-svg an-chart-svg--sm" preserveAspectRatio="none">
      {buckets.map((b, i) => {
        const newH = ((b.newCustomers || 0) / maxT) * iH;
        const retH = ((b.returningCustomers || 0) / maxT) * iH;
        const x = xOf(i), bot = PT + iH;
        return (
          <g key={b.key}>
            {newH > 0 && <rect x={x} y={bot - newH - retH} width={barW} height={newH} fill="#4dabf7" rx="2" />}
            {retH > 0 && <rect x={x} y={bot - retH}        width={barW} height={retH} fill="#e64980" rx="2" />}
          </g>
        );
      })}
      {ticks.map(i => (
        <text key={i} x={xOf(i) + barW / 2} y={H - 2} textAnchor="middle" fontSize="10" fill="#4a5568">
          {buckets[i].label}
        </text>
      ))}
    </svg>
  );
}

// ---------------------------------------------------------------------------
// Main Dashboard
// ---------------------------------------------------------------------------
function readAnParams() {
  const h = (location.hash || "").replace(/^#\/?/, "");
  const q = h.includes("?") ? h.split("?")[1] : "";
  const p = new URLSearchParams(q);
  return {
    rangeKey:   p.get("r")    || "30d",
    offset:     p.has("o")    ? parseInt(p.get("o"), 10) : 0,
    bucket:     p.get("b")    || "day",
    customFrom: p.get("from") || "",
    customTo:   p.get("to")   || "",
  };
}

function AnalyticsDashboard({ go }) {
  const init = React.useMemo(readAnParams, []);
  const [rangeKey, setRangeKey] = React.useState(init.rangeKey);
  const [offset,   setOffset]   = React.useState(init.offset);
  const [bucket,   setBucket]   = React.useState(init.bucket);
  const [excludeRanges, setExcludeRanges] = React.useState([]);
  const [activeMetrics, setActiveMetrics] = React.useState(["gaSessions", "orders"]);

  // Custom date range
  const [customFrom, setCustomFrom] = React.useState(init.customFrom);
  const [customTo,   setCustomTo]   = React.useState(init.customTo);

  const [data,    setData]    = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [err,     setErr]     = React.useState(null);

  const isCustom = rangeKey === "custom";

  // Resolved from/to ms
  const bounds = React.useMemo(() => {
    if (isCustom) {
      if (!customFrom || !customTo) return null;
      return {
        from: new Date(customFrom + "T00:00:00Z").getTime(),
        to:   new Date(customTo   + "T23:59:59Z").getTime(),
      };
    }
    return periodBounds(rangeKey, offset);
  }, [rangeKey, offset, isCustom, customFrom, customTo]);

  const from = bounds && bounds.from;
  const to   = bounds && bounds.to;

  React.useEffect(() => {
    if (isCustom) return;
    const r = AN_RANGES.find(x => x.key === rangeKey);
    if (r && r.unit) setBucket(r.unit);
    setOffset(0);
  }, [rangeKey]);

  React.useEffect(() => {
    if (!from || !to) return;
    let alive = true;
    setLoading(true); setErr(null);
    const ex = excludeRanges.length ? "&excludeRanges=" + encodeURIComponent(JSON.stringify(excludeRanges)) : "";
    fetch(`/api/analytics/timeseries?from=${from}&to=${to}&bucket=${bucket}&compareWithPrev=1&includeGa=1${ex}`)
      .then(r => r.json())
      .then(j => {
        if (!alive) return;
        if (j.error) setErr(j.error); else setData(j);
        setLoading(false);
      })
      .catch(e => { if (alive) { setErr(e.message); setLoading(false); } });
    return () => { alive = false; };
  }, [from, to, bucket, excludeRanges]);

  // Auto-pick bucket when custom range changes
  React.useEffect(() => {
    if (!isCustom || !from || !to) return;
    const days = (to - from) / 86400000;
    if (days > 90) setBucket("week");
    else if (days > 14) setBucket("day");
  }, [from, to]);

  // Sync state → URL so the page is reloadable at the same position
  React.useEffect(() => {
    const p = new URLSearchParams({ r: rangeKey, b: bucket });
    if (offset !== 0) p.set("o", offset);
    if (isCustom && customFrom) p.set("from", customFrom);
    if (isCustom && customTo)   p.set("to",   customTo);
    history.replaceState(null, "", "#/analytics?" + p.toString());
  }, [rangeKey, offset, bucket, customFrom, customTo]);

  function toggleMetric(key) {
    setActiveMetrics(prev =>
      prev.includes(key) ? (prev.length > 1 ? prev.filter(k => k !== key) : prev) : [...prev, key]
    );
  }

  const deltas    = data && data.deltas;
  const gaOk      = data && data.gaTotals && !data.gaError;
  const showChart = data && data.buckets && data.buckets.length > 1;
  const visibleChartSeries = CHART_SERIES.filter(s =>
    (!s.gaOnly || gaOk) && (!s.pixelOnly || !gaOk)
  );
  const navLabel  = isCustom ? "" : periodLabel(rangeKey, offset);

  return (
    <div className="board an-board">
      {/* Toolbar */}
      <div className="toolbar an-bar">
        <div className="seg an-seg-ranges">
          {AN_RANGES.map(r => (
            <button key={r.key} className={rangeKey === r.key ? "is-active" : ""} onClick={() => setRangeKey(r.key)}>
              {r.label}
            </button>
          ))}
        </div>

        {/* Custom date inputs */}
        {isCustom && (
          <div className="an-custom-range">
            <input type="date" value={customFrom} onChange={e => setCustomFrom(e.target.value)} className="shop-lin an-date-in" />
            <span className="dim">–</span>
            <input type="date" value={customTo}   onChange={e => setCustomTo(e.target.value)}   className="shop-lin an-date-in" />
          </div>
        )}

        {/* Prev / Next (hidden for custom) */}
        {!isCustom && (
          <div className="an-nav">
            <button className="icon-btn" onClick={() => setOffset(o => o - 1)} title="Previous period">
              <span style={{ display:"block", transform:"rotate(180deg)" }}><Icon name="chevron" /></span>
            </button>
            <span className="an-nav__label">
              {navLabel}
              {loading && data && <span className="spin spin--dark" style={{ marginLeft:6, display:"inline-block", verticalAlign:"middle" }} />}
            </span>
            <button className="icon-btn" onClick={() => setOffset(o => o + 1)} disabled={offset >= 0} title="Next period">
              <Icon name="chevron" />
            </button>
          </div>
        )}

        <div className="seg">
          {["day", "week", "month"].map(b => (
            <button key={b} className={bucket === b ? "is-active" : ""} onClick={() => setBucket(b)}>
              {b[0].toUpperCase() + b.slice(1)}
            </button>
          ))}
        </div>

        <div className="right" style={{ display:"flex", gap:8, marginLeft:"auto" }}>
          <SpamExclusion excludeRanges={excludeRanges} onChange={setExcludeRanges} />
        </div>
      </div>

      {/* Custom range prompt */}
      {isCustom && (!customFrom || !customTo) && !loading && (
        <div className="empty" style={{ marginTop:24 }}>
          <Icon name="chart" />
          <div className="t">Pick a date range</div>
          <div className="m">Select a start and end date above to load the report.</div>
        </div>
      )}

      {err && <div className="empty"><Icon name="spark" /><div className="t">Couldn't load</div><div className="m">{err}</div></div>}
      {loading && !data && <SkeletonRows rows={4} />}

      {data && (
        <div style={{ opacity: loading ? 0.45 : 1, pointerEvents: loading ? "none" : "", transition: "opacity 0.15s" }}>
          {/* KPI cards */}
          <div className="an-kpis">
            {METRICS.map(m => {
              const isChart = visibleChartSeries.some(s => s.key === m.key || (m.key === "sessions" && (s.key === "sessions" || s.key === "gaSessions")));
              const chartKey = m.key === "sessions" && gaOk ? "gaSessions" : m.key === "visitors" && gaOk ? "gaUsers" : m.key;
              // For sessions/visitors, override delta with GA4 totals when available
              let delta = deltas && deltas[m.key];
              if (gaOk && data.gaTotals) {
                if (m.key === "sessions") delta = { current: data.gaTotals.sessions, previous: null, change: null, pct: null };
                if (m.key === "visitors") delta = { current: data.gaTotals.users,    previous: null, change: null, pct: null };
                if (m.key === "cr" && data.gaTotals.cr != null) delta = { current: data.gaTotals.cr, previous: deltas && deltas.cr && deltas.cr.previous, change: null, pct: null };
              }
              return (
                <DeltaCard key={m.key} metric={m} delta={delta}
                  active={isChart && (activeMetrics.includes(m.key) || activeMetrics.includes(chartKey))}
                  onClick={isChart ? () => toggleMetric(chartKey) : undefined}
                  gaTag={gaOk && (m.key === "sessions" || m.key === "visitors" || m.key === "cr")} />
              );
            })}
          </div>

          {/* Chart */}
          {showChart && (
            <div className="card an-card-chart" style={{ marginTop:14 }}>
              <div className="card__head" style={{ paddingBottom:4 }}>
                <div className="an-legend">
                  {visibleChartSeries.map(s => (
                    <button key={s.key}
                      className={"an-legend__item" + (activeMetrics.includes(s.key) ? " is-active" : "")}
                      onClick={() => toggleMetric(s.key)}>
                      <span className="an-legend__dot" style={{ background: activeMetrics.includes(s.key) ? s.color : "#33363b" }} />
                      {s.label}
                    </button>
                  ))}
                  {gaOk && <span className="an-ga-badge">GA4 · UK only</span>}
                  {data.gaError && <span className="an-ga-badge an-ga-badge--err" title={data.gaError}>GA4 error</span>}
                </div>
              </div>
              <AnLineChart buckets={data.buckets} visibleSeries={activeMetrics} allSeries={visibleChartSeries} />
            </div>
          )}

          {/* Customer row */}
          <div className="an-row2">
            <div className="card an-card-cust">
              <div className="card__head"><h3>Customer growth</h3>
                <div style={{ display:"flex", gap:14, fontSize:11, color:"var(--txt-3)", marginLeft:8 }}>
                  <span><span style={{ display:"inline-block", width:8, height:8, borderRadius:2, background:"#4dabf7", marginRight:4 }} />New</span>
                  <span><span style={{ display:"inline-block", width:8, height:8, borderRadius:2, background:"#e64980", marginRight:4 }} />Returning</span>
                </div>
              </div>
              <div className="card__body" style={{ padding:"8px 0 0" }}>
                <CustomerGrowthChart buckets={data.buckets} />
              </div>
            </div>
            <div className="card an-card-ret">
              <div className="card__head"><h3>Retention</h3></div>
              <div className="card__body an-ret">
                <div className="an-ret__big">
                  {data.totals.retentionRate != null ? data.totals.retentionRate.toFixed(1) + "%" : "—"}
                </div>
                <div className="an-ret__label">orders from returning customers</div>
                {deltas && deltas.retentionRate && deltas.retentionRate.pct != null && (
                  <div className="an-ret__delta" style={{ color: deltaCol(deltas.retentionRate.pct) }}>
                    {signStr(deltas.retentionRate.pct)}{Math.abs(deltas.retentionRate.pct).toFixed(1)}% vs prev period
                  </div>
                )}
                <div className="an-ret__breakdown">
                  <div>
                    <div className="an-ret__num" style={{ color:"#4dabf7" }}>{data.totals.newCustomers}</div>
                    <div className="an-ret__sublabel">New</div>
                  </div>
                  <div style={{ color:"var(--rule)", fontSize:18 }}>|</div>
                  <div>
                    <div className="an-ret__num" style={{ color:"#e64980" }}>{data.totals.returningCustomers}</div>
                    <div className="an-ret__sublabel">Returning</div>
                  </div>
                </div>
              </div>
            </div>
          </div>

          <AiInsights from={from} to={to} excludeRanges={excludeRanges} />
        </div>
      )}
    </div>
  );
}
