/* ═══════════════════════════════════════════════════════════════════
   SeoCharts.jsx — SVG chart primitives for SEO Tracker. v1.0.0
   Ported verbatim from the Claude Design v2 package
   (_CLAUDE-DESIGN/_2026-06-12-v2-SaaS-Redesign/seo-charts.jsx) — port,
   not redesign. All colors via --viz-* tokens (landed in Increment 1;
   aliases of beam tokens, theme-flip automatically). No inline hex.
   Load order: FIRST of the five Seo modules (charts → data → tracker →
   detail → surface).
   Registers: window.WPSB.SeoCharts = { Sparkline, RankTrendChart,
   DifficultyBar, DifficultyDot, OverlapBar, MiniBars, Delta }.
   Deliberately NOT exported as bare globals (Delta/Sparkline are too
   generic — collision risk with other modules).
   ═══════════════════════════════════════════════════════════════════ */
(function () {

/* map a value array to SVG points within a box, optional inverted y (for rank:
   lower position number = better = higher on chart). */
function _points(vals, w, h, pad, invert) {
  const min = Math.min(...vals), max = Math.max(...vals);
  const span = (max - min) || 1;
  const n = vals.length;
  return vals.map((v, i) => {
    const x = pad + (i / (n - 1)) * (w - pad * 2);
    let t = (v - min) / span;                 // 0..1, 0 = min
    if (invert) t = 1 - t;                     // invert so smaller value sits higher
    const y = pad + (1 - t) * (h - pad * 2);
    return [x, y];
  });
}

/* ── tiny inline sparkline (table rows, stat cards) ─────────────────── */
function Sparkline({ data, w = 96, h = 28, color = 'var(--viz-1)', invert = false, dot = true, area = false }) {
  const pts = _points(data, w, h, 3, invert);
  const d = pts.map((p, i) => (i ? 'L' : 'M') + p[0].toFixed(1) + ' ' + p[1].toFixed(1)).join(' ');
  const last = pts[pts.length - 1];
  return React.createElement('svg', { width: w, height: h, viewBox: '0 0 ' + w + ' ' + h, style: { display: 'block', overflow: 'visible' }, 'aria-hidden': 'true' },
    area && React.createElement('path', { d: d + ' L' + last[0].toFixed(1) + ' ' + (h - 2) + ' L' + pts[0][0].toFixed(1) + ' ' + (h - 2) + ' Z', fill: 'var(--viz-area)' }),
    React.createElement('path', { d, fill: 'none', stroke: color, strokeWidth: 2.25, strokeLinecap: 'round', strokeLinejoin: 'round' }),
    dot && React.createElement('circle', { cx: last[0], cy: last[1], r: 2.6, fill: color })
  );
}

/* ── delta indicator: up/down triangles / em-dash (rank: up = improved) ── */
function Delta({ value, size = '.8rem', showZero = true }) {
  // value > 0 means improved (moved up in rank); < 0 worsened.
  if (!value) return showZero
    ? React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 3, color: 'var(--viz-flat)', fontFamily: 'var(--font-mono)', fontSize: size } },
        React.createElement('span', { 'aria-hidden': 'true' }, '—'),
        React.createElement('span', { className: 'sr-only' }, 'no change'))
    : null;
  const up = value > 0;
  const c = up ? 'var(--viz-up)' : 'var(--viz-down)';
  return React.createElement('span', {
    style: { display: 'inline-flex', alignItems: 'center', gap: 2, color: c, fontFamily: 'var(--font-mono)', fontWeight: 'var(--fw-bold)', fontSize: size },
  },
    React.createElement('span', { 'aria-hidden': 'true', style: { fontSize: '.7em', lineHeight: 1 } }, up ? '▲' : '▼'),
    Math.abs(value),
    React.createElement('span', { className: 'sr-only' }, up ? 'improved by ' + Math.abs(value) : 'dropped by ' + Math.abs(value))
  );
}

/* ── large rank-trend chart (detail view) — inverted axis ───────────── */
function RankTrendChart({ series, labels, height = 240, best = 1 }) {
  // series: [{ name, data:[positions], color, primary }]
  const w = 720, h = height, padL = 38, padR = 16, padT = 16, padB = 28;
  const all = series.flatMap(s => s.data);
  const min = Math.max(1, Math.min(...all) - 1);
  const max = Math.max(...all) + 2;
  const span = (max - min) || 1;
  const xFor = (i, n) => padL + (i / (n - 1)) * (w - padL - padR);
  const yFor = (v) => padT + ((v - min) / span) * (h - padT - padB); // smaller = higher
  // gridlines at nice rank values
  const ticks = [];
  const step = Math.max(1, Math.round(span / 4));
  for (let v = min; v <= max; v += step) ticks.push(Math.round(v));

  return React.createElement('div', { style: { width: '100%', overflowX: 'auto' } },
    React.createElement('svg', { viewBox: '0 0 ' + w + ' ' + h, width: '100%', style: { minWidth: 520, display: 'block' }, role: 'img', 'aria-label': 'Rank trend over time' },
      // gridlines + y labels (rank)
      ticks.map((t, i) => React.createElement('g', { key: 'g' + i },
        React.createElement('line', { x1: padL, y1: yFor(t), x2: w - padR, y2: yFor(t), stroke: 'var(--viz-grid)', strokeWidth: 1, strokeDasharray: '3 4' }),
        React.createElement('text', { x: padL - 8, y: yFor(t) + 3, textAnchor: 'end', fontSize: 10, fontFamily: 'var(--font-mono)', fill: 'var(--viz-axis)' }, '#' + t))),
      // top-10 band highlight
      React.createElement('rect', { x: padL, y: yFor(min), width: w - padL - padR, height: Math.max(0, yFor(10) - yFor(min)), fill: 'var(--viz-area)', opacity: .5 }),
      // x labels
      labels.map((lb, i) => React.createElement('text', { key: 'x' + i, x: xFor(i, labels.length), y: h - 8, textAnchor: 'middle', fontSize: 10, fontFamily: 'var(--font-mono)', fill: 'var(--viz-axis)' }, lb)),
      // series
      series.map((s, si) => {
        const n = s.data.length;
        const d = s.data.map((v, i) => (i ? 'L' : 'M') + xFor(i, n).toFixed(1) + ' ' + yFor(v).toFixed(1)).join(' ');
        const last = [xFor(n - 1, n), yFor(s.data[n - 1])];
        return React.createElement('g', { key: 's' + si },
          s.primary && React.createElement('path', { d: d + ' L' + last[0].toFixed(1) + ' ' + yFor(max) + ' L' + padL + ' ' + yFor(max) + ' Z', fill: 'var(--viz-area)', opacity: .6 }),
          React.createElement('path', { d, fill: 'none', stroke: s.color, strokeWidth: s.primary ? 2.5 : 1.75, strokeLinecap: 'round', strokeLinejoin: 'round', opacity: s.primary ? 1 : .8, strokeDasharray: s.primary ? '' : '5 4' }),
          s.primary && s.data.map((v, i) => React.createElement('circle', { key: 'p' + i, cx: xFor(i, n), cy: yFor(v), r: 2.6, fill: s.color })),
          React.createElement('circle', { cx: last[0], cy: last[1], r: s.primary ? 4 : 3, fill: s.color, stroke: 'var(--surface)', strokeWidth: 1.5 }));
      })
    )
  );
}

/* ── difficulty bar (warn if >60 else beam) ─────────────────────────── */
function DifficultyBar({ value, w = 92 }) {
  const c = value > 60 ? 'var(--viz-4)' : 'var(--viz-1)';
  return React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8 },
    role: 'img', 'aria-label': 'Difficulty ' + value + ' of 100' },
    React.createElement('div', { style: { width: w, height: 6, borderRadius: 999, background: 'var(--viz-track)', overflow: 'hidden', flex: 'none' } },
      React.createElement('div', { style: { width: value + '%', height: '100%', background: c, borderRadius: 999 } })),
    React.createElement('span', { style: { fontFamily: 'var(--font-mono)', fontSize: '.72rem', color: 'var(--text-2)', fontVariantNumeric: 'tabular-nums' } }, value));
}

/* ── compact difficulty: 3-tier colored dot + number ────────────────── */
function DifficultyDot({ value }) {
  const tier = value >= 60 ? { c: 'var(--v-poor-fill)', t: 'Hard' }
            : value >= 40 ? { c: 'var(--v-warn-band)', t: 'Medium' }
            : { c: 'var(--v-good-fill)', t: 'Easy' };
  return React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 7 }, title: tier.t + ' (' + value + '/100)' },
    React.createElement('span', { style: { width: 9, height: 9, borderRadius: '50%', background: tier.c, flex: 'none' } }),
    React.createElement('span', { style: { fontFamily: 'var(--font-mono)', fontSize: '.8rem', color: 'var(--text-2)', fontVariantNumeric: 'tabular-nums' } }, value),
    React.createElement('span', { className: 'sr-only' }, tier.t + ' difficulty'));
}

/* ── overlap bar (competitor share-of-voice) ────────────────────────── */
function OverlapBar({ value, color = 'var(--viz-2)', w = '100%' }) {
  return React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 10, width: '100%' },
    role: 'img', 'aria-label': 'Keyword overlap ' + value + ' percent' },
    React.createElement('div', { style: { flex: 1, height: 7, borderRadius: 999, background: 'var(--viz-track)', overflow: 'hidden' } },
      React.createElement('div', { style: { width: value + '%', height: '100%', background: color, borderRadius: 999 } })),
    React.createElement('span', { style: { fontFamily: 'var(--font-mono)', fontSize: '.74rem', color: 'var(--text)', fontWeight: 'var(--fw-bold)', minWidth: 34, textAlign: 'right' } }, value + '%'));
}

/* ── mini vertical bars (stat-card distribution) ────────────────────── */
function MiniBars({ data, w = 96, h = 28, color = 'var(--viz-1)' }) {
  const max = Math.max(...data) || 1;
  const n = data.length;
  const bw = (w - (n - 1) * 3) / n;
  return React.createElement('svg', { width: w, height: h, viewBox: '0 0 ' + w + ' ' + h, style: { display: 'block' }, 'aria-hidden': 'true' },
    data.map((v, i) => {
      const bh = Math.max(2, (v / max) * (h - 2));
      return React.createElement('rect', { key: i, x: i * (bw + 3), y: h - bh, width: bw, height: bh, rx: 1.5, fill: color, opacity: i === n - 1 ? 1 : .55 });
    }));
}

window.WPSB = window.WPSB || {};
window.WPSB.SeoCharts = { Sparkline, RankTrendChart, DifficultyBar, DifficultyDot, OverlapBar, MiniBars, Delta };
console.log('[WPSB] SeoCharts v1.0.0 loaded');
})();
