/* ============================================================================
   wc-app.jsx — World Cup Heat Stress dashboard app
   ============================================================================ */
/* global React, ReactDOM, METRICS, ROOFS, STADIUMS, COUNTRY_ORDER, DAY_NAMES, DAY_DATES, STADIUM_PHOTOS, PW_STATIONS, REAL_FORECAST, FORECAST_SOURCE, nextMatchFor, dayPeakValue, flagTeam, startLivePolling, liveNow, venueLocalHour, clockLabel,
   TIER_COLORS, tierFor, tierLabel, tierTone, tierColor, toUnit, fmtTemp, unitSym,
   buildForecast, buildHistory, matchRiskValue, hourLabel,
   ForecastMini, ForecastChart, HistoryChart,
   TweaksPanel, TweakSection, TweakRadio, useTweaks */
const { useState, useEffect, useMemo, useCallback } = React;

const NOW_HOUR = 12; // legacy fallback anchor (no longer drives the Now reading)

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "metric": "wbgt",
  "unit": "F",
  "sort": "country"
} /*EDITMODE-END*/;

/* ── icons ─────────────────────────────────────────────── */
function Icon({ name, size = 18 }) {
  const s = { width: size, height: size, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 1.7, strokeLinecap: 'round', strokeLinejoin: 'round' };
  switch (name) {
    case 'close': return <svg {...s}><path d="M6 6l12 12M18 6 6 18" /></svg>;
    case 'warn': return <svg viewBox="0 0 24 24" width={size} height={size} fill="currentColor" aria-hidden="true"><path d="M12 5.99 19.53 19H4.47zM2.74 18c-.77 1.33.19 3 1.73 3h15.06c1.54 0 2.5-1.67 1.73-3L13.73 4.99c-.77-1.33-2.69-1.33-3.46 0zM11 11v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1m0 5h2v2h-2z" /></svg>;
    case 'check': return <svg {...s}><path d="M20 6 9 17l-5-5" /></svg>;
    case 'roof-open': return <svg {...s}><path d="M3 11l9-6 9 6" /><path d="M5 10v10h14V10" /></svg>;
    case 'roof-closed': return <svg {...s}><path d="M3 11l9-6 9 6" /><path d="M3 11h18" /><path d="M5 11v9h14v-9" /></svg>;
    case 'history': return <svg {...s}><path d="M3 12a9 9 0 1 0 3-6.7L3 8" /><path d="M3 4v4h4" /><path d="M12 8v4l3 2" /></svg>;
    case 'station': return <svg {...s}><circle cx="12" cy="11" r="2" /><path d="M12 13v7" /><path d="M8.5 7.5a5 5 0 0 0 0 7" /><path d="M15.5 7.5a5 5 0 0 1 0 7" /><path d="M5.6 4.6a9 9 0 0 0 0 12.8" /><path d="M18.4 4.6a9 9 0 0 1 0 12.8" /></svg>;
    case 'forecast': return <svg {...s}><path d="M3 17l5-5 4 4 8-9" /><path d="M3 21h18" opacity="0.5" /></svg>;
    case 'whistle': return <svg {...s}><circle cx="9" cy="13" r="6" /><path d="M14 10l7-3" /><path d="M9 7V4h4" /></svg>;
    default: return null;
  }
}

function BrandMark() {
  return (
    <svg className="wc-brand-mark" viewBox="0 0 24 40" fill="none" aria-hidden="true">
      <path fillRule="evenodd" clipRule="evenodd" d="M23.9907 11.5297C23.9909 9.48024 23.4194 7.46788 22.3351 5.70064C21.2509 3.9334 19.6933 2.47541 17.8234 1.47737C15.3958 0.126163 12.5324 -0.318832 9.78295 0.227849C9.46969 0.290325 9.13032 0.365296 8.85622 0.452762C6.313 1.11616 4.06825 2.56177 2.46788 4.56683C0.867514 6.57189 0.000342211 9.02512 0 11.5485V11.8234C0 12.2732 0 12.723 0.045689 13.1666C0.27411 15.5907 1.17474 17.8585 2.0819 20.1077C4.41024 25.7149 7.07078 31.1904 10.0505 36.5076C10.7032 37.6509 11.3166 38.7817 12.0084 40C12.1781 39.7189 12.3021 39.5252 12.4131 39.319C14.26 35.8516 16.1657 32.4092 17.9408 28.9043C20.0902 24.8032 21.8971 20.5457 23.3446 16.1717C23.8071 14.7571 24.0275 13.2803 23.9973 11.7984C23.9973 11.7047 23.9973 11.6172 23.9973 11.5235L23.9907 11.5297Z" fill="url(#wc-grad)" />
      <path fillRule="evenodd" clipRule="evenodd" d="M15.676 2.90161L5.3253 13.9598H10.9118L7.93583 22.5315L18.7108 10.8485H13.3918L15.676 2.90161Z" fill="#121A24" />
      <defs><linearGradient id="wc-grad" x1="28.4219" y1="-3.5" x2="0" y2="40" gradientUnits="userSpaceOnUse"><stop stopColor="#FFE37E" /><stop offset="1" stopColor="#22BACF" /></linearGradient></defs>
    </svg>
  );
}

/* ── guidance copy by metric + tier ────────────────────── */
const GUIDANCE = {
  wbgt: [
    'Conditions are low risk. Normal activity — keep unlimited water access and at least three rest breaks of ≥3 min per hour.',
    'Moderate heat stress. Increase rest to three breaks of ≥4 min per hour and encourage proactive hydration before kickoff.',
    'High heat stress. Limit intense bouts, provide four shaded rest breaks of ≥4 min per hour, and monitor players closely.',
    'Very high heat stress. Shorten sessions, mandate shaded cooling breaks, and keep active cooling on standby. Watch for early heat-illness signs.',
    'Extreme heat stress. Consider rescheduling or suspending play. Cooling and medical resources must be on site; cancel for unacclimatized groups.',
  ],
  heatIndex: [
    'Low risk. Standard hydration protocols are sufficient for all activity levels.',
    'Caution. Fatigue is possible with prolonged exposure — schedule regular hydration breaks.',
    'Extreme caution. Heat cramps and exhaustion are possible. Add rest and shade and monitor at-risk individuals.',
    'Danger. Heat cramps and exhaustion are likely and heat stroke is possible. Limit exertion with mandatory cooling breaks.',
    'Extreme danger. Heat stroke is highly likely. Suspend outdoor activity; medical standby is required.',
  ],
  ambient: [
    'Mild conditions. Normal activity with standard hydration protocols.',
    'Warm. Encourage regular water breaks and monitor effort levels in direct sun.',
    'Hot. Add shaded rest breaks each hour, increase hydration, and rotate players more frequently.',
    'Very hot. Limit warm-up intensity, mandate cooling breaks each half, and keep active cooling available.',
    'Extreme heat. Consider shifting play to a cooler window. Continuous monitoring and medical standby required.',
  ],
};

/* live "now" reading for a stadium+metric (°F): live station data when fresh,
   otherwise the NWS forecast point nearest the current venue-local hour. */
function nowValue(stadium, metric) {
  const live = liveNow(stadium.id, metric);
  if (live != null) return live;
  const fc = buildForecast(stadium, metric);
  const target = venueLocalHour(stadium);
  let best = fc[0], bestDist = Infinity;
  for (const p of fc) {
    if (p.day !== 0 && p.day !== 1) continue;
    const dist = Math.abs(p.day * 24 + p.h - target);
    if (dist < bestDist) { bestDist = dist; best = p; }
  }
  return best.value;
}
function isLive(stadium, metric) { return liveNow(stadium.id, metric) != null; }
function todayPeak(stadium, metric) {
  const fc = buildForecast(stadium, metric).filter(p => p.day === 0);
  return fc.reduce((a, b) => (b.value > a.value ? b : a), fc[0]);
}
function dayPeakFor(stadium, metric, day) {
  const fc = buildForecast(stadium, metric).filter(p => p.day === day);
  return fc.reduce((a, b) => (b.value > a.value ? b : a), fc[0]);
}

/* ── Risk pill ─────────────────────────────────────────── */
function RiskPill({ metric, value, persona = 'spectators' }) {
  const tone = tierTone(metric, value, persona);
  return (
    <span className={`wc-pill ${tone}`}>
      <span className="wc-pill-dot" />
      {tierLabel(metric, value, persona)}
    </span>
  );
}

/* ── Roof badge ────────────────────────────────────────── */
function RoofBadge({ roof }) {
  const r = ROOFS[roof];
  const controlled = roof === 'retractable';
  const icon = roof === 'open' ? 'roof-open' : 'roof-closed';
  return (
    <span className={`wc-roof ${controlled ? 'controlled' : ''}`} title={`${r.label} — effective heat stress reduced`}>
      <Icon name={icon} size={13} />
      {r.short}
    </span>
  );
}

/* ── Stadium card ──────────────────────────────────────── */
function StadiumCard({ stadium, metric, unit, onOpen }) {
  const now = nowValue(stadium, metric);
  const peak = todayPeak(stadium, metric);
  const matchVal = matchRiskValue(stadium, metric);
  const nm = nextMatchFor(stadium);
  const gdayLabel = nm.whenLabel;

  return (
    <button className="wc-card" type="button" onClick={() => onOpen(stadium.id)}>
      <div className="wc-card-banner">
        {STADIUM_PHOTOS[stadium.id] ? (
          <span className="wc-card-photo">
            <img className="wc-card-img" src={STADIUM_PHOTOS[stadium.id]} alt={`${stadium.venue} photo`} />
          </span>
        ) : (
          /* empty slot: clicks stay inside so drag-drop / browse works */
          <span className="wc-card-photo" onClick={(e) => e.stopPropagation()}>
            <image-slot id={`stadium-photo-${stadium.id}`} shape="rect" fit="cover"
              placeholder="Drop stadium photo"></image-slot>
          </span>
        )}
        <div className="wc-banner-scrim"></div>
        <div className="wc-card-head">
          <div className="wc-card-id">
            <div className="wc-card-venue">{stadium.venue}</div>
            <div className="wc-card-city">{stadium.city} · {stadium.tz}</div>
          </div>
        <div className="wc-card-match">
          <div className="wc-next-badge">Up next</div>
          <div className="wc-match-stage">{nm.stage}</div>
          <div className="wc-match-teams">{flagTeam(nm.teamA)} vs {flagTeam(nm.teamB)}</div>
          <div className="wc-match-time"><strong>{nm.whenLabel}</strong> · {hourLabel(nm.kickoff).replace(':00', '')} {stadium.tz}</div>
        </div>
        </div>
      </div>

      <div className="wc-statgrid">
        <div className="wc-stat">
          <div className="wc-stat-label" title={isLive(stadium, metric) ? 'Live · Perry Weather on-site conditions' : 'Estimated from the NWS hourly forecast'}>
            {isLive(stadium, metric) && <span className="wc-live-dot" />}
            Now · {clockLabel()}
          </div>
          <div className="wc-stat-val"><span className="wc-stat-num">{fmtTemp(now, unit)}<span className="wc-stat-u">{unitSym(unit)}</span></span><span className="wc-metric-pill">{METRICS[metric].label}</span></div>
          <div className="wc-stat-sub">Peak <strong>{fmtTemp(peak.value, unit)}{unitSym(unit)}</strong> @ {hourLabel(peak.h).replace(':00', '')}</div>
        </div>
        <div className="wc-stat">
          <div className="wc-stat-label">At kickoff · {gdayLabel}</div>
          <div className="wc-stat-val"><span className="wc-stat-num">{fmtTemp(matchVal, unit)}<span className="wc-stat-u">{unitSym(unit)}</span></span><span className="wc-metric-pill">{METRICS[metric].label}</span></div>
        </div>
      </div>
    </button>
  );
}

/* ── Threshold gauge (modal readout) ───────────────────── */
function Gauge({ metric, value, unit }) {
  const m = METRICS[metric];
  const tier = tierFor(metric, value);
  const pct = Math.max(0, Math.min(100, (value - m.scaleMin) / (m.scaleMax - m.scaleMin) * 100));
  return (
    <div className="wc-gauge">
      <div className="wc-gauge-track">
        <div className="wc-gauge-segs">
          {[0, 1, 2, 3, 4].map(i => <div key={i} className={`wc-gauge-seg s${i} ${tier === i ? 'active' : ''}`} />)}
        </div>
        <div className="wc-gauge-marker" style={{ left: `${pct}%` }} />
      </div>
      <div className="wc-gauge-labels">
        {m.rangeLabels.map((rl, i) => <span key={i} className={tier === i ? 'active' : ''}>{rl}</span>)}
      </div>
    </div>
  );
}

/* ── Detail modal ──────────────────────────────────────── */
function DetailModal({ stadium, initialMetric, initialUnit, onClose }) {
  const [metric, setMetric] = useState(initialMetric);
  const [unit, setUnit] = useState(initialUnit);
  const [histRange, setHistRange] = useState(3);
  const [persona, setPersona] = useState('spectators');
  // reset to the page's current selection whenever a different venue is opened
  useEffect(() => { setMetric(initialMetric); setUnit(initialUnit); setHistRange(3); setPersona('spectators'); }, [stadium && stadium.id]);

  useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    document.body.style.overflow = 'hidden';
    return () => { window.removeEventListener('keydown', onKey); document.body.style.overflow = ''; };
  }, [onClose]);

  if (!stadium) return null;
  const m = METRICS[metric];
  const peak = todayPeak(stadium, metric);
  const r = ROOFS[stadium.roof];
  const nm = nextMatchFor(stadium);
  const histRes = histRange <= 1 ? '15-minute readings' : histRange <= 3 ? 'Hourly readings' : histRange <= 7 ? '3-hour readings' : 'Daily peak readings';
  const histTitle = histRange === 1 ? '24-hour' : `${histRange}-day`;

  return (
    <div className="wc-scrim open" onClick={(e) => { if (e.target === e.currentTarget) onClose(); }} role="presentation">
      <div className="wc-modal" role="dialog" aria-modal="true" aria-label={`${stadium.venue} forecast details`} data-screen-label={`Detail ${stadium.city}`}>
        <div className="wc-modal-head">
          <div className="wc-modal-titles">
            <span className="wc-modal-eyebrow">{stadium.flag} {stadium.city} · {stadium.country}</span>
            <h2 className="wc-modal-title">{stadium.venue}</h2>
            <div className="wc-modal-sub">
              <span>{r.label}</span><span className="sep" />
              <span>Capacity {stadium.capacity.toLocaleString()}</span><span className="sep" />
              <span>{stadium.tz}</span>
            </div>
          </div>
          <div className="wc-modal-next">
            <div className="wc-next-badge">Up next</div>
            <div className="wc-mn-stage">{nm.stage}</div>
            <div className="wc-mn-teams">{flagTeam(nm.teamA)} vs {flagTeam(nm.teamB)}</div>
            <div className="wc-mn-time"><strong>{nm.whenLabel}</strong> · {hourLabel(nm.kickoff).replace(':00', '')} {stadium.tz}</div>
            <div className="wc-mn-stats">
              <span className="wc-mn-stat">
                <span className="wc-mn-stat-lbl">{m.label} @ kickoff</span>
                <strong>{fmtTemp(matchRiskValue(stadium, metric), unit)}{unitSym(unit)}</strong>
              </span>
              <span className="wc-mn-stat">
                <span className="wc-mn-stat-lbl">Day max</span>
                <strong>{fmtTemp(dayPeakValue(stadium, metric, new Date(2026, 5, 11 + nm.dayOffset)), unit)}{unitSym(unit)}</strong>
              </span>
            </div>
          </div>
          <button className="wc-modal-close" type="button" aria-label="Close" onClick={onClose}><Icon name="close" size={22} /></button>
        </div>

        <div className="wc-modal-tools">
          <div className="wc-control-group">
            <span className="wc-control-label">Metric</span>
            <div className="wc-seg" role="tablist" aria-label="Metric">
              {[['wbgt', 'WBGT'], ['heatIndex', 'Heat Index'], ['ambient', 'Ambient']].map(([k, lbl]) => (
                <button key={k} type="button" role="tab" aria-selected={metric === k}
                  className={`wc-seg-btn ${metric === k ? 'active' : ''}`} onClick={() => setMetric(k)}>{lbl}</button>
              ))}
            </div>
          </div>
          <div className="wc-control-group">
            <span className="wc-control-label">Units</span>
            <div className="wc-seg units" role="tablist" aria-label="Units">
              {[['F', '°F'], ['C', '°C']].map(([k, lbl]) => (
                <button key={k} type="button" role="tab" aria-selected={unit === k}
                  className={`wc-seg-btn ${unit === k ? 'active' : ''}`} onClick={() => setUnit(k)}>{lbl}</button>
              ))}
            </div>
          </div>
        </div>

        {/* 5-day forecast */}
        <div className="wc-panel">
          <div className="wc-panel-head">
            <span className="wc-panel-icon"><Icon name="forecast" size={18} /></span>
            <h3>{REAL_FORECAST[stadium.id] ? `${REAL_FORECAST[stadium.id].days}-day` : '3-day'} {m.label} forecast</h3>
            <span className="wc-panel-spacer" />
            <span className="wc-panel-meta">Peak {fmtTemp(peak.value, unit)}{unitSym(unit)} @ {hourLabel(peak.h).replace(':00', '')} today</span>
          </div>
          <div className="wc-chart-inner"><ForecastChart stadium={stadium} metric={metric} unit={unit} persona={persona} games={buildSchedule(stadium)} /></div>
          {REAL_FORECAST[stadium.id] ? (
            <div className="wc-src-row">
              <img className="wc-src-logo nws" src={RES('nwsLogo', 'assets/logos/nws.png')} alt="National Weather Service" />
              <div className="wc-hist-source">
                <Icon name="forecast" size={14} />
                <span>Forecast sourced from the National Weather Service (NWS) · hourly point forecast</span>
              </div>
            </div>
          ) : (
            <div className="wc-hist-source muted">
              <Icon name="forecast" size={14} />
              <span>Modeled forecast — NWS data unavailable for this venue</span>
            </div>
          )}
          <div className="wc-guidance">
            <div className="wc-guidance-row">
              <h3>Activity guidelines</h3>
              <RiskPill metric={metric} value={peak.value} persona={persona} />
              <span className="wc-panel-spacer" />
              <div className="wc-seg compact" role="tablist" aria-label="Audience">
                {PERSONA_ORDER.map((k) => (
                  <button key={k} type="button" role="tab" aria-selected={persona === k} title={PERSONAS[k].sub}
                    className={`wc-seg-btn ${persona === k ? 'active' : ''}`} onClick={() => setPersona(k)}>{PERSONAS[k].label}</button>
                ))}
              </div>
            </div>
            <div className="wc-guidance-meta">{PERSONAS[persona].sub} · today's peak {m.label} · {PERSONA_REF}</div>
            <p className="wc-guidance-body">{PERSONAS[persona].guidance[tierFor(metric, peak.value, persona)]}</p>
            <div className="wc-key">
              {PERSONAS[persona].tierLabels.map((lbl, i) => {
                const t = thresholdsFor(metric, persona);
                const f = (v) => `${fmtTemp(v, unit, unit === 'C' ? 1 : (Number.isInteger(v) ? 0 : 1))}${unitSym(unit)}`;
                const range = i === 0 ? `Below ${f(t[0])}` : i === 4 ? `Above ${f(t[3])}` : `${f(t[i - 1])} – ${f(t[i])}`;
                const active = tierFor(metric, peak.value, persona) === i;
                return (
                  <div className={`wc-key-item ${active ? 'active' : ''}`} key={i}>
                    <span className="wc-key-swatch" style={{ background: TIER_COLORS[i] }} />
                    <div className="wc-key-text">
                      <span className="wc-key-label">{lbl}</span>
                      <span className="wc-key-range">{range}</span>
                    </div>
                  </div>
                );
              })}
            </div>
          </div>
        </div>

        {/* historical */}
        <div className="wc-panel">
          <div className="wc-panel-head">
            <span className="wc-panel-icon"><Icon name="history" size={18} /></span>
            <h3>{PW_STATIONS[stadium.id] ? `${histTitle} ${m.label} history` : `${m.label} history`}</h3>
            <span className="wc-panel-spacer" />
            {PW_STATIONS[stadium.id] && <span className="wc-panel-meta">{histRes}</span>}
            {PW_STATIONS[stadium.id] && (
              <div className="wc-seg compact" role="tablist" aria-label="History range">
                {[[1, '1d'], [3, '3d'], [7, '7d'], [14, '14d']].map(([k, lbl]) => (
                  <button key={k} type="button" role="tab" aria-selected={histRange === k}
                    className={`wc-seg-btn ${histRange === k ? 'active' : ''}`} onClick={() => setHistRange(k)}>{lbl}</button>
                ))}
              </div>
            )}
          </div>
          {PW_STATIONS[stadium.id] ? (
            <React.Fragment>
              <div className="wc-panel-tools">
                <div className="wc-seg compact" role="tablist" aria-label="Metric (history)">
                  {[['wbgt', 'WBGT'], ['heatIndex', 'Heat Index'], ['ambient', 'Ambient']].map(([k, lbl]) => (
                    <button key={k} type="button" role="tab" aria-selected={metric === k}
                      className={`wc-seg-btn ${metric === k ? 'active' : ''}`} onClick={() => setMetric(k)}>{lbl}</button>
                  ))}
                </div>
                <div className="wc-seg compact" role="tablist" aria-label="Units (history)">
                  {[['F', '°F'], ['C', '°C']].map(([k, lbl]) => (
                    <button key={k} type="button" role="tab" aria-selected={unit === k}
                      className={`wc-seg-btn ${unit === k ? 'active' : ''}`} onClick={() => setUnit(k)}>{lbl}</button>
                  ))}
                </div>
              </div>
              <div className="wc-chart-inner"><HistoryChart stadium={stadium} metric={metric} unit={unit} rangeDays={histRange} persona={persona} /></div>
              <div className="wc-src-row">
                <img className="wc-src-logo" src={RES('pwLogo', 'assets/logos/perryweather.png')} alt="Perry Weather" />
                <div className="wc-hist-source">
                  <Icon name="station" size={14} />
                  <span>{`Avg max ${m.label} · aggregated from ${PW_STATIONS[stadium.id]} Perry Weather station${PW_STATIONS[stadium.id] === 1 ? '' : 's'}`}</span>
                </div>
              </div>
            </React.Fragment>
          ) : (
            <div className="wc-hist-empty">
              <Icon name="station" size={22} />
              <div className="wc-hist-empty-title">Historical data not available</div>
              <div className="wc-hist-empty-sub">No Perry Weather stations report from this venue.</div>
            </div>
          )}
        </div>

        {/* match schedule */}
        <div className="wc-panel">
          <div className="wc-panel-head">
            <span className="wc-panel-icon"><Icon name="whistle" size={18} /></span>
            <h3>Match schedule</h3>
            <span className="wc-panel-spacer" />
            <span className="wc-panel-meta">All times {stadium.tz} · {m.label} at kickoff · completed games show final score & daily peak</span>
          </div>
          <div className="wc-table-wrap">
            <table className="wc-table">
              <thead>
                <tr><th>Date</th><th>Kickoff</th><th>Stage</th><th>Match</th><th className="num">{m.label}</th></tr>
              </thead>
              <tbody>
                {buildSchedule(stadium).map(g => (
                  <tr key={g.id} className={g.completed ? 'done' : ''}>
                    <td className="nowrap">{g.dow} {g.dateLabel}</td>
                    <td className="nowrap">{hourLabel(g.kickoff).replace(':00', '')}</td>
                    <td className="nowrap">{g.stage}</td>
                    <td>
                      {g.completed && g.score ? (
                        <span>{flagTeam(g.teamA)} <strong className="wc-score">{g.score}</strong> {flagTeam(g.teamB)} <span className="wc-ft-tag">FT</span></span>
                      ) : (
                        <span>{flagTeam(g.teamA)} vs {flagTeam(g.teamB)}</span>
                      )}
                    </td>
                    <td className="num">
                      {g.completed ? (
                        <span>{fmtTemp(dayPeakValue(stadium, metric, g.dateObj), unit)}{unitSym(unit)} <span className="wc-peak-tag">peak</span></span>
                      ) : (
                        <span>{fmtTemp(kickoffValue(stadium, metric, g.kickoff), unit)}{unitSym(unit)}</span>
                      )}
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </div>
      </div>
    </div>
  );
}

/* ── Dashboard root ────────────────────────────────────── */
function Dashboard() {
  const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const { metric, unit, sort } = tweaks;
  const [openId, setOpenId] = useState(() => {
    const h = (typeof location !== 'undefined' ? location.hash : '').replace('#', '');
    return STADIUMS.some(s => s.id === h) ? h : null;
  });
  const [defsOpen, setDefsOpen] = useState(false);

  const open = useCallback((id) => { setOpenId(id); if (typeof history !== 'undefined') history.replaceState(null, '', '#' + id); }, []);
  const close = useCallback(() => { setOpenId(null); if (typeof history !== 'undefined') history.replaceState(null, '', location.pathname + location.search); }, []);

  useEffect(() => {
    const onHash = () => {
      const h = location.hash.replace('#', '');
      setOpenId(STADIUMS.some(s => s.id === h) ? h : null);
    };
    window.addEventListener('hashchange', onHash);
    return () => window.removeEventListener('hashchange', onHash);
  }, []);
  const openStadium = STADIUMS.find(s => s.id === openId) || null;

  // live conditions polling (60s) + 30s clock tick so “Now · time” stays current
  const [liveTick, setLiveTick] = useState(0);
  useEffect(() => {
    startLivePolling(() => setLiveTick(t => t + 1), 60000);
    const clock = setInterval(() => setLiveTick(t => t + 1), 30000);
    return () => clearInterval(clock);
  }, []);

  // risk rollup across all venues at "now"
  const rollup = useMemo(() => {
    const counts = [0, 0, 0, 0, 0];
    STADIUMS.forEach(s => { counts[tierFor(metric, nowValue(s, metric))]++; });
    return counts;
  }, [metric, liveTick]);
  const elevated = rollup[2] + rollup[3] + rollup[4];

  // grouped or sorted — within groups, soonest scheduled kickoff first
  const nextKey = (s) => { const nm = nextMatchFor(s); return (nm.dayOffset * 24 + nm.kickoff - (window.utcOffsetHours ? window.utcOffsetHours(s) : 0)); };
  const sections = useMemo(() => {
    if (sort === 'risk') {
      const sorted = [...STADIUMS].sort((a, b) => nowValue(b, metric) - nowValue(a, metric));
      return [{ country: null, items: sorted }];
    }
    return COUNTRY_ORDER.map(c => ({ country: c, items: STADIUMS.filter(s => s.country === c).sort((a, b) => nextKey(a) - nextKey(b)) }));
  }, [metric, sort, liveTick]);

  const flagFor = (c) => ({ 'United States': '🇺🇸', 'Canada': '🇨🇦', 'Mexico': '🇲🇽' }[c] || '');

  return (
    <div className="wc-shell">
      <header className="wc-topbar">
        <div className="wc-brand">
          <BrandMark />
          <div className="wc-brand-text">
            <h1 className="wc-title">2026 FIFA World Cup Venue Heat Stress Tool</h1>
            <span className="wc-presented">Presented by Perry Weather and Korey Stringer Institute</span>
          </div>
          <span className="wc-brand-divider"></span>
          <div className="wc-partners">
            <img className="wc-partner-logo ksi" src={RES('ksiLogo', 'assets/logos/ksi.png')} alt="Korey Stringer Institute" />
          </div>
        </div>
        <div className="wc-topbar-spacer" />
        <div className="wc-controls">
          <div className="wc-control-group">
            <span className="wc-control-label">Metric</span>
            <div className="wc-seg" role="tablist" aria-label="Metric">
              {[['wbgt', 'WBGT'], ['heatIndex', 'Heat Index'], ['ambient', 'Ambient']].map(([k, lbl]) => (
                <button key={k} type="button" role="tab" aria-selected={metric === k}
                  className={`wc-seg-btn ${metric === k ? 'active' : ''}`} onClick={() => setTweak('metric', k)}>{lbl}</button>
              ))}
            </div>
          </div>
          <div className="wc-control-group">
            <span className="wc-control-label">Units</span>
            <div className="wc-seg units" role="tablist" aria-label="Units">
              {[['F', '°F'], ['C', '°C']].map(([k, lbl]) => (
                <button key={k} type="button" role="tab" aria-selected={unit === k}
                  className={`wc-seg-btn ${unit === k ? 'active' : ''}`} onClick={() => setTweak('unit', k)}>{lbl}</button>
              ))}
            </div>
          </div>
        </div>
      </header>

      <div className="wc-intro">
        <p className="wc-intro-copy">
          <strong>Heat will shape this World Cup.</strong> A summer tournament across North America means players,
          volunteers, and fans contend with serious heat and humidity — FIFA has scheduled cooling breaks in every
          match, regardless of conditions. This dashboard is a single reference for what it actually feels like at
          each of the 16 host stadiums: live readings from on-site Perry Weather stations, NWS forecasts, and
          Korey Stringer Institute guidance on what those conditions mean for spectators, volunteers, and workers.
        </p>
        
        <div className="wc-sources">
          <span className="wc-source-item">
            <img className="wc-source-logo round" src={RES('nwsLogo', 'assets/logos/nws.png')} alt="National Weather Service" />
            <span>Forecasted &amp; current conditions · <strong>National Weather Service (NWS)</strong></span>
          </span>
          <span className="wc-source-item">
            <img className="wc-source-logo" src={RES('pwLogo', 'assets/logos/perryweather.png')} alt="Perry Weather" />
            <span>Historical data · <strong>Perry Weather</strong></span>
          </span>
        </div>
      </div>

      <div className="wc-defs-drawer">
        <button className="wc-defs-toggle" type="button" aria-expanded={defsOpen} onClick={() => setDefsOpen(o => !o)}>
          <span>What do WBGT, Heat Index, and Ambient Temp measure?</span>
          <svg className={`wc-defs-chev ${defsOpen ? 'open' : ''}`} viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg>
        </button>
        {defsOpen && (
        <div className="wc-defs">
        <div className="wc-def">
          <div className="wc-def-term">WBGT</div>
          <p className="wc-def-body">Wet Bulb Globe Temperature is the most complete measure of heat stress on the
            body — it accounts for temperature, humidity, wind, and direct sunlight. It's the standard used to
            guide activity in sport and occupational settings.</p>
          <a className="wc-def-link" href="https://perryweather.com/resources/what-is-wbgt-and-how-do-you-calculate-it/" target="_blank" rel="noopener noreferrer">What is WBGT and how is it calculated? →</a>
        </div>
        <div className="wc-def">
          <div className="wc-def-term">Heat Index</div>
          <p className="wc-def-body">How hot it actually feels in the shade, combining air temperature with
            humidity — the familiar “feels-like” figure from public weather forecasts. It does not account for
            sun exposure or wind.</p>
          <a className="wc-def-link" href="https://perryweather.com/resources/heat-index-wet-bulb-globe-temp/" target="_blank" rel="noopener noreferrer">Heat index vs. WBGT →</a>
        </div>
        <div className="wc-def">
          <div className="wc-def-term">Ambient Temp</div>
          <p className="wc-def-body">The air temperature a standard thermometer reads, with no adjustment for
            humidity, sun, or wind. A useful baseline — but on its own it understates how punishing conditions
            feel in a packed, sun-exposed stadium.</p>
        </div>
        </div>
        )}
      </div>

      {sections.map((sec, si) => (
        <section className="wc-section" key={si}>
          {sec.country && (
            <div className="wc-section-head">
              <span className="wc-section-flag">{flagFor(sec.country)}</span>
              <h2 className="wc-section-title">{sec.country}</h2>
              <span className="wc-section-count">{sec.items.length} venues</span>
              <span className="wc-section-rule" />
            </div>
          )}
          <div className="wc-grid">
            {sec.items.map(s => <StadiumCard key={s.id} stadium={s} metric={metric} unit={unit} onOpen={open} />)}
          </div>
        </section>
      ))}

      <div className="wc-about">
        <div className="wc-about-card">
          <img className="wc-about-logo" src={RES('pwLogo', 'assets/logos/perryweather.png')} alt="Perry Weather" />
          <p className="wc-def-body">Perry Weather is a weather-safety monitoring platform trusted by outdoor
            operations across athletics, construction, energy, and live events. On-site weather stations stream
            hyper-local conditions — WBGT, heat index, lightning, and more — so organizations know exactly what's
            happening at their site, not at the nearest airport.</p>
          <a className="wc-def-link" href="https://perryweather.com" target="_blank" rel="noopener noreferrer">perryweather.com →</a>
        </div>
        <div className="wc-about-card">
          <img className="wc-about-logo ksi" src={RES('ksiLogo', 'assets/logos/ksi.png')} alt="Korey Stringer Institute" />
          <p className="wc-def-body">The Korey Stringer Institute at the University of Connecticut is a nonprofit
            dedicated to preventing sudden death in sport and physical activity, with a focus on exertional heat
            stroke. Named for Minnesota Vikings lineman Korey Stringer, who died of heat stroke in 2001, KSI has
            led heat-safety research, policy, and education since 2010.</p>
          <a className="wc-def-link" href="https://ksi.uconn.edu" target="_blank" rel="noopener noreferrer">ksi.uconn.edu →</a>
        </div>
      </div>

      {openStadium && <DetailModal stadium={openStadium} initialMetric={metric} initialUnit={unit} onClose={close} />}

      <TweaksPanel title="Tweaks">
        <TweakSection label="Display">
          <TweakRadio label="Metric" value={metric}
            options={[{ value: 'wbgt', label: 'WBGT' }, { value: 'heatIndex', label: 'Heat Index' }, { value: 'ambient', label: 'Ambient' }]}
            onChange={(v) => setTweak('metric', v)} />
          <TweakRadio label="Units" value={unit}
            options={[{ value: 'F', label: '°F' }, { value: 'C', label: '°C' }]}
            onChange={(v) => setTweak('unit', v)} />
        </TweakSection>
        <TweakSection label="Layout">
          <TweakRadio label="Organize" value={sort}
            options={[{ value: 'country', label: 'By country' }, { value: 'risk', label: 'By risk' }]}
            onChange={(v) => setTweak('sort', v)} />
        </TweakSection>
      </TweaksPanel>
    </div>
  );
}

(window.DATA_READY || window.HISTORY_READY || Promise.resolve()).then(() => {
  ReactDOM.createRoot(document.getElementById('root')).render(<Dashboard />);
});
