// Plant Analytics — bespoke widgets (hardened copy of the kit's Widgets.jsx).
// Differences vs the kit: every `Math.max(...)` / total denominator is guarded
// against 0 / empty (real Firebase data is often all-zeros early on, which would
// otherwise produce NaN widths/colors).
(function () {
  const NS = window.HyperChartsDesignSystem_e51a95;
  const { TrendChip, Badge } = NS;
  const I = window.HyperIcons;

  function smooth(points, t = 0.5) {
    if (points.length < 2) return '';
    const d = [`M ${points[0][0]} ${points[0][1]}`];
    for (let i = 0; i < points.length - 1; i++) {
      const p0 = points[i - 1] || points[i], p1 = points[i], p2 = points[i + 1], p3 = points[i + 2] || p2;
      const c1x = p1[0] + ((p2[0] - p0[0]) / 6) * t * 2, c1y = p1[1] + ((p2[1] - p0[1]) / 6) * t * 2;
      const c2x = p2[0] - ((p3[0] - p1[0]) / 6) * t * 2, c2y = p2[1] - ((p3[1] - p1[1]) / 6) * t * 2;
      d.push(`C ${c1x} ${c1y}, ${c2x} ${c2y}, ${p2[0]} ${p2[1]}`);
    }
    return d.join(' ');
  }

  function ChartCard({ title, eyebrow, action, children, span, pad = 22, style }) {
    return (
      <div style={{
        gridColumn: span ? `span ${span}` : undefined,
        background: 'var(--surface-card)', border: '1px solid var(--border-subtle)',
        borderRadius: 'var(--radius-2xl)', boxShadow: 'var(--shadow-card)', padding: pad,
        display: 'flex', flexDirection: 'column', minWidth: 0, ...style,
      }}>
        {(title || action) && (
          <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', marginBottom: 16, gap: 12 }}>
            <div>
              {eyebrow && <div style={{ fontFamily: 'var(--font-mono-display)', fontSize: 10, letterSpacing: '.14em', textTransform: 'uppercase', color: 'var(--text-muted)', marginBottom: 6 }}>{eyebrow}</div>}
              {title && <div style={{ fontFamily: 'var(--font-sans)', fontWeight: 700, fontSize: 17, color: 'var(--text-primary)' }}>{title}</div>}
            </div>
            {action}
          </div>
        )}
        {children}
      </div>
    );
  }

  function StatTile({ icon, label, value, delta, sub, accent = 'var(--app-accent)', span }) {
    return (
      <div style={{
        gridColumn: span ? `span ${span}` : undefined,
        background: 'var(--surface-card)', border: '1px solid var(--border-subtle)',
        borderRadius: 'var(--radius-xl)', boxShadow: 'var(--shadow-card)', padding: 18,
        display: 'flex', flexDirection: 'column', gap: 12, minWidth: 0,
      }}>
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
          <span style={{ fontFamily: 'var(--font-sans)', fontWeight: 600, fontSize: 13, color: 'var(--text-secondary)' }}>{label}</span>
          {icon && <span style={{ width: 32, height: 32, borderRadius: 9, display: 'grid', placeItems: 'center', background: 'color-mix(in srgb, ' + accent + ' 14%, transparent)', color: accent }}>{icon}</span>}
        </div>
        <div style={{ display: 'flex', alignItems: 'baseline', gap: 8, flexWrap: 'wrap' }}>
          <span style={{ fontFamily: 'var(--font-sans)', fontWeight: 700, fontSize: 26, letterSpacing: '-.02em', color: 'var(--text-primary)', lineHeight: 1 }}>{value}</span>
          {delta != null && <TrendChip value={delta} size="sm" />}
        </div>
        {sub && <span style={{ fontFamily: 'var(--font-sans)', fontSize: 12, color: 'var(--text-muted)' }}>{sub}</span>}
      </div>
    );
  }

  function Funnel({ steps, height = 296, stepW = 64, accent = '#8338EC', mode = 'percent' }) {
    const max = (steps[0] && steps[0].value) || 1;
    const W = stepW * steps.length;
    const pillH = 40, funTop = 62;
    const funH = height - funTop - 6;
    const cy = funTop + funH / 2;
    const maxH = funH - 8;
    const pillW = Math.min(stepW - 8, 62);
    const xOf = (i) => i * stepW + stepW / 2;
    const topPts = steps.map((s, i) => [xOf(i), cy - (s.value / max) * maxH / 2]);
    const botPts = steps.map((s, i) => [xOf(i), cy + (s.value / max) * maxH / 2]);
    topPts.unshift([0, topPts[0][1]]); botPts.unshift([0, botPts[0][1]]);
    topPts.push([W, topPts[topPts.length - 1][1]]); botPts.push([W, botPts[botPts.length - 1][1]]);
    const topD = smooth(topPts);
    const botRev = [...botPts].reverse();
    const botD = smooth(botRev);
    const area = `${topD} L ${botRev[0][0]} ${botRev[0][1]} ${botD.replace(/^M[^C]*/, '')} Z`;
    const uid = 'fn' + Math.random().toString(36).slice(2, 7);
    const haloScale = 1.32;
    const haloTop = topPts.map(([x, y]) => [x, cy - (cy - y) * haloScale]);
    const haloBotRev = botRev.map(([x, y]) => [x, cy + (y - cy) * haloScale]);
    const haloTopD = smooth(haloTop);
    const haloBotD = smooth(haloBotRev);
    const haloArea = `${haloTopD} L ${haloBotRev[0][0]} ${haloBotRev[0][1]} ${haloBotD.replace(/^M[^C]*/, '')} Z`;
    return (
      <div style={{ overflowX: 'auto', paddingBottom: 6 }}>
        <div style={{ width: W, minWidth: '100%' }}>
          <svg width={W} height={height} viewBox={`0 0 ${W} ${height}`} style={{ display: 'block', width: W, overflow: 'visible' }}>
            <defs>
              <linearGradient id={uid} x1="0" y1="0" x2="1" y2="0">
                <stop offset="0%" stopColor="#6D28D9" /><stop offset="38%" stopColor="#8676FF" />
                <stop offset="72%" stopColor="#B771F3" /><stop offset="100%" stopColor="#E879F9" />
              </linearGradient>
              <linearGradient id={`${uid}halo`} x1="0" y1="0" x2="1" y2="0">
                <stop offset="0%" stopColor="#8B5CF6" /><stop offset="60%" stopColor="#B771F3" /><stop offset="100%" stopColor="#F0ABFC" />
              </linearGradient>
              <linearGradient id={`${uid}sheen`} x1="0" y1="0" x2="0" y2="1">
                <stop offset="0%" stopColor="#ffffff" stopOpacity="0.42" /><stop offset="32%" stopColor="#ffffff" stopOpacity="0.06" />
                <stop offset="62%" stopColor="#000000" stopOpacity="0" /><stop offset="100%" stopColor="#3B0764" stopOpacity="0.22" />
              </linearGradient>
            </defs>
            <path d={haloArea} fill={`url(#${uid}halo)`} opacity="0.16" style={{ filter: 'blur(11px)' }} />
            <path d={area} fill={`url(#${uid})`} style={{ filter: `drop-shadow(0 12px 26px rgba(131,56,236,.34))` }} />
            <path d={area} fill={`url(#${uid}sheen)`} />
            <path d={topD} fill="none" stroke="#ffffff" strokeWidth="1.5" opacity="0.6" />
            <path d={botD} fill="none" stroke="#ffffff" strokeWidth="1" opacity="0.22" />
            {steps.map((s, i) => {
              const x = xOf(i);
              const ty = cy - (s.value / max) * maxH / 2;
              const by = cy + (s.value / max) * maxH / 2;
              const reached = (s.value / max) * 100;
              const drop = i === 0 ? 0 : ((steps[i - 1].value - s.value) / (steps[i - 1].value || 1)) * 100;
              // Only show the pill where the count actually changes from the
              // previous step (and on the very first step) — skip the long runs
              // of identical values so the funnel doesn't repeat the same pill.
              const showPill = i === 0 || s.value !== steps[i - 1].value;
              return (
                <g key={i}>
                  <line x1={x} y1={ty} x2={x} y2={by} stroke="rgba(255,255,255,.45)" strokeWidth="1" />
                  {showPill ? (
                    <g>
                      <line x1={x} y1={pillH + 6} x2={x} y2={ty} stroke="var(--border-default)" strokeWidth="1" strokeDasharray="2 3" />
                      <circle cx={x} cy={ty} r="3" fill="#fff" stroke={accent} strokeWidth="1.6" />
                      <rect x={x - pillW / 2} y="5" width={pillW} height={pillH} rx="9" fill="#fff" stroke="var(--border-subtle)" style={{ filter: 'drop-shadow(0 3px 6px rgba(108,73,172,.12))' }} />
                      <text x={x} y="22" textAnchor="middle" style={{ fontFamily: 'var(--font-sans)', fontWeight: 700, fontSize: 12.5, fill: 'var(--text-primary)' }}>{mode === 'number' ? compactNum(s.value) : reached.toFixed(0) + '%'}</text>
                      <text x={x} y="37" textAnchor="middle" style={{ fontFamily: 'var(--font-mono)', fontSize: 9.5, fill: drop > 0 ? 'var(--negative)' : 'var(--app-accent)' }}>{drop > 0 ? '▼' + drop.toFixed(1) + '%' : 'start'}</text>
                    </g>
                  ) : null}
                </g>
              );
            })}
          </svg>
          <div style={{ display: 'flex' }}>
            {steps.map((s, i) => {
              const lost = i === 0 ? 0 : steps[i - 1].value - s.value;
              return (
                <div key={i} style={{ width: stepW, padding: '8px 2px 0', textAlign: 'center', borderRight: i < steps.length - 1 ? '1px dashed var(--border-default)' : 'none' }}>
                  <div style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--text-muted)' }}>{String(i + 1).padStart(2, '0')}</div>
                  <div style={{ fontFamily: 'var(--font-sans)', fontSize: 9.5, color: 'var(--text-secondary)', minHeight: 24, lineHeight: 1.15, margin: '3px 0' }}>{s.name}</div>
                  <div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, fontWeight: 700, color: lost > 0 ? 'var(--negative)' : 'var(--text-muted)' }}>{lost > 0 ? '-' + lost.toLocaleString() : '—'}</div>
                  <div style={{ fontFamily: 'var(--font-sans)', fontSize: 8.5, color: 'var(--text-muted)' }}>{lost > 0 ? 'users left' : 'entry'}</div>
                </div>
              );
            })}
          </div>
        </div>
      </div>
    );
  }

  function MiniFunnel({ stages }) {
    const max = (stages[0] && stages[0].value) || 1;
    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
        {stages.map((s, i) => {
          const pct = (s.value / max) * 100;
          const conv = i === 0 ? 100 : (s.value / (stages[i - 1].value || 1)) * 100;
          return (
            <div key={i}>
              <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 6 }}>
                <span style={{ fontFamily: 'var(--font-sans)', fontWeight: 600, fontSize: 14, color: 'var(--text-primary)' }}>{s.name}</span>
                <span style={{ fontFamily: 'var(--font-mono)', fontSize: 13, color: 'var(--text-secondary)' }}>{s.value.toLocaleString()}<span style={{ color: 'var(--text-muted)', marginLeft: 8 }}>{conv.toFixed(1)}%</span></span>
              </div>
              <div style={{ height: 30, borderRadius: 8, background: 'var(--surface-sunken)', overflow: 'hidden' }}>
                <div style={{ width: pct + '%', height: '100%', borderRadius: 8, background: s.color || 'var(--app-accent)' }} />
              </div>
            </div>
          );
        })}
      </div>
    );
  }

  function HeatGrid({ data, color = '#2ACF56', rows = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] }) {
    const hours = Array.from({ length: 12 }, (_, i) => i * 2);
    const max = Math.max(...data.flat(), 1);
    return (
      <div>
        <div style={{ display: 'grid', gridTemplateColumns: `34px repeat(12, 1fr)`, gap: 4 }}>
          <div />
          {hours.map((h) => <div key={h} style={{ textAlign: 'center', fontFamily: 'var(--font-mono)', fontSize: 9, color: 'var(--text-muted)' }}>{String(h).padStart(2, '0')}</div>)}
          {rows.map((r, ri) => (
            <React.Fragment key={ri}>
              <div style={{ display: 'flex', alignItems: 'center', fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--text-muted)' }}>{r}</div>
              {hours.map((h, ci) => {
                const v = (data[ri] && data[ri][ci]) || 0;
                const a = 0.08 + (v / max) * 0.92;
                return <div key={ci} title={`${r} ${String(h).padStart(2, '0')}:00 — ${v}`} style={{ height: 22, borderRadius: 5, background: `color-mix(in srgb, ${color} ${Math.round(a * 100)}%, var(--surface-sunken))` }} />;
              })}
            </React.Fragment>
          ))}
        </div>
      </div>
    );
  }

  function CohortGrid({ cohorts, weeks = 8 }) {
    return (
      <div style={{ overflowX: 'auto' }}>
        <div style={{ display: 'grid', gridTemplateColumns: `90px repeat(${weeks}, 1fr)`, gap: 4, minWidth: 560 }}>
          <div style={{ fontFamily: 'var(--font-mono-display)', fontSize: 9, letterSpacing: '.1em', textTransform: 'uppercase', color: 'var(--text-muted)', display: 'flex', alignItems: 'flex-end', paddingBottom: 4 }}>Cohort</div>
          {Array.from({ length: weeks }).map((_, w) => <div key={w} style={{ textAlign: 'center', fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--text-muted)', paddingBottom: 4 }}>W{w}</div>)}
          {cohorts.map((c, ri) => (
            <React.Fragment key={ri}>
              <div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
                <span style={{ fontFamily: 'var(--font-sans)', fontSize: 12, fontWeight: 600, color: 'var(--text-primary)' }}>{c.label}</span>
                <span style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--text-muted)' }}>{c.size.toLocaleString()}</span>
              </div>
              {c.values.map((v, ci) => {
                const a = v == null ? 0 : 0.12 + (v / 100) * 0.88;
                return <div key={ci} style={{ height: 34, borderRadius: 6, display: 'grid', placeItems: 'center', background: v == null ? 'transparent' : `color-mix(in srgb, var(--app-accent) ${Math.round(a * 100)}%, #fff)` }}>
                  {v != null && <span style={{ fontFamily: 'var(--font-mono)', fontSize: 11, fontWeight: 600, color: a > 0.55 ? '#fff' : 'var(--text-secondary)' }}>{v}%</span>}
                </div>;
              })}
            </React.Fragment>
          ))}
        </div>
      </div>
    );
  }

  function ComparisonCard({ title, periodA, periodB, rangeA, rangeB, metrics, span = 6 }) {
    const Sel = ({ label, range }) => (
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontFamily: 'var(--font-mono-display)', fontSize: 9, letterSpacing: '.1em', textTransform: 'uppercase', color: 'var(--text-muted)', marginBottom: 6 }}>{label}</div>
        <button style={{ display: 'flex', alignItems: 'center', gap: 8, width: '100%', padding: '9px 12px', borderRadius: 'var(--radius-md)', border: '1px solid var(--border-default)', background: 'var(--surface-card)', cursor: 'pointer' }}>
          <span style={{ color: 'var(--text-muted)', display: 'flex', flexShrink: 0 }}>{I.calendar({ size: 16 })}</span>
          <span style={{ fontFamily: 'var(--font-sans)', fontSize: 13, fontWeight: 600, flex: 1, textAlign: 'left', color: 'var(--text-primary)' }}>{range}</span>
          <span style={{ color: 'var(--text-muted)', display: 'flex' }}>{I.chevron({ size: 15 })}</span>
        </button>
      </div>
    );
    return (
      <ChartCard title={title} eyebrow="Period comparison" span={span}>
        <div style={{ display: 'flex', alignItems: 'flex-end', gap: 12, marginBottom: 20 }}>
          <Sel label="Period A" range={rangeA} />
          <span style={{ fontFamily: 'var(--font-mono-display)', fontSize: 11, fontWeight: 600, color: 'var(--text-muted)', paddingBottom: 10 }}>VS</span>
          <Sel label="Period B" range={rangeB} />
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: '1.4fr 1fr 1fr 0.8fr', gap: 8, alignItems: 'center' }}>
          <div />
          <div style={{ textAlign: 'right', fontFamily: 'var(--font-mono-display)', fontSize: 10, letterSpacing: '.08em', textTransform: 'uppercase', color: 'var(--text-muted)' }}>{periodA}</div>
          <div style={{ textAlign: 'right', fontFamily: 'var(--font-mono-display)', fontSize: 10, letterSpacing: '.08em', textTransform: 'uppercase', color: 'var(--text-muted)' }}>{periodB}</div>
          <div />
          {metrics.map((m, i) => {
            const delta = m.a ? ((m.b - m.a) / m.a) * 100 : 0;
            return (
              <React.Fragment key={i}>
                <div style={{ fontFamily: 'var(--font-sans)', fontSize: 13, color: 'var(--text-secondary)', borderTop: '1px solid var(--border-subtle)', paddingTop: 11 }}>{m.label}</div>
                <div style={{ textAlign: 'right', fontFamily: 'var(--font-mono)', fontSize: 13, color: 'var(--text-muted)', borderTop: '1px solid var(--border-subtle)', paddingTop: 11 }}>{m.fmt ? m.fmt(m.a) : m.a.toLocaleString()}</div>
                <div style={{ textAlign: 'right', fontFamily: 'var(--font-mono)', fontSize: 13, fontWeight: 700, color: 'var(--text-primary)', borderTop: '1px solid var(--border-subtle)', paddingTop: 11 }}>{m.fmt ? m.fmt(m.b) : m.b.toLocaleString()}</div>
                <div style={{ textAlign: 'right', borderTop: '1px solid var(--border-subtle)', paddingTop: 11 }}><TrendChip value={Number(delta.toFixed(1))} size="sm" /></div>
              </React.Fragment>
            );
          })}
        </div>
      </ChartCard>
    );
  }

  function DropList({ items, accent = '#FF5C7A' }) {
    const max = Math.max(...items.map((i) => i.value), 1);
    const total = items.reduce((s, i) => s + i.value, 0) || 1;
    const uid = 'dl' + Math.random().toString(36).slice(2, 7);
    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
        <svg width="0" height="0" style={{ position: 'absolute' }}><defs>
          <linearGradient id={uid} x1="0" y1="0" x2="1" y2="0"><stop offset="0%" stopColor="#8676FF" /><stop offset="100%" stopColor={accent} /></linearGradient>
        </defs></svg>
        {items.map((it, i) => {
          const pct = (it.value / total) * 100;
          return (
            <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
              <span style={{ width: 26, height: 26, flexShrink: 0, borderRadius: 8, background: i === 0 ? 'rgba(255,92,122,.12)' : 'var(--surface-sunken)', color: i === 0 ? accent : 'var(--text-muted)', fontFamily: 'var(--font-mono)', fontWeight: 700, fontSize: 12, display: 'grid', placeItems: 'center' }}>{i + 1}</span>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontFamily: 'var(--font-sans)', fontWeight: 600, fontSize: 13, color: 'var(--text-primary)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', marginBottom: 7 }}>{it.label}</div>
                <div style={{ display: 'flex', alignItems: 'center', gap: 9 }}>
                  <div style={{ flex: 1, minWidth: 0, height: 8, background: 'var(--surface-sunken)', borderRadius: 99, overflow: 'hidden' }}>
                    <div style={{ width: (it.value / max) * 100 + '%', height: '100%', borderRadius: 99, background: `linear-gradient(90deg, #8676FF, ${accent})`, boxShadow: `0 1px 6px ${accent}55` }} />
                  </div>
                  <span style={{ display: 'flex', alignItems: 'baseline', gap: 6, flexShrink: 0 }}>
                    <span style={{ fontFamily: 'var(--font-mono)', fontSize: 12.5, fontWeight: 700, color: 'var(--text-primary)' }}>{it.value.toLocaleString()}</span>
                    <span style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--text-muted)' }}>{pct.toFixed(0)}%</span>
                  </span>
                </div>
              </div>
            </div>
          );
        })}
      </div>
    );
  }

  function DistBars({ items, color = 'var(--app-accent)', unit = '' }) {
    const max = Math.max(...items.map((i) => i.value), 1);
    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
        {items.map((it, i) => (
          <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <span style={{ width: 64, fontFamily: 'var(--font-sans)', fontSize: 12, color: 'var(--text-secondary)', flexShrink: 0 }}>{it.label}</span>
            <div style={{ flex: 1, height: 14, background: 'var(--surface-sunken)', borderRadius: 99 }}>
              <div style={{ width: (it.value / max) * 100 + '%', height: '100%', borderRadius: 99, background: it.color || color }} />
            </div>
            <span style={{ width: 52, textAlign: 'right', fontFamily: 'var(--font-mono)', fontSize: 12, fontWeight: 600, color: 'var(--text-primary)', flexShrink: 0 }}>{it.value.toLocaleString()}{unit}</span>
          </div>
        ))}
      </div>
    );
  }

  function DonutStat({ data, centerValue, centerLabel, size = 188 }) {
    const { PieChart } = NS;
    const palette = ['var(--series-violet)', 'var(--series-orange)', 'var(--series-green)', 'var(--series-pink)', 'var(--series-blue)', 'var(--series-amber)'];
    const glow = (data[0] && data[0].color) || 'var(--series-violet)';
    const thickness = 15;
    return (
      <div style={{ display: 'flex', alignItems: 'center', gap: 24 }}>
        <div style={{ position: 'relative', width: size, height: size, flexShrink: 0 }}>
          <div style={{ position: 'absolute', inset: 0, borderRadius: '50%', border: `${thickness}px solid var(--surface-sunken)`, boxSizing: 'border-box' }} />
          <div style={{ position: 'relative', filter: `drop-shadow(0 6px 16px ${glow}55) drop-shadow(0 0 10px ${glow}33)` }}>
            <PieChart size={size} thickness={thickness} gap={0.06} centerValue={centerValue} centerLabel={centerLabel} data={data} />
          </div>
        </div>
        <div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 13 }}>
          {data.map((dd, i) => {
            const c = dd.color || palette[i % 6];
            return (
              <div key={i}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 9, marginBottom: 6 }}>
                  <span style={{ width: 10, height: 10, borderRadius: 3, background: c, flexShrink: 0 }} />
                  <span style={{ fontFamily: 'var(--font-sans)', fontSize: 13, color: 'var(--text-secondary)', flex: 1 }}>{dd.label}</span>
                  <span style={{ fontFamily: 'var(--font-mono)', fontSize: 13, fontWeight: 700, color: 'var(--text-primary)' }}>{dd.pct}%</span>
                </div>
                <div style={{ height: 6, background: 'var(--surface-sunken)', borderRadius: 99 }}>
                  <div style={{ width: dd.pct + '%', height: '100%', borderRadius: 99, background: c }} />
                </div>
              </div>
            );
          })}
        </div>
      </div>
    );
  }

  function WorldMap({ values, top }) {
    const ref = React.useRef(null);
    const mapRef = React.useRef(null);
    React.useEffect(() => {
      if (!window.jsVectorMap || !ref.current) return;
      ref.current.innerHTML = '';
      try {
        mapRef.current = new window.jsVectorMap({
          selector: ref.current, map: 'world',
          // Zoom (buttons + scroll wheel) + drag-to-pan when zoomed in.
          zoomButtons: true, zoomOnScroll: true, zoomMax: 12, zoomMin: 1, zoomAnimate: true,
          backgroundColor: 'transparent',
          regionStyle: { initial: { fill: '#EDEDF3', stroke: '#ffffff', 'stroke-width': 0.4 }, hover: { fill: '#8676FF' } },
        });
      } catch (e) { return; }
      const vals = Object.values(values);
      const maxV = vals.length ? Math.max(...vals) : 1;
      const c0 = [237, 233, 251], c1 = [131, 56, 236];
      const lerp = (a, b, t) => Math.round(a + (b - a) * t);
      const paint = () => {
        Object.entries(values).forEach(([code, v]) => {
          const t = Math.pow(v / (maxV || 1), 0.7);
          const col = `rgb(${lerp(c0[0], c1[0], t)},${lerp(c0[1], c1[1], t)},${lerp(c0[2], c1[2], t)})`;
          const p = ref.current && ref.current.querySelector(`path[data-code="${code}"]`);
          if (p) { p.setAttribute('fill', col); p.style.fill = col; }
        });
      };
      paint(); setTimeout(paint, 80);
    }, [JSON.stringify(values)]);
    return (
      <div style={{ display: 'flex', gap: 18, flexWrap: 'wrap' }}>
        <div ref={ref} style={{ flex: 1, minWidth: 360, height: 320 }} />
        <div style={{ width: 220, display: 'flex', flexDirection: 'column', gap: 12, paddingTop: 6 }}>
          <div style={{ fontFamily: 'var(--font-mono-display)', fontSize: 10, letterSpacing: '.12em', textTransform: 'uppercase', color: 'var(--text-muted)' }}>Top 5 countries</div>
          {top.map((c, i) => (
            <div key={i}>
              <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 5 }}>
                <span style={{ fontFamily: 'var(--font-sans)', fontSize: 13, fontWeight: 600, color: 'var(--text-primary)' }}>{c.flag} {c.name}</span>
                <span style={{ fontFamily: 'var(--font-mono)', fontSize: 12.5, fontWeight: 700, color: 'var(--text-primary)' }}>{c.pct}%</span>
              </div>
              <div style={{ height: 8, background: 'var(--surface-sunken)', borderRadius: 99 }}>
                <div style={{ width: Math.min(c.pct * 2.6, 100) + '%', height: '100%', borderRadius: 99, background: 'var(--app-accent)' }} />
              </div>
            </div>
          ))}
        </div>
      </div>
    );
  }

  // ── Nice axis ticks + compact number formatting ──
  function niceTicks(min, max, count) {
    if (max <= min) max = min + 1; // avoid a degenerate [v, v] axis
    let range = max - min;
    if (range <= 0) range = Math.abs(max) || 1;
    const rawStep = range / Math.max(count, 1);
    const mag = Math.pow(10, Math.floor(Math.log10(rawStep)));
    const norm = rawStep / mag;
    const step = (norm >= 5 ? 5 : norm >= 2 ? 2 : 1) * mag;
    const niceMin = Math.floor(min / step) * step;
    const niceMax = Math.ceil(max / step) * step;
    const ticks = [];
    for (let v = niceMin; v <= niceMax + step * 0.5; v += step) ticks.push(Math.round(v * 1e6) / 1e6);
    return ticks.length >= 2 ? ticks : [min, max];
  }
  function compactNum(n) {
    const a = Math.abs(n);
    if (a >= 1e9) return (n / 1e9).toFixed(a >= 1e10 ? 0 : 1) + 'B';
    if (a >= 1e6) return (n / 1e6).toFixed(a >= 1e7 ? 0 : 1) + 'M';
    if (a >= 1e3) return (n / 1e3).toFixed(a >= 1e4 ? 0 : 1) + 'K';
    return String(Math.round(n * 100) / 100);
  }

  // ── Interactive line chart: x/y axes, gridlines, hover crosshair + tooltip ──
  function LineChart({ series, labels, height = 210, area = true, valueFmt }) {
    const wrapRef = React.useRef(null);
    const [w, setW] = React.useState(760);
    const [hi, setHi] = React.useState(null);
    React.useEffect(() => {
      if (!wrapRef.current || typeof ResizeObserver === 'undefined') return;
      const ro = new ResizeObserver((es) => { const cw = es[0] && es[0].contentRect.width; if (cw) setW(Math.max(220, Math.round(cw))); });
      ro.observe(wrapRef.current);
      return () => ro.disconnect();
    }, []);

    const data0 = (series && series[0] && series[0].data) || [];
    const n = (labels && labels.length) || data0.length;
    const W = w, H = height;
    const padL = 48, padR = 16, padT = 14, padB = 30;
    const plotW = W - padL - padR, plotH = H - padT - padB;
    const fmtAxis = valueFmt || compactNum;

    if (!n) return <div style={{ height, display: 'grid', placeItems: 'center', color: 'var(--text-muted)', fontFamily: 'var(--font-sans)', fontSize: 13 }}>No data</div>;

    const allVals = (series || []).flatMap((s) => (s.data || []).filter((v) => typeof v === 'number'));
    const rawMax = allVals.length ? Math.max(...allVals) : 1;
    const rawMin = Math.min(0, allVals.length ? Math.min(...allVals) : 0);
    const ticks = niceTicks(rawMin, rawMax, 4);
    const yMin = ticks[0], yMax = ticks[ticks.length - 1];
    const span = (yMax - yMin) || 1;

    const X = (i) => padL + (n <= 1 ? plotW / 2 : (i / (n - 1)) * plotW);
    const Y = (v) => padT + plotH - ((v - yMin) / span) * plotH;
    const baseY = Y(Math.max(yMin, 0));
    const labStep = Math.max(1, Math.ceil(n / 7));
    const showDots = n <= 31;
    const uid = 'lc' + Math.random().toString(36).slice(2, 7);

    const onMove = (e) => {
      const r = wrapRef.current && wrapRef.current.getBoundingClientRect();
      if (!r || !r.width) return;
      const fx = e.clientX - r.left;
      let best = 0, bd = Infinity;
      for (let i = 0; i < n; i++) { const dd = Math.abs(X(i) - fx); if (dd < bd) { bd = dd; best = i; } }
      setHi(best);
    };

    return (
      <div ref={wrapRef} style={{ position: 'relative', width: '100%' }} onMouseMove={onMove} onMouseLeave={() => setHi(null)}>
        <svg width={W} height={H} viewBox={`0 0 ${W} ${H}`} style={{ display: 'block', width: '100%' }}>
          <defs>
            {(series || []).map((s, si) => (
              <linearGradient key={si} id={`${uid}g${si}`} x1="0" y1="0" x2="0" y2="1">
                <stop offset="0%" stopColor={s.color || 'var(--app-accent)'} stopOpacity="0.26" />
                <stop offset="100%" stopColor={s.color || 'var(--app-accent)'} stopOpacity="0" />
              </linearGradient>
            ))}
          </defs>

          {ticks.map((t, i) => (
            <g key={i}>
              <line x1={padL} y1={Y(t)} x2={W - padR} y2={Y(t)} stroke="var(--border-subtle)" strokeWidth="1" />
              <text x={padL - 8} y={Y(t) + 3.5} textAnchor="end" style={{ fontFamily: 'var(--font-mono)', fontSize: 10, fill: 'var(--text-muted)' }}>{fmtAxis(t)}</text>
            </g>
          ))}

          {(labels || []).map((lb, i) => ((i % labStep === 0 || i === n - 1) ? (
            <text key={i} x={X(i)} y={H - 9} textAnchor="middle" style={{ fontFamily: 'var(--font-mono)', fontSize: 9.5, fill: 'var(--text-muted)' }}>{lb}</text>
          ) : null))}

          {(series || []).map((s, si) => {
            const pts = (s.data || []).map((v, i) => [X(i), Y(typeof v === 'number' ? v : 0)]);
            const col = s.color || 'var(--app-accent)';
            const linePath = pts.length >= 2 ? smooth(pts) : (pts.length ? `M ${pts[0][0]} ${pts[0][1]}` : '');
            const areaPath = area && pts.length >= 2 ? `${linePath} L ${pts[pts.length - 1][0]} ${baseY} L ${pts[0][0]} ${baseY} Z` : '';
            return (
              <g key={si}>
                {area && areaPath && <path d={areaPath} fill={`url(#${uid}g${si})`} />}
                {linePath && <path d={linePath} fill="none" stroke={col} strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" />}
                {showDots && pts.map((p, i) => <circle key={i} cx={p[0]} cy={p[1]} r="2.6" fill="#fff" stroke={col} strokeWidth="1.6" />)}
              </g>
            );
          })}

          {hi != null && (
            <g>
              <line x1={X(hi)} y1={padT} x2={X(hi)} y2={padT + plotH} stroke="var(--text-muted)" strokeWidth="1" strokeDasharray="3 3" opacity="0.55" />
              {(series || []).map((s, si) => {
                const v = s.data && s.data[hi];
                if (typeof v !== 'number') return null;
                return <circle key={si} cx={X(hi)} cy={Y(v)} r="4.5" fill="#fff" stroke={s.color || 'var(--app-accent)'} strokeWidth="2.5" />;
              })}
            </g>
          )}
        </svg>

        {hi != null && (
          <div style={{
            position: 'absolute', top: 4, pointerEvents: 'none', zIndex: 5,
            left: (X(hi) / W) * 100 + '%', transform: `translateX(${X(hi) > W * 0.62 ? 'calc(-100% - 10px)' : '10px'})`,
            background: 'var(--surface-card)', border: '1px solid var(--border-subtle)', borderRadius: 'var(--radius-md)', boxShadow: 'var(--shadow-card)', padding: '8px 11px', minWidth: 96,
          }}>
            <div style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--text-muted)', marginBottom: 5 }}>{labels ? labels[hi] : '#' + (hi + 1)}</div>
            {(series || []).map((s, si) => (
              <div key={si} style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
                <span style={{ width: 8, height: 8, borderRadius: 2, background: s.color || 'var(--app-accent)', flexShrink: 0 }} />
                {s.name && <span style={{ fontFamily: 'var(--font-sans)', fontSize: 11, color: 'var(--text-secondary)' }}>{s.name}</span>}
                <span style={{ fontFamily: 'var(--font-sans)', fontWeight: 700, fontSize: 13.5, color: 'var(--text-primary)', marginLeft: 'auto' }}>{typeof (s.data && s.data[hi]) === 'number' ? Math.round(s.data[hi]).toLocaleString() : '—'}</span>
              </div>
            ))}
          </div>
        )}
      </div>
    );
  }

  window.PA = { ChartCard, StatTile, Funnel, MiniFunnel, HeatGrid, CohortGrid, ComparisonCard, DistBars, DropList, DonutStat, WorldMap, LineChart, smooth };
})();
