/* ============================================================================
   wc-charts.jsx — chart components for the World Cup Heat Stress dashboard.
   Neutral (non-risk) line coloring — data is not tied to policies.
   All plot in °F internally; axis labels convert via toUnit/fmtTemp.

   Exports: ForecastMini, ForecastChart, HistoryChart
   ============================================================================ */
/* global React, METRICS, gradientStops, tierColor, darken, toUnit, fmtTemp, unitSym, hourLabel, shortHour,
   DAY_NAMES, DAY_DATES, buildForecast, buildHistory */
const { useRef, useState, useMemo, useCallback, useEffect } = React;

/* measure the rendered chart width so the SVG coordinate system matches CSS
   pixels — keeps text/markers undistorted at any viewport (mobile fix) */
function useChartWidth(ref, fallback) {
  const [w, setW] = useState(fallback);
  useEffect(() => {
    const el = ref.current;
    if (!el || typeof ResizeObserver === 'undefined') return undefined;
    const ro = new ResizeObserver((entries) => {
      const cw = Math.round(entries[0].contentRect.width);
      if (cw > 0) setW(cw);
    });
    ro.observe(el);
    return () => ro.disconnect();
  }, [ref]);
  return w;
}

/* neutral data-line palette (light mode) */
const LINE = '#3E6E96';
const LINE_SOFT = '#6E9CC0';

/* Catmull–Rom → cubic Bézier smoothing */
function smoothPath(pts) {
  if (pts.length < 2) return '';
  let d = `M ${pts[0].x.toFixed(2)} ${pts[0].y.toFixed(2)}`;
  for (let i = 0; i < pts.length - 1; i++) {
    const p0 = pts[Math.max(0, i - 1)], p1 = pts[i];
    const p2 = pts[i + 1], p3 = pts[Math.min(pts.length - 1, i + 2)];
    const c1x = p1.x + (p2.x - p0.x) / 6, c1y = p1.y + (p2.y - p0.y) / 6;
    const c2x = p2.x - (p3.x - p1.x) / 6, c2y = p2.y - (p3.y - p1.y) / 6;
    d += ` C ${c1x.toFixed(2)} ${c1y.toFixed(2)}, ${c2x.toFixed(2)} ${c2y.toFixed(2)}, ${p2.x.toFixed(2)} ${p2.y.toFixed(2)}`;
  }
  return d;
}

function domainFor(values) {
  let lo = Math.min(...values), hi = Math.max(...values);
  const pad = Math.max(4, (hi - lo) * 0.18);
  lo = Math.floor(lo - pad); hi = Math.ceil(hi + pad);
  return [lo, hi];
}
function yTicksFor(lo, hi) {
  const span = hi - lo;
  const step = span > 40 ? 10 : span > 20 ? 5 : span > 10 ? 4 : 2;
  const start = Math.ceil(lo / step) * step;
  const ticks = [];
  for (let v = start; v < hi; v += step) ticks.push(v);
  return ticks;
}

/* contiguous same-day spans in a forecast point array */
function daySpansOf(data) {
  const spans = [];
  data.forEach((p, i) => {
    const s = spans[spans.length - 1];
    if (!s || s.day !== p.day) spans.push({ day: p.day, start: i, end: i, label: p.dayLabel || DAY_NAMES[p.day] || '', date: p.dateLabel || DAY_DATES[p.day] || '' });
    else s.end = i;
  });
  return spans;
}

/* shared <defs> — threshold-tier gradient (stroke + faded fill) */
function GradDefs({ id, stops }) {
  return (
    <React.Fragment>
      <linearGradient id={`${id}-s`} x1="0" y1="0" x2="0" y2="1">
        {stops.map((s, i) => <stop key={i} offset={`${(s.offset * 100).toFixed(2)}%`} stopColor={s.color} />)}
      </linearGradient>
      <linearGradient id={`${id}-f`} x1="0" y1="0" x2="0" y2="1">
        {stops.map((s, i) => <stop key={i} offset={`${(s.offset * 100).toFixed(2)}%`} stopColor={s.color} stopOpacity={Math.max(0, 0.38 - s.offset * 0.30)} />)}
      </linearGradient>
    </React.Fragment>
  );
}

/* ────────────────────────────────────────────────────────────────────────
   ForecastMini — compact 3-day forecast for the summary cards.
   ──────────────────────────────────────────────────────────────────────── */
function ForecastMini({ stadium, metric, unit, persona = 'spectators' }) {
  const gid = `mini-${stadium.id}-${metric}`;
  const data = useMemo(() => buildForecast(stadium, metric), [stadium.id, metric]);
  const vals = data.map(p => p.value);
  const [lo, hi] = useMemo(() => domainFor(vals), [stadium.id, metric]);
  const stops = useMemo(() => gradientStops(metric, lo, hi, persona), [metric, lo, hi, persona]);

  const W = 600, H = 150, PL = 38, PR = 10, PT = 14, PB = 26;
  const PW = W - PL - PR, PH = H - PT - PB;
  const N = data.length;
  const xAt = (i) => PL + (i / (N - 1)) * PW;
  const yAt = (v) => PT + (1 - (v - lo) / (hi - lo)) * PH;
  const pts = data.map((p, i) => ({ x: xAt(i), y: yAt(p.value) }));
  const line = smoothPath(pts);
  const fill = `${line} L ${pts[N - 1].x.toFixed(2)} ${PT + PH} L ${pts[0].x.toFixed(2)} ${PT + PH} Z`;
  const yticks = yTicksFor(lo, hi);
  const daySpans = useMemo(() => daySpansOf(data), [data]);

  return (
    <svg viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="none" className="wc-mini-svg" role="img"
      aria-label={`${stadium.city} ${daySpans.length}-day ${METRICS[metric].label} forecast`}>
      <defs><GradDefs id={gid} stops={stops} /></defs>
      {daySpans.slice(1).map((s) => (
        <line key={`dv${s.day}`} className="wc-day-divider" x1={xAt(s.start)} y1={PT} x2={xAt(s.start)} y2={PT + PH} />
      ))}
      {yticks.map((t) => {
        const y = yAt(t);
        return (
          <g key={t}>
            <line className="wc-grid" x1={PL} y1={y} x2={PL + PW} y2={y} />
            <text className="wc-axis" x={PL - 8} y={y + 4} textAnchor="end">{fmtTemp(t, unit)}{unitSym(unit)}</text>
          </g>
        );
      })}
      <path d={fill} fill={`url(#${gid}-f)`} />
      <path d={line} fill="none" stroke={`url(#${gid}-s)`} strokeWidth="2.5" strokeLinejoin="round" strokeLinecap="round" />
      {daySpans.map((s) => (
        <text key={`dl${s.day}`} className="wc-axis" x={xAt((s.start + s.end) / 2)} y={PT + PH + 18} textAnchor="middle">{s.label}</text>
      ))}
    </svg>
  );
}

/* ────────────────────────────────────────────────────────────────────────
   ForecastChart — large interactive 3-day forecast (hover crosshair+tooltip).
   ──────────────────────────────────────────────────────────────────────── */
function ForecastChart({ stadium, metric, unit, persona = 'spectators', peakBand = true, games = [] }) {
  const gid = `fc-${stadium.id}-${metric}`;
  const svgRef = useRef(null);
  const data = useMemo(() => buildForecast(stadium, metric), [stadium.id, metric]);
  const vals = data.map(p => p.value);
  const [lo, hi] = useMemo(() => domainFor(vals), [stadium.id, metric]);
  const stops = useMemo(() => gradientStops(metric, lo, hi, persona), [metric, lo, hi, persona]);

  const peakIdx = useMemo(() => vals.indexOf(Math.max(...vals)), [stadium.id, metric]);
  const [cursor, setCursor] = useState(peakIdx);
  const [hover, setHover] = useState(false);

  const frameRef = useRef(null);
  const cw = useChartWidth(frameRef, 1014);
  const compact = cw < 560;
  const W = Math.max(220, cw), H = compact ? 230 : 300, PL = compact ? 46 : 50, PR = compact ? 8 : 16, PT = 18, PB = 38;
  const PW = W - PL - PR, PH = H - PT - PB;
  const N = data.length;
  const xAt = (i) => PL + (i / (N - 1)) * PW;
  const yAt = (v) => PT + (1 - (v - lo) / (hi - lo)) * PH;
  const pts = data.map((p, i) => ({ x: xAt(i), y: yAt(p.value) }));
  const line = smoothPath(pts);
  const fill = `${line} L ${pts[N - 1].x.toFixed(2)} ${PT + PH} L ${pts[0].x.toFixed(2)} ${PT + PH} Z`;
  const yticks = yTicksFor(lo, hi);

  const cur = data[cursor] || data[0];
  const curPt = pts[cursor] || pts[0];
  const daySpans = useMemo(() => daySpansOf(data), [data]);

  /* kickoff markers — games whose kickoff hour falls inside the chart window */
  const koMarkers = useMemo(() => games.map(g => {
    const idx = data.findIndex(p => p.dateLabel === `${g.dow} ${g.dateLabel}` && p.h === g.kickoff);
    if (idx < 0) return null;
    return { idx, label: `Kickoff · ${hourLabel(g.kickoff).replace(':00', '')}` };
  }).filter(Boolean), [games, data]);

  const handleMove = useCallback((e) => {
    const svg = svgRef.current; if (!svg) return;
    const rect = svg.getBoundingClientRect();
    const xPx = ((e.clientX - rect.left) / rect.width) * W;
    const t = Math.max(0, Math.min(1, (xPx - PL) / PW));
    setCursor(Math.round(t * (N - 1)));
    setHover(true);
  }, [N]);

  const focusDay = cur.day;
  const focusSpan = daySpans.find(s => s.day === focusDay) || daySpans[0];
  let pkIdx = focusSpan.start;
  for (let i = focusSpan.start; i <= focusSpan.end; i++) if (data[i].value > data[pkIdx].value) pkIdx = i;
  const bandX1 = xAt(Math.max(focusSpan.start, pkIdx - 1));
  const bandX2 = xAt(Math.min(focusSpan.end, pkIdx + 2));

  const tickHours = [0, 6, 12, 18];
  const tipPct = (curPt.x / W) * 100;
  const tipX = tipPct > 80 ? 'calc(-100% - 14px)' : tipPct < 16 ? '14px' : '-50%';
  const tipBelow = curPt.y < PT + 64;
  const tipY = tipBelow ? 'calc(0% + 20px)' : 'calc(-100% - 14px)';

  return (
    <div className="wc-chart-frame" ref={frameRef}>
      <svg ref={svgRef} viewBox={`0 0 ${W} ${H}`} className="wc-chart-svg" preserveAspectRatio="none" style={{ height: H }}
        onPointerMove={handleMove} onPointerLeave={() => setHover(false)} onPointerDown={handleMove}>
        <defs>
          <GradDefs id={gid} stops={stops} />
          <clipPath id={`${gid}-clip`}><rect x={PL} y={PT} width={PW} height={PH} /></clipPath>
        </defs>

        {yticks.map((t) => {
          const y = yAt(t);
          return (<g key={t}>
            <line className="wc-grid" x1={PL} y1={y} x2={PL + PW} y2={y} />
            <text className="wc-axis" x={PL - 12} y={y + 4} textAnchor="end">{fmtTemp(t, unit)}{unitSym(unit)}</text>
          </g>);
        })}
        {daySpans.slice(1).map((s) => (
          <line key={`dv${s.day}`} className="wc-day-divider" x1={xAt(s.start)} y1={PT} x2={xAt(s.start)} y2={PT + PH} />
        ))}

        <path d={fill} fill={`url(#${gid}-f)`} clipPath={`url(#${gid}-clip)`} />
        <path d={line} fill="none" stroke={`url(#${gid}-s)`} strokeWidth="2.5" strokeLinejoin="round" strokeLinecap="round" clipPath={`url(#${gid}-clip)`} />

        {peakBand && !compact && (
          <rect className="wc-peak-band" x={bandX1} y={PT} width={bandX2 - bandX1} height={PH} rx="2" clipPath={`url(#${gid}-clip)`} />
        )}

        {koMarkers.map((mk) => {
          const x = xAt(mk.idx);
          const koF = compact ? 10 : 12;
          const koH = compact ? 16 : 20;
          const w = mk.label.length * koF * 0.54 + (compact ? 14 : 18);
          const px = Math.max(PL + w / 2 + 2, Math.min(PL + PW - w / 2 - 2, x));
          return (
            <g key={`ko${mk.idx}`}>
              <line className="wc-ko-line" x1={x} y1={PT} x2={x} y2={PT + PH} clipPath={`url(#${gid}-clip)`} />
              <circle className="wc-ko-dot" cx={x} cy={yAt(data[mk.idx].value)} r={compact ? 4 : 5} />
              <g transform={`translate(${px}, ${PT + koH / 2})`}>
                <rect className="wc-ko-pill" x={-w / 2} y={-koH / 2} width={w} height={koH} rx={koH / 2} />
                <text className="wc-ko-text" x="0" y={koF / 3} textAnchor="middle" style={{ fontSize: koF }}>{mk.label}</text>
              </g>
            </g>
          );
        })}

        {daySpans.map((s) => {
          const mid = Math.round((s.start + s.end) / 2);
          return (
            <g key={`xl${s.day}`}>
              {!compact && <text className="wc-axis" x={xAt(s.start)} y={PT + PH + 22} textAnchor="start">{shortHour(data[s.start].h)}</text>}
              <text className="wc-axis" x={xAt(mid)} y={PT + PH + 22} textAnchor="middle">{shortHour(data[mid].h)}</text>
              <text className="wc-axis-day" x={xAt(mid)} y={PT + PH + 35} textAnchor="middle">{s.date.replace(/^[A-Za-z]+ /, '')}</text>
            </g>
          );
        })}

        <g clipPath={`url(#${gid}-clip)`}>
          <line className="wc-crosshair" x1={curPt.x} y1={PT} x2={curPt.x} y2={PT + PH} />
        </g>
        <circle cx={curPt.x} cy={curPt.y} r="8" className="wc-hover-dot" />
        <circle cx={curPt.x} cy={curPt.y} r="3.5" fill={tierColor(metric, cur.value, persona)} />
      </svg>

      <div className="wc-tooltip" style={{ left: `${tipPct}%`, top: `${(curPt.y / H) * 100}%`, transform: `translate(${tipX}, ${tipY})`, opacity: 1 }}>
        <div className="wc-tooltip-date">{cur.dateLabel || DAY_DATES[cur.day]} · {hourLabel(cur.h)}</div>
        <div className="wc-tooltip-card">
          <div className="wc-tooltip-row">
            <span className="wc-tooltip-dot" style={{ background: tierColor(metric, cur.value, persona), borderColor: darken(tierColor(metric, cur.value, persona), 0.55) }} />
            <span className="wc-tooltip-temp">{fmtTemp(cur.value, unit, 1)}{unitSym(unit)}</span>
          </div>
          <div className="wc-tooltip-unit">{METRICS[metric].label}</div>
        </div>
      </div>
    </div>
  );
}

/* ────────────────────────────────────────────────────────────────────────
   HistoryChart — 30-day daily-peak history with hover.
   ──────────────────────────────────────────────────────────────────────── */
function HistoryChart({ stadium, metric, unit, rangeDays = 3, persona = 'spectators' }) {
  const gid = `hist-${stadium.id}-${metric}`;
  const svgRef = useRef(null);
  const series = useMemo(() => buildHistorySeries(stadium, metric, rangeDays), [stadium.id, metric, rangeDays]);
  const data = series.points;
  const vals = data.map(p => p.value);
  const [lo, hi] = useMemo(() => domainFor(vals), [stadium.id, metric, rangeDays]);
  const stops = useMemo(() => gradientStops(metric, lo, hi, persona), [metric, lo, hi, persona]);
  const [cursor, setCursor] = useState(-1);

  const frameRef = useRef(null);
  const cw = useChartWidth(frameRef, 1014);
  const compact = cw < 560;
  const W = Math.max(220, cw), H = compact ? 220 : 280, PL = compact ? 46 : 50, PR = compact ? 8 : 16, PT = 18, PB = 40;
  const PW = W - PL - PR, PH = H - PT - PB;
  const N = data.length;
  const xAt = (i) => PL + (N === 1 ? PW / 2 : (i / (N - 1)) * PW);
  const yAt = (v) => PT + (1 - (v - lo) / (hi - lo)) * PH;
  const pts = data.map((p, i) => ({ x: xAt(i), y: yAt(p.value) }));
  const line = smoothPath(pts);
  const fill = `${line} L ${pts[N - 1].x.toFixed(2)} ${PT + PH} L ${pts[0].x.toFixed(2)} ${PT + PH} Z`;
  const yticks = yTicksFor(lo, hi);
  const showDots = series.mode === 'daily';

  const handleMove = useCallback((e) => {
    const svg = svgRef.current; if (!svg) return;
    const rect = svg.getBoundingClientRect();
    const xPx = ((e.clientX - rect.left) / rect.width) * W;
    const t = Math.max(0, Math.min(1, (xPx - PL) / PW));
    setCursor(Math.round(t * (N - 1)));
  }, [N]);

  const cur = cursor >= 0 ? data[cursor] : null;
  const curPt = cursor >= 0 ? pts[cursor] : null;
  const labelEvery = Math.max(1, Math.ceil(N / Math.max(3, Math.floor(W / 170))));

  return (
    <div className="wc-chart-frame" ref={frameRef}>
      <svg ref={svgRef} viewBox={`0 0 ${W} ${H}`} className="wc-chart-svg" preserveAspectRatio="none" style={{ height: H }}
        onPointerMove={handleMove} onPointerLeave={() => setCursor(-1)} onPointerDown={handleMove}>
        <defs>
          <GradDefs id={gid} stops={stops} />
          <clipPath id={`${gid}-clip`}><rect x={PL} y={PT} width={PW} height={PH} /></clipPath>
        </defs>

        {yticks.map((t) => {
          const y = yAt(t);
          return (<g key={t}>
            <line className="wc-grid" x1={PL} y1={y} x2={PL + PW} y2={y} />
            <text className="wc-axis" x={PL - 12} y={y + 4} textAnchor="end">{fmtTemp(t, unit)}{unitSym(unit)}</text>
          </g>);
        })}

        <path d={fill} fill={`url(#${gid}-f)`} clipPath={`url(#${gid}-clip)`} />
        <path d={line} fill="none" stroke={`url(#${gid}-s)`} strokeWidth="2.5" strokeLinejoin="round" strokeLinecap="round" clipPath={`url(#${gid}-clip)`} />

        {showDots && pts.map((p, i) => (
          <circle key={i} cx={p.x} cy={p.y} r={cursor === i ? 4.5 : 2.6} fill={tierColor(metric, data[i].value, persona)} opacity={cursor === i ? 1 : 0.9} />
        ))}

        {data.map((p, i) => (i % labelEvery === 0 || i === N - 1) ? (
          <text key={i} className="wc-axis" x={xAt(i)} y={PT + PH + 24} textAnchor="middle">{p.axisLabel}</text>
        ) : null)}

        {curPt && (<g clipPath={`url(#${gid}-clip)`}>
          <line className="wc-crosshair" x1={curPt.x} y1={PT} x2={curPt.x} y2={PT + PH} />
        </g>)}
        {curPt && <circle cx={curPt.x} cy={curPt.y} r="8" className="wc-hover-dot" />}
        {curPt && <circle cx={curPt.x} cy={curPt.y} r="3.5" fill={tierColor(metric, cur.value, persona)} />}
      </svg>

      {cur && curPt && (
        <div className="wc-tooltip" style={{
          left: `${(curPt.x / W) * 100}%`, top: `${(curPt.y / H) * 100}%`,
          transform: `translate(${(curPt.x / W) * 100 > 80 ? 'calc(-100% - 14px)' : (curPt.x / W) * 100 < 16 ? '14px' : '-50%'}, calc(-100% - 14px))`,
          opacity: 1,
        }}>
          <div className="wc-tooltip-date">{cur.tipTitle}</div>
          <div className="wc-tooltip-card">
            <div className="wc-tooltip-row">
              <span className="wc-tooltip-dot" style={{ background: tierColor(metric, cur.value, persona), borderColor: darken(tierColor(metric, cur.value, persona), 0.55) }} />
              <span className="wc-tooltip-temp">{fmtTemp(cur.value, unit, 1)}{unitSym(unit)}</span>
            </div>
            <div className="wc-tooltip-unit">{series.mode === 'daily' ? 'Daily peak' : ''} {METRICS[metric].label}</div>
          </div>
        </div>
      )}
    </div>
  );
}

Object.assign(window, { ForecastMini, ForecastChart, HistoryChart });
