/* ============================================================
   Eliminatorias — knockout bracket screen
   ------------------------------------------------------------
   Reuses ui.jsx primitives (Icon, fmtWhen, roundLabel), app.css
   classes (.qm-match + states, .qm-chip, .qm-filters, …) and the
   bracket engine (window.QBracket via window.QStore.resolveBracket).

   Two modes (toggle):
     • "confirmed" → berths/scores from FINAL results only; undecided
       ties stay as labelled placeholders.
     • "proj"      → live/partial scores are promoted so the engine
       projects who WOULD advance; flagged as NOT official.
   ============================================================ */
const { useMemo } = React;

/* ---------- round model ---------- */
const ROUND_DEFS = [
  ["R32", "1/16", "Dieciseisavos"],
  ["R16", "1/8", "Octavos"],
  ["QF", "1/4", "Cuartos de final"],
  ["SF", "Semis", "Semifinales"],
  ["FIN", "Final", "Final"],
];
function buildRounds() {
  const Q = window.QStore;
  // Match numbers follow the SCHEDULE, not bracket adjacency, so a plain numeric
  // sort mis-pairs each column: e.g. #73 ends up next to #74, both feeding the
  // node labelled "W74/W77" — but #89 is actually fed by #74 and #77. Order each
  // column by walking the bracket tree from the final (home branch before away),
  // so the two matches that feed a given next-round match are drawn adjacent and
  // line up with their parent.
  const byId = {};
  Q.db.matches.forEach((m) => { byId[m.id] = m; });
  const childId = (src) => (src && src[0] === "W" ? "m" + src.slice(1) : null); // "W074" → "m074"
  const order = { R32: [], R16: [], QF: [], SF: [], FIN: [] };
  const seen = new Set();
  (function visit(id) {
    const m = id && byId[id];
    if (!m || seen.has(id)) return;
    seen.add(id);
    if (order[m.group]) order[m.group].push(id);
    visit(childId(m.homeSrc));   // top branch first → preserves top-to-bottom order
    visit(childId(m.awaySrc));
  })("m104");
  return ROUND_DEFS.map(([code, tab, name]) => ({
    code, tab, name,
    // fall back to a numeric sort if the wiring is ever unavailable
    ids: order[code] && order[code].length
      ? order[code]
      : Q.db.matches.filter((m) => m.group === code).map((m) => m.id).sort(),
  }));
}

/* ---------- seed labels for unresolved berths ---------- */
function feederRoundTab(num) {
  if (num >= 73 && num <= 88) return "1/16";
  if (num >= 89 && num <= 96) return "1/8";
  if (num >= 97 && num <= 100) return "1/4";
  if (num === 101 || num === 102) return "Semis";
  return "Final";
}
function seedToken(src) {
  if (!src) return "–";
  if (src[0] === "G") return src[1] + src.slice(2);       // G1E → "1E"
  if (src[0] === "T") return "3.º";
  if (src[0] === "W") return "W" + Number(src.slice(1));  // W074 → "W74"
  if (src[0] === "L") return "L" + Number(src.slice(1));
  return "–";
}
function thirdGroups(src) {
  const set = (window.QBracket && window.QBracket.THIRD_ALLOWED && window.QBracket.THIRD_ALLOWED[src]) || "";
  return set.split("").join("/");
}
function seedLabel(src) {
  if (!src) return "Por definir";
  if (src[0] === "G") return (src[1] === "1" ? "1.º " : "2.º ") + "Grupo " + src.slice(2);
  if (src[0] === "T") return "3.º " + thirdGroups(src);           // ej. "3.º A/B/C/D/F"
  if (src[0] === "W") return "Ganador " + seedToken(src);          // "Ganador W89"
  if (src[0] === "L") return "Perdedor " + seedToken(src);         // "Perdedor L101"
  return "Por definir";
}
// origin tag for a RESOLVED Round-of-32 team (position + group / third pool)
function originLabel(src) {
  if (!src) return null;
  if (src[0] === "G") return (src[1] === "1" ? "1.º " : "2.º ") + src.slice(2);   // "1.º A"
  if (src[0] === "T") return "3.º " + thirdGroups(src);                              // "3.º A/B/C/D/F"
  return null;
}
// reliable horizontal tween (this engine ignores scrollTo behavior:smooth, and
// rAF can be throttled in a backgrounded iframe — so we also land the value via timeout)
function smoothScrollLeft(el, to, dur) {
  dur = dur || 320;
  const start = el.scrollLeft, delta = to - start, t0 = performance.now();
  if (Math.abs(delta) < 2) { el.scrollLeft = to; return; }
  let done = false;
  const step = (now) => {
    const p = Math.min(1, (now - t0) / dur);
    const e = p < 0.5 ? 2 * p * p : 1 - Math.pow(-2 * p + 2, 2) / 2;
    el.scrollLeft = start + delta * e;
    if (p < 1) requestAnimationFrame(step); else done = true;
  };
  requestAnimationFrame(step);
  setTimeout(() => { if (!done) el.scrollLeft = to; }, dur + 80);
}

/* ---------- who advances (final score, pens, or live leader) ---------- */
function decideWinner(res, homeCode, awayCode) {
  if (!res || !homeCode || !awayCode) return null;
  if (res.homeGoals > res.awayGoals) return homeCode;
  if (res.homeGoals < res.awayGoals) return awayCode;
  if (res.advances) return res.advances;
  if (res.pens) return res.pens.h > res.pens.a ? homeCode : awayCode;
  return null;
}

/* date/time, no weekday — e.g. "8 jul · 11:00" (shown top-right) */
const _bkDateFmt = new Intl.DateTimeFormat("es", { day: "numeric", month: "short", timeZone: "America/Lima" });
function fmtWhenShort(t) { return _bkDateFmt.format(new Date(t)).replace(".", "") + " · " + fmtTime(t); }

/* ---------- one team row inside a card ---------- */
function BkSide({ code, src, score, pen, win, out, showScore, check }) {
  const Q = window.QStore;
  if (code) {
    const t = Q.team(code) || { flag: "🏳️", name: code, code };
    const origin = originLabel(src);
    return (
      <div className={`qm-bk-side ${win ? "win" : ""} ${out ? "out" : ""}`}>
        <span className="fl">{t.flag}</span>
        <div className="qm-bk-meta">
          <span className="nm-row">
            <span className="nm">{t.name}</span>
            {check && <span className="qm-bk-ok" title="Clasificado a este partido"><Icon name="check" size={13} stroke={3} /></span>}
          </span>
          {origin && <span className="qm-bk-pos">{origin}</span>}
        </div>
        <span className="spacer" />
        {showScore && <span className="sc">{score}{pen != null && <span className="pen">({pen})</span>}</span>}
      </div>
    );
  }
  return (
    <div className="qm-bk-side ph">
      {src && src[0] === "G" && <span className="qm-bk-seed">{seedToken(src)}</span>}
      <span className="nm">{seedLabel(src)}</span>
    </div>
  );
}

/* ---------- a knockout match card (reuses .qm-match.compact shell) ---------- */
function KOCard({ m, mode, resolveMap, confMap }) {
  const Q = window.QStore;
  const r = resolveMap[m.id] || {};
  const homeCode = r.home || null;
  const awayCode = r.away || null;
  // show the live OR final score on the match itself in BOTH modes (a live match
  // always shows its live score); the mode only changes berth PROPAGATION below.
  const scoreRes = Q.liveOrFinalRes(m.id);
  const status = Q.matchStatus(m);                 // final | live | locked | open
  const isFinal = status === "final";
  const isLive = status === "live";
  const bothPh = !homeCode && !awayCode;
  const ready = homeCode && awayCode;
  const winner = decideWinner(scoreRes, homeCode, awayCode);
  const showScore = !!scoreRes && ready;           // numeric only when teams known + score available in this mode

  // projected-only sides (resolved in projection but not in confirmed)
  const cr = confMap[m.id] || {};
  const projHome = mode === "proj" && homeCode && cr.home !== homeCode;
  const projAway = mode === "proj" && awayCode && cr.away !== awayCode;
  const isR32 = m.group === "R32";              // checkmarks only in the Round of 32
  const anyProjected = mode === "proj" && isR32 && isLive && showScore;   // projected winner on a LIVE R32 card (proj mode only)

  const cls = ["qm-match", "compact", "qm-bk-match"];
  if (isLive) cls.push("is-live");
  if (isFinal) cls.push("is-confirmed");
  if (bothPh) cls.push("is-ph");

  // top-right indicator (always present so all cards share the same height):
  // "Vivo" while live, otherwise the kickoff date — both the same small size.
  const topRight = isLive
    ? <span className="qm-bk-when qm-bk-live"><span className="dot" />Vivo</span>
    : <span className="qm-bk-when">{fmtWhenShort(m.kickoff)}</span>;

  return (
    <div className={cls.join(" ")}>
      <div className="qm-bk-top">{topRight}</div>

      <BkSide code={homeCode} src={m.homeSrc} score={scoreRes && scoreRes.homeGoals}
        pen={scoreRes && scoreRes.pens ? scoreRes.pens.h : null}
        win={winner === homeCode} out={winner && winner !== homeCode}
        showScore={showScore} check={isR32 && !!homeCode && !isFinal && !isLive && !projHome} />
      <BkSide code={awayCode} src={m.awaySrc} score={scoreRes && scoreRes.awayGoals}
        pen={scoreRes && scoreRes.pens ? scoreRes.pens.a : null}
        win={winner === awayCode} out={winner && winner !== awayCode}
        showScore={showScore} check={isR32 && !!awayCode && !isFinal && !isLive && !projAway} />

      {/* projection flag */}
      {anyProjected && <div className="qm-bk-projtag"><span className="warn-dot" />Proyectado · no oficial</div>}
    </div>
  );
}

/* ---------- desktop/mobile multi-column board ---------- */
function BracketBoard({ rounds, mode, resolveMap, confMap, innerRef, onScroll, leadSpace, trailSpace, selectedCode }) {
  const Q = window.QStore;
  return (
    <div className="qm-bk-board" ref={innerRef} onScroll={onScroll}>
      {leadSpace ? <div className="qm-bk-spacer" style={{ width: leadSpace }} aria-hidden="true" /> : null}
      {rounds.map((rd, ci) => {
        const hasNext = ci < rounds.length - 1;
        const hasPrev = ci > 0;
        return (
          <div key={rd.code} className={`qm-bk-col ${hasNext ? "has-next" : ""} ${hasPrev ? "has-prev" : ""} ${rd.code === selectedCode ? "is-selected" : ""}`} data-round={rd.code}>
            <div className="qm-bk-col-head">{rd.tab}<small>{rd.name}</small></div>
            <div className="qm-bk-col-body">
              {rd.ids.map((id, si) => (
                <div key={id} className="qm-bk-slot">
                  {hasPrev && <span className="qm-bk-stub-l" />}
                  {hasNext && <span className="qm-bk-stub-r" />}
                  {hasNext && si % 2 === 0 && <span className="qm-bk-vert" />}
                  <KOCard m={Q.matchById(id)} mode={mode} resolveMap={resolveMap} confMap={confMap} />
                </div>
              ))}
            </div>
          </div>
        );
      })}
      {trailSpace ? <div className="qm-bk-spacer" style={{ width: trailSpace }} aria-hidden="true" /> : null}
    </div>
  );
}

/* ---------- mobile: a 2-column slice (selected round + next peeking) ----------
   Centered like a real bracket and sized to the SELECTED round, so switching
   stages never lands on blank space. Swiping right past the next round (or
   tapping its chip) "rearranges" so that round becomes primary. */
function MobileBracket({ rounds, roundCode, setRound, mode, resolveMap, confMap }) {
  const idx = Math.max(0, rounds.findIndex((r) => r.code === roundCode));
  const prev = rounds[idx - 1];
  const cur = rounds[idx];
  const next = rounds[idx + 1];
  // only [cur, next] are real columns, so the board height stays sized to the
  // CURRENT round (compact) — prev would inflate it. Empty spacers give room to
  // drag left (→ previous) / right (→ next); crossing the threshold reorganizes.
  const slice = [cur, next].filter(Boolean);
  const STEP = 258;                       // mobile column width (230) + gap (28)
  const lead = prev ? STEP : 0;
  const trail = STEP;
  const ref = useRef(null);
  const timerRef = useRef(0);

  useEffect(() => { if (ref.current) ref.current.scrollLeft = lead; }, [roundCode]);

  const onScroll = () => {
    const board = ref.current;
    if (!board) return;
    clearTimeout(timerRef.current);
    timerRef.current = setTimeout(() => {
      const sl = board.scrollLeft;
      if (next && sl >= lead + STEP * 0.6) { setRound(next.code); return; }
      if (prev && sl <= lead - STEP * 0.6) { setRound(prev.code); return; }
    }, 160);
  };

  return (
    <div className="qm-bk-sliceboard">
      <BracketBoard rounds={slice} mode={mode} resolveMap={resolveMap} confMap={confMap}
        innerRef={ref} onScroll={onScroll} leadSpace={lead} trailSpace={trail} selectedCode={cur.code} />
      {!next && (
        <>
          <div className="qm-bk-champ"><Icon name="trophy" size={30} stroke={2.2} /><span>Campeón</span></div>
          <ThirdPlace mode={mode} resolveMap={resolveMap} confMap={confMap} />
        </>
      )}
    </div>
  );
}

/* ---------- third-place aside (below the board) ---------- */
function ThirdPlace({ mode, resolveMap, confMap }) {
  const Q = window.QStore;
  const m = Q.matchById("m103");
  if (!m) return null;
  return (
    <div className="qm-bk-aside">
      <div className="qm-bk-3pl-lbl">Partido por el tercer puesto</div>
      <div style={{ maxWidth: 264 }}>
        <KOCard m={m} mode={mode} resolveMap={resolveMap} confMap={confMap} />
      </div>
    </div>
  );
}

/* ---------- viewport mode (mobile / narrow desktop / wide desktop) ---------- */
function useViewport() {
  const calc = () => {
    const w = (typeof window !== "undefined" && window.innerWidth) || 1440;
    return w <= 720 ? "mobile" : w < 1600 ? "deskNarrow" : "deskWide";
  };
  const [vp, setVp] = useState(calc);
  useEffect(() => {
    const on = () => setVp(calc());
    window.addEventListener("resize", on);
    return () => window.removeEventListener("resize", on);
  }, []);
  return vp;
}

/* ---------- screen ---------- */
function BracketScreen() {
  useStore();
  const rounds = useMemo(buildRounds, []);
  const confMap = useMemo(() => window.QStore.resolveBracket("confirmed"), []);
  const projMap = useMemo(() => window.QStore.resolveBracket("proj"), []);
  const vp = useViewport();

  const [mode, setMode] = useState("confirmed");
  const [round, setRound] = useState("R32");
  const [full, setFull] = useState(false);
  const boardRef = useRef(null);

  // Cuando los cupos de la ronda de entrada (1/16) ya están todos confirmados, la
  // proyección "si cerrase hoy" no aporta nada: se oculta el toggle y se fuerza el
  // modo confirmado. Queda listo para reutilizarse en otro torneo (mientras los
  // grupos sigan abiertos, habrá cupos nulos y el toggle reaparece).
  const allConfirmed = useMemo(() => {
    const entry = rounds[0];
    if (!entry || !entry.ids.length) return false;
    return entry.ids.every((id) => confMap[id] && confMap[id].home && confMap[id].away);
  }, [rounds, confMap]);

  const effMode = allConfirmed ? "confirmed" : mode;
  const activeMap = effMode === "proj" ? projMap : confMap;

  // narrow desktop: a 3-stage window centered on the selected round, clamped at the ends
  const selIdx = Math.max(0, rounds.findIndex((r) => r.code === round));
  const winStart = Math.max(0, Math.min(selIdx - 1, rounds.length - 3));
  const windowRounds = rounds.slice(winStart, winStart + 3);

  // horizontal trackpad scroll moves between stages (desktop window)
  const wheelLockRef = useRef(0);
  const onDeskWheel = (e) => {
    if (Math.abs(e.deltaX) <= Math.abs(e.deltaY) || Math.abs(e.deltaX) < 16) return;
    if (Date.now() < wheelLockRef.current) return;
    wheelLockRef.current = Date.now() + 450;
    const dir = e.deltaX > 0 ? 1 : -1;
    const i = rounds.findIndex((r) => r.code === round);
    const ni = Math.max(0, Math.min(rounds.length - 1, i + dir));
    if (ni !== i) setRound(rounds[ni].code);
  };

  const goRound = (code) => {
    setRound(code);
    const board = boardRef.current;
    if (!board) return;
    const el = board.querySelector(`[data-round="${code}"]`);
    if (!el) return;
    const left = el.getBoundingClientRect().left - board.getBoundingClientRect().left + board.scrollLeft - 12;
    smoothScrollLeft(board, Math.max(0, left));
  };

  // keep the active round chip in sync as the board is scrolled (mobile swipe).
  // time-throttled (not rAF — that can be throttled inside a backgrounded iframe).
  const lastSyncRef = useRef(0);
  const onBoardScroll = () => {
    const nowt = Date.now();
    if (nowt - lastSyncRef.current < 90) return;
    lastSyncRef.current = nowt;
    const board = boardRef.current;
    if (!board) return;
    const bl = board.getBoundingClientRect().left;
    let best = null, bestDist = Infinity;
    rounds.forEach((rd) => {
      const el = board.querySelector(`[data-round="${rd.code}"]`);
      if (!el) return;
      const d = Math.abs(el.getBoundingClientRect().left - bl - 12);
      if (d < bestDist) { bestDist = d; best = rd.code; }
    });
    if (best) setRound((cur) => (cur === best ? cur : best));
  };

  // Esc closes fullscreen
  useEffect(() => {
    if (!full) return;
    const onKey = (e) => { if (e.key === "Escape") setFull(false); };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [full]);

  return (
    <div className="qm-bk">
      <div className="qm-eyebrow"><Icon name="trophy" size={14} />Eliminatorias</div>
      <h1 className="qm-h">Fase final</h1>

      {/* round tabs + fullscreen */}
      <div className="qm-bk-controls">
        <div className="qm-filters">
          {rounds.map((rd) => (
            <button key={rd.code} className={round === rd.code ? "on" : ""} onClick={() => goRound(rd.code)}>
              {rd.tab}
            </button>
          ))}
        </div>
        <button className="qm-bk-full" onClick={() => setFull(true)}>
          <Icon name="table" size={15} stroke={2.2} />Pantalla completa
        </button>
      </div>

      {/* mode toggle + note — oculto cuando la llave ya está 100% confirmada */}
      {!allConfirmed && (
        <div className="qm-bk-modebar">
          <div className="qm-bk-toggle" role="tablist" aria-label="Modo del cuadro">
            <button className={mode === "proj" ? "on" : ""} onClick={() => setMode("proj")} role="tab" aria-selected={mode === "proj"}>
              <Icon name="bolt" size={14} stroke={2.4} />Si cerrase hoy
            </button>
            <button className={mode === "confirmed" ? "on" : ""} onClick={() => setMode("confirmed")} role="tab" aria-selected={mode === "confirmed"}>
              <Icon name="check" size={14} stroke={2.6} />Solo confirmados
            </button>
          </div>
          <div className="qm-bk-modenote">
            {mode === "proj"
              ? <><span className="warn-dot" />Proyección en vivo · <b>no oficial</b>.</>
              : <><Icon name="lock" size={13} />Solo resultados oficiales confirmados. Las llaves no decididas quedan por definir.</>}
          </div>
        </div>
      )}

      {/* board: full bracket (wide desktop), 3-stage window (narrow desktop), slice (mobile) */}
      {vp === "mobile" ? (
        <MobileBracket rounds={rounds} roundCode={round} setRound={setRound} mode={effMode} resolveMap={activeMap} confMap={confMap} />
      ) : vp === "deskNarrow" ? (
        <div className="qm-bk-deskwindow" onWheel={onDeskWheel}>
          <BracketBoard rounds={windowRounds} mode={effMode} resolveMap={activeMap} confMap={confMap} selectedCode={round} />
          {windowRounds.some((r) => r.code === "FIN") && <div className="qm-bk-aside-wrap"><ThirdPlace mode={effMode} resolveMap={activeMap} confMap={confMap} /></div>}
        </div>
      ) : (
        <div className="qm-bk-fullboard">
          <BracketBoard rounds={rounds} mode={effMode} resolveMap={activeMap} confMap={confMap} innerRef={boardRef} onScroll={onBoardScroll} selectedCode={round} />
          <div className="qm-bk-aside-wrap"><ThirdPlace mode={effMode} resolveMap={activeMap} confMap={confMap} /></div>
        </div>
      )}

      {/* fullscreen overlay (full bracket, both-axis scroll) */}
      {full && (
        <div className="qm-bk-overlay">
          <div className="qm-bk-overlay-bar">
            <span className="ti">Cuadro completo</span>
            <span className="qm-chip open" style={{ marginLeft: 4 }}>{effMode === "proj" ? "Proyección" : "Confirmados"}</span>
            <span className="spacer" />
            <button className="qm-bk-overlay-close" onClick={() => setFull(false)}><Icon name="x" size={15} stroke={2.4} />Cerrar</button>
          </div>
          <div className="qm-bk-overlay-scroll">
            <BracketBoard rounds={rounds} mode={effMode} resolveMap={activeMap} confMap={confMap} />
            <ThirdPlace mode={effMode} resolveMap={activeMap} confMap={confMap} />
          </div>
        </div>
      )}
    </div>
  );
}

window.BracketScreen = BracketScreen;
