// charts.jsx — chart primitives (no external libs)
// Series: line, area, multi-area, bars, stacked bars, donut, isometric 3D bars, sparkline

const SERIES = ['#5B5BF0', '#FF8FB1', '#6FD2C0', '#FFB876', '#87B6FF', '#C39BF5', '#F4D35E', '#93E0A4'];

// ─────── Sparkline ───────
function Sparkline({ data, color = '#5B5BF0', height = 36, fill = true }) {
  const w = 200, h = height;
  const min = Math.min(...data), max = Math.max(...data);
  const span = max - min || 1;
  const step = w / (data.length - 1);
  const pts = data.map((v, i) => [i * step, h - 4 - ((v - min) / span) * (h - 8)]);
  const path = pts.map((p, i) => `${i ? 'L' : 'M'}${p[0]},${p[1]}`).join(' ');
  const area = `${path} L${w},${h} L0,${h} Z`;
  const id = React.useId();
  return (
    <svg viewBox={`0 0 ${w} ${h}`} preserveAspectRatio="none" style={{ width: '100%', height: '100%', display: 'block' }}>
      {fill && (
        <>
          <defs>
            <linearGradient id={id} x1="0" x2="0" y1="0" y2="1">
              <stop offset="0%" stopColor={color} stopOpacity="0.28"/>
              <stop offset="100%" stopColor={color} stopOpacity="0"/>
            </linearGradient>
          </defs>
          <path d={area} fill={`url(#${id})`}/>
        </>
      )}
      <path d={path} fill="none" stroke={color} strokeWidth="1.6" strokeLinejoin="round" strokeLinecap="round"/>
    </svg>
  );
}

// ─────── Line / Area chart with hover tooltip ───────
function LineChart({ series, labels, height = 240, mode = 'line', formatY = (v) => v.toLocaleString(), showLegend = true, compare = null, yLabel }) {
  const ref = React.useRef(null);
  const [hover, setHover] = React.useState(null); // index
  const [box, setBox] = React.useState({ w: 600, h: height });

  React.useLayoutEffect(() => {
    if (!ref.current) return;
    const ro = new ResizeObserver(([e]) => {
      setBox({ w: e.contentRect.width, h: height });
    });
    ro.observe(ref.current);
    return () => ro.disconnect();
  }, [height]);

  const padL = 44, padR = 14, padT = 12, padB = 26;
  const w = box.w, h = box.h;
  const innerW = Math.max(10, w - padL - padR);
  const innerH = Math.max(10, h - padT - padB);

  const allVals = series.flatMap(s => s.data).concat(compare?.flatMap(s => s.data) || []);
  const maxV = Math.max(...allVals);
  const minV = Math.min(0, Math.min(...allVals));
  const span = (maxV - minV) || 1;
  const yMax = Math.ceil(maxV / niceStep(span)) * niceStep(span) || maxV;
  const yMin = minV;
  const ySpan = (yMax - yMin) || 1;

  const stepX = labels.length > 1 ? innerW / (labels.length - 1) : innerW;
  const xAt = (i) => padL + i * stepX;
  const yAt = (v) => padT + innerH - ((v - yMin) / ySpan) * innerH;

  const ticks = 4;
  const yTicks = Array.from({ length: ticks + 1 }, (_, i) => yMin + (ySpan * i) / ticks);

  const onMove = (e) => {
    const rect = ref.current.getBoundingClientRect();
    const x = e.clientX - rect.left - padL;
    const i = Math.round((x / innerW) * (labels.length - 1));
    if (i >= 0 && i < labels.length) setHover(i);
  };

  return (
    <div style={{ position: 'relative', width: '100%' }} ref={ref}
         onMouseMove={onMove} onMouseLeave={() => setHover(null)}>
      <svg width={w} height={h} style={{ display: 'block', overflow: 'visible' }}>
        {/* grid */}
        {yTicks.map((v, i) => (
          <g key={i}>
            <line x1={padL} x2={w - padR} y1={yAt(v)} y2={yAt(v)} stroke="rgba(124,124,245,0.10)" strokeWidth="1" strokeDasharray={i === 0 ? '0' : '3 3'}/>
            <text x={padL - 8} y={yAt(v) + 3} fontSize="10.5" fill="#9089AD" textAnchor="end">{formatY(v)}</text>
          </g>
        ))}

        {/* compare series (dashed) */}
        {compare && compare.map((s, si) => {
          const path = s.data.map((v, i) => `${i ? 'L' : 'M'}${xAt(i)},${yAt(v)}`).join(' ');
          return (
            <path key={'cmp'+si} d={path} fill="none" stroke={s.color || SERIES[si]} strokeWidth="1.4" strokeDasharray="4 4" opacity="0.55"/>
          );
        })}

        {/* main series */}
        {series.map((s, si) => {
          const color = s.color || SERIES[si];
          const path = s.data.map((v, i) => `${i ? 'L' : 'M'}${xAt(i)},${yAt(v)}`).join(' ');
          const area = `${path} L${xAt(s.data.length - 1)},${padT + innerH} L${xAt(0)},${padT + innerH} Z`;
          const gid = `g-${si}-${labels.length}`;
          return (
            <g key={si}>
              <defs>
                <linearGradient id={gid} x1="0" x2="0" y1="0" y2="1">
                  <stop offset="0%" stopColor={color} stopOpacity={mode === 'area' ? 0.32 : 0.16}/>
                  <stop offset="100%" stopColor={color} stopOpacity="0"/>
                </linearGradient>
              </defs>
              {(mode === 'area' || series.length === 1) && <path d={area} fill={`url(#${gid})`}/>}
              <path d={path} fill="none" stroke={color} strokeWidth="2" strokeLinejoin="round" strokeLinecap="round"/>
              {hover != null && (
                <circle cx={xAt(hover)} cy={yAt(s.data[hover])} r="4" fill="#F5F2FF" stroke={color} strokeWidth="2"/>
              )}
            </g>
          );
        })}

        {/* x labels */}
        {labels.map((l, i) => {
          const skip = labels.length > 12 ? Math.ceil(labels.length / 8) : 1;
          const isLast = i === labels.length - 1;
          const isPenult = i === labels.length - 2;
          // Show first, last, and every Nth — but skip penultimate if it would collide with last
          if (i % skip !== 0 && !isLast) return null;
          if (isPenult && skip > 1 && (labels.length - 1) % skip !== 0) return null;
          return <text key={i} x={xAt(i)} y={h - 8} fontSize="10.5" fill="#9089AD" textAnchor="middle">{l}</text>;
        })}

        {/* hover line */}
        {hover != null && (
          <line x1={xAt(hover)} x2={xAt(hover)} y1={padT} y2={padT + innerH} stroke="#6C6587" strokeWidth="1" strokeDasharray="3 3"/>
        )}
      </svg>

      {hover != null && (
        <div className="tt" style={{ left: xAt(hover), top: padT + 8 }}>
          <div style={{ marginBottom: 4, fontWeight: 600 }}>{labels[hover]}</div>
          {series.map((s, si) => (
            <div className="tt-row" key={si}>
              <span><span className="tt-dot" style={{ background: s.color || SERIES[si] }}/>{s.name}</span>
              <span className="tt-val">{formatY(s.data[hover])}</span>
            </div>
          ))}
          {compare && compare.map((s, si) => (
            <div className="tt-row" key={'c'+si} style={{ opacity: 0.65 }}>
              <span><span className="tt-dot" style={{ background: s.color || SERIES[si], opacity: 0.5 }}/>{s.name}</span>
              <span className="tt-val">{formatY(s.data[hover])}</span>
            </div>
          ))}
        </div>
      )}

      {showLegend && (
        <div className="legend" style={{ marginTop: 8 }}>
          {series.map((s, si) => (
            <div key={si} className="legend-item">
              <span className="legend-swatch" style={{ background: s.color || SERIES[si] }}/>
              {s.name}
            </div>
          ))}
          {compare && <div className="legend-item"><span style={{ width: 14, height: 0, borderTop: '1.4px dashed #B8B3C7' }}/>Previous period</div>}
        </div>
      )}
    </div>
  );
}

function niceStep(span) {
  const exp = Math.pow(10, Math.floor(Math.log10(span)));
  const n = span / exp;
  if (n < 1.5) return 0.2 * exp;
  if (n < 3) return 0.5 * exp;
  if (n < 7) return 1 * exp;
  return 2 * exp;
}

// ─────── Bar chart (grouped or single) ───────
function BarChart({ data, labels, height = 220, formatY = (v) => v.toLocaleString(), color = '#5B5BF0', horizontal = false, gradient = true }) {
  const ref = React.useRef(null);
  const [hover, setHover] = React.useState(null);
  const [box, setBox] = React.useState({ w: 600 });

  React.useLayoutEffect(() => {
    if (!ref.current) return;
    const ro = new ResizeObserver(([e]) => setBox({ w: e.contentRect.width }));
    ro.observe(ref.current);
    return () => ro.disconnect();
  }, []);

  const padL = horizontal ? 100 : 44, padR = 14, padT = 12, padB = 26;
  const w = box.w, h = height;
  const innerW = Math.max(10, w - padL - padR);
  const innerH = Math.max(10, h - padT - padB);
  const max = Math.max(...data) * 1.1 || 1;
  const id = `bg-${React.useId().replace(/:/g, '')}`;

  if (horizontal) {
    const barH = (innerH / data.length) * 0.7;
    const gap = (innerH / data.length) * 0.3;
    return (
      <div ref={ref} style={{ width: '100%', position: 'relative' }}>
        <svg width={w} height={h} style={{ display: 'block' }}>
          <defs>
            <linearGradient id={id} x1="0" x2="1">
              <stop offset="0%" stopColor={color} stopOpacity="0.85"/>
              <stop offset="100%" stopColor={color} stopOpacity="1"/>
            </linearGradient>
          </defs>
          {data.map((v, i) => {
            const y = padT + i * (barH + gap) + gap / 2;
            const bw = (v / max) * innerW;
            return (
              <g key={i} onMouseEnter={() => setHover(i)} onMouseLeave={() => setHover(null)}>
                <rect x={padL} y={y} width={innerW} height={barH} fill="rgba(124,124,245,0.08)" rx="4"/>
                <rect x={padL} y={y} width={bw} height={barH} fill={gradient ? `url(#${id})` : color} rx="4"/>
                <text x={padL - 10} y={y + barH/2 + 4} fontSize="11.5" fill="#C9C3DD" textAnchor="end">{labels[i]}</text>
                <text x={padL + bw + 8} y={y + barH/2 + 4} fontSize="11.5" fill="#F5F2FF" fontWeight="600">{formatY(v)}</text>
              </g>
            );
          })}
        </svg>
      </div>
    );
  }

  const barW = (innerW / data.length) * 0.6;
  const gap = (innerW / data.length) * 0.4;
  const xAt = (i) => padL + i * (barW + gap) + gap / 2;
  return (
    <div ref={ref} style={{ width: '100%', position: 'relative' }}>
      <svg width={w} height={h}>
        <defs>
          <linearGradient id={id} x1="0" x2="0" y1="0" y2="1">
            <stop offset="0%" stopColor={color} stopOpacity="1"/>
            <stop offset="100%" stopColor={color} stopOpacity="0.65"/>
          </linearGradient>
        </defs>
        {[0, 0.25, 0.5, 0.75, 1].map((t, i) => {
          const v = max * (1 - t);
          return (
            <g key={i}>
              <line x1={padL} x2={w - padR} y1={padT + t * innerH} y2={padT + t * innerH} stroke="rgba(124,124,245,0.10)" strokeDasharray={i === 4 ? '0' : '3 3'}/>
              <text x={padL - 8} y={padT + t * innerH + 3} fontSize="10.5" fill="#9089AD" textAnchor="end">{formatY(v)}</text>
            </g>
          );
        })}
        {data.map((v, i) => {
          const bh = (v / max) * innerH;
          return (
            <g key={i} onMouseEnter={() => setHover(i)} onMouseLeave={() => setHover(null)}>
              <rect x={xAt(i)} y={padT + innerH - bh} width={barW} height={bh} fill={gradient ? `url(#${id})` : color} rx="4"
                    opacity={hover != null && hover !== i ? 0.45 : 1}/>
              <text x={xAt(i) + barW/2} y={h - 8} fontSize="10.5" fill="#9089AD" textAnchor="middle">{labels[i]}</text>
            </g>
          );
        })}
      </svg>
      {hover != null && (
        <div className="tt" style={{ left: xAt(hover) + barW/2, top: padT + 8 }}>
          <div style={{ fontWeight: 600 }}>{labels[hover]}</div>
          <div className="tt-val">{formatY(data[hover])}</div>
        </div>
      )}
    </div>
  );
}

// ─────── Stacked bars (e.g. country split) ───────
function StackedBars({ stacks, labels, seriesNames, colors, height = 220, formatY = (v) => v.toLocaleString() }) {
  const ref = React.useRef(null);
  const [box, setBox] = React.useState({ w: 600 });
  const [hover, setHover] = React.useState(null);
  React.useLayoutEffect(() => {
    if (!ref.current) return;
    const ro = new ResizeObserver(([e]) => setBox({ w: e.contentRect.width }));
    ro.observe(ref.current); return () => ro.disconnect();
  }, []);
  const padL = 44, padR = 14, padT = 12, padB = 26;
  const w = box.w, h = height;
  const innerW = w - padL - padR, innerH = h - padT - padB;
  const totals = stacks.map(s => s.reduce((a, b) => a + b, 0));
  const max = Math.max(...totals) * 1.1;
  const barW = (innerW / stacks.length) * 0.6;
  const gap = (innerW / stacks.length) * 0.4;
  const xAt = (i) => padL + i * (barW + gap) + gap / 2;
  return (
    <div ref={ref} style={{ width: '100%', position: 'relative' }}>
      <svg width={w} height={h}>
        {[0, 0.25, 0.5, 0.75, 1].map((t, i) => (
          <g key={i}>
            <line x1={padL} x2={w - padR} y1={padT + t * innerH} y2={padT + t * innerH} stroke="rgba(124,124,245,0.10)" strokeDasharray={i === 4 ? '0' : '3 3'}/>
            <text x={padL - 8} y={padT + t * innerH + 3} fontSize="10.5" fill="#9089AD" textAnchor="end">{formatY(max * (1-t))}</text>
          </g>
        ))}
        {stacks.map((stack, i) => {
          let acc = 0;
          return (
            <g key={i} onMouseEnter={() => setHover(i)} onMouseLeave={() => setHover(null)}>
              {stack.map((v, si) => {
                const sh = (v / max) * innerH;
                const y = padT + innerH - acc - sh;
                acc += sh;
                const isTop = si === stack.length - 1;
                const isBot = si === 0;
                return (
                  <rect key={si} x={xAt(i)} y={y} width={barW} height={Math.max(0, sh - 1)} fill={colors[si]} rx={isTop ? 4 : 0} ry={isTop ? 4 : 0}
                        opacity={hover != null && hover !== i ? 0.45 : 1}/>
                );
              })}
              <text x={xAt(i) + barW/2} y={h - 8} fontSize="10.5" fill="#9089AD" textAnchor="middle">{labels[i]}</text>
            </g>
          );
        })}
      </svg>
      {hover != null && (
        <div className="tt" style={{ left: xAt(hover) + barW/2, top: padT + 8 }}>
          <div style={{ fontWeight: 600, marginBottom: 4 }}>{labels[hover]}</div>
          {stacks[hover].map((v, si) => (
            <div key={si} className="tt-row">
              <span><span className="tt-dot" style={{ background: colors[si] }}/>{seriesNames[si]}</span>
              <span className="tt-val">{formatY(v)}</span>
            </div>
          ))}
          <div className="tt-row" style={{ borderTop: '1px solid rgba(255,255,255,0.15)', marginTop: 4, paddingTop: 4 }}>
            <span style={{ color: 'rgba(255,255,255,0.65)' }}>Total</span>
            <span className="tt-val">{formatY(totals[hover])}</span>
          </div>
        </div>
      )}
    </div>
  );
}

// ─────── Donut ───────
function Donut({ data, colors, height = 200, label, formatV = (v) => v.toLocaleString() }) {
  const total = data.reduce((a, b) => a + b.value, 0);
  const r = height / 2 - 14;
  const cx = height / 2, cy = height / 2;
  const stroke = 22;
  let acc = 0;
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 18 }}>
      <svg width={height} height={height}>
        <circle cx={cx} cy={cy} r={r} fill="none" stroke="rgba(124,124,245,0.08)" strokeWidth={stroke}/>
        {data.map((d, i) => {
          const frac = d.value / total;
          const len = 2 * Math.PI * r * frac;
          const dash = `${len} ${2 * Math.PI * r}`;
          const offset = -2 * Math.PI * r * (acc / total);
          acc += d.value;
          return (
            <circle key={i} cx={cx} cy={cy} r={r} fill="none"
              stroke={colors[i]} strokeWidth={stroke} strokeDasharray={dash} strokeDashoffset={offset}
              strokeLinecap="butt"
              transform={`rotate(-90 ${cx} ${cy})`}/>
          );
        })}
        <text x={cx} y={cy - 4} fontSize="11" fill="#9089AD" textAnchor="middle">{label || 'Total'}</text>
        <text x={cx} y={cy + 14} fontSize="17" fontWeight="600" fill="#F5F2FF" textAnchor="middle">{formatV(total)}</text>
      </svg>
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 8 }}>
        {data.map((d, i) => (
          <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
            <span style={{ width: 10, height: 10, borderRadius: 3, background: colors[i] }}/>
            <span style={{ flex: 1, fontSize: 12.5, color: 'var(--ink-2)' }}>{d.label}</span>
            <span style={{ fontVariantNumeric: 'tabular-nums', fontWeight: 600, color: 'var(--ink)', fontSize: 12.5 }}>{formatV(d.value)}</span>
            <span style={{ fontVariantNumeric: 'tabular-nums', color: 'var(--ink-4)', fontSize: 11.5, width: 38, textAlign: 'right' }}>
              {((d.value / total) * 100).toFixed(1)}%
            </span>
          </div>
        ))}
      </div>
    </div>
  );
}

// ─────── 3D Mountain Terrain Chart (hero chart) ───────
// Stacked translucent ridges in 3D perspective — each row is a "mountain range" representing a country/series
function IsometricBars({ rows, cols, data, colors, height = 320, label = 'Revenue', formatV = (v) => `$${(v/1000).toFixed(0)}k` }) {
  const ref = React.useRef(null);
  const [box, setBox] = React.useState({ w: 700 });
  const [hover, setHover] = React.useState(null);
  React.useLayoutEffect(() => {
    if (!ref.current) return;
    const ro = new ResizeObserver(([e]) => setBox({ w: e.contentRect.width }));
    ro.observe(ref.current); return () => ro.disconnect();
  }, []);

  const w = box.w, h = height;
  const allVals = data.flat();
  const maxV = Math.max(...allVals, 1);

  const padL = 40, padR = 20, padT = 20, padB = 40;
  const innerW = w - padL - padR;
  const innerH = h - padT - padB;

  // Each row gets a vertical "depth" offset to create perspective
  const depthY = innerH * 0.18; // total z-depth in screen units
  const depthX = innerW * 0.10; // horizontal skew for perspective
  const rowSpan = depthY / Math.max(1, rows.length - 1 + 0.5);
  const rowSpanX = depthX / Math.max(1, rows.length - 1 + 0.5);

  // For each row: mountain peak height max
  const peakH = innerH * 0.55;

  // Smooth curve through points using catmull-rom approximation -> bezier
  const smoothPath = (pts) => {
    if (pts.length < 2) return '';
    let d = `M ${pts[0][0]},${pts[0][1]}`;
    for (let i = 0; i < pts.length - 1; i++) {
      const p0 = pts[i - 1] || pts[i];
      const p1 = pts[i];
      const p2 = pts[i + 1];
      const p3 = pts[i + 2] || pts[i + 1];
      const cp1x = p1[0] + (p2[0] - p0[0]) / 6;
      const cp1y = p1[1] + (p2[1] - p0[1]) / 6;
      const cp2x = p2[0] - (p3[0] - p1[0]) / 6;
      const cp2y = p2[1] - (p3[1] - p1[1]) / 6;
      d += ` C ${cp1x},${cp1y} ${cp2x},${cp2y} ${p2[0]},${p2[1]}`;
    }
    return d;
  };

  // Resample data to higher resolution for smoother mountains
  const RES = 80;
  const resample = (vals) => {
    const out = [];
    for (let i = 0; i < RES; i++) {
      const t = (i / (RES - 1)) * (vals.length - 1);
      const i0 = Math.floor(t), i1 = Math.min(vals.length - 1, i0 + 1);
      const frac = t - i0;
      // smooth interp
      const v = vals[i0] * (1 - frac) + vals[i1] * frac;
      out.push(v);
    }
    return out;
  };

  // Build "mountains" — sort back-to-front (last row is back)
  const ridges = rows.map((rowName, ri) => {
    const vals = resample(data[ri]);
    const offY = (rows.length - 1 - ri) * rowSpan;
    const offX = (rows.length - 1 - ri) * rowSpanX;
    const baseY = padT + innerH - offY;
    const stepX = innerW / (vals.length - 1);
    const points = vals.map((v, i) => [
      padL + offX + i * stepX * (1 - (rows.length - 1 - ri) * 0.04),
      baseY - (v / maxV) * peakH,
    ]);
    const baseLeft = [points[0][0], baseY];
    const baseRight = [points[points.length - 1][0], baseY];
    return { ri, rowName, points, baseLeft, baseRight, baseY, color: colors[ri % colors.length] };
  });

  // Draw back-to-front
  const sortedRidges = [...ridges].sort((a, b) => (rows.length - a.ri) - (rows.length - b.ri));

  // Find hover
  const onMove = (e) => {
    const rect = ref.current.getBoundingClientRect();
    const mx = e.clientX - rect.left;
    const my = e.clientY - rect.top;
    // Try last (frontmost) ridge first
    for (let ri = 0; ri < ridges.length; ri++) {
      const r = ridges[ri];
      const stepX = (r.points[r.points.length - 1][0] - r.points[0][0]) / (r.points.length - 1);
      const i = Math.round((mx - r.points[0][0]) / stepX);
      if (i < 0 || i >= r.points.length) continue;
      const px = r.points[i][0], py = r.points[i][1];
      if (Math.abs(mx - px) < 8 && my > py - 14 && my < r.baseY) {
        // Map back to original cols index
        const colIdx = Math.round((i / (r.points.length - 1)) * (cols.length - 1));
        const v = data[ri][colIdx];
        setHover({ ri, ci: colIdx, v, x: px, y: py });
        return;
      }
    }
    setHover(null);
  };

  return (
    <div ref={ref} style={{ width: '100%', position: 'relative' }} onMouseMove={onMove} onMouseLeave={() => setHover(null)}>
      <svg width={w} height={h} style={{ display: 'block', overflow: 'visible' }}>
        <defs>
          {ridges.map((r, i) => (
            <linearGradient key={`mg-${i}`} id={`mg-${i}-${rows.length}`} x1="0" x2="0" y1="0" y2="1">
              <stop offset="0%" stopColor={r.color} stopOpacity="0.95"/>
              <stop offset="40%" stopColor={r.color} stopOpacity="0.55"/>
              <stop offset="100%" stopColor={r.color} stopOpacity="0.18"/>
            </linearGradient>
          ))}
          <linearGradient id="sky-fade" x1="0" x2="0" y1="0" y2="1">
            <stop offset="0%" stopColor="rgba(124,124,245,0.08)" stopOpacity="0.6"/>
            <stop offset="100%" stopColor="rgba(124,124,245,0.08)" stopOpacity="0"/>
          </linearGradient>
        </defs>

        {/* subtle horizontal grid in the back */}
        {[0.25, 0.5, 0.75].map((t, i) => (
          <line key={i} x1={padL} x2={padL + innerW} y1={padT + t * innerH} y2={padT + t * innerH} stroke="rgba(124,124,245,0.10)" strokeDasharray="3 3"/>
        ))}

        {sortedRidges.map((r) => {
          const path = smoothPath(r.points);
          const fillPath = `${path} L ${r.baseRight[0]},${r.baseRight[1]} L ${r.baseLeft[0]},${r.baseLeft[1]} Z`;
          const isHover = hover && hover.ri === r.ri;
          return (
            <g key={r.ri} opacity={hover && !isHover ? 0.55 : 1}>
              {/* shadow under ridge */}
              <ellipse cx={(r.baseLeft[0] + r.baseRight[0]) / 2} cy={r.baseY + 2} rx={(r.baseRight[0] - r.baseLeft[0]) / 2.2} ry="3" fill={r.color} opacity="0.08"/>
              {/* filled mountain */}
              <path d={fillPath} fill={`url(#mg-${r.ri}-${rows.length})`}/>
              {/* ridge highlight line */}
              <path d={path} fill="none" stroke={r.color} strokeWidth="1.8" strokeLinejoin="round" strokeLinecap="round"/>
              {/* base line */}
              <line x1={r.baseLeft[0]} y1={r.baseY} x2={r.baseRight[0]} y2={r.baseY} stroke={r.color} strokeWidth="1" opacity="0.35"/>
              {/* row label on left base */}
              <text x={r.baseLeft[0] - 6} y={r.baseY + 3} fontSize="10.5" fill="#9089AD" textAnchor="end">{r.rowName}</text>
            </g>
          );
        })}

        {/* x-axis labels along the front-most ridge */}
        {(() => {
          const front = ridges[0];
          if (!front) return null;
          const stepX = (front.points[front.points.length - 1][0] - front.points[0][0]) / (cols.length - 1 || 1);
          return cols.map((c, i) => (
            <text key={'cl'+i} x={front.points[0][0] + i * stepX} y={front.baseY + 18} fontSize="10.5" fill="#9089AD" textAnchor="middle">{c}</text>
          ));
        })()}

        {/* hover marker */}
        {hover && (
          <g>
            <line x1={hover.x} y1={hover.y} x2={hover.x} y2={ridges[hover.ri].baseY} stroke={ridges[hover.ri].color} strokeWidth="1.5" strokeDasharray="3 3"/>
            <circle cx={hover.x} cy={hover.y} r="5" fill="#F5F2FF" stroke={ridges[hover.ri].color} strokeWidth="2"/>
          </g>
        )}
      </svg>

      {hover && (
        <div className="tt" style={{ left: hover.x, top: hover.y - 4 }}>
          <div style={{ fontWeight: 600, marginBottom: 2 }}>{rows[hover.ri]} · {cols[hover.ci]}</div>
          <div className="tt-val">{label}: {formatV(hover.v)}</div>
        </div>
      )}
    </div>
  );
}

function lighten(hex, amt) {
  const c = hex.replace('#', '');
  const r = parseInt(c.slice(0,2), 16), g = parseInt(c.slice(2,4), 16), b = parseInt(c.slice(4,6), 16);
  const adj = (v) => Math.max(0, Math.min(255, Math.round(v + amt * 255)));
  return `rgb(${adj(r)}, ${adj(g)}, ${adj(b)})`;
}

// ─────── Heatmap (compact) ───────
function Heatmap({ rows, cols, data, color = '#5B5BF0', formatV = (v) => v }) {
  const max = Math.max(...data.flat());
  return (
    <div style={{ display: 'grid', gridTemplateColumns: `auto repeat(${cols.length}, 1fr)`, gap: 3, fontSize: 11 }}>
      <div/>
      {cols.map((c, i) => <div key={i} style={{ textAlign: 'center', color: 'var(--ink-4)', padding: '2px 0' }}>{c}</div>)}
      {rows.map((r, ri) => (
        <React.Fragment key={ri}>
          <div style={{ color: 'var(--ink-3)', paddingRight: 8, alignSelf: 'center', fontSize: 11.5 }}>{r}</div>
          {data[ri].map((v, ci) => {
            const t = v / max;
            return (
              <div key={ci} title={`${r} · ${cols[ci]}: ${formatV(v)}`} style={{
                aspectRatio: '1', borderRadius: 5,
                background: t > 0 ? `${color}${Math.round(0.18 + t * 0.65 * 255).toString(16).padStart(2,'0').slice(0,2)}` : 'rgba(124,124,245,0.08)',
                display: 'grid', placeItems: 'center',
                color: t > 0.5 ? 'white' : 'var(--ink-2)',
                fontWeight: 500, fontSize: 10.5,
                fontVariantNumeric: 'tabular-nums',
              }}>{formatV(v)}</div>
            );
          })}
        </React.Fragment>
      ))}
    </div>
  );
}

// ─────── Grouped Bar Chart (clearer for country × channel) ───────
function GroupedBars({ rows, cols, data, colors, height = 320, label = 'Value', formatV = (v) => v.toLocaleString() }) {
  const ref = React.useRef(null);
  const [box, setBox] = React.useState({ w: 700 });
  const [hover, setHover] = React.useState(null);
  React.useLayoutEffect(() => {
    if (!ref.current) return;
    const ro = new ResizeObserver(([e]) => setBox({ w: e.contentRect.width }));
    ro.observe(ref.current); return () => ro.disconnect();
  }, []);

  const w = box.w, h = height;
  const padL = 50, padR = 16, padT = 16, padB = 56;
  const innerW = w - padL - padR;
  const innerH = h - padT - padB;
  const max = Math.max(...data.flat()) * 1.15 || 1;

  // Each col is a group (channel); within group, rows (countries) are bars
  const groupW = innerW / cols.length;
  const barGap = 4;
  const barW = (groupW * 0.78 - barGap * (rows.length - 1)) / rows.length;
  const groupPad = (groupW - (barW * rows.length + barGap * (rows.length - 1))) / 2;

  const yAt = (v) => padT + innerH - (v / max) * innerH;
  const groupX = (ci) => padL + ci * groupW;

  const niceTicks = [0, 0.25, 0.5, 0.75, 1];

  return (
    <div ref={ref} style={{ width: '100%', position: 'relative' }}>
      <svg width={w} height={h} style={{ display: 'block' }}>
        <defs>
          {rows.map((r, ri) => (
            <linearGradient key={ri} id={`gb-${ri}-${rows.length}`} x1="0" x2="0" y1="0" y2="1">
              <stop offset="0%" stopColor={colors[ri]} stopOpacity="1"/>
              <stop offset="100%" stopColor={colors[ri]} stopOpacity="0.7"/>
            </linearGradient>
          ))}
        </defs>

        {/* gridlines */}
        {niceTicks.map((t, i) => (
          <g key={i}>
            <line x1={padL} x2={padL + innerW} y1={padT + (1 - t) * innerH} y2={padT + (1 - t) * innerH} stroke="rgba(124,124,245,0.10)" strokeDasharray={t === 0 ? '0' : '3 3'}/>
            <text x={padL - 10} y={padT + (1 - t) * innerH + 3} fontSize="10.5" fill="#9089AD" textAnchor="end">{formatV(max * t)}</text>
          </g>
        ))}

        {/* group dividers */}
        {cols.map((c, ci) => ci > 0 && (
          <line key={'gd'+ci} x1={groupX(ci)} y1={padT} x2={groupX(ci)} y2={padT + innerH} stroke="rgba(124,124,245,0.08)"/>
        ))}

        {/* bars */}
        {data.map((rowData, ri) => rowData.map((v, ci) => {
          const x = groupX(ci) + groupPad + ri * (barW + barGap);
          const bh = (v / max) * innerH;
          const isHover = hover && hover.ri === ri && hover.ci === ci;
          const op = hover && !isHover ? 0.4 : 1;
          return (
            <g key={`${ri}-${ci}`} opacity={op} onMouseEnter={() => setHover({ ri, ci, v, x: x + barW/2, y: yAt(v) })} onMouseLeave={() => setHover(null)}>
              <rect x={x} y={padT + innerH - bh} width={barW} height={bh} rx="4" fill={`url(#gb-${ri}-${rows.length})`}/>
            </g>
          );
        }))}

        {/* col labels */}
        {cols.map((c, ci) => (
          <text key={'cl'+ci} x={groupX(ci) + groupW/2} y={padT + innerH + 18} fontSize="11.5" fill="#C9C3DD" textAnchor="middle" fontWeight="500">{c}</text>
        ))}

        {/* legend */}
        {rows.map((r, ri) => {
          const lw = 90;
          const totalW = rows.length * lw;
          const startX = (w - totalW) / 2;
          return (
            <g key={'lg'+ri} transform={`translate(${startX + ri * lw}, ${h - 14})`}>
              <rect x="0" y="-7" width="10" height="10" rx="2" fill={colors[ri]}/>
              <text x="14" y="2" fontSize="11" fill="#C9C3DD">{r}</text>
            </g>
          );
        })}
      </svg>

      {hover && (
        <div className="tt" style={{ left: hover.x, top: hover.y - 4 }}>
          <div style={{ fontWeight: 600, marginBottom: 2 }}>{rows[hover.ri]} · {cols[hover.ci]}</div>
          <div className="tt-val">{label}: {formatV(hover.v)}</div>
        </div>
      )}
    </div>
  );
}

Object.assign(window, { Sparkline, LineChart, BarChart, StackedBars, Donut, IsometricBars, GroupedBars, Heatmap, SERIES });
