// analytics-charts.jsx — reusable chart primitives for Analytics
// All charts are inline SVG, no deps. Pure visual functions.
// ────────────────────────────────────────────────────────────
(function () {
  if (typeof window === 'undefined' || !window.React) return;
  if (window.AnalyticsCharts) return;

  const fmt$ = (n) => {
    if (n == null || isNaN(n)) return '—';
    if (Math.abs(n) >= 1e9) return '$' + (n / 1e9).toFixed(2) + 'B';
    if (Math.abs(n) >= 1e6) return '$' + (n / 1e6).toFixed(2) + 'M';
    if (Math.abs(n) >= 1e3) return '$' + (n / 1e3).toFixed(1) + 'K';
    return '$' + n.toFixed(0);
  };
  const fmtN = (n) => {
    if (n == null || isNaN(n)) return '—';
    if (Math.abs(n) >= 1e9) return (n / 1e9).toFixed(2) + 'B';
    if (Math.abs(n) >= 1e6) return (n / 1e6).toFixed(2) + 'M';
    if (Math.abs(n) >= 1e3) return (n / 1e3).toFixed(1) + 'K';
    return Math.round(n).toLocaleString();
  };
  const fmtPct = (p) => (p * 100 >= 0 ? '+' : '') + (p * 100).toFixed(1) + '%';

  const Mono = ({ children, upper, size, color, style }) =>
    React.createElement('span', { className: 'ff-mono' + (upper ? ' upper' : ''),
      style: { fontSize: size || 11, color: color || 'var(--ink)', letterSpacing: upper ? '.08em' : 0, ...style } }, children);

  // ─── KPI tile (for KPI strip) ────────────────────────────
  function KPI({ label, value, sub, pop, tone, sparkSeries, onClick }) {
    return React.createElement('div', {
      onClick,
      style: { padding: '18px 22px', borderRight: '1px solid var(--rule)', cursor: onClick ? 'pointer' : 'default', position: 'relative' }
    },
      React.createElement(Mono, { upper: true, size: 9, color: 'var(--ink-3)' }, label),
      React.createElement('div', { className: 'ff-display', style: { fontSize: 26, fontWeight: 600, letterSpacing: '-0.02em', marginTop: 4, color: tone || 'var(--ink)' } }, value),
      sub && React.createElement('div', { style: { fontSize: 11, color: 'var(--ink-2)', marginTop: 3 } }, sub),
      pop != null && React.createElement('div', { style: { fontSize: 11, marginTop: 4, color: pop >= 0 ? '#0a8754' : '#a32a18', fontWeight: 600 } }, fmtPct(pop), ' vs prior'),
      sparkSeries && React.createElement(Sparkline, { series: sparkSeries, w: 100, h: 22, style: { position: 'absolute', right: 14, top: 18 } })
    );
  }

  // ─── Sparkline ────────────────────────────────────────────
  function Sparkline({ series, w = 80, h = 20, color, style }) {
    if (!series || series.length === 0) return null;
    const vs = series.map(p => p.v);
    const min = Math.min(...vs), max = Math.max(...vs);
    const range = max - min || 1;
    const pts = series.map((p, i) => `${(i / (series.length - 1)) * w},${h - ((p.v - min) / range) * h}`).join(' ');
    return React.createElement('svg', { width: w, height: h, style },
      React.createElement('polyline', { points: pts, fill: 'none', stroke: color || 'var(--ink)', strokeWidth: 1.2 })
    );
  }

  // ─── Line chart with anomaly dots ────────────────────────
  function LineChart({ series, w = 600, h = 200, color, label, dual, anomalies }) {
    if (!series || series.length === 0) return null;
    const padL = 50, padR = 14, padT = 14, padB = 28;
    const W = w - padL - padR, H = h - padT - padB;
    const vs = series.map(p => p.v);
    const min = 0, max = Math.max(...vs) * 1.08;
    const range = max - min || 1;
    const x = (i) => padL + (i / (series.length - 1)) * W;
    const y = (v) => padT + H - ((v - min) / range) * H;
    const pts = series.map((p, i) => `${x(i)},${y(p.v)}`).join(' ');
    const area = `${x(0)},${y(0)} ${pts} ${x(series.length - 1)},${y(0)}`;
    const ticks = 4;
    return React.createElement('svg', { width: w, height: h, style: { display: 'block' } },
      [...Array(ticks + 1)].map((_, i) => {
        const v = min + (i / ticks) * range;
        return React.createElement('g', { key: i },
          React.createElement('line', { x1: padL, x2: w - padR, y1: y(v), y2: y(v), stroke: 'var(--rule-soft)', strokeWidth: 0.5 }),
          React.createElement('text', { x: padL - 6, y: y(v) + 3, textAnchor: 'end', className: 'ff-mono', style: { fontSize: 9, fill: 'var(--ink-3)' } }, fmt$(v))
        );
      }),
      React.createElement('polygon', { points: area, fill: color || 'var(--ink)', opacity: 0.08 }),
      React.createElement('polyline', { points: pts, fill: 'none', stroke: color || 'var(--ink)', strokeWidth: 1.4 }),
      anomalies && series.map((p, i) => {
        if (!anomalies[i]) return null;
        return React.createElement('circle', { key: i, cx: x(i), cy: y(p.v), r: 3.5, fill: '#a32a18', stroke: 'var(--bg)', strokeWidth: 1.5 });
      }),
      label && React.createElement('text', { x: padL, y: 10, className: 'ff-mono upper', style: { fontSize: 9, fill: 'var(--ink-3)', letterSpacing: '.08em' } }, label)
    );
  }

  // ─── Bar chart (horizontal) ─────────────────────────────
  function BarsH({ rows, w = 320, h = 240, valueKey = 'total', labelKey = 'label', color, max, onRowClick, fmtVal }) {
    if (!rows || rows.length === 0) return null;
    const maxV = max || Math.max(...rows.map(r => r[valueKey]));
    const rowH = (h - 14) / rows.length;
    const labelW = 110;
    const valW = 60;
    const barW = w - labelW - valW - 8;
    return React.createElement('svg', { width: w, height: h },
      rows.map((r, i) => {
        const v = r[valueKey] || 0;
        const bw = (v / maxV) * barW;
        const yy = i * rowH + 4;
        return React.createElement('g', { key: r.id || i, onClick: () => onRowClick && onRowClick(r), style: { cursor: onRowClick ? 'pointer' : 'default' } },
          React.createElement('text', { x: labelW - 6, y: yy + rowH / 2 + 3, textAnchor: 'end', style: { fontSize: 11, fill: 'var(--ink-2)' } },
            (r[labelKey] || '').slice(0, 18)),
          React.createElement('rect', { x: labelW, y: yy + 2, width: bw, height: rowH - 6, fill: r.color || color || 'var(--ink)', opacity: 0.85 }),
          React.createElement('text', { x: labelW + bw + 6, y: yy + rowH / 2 + 3, className: 'ff-mono', style: { fontSize: 10, fill: 'var(--ink-3)' } },
            fmtVal ? fmtVal(v) : fmt$(v))
        );
      })
    );
  }

  // ─── Heatmap calendar ────────────────────────────────────
  function HeatmapCal({ series, w = 720, h = 110 }) {
    if (!series || series.length === 0) return null;
    const cell = 12;
    const gap = 2;
    const cols = Math.ceil(series.length / 7);
    const max = Math.max(...series.map(p => p.v));
    return React.createElement('svg', { width: cols * (cell + gap), height: 7 * (cell + gap) + 18 },
      series.map((p, i) => {
        const col = Math.floor(i / 7);
        const row = i % 7;
        const intensity = max === 0 ? 0 : p.v / max;
        const lit = Math.max(0.05, intensity);
        return React.createElement('rect', {
          key: i, x: col * (cell + gap), y: row * (cell + gap), width: cell, height: cell,
          fill: 'var(--ink)', opacity: lit,
          style: { cursor: 'pointer' }
        }, React.createElement('title', null, new Date(p.t).toDateString() + ' · ' + fmt$(p.v)));
      })
    );
  }

  // ─── Choropleth (simple latitude/longitude bubble map) ──
  function Choropleth({ rows, w = 720, h = 360 }) {
    // Equirectangular projection
    const project = (lat, lng) => {
      const x = ((lng + 180) / 360) * w;
      const y = ((90 - lat) / 180) * h;
      return [x, y];
    };
    const max = Math.max(...rows.map(r => r.total || 0));
    return React.createElement('svg', { width: w, height: h, style: { background: 'var(--bg-2)', display: 'block' } },
      // Rough graticule
      [...Array(7)].map((_, i) => React.createElement('line', { key: 'g' + i, x1: 0, x2: w, y1: (i + 1) * h / 8, y2: (i + 1) * h / 8, stroke: 'var(--rule-soft)', strokeWidth: 0.5 })),
      [...Array(13)].map((_, i) => React.createElement('line', { key: 'm' + i, y1: 0, y2: h, x1: (i + 1) * w / 14, x2: (i + 1) * w / 14, stroke: 'var(--rule-soft)', strokeWidth: 0.5 })),
      rows.map((r, i) => {
        if (r.lat == null || r.lng == null) return null;
        const [x, y] = project(r.lat, r.lng);
        const radius = 4 + Math.sqrt((r.total || 0) / max) * 26;
        return React.createElement('g', { key: r.id || i },
          React.createElement('circle', { cx: x, cy: y, r: radius, fill: 'var(--ink)', opacity: 0.45 }),
          React.createElement('circle', { cx: x, cy: y, r: 2.5, fill: 'var(--ink)' }),
          React.createElement('text', { x: x + radius + 4, y: y + 3, className: 'ff-mono', style: { fontSize: 9.5, fill: 'var(--ink-2)' } }, r.code + ' · ' + fmt$(r.total))
        );
      })
    );
  }

  // ─── Sankey (territory → DSP) ───────────────────────────
  function Sankey({ flows, leftLabel = 'Market', rightLabel = 'Platform', w = 720, h = 380, leftKeys, rightKeys, leftMap, rightMap, onClick }) {
    // Build layout
    const leftTot = {};
    const rightTot = {};
    flows.forEach(f => { leftTot[f.from] = (leftTot[f.from] || 0) + f.value; rightTot[f.to] = (rightTot[f.to] || 0) + f.value; });
    const grandL = Object.values(leftTot).reduce((s, v) => s + v, 0);
    const grandR = Object.values(rightTot).reduce((s, v) => s + v, 0);
    const leftSorted = Object.entries(leftTot).sort((a, b) => b[1] - a[1]).slice(0, 10).map(([k]) => k);
    const rightSorted = Object.entries(rightTot).sort((a, b) => b[1] - a[1]).slice(0, 8).map(([k]) => k);
    const padT = 18, padB = 12;
    const H = h - padT - padB;
    const colW = 14;

    let yL = padT, leftPos = {};
    leftSorted.forEach(k => { const ht = (leftTot[k] / grandL) * H; leftPos[k] = { y: yL, h: ht }; yL += ht + 2; });
    let yR = padT, rightPos = {};
    rightSorted.forEach(k => { const ht = (rightTot[k] / grandR) * H; rightPos[k] = { y: yR, h: ht }; yR += ht + 2; });

    // Re-allocate flows over each node's height
    const drawn = [];
    const leftCursor = {};
    const rightCursor = {};
    flows.filter(f => leftPos[f.from] && rightPos[f.to])
      .sort((a, b) => b.value - a.value)
      .forEach(f => {
        const lp = leftPos[f.from], rp = rightPos[f.to];
        const lc = leftCursor[f.from] = (leftCursor[f.from] || 0);
        const rc = rightCursor[f.to] = (rightCursor[f.to] || 0);
        const ht = (f.value / leftTot[f.from]) * lp.h;
        const htR = (f.value / rightTot[f.to]) * rp.h;
        drawn.push({ ...f,
          y0: lp.y + lc, y1: lp.y + lc + ht,
          y2: rp.y + rc, y3: rp.y + rc + htR,
        });
        leftCursor[f.from] = lc + ht;
        rightCursor[f.to] = rc + htR;
      });

    return React.createElement('svg', { width: w, height: h },
      // labels
      React.createElement('text', { x: 0, y: 12, className: 'ff-mono upper', style: { fontSize: 9, fill: 'var(--ink-3)', letterSpacing: '.08em' } }, leftLabel),
      React.createElement('text', { x: w - colW, y: 12, className: 'ff-mono upper', style: { fontSize: 9, fill: 'var(--ink-3)', letterSpacing: '.08em' } }, rightLabel),
      // ribbons
      drawn.map((f, i) => {
        const x0 = colW, x1 = w - colW;
        const cx0 = x0 + (x1 - x0) * 0.5, cx1 = x0 + (x1 - x0) * 0.5;
        const d = `M${x0} ${f.y0} C${cx0} ${f.y0}, ${cx1} ${f.y2}, ${x1} ${f.y2} L${x1} ${f.y3} C${cx1} ${f.y3}, ${cx0} ${f.y1}, ${x0} ${f.y1} Z`;
        return React.createElement('path', { key: i, d, fill: 'var(--ink)', opacity: 0.12, onClick: () => onClick && onClick(f), style: { cursor: onClick ? 'pointer' : 'default' } });
      }),
      // left column
      leftSorted.map(k => React.createElement('g', { key: 'L' + k },
        React.createElement('rect', { x: 0, y: leftPos[k].y, width: colW, height: leftPos[k].h, fill: 'var(--ink)' }),
        React.createElement('text', { x: colW + 6, y: leftPos[k].y + Math.min(11, leftPos[k].h - 2), className: 'ff-mono', style: { fontSize: 10, fill: 'var(--ink-2)' } }, (leftMap && leftMap[k] && leftMap[k].l) || k)
      )),
      // right column
      rightSorted.map(k => React.createElement('g', { key: 'R' + k },
        React.createElement('rect', { x: w - colW, y: rightPos[k].y, width: colW, height: rightPos[k].h, fill: 'var(--ink)' }),
        React.createElement('text', { x: w - colW - 6, y: rightPos[k].y + Math.min(11, rightPos[k].h - 2), textAnchor: 'end', className: 'ff-mono', style: { fontSize: 10, fill: 'var(--ink-2)' } }, (rightMap && rightMap[k] && rightMap[k].l) || k)
      ))
    );
  }

  // ─── Funnel ─────────────────────────────────────────────
  function Funnel({ stages, w = 480, h = 280 }) {
    if (!stages || stages.length === 0) return null;
    const max = Math.max(...stages.map(s => s.n));
    const rowH = (h - 14) / stages.length;
    return React.createElement('svg', { width: w, height: h },
      stages.map((s, i) => {
        const ratio = s.n / max;
        const bw = ratio * (w - 200);
        const cx = 100;
        const yy = i * rowH + 4;
        const prev = i > 0 ? stages[i - 1].n : null;
        const drop = prev != null ? (1 - s.n / prev) : null;
        return React.createElement('g', { key: i },
          React.createElement('text', { x: cx - 6, y: yy + rowH / 2 + 3, textAnchor: 'end', style: { fontSize: 11, fill: 'var(--ink-2)' } }, s.l),
          React.createElement('rect', { x: cx, y: yy + 2, width: bw, height: rowH - 6, fill: 'var(--ink)', opacity: 0.78 }),
          React.createElement('text', { x: cx + bw + 8, y: yy + rowH / 2 + 3, className: 'ff-mono', style: { fontSize: 10, fill: 'var(--ink-3)' } }, fmtN(s.n)),
          drop != null && React.createElement('text', { x: cx + bw + 60, y: yy + rowH / 2 + 3, className: 'ff-mono', style: { fontSize: 10, fill: '#a32a18' } }, '−' + (drop * 100).toFixed(0) + '%')
        );
      })
    );
  }

  // ─── Pareto ─────────────────────────────────────────────
  function Pareto({ rows, w = 460, h = 220 }) {
    if (!rows || rows.length === 0) return null;
    const padL = 36, padR = 12, padT = 14, padB = 24;
    const W = w - padL - padR, H = h - padT - padB;
    const total = rows.reduce((s, r) => s + r.total, 0);
    let acc = 0;
    const pts = rows.slice(0, 30).map((r, i) => {
      acc += r.total;
      const x = padL + (i / Math.max(1, rows.length - 1)) * W;
      const y = padT + H - (acc / total) * H;
      return { x, y, label: r.label, share: r.total / total, cum: acc / total };
    });
    const path = pts.map((p, i) => (i === 0 ? `M${p.x} ${p.y}` : `L${p.x} ${p.y}`)).join(' ');
    return React.createElement('svg', { width: w, height: h },
      // 80% line
      React.createElement('line', { x1: padL, x2: w - padR, y1: padT + H * 0.2, y2: padT + H * 0.2, stroke: '#a32a18', strokeDasharray: '3,3', strokeWidth: 0.8 }),
      React.createElement('text', { x: w - padR - 4, y: padT + H * 0.2 - 3, textAnchor: 'end', className: 'ff-mono', style: { fontSize: 9, fill: '#a32a18' } }, '80% threshold'),
      [0, 0.5, 1].map(t => React.createElement('text', { key: t, x: padL - 4, y: padT + H * (1 - t) + 3, textAnchor: 'end', className: 'ff-mono', style: { fontSize: 9, fill: 'var(--ink-3)' } }, (t * 100).toFixed(0) + '%')),
      React.createElement('path', { d: path, fill: 'none', stroke: 'var(--ink)', strokeWidth: 1.4 }),
      pts.filter((_, i) => i % 5 === 0).map((p, i) => React.createElement('circle', { key: i, cx: p.x, cy: p.y, r: 2, fill: 'var(--ink)' }))
    );
  }

  // ─── Quadrant scatter ────────────────────────────────────
  function Quadrant({ rows, xKey = 'pop', yKey = 'total', xLabel = 'Growth', yLabel = 'Earnings', w = 460, h = 320 }) {
    const padL = 44, padR = 14, padT = 14, padB = 30;
    const W = w - padL - padR, H = h - padT - padB;
    const xs = rows.map(r => r[xKey]);
    const ys = rows.map(r => r[yKey]);
    const xMin = Math.min(...xs), xMax = Math.max(...xs);
    const yMin = 0, yMax = Math.max(...ys);
    const xR = xMax - xMin || 1;
    const yR = yMax - yMin || 1;
    const x = (v) => padL + ((v - xMin) / xR) * W;
    const y = (v) => padT + H - ((v - yMin) / yR) * H;
    const x0 = x(0); // zero line for growth
    return React.createElement('svg', { width: w, height: h },
      // quadrant lines
      React.createElement('line', { x1: x0, x2: x0, y1: padT, y2: padT + H, stroke: 'var(--rule)', strokeWidth: 0.6 }),
      React.createElement('line', { x1: padL, x2: w - padR, y1: padT + H / 2, y2: padT + H / 2, stroke: 'var(--rule)', strokeWidth: 0.6 }),
      // labels
      React.createElement('text', { x: padL + 4, y: padT + 10, className: 'ff-mono upper', style: { fontSize: 8, fill: 'var(--ink-3)', letterSpacing: '.08em' } }, 'rising · high earn'),
      React.createElement('text', { x: w - padR - 4, y: padT + H - 4, textAnchor: 'end', className: 'ff-mono upper', style: { fontSize: 8, fill: 'var(--ink-3)', letterSpacing: '.08em' } }, 'rising · low earn'),
      React.createElement('text', { x: padL + 4, y: padT + H - 4, className: 'ff-mono upper', style: { fontSize: 8, fill: 'var(--ink-3)', letterSpacing: '.08em' } }, 'falling · low earn'),
      // dots
      rows.map((r, i) => {
        const cx = x(r[xKey]), cy = y(r[yKey]);
        return React.createElement('g', { key: r.id || i },
          React.createElement('circle', { cx, cy, r: 4, fill: 'var(--ink)', opacity: 0.6 }),
          i < 8 && React.createElement('text', { x: cx + 6, y: cy + 3, style: { fontSize: 9.5, fill: 'var(--ink-2)' } }, (r.label || '').slice(0, 14))
        );
      }),
      // axes
      React.createElement('text', { x: w - padR, y: padT + H + 18, textAnchor: 'end', className: 'ff-mono upper', style: { fontSize: 9, fill: 'var(--ink-3)', letterSpacing: '.08em' } }, xLabel + ' →'),
      React.createElement('text', { x: padL - 36, y: padT + 4, className: 'ff-mono upper', style: { fontSize: 9, fill: 'var(--ink-3)', letterSpacing: '.08em' } }, '↑ ' + yLabel)
    );
  }

  // ─── Cohort matrix ──────────────────────────────────────
  function Cohort({ cohorts, w = 720, h = 240 }) {
    if (!cohorts || cohorts.length === 0) return null;
    const cols = 30; // months
    const rowH = Math.floor((h - 14) / cohorts.length);
    const labelW = 60;
    const cellW = (w - labelW) / cols;
    let max = 0;
    cohorts.forEach(c => Object.values(c.monthly).forEach(v => max = Math.max(max, v)));
    return React.createElement('svg', { width: w, height: h },
      cohorts.map((c, ci) => {
        return [
          React.createElement('text', { key: 'l' + ci, x: labelW - 6, y: 4 + ci * rowH + rowH / 2 + 3, textAnchor: 'end', className: 'ff-mono', style: { fontSize: 10, fill: 'var(--ink-2)' } }, c.year),
          ...[...Array(cols)].map((_, m) => {
            const v = c.monthly[m] || 0;
            const intensity = max === 0 ? 0 : v / max;
            return React.createElement('rect', {
              key: 'c' + ci + ',' + m,
              x: labelW + m * cellW, y: 4 + ci * rowH,
              width: cellW - 1, height: rowH - 2,
              fill: 'var(--ink)', opacity: Math.max(0.05, intensity)
            }, React.createElement('title', null, c.year + ' · m' + m + ' · ' + fmt$(v)));
          })
        ];
      }).flat()
    );
  }

  // ─── Stacked bars (months × platforms or markets) ───────
  function StackedBars({ months, segments, w = 720, h = 200 }) {
    // months: ['2025-01', ...]
    // segments: [{ k, l, color, values: [v1, v2, ...] }]
    if (!months || months.length === 0) return null;
    const padL = 50, padR = 14, padT = 14, padB = 22;
    const W = w - padL - padR, H = h - padT - padB;
    const totals = months.map((_, i) => segments.reduce((s, seg) => s + seg.values[i], 0));
    const max = Math.max(...totals) * 1.05 || 1;
    const colW = W / months.length;
    return React.createElement('svg', { width: w, height: h },
      months.map((mo, i) => {
        let yCursor = padT + H;
        return segments.map((seg, si) => {
          const v = seg.values[i];
          const bh = (v / max) * H;
          yCursor -= bh;
          return React.createElement('rect', {
            key: 's' + i + '-' + si, x: padL + i * colW + 2, y: yCursor, width: colW - 4, height: bh,
            fill: seg.color, opacity: 0.88
          }, React.createElement('title', null, mo + ' · ' + seg.l + ' · ' + fmt$(v)));
        });
      }).flat(),
      months.map((mo, i) => i % Math.ceil(months.length / 8) === 0 ? React.createElement('text', { key: 'lbl' + i, x: padL + i * colW + colW / 2, y: padT + H + 14, textAnchor: 'middle', className: 'ff-mono', style: { fontSize: 9, fill: 'var(--ink-3)' } }, mo) : null)
    );
  }

  // ─── Donut ──────────────────────────────────────────────
  function Donut({ slices, size = 140 }) {
    if (!slices || slices.length === 0) return null;
    const total = slices.reduce((s, x) => s + x.value, 0) || 1;
    const cx = size / 2, cy = size / 2, r = size / 2 - 4, ir = r - 14;
    let a = -Math.PI / 2;
    return React.createElement('svg', { width: size, height: size },
      slices.map((s, i) => {
        const frac = s.value / total;
        const a2 = a + frac * Math.PI * 2;
        const x1 = cx + r * Math.cos(a), y1 = cy + r * Math.sin(a);
        const x2 = cx + r * Math.cos(a2), y2 = cy + r * Math.sin(a2);
        const xi1 = cx + ir * Math.cos(a2), yi1 = cy + ir * Math.sin(a2);
        const xi2 = cx + ir * Math.cos(a), yi2 = cy + ir * Math.sin(a);
        const big = frac > 0.5 ? 1 : 0;
        const d = `M${x1} ${y1} A${r} ${r} 0 ${big} 1 ${x2} ${y2} L${xi1} ${yi1} A${ir} ${ir} 0 ${big} 0 ${xi2} ${yi2} Z`;
        const node = React.createElement('path', { key: i, d, fill: s.color || 'var(--ink)', opacity: 0.88 });
        a = a2;
        return node;
      })
    );
  }

  window.AnalyticsCharts = { KPI, Sparkline, LineChart, BarsH, HeatmapCal, Choropleth, Sankey, Funnel, Pareto, Quadrant, Cohort, StackedBars, Donut, fmt$, fmtN, fmtPct, Mono };
})();
