// engine.jsx — puntos de la quiniela + tablas de grupo + clasificación + cuadro. → window
//
// Puntos por partido:  marcador exacto = EXACT (5) · resultado correcto = RESULT (3) · otro = 0

const DEFAULT_SCORING = { exact: 5, result: 3 };

function matchPoints(pred, res, scoring) {
  const S = scoring || DEFAULT_SCORING;
  if (!pred || !res || res.h == null || res.a == null || pred.h == null || pred.a == null) return null;
  if (Number(pred.h) === Number(res.h) && Number(pred.a) === Number(res.a)) return S.exact;
  if (Math.sign(pred.h - pred.a) === Math.sign(res.h - res.a)) return S.result;
  return 0;
}
function matchOutcome(pred, res) {
  if (!pred || !res || res.h == null || res.a == null || pred.h == null || pred.a == null) return null;
  if (Number(pred.h) === Number(res.h) && Number(pred.a) === Number(res.a)) return "exact";
  if (Math.sign(pred.h - pred.a) === Math.sign(res.h - res.a)) return "result";
  return "miss";
}
function resultFor(match, state) {
  if (match.archived) return window.SEED_RESULTS[match.id] || null;
  return (state.results && state.results[match.id]) || null;
}
function isFinal(match, state) {
  const r = resultFor(match, state);
  // Compatibilidad: versiones anteriores podían guardar marcador sin `final: true`.
  // Si tiene ambos goles y no fue reabierto explícitamente con final:false, debe contar en la tabla.
  return !!(r && r.h != null && r.a != null && r.final !== false);
}

// ---- Tabla de un grupo
function groupTable(group, state) {
  const teams = window.GROUP_TEAMS[group];
  const row = {};
  teams.forEach((c) => (row[c] = { code: c, group, pj: 0, pg: 0, pe: 0, pp: 0, pts: 0, gf: 0, gc: 0, dg: 0 }));
  window.MATCHES.filter((m) => m.group === group).forEach((m) => {
    if (!isFinal(m, state)) return;
    const r = resultFor(m, state);
    const H = row[m.home], A = row[m.away];
    H.pj++; A.pj++; H.gf += r.h; H.gc += r.a; A.gf += r.a; A.gc += r.h;
    if (r.h > r.a) { H.pts += 3; H.pg++; A.pp++; }
    else if (r.h < r.a) { A.pts += 3; A.pg++; H.pp++; }
    else { H.pts++; A.pts++; H.pe++; A.pe++; }
  });
  const arr = Object.values(row);
  arr.forEach((t) => (t.dg = t.gf - t.gc));
  arr.sort((a, b) => b.pts - a.pts || b.dg - a.dg || b.gf - a.gf || a.code.localeCompare(b.code));
  return arr;
}
function groupComplete(group, state) {
  return window.MATCHES.filter((m) => m.group === group).every((m) => isFinal(m, state));
}

// ---- Ranking de los 12 terceros → 8 mejores clasifican
function thirdsRanking(state) {
  const allComplete = Object.keys(window.GROUP_TEAMS).every((g) => groupComplete(g, state));
  const thirds = [];
  Object.keys(window.GROUP_TEAMS).forEach((g) => {
    const t = groupTable(g, state);
    const complete = groupComplete(g, state);
    thirds.push({ ...t[2], group: g, complete });
  });
  thirds.sort((a, b) => b.pts - a.pts || b.dg - a.dg || b.gf - a.gf || a.group.localeCompare(b.group));
  thirds.forEach((t, i) => { t.qualifies = i < 8; t.rank = i + 1; t.provisional = !allComplete; });
  return thirds;
}

function tournamentState(state) {
  const groups = {};
  let allComplete = true;
  Object.keys(window.GROUP_TEAMS).forEach((g) => {
    const table = groupTable(g, state);
    const complete = groupComplete(g, state);
    if (!complete) allComplete = false;
    groups[g] = { table, complete, winner: complete ? table[0].code : null, runner: complete ? table[1].code : null, third: table[2].code };
  });
  const thirds = thirdsRanking(state);
  return { groups, thirds, allComplete };
}

// ---- Plantilla oficial del cuadro desde 16avos.
// W/R = 1º/2º de grupo. T = tercer lugar de un conjunto permitido.
const R32_TEMPLATE = [
  { key: "r32-1", num: 73, home: ["R", "A"], away: ["R", "B"] },
  { key: "r32-2", num: 74, home: ["W", "E"], away: ["T", "ABCDF", "1E"] },
  { key: "r32-3", num: 75, home: ["W", "F"], away: ["R", "C"] },
  { key: "r32-4", num: 76, home: ["W", "C"], away: ["R", "F"] },
  { key: "r32-5", num: 77, home: ["W", "I"], away: ["T", "CDFGH", "1I"] },
  { key: "r32-6", num: 78, home: ["R", "E"], away: ["R", "I"] },
  { key: "r32-7", num: 79, home: ["W", "A"], away: ["T", "CEFHI", "1A"] },
  { key: "r32-8", num: 80, home: ["W", "L"], away: ["T", "EHIJK", "1L"] },
  { key: "r32-9", num: 81, home: ["W", "D"], away: ["T", "BEFIJ", "1D"] },
  { key: "r32-10", num: 82, home: ["W", "G"], away: ["T", "AEHIJ", "1G"] },
  { key: "r32-11", num: 83, home: ["R", "K"], away: ["R", "L"] },
  { key: "r32-12", num: 84, home: ["W", "H"], away: ["R", "J"] },
  { key: "r32-13", num: 85, home: ["W", "B"], away: ["T", "EFGIJ", "1B"] },
  { key: "r32-14", num: 86, home: ["W", "J"], away: ["R", "H"] },
  { key: "r32-15", num: 87, home: ["W", "K"], away: ["T", "DEIJL", "1K"] },
  { key: "r32-16", num: 88, home: ["R", "D"], away: ["R", "G"] },
];

const THIRD_SLOT_META = {
  "1A": { key: "r32-7", allowed: "CEFHI" },
  "1B": { key: "r32-13", allowed: "EFGIJ" },
  "1D": { key: "r32-9", allowed: "BEFIJ" },
  "1E": { key: "r32-2", allowed: "ABCDF" },
  "1G": { key: "r32-10", allowed: "AEHIJ" },
  "1I": { key: "r32-5", allowed: "CDFGH" },
  "1K": { key: "r32-15", allowed: "DEIJL" },
  "1L": { key: "r32-8", allowed: "EHIJK" },
};
const THIRD_SLOT_ORDER = ["1A", "1B", "1D", "1E", "1G", "1I", "1K", "1L"];

function slotLabel(spec) {
  if (spec[0] === "W") return "1º " + spec[1];
  if (spec[0] === "R") return "2º " + spec[1];
  return "3º " + spec[1].split("").join("/");
}
function resolveGroupSlot(spec, T) {
  const g = T.groups[spec[1]];
  if (spec[0] === "W") return g.winner;
  return g.runner;
}

// Asigna los terceros clasificados a sus slots permitidos de forma automática.
// Para producción federativa estricta se puede reemplazar por la tabla completa de Annexe C (495 filas),
// pero esta resolución respeta las restricciones de rivales de cada slot y evita revanchas inmediatas.
function assignThirds(thirds, allComplete) {
  if (!allComplete) return {};
  const qualified = thirds.filter((t) => t.qualifies).map((t) => t.group).sort();
  if (qualified.length !== 8) return {};

  const used = new Set();
  const outBySlot = {};

  function candidates(slot) {
    return THIRD_SLOT_META[slot].allowed.split("").filter((g) => qualified.includes(g) && !used.has(g));
  }

  function fillForced() {
    let changed = true;
    while (changed) {
      changed = false;
      for (const slot of THIRD_SLOT_ORDER) {
        if (outBySlot[slot]) continue;
        const c = candidates(slot);
        if (c.length === 1) {
          outBySlot[slot] = c[0]; used.add(c[0]); changed = true;
        }
      }
      for (const g of qualified) {
        if (used.has(g)) continue;
        const possible = THIRD_SLOT_ORDER.filter((slot) => !outBySlot[slot] && THIRD_SLOT_META[slot].allowed.includes(g));
        if (possible.length === 1) {
          outBySlot[possible[0]] = g; used.add(g); changed = true;
        }
      }
    }
  }

  function solve() {
    fillForced();
    const open = THIRD_SLOT_ORDER.filter((slot) => !outBySlot[slot]);
    if (!open.length) return true;
    open.sort((a, b) => candidates(a).length - candidates(b).length || THIRD_SLOT_ORDER.indexOf(a) - THIRD_SLOT_ORDER.indexOf(b));
    const slot = open[0];
    for (const g of candidates(slot)) {
      const snapshot = { out: { ...outBySlot }, used: new Set(used) };
      outBySlot[slot] = g; used.add(g);
      if (solve()) return true;
      Object.keys(outBySlot).forEach((k) => delete outBySlot[k]);
      Object.assign(outBySlot, snapshot.out);
      used.clear(); snapshot.used.forEach((x) => used.add(x));
    }
    return false;
  }

  if (!solve()) return {};
  const byMatchKey = {};
  Object.keys(outBySlot).forEach((slot) => {
    byMatchKey[THIRD_SLOT_META[slot].key] = outBySlot[slot];
  });
  return byMatchKey;
}

function resolveSpec(spec, T, thirdMap, matchKey) {
  if (spec[0] === "T") {
    const group = thirdMap[matchKey] || null;
    const code = group ? (T.groups[group] && T.groups[group].third) : null;
    return { code, group, label: slotLabel(spec) };
  }
  return { code: resolveGroupSlot(spec, T), group: spec[1], label: slotLabel(spec) };
}

// Construye el cuadro completo y lo resuelve con los ganadores marcados (koWinners)
function buildBracket(state) {
  const T = tournamentState(state);
  const thirdMap = assignThirds(T.thirds, T.allComplete);
  const ko = state.koWinners || {};
  const matches = {}; // key -> {key, round, home:{code,label}, away:{...}, num}

  R32_TEMPLATE.forEach((m) => {
    matches[m.key] = {
      key: m.key,
      round: "r32",
      num: m.num,
      home: resolveSpec(m.home, T, thirdMap, m.key),
      away: resolveSpec(m.away, T, thirdMap, m.key),
    };
  });

  const winnerOf = (key) => {
    const m = matches[key];
    if (!m) return { code: null, label: "Ganador" };
    const w = ko[key];
    if (w === "home" && m.home.code) return { code: m.home.code, label: m.home.code };
    if (w === "away" && m.away.code) return { code: m.away.code, label: m.away.code };
    return { code: null, label: "Gana P" + m.num };
  };

  const add = (key, round, num, fromH, fromA) => {
    matches[key] = { key, round, num, home: winnerOf(fromH), away: winnerOf(fromA), fromH, fromA };
  };

  add("r16-1", "r16", 89, "r32-2", "r32-5");
  add("r16-2", "r16", 90, "r32-1", "r32-3");
  add("r16-3", "r16", 91, "r32-4", "r32-6");
  add("r16-4", "r16", 92, "r32-7", "r32-8");
  add("r16-5", "r16", 93, "r32-11", "r32-12");
  add("r16-6", "r16", 94, "r32-9", "r32-10");
  add("r16-7", "r16", 95, "r32-14", "r32-16");
  add("r16-8", "r16", 96, "r32-13", "r32-15");

  add("qf-1", "qf", 97, "r16-1", "r16-2");
  add("qf-2", "qf", 98, "r16-5", "r16-6");
  add("qf-3", "qf", 99, "r16-3", "r16-4");
  add("qf-4", "qf", 100, "r16-7", "r16-8");

  add("sf-1", "sf", 101, "qf-1", "qf-2");
  add("sf-2", "sf", 102, "qf-3", "qf-4");
  add("third", "third", 103, "sf-1", "sf-2");
  add("final", "final", 104, "sf-1", "sf-2");

  return { matches, T, thirdMap };
}

function nextKoKeys(key) {
  if (key.startsWith("r32-")) return ["r16-" + Math.ceil(Number(key.split("-")[1]) / 2)];
  if (key.startsWith("r16-")) {
    const n = Number(key.split("-")[1]);
    if (n <= 2) return ["qf-1"];
    if (n <= 4) return ["qf-3"];
    if (n <= 6) return ["qf-2"];
    return ["qf-4"];
  }
  if (key.startsWith("qf-")) return [Number(key.split("-")[1]) <= 2 ? "sf-1" : "sf-2"];
  if (key.startsWith("sf-")) return ["final", "third"];
  return [];
}
function clearKoDependants(ko, key) {
  const out = { ...(ko || {}) };
  const stack = nextKoKeys(key);
  while (stack.length) {
    const k = stack.pop();
    delete out[k];
    nextKoKeys(k).forEach((x) => stack.push(x));
  }
  return out;
}

// ---- Clasificación de jugadores (sin bonos)
const BASE_MATCHES_COUNT = 4;
function inferBaseStats(base) {
  // Los puntos base vienen de los 4 partidos archivados.
  // Con 5 pts exacto y 3 pts acierto, estos acumulados permiten reconstruir el resumen visible.
  const exact = base === 11 ? 1 : 0;
  const result = Math.max(0, Math.round((base - exact * DEFAULT_SCORING.exact) / DEFAULT_SCORING.result));
  const miss = Math.max(0, BASE_MATCHES_COUNT - exact - result);
  return { exact, result, miss, played: BASE_MATCHES_COUNT };
}
function computeStandings(state, scoring) {
  const S = scoring || DEFAULT_SCORING;
  const preds = state.preds || {};

  const hasSavedArchivedPred = (playerId) => window.MATCHES.some((m) => {
    if (!m.archived) return false;
    const pred = preds[m.id] && preds[m.id][playerId];
    return pred && pred.h != null && pred.a != null;
  });

  const addOutcome = (bucket, pred, res) => {
    const pts = matchPoints(pred, res, S);
    if (pts == null) return;
    bucket.pts += pts;
    bucket.played++;
    const oc = matchOutcome(pred, res);
    if (oc === "exact") bucket.exact++;
    else if (oc === "result") bucket.result++;
    else bucket.miss++;
  };

  const rows = window.PLAYERS.map((p) => {
    const useSavedArchived = hasSavedArchivedPred(p.id);
    const fallbackBase = inferBaseStats(p.base);
    const baseBucket = useSavedArchived
      ? { pts: 0, exact: 0, result: 0, miss: 0, played: 0 }
      : {
          pts: p.base || 0,
          exact: p.baseExact ?? fallbackBase.exact,
          result: p.baseResult ?? fallbackBase.result,
          miss: p.baseMiss ?? fallbackBase.miss,
          played: p.basePlayed ?? fallbackBase.played,
        };
    const liveBucket = { pts: 0, exact: 0, result: 0, miss: 0, played: 0 };

    window.MATCHES.forEach((m) => {
      if (!isFinal(m, state)) return;
      const pred = preds[m.id] && preds[m.id][p.id];
      const res = resultFor(m, state);
      if (m.archived) {
        // Si ya cargaste pronósticos de los primeros partidos en Supabase,
        // recalculamos esos puntos reales y dejamos de depender del acumulado escrito a mano.
        if (useSavedArchived) addOutcome(baseBucket, pred, res);
        return;
      }
      addOutcome(liveBucket, pred, res);
    });

    const exact = baseBucket.exact + liveBucket.exact;
    const result = baseBucket.result + liveBucket.result;
    const miss = baseBucket.miss + liveBucket.miss;
    const played = baseBucket.played + liveBucket.played;
    const matchPts = liveBucket.pts;
    const total = baseBucket.pts + liveBucket.pts;

    return {
      ...p,
      base: baseBucket.pts,
      originalBase: p.base,
      matchPts,
      total,
      exact,
      result,
      miss,
      played,
      baseExact: baseBucket.exact,
      baseResult: baseBucket.result,
      baseMiss: baseBucket.miss,
      liveExact: liveBucket.exact,
      liveResult: liveBucket.result,
      liveMiss: liveBucket.miss,
      livePlayed: liveBucket.played,
      recalculatedBase: useSavedArchived,
    };
  });
  rows.sort((a, b) => b.total - a.total || b.exact - a.exact || a.full.localeCompare(b.full));
  let lastTotal = null, lastRank = 0;
  rows.forEach((r, i) => { if (r.total !== lastTotal) { lastRank = i + 1; lastTotal = r.total; } r.rank = lastRank; });
  return rows;
}

Object.assign(window, {
  DEFAULT_SCORING, matchPoints, matchOutcome, resultFor, isFinal,
  groupTable, groupComplete, thirdsRanking, tournamentState, buildBracket, clearKoDependants, computeStandings,
});
