/* time-series.jsx — visibility over time.
 *
 * Pulls history from `aiso:history:v1` (per site). Each scan appends a record:
 *   { at, score, perModel: {modelId: 0..100}, configured: [modelIds], totalCalls }
 *
 * Renders an editorial six-ridge chart — one thin line per model, score over time.
 * Latest point glows. Hover gives the per-model values.
 */

const { useState, useEffect, useMemo } = React;

const HISTORY_KEY = 'aiso:history:v1';

function loadHistory() {
  try { return JSON.parse(localStorage.getItem(HISTORY_KEY) || '{}'); }
  catch { return {}; }
}
function saveHistory(h) {
  try { localStorage.setItem(HISTORY_KEY, JSON.stringify(h)); } catch {}
}

function recordScanIntoHistory(siteId, scan) {
  if (!siteId || !scan) return;
  const all = loadHistory();
  const arr = all[siteId] || [];
  // Dedupe: don't push if last entry is < 5min old with same score
  const last = arr[arr.length - 1];
  if (last && (Date.now() - last.at < 5 * 60 * 1000) && Math.abs((last.score || 0) - (scan.score || 0)) < 1) {
    return;
  }
  const entry = {
    at: scan.runAt || Date.now(),
    score: scan.score,
    perModel: scan.perModel || {},
    configured: scan.configured || [],
    totalCalls: scan.totalCalls,
  };
  all[siteId] = [...arr, entry].slice(-60); // last 60 scans
  saveHistory(all);
}

function getHistoryForSite(siteId) {
  if (!siteId) return [];
  const all = loadHistory();
  return all[siteId] || [];
}

function VisibilityHistory({ activeSite, brand, onRunScan, running }) {
  const [tick, setTick] = useState(0);
  const points = useMemo(() => getHistoryForSite(activeSite?.id), [activeSite?.id, tick]);
  const [hoverIdx, setHoverIdx] = useState(null);

  // Re-read whenever the active site or app gets focus
  useEffect(() => {
    const onFocus = () => setTick(t => t + 1);
    window.addEventListener('focus', onFocus);
    window.addEventListener('aiso:scan-recorded', onFocus);
    return () => {
      window.removeEventListener('focus', onFocus);
      window.removeEventListener('aiso:scan-recorded', onFocus);
    };
  }, []);

  if (!activeSite) return null;

  const empty = points.length === 0;
  const single = points.length === 1;

  return (
    <div className="ts-section">
      <div className="ts-head">
        <div>
          <div className="eyebrow"><span className="db-dot live-pulse" /> Time series · localStorage · per site</div>
          <h3>Visibility over time</h3>
          <p className="ts-sub">Every real scan you run gets recorded. Six ridges, one per model.</p>
        </div>
        <div className="ts-actions">
          <span className="ts-count">{points.length} scan{points.length === 1 ? '' : 's'} recorded</span>
          {onRunScan && (
            <button className="btn" disabled={running} onClick={() => { try { window.scrollTo({ top: 0, behavior: 'smooth' }); } catch {} onRunScan(); }}>
              {running ? 'Scanning…' : (empty ? 'Run first scan →' : 'Run scan now')}
            </button>
          )}
        </div>
      </div>

      {empty ? (
        <div className="ts-empty">
          <div className="ts-empty-orb">
            <svg width="80" height="80" viewBox="0 0 80 80">
              <circle cx="40" cy="40" r="34" fill="none" stroke="var(--line)" strokeDasharray="3 4" />
              <circle cx="40" cy="40" r="22" fill="none" stroke="var(--line)" strokeDasharray="2 5" />
              <circle cx="40" cy="40" r="6" fill="var(--accent)" opacity="0.4" />
            </svg>
          </div>
          <div>
            <h4>No baseline yet</h4>
            <p>Run a scan — your score becomes a dot. Run it again tomorrow, that's two dots. Soon it's a story, one ridge per model.</p>
          </div>
        </div>
      ) : (
        <RidgeChart points={points} hoverIdx={hoverIdx} setHoverIdx={setHoverIdx} />
      )}
    </div>
  );
}

function RidgeChart({ points, hoverIdx, setHoverIdx }) {
  // chart geometry
  const W = 920;
  const H = 320;
  const padL = 48, padR = 28, padT = 22, padB = 36;
  const innerW = W - padL - padR;
  const innerH = H - padT - padB;

  const models = window.MODELS || [];
  const n = points.length;
  const xs = (i) => padL + (n === 1 ? innerW / 2 : (i / (n - 1)) * innerW);
  const ys = (v) => padT + innerH - (v / 100) * innerH;

  const hovered = hoverIdx != null && points[hoverIdx];

  // ridges per model (path)
  const ridges = models.map((m) => {
    const pts = points.map((p, i) => {
      const v = (p.perModel && p.perModel[m.id] != null) ? p.perModel[m.id] : null;
      return { x: xs(i), y: v != null ? ys(v) : null, v };
    });
    // build path skipping nulls
    let d = '';
    let started = false;
    pts.forEach((pt, i) => {
      if (pt.y == null) { started = false; return; }
      const prev = pts[i - 1];
      if (!started || !prev || prev.y == null) {
        d += `M ${pt.x.toFixed(1)} ${pt.y.toFixed(1)} `;
        started = true;
      } else {
        // smooth: control points at midpoints
        const mx = (prev.x + pt.x) / 2;
        d += `Q ${mx.toFixed(1)} ${prev.y.toFixed(1)} ${pt.x.toFixed(1)} ${pt.y.toFixed(1)} `;
      }
    });
    return { model: m, d, pts };
  });

  // aggregate "score" line — bolder
  const scoreLine = points.map((p, i) => ({ x: xs(i), y: ys(p.score || 0), v: p.score }));
  let scoreD = '';
  scoreLine.forEach((pt, i) => {
    if (i === 0) scoreD += `M ${pt.x.toFixed(1)} ${pt.y.toFixed(1)} `;
    else {
      const prev = scoreLine[i - 1];
      const mx = (prev.x + pt.x) / 2;
      scoreD += `Q ${mx.toFixed(1)} ${prev.y.toFixed(1)} ${pt.x.toFixed(1)} ${pt.y.toFixed(1)} `;
    }
  });

  function handleMove(e) {
    const rect = e.currentTarget.getBoundingClientRect();
    const x = ((e.clientX - rect.left) / rect.width) * W;
    if (x < padL || x > W - padR) { setHoverIdx(null); return; }
    const t = (x - padL) / innerW;
    const i = Math.round(t * (n - 1));
    setHoverIdx(Math.max(0, Math.min(n - 1, i)));
  }

  const formatDate = (ts) => {
    const d = new Date(ts);
    return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
  };

  return (
    <div className="ts-chart-wrap">
      <svg viewBox={`0 0 ${W} ${H}`} className="ts-chart" onMouseMove={handleMove} onMouseLeave={() => setHoverIdx(null)}>
        {/* y gridlines */}
        {[0, 25, 50, 75, 100].map((v) => (
          <g key={v}>
            <line x1={padL} y1={ys(v)} x2={W - padR} y2={ys(v)} stroke="var(--line)" strokeWidth="0.6" strokeDasharray="2 6" />
            <text x={padL - 10} y={ys(v) + 3} fontFamily="var(--mono)" fontSize="10" fill="var(--fg-3)" textAnchor="end">{v}</text>
          </g>
        ))}

        {/* hover vertical line */}
        {hovered && (
          <line x1={xs(hoverIdx)} y1={padT} x2={xs(hoverIdx)} y2={H - padB} stroke="var(--accent)" strokeWidth="0.8" strokeDasharray="2 3" opacity="0.7" />
        )}

        {/* x ticks (sparse) */}
        {points.map((p, i) => {
          if (n > 8 && i % Math.ceil(n / 6) !== 0 && i !== n - 1) return null;
          return (
            <text key={i} x={xs(i)} y={H - padB + 18} fontFamily="var(--mono)" fontSize="10" fill="var(--fg-3)" textAnchor="middle">{formatDate(p.at)}</text>
          );
        })}

        {/* per-model ridges */}
        {ridges.map(({ model, d, pts }) => (
          <g key={model.id} className={`ts-ridge ${hovered ? 'ts-faint' : ''}`}>
            <path d={d} fill="none" stroke={`oklch(78% 0.16 ${model.hue})`} strokeWidth="1.2" strokeLinecap="round" opacity="0.75" />
          </g>
        ))}

        {/* aggregate score line */}
        <path d={scoreD} fill="none" stroke="var(--accent)" strokeWidth="2.2" strokeLinecap="round" filter="url(#ts-glow)" />

        <defs>
          <filter id="ts-glow">
            <feGaussianBlur stdDeviation="3" result="b" />
            <feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
          </filter>
        </defs>

        {/* dots at each scan, hover state */}
        {scoreLine.map((pt, i) => (
          <g key={i}>
            <circle cx={pt.x} cy={pt.y} r={hoverIdx === i ? 5 : 3} fill="var(--accent)" opacity={hoverIdx === i ? 1 : 0.85} />
            {i === scoreLine.length - 1 && (
              <circle cx={pt.x} cy={pt.y} r="8" fill="none" stroke="var(--accent)" strokeWidth="1.2" opacity="0.4">
                <animate attributeName="r" values="6;14;6" dur="2.5s" repeatCount="indefinite" />
                <animate attributeName="opacity" values="0.6;0;0.6" dur="2.5s" repeatCount="indefinite" />
              </circle>
            )}
          </g>
        ))}

        {/* hover hit area */}
        <rect x={padL} y={padT} width={innerW} height={innerH} fill="transparent" />
      </svg>

      {/* model legend */}
      <div className="ts-legend">
        {models.map((m) => (
          <span key={m.id} className="ts-legend-item">
            <span className="ts-leg-dot" style={{ background: `oklch(78% 0.16 ${m.hue})`, boxShadow: `0 0 8px oklch(78% 0.16 ${m.hue})` }} />
            {m.name}
          </span>
        ))}
        <span className="ts-legend-item ts-legend-score">
          <span className="ts-leg-dot ts-leg-score" />
          aggregate
        </span>
      </div>

      {/* hover tooltip */}
      {hovered && (
        <div className="ts-tip" style={{
          left: `${(xs(hoverIdx) / W) * 100}%`,
        }}>
          <div className="ts-tip-head">
            <span className="ts-tip-date">{new Date(hovered.at).toLocaleString()}</span>
            <span className="ts-tip-score">{hovered.score}%</span>
          </div>
          <div className="ts-tip-body">
            {(window.MODELS || []).map(m => (
              hovered.perModel?.[m.id] != null && (
                <div key={m.id} className="ts-tip-row">
                  <span className="ts-tip-dot" style={{ background: `oklch(78% 0.16 ${m.hue})` }} />
                  <span className="ts-tip-name">{m.name}</span>
                  <span className="ts-tip-val">{hovered.perModel[m.id]}%</span>
                </div>
              )
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

Object.assign(window, { VisibilityHistory, recordScanIntoHistory, getHistoryForSite });
