/* WorldCup 2026 — app (React vía Babel). Datos reales de ESPN (/api/wc/*) y feature
   de audio cableada al motor real (window.WCAudioEngine). Responsive iPhone + iPad. */
const { useState, useEffect, useRef, useMemo, useCallback } = React;
const ENG = window.WCAudioEngine;

/* ---------------- API ---------------- */
async function api(path) {
  const r = await fetch(path);
  if (r.status === 401) { location.href = '/login.html'; throw new Error('401'); }
  if (!r.ok) throw new Error('HTTP ' + r.status);
  return r.json();
}
async function apiSend(path, method, body) {
  const r = await fetch(path, {
    method,
    headers: body ? { 'Content-Type': 'application/json' } : {},
    body: body ? JSON.stringify(body) : undefined,
  });
  if (r.status === 401) { location.href = '/login.html'; throw new Error('401'); }
  return r.json().catch(() => ({}));
}

/* parsea una URL Xtream completa (get.php / live/USER/PASS/123.ts / m3u_plus) a campos */
function parseXtreamUrl(str) {
  str = (str || '').trim();
  if (!/^https?:\/\//i.test(str)) return null;
  let u;
  try { u = new URL(str); } catch { return null; }
  const out = {
    host: `${u.protocol}//${u.hostname}`,
    port: u.port || '',
    user: u.searchParams.get('username') || '',
    pass: u.searchParams.get('password') || '',
  };
  if (!out.user || !out.pass) {
    const m = u.pathname.match(/\/(?:live\/)?([^/]+)\/([^/]+)\/\d+\.(?:ts|m3u8)/);
    if (m) { out.user = decodeURIComponent(m[1]); out.pass = decodeURIComponent(m[2]); }
  }
  return out;
}

/* ---------------- helpers ---------------- */
function fmtOffset(o) {
  if (o == null || Math.abs(o) < 0.05) return { text: 'EN VIVO', live: true };
  const sign = o < 0 ? '−' : '+';
  return { text: sign + Math.abs(o).toFixed(1) + 's', live: false };
}
const shortCode = (name) => (name || '').replace(/[^A-Za-zÀ-ÿ0-9 ]/g, '').trim().slice(0, 3).toUpperCase() || '📡';
// "90+5" -> { base:90, extra:5 } para ordenar bien el descuento (parseInt los empata en 90)
function minParts(s) {
  const m = String(s || '').match(/^(\d+)(?:\s*\+\s*(\d+))?/);
  return { base: m ? +m[1] : 0, extra: m && m[2] ? +m[2] : 0 };
}
const ymd = (d) => `${d.getFullYear()}${String(d.getMonth() + 1).padStart(2, '0')}${String(d.getDate()).padStart(2, '0')}`;

/* coordenadas de cancha a partir de la formación (GK + líneas) */
function pitchCoords(formationStr, n) {
  const lines = String(formationStr || '').split('-').map((x) => parseInt(x, 10)).filter((x) => x > 0);
  const res = [{ x: 50, y: 92 }]; // arquero
  const tl = lines.length;
  lines.forEach((count, li) => {
    const y = tl > 1 ? 76 - li * (58 / (tl - 1)) : 46;
    for (let i = 0; i < count; i++) {
      const x = count === 1 ? 50 : 14 + i * (72 / (count - 1));
      res.push({ x, y });
    }
  });
  if (res.length !== n) { // formación no cuadra: grilla simple
    return Array.from({ length: n }, (_, i) => ({ x: 12 + (i % 4) * 25, y: 86 - Math.floor(i / 4) * 16 }));
  }
  return res;
}

/* ---------------- iconos ---------------- */
const HeadIcon = ({ s = 18 }) => (
  <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <path d="M4 14v-2a8 8 0 0 1 16 0v2"></path><rect x="2.5" y="13.5" width="4.5" height="7" rx="2"></rect><rect x="17" y="13.5" width="4.5" height="7" rx="2"></rect>
  </svg>
);
const TvIcon = ({ s = 16 }) => (
  <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <rect x="2.5" y="7" width="19" height="13" rx="2"></rect><path d="M8 3l4 4 4-4"></path>
  </svg>
);
const LiveIcon = ({ s = 16 }) => (
  <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <path d="M5 8a9 9 0 0 0 0 8M19 8a9 9 0 0 1 0 8M8.5 11a4 4 0 0 0 0 2M15.5 11a4 4 0 0 1 0 2"></path><circle cx="12" cy="12" r="1.6" fill="currentColor" stroke="none"></circle>
  </svg>
);
const StarIcon = ({ filled, s = 16 }) => (
  <svg width={s} height={s} viewBox="0 0 24 24" fill={filled ? 'currentColor' : 'none'} stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <polygon points="12 2 15.1 8.6 22 9.3 17 14.1 18.2 21 12 17.6 5.8 21 7 14.1 2 9.3 8.9 8.6 12 2"></polygon>
  </svg>
);
const DbIcon = ({ s = 19 }) => (
  <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <ellipse cx="12" cy="5" rx="8" ry="3"></ellipse><path d="M4 5v6c0 1.7 3.6 3 8 3s8-1.3 8-3V5"></path><path d="M4 11v6c0 1.7 3.6 3 8 3s8-1.3 8-3v-6"></path>
  </svg>
);
const PlayGlyph = ({ playing }) => playing ? <span className="glyph-pause"><i></i><i></i></span> : <span className="glyph-play"></span>;
const Eq = () => <span className="eq"><i></i><i></i><i></i></span>;
const TeamLogo = ({ src, size = 40 }) => src
  ? <img className="team-logo" src={src} alt="" width={size} height={size} onError={(e) => (e.target.style.visibility = 'hidden')} />
  : <span className="team-logo" style={{ width: size, height: size, display: 'inline-block' }} />;

/* ============================================================ ENGINE HOOK */
function useEngine() {
  const [st, setSt] = useState(() => ENG.getState());
  useEffect(() => ENG.subscribe(setSt), []);
  return st;
}

/* ============================================================ AUDIO FEATURE */
function EscucharButton({ active, playing, onOpen }) {
  return (
    <button className={'btn-icon btn-listen-ico ' + (active ? 'is-active' : '')} onClick={onOpen} title={active ? 'Escuchando' : 'Escuchar el relato'}>
      {active && playing ? <Eq /> : <HeadIcon s={18} />}
    </button>
  );
}
function WatchButton({ onOpen }) {
  return (
    <button className="btn-icon" onClick={onOpen} title="Ver el partido en pantalla completa">
      <TvIcon s={19} />
    </button>
  );
}

/* tarjeta de canal (con estrella de favorito y subtítulo opcional) */
function ChannelCard({ c, fav, onPick, onToggleFav, sub }) {
  return (
    <div className="bcast-card" onClick={() => onPick(c)}>
      {c.icon ? <img className="team-logo" src={c.icon} width={40} height={40} alt="" onError={(e) => (e.target.style.visibility = 'hidden')} />
        : <span className="bcast-logo" style={{ width: 40, height: 40, background: '#1B3BFF', color: '#fff', fontSize: 13 }}>{shortCode(c.name)}</span>}
      <div className="bcast-info"><b>{c.name}</b>{sub ? <span>{sub}</span> : null}</div>
      <button className={'bcast-fav ' + (fav ? 'on' : '')} title="Favorito" onClick={(e) => { e.stopPropagation(); onToggleFav(c); }}><StarIcon filled={fav} /></button>
      <span className="bcast-go">▶</span>
    </div>
  );
}

/* hoja (a): elegir transmisión por CATEGORÍA (recomendados → favoritos → categorías → canales) */
function ChooseSheet({ onPick, onClose, matchId, matchLabel }) {
  const [cats, setCats] = useState(null);
  const [cat, setCat] = useState(null);   // categoría abierta (null = lista de categorías)
  const [channels, setChannels] = useState(null);
  const [recs, setRecs] = useState(matchId ? null : []);
  const [q, setQ] = useState('');
  const [favs, setFavs] = useState(() => { try { return JSON.parse(localStorage.getItem('wc_favs') || '{}'); } catch { return {}; } });

  useEffect(() => { api('/api/categories').then(setCats).catch(() => setCats([])); }, []);
  useEffect(() => {
    if (!matchId) return;
    api('/api/wc/recommend?match=' + matchId).then((d) => setRecs(d.recs || [])).catch(() => setRecs([]));
  }, [matchId]);

  const openCat = (c) => {
    setCat(c); setChannels(null); setQ('');
    api('/api/streams?category_id=' + encodeURIComponent(c.category_id)).then(setChannels).catch(() => setChannels([]));
  };
  const backToCats = () => { setCat(null); setChannels(null); setQ(''); };

  const toggleFav = (c) => setFavs((prev) => {
    const next = { ...prev }; const id = String(c.stream_id);
    if (next[id]) delete next[id]; else next[id] = { stream_id: c.stream_id, name: c.name, icon: c.icon || '', category_id: c.category_id };
    try { localStorage.setItem('wc_favs', JSON.stringify(next)); } catch {}
    return next;
  });

  const term = q.trim().toLowerCase();
  const favList = Object.values(favs);

  // vista de canales (dentro de una categoría)
  if (cat) {
    const list = (channels || []).filter((c) => !term || (c.name || '').toLowerCase().includes(term));
    return (
      <div className="audio-scrim" onClick={onClose}>
        <div className="audio-sheet sheet-choose" onClick={(e) => e.stopPropagation()}>
          <div className="sheet-head">
            <div className="sheet-head-text">
              <b><button className="sheet-back" onClick={backToCats}>‹</button> {cat.category_name}</b>
              <span>Elegí el canal del relato — se sincroniza a mano con tu TV</span>
            </div>
            <button className="sheet-x" onClick={onClose}>✕</button>
          </div>
          <input className="bcast-search" placeholder="Buscar dentro de esta categoría…" value={q} onChange={(e) => setQ(e.target.value)} autoFocus />
          {channels == null ? <div className="wc-loading">Cargando canales…</div> : (
            <div className="bcast-grid channels">
              {list.map((c) => <ChannelCard key={c.stream_id} c={c} fav={!!favs[String(c.stream_id)]} onPick={onPick} onToggleFav={toggleFav} />)}
              {!list.length && <div className="wc-empty">Sin canales en esta categoría.</div>}
            </div>
          )}
          <div className="sheet-foot"><span className="bt-dot"></span> Salida por Bluetooth · escuchá sin gastar datos de video</div>
        </div>
      </div>
    );
  }

  // vista de categorías (+ favoritos arriba)
  const catList = (cats || []).filter((c) => !term || (c.category_name || '').toLowerCase().includes(term));
  const favShown = favList.filter((c) => !term || (c.name || '').toLowerCase().includes(term));
  return (
    <div className="audio-scrim" onClick={onClose}>
      <div className="audio-sheet sheet-choose" onClick={(e) => e.stopPropagation()}>
        <div className="sheet-head">
          <div className="sheet-head-text">
            <b><HeadIcon s={20} /> ESCUCHAR EL PARTIDO</b>
            <span>Elegí la categoría del relato — después el canal</span>
          </div>
          <button className="sheet-x" onClick={onClose}>✕</button>
        </div>
        <input className="bcast-search" placeholder="Buscar categoría o favorito…" value={q} onChange={(e) => setQ(e.target.value)} autoFocus />
        {cats == null ? <div className="wc-loading">Cargando categorías…</div> : (
          <div className="bcast-grid channels">
            {!term && matchId ? (
              recs == null ? <div className="cat-section">⭐ RECOMENDADOS · buscando transmisiones…</div> : recs.length ? (
                <React.Fragment>
                  <div className="cat-section">⭐ RECOMENDADOS{matchLabel ? ' · ' + matchLabel : ''}</div>
                  {recs.map((c) => <ChannelCard key={'r' + c.stream_id} c={c} fav={!!favs[String(c.stream_id)]} onPick={onPick} onToggleFav={toggleFav} sub={c.label} />)}
                  <div className="cat-section">EXPLORAR</div>
                </React.Fragment>
              ) : null
            ) : null}
            {favShown.length ? (
              <React.Fragment>
                <div className="cat-section">★ FAVORITOS</div>
                {favShown.map((c) => <ChannelCard key={'f' + c.stream_id} c={c} fav onPick={onPick} onToggleFav={toggleFav} />)}
                <div className="cat-section">CATEGORÍAS</div>
              </React.Fragment>
            ) : null}
            {catList.map((c) => (
              <button className="bcast-card cat-card" key={c.category_id || 'all'} onClick={() => openCat(c)}>
                <span className="bcast-logo cat-folder">📁</span>
                <div className="bcast-info"><b>{c.category_name}</b></div>
                <span className="bcast-go">›</span>
              </button>
            ))}
            {!catList.length && !favShown.length && <div className="wc-empty">Sin categorías. Agregá/activá una cuenta IPTV con 🗄️.</div>}
          </div>
        )}
        <div className="sheet-foot"><span className="bt-dot"></span> Salida por Bluetooth · escuchá sin gastar datos de video</div>
      </div>
    </div>
  );
}

/* video real del motor: montamos el <video id="wc-video"> dentro del frame */
function VideoFrame({ st, onToggle }) {
  const ref = useRef(null);
  useEffect(() => {
    const v = document.getElementById('wc-video');
    const holder = document.getElementById('video-holder');
    if (v && ref.current) { ref.current.appendChild(v); v.style.display = 'block'; }
    return () => { if (v && holder) { holder.appendChild(v); v.style.display = 'none'; } };
  }, []);
  return (
    <div className="vid-frame" ref={ref} onClick={onToggle}>
      <div className="vid-live"><span className="vid-live-dot"></span>EN DIRECTO</div>
      <div className="vid-src">{st.name ? st.name.toUpperCase() : ''}</div>
      {!st.playing && <div className="vid-play-overlay"><span className="vid-play-big"></span></div>}
    </div>
  );
}

/* panel de diagnóstico read-only (para entender la deriva del audio) */
function DiagPanel() {
  const [d, setD] = useState(() => ENG.getDiag());
  const peak = useRef({ lag: 0, behind: 0 });
  const [copied, setCopied] = useState(false);
  useEffect(() => {
    const t = setInterval(() => {
      const g = ENG.getDiag();
      if (g.realLagS != null) peak.current.lag = Math.max(peak.current.lag, g.realLagS);
      if (g.behindEdge != null) peak.current.behind = Math.max(peak.current.behind, g.behindEdge);
      setD(g);
    }, 500);
    return () => clearInterval(t);
  }, []);
  const copy = () => {
    const payload = { ...d, peakLagS: +peak.current.lag.toFixed(1), peakBehindS: +peak.current.behind.toFixed(1), ua: navigator.userAgent, at: new Date().toISOString() };
    const text = 'WC sync diag\n' + JSON.stringify(payload, null, 2);
    const ok = () => { setCopied(true); setTimeout(() => setCopied(false), 1500); };
    if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(text).then(ok).catch(() => window.prompt('Copiá el diagnóstico:', text));
    else window.prompt('Copiá el diagnóstico:', text);
  };
  const Row = ({ k, v, hot }) => <div className="diag-row"><span>{k}</span><b className={hot ? 'diag-hot' : ''}>{v}</b></div>;
  return (
    <div className="diag-panel">
      <Row k="Modo" v={d.mode} />
      <Row k="Estado" v={d.playing ? 'reproduciendo' : 'PAUSA'} hot={!d.playing} />
      <Row k="Detrás del borde" v={d.behindEdge != null ? d.behindEdge + ' s' : '—'} />
      <Row k="Objetivo (borde)" v={d.targetBehindS != null ? d.targetBehindS + ' s' : '—'} />
      <Row k="Catch-up" v={d.catchup ? (d.rate > 1 ? 'acelerando ' : 'frenando ') + (d.rate ?? '') + '×' : (d.rate === 1 ? 'en objetivo' : (d.rate ?? '—') + '×')} hot={d.catchup} />
      <Row k="Atraso vs reloj" v={d.realLagS != null ? d.realLagS + ' s' : '—'} />
      <Row k="Rebuffers" v={d.stalls + ' · ' + d.stallS + ' s'} hot={d.stalls > 0} />
      <Row k="Buffer adelante" v={d.bufAheadS != null ? d.bufAheadS + ' s' : '—'} hot={d.bufAheadS != null && d.bufAheadS < 1} />
      <Row k="DVR" v={(d.dvr ?? '—') + ' s'} />
      <Row k="Sonando hace" v={d.upS != null ? d.upS + ' s' : '—'} />
      <button className="diag-copy" onClick={copy}>{copied ? '✓ Copiado' : '📋 Copiar diagnóstico'}</button>
    </div>
  );
}

/* hoja (b): sincronizar con video */
function SyncSheet({ st, onClose }) {
  const off = fmtOffset(st.offset);
  const [diag, setDiag] = useState(false);
  const JUMPS_L = [-5, -1, -0.5], JUMPS_R = [0.5, 1, 5];
  const lbl = (j) => (j > 0 ? '+' : '−') + Math.abs(j) + 's';
  return (
    <div className="audio-scrim" onClick={onClose}>
      <div className="audio-sheet sheet-sync" onClick={(e) => e.stopPropagation()}>
        <div className="sheet-head">
          <div className="sheet-head-text">
            <b><HeadIcon s={20} /> {st.name ? st.name.toUpperCase() : 'RELATO'}</b>
            <span>Calzá el minuto con lo que ves en tu TV, después pasá a solo audio</span>
          </div>
          <button className="sheet-x" onClick={onClose}>✕</button>
        </div>
        <div className="sync-body">
          <VideoFrame st={st} onToggle={() => ENG.toggle()} />
          <div className="sync-controls-col">
            <div className="offset">
              <div className={'offset-num ' + (off.live ? 'live' : '')}>{off.text}</div>
              <div className="offset-label">DESFASE CON TU TV</div>
            </div>
            <div className="jumps">
              {JUMPS_L.map((j) => <button key={j} className="jump-btn" onClick={() => ENG.jump(j)}>{lbl(j)}</button>)}
              <button className="play-big" onClick={() => ENG.toggle()}><PlayGlyph playing={st.playing} /></button>
              {JUMPS_R.map((j) => <button key={j} className="jump-btn" onClick={() => ENG.jump(j)}>{lbl(j)}</button>)}
            </div>
            <div className="sync-actions">
              <button className="btn-ghost" onClick={() => ENG.goLive()}><LiveIcon /> IR A VIVO</button>
              <button className="btn-solo" onClick={() => ENG.soloAudio()}><HeadIcon s={18} /> SOLO AUDIO</button>
            </div>
            {st.status ? <div style={{ textAlign: 'center', fontSize: 12, color: 'rgba(255,255,255,.5)' }}>{st.status}</div> : null}
            <button className="diag-toggle" onClick={() => setDiag((v) => !v)}>{diag ? '▾' : '▸'} Diagnóstico de sincronía</button>
            {diag && <DiagPanel />}
          </div>
        </div>
      </div>
    </div>
  );
}

/* (c) barra compacta solo-audio */
function AudioBar({ st, onSync, onMinimize }) {
  const off = fmtOffset(st.offset);
  const [diag, setDiag] = useState(false);
  return (
    <React.Fragment>
    {diag && <div className="diag-float"><DiagPanel /></div>}
    <div className="audio-bar">
      <div className="ab-id">
        <span className="bcast-logo" style={{ width: 40, height: 40, background: '#1B3BFF', color: '#fff', fontSize: 13 }}>{shortCode(st.name)}</span>
        <div className="ab-id-text"><b>{st.name}</b><span>{st.playing ? <Eq /> : null} SOLO AUDIO</span></div>
      </div>
      <div className="ab-controls">
        <button className="jump-btn jb-sm" onClick={() => ENG.jump(-1)}>−1s</button>
        <button className="jump-btn jb-sm" onClick={() => ENG.jump(-0.5)}>−.5</button>
        <button className="play-mid" onClick={() => ENG.toggle()}><PlayGlyph playing={st.playing} /></button>
        <button className="jump-btn jb-sm" onClick={() => ENG.jump(0.5)}>+.5</button>
        <button className="jump-btn jb-sm" onClick={() => ENG.jump(1)}>+1s</button>
        <div className={'ab-offset ' + (off.live ? 'live' : '')}>{off.text}</div>
      </div>
      <div className="ab-actions">
        <button className="btn-ghost btn-ghost-sm" onClick={() => ENG.goLive()}><LiveIcon s={15} /> VIVO</button>
        <button className="btn-ghost btn-ghost-sm" onClick={onSync}><TvIcon s={15} /> SINCRONIZAR</button>
        <button className={'ab-min' + (diag ? ' on' : '')} onClick={() => setDiag((v) => !v)} title="Diagnóstico">🔧</button>
        <button className="ab-min" onClick={onMinimize} title="Minimizar">⌄</button>
      </div>
    </div>
    </React.Fragment>
  );
}

function MiniPlayer({ st, onExpand }) {
  const off = fmtOffset(st.offset);
  return (
    <div className="audio-mini" onClick={onExpand}>
      <span className="mini-head"><HeadIcon s={16} /></span>
      <span className="bcast-logo" style={{ width: 30, height: 30, background: '#1B3BFF', color: '#fff', fontSize: 10 }}>{shortCode(st.name)}</span>
      <div className="mini-name"><b>{st.name}</b><span>{st.playing ? <Eq /> : null} SOLO AUDIO</span></div>
      <div className={'mini-offset ' + (off.live ? 'live' : '')}>{off.text}</div>
      <button className="mini-play" onClick={(e) => { e.stopPropagation(); ENG.toggle(); }}><PlayGlyph playing={st.playing} /></button>
      <button className="mini-up" onClick={onExpand} title="Abrir">⌃</button>
      <button className="mini-x" onClick={(e) => { e.stopPropagation(); ENG.stop(); }} title="Detener">✕</button>
    </div>
  );
}

const FsIcon = ({ on, s = 18 }) => (
  <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    {on
      ? <g><path d="M9 3v3a2 2 0 0 1-2 2H4M15 3v3a2 2 0 0 0 2 2h3M9 21v-3a2 2 0 0 0-2-2H4M15 21v-3a2 2 0 0 1 2-2h3" /></g>
      : <g><path d="M4 9V5a2 2 0 0 1 2-2h4M20 9V5a2 2 0 0 0-2-2h-4M4 15v4a2 2 0 0 0 2 2h4M20 15v4a2 2 0 0 1-2 2h-4" /></g>}
  </svg>
);

/* modo TV: ver el partido en pantalla completa (video+audio en vivo) */
function WatchSheet({ st, onClose, onSync }) {
  const ref = useRef(null);       // contenedor del <video>
  const rootRef = useRef(null);   // overlay (objetivo de pantalla completa)
  const [ui, setUi] = useState(true);
  const [fs, setFs] = useState(false);
  useEffect(() => {
    const v = document.getElementById('wc-video');
    const holder = document.getElementById('video-holder');
    if (v && ref.current) { ref.current.appendChild(v); v.style.display = 'block'; }
    if (st.mode !== 'av') ENG.syncVideo(); // asegurar que haya video
    const onFs = () => setFs(!!(document.fullscreenElement || document.webkitFullscreenElement));
    document.addEventListener('fullscreenchange', onFs);
    document.addEventListener('webkitfullscreenchange', onFs);
    return () => {
      document.removeEventListener('fullscreenchange', onFs);
      document.removeEventListener('webkitfullscreenchange', onFs);
      try { if (document.fullscreenElement || document.webkitFullscreenElement) (document.exitFullscreen || document.webkitExitFullscreen || document.webkitCancelFullScreen).call(document); } catch {}
      if (v && holder) { holder.appendChild(v); v.style.display = 'none'; }
    };
  }, []);
  const toggleFs = (e) => {
    e.stopPropagation();
    const d = document, node = rootRef.current, v = document.getElementById('wc-video');
    if (d.fullscreenElement || d.webkitFullscreenElement) {
      (d.exitFullscreen || d.webkitExitFullscreen || d.webkitCancelFullScreen).call(d);
    } else if (node && (node.requestFullscreen || node.webkitRequestFullscreen)) {
      (node.requestFullscreen || node.webkitRequestFullscreen).call(node);
    } else if (v && v.webkitEnterFullscreen) {
      v.webkitEnterFullscreen(); // iPhone: pantalla completa nativa del video
    }
  };
  return (
    <div className="watch-full" ref={rootRef}>
      <div className="watch-video" ref={ref} onClick={() => setUi((u) => !u)}>
        {!st.playing && <div className="vid-play-overlay"><span className="vid-play-big"></span></div>}
      </div>
      {ui && (
        <React.Fragment>
          <div className="watch-top">
            <span className="watch-name">{st.name}</span>
            <div className="watch-top-btns">
              <button className="sheet-x" onClick={toggleFs} title="Pantalla completa"><FsIcon on={fs} /></button>
              <button className="sheet-x" onClick={onClose} title="Cerrar">✕</button>
            </div>
          </div>
          {!fs && (
            <div className="watch-bottom">
              <button className="play-mid" onClick={(e) => { e.stopPropagation(); ENG.toggle(); }}><PlayGlyph playing={st.playing} /></button>
              <button className="btn-ghost btn-ghost-sm" onClick={() => ENG.goLive()}><LiveIcon s={15} /> EN VIVO</button>
              <button className="btn-ghost btn-ghost-sm" onClick={onSync}><HeadIcon s={15} /> RELATO</button>
            </div>
          )}
        </React.Fragment>
      )}
    </div>
  );
}

/* orquestador del dock de audio (sheet state) */
function AudioDock({ sheet, setSheet, matchId, matchLabel }) {
  const st = useEngine();
  const active = st.active;
  // si el audio se detiene desde afuera, cerrar
  useEffect(() => { if (!active && sheet !== 'choose' && sheet !== 'wchoose' && sheet !== 'closed') setSheet('closed'); }, [active]);

  return (
    <React.Fragment>
      {(sheet === 'choose' || sheet === 'wchoose') && (
        <ChooseSheet
          matchId={matchId} matchLabel={matchLabel}
          onPick={(c) => { ENG.open(String(c.stream_id), c.name); setSheet(sheet === 'wchoose' ? 'watch' : 'sync'); }}
          onClose={() => setSheet('closed')} />
      )}
      {sheet === 'sync' && active && <SyncSheet st={st} onClose={() => setSheet('closed')} />}
      {sheet === 'watch' && active && <WatchSheet st={st} onClose={() => setSheet('closed')} onSync={() => { ENG.syncVideo(); setSheet('sync'); }} />}
      {sheet === 'audio' && active && <AudioBar st={st} onSync={() => { ENG.syncVideo(); setSheet('sync'); }} onMinimize={() => setSheet('closed')} />}
      {active && sheet === 'closed' && <MiniPlayer st={st} onExpand={() => setSheet('audio')} />}
    </React.Fragment>
  );
}

/* ============================================================ CUENTAS IPTV */
const BLANK_ACC = { label: '', host: '', port: '', user: '', pass: '' };

function AccountForm({ initial, editingId, onSaved, onCancel }) {
  const [f, setF] = useState(initial || BLANK_ACC);
  const [probe, setProbe] = useState(null);  // {testing} | {ok,...}
  const [busy, setBusy] = useState(false);
  const set = (k) => (e) => setF((p) => ({ ...p, [k]: e.target.value }));

  const onPaste = (e) => {
    const parsed = parseXtreamUrl(e.target.value);
    if (parsed) setF((p) => ({ ...p, ...parsed, label: p.label || '' }));
  };
  const test = async () => {
    setProbe({ testing: true });
    try { setProbe(await apiSend('/api/accounts/test-draft', 'POST', f)); } catch { setProbe({ ok: false, error: 'Error de red' }); }
  };
  const save = async () => {
    if (!f.host || !f.user || !f.pass) { setProbe({ ok: false, error: 'Faltan host, usuario o contraseña' }); return; }
    setBusy(true);
    try {
      if (editingId) await apiSend('/api/accounts/' + editingId, 'PUT', f);
      else await apiSend('/api/accounts', 'POST', f);
      onSaved();
    } finally { setBusy(false); }
  };

  return (
    <div className="acc-form">
      <label className="acc-field acc-field-full">
        <span>Pegar URL / M3U (opcional)</span>
        <input className="acc-input" placeholder="http://servidor:puerto/get.php?username=…&password=…" onChange={onPaste} />
      </label>
      <label className="acc-field acc-field-full"><span>Nombre</span><input className="acc-input" value={f.label} onChange={set('label')} placeholder="Ej. Servidor principal" /></label>
      <label className="acc-field"><span>Host</span><input className="acc-input" value={f.host} onChange={set('host')} placeholder="http://servidor.com" /></label>
      <label className="acc-field"><span>Puerto</span><input className="acc-input" value={f.port} onChange={set('port')} placeholder="8080" /></label>
      <label className="acc-field"><span>Usuario</span><input className="acc-input" value={f.user} onChange={set('user')} autoCapitalize="none" /></label>
      <label className="acc-field"><span>Contraseña</span><input className="acc-input" value={f.pass} onChange={set('pass')} placeholder={editingId ? '(sin cambios)' : ''} /></label>
      {probe && !probe.testing ? (
        <div className={'acc-probe ' + (probe.ok ? 'ok' : 'bad')}>
          {probe.ok ? `✓ Conecta · ${probe.status || 'activo'}${probe.expDate ? ' · vence ' + new Date(probe.expDate * 1000).toLocaleDateString('es') : ''}${probe.maxConnections ? ' · ' + (probe.activeConnections || 0) + '/' + probe.maxConnections + ' con.' : ''}` : '✕ ' + (probe.error || 'No conecta')}
        </div>
      ) : probe?.testing ? <div className="acc-probe">Probando…</div> : null}
      <div className="acc-form-actions">
        <button className="btn-ghost btn-ghost-sm" onClick={onCancel}>Cancelar</button>
        <button className="btn-ghost btn-ghost-sm" onClick={test}>Probar</button>
        <button className="btn-solo" onClick={save} disabled={busy}>{busy ? 'Guardando…' : 'Guardar'}</button>
      </div>
    </div>
  );
}

function AccountsSheet({ onClose }) {
  const [accs, setAccs] = useState(null);
  const [activeId, setActiveId] = useState(null);
  const [edit, setEdit] = useState(null);    // null=lista | {id?,...}=formulario
  const [tests, setTests] = useState({});    // id -> probe result

  const reload = useCallback(() => api('/api/accounts').then((d) => { setAccs(d.accounts || []); setActiveId(d.activeId); }).catch(() => setAccs([])), []);
  useEffect(() => { reload(); }, [reload]);

  const activate = async (id) => { await apiSend('/api/accounts/' + id + '/activate', 'POST'); reload(); };
  const remove = async (id) => { if (!confirm('¿Borrar esta cuenta?')) return; await apiSend('/api/accounts/' + id, 'DELETE'); reload(); };
  const test = async (id) => {
    setTests((t) => ({ ...t, [id]: { testing: true } }));
    const r = await apiSend('/api/accounts/' + id + '/test', 'POST');
    setTests((t) => ({ ...t, [id]: r }));
  };

  return (
    <div className="audio-scrim" onClick={onClose}>
      <div className="audio-sheet sheet-choose" onClick={(e) => e.stopPropagation()}>
        <div className="sheet-head">
          <div className="sheet-head-text">
            <b>{edit ? <button className="sheet-back" onClick={() => setEdit(null)}>‹</button> : <DbIcon s={20} />} {edit ? (edit.id ? 'EDITAR CUENTA' : 'NUEVA CUENTA') : 'CUENTAS IPTV'}</b>
            <span>{edit ? 'Pegá la URL del proveedor o completá los campos' : 'La cuenta activa alimenta los canales del relato'}</span>
          </div>
          <button className="sheet-x" onClick={onClose}>✕</button>
        </div>

        {edit ? (
          <AccountForm initial={edit.id ? edit : BLANK_ACC} editingId={edit.id} onCancel={() => setEdit(null)} onSaved={() => { setEdit(null); reload(); }} />
        ) : accs == null ? <div className="wc-loading">Cargando cuentas…</div> : (
          <React.Fragment>
            <div className="acc-list">
              {accs.map((a) => {
                const t = tests[a.id];
                return (
                  <div className={'acc-card ' + (a.active ? 'active' : '')} key={a.id}>
                    <div className="acc-main">
                      <div className="acc-name">{a.label} {a.active && <span className="acc-badge">ACTIVA</span>}</div>
                      <div className="acc-sub">{a.host}{a.port ? ':' + a.port : ''} · {a.user}</div>
                      {t && !t.testing ? <div className={'acc-probe ' + (t.ok ? 'ok' : 'bad')}>{t.ok ? `✓ Conecta${t.expDate ? ' · vence ' + new Date(t.expDate * 1000).toLocaleDateString('es') : ''}` : '✕ ' + (t.error || 'No conecta')}</div> : t?.testing ? <div className="acc-probe">Probando…</div> : null}
                    </div>
                    <div className="acc-actions">
                      {!a.active && <button className="acc-btn acc-btn-go" onClick={() => activate(a.id)}>Activar</button>}
                      <button className="acc-btn" onClick={() => test(a.id)}>Probar</button>
                      <button className="acc-btn" onClick={() => setEdit({ ...a, pass: '' })}>Editar</button>
                      <button className="acc-btn acc-btn-del" onClick={() => remove(a.id)}>Borrar</button>
                    </div>
                  </div>
                );
              })}
              {!accs.length && <div className="wc-empty">Todavía no hay cuentas. Agregá una abajo.</div>}
            </div>
            <button className="btn-solo acc-add" onClick={() => setEdit({})}>+ Agregar cuenta</button>
          </React.Fragment>
        )}
      </div>
    </div>
  );
}

function AccountsButton() {
  const [open, setOpen] = useState(false);
  return (
    <React.Fragment>
      <button className="btn-icon" title="Cuentas IPTV" onClick={() => setOpen(true)}><DbIcon /></button>
      {open && <AccountsSheet onClose={() => setOpen(false)} />}
    </React.Fragment>
  );
}

/* ============================================================ DATOS DE PARTIDO */
function EventIcon({ type }) {
  if (type === 'goal') return <span className="ev-ico ev-goal"><span className="ev-goal-inner"></span></span>;
  if (type === 'yellow') return <span className="ev-ico"><span className="card-shape" style={{ background: '#FFE600' }}></span></span>;
  if (type === 'red') return <span className="ev-ico"><span className="card-shape" style={{ background: '#FF2D2D' }}></span></span>;
  return <span className="ev-ico ev-sub">⇄</span>;
}
const EVENT_LABEL = { goal: 'GOL', yellow: 'AMARILLA', red: 'ROJA', sub: 'CAMBIO' };

function Resumen({ m }) {
  // bloque de minuto más reciente arriba; dentro del descuento, 90+1 antes que 90+5
  const evs = [...(m.events || [])].sort((a, b) => {
    const A = minParts(a.min), B = minParts(b.min);
    return B.base - A.base || A.extra - B.extra;
  });
  return (
    <div className="panel-grid">
      <div className="panel timeline-panel">
        <div className="panel-head">CRONOLOGÍA</div>
        <div className="timeline">
          {evs.length ? evs.map((e, i) => (
            <div className={'ev-row ' + (e.side === 'away' ? 'ev-away' : 'ev-home')} key={i}>
              <div className="ev-content">
                <EventIcon type={e.type} />
                <div className="ev-text">
                  <div className="ev-player">{e.player} <span className="ev-tag">{EVENT_LABEL[e.type]}</span></div>
                  {e.detail ? <div className="ev-detail">{e.detail}</div> : null}
                </div>
              </div>
              <div className="ev-min">{e.min}'</div>
              <div className="ev-spacer"></div>
            </div>
          )) : <div className="wc-empty">Sin eventos todavía.</div>}
          <div className="ev-row ev-center"><div className="ev-kickoff">INICIO DEL PARTIDO</div></div>
        </div>
      </div>
      <div className="panel side-panel">
        <div className="panel-head">DATOS DEL PARTIDO</div>
        <div className="meta-list">
          {m.meta?.venue && <div className="meta-row"><span>ESTADIO</span><b>{m.meta.venue}</b></div>}
          {m.meta?.city && <div className="meta-row"><span>CIUDAD</span><b>{m.meta.city}</b></div>}
          {m.meta?.referee && <div className="meta-row"><span>ÁRBITRO</span><b>{m.meta.referee}</b></div>}
          {m.meta?.attendance && <div className="meta-row"><span>ASISTENCIA</span><b>{m.meta.attendance}</b></div>}
          <div className="meta-row"><span>ESTADO</span><b>{m.status.detail || '—'}</b></div>
        </div>
        {m.stats && m.stats.length ? (
          <React.Fragment>
            <div className="panel-head" style={{ marginTop: 22 }}>ESTADÍSTICAS CLAVE</div>
            <div className="ministats">{m.stats.slice(0, 3).map((s, i) => <StatBar key={i} stat={s} compact />)}</div>
          </React.Fragment>
        ) : null}
      </div>
    </div>
  );
}

function StatBar({ stat, compact }) {
  const total = stat.home + stat.away || 1;
  const hp = (stat.home / total) * 100;
  const homeWins = stat.home >= stat.away;
  return (
    <div className={'stat ' + (compact ? 'stat-compact' : '')}>
      <div className="stat-nums">
        <b className={homeWins ? 'stat-lead' : ''}>{stat.home}</b>
        <span className="stat-label">{stat.label}</span>
        <b className={!homeWins ? 'stat-lead' : ''}>{stat.away}</b>
      </div>
      <div className="stat-track">
        <div className="stat-fill stat-fill-home" style={{ width: hp + '%' }}></div>
        <div className="stat-fill stat-fill-away" style={{ width: (100 - hp) + '%' }}></div>
      </div>
    </div>
  );
}

function Estadisticas({ m }) {
  if (!m.stats || !m.stats.length) return <div className="panel"><div className="wc-empty">Estadísticas no disponibles aún.</div></div>;
  return (
    <div className="stats-wrap panel">
      <div className="stats-teams">
        <div className="stats-team"><TeamLogo src={m.home.logo} size={28} /><b>{m.home.abbr}</b></div>
        <div className="panel-head" style={{ margin: 0 }}>ESTADÍSTICAS</div>
        <div className="stats-team stats-team-r"><b>{m.away.abbr}</b><TeamLogo src={m.away.logo} size={28} /></div>
      </div>
      <div className="stats-grid">{m.stats.map((s, i) => <StatBar key={i} stat={s} />)}</div>
    </div>
  );
}

function Pitch({ team, lineup, color, onPlayer }) {
  if (!lineup || !lineup.starters?.length) return null;
  const coords = pitchCoords(lineup.formation, lineup.starters.length);
  return (
    <div className="pitch-card">
      <div className="pitch-head">
        <TeamLogo src={team.logo} size={29} />
        <div className="pitch-head-text"><b>{team.name}</b><span>{lineup.formation || ''}</span></div>
      </div>
      <div className="pitch">
        <div className="pitch-lines"><div className="pl-box"></div><div className="pl-arc"></div></div>
        {lineup.starters.map((p, i) => {
          const c = coords[i] || { x: 50, y: 50 };
          const isGK = i === 0;
          return (
            <button className="player" key={i} style={{ left: c.x + '%', top: c.y + '%' }} onClick={() => onPlayer(p)}>
              <span className="player-dot" style={{ background: isGK ? '#B86BFF' : (color || '#1B3BFF'), color: isGK ? '#1A0533' : '#fff' }}>
                {p.num}{p.onMin ? <span className="player-sub" title={'Ingresó ' + p.onMin + "'"}>▲</span> : null}
              </span>
              <span className="player-name">{p.name}{p.captain ? ' Ⓒ' : ''}</span>
            </button>
          );
        })}
      </div>
      {lineup.bench?.length ? (
        <div className="bench">
          <span className="bench-label">BANCA</span>
          <span className="bench-names">
            {lineup.bench.map((b, i) => <button className="bench-chip" key={b.id || i} onClick={() => onPlayer(b)}>{b.num ? b.num + ' ' : ''}{b.name}</button>)}
          </span>
        </div>
      ) : null}
      {lineup.subs?.length ? (
        <div className="subs-log">
          <span className="bench-label">CAMBIOS</span>
          <div className="subs-rows">
            {lineup.subs.map((s, i) => (
              <div className="sub-row" key={i}>
                <span className="sub-min">{s.min}'</span>
                <span className="sub-in">▲ {s.inName}</span>
                <span className="sub-out">▼ {s.outName}</span>
              </div>
            ))}
          </div>
        </div>
      ) : null}
    </div>
  );
}

/* tarjeta de jugador (al tocar un jugador en la cancha o la banca) */
function PlayerCard({ p, onClose }) {
  const sub = [p.pos, p.onMin ? `Ingresó ${p.onMin}'${p.replaced ? ' por ' + p.replaced : ''}` : null].filter(Boolean).join(' · ');
  return (
    <div className="audio-scrim" onClick={onClose}>
      <div className="audio-sheet player-sheet" onClick={(e) => e.stopPropagation()}>
        <div className="sheet-head">
          <div className="sheet-head-text">
            <b>{p.num ? '#' + p.num + '  ' : ''}{p.fullName || p.name}{p.captain ? ' Ⓒ' : ''}</b>
            {sub ? <span>{sub}</span> : null}
          </div>
          <button className="sheet-x" onClick={onClose}>✕</button>
        </div>
        {p.stats && p.stats.length ? (
          <div className="pstats">
            {p.stats.map((s, i) => <div className="pstat" key={i}><b>{s.value}</b><span>{s.label}</span></div>)}
          </div>
        ) : <div className="wc-empty">Sin estadísticas individuales todavía.</div>}
      </div>
    </div>
  );
}

function Alineaciones({ m }) {
  const [player, setPlayer] = useState(null);
  if (!m.lineups) {
    const pre = m.status.state === 'pre';
    return <div className="panel"><div className="wc-empty">{pre ? 'Las alineaciones se publican ~1 h antes del inicio.' : 'Alineaciones no disponibles para este partido.'}</div></div>;
  }
  return (
    <React.Fragment>
      <div className="pitches">
        <Pitch team={m.home} lineup={m.lineups.home} color={m.home.color} onPlayer={setPlayer} />
        <Pitch team={m.away} lineup={m.lineups.away} color={m.away.color} onPlayer={setPlayer} />
      </div>
      {player && <PlayerCard p={player} onClose={() => setPlayer(null)} />}
    </React.Fragment>
  );
}

/* pestaña Posiciones: tabla del grupo del partido (en vivo), con los 2 equipos resaltados */
function Posiciones({ m }) {
  const s = m.standings;
  if (!s || !s.teams || !s.teams.length) return <div className="panel"><div className="wc-empty">Posiciones del grupo no disponibles.</div></div>;
  return (
    <div className="groups groups-one">
      <div className="group">
        <h3>POSICIONES DEL GRUPO</h3>
        <div className="grow grow-head"><span>#</span><span>EQUIPO</span><span>PJ</span><span>DG</span><span>PTS</span></div>
        {s.teams.map((t, i) => (
          <div className={'grow ' + ((t.rank || i + 1) <= 2 ? 'grow-q ' : '') + (t.isMatch ? 'grow-hl' : '')} key={i}>
            <span>{t.rank || i + 1}</span>
            <span className="grow-name"><TeamLogo src={t.logo} size={22} />{t.name}</span>
            <span>{t.played}</span>
            <span>{t.gdText}</span>
            <span className="grow-pts">{t.points}</span>
          </div>
        ))}
      </div>
    </div>
  );
}

function Scoreboard({ m, onListen, audioActive, audioPlaying }) {
  const live = m.status.state === 'in';
  return (
    <div className="scoreboard">
      <div className="sb-team sb-home">
        <TeamLogo src={m.home.logo} size={56} />
        <div className="sb-team-text"><div className="sb-code">{m.home.abbr}</div><div className="sb-name">{m.home.name}</div></div>
      </div>
      <div className="sb-center">
        <div className="sb-score"><span>{m.home.score ?? '-'}</span><span className="sb-dash">–</span><span>{m.away.score ?? '-'}</span></div>
        <div className="sb-live-row">
          <div className="sb-live">{live ? <span className="live-dot"></span> : null}{m.status.detail || (live ? 'EN VIVO' : '')}</div>
        </div>
      </div>
      <div className="sb-team sb-away">
        <div className="sb-team-text sb-right"><div className="sb-code">{m.away.abbr}</div><div className="sb-name">{m.away.name}</div></div>
        <TeamLogo src={m.away.logo} size={56} />
      </div>
    </div>
  );
}

/* ============================================================ DETALLE */
const TABS = ['RESUMEN', 'ESTADÍSTICAS', 'ALINEACIONES', 'POSICIONES'];

function MatchDetail({ id, onBack }) {
  const [m, setM] = useState(null);
  const [err, setErr] = useState('');
  const [tab, setTab] = useState(0);
  const [sheet, setSheet] = useState('closed');
  const eng = useEngine();

  const load = useCallback(() => {
    api('/api/wc/match/' + id).then(setM).catch(() => setErr('No se pudo cargar el partido'));
  }, [id]);
  useEffect(() => { load(); }, [load]);
  // polling en vivo
  useEffect(() => {
    if (!m || m.status.state !== 'in') return;
    const t = setInterval(load, 30000);
    return () => clearInterval(t);
  }, [m, load]);

  if (err) return <div className="wc-empty">{err} <button className="day-tab" onClick={onBack} style={{ marginLeft: 12 }}>← Volver</button></div>;
  if (!m) return <div className="wc-loading">Cargando partido…</div>;

  return (
    <React.Fragment>
      <header className="topbar">
        <div className="topbar-title">
          <button className="wc-mark" onClick={onBack} title="Volver" style={{ border: 'none', cursor: 'pointer' }}>26</button>
          <div><b>{m.competition}</b><span>{m.status.detail || ''}</span></div>
        </div>
        <div className="topbar-right">
          <AccountsButton />
          <WatchButton onOpen={() => setSheet(eng.active ? 'watch' : 'wchoose')} />
          <EscucharButton active={eng.active} playing={eng.playing} onOpen={() => setSheet(eng.active ? 'audio' : 'choose')} />
        </div>
      </header>

      <Scoreboard m={m} />

      <nav className="tabs">
        {TABS.map((name, i) => <button key={name} className={'tab ' + (tab === i ? 'tab-on' : '')} onClick={() => setTab(i)}>{name}</button>)}
      </nav>

      <main className="content">
        {tab === 0 && <Resumen m={m} />}
        {tab === 1 && <Estadisticas m={m} />}
        {tab === 2 && <Alineaciones m={m} />}
        {tab === 3 && <Posiciones m={m} />}
      </main>

      <AudioDock sheet={sheet} setSheet={setSheet} matchId={id} matchLabel={(m.home.abbr || m.home.name) + ' vs ' + (m.away.abbr || m.away.name)} />
    </React.Fragment>
  );
}

/* ============================================================ LISTA / GRUPOS */
function MatchesList({ onOpen }) {
  const [view, setView] = useState('matches'); // matches | groups
  const [dayOffset, setDayOffset] = useState(0);
  const [data, setData] = useState(null);
  const [groups, setGroups] = useState(null);
  const [sheetState, setSheetState] = useState('closed');
  const eng = useEngine();

  const days = [-1, 0, 1, 2, 3];
  const dayLabel = (off) => {
    if (off === 0) return 'HOY'; if (off === -1) return 'AYER'; if (off === 1) return 'MAÑANA';
    const d = new Date(); d.setDate(d.getDate() + off);
    return d.toLocaleDateString('es', { weekday: 'short', day: 'numeric' }).toUpperCase();
  };

  useEffect(() => {
    if (view !== 'matches') return;
    setData(null);
    const d = new Date(); d.setDate(d.getDate() + dayOffset);
    api('/api/wc/matches?date=' + ymd(d)).then(setData).catch(() => setData({ events: [] }));
  }, [view, dayOffset]);

  useEffect(() => {
    if (view !== 'groups' || groups) return;
    api('/api/wc/groups').then((g) => setGroups(g.groups || [])).catch(() => setGroups([]));
  }, [view]);

  return (
    <React.Fragment>
      <header className="topbar">
        <div className="topbar-title">
          <span className="wc-mark">26</span>
          <div><b>COPA MUNDIAL 2026</b><span>{view === 'matches' ? 'PARTIDOS' : 'GRUPOS'}</span></div>
        </div>
        <div className="topbar-right">
          <AccountsButton />
          <WatchButton onOpen={() => setSheetState(eng.active ? 'watch' : 'wchoose')} />
          <EscucharButton active={eng.active} playing={eng.playing} onOpen={() => setSheetState(eng.active ? 'audio' : 'choose')} />
        </div>
      </header>

      <nav className="tabs">
        <button className={'tab ' + (view === 'matches' ? 'tab-on' : '')} onClick={() => setView('matches')}>PARTIDOS</button>
        <button className={'tab ' + (view === 'groups' ? 'tab-on' : '')} onClick={() => setView('groups')}>GRUPOS</button>
      </nav>

      <main className="content">
        {view === 'matches' ? (
          <React.Fragment>
            <div className="day-tabs">
              {days.map((off) => <button key={off} className={'day-tab ' + (off === dayOffset ? 'on' : '')} onClick={() => setDayOffset(off)}>{dayLabel(off)}</button>)}
            </div>
            {data == null ? <div className="wc-loading">Cargando partidos…</div> : (
              data.events.length ? (
                <div className="mlist">
                  {data.events.map((e) => {
                    const live = e.status.state === 'in';
                    const started = e.status.state !== 'pre';
                    return (
                      <div className="mcard" key={e.id} onClick={() => onOpen(e.id)}>
                        <div className="mcard-team home"><b>{e.home.abbr}</b><TeamLogo src={e.home.logo} size={40} /></div>
                        <div className="mcard-center">
                          {started
                            ? <div className="mcard-score"><span>{e.home.score ?? 0}</span><span className="sb-dash">–</span><span>{e.away.score ?? 0}</span></div>
                            : <div className="mcard-sub">{new Date(e.date).toLocaleTimeString('es', { hour: '2-digit', minute: '2-digit' })}</div>}
                          <div className={'mcard-status ' + (live ? 'live' : '')}>{e.status.detail || (started ? 'FIN' : 'PROGRAMADO')}</div>
                        </div>
                        <div className="mcard-team away"><TeamLogo src={e.away.logo} size={40} /><b>{e.away.abbr}</b></div>
                      </div>
                    );
                  })}
                </div>
              ) : <div className="wc-empty">No hay partidos este día.</div>
            )}
          </React.Fragment>
        ) : (
          groups == null ? <div className="wc-loading">Cargando grupos…</div> : (
            <div className="groups">
              {groups.map((g) => (
                <div className="group" key={g.name}>
                  <h3>{g.name}</h3>
                  <div className="grow grow-head"><span>#</span><span>EQUIPO</span><span>PJ</span><span>DG</span><span>PTS</span></div>
                  {g.teams.map((t, i) => (
                    <div className={'grow ' + ((t.rank || i + 1) <= 2 ? 'grow-q' : '')} key={i}>
                      <span>{t.rank || i + 1}</span>
                      <span className="grow-name"><TeamLogo src={t.logo} size={22} />{t.abbr || t.name}</span>
                      <span>{t.played ?? '-'}</span>
                      <span>{t.gdText ?? t.gd ?? '-'}</span>
                      <span className="grow-pts">{t.points ?? '-'}</span>
                    </div>
                  ))}
                </div>
              ))}
            </div>
          )
        )}
      </main>

      <AudioDock sheet={sheetState} setSheet={setSheetState} />
    </React.Fragment>
  );
}

/* ============================================================ APP / ROUTER */
function getMatchId() { return new URLSearchParams(location.search).get('match'); }

function App() {
  const [matchId, setMatchId] = useState(getMatchId());
  useEffect(() => {
    const onPop = () => setMatchId(getMatchId());
    window.addEventListener('popstate', onPop);
    return () => window.removeEventListener('popstate', onPop);
  }, []);
  const open = (id) => { history.pushState({}, '', '?match=' + id); setMatchId(String(id)); };
  const back = () => { history.pushState({}, '', location.pathname); setMatchId(null); };

  return (
    <div className="viewport">
      <div className="stage" style={{ '--accent': '#FFE600' }}>
        <div className="bg-rays"></div>
        <div className="ribbon"><span style={{ background: '#FF3E3E' }}></span><span style={{ background: '#00B557' }}></span><span style={{ background: '#16D5FF' }}></span></div>
        {matchId ? <MatchDetail id={matchId} onBack={back} /> : <MatchesList onOpen={open} />}
      </div>
    </div>
  );
}

ENG.setElement(document.getElementById('wc-video'));
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
