
function normalizeChronoPlayerKey(name){
  return String(name || "")
    .trim()
    .toLowerCase()
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-+|-+$/g, "");
}


function getChronoPlayerOwnerKey(){
  const client = window.CHRONO_PARTY_CLIENT || {};
  const raw = String(
    client.client_id ||
    client.gift_code ||
    window.CHRONO_ACCESS_LABEL ||
    window.CHRONO_ACCESS_CODE ||
    "default"
  ).trim();

  return normalizeChronoPlayerKey(raw) || "default";
}

function getChronoStorageKey(baseKey){
  return `${baseKey}_${getChronoPlayerOwnerKey()}`;
}

function splitChronoPlayerIdentity(fullName){
  const clean = String(fullName || "").trim().replace(/\s+/g, " ");
  const parts = clean.split(" ").filter(Boolean);
  const initials = String(parts.pop() || "").toUpperCase().replace(/[^A-Z0-9]/g, "").slice(0, 2);
  const name = parts.join(" ").trim();

  return {
    name: name || clean,
    initials
  };
}

function readChronoPlayersRegistry(){
  try{
    return JSON.parse(localStorage.getItem(getChronoStorageKey("chrono_players_registry")) || "{}");
  }catch(e){
    return {};
  }
}

function writeChronoPlayersRegistry(registry){
  try{
    localStorage.setItem(getChronoStorageKey("chrono_players_registry"), JSON.stringify(registry || {}));
  }catch(e){}
}

function getOrCreateChronoPlayer(name){
  const cleanName = String(name || "").trim();

  if(!cleanName){
    return {
      id: "pl_unknown",
      name: ""
    };
  }

  const registry = readChronoPlayersRegistry();
  const normalized = normalizeChronoPlayerKey(cleanName);

  if(registry[normalized] && registry[normalized].id){
    return registry[normalized];
  }

  // Secours local si le serveur est temporairement indisponible.
  // Normalement, les profils sont résolus avant le début de partie via /api/player-profile.php.
  const player = {
    id: "pl_local_" + Math.random().toString(36).slice(2,10),
    name: cleanName
  };

  registry[normalized] = player;
  writeChronoPlayersRegistry(registry);

  return player;
}

async function resolveChronoPlayerProfile(playerName){
  const cleanName = String(playerName || "").trim();

  if(!cleanName){
    return getOrCreateChronoPlayer(cleanName);
  }

  const identity = splitChronoPlayerIdentity(cleanName);

  if(!identity.name || !identity.initials){
    return getOrCreateChronoPlayer(cleanName);
  }

  const response = await fetch(PLAYER_PROFILE_URL, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      name: identity.name,
      initials: identity.initials,
      owner_key: getChronoPlayerOwnerKey()
    })
  });

  const data = await response.json();

  if(!response.ok || !data || data.ok !== true || !data.player || !data.player.id){
    throw new Error((data && data.error) ? data.error : "Profil joueur impossible à récupérer");
  }

  const registry = readChronoPlayersRegistry();
  const displayName = `${data.player.name} ${data.player.initials}`.trim();
  const normalized = normalizeChronoPlayerKey(cleanName);

registry[normalized] = {
  id: data.player.id,
  name: displayName,
  initials: data.player.initials,
  key: data.player.key || normalized
};

writeChronoPlayersRegistry(registry);

try{
  localStorage.setItem("chrono_last_player_id", registry[normalized].id);
}catch(e){}

return registry[normalized];
}

async function resolveChronoPlayerProfiles(playerNames){
  const uniqueNames = Array.from(new Set((playerNames || []).map(name => String(name || "").trim()).filter(Boolean)));

  for(const playerName of uniqueNames){
    await resolveChronoPlayerProfile(playerName);
  }

  if(typeof refreshChronoKnownPlayersSuggestions === "function"){
    refreshChronoKnownPlayersSuggestions(true);
  }
}


let chronoKnownPlayersCache = [];
let chronoKnownPlayersLoaded = false;
let chronoKnownPlayersLoading = null;

function getChronoKnownPlayersFromLocalRegistry(){
  const registry = readChronoPlayersRegistry();

  return Object.values(registry || {})
    .map(player => {
      const displayName = String(player?.name || "").trim();
      return displayName ? { displayName, source: "local" } : null;
    })
    .filter(Boolean);
}

function mergeChronoKnownPlayers(players){
  const map = new Map();

  // Important : les suggestions doivent venir du fichier serveur
  // privatedata/players-profiles.json uniquement.
  // Sinon, un prénom supprimé dans admin-profils pouvait rester visible
  // à cause du localStorage du navigateur.
  (players || []).forEach(player => {
    const displayName = String(
      player?.displayName ||
      `${player?.name || ""} ${player?.initials || ""}`
    ).trim().replace(/\s+/g, " ");

    if(!displayName) return;

    const key = normalizeChronoPlayerKey(displayName);
    if(!key || map.has(key)) return;

    map.set(key, {
      displayName,
      avatar: player?.avatar || "",
      source: "server"
    });
  });

  return Array.from(map.values()).sort((a, b) => a.displayName.localeCompare(b.displayName, "fr"));
}

async function loadChronoKnownPlayers(force = false){
  if(chronoKnownPlayersLoaded && !force){
    chronoKnownPlayersCache = mergeChronoKnownPlayers(chronoKnownPlayersCache);
    return chronoKnownPlayersCache;
  }

  if(chronoKnownPlayersLoading && !force){
    return chronoKnownPlayersLoading;
  }

  chronoKnownPlayersLoading = fetch(`${PLAYER_PROFILES_LIST_URL}?owner_key=${encodeURIComponent(getChronoPlayerOwnerKey())}`, { cache: "no-store" })
    .then(response => response.ok ? response.json() : { ok:false, players:[] })
    .then(data => {
      const serverPlayers = Array.isArray(data?.players) ? data.players : [];
      chronoKnownPlayersCache = mergeChronoKnownPlayers(serverPlayers);
      chronoKnownPlayersLoaded = true;
      return chronoKnownPlayersCache;
    })
    .catch(() => {
      chronoKnownPlayersCache = mergeChronoKnownPlayers([]);
      chronoKnownPlayersLoaded = true;
      return chronoKnownPlayersCache;
    })
    .finally(() => {
      chronoKnownPlayersLoading = null;
    });

  return chronoKnownPlayersLoading;
}

function getRandomPlayersTextareaNames(){
  return parsePlayersList($("randomPlayersInput")?.value || "").map(formatPlayerIdentity).filter(Boolean);
}

function addKnownPlayerToRandomTextarea(displayName){
  const input = $("randomPlayersInput");
  if(!input) return;

  const cleanName = formatPlayerIdentity(displayName);
  const existing = getRandomPlayersTextareaNames().map(normalizeChronoPlayerKey);

  if(!cleanName || existing.includes(normalizeChronoPlayerKey(cleanName))){
    input.focus();
    return;
  }

  const current = String(input.value || "").trim();
  input.value = current ? `${current}\n${cleanName}` : cleanName;
  input.dispatchEvent(new Event("input", { bubbles:true }));
  input.focus();

  renderChronoKnownPlayersSuggestions();
}

function ensureChronoKnownPlayersSuggestionsBox(){
  const input = $("randomPlayersInput");
  if(!input) return null;

  let box = $("chronoKnownPlayersSuggestions");
  if(box) return box;

  box = document.createElement("div");
  box.id = "chronoKnownPlayersSuggestions";
  box.style.marginTop = "10px";
  box.style.display = "none";

  input.insertAdjacentElement("afterend", box);
  return box;
}

function renderChronoKnownPlayersSuggestions(){
  const input = $("randomPlayersInput");
  const box = ensureChronoKnownPlayersSuggestionsBox();
  if(!input || !box) return;

  const players = mergeChronoKnownPlayers(chronoKnownPlayersCache);
  const existingKeys = new Set(getRandomPlayersTextareaNames().map(normalizeChronoPlayerKey));
  const available = players.filter(player => !existingKeys.has(normalizeChronoPlayerKey(player.displayName))).slice(0, 18);

  if(available.length === 0){
    box.style.display = "none";
    box.innerHTML = "";
    return;
  }

  box.style.display = "block";
  box.innerHTML = `
    <div style="font-weight:800;color:#ffd34d;margin-bottom:8px;">Joueurs déjà enregistrés</div>
    <div style="display:flex;flex-wrap:wrap;gap:8px;"></div>
  `;

  const chips = box.querySelector("div:last-child");

  available.forEach(player => {
    const btn = document.createElement("button");
    btn.type = "button";
    btn.textContent = `+ ${player.displayName}`;
    btn.style.border = "1px solid rgba(255,255,255,.22)";
    btn.style.borderRadius = "999px";
    btn.style.padding = "8px 12px";
    btn.style.background = "rgba(255,255,255,.08)";
    btn.style.color = "#fff";
    btn.style.fontWeight = "800";
    btn.style.cursor = "pointer";
    btn.addEventListener("click", () => addKnownPlayerToRandomTextarea(player.displayName));
    chips.appendChild(btn);
  });
}

async function refreshChronoKnownPlayersSuggestions(force = false){
  await loadChronoKnownPlayers(force);
  renderChronoKnownPlayersSuggestions();
}

function initChronoKnownPlayersSuggestions(){
  const input = $("randomPlayersInput");
  if(!input) return;

  ensureChronoKnownPlayersSuggestionsBox();

  input.addEventListener("focus", () => refreshChronoKnownPlayersSuggestions(true));
  input.addEventListener("input", () => renderChronoKnownPlayersSuggestions());

  refreshChronoKnownPlayersSuggestions(true);
}


function getManualPlayerInputNames(exceptInput = null){
  return Array.from(document.querySelectorAll('input[id^="playerName_"]'))
    .filter(input => input !== exceptInput)
    .map(input => formatPlayerIdentity(input.value || ""))
    .filter(Boolean);
}

function ensureChronoManualPlayerSuggestionsBox(input){
  if(!input || !input.id) return null;

  let box = document.getElementById("chronoKnownPlayersSuggestions_" + input.id);
  if(box) return box;

  box = document.createElement("div");
  box.id = "chronoKnownPlayersSuggestions_" + input.id;
  box.className = "chronoKnownPlayersManualSuggestions";
  box.style.marginTop = "8px";
  box.style.display = "none";

  input.insertAdjacentElement("afterend", box);
  return box;
}

function renderChronoManualPlayerSuggestions(input){
  if(!input || !String(input.id || "").startsWith("playerName_")) return;

  const box = ensureChronoManualPlayerSuggestionsBox(input);
  if(!box) return;

  const players = mergeChronoKnownPlayers(chronoKnownPlayersCache);
  const currentKey = normalizeChronoPlayerKey(input.value || "");
  const usedKeys = new Set(getManualPlayerInputNames(input).map(normalizeChronoPlayerKey));

  const available = players
    .filter(player => {
      const key = normalizeChronoPlayerKey(player.displayName);
      if(!key) return false;
      if(key === currentKey) return false;
      if(usedKeys.has(key)) return false;
      return true;
    })
    .slice(0, 12);

  if(available.length === 0){
    box.style.display = "none";
    box.innerHTML = "";
    return;
  }

  box.style.display = "block";
  box.innerHTML = `
    <div style="font-weight:800;color:#ffd34d;margin-bottom:7px;font-size:13px;">Joueurs déjà enregistrés</div>
    <div style="display:flex;flex-wrap:wrap;gap:7px;"></div>
  `;

  const chips = box.querySelector("div:last-child");

  available.forEach(player => {
    const btn = document.createElement("button");
    btn.type = "button";
    btn.textContent = `+ ${player.displayName}`;
    btn.style.border = "1px solid rgba(255,255,255,.22)";
    btn.style.borderRadius = "999px";
    btn.style.padding = "7px 10px";
    btn.style.background = "rgba(255,255,255,.08)";
    btn.style.color = "#fff";
    btn.style.fontWeight = "800";
    btn.style.cursor = "pointer";
    btn.addEventListener("mousedown", (event) => {
      event.preventDefault();
      input.value = formatPlayerIdentity(player.displayName);
      input.dispatchEvent(new Event("input", { bubbles:true }));
      input.dispatchEvent(new Event("change", { bubbles:true }));
      box.style.display = "none";
      window.setTimeout(refreshSetupUX, 0);
      input.focus();
    });
    chips.appendChild(btn);
  });
}

function initChronoManualPlayerSuggestions(root = document){
  const inputs = Array.from(root.querySelectorAll ? root.querySelectorAll('input[id^="playerName_"]') : []);

  inputs.forEach(input => {
    if(input.dataset.chronoKnownPlayersSuggestions === "1") return;
    input.dataset.chronoKnownPlayersSuggestions = "1";
    input.setAttribute("autocomplete", "off");

    input.addEventListener("focus", async () => {
      await loadChronoKnownPlayers(true);
      renderChronoManualPlayerSuggestions(input);
    });

    input.addEventListener("input", () => {
      renderChronoManualPlayerSuggestions(input);
    });

    input.addEventListener("blur", () => {
      const box = document.getElementById("chronoKnownPlayersSuggestions_" + input.id);
      if(box){
        window.setTimeout(() => { box.style.display = "none"; }, 180);
      }
    });
  });
}


function getQuickPlayerInputNames(exceptInput = null){
  return Array.from(document.querySelectorAll('input[id^="quickPlayer"]'))
    .filter(input => input !== exceptInput)
    .map(input => formatPlayerIdentity(input.value || ""))
    .filter(Boolean);
}

function ensureChronoQuickPlayerSuggestionsBox(input){
  if(!input || !input.id) return null;

  let box = document.getElementById("chronoKnownPlayersSuggestions_" + input.id);
  if(box) return box;

  box = document.createElement("div");
  box.id = "chronoKnownPlayersSuggestions_" + input.id;
  box.className = "chronoKnownPlayersQuickSuggestions";
  box.style.marginTop = "8px";
  box.style.marginBottom = "8px";
  box.style.display = "none";

  input.insertAdjacentElement("afterend", box);
  return box;
}

function renderChronoQuickPlayerSuggestions(input){
  if(!input || !String(input.id || "").startsWith("quickPlayer")) return;

  const box = ensureChronoQuickPlayerSuggestionsBox(input);
  if(!box) return;

  const players = mergeChronoKnownPlayers(chronoKnownPlayersCache);
  const currentKey = normalizeChronoPlayerKey(input.value || "");
  const usedKeys = new Set(getQuickPlayerInputNames(input).map(normalizeChronoPlayerKey));

  const available = players
    .filter(player => {
      const key = normalizeChronoPlayerKey(player.displayName);
      if(!key) return false;
      if(key === currentKey) return false;
      if(usedKeys.has(key)) return false;
      return true;
    })
    .slice(0, 12);

  if(available.length === 0){
    box.style.display = "none";
    box.innerHTML = "";
    return;
  }

  box.style.display = "block";
  box.innerHTML = `
    <div style="font-weight:800;color:#ffd34d;margin-bottom:7px;font-size:13px;">Joueurs déjà enregistrés</div>
    <div style="display:flex;flex-wrap:wrap;gap:7px;"></div>
  `;

  const chips = box.querySelector("div:last-child");

  available.forEach(player => {
    const btn = document.createElement("button");
    btn.type = "button";
    btn.textContent = `+ ${player.displayName}`;
    btn.style.border = "1px solid rgba(255,255,255,.22)";
    btn.style.borderRadius = "999px";
    btn.style.padding = "7px 10px";
    btn.style.background = "rgba(255,255,255,.08)";
    btn.style.color = "#fff";
    btn.style.fontWeight = "800";
    btn.style.cursor = "pointer";
    btn.addEventListener("mousedown", (event) => {
      event.preventDefault();
      input.value = formatPlayerIdentity(player.displayName);
      input.dispatchEvent(new Event("input", { bubbles:true }));
      input.dispatchEvent(new Event("change", { bubbles:true }));
      box.style.display = "none";
      validateQuickSetupForm();
      input.focus();
    });
    chips.appendChild(btn);
  });
}

function initChronoQuickPlayerSuggestions(root = document){
  const inputs = Array.from(root.querySelectorAll ? root.querySelectorAll('input[id^="quickPlayer"]') : []);

  inputs.forEach(input => {
    if(input.dataset.chronoKnownPlayersQuickSuggestions === "1") return;
    input.dataset.chronoKnownPlayersQuickSuggestions = "1";
    input.setAttribute("autocomplete", "off");

    input.addEventListener("focus", async () => {
      await loadChronoKnownPlayers(true);
      renderChronoQuickPlayerSuggestions(input);
    });

    input.addEventListener("input", () => {
      renderChronoQuickPlayerSuggestions(input);
    });

    input.addEventListener("blur", () => {
      const box = document.getElementById("chronoKnownPlayersSuggestions_" + input.id);
      if(box){
        window.setTimeout(() => { box.style.display = "none"; }, 180);
      }
    });
  });
}


function getGuidedPlayerInputNames(exceptInput = null){
  return Array.from(document.querySelectorAll('input[id^="guidedName"]'))
    .filter(input => input !== exceptInput)
    .map(input => formatPlayerIdentity(input.value || ""))
    .filter(Boolean);
}

function ensureChronoGuidedPlayerSuggestionsBox(input){
  if(!input || !input.id) return null;

  let box = document.getElementById("chronoKnownPlayersSuggestions_" + input.id);
  if(box) return box;

  box = document.createElement("div");
  box.id = "chronoKnownPlayersSuggestions_" + input.id;
  box.className = "chronoKnownPlayersGuidedSuggestions";
  box.style.marginTop = "8px";
  box.style.marginBottom = "8px";
  box.style.display = "none";

  input.insertAdjacentElement("afterend", box);
  return box;
}

function renderChronoGuidedPlayerSuggestions(input){
  if(!input || !String(input.id || "").startsWith("guidedName")) return;

  const box = ensureChronoGuidedPlayerSuggestionsBox(input);
  if(!box) return;

  const players = mergeChronoKnownPlayers(chronoKnownPlayersCache);
  const currentKey = normalizeChronoPlayerKey(input.value || "");
  const usedKeys = new Set(getGuidedPlayerInputNames(input).map(normalizeChronoPlayerKey));

  const available = players
    .filter(player => {
      const key = normalizeChronoPlayerKey(player.displayName);
      if(!key) return false;
      if(key === currentKey) return false;
      if(usedKeys.has(key)) return false;
      return true;
    })
    .slice(0, 12);

  if(available.length === 0){
    box.style.display = "none";
    box.innerHTML = "";
    return;
  }

  box.style.display = "block";
  box.innerHTML = `
    <div style="font-weight:800;color:#ffd34d;margin-bottom:7px;font-size:13px;">Joueurs déjà enregistrés</div>
    <div style="display:flex;flex-wrap:wrap;gap:7px;"></div>
  `;

  const chips = box.querySelector("div:last-child");

  available.forEach(player => {
    const btn = document.createElement("button");
    btn.type = "button";
    btn.textContent = `+ ${player.displayName}`;
    btn.style.border = "1px solid rgba(255,255,255,.22)";
    btn.style.borderRadius = "999px";
    btn.style.padding = "7px 10px";
    btn.style.background = "rgba(255,255,255,.08)";
    btn.style.color = "#fff";
    btn.style.fontWeight = "800";
    btn.style.cursor = "pointer";
    btn.addEventListener("mousedown", (event) => {
      event.preventDefault();
      input.value = formatPlayerIdentity(player.displayName);
      input.dispatchEvent(new Event("input", { bubbles:true }));
      input.dispatchEvent(new Event("change", { bubbles:true }));
      box.style.display = "none";
      input.focus();
    });
    chips.appendChild(btn);
  });
}

function initChronoGuidedPlayerSuggestions(root = document){
  const inputs = Array.from(root.querySelectorAll ? root.querySelectorAll('input[id^="guidedName"]') : []);

  inputs.forEach(input => {
    if(input.dataset.chronoKnownPlayersGuidedSuggestions === "1") return;
    input.dataset.chronoKnownPlayersGuidedSuggestions = "1";
    input.setAttribute("autocomplete", "off");

    input.addEventListener("focus", async () => {
      await loadChronoKnownPlayers(true);
      renderChronoGuidedPlayerSuggestions(input);
    });

    input.addEventListener("input", () => {
      renderChronoGuidedPlayerSuggestions(input);
    });

    input.addEventListener("blur", () => {
      const box = document.getElementById("chronoKnownPlayersSuggestions_" + input.id);
      if(box){
        window.setTimeout(() => { box.style.display = "none"; }, 180);
      }
    });
  });
}

const CONFIG_URL = "./api/game-config.php";
const KIDS_CONFIG_URL = "./api/game-config-kids.php";
const FAMILY_CONFIG_URL = "./api/game-config-family.php";
const START_GAME_URL = "./api/start-game.php";
const TRACK_URL = "./api/track.php";
const PLAYER_PROFILE_URL = "/api/player-profile.php";
const PLAYER_PROFILES_LIST_URL = "/api/player-profiles-list.php";



const QUICK_GAME_CONFIG = {
  teamCount: 2,
  teamMode: "manual",
  wordCount: 30,
  turnSeconds: 30,
  passesPerTurn: 5,
  difficulty: "facile",
  quickThemes: ["Objets du quotidien", "Cinéma & Séries", "Musique", "Métiers"]
};

const DEFAULT_DATA = {};

let WORD_DATA = JSON.parse(JSON.stringify(DEFAULT_DATA));
let GAME_CONFIG = {
  themes: Object.keys(DEFAULT_DATA),
  difficulties: ["facile","moyen","difficile"],
  maxWords: 80
};
const TOTAL_ROUNDS = 4;
const DEFAULT_TEAM_NAMES = [
  "Équipe violette",
  "Équipe bleue",
  "Équipe verte",
  "Équipe orange",
  "Équipe rouge",
  "Équipe jaune",
  "Équipe rose",
  "Équipe turquoise",
  "Équipe blanche",
  "Équipe noire",
  "Équipe grise",
  "Équipe dorée"
];

function getDefaultTeamName(index){
  return DEFAULT_TEAM_NAMES[index] || `Équipe ${index + 1}`;
}
function getSpokenTeamName(team){
  const teamName = String(team?.name || "").trim();

  if(!teamName){
    return "Équipe";
  }

  return /^équipe\s/i.test(teamName) || /^equipe\s/i.test(teamName)
    ? teamName
    : `Équipe ${teamName}`;
}
const state = {
  inGame: false,
  round: 1,
  teams: [],
  teamIndex: 0,
  originalDeck: [],
  deck: [],
  remaining: [],
  passesLeft: 0,
  secondsPerTurn: 30,
  timerId: null,
  timeLeft: 0,
  sound: true,
  voice: true,
  currentWord: null,
  foundThisTurn: [],
  passedThisTurn: 0,
  turnStartedAt: null,
  lastTurn: null,
  pendingRoundEnd: false,
  lastTenAnnounced: false,
  endedByTimer: false,
  teamMode: "manual",
  adaptiveMaster: true,
  nextTurnBonusSeconds: 0,
  nextTurnBonusPasses: 0,
  coop: {
    enabled: false,
    level: "standard",
    maxTurns: 0,
    endedByBoss: false
  },
gameSessionId: null,
  gameStartedAt: null
};


function isCoopMode(){
  const input = typeof $ === "function" ? $("gameMode") : null;
  return input && input.value === "coop";
}

const COOP_BOSS_LEVELS = {
  easy: {
    label: "Petit Boss",
    icon: "🥉",
    wordsPerTurn: 2.4
  },
  standard: {
    label: "Boss Chrono",
    icon: "🥈",
    wordsPerTurn: 3
  },
  hard: {
    label: "Grand Boss",
    icon: "🥇",
    wordsPerTurn: 3.5
  },
  legend: {
    label: "Boss Légende",
    icon: "🏆",
    wordsPerTurn: 4
  }
};

function getCoopBossLevelKey(){
  const value = $("coopBossLevel")?.value || "standard";
  return COOP_BOSS_LEVELS[value] ? value : "standard";
}

function getCoopBossLevel(){
  return COOP_BOSS_LEVELS[getCoopBossLevelKey()] || COOP_BOSS_LEVELS.standard;
}

function getCoopBossMaxTurnsForLevel(levelKey = getCoopBossLevelKey(), wordCount = null){
  const words = Math.max(1, Number(wordCount || state.originalDeck?.length || $("wordCount")?.value || 30));
  const level = COOP_BOSS_LEVELS[levelKey] || COOP_BOSS_LEVELS.standard;
  return Math.max(1, Math.ceil((words * TOTAL_ROUNDS) / level.wordsPerTurn));
}

function getCoopBossMaxTurns(wordCount = null){
  return getCoopBossMaxTurnsForLevel(getCoopBossLevelKey(), wordCount);
}

function getCoopTurnsUsed(){
  if(!state || !Array.isArray(state.teams)) return 0;

  return state.teams.reduce((total, team) => {
    if(Array.isArray(team.turnStats)) return total + team.turnStats.length;

    const turnsPlayed = team.turnsPlayed || {};
    return total + Object.keys(turnsPlayed).reduce((sum, key) => sum + Number(turnsPlayed[key] || 0), 0);
  }, 0);
}

function getCoopTurnsRemaining(){
  const maxTurns = Math.max(0, Number(state.coop?.maxTurns || getCoopBossMaxTurns()));
  return Math.max(0, maxTurns - getCoopTurnsUsed());
}

function getCoopTurnsLabel(){
  const maxTurns = Math.max(1, Number(state.coop?.maxTurns || getCoopBossMaxTurns()));
  const used = getCoopTurnsUsed();
  return `${used}/${maxTurns} tours`;
}

function getCoopBossRank(turnsUsed = getCoopTurnsUsed(), maxTurns = Number(state.coop?.maxTurns || getCoopBossMaxTurns())){
  const remaining = Math.max(0, Number(maxTurns || 0) - Number(turnsUsed || 0));
  if(remaining >= 20) return { icon:"🏆", label:"Légende", key:"legend" };
  if(remaining >= 10) return { icon:"🥇", label:"Or", key:"gold" };
  if(remaining >= 5) return { icon:"🥈", label:"Argent", key:"silver" };
  return { icon:"🥉", label:"Bronze", key:"bronze" };
}

function getCoopPartyPayload(){
  if(!state.coop?.enabled) return null;

  const levelKey = state.coop.level || getCoopBossLevelKey();
  const level = COOP_BOSS_LEVELS[levelKey] || COOP_BOSS_LEVELS.standard;
  const turnsUsed = getCoopTurnsUsed();
  const maxTurns = Math.max(1, Number(state.coop.maxTurns || getCoopBossMaxTurns()));
  const won = turnsUsed <= maxTurns && !state.coop.endedByBoss;
  const rank = won ? getCoopBossRank(turnsUsed, maxTurns) : { icon:"💀", label:"Défi perdu", key:"lost" };

  return {
    enabled: true,
    level: levelKey,
    label: level.label,
    icon: level.icon,
    max_turns: maxTurns,
    turns_used: turnsUsed,
    turns_remaining: Math.max(0, maxTurns - turnsUsed),
    over_by: Math.max(0, turnsUsed - maxTurns),
    won: won,
    result: won ? "victory" : "defeat",
    rank_key: rank.key,
    rank_label: rank.label,
    rank_icon: rank.icon,
    words_per_turn_target: level.wordsPerTurn
  };
}

function initCoopState(){
  const enabled = isCoopMode();
  const levelKey = getCoopBossLevelKey();

  state.coop = {
    enabled,
    level: levelKey,
    maxTurns: enabled ? getCoopBossMaxTurns(state.originalDeck?.length || 0) : 0,
    endedByBoss: false
  };

  if(enabled){
    state.adaptiveMaster = false;
  }
}

function getCoopStatusHtml(){
  if(!state.coop?.enabled) return "";

  const turnsUsed = getCoopTurnsUsed();
  const maxTurns = Math.max(1, Number(state.coop.maxTurns || getCoopBossMaxTurns()));
  const pct = Math.min(100, Math.round((turnsUsed / maxTurns) * 100));
  const level = COOP_BOSS_LEVELS[state.coop.level] || COOP_BOSS_LEVELS.standard;

  return `
    <div class="coopBossHud">
      <div class="coopBossHudTop">
        <span>${level.icon} ${escapeHtml(level.label)}</span>
        <strong>${turnsUsed}/${maxTurns} tours</strong>
      </div>
      <div class="coopBossHudBar"><span style="width:${pct}%"></span></div>
      <div class="coopBossHudText">Objectif : finir les 4 manches en ${maxTurns} tours maximum. Tours restants : ${Math.max(0, maxTurns - turnsUsed)}.</div>
    </div>
  `;
}

function getCoopConfiguredPlayersFromInputs(){
  const mode = $("teamMode")?.value || "random";

  if(mode === "random"){
    return parsePlayersList($("randomPlayersInput")?.value || "").map(formatPlayerIdentity).filter(Boolean);
  }

  const teamCount = clamp(parseInt($("teamCount")?.value || "2", 10), 2, 12);
  const players = [];

  for(let i = 0; i < teamCount; i++){
    const playerCount = clamp(parseInt($("playerCount_" + i)?.value || "2", 10), 2, 12);

    for(let j = 0; j < playerCount; j++){
      const value = formatPlayerIdentity(($("playerName_" + i + "_" + j)?.value || "").trim());
      if(value) players.push(value);
    }
  }

  return players;
}

async function initTeamsCoopMode(){
  const allPlayers = getCoopConfiguredPlayersFromInputs();
  const invalidPlayer = allPlayers.find(name => !isValidPlayerIdentity(name));

  if(allPlayers.length < 2){
    showSimpleModal(
      "Joueurs insuffisants",
      "Le mode coopératif nécessite au moins 2 joueurs.",
      "OK"
    );
    return false;
  }

  if(invalidPlayer){
    showSimpleModal(
      "Nom incomplet",
      `Merci d’indiquer le prénom suivi de l’initiale du nom. Exemple : Bruno D<br><br>À corriger : <b>${escapeHtml(invalidPlayer)}</b>`,
      "OK"
    );
    return false;
  }

  try{
    await resolveChronoPlayerProfiles(allPlayers);
  }catch(error){
    showSimpleModal(
      "Profil joueur indisponible",
      "Impossible de vérifier les profils joueurs sur le serveur. Vérifiez la connexion puis réessayez.<br><br>" + escapeHtml(error.message || "Erreur inconnue"),
      "OK"
    );
    return false;
  }

  const team = createTeamObject("Groupe Coop", allPlayers);
  team.playerIndex = pickRandomIndex(team.players.length);
  state.teams = [team];
  return true;
}

function shouldEndCoopByTurns(){
  if(!state.coop?.enabled) return false;
  return getCoopTurnsUsed() >= Number(state.coop.maxTurns || 0);
}

function endCoopByTurns(){
  state.inGame = false;
  state.coop.endedByBoss = true;
  clearAdaptiveBonuses();
  clearInterval(state.timerId);
  hideTurnModal();
  clearSavedGame();
  showFinalOverlay();
}

let modalReturnStack = [];
let audioCtx = null;
let turnActionLocked = false;
let heartbeatTimerId = null;
let pageLeaveTracked = false;
let loginRedirectScheduled = false;
let accessErrorLocked = false;
let voiceTimeoutId = null;

function redirectToLogin(){
  if(loginRedirectScheduled) return;
  loginRedirectScheduled = true;
  window.setTimeout(() => {
    window.location.href = "./login/";
  }, 150);
}

function isAuthenticationErrorMessage(message){
  const normalized = String(message || "")
    .toLowerCase()
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "");

  return normalized === "acces non authentifie" || normalized === "session expiree";
}

function legacyShowAccessError(message){
  if(message === "Accès non authentifié"){
    accessErrorLocked = true;
    $("remoteInfo").textContent = "⚠️ Accès non authentifié. Reconnectez-vous.";
    return true;
  }

  if(message === "Session expirée"){
    accessErrorLocked = true;
    $("remoteInfo").textContent = "⚠️ Session expirée.";
    return true;
  }

  return false;
}

function showAccessError(message){
  accessErrorLocked = isAuthenticationErrorMessage(message);
  if(!accessErrorLocked){
    return false;
  }

  $("remoteInfo").textContent = "Connexion requise. Redirection vers la connexion...";
  return true;
}

function trackChronoParty(action, details = ""){
  try{
    fetch(TRACK_URL, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      credentials: "same-origin",
      keepalive: true,
      body: JSON.stringify({
        action,
        details,
        page: location.pathname
      })
    })
      .then(() => {})
      .catch(()=>{});
  }catch(e){}
}

function getTrackingContext(){
  if(!state.inGame){
    return "hors_partie";
  }

  return `manche=${state.round} | equipe=${getCurrentTeam()?.name || "-"} | joueur=${getCurrentPlayerName()} | restants=${state.remaining.length}`;
}

function sendTrackingBeacon(action, details = ""){
  try{
    if(pageLeaveTracked && action === "fermeture_page") return;
    if(action === "fermeture_page") pageLeaveTracked = true;

    const payload = JSON.stringify({
      action,
      details,
      page: location.pathname
    });

    if(navigator.sendBeacon){
      const blob = new Blob([payload], { type: "application/json" });
      navigator.sendBeacon(TRACK_URL, blob);
      return;
    }
  }catch(e){}

  trackChronoParty(action, details);
}

function $(id){ return document.getElementById(id); }
function clamp(n,min,max){ return Math.max(min, Math.min(max,n)); }

function sleep(ms){
  return new Promise(resolve => window.setTimeout(resolve, ms));
}

async function requestJson(url, options = {}, timeoutMs = 12000){
  const controller = new AbortController();
  const timer = window.setTimeout(()=> controller.abort(), timeoutMs);

  try{
    const response = await fetch(url, {
      cache: "no-store",
      credentials: "same-origin",
      ...options,
      signal: controller.signal
    });

    const text = await response.text();
    let data = null;

    try{
      data = text ? JSON.parse(text) : null;
    }catch(parseError){}

    return { response, data, text };
  }catch(error){
    if(error?.name === "AbortError"){
      throw new Error("Le serveur met trop de temps à répondre.");
    }
    throw error;
  }finally{
    window.clearTimeout(timer);
  }
}

function shuffle(arr){
  const a = arr.slice();
  for(let i=a.length-1;i>0;i--){
    const j = Math.floor(Math.random()*(i+1));
    [a[i],a[j]]=[a[j],a[i]];
  }
  return a;
}
function mixDeckByTheme(deck){
  const list = shuffle(deck || []);
  const groups = {};

  list.forEach(card => {
    const theme = card?.theme || "Autre";
    if(!groups[theme]) groups[theme] = [];
    groups[theme].push(card);
  });

  const themes = Object.keys(groups);
  const mixed = [];
  let lastTheme = null;

  while(mixed.length < list.length){
    const availableThemes = themes
      .filter(theme => groups[theme].length > 0)
      .sort((a, b) => groups[b].length - groups[a].length);

    if(!availableThemes.length) break;

    let chosenTheme = availableThemes.find(theme => theme !== lastTheme);

    if(!chosenTheme){
      chosenTheme = availableThemes[0];
    }

    const card = groups[chosenTheme].shift();
    mixed.push(card);
    lastTheme = chosenTheme;
  }

  return mixed;
}
function escapeHtml(s){
  return String(s).replace(/[&<>"']/g, m => ({
    '&':'&amp;', '<':'&lt;', '>':'&gt;', '"':'&quot;', "'":'&#039;'
  }[m]));
}

function pickRandomIndex(max){
  if(max <= 1) return 0;
  return Math.floor(Math.random() * max);
}

function allRoundsArray(){
  return Array.from({length: TOTAL_ROUNDS}, (_,i)=>i+1);
}

function stopCurrentTurnTimer(){
  clearInterval(state.timerId);
  state.timerId = null;
}

function closeAllOverlays(){
  $("modalBackdrop").style.display = "none";
  $("modalBackdrop").classList.remove("simpleModal");

  $("turnOverlay").style.display = "none";
  $("finalOverlay").style.display = "none";
  if($("installHelpModal")){
  $("installHelpModal").style.display = "none";
}
  $("quickSetupModal").style.display = "none";

  modalReturnStack = [];
  $("modalPill").classList.remove("pillBig");
}

function showScreen(screenId){
  ["homeScreen","rulesScreen","configScreen"].forEach(id=>{
    $(id).classList.toggle("active", id === screenId);
  });

  const mobileBtn = document.querySelector(".mobileStartButton");
  if(mobileBtn){
    mobileBtn.style.display = screenId === "configScreen" && !state.inGame ? "" : "none";
  }
}

function forceGoHome(){
  stopCurrentTurnTimer();
  stopVoice();
  closeAllOverlays();
  state.inGame = false;
  showScreen("homeScreen");

  window.setTimeout(() => {
    if(typeof refreshSetupUX === "function"){
      refreshSetupUX();
    }else if(typeof updateStartButtonsState === "function"){
      updateStartButtonsState();
    }
  }, 0);
}

function cancelGameAndReturnConfig(){
  stopVoice();

  trackChronoParty(
    "annulation_partie",
    `manche=${state.round} | equipe=${getCurrentTeam()?.name || "-"}`
  );

  stopCurrentTurnTimer();
  closeAllOverlays();

  state.inGame = false;
  state.currentWord = null;
  state.foundThisTurn = [];
  state.lastTurn = null;
  state.pendingRoundEnd = false;
  state.endedByTimer = false;
  turnActionLocked = false;

  showScreen("configScreen");

window.setTimeout(() => {
  if(typeof refreshSetupUX === "function"){
    refreshSetupUX();
  }else if(typeof updateStartButtonsState === "function"){
    updateStartButtonsState();
  }
}, 0);
}

function goHome(){
  if(state.inGame){
    askHomeDuringGame();
    return;
  }
  forceGoHome();
}

function goRules(){
  stopCurrentTurnTimer();
  closeAllOverlays();
  showScreen("rulesScreen");
}

function goConfig(){
  stopCurrentTurnTimer();
  closeAllOverlays();
  showScreen("configScreen");

  window.setTimeout(() => {
    if(typeof refreshSetupUX === "function"){
      refreshSetupUX();
    }else if(typeof updateStartButtonsState === "function"){
      updateStartButtonsState();
    }
  }, 0);
}

function startQuickGame(){
  openQuickSetupModal();
}

function roundLabel(r){
  if(r===1) return "🟢 Manche 1 — Description libre";
  if(r===2) return "🟡 Manche 2 — Un seul mot";
  if(r===3) return "🔵 Manche 3 — Mime";
  return "🟣 Manche 4 — Statue sonore";
}

function roundDesc(r){
  if(r===1) return "On peut parler librement (sans dire le mot exact).";
  if(r===2) return "Un seul mot autorisé (répétable), pas de phrase.";
  if(r===3) return "Mime uniquement : silence total.";
  return "Statue sonore : prenez une pose fixe (ne bougez plus) et utilisez uniquement des bruitages ou fredonnements à volonté pour aider votre équipe sans parler.";
}

function roundRuleShort(r){
  if(r===1) return "Description libre";
  if(r===2) return "1 seul mot";
  if(r===3) return "Mime";
  return "Statue sonore";
}

function modalRoundBoxHtml(){
  return `
    <div class="modalRoundBox">
      <div class="k">Manche en cours</div>
      <div class="v">${roundLabel(state.round)}</div>
      <div class="d">${roundDesc(state.round)}</div>
    </div>
  `;
}

function getCurrentTeam(){
  return state.teams[state.teamIndex] || null;
}

function getPlayerName(team, idx){
  if(!team || !Array.isArray(team.players) || !team.players.length) return "Joueur";
  return team.players[idx] || "Joueur";
}

function getCurrentPlayerName(team = getCurrentTeam()){
  if(!team) return "Joueur";
  return getPlayerName(team, team.playerIndex || 0);
}

function getSpokenPlayerName(name){
  return String(name || "")
    .trim()
    .replace(/\s+[A-Za-zÀ-ÖØ-öø-ÿ]{1,2}\.?$/, "");
}
function formatPlayerIdentity(name){
  const clean = String(name || "")
    .trim()
    .toLowerCase()
    .replace(/\s+/g, " ");

  const parts = clean.split(" ");
  const last = parts.pop() || "";
  const firstNames = parts.join(" ");

  const formattedFirstNames = firstNames
    .split(" ")
    .map(part => part
      .split("-")
      .map(piece => piece.charAt(0).toUpperCase() + piece.slice(1))
      .join("-")
    )
    .join(" ");

  return `${formattedFirstNames} ${last.toUpperCase().replace(".", "")}`.trim();
}
function resetModalOkButton(){
  const btn = $("modalOk");
  if(!btn) return;

  btn.onpointerdown = null;
  btn.disabled = false;
  btn.style.display = "";
  btn.classList.remove("hidden");
}
function advancePlayerForTeam(teamIndex){
  const team = state.teams[teamIndex];
  if(!team || !Array.isArray(team.players) || !team.players.length) return;
  team.playerIndex = (team.playerIndex + 1) % team.players.length;
}

function ensureAudio(){
  if(!state.sound) return null;
  if(!audioCtx){
    audioCtx = new (window.AudioContext || window.webkitAudioContext)();
  }
  if(audioCtx.state === "suspended"){
    audioCtx.resume().catch(()=>{});
  }
  return audioCtx;
}

function beep(type){
  if(!state.sound) return;

  const ctx = ensureAudio();
  if(!ctx) return;

  try{
    const now = ctx.currentTime;
    const o = ctx.createOscillator();
    const g = ctx.createGain();

    let freq = 440;
    let duration = 0.16;
    let volume = 0.08;

    if(type === "good"){
      freq = 720;
      duration = 0.16;
      volume = 0.10;
    }else if(type === "bad"){
      freq = 220;
      duration = 0.18;
      volume = 0.09;
    }else if(type === "tick"){
      freq = 520;
      duration = 0.08;
      volume = 0.06;
    }else if(type === "ding"){
      freq = 880;
      duration = 0.24;
      volume = 0.10;
    }

    o.type = "sine";
    o.frequency.setValueAtTime(freq, now);

    g.gain.setValueAtTime(0.0001, now);
    g.gain.exponentialRampToValueAtTime(volume, now + 0.01);
    g.gain.exponentialRampToValueAtTime(0.0001, now + duration);

    o.connect(g);
    g.connect(ctx.destination);

    o.start(now);
    o.stop(now + duration + 0.02);
  }catch(e){
    console.warn("Erreur son :", e);
  }
}

function playCoopFinalSound(success){
  if(!state.sound) return;

  const ctx = ensureAudio();
  if(!ctx) return;

  const notes = success
    ? [
        { f: 523.25, t: 0.00, d: 0.12, v: 0.09 },
        { f: 659.25, t: 0.14, d: 0.12, v: 0.09 },
        { f: 783.99, t: 0.28, d: 0.18, v: 0.10 },
        { f: 1046.50, t: 0.50, d: 0.30, v: 0.11 }
      ]
    : [
        { f: 220.00, t: 0.00, d: 0.22, v: 0.10 },
        { f: 185.00, t: 0.24, d: 0.24, v: 0.10 },
        { f: 146.83, t: 0.52, d: 0.38, v: 0.11 }
      ];

  try{
    const start = ctx.currentTime;

    notes.forEach(note => {
      const o = ctx.createOscillator();
      const g = ctx.createGain();
      const noteStart = start + note.t;
      const noteEnd = noteStart + note.d;

      o.type = success ? "triangle" : "sawtooth";
      o.frequency.setValueAtTime(note.f, noteStart);

      g.gain.setValueAtTime(0.0001, noteStart);
      g.gain.exponentialRampToValueAtTime(note.v, noteStart + 0.02);
      g.gain.exponentialRampToValueAtTime(0.0001, noteEnd);

      o.connect(g);
      g.connect(ctx.destination);

      o.start(noteStart);
      o.stop(noteEnd + 0.03);
    });
  }catch(e){
    console.warn("Erreur son final Coop :", e);
  }
}

/* VOIX D’ANNONCE — DÉBUT */
function stopVoice(){
  try{
    window.clearTimeout(voiceTimeoutId);
    voiceTimeoutId = null;

    if("speechSynthesis" in window){
      window.speechSynthesis.cancel();
    }
  }catch(e){}
}

function speakChrono(text){
  if(!state.voice) return;
  if(!("speechSynthesis" in window)) return;

  try{
    window.speechSynthesis.cancel();

   const utterance = new SpeechSynthesisUtterance(text);
utterance.lang = "fr-FR";
utterance.rate = 1.05;
utterance.pitch = 1;
utterance.volume = 1;

const voices = window.speechSynthesis.getVoices();

const preferredVoice =
  voices.find(v => v.lang === "fr-FR" && /audrey|amelie|thomas|google|microsoft|hortense|eloquence/i.test(v.name)) ||
  voices.find(v => v.lang === "fr-FR") ||
  voices.find(v => String(v.lang || "").toLowerCase().startsWith("fr"));

if(preferredVoice){
  utterance.voice = preferredVoice;
}

    window.speechSynthesis.speak(utterance);
  }catch(e){}
}

if("speechSynthesis" in window){
  window.speechSynthesis.onvoiceschanged = () => {
    window.speechSynthesis.getVoices();
  };
}

function scheduleVoiceAnnouncement(callback, delay = 500){
  window.clearTimeout(voiceTimeoutId);

  voiceTimeoutId = window.setTimeout(() => {
    voiceTimeoutId = null;

    if (
      state.inGame &&
      $("modalBackdrop").style.display === "flex" &&
      $("turnOverlay").style.display !== "flex" &&
      $("finalOverlay").style.display !== "flex" &&
      typeof callback === "function"
    ) {
      callback();
    }
  }, delay);
}

function cancelVoiceAnnouncement(){
  window.clearTimeout(voiceTimeoutId);
  voiceTimeoutId = null;
  stopVoice();
}


function announceReadyToStart(){
  const team = getCurrentTeam();
  const player = getSpokenPlayerName(getCurrentPlayerName(team));

  if(!team || !player) return;

  const spokenTeamName = getSpokenTeamName(team);

  if(state.coop?.enabled){
    const turnsUsed = getCoopTurnsUsed();
    const maxTurns = Number(state.coop.maxTurns || getCoopBossMaxTurns());
    speakChrono(
      `Manche ${state.round} : ${roundRuleShort(state.round)}. ` +
      `${player}, à toi... ` +
      `${turnsUsed} tours utilisés sur ${maxTurns} pour battre Chrono Boss.`
    );
    return;
  }

  speakChrono(
    `${spokenTeamName}, préparez-vous. ` +
    `${player}, c’est à toi de faire deviner. ` +
    `Manche ${state.round}, ${roundRuleShort(state.round)}. ` +
    `Appuie sur démarrer le tour quand tu es prêt.`
  );
}

function announceGo(){
  speakChrono("C’est parti !");
}

function announceTimeUp(){
  speakChrono("Temps écoulé.");
}

function announceFound(){
  speakChrono("Trouvé.");
}

function announcePass(){
  speakChrono("Passé.");
}

function getAdaptiveTeamName(){
  if(state && state.teams && state.teams[state.teamIndex] && state.teams[state.teamIndex].name){
    return state.teams[state.teamIndex].name;
  }

  return "Cette équipe";
}

function randomItem(arr){
  return arr[Math.floor(Math.random() * arr.length)];
}

function getAdaptiveMasterComment(foundThisTurn, passedThisTurn, teamName){

  if(foundThisTurn >= 6){
    return randomItem([
      `${teamName}, vous êtes trop forts !`,
      `${teamName}, quel rythme incroyable !`,
      `${teamName}, le maître du jeu est impressionné !`
    ]);
  }

  if(passedThisTurn >= 3){
    return randomItem([
      `${teamName}, tour difficile, rien n’est perdu.`,
      `${teamName}, beaucoup de passes sur ce tour.`,
      `${teamName}, le prochain tour peut tout changer.`
    ]);
  }

  if(foundThisTurn <= 1){
    return randomItem([
      `${teamName}, tour compliqué.`,
      `${teamName}, ça arrive même aux meilleures équipes.`,
      `${teamName}, la prochaine manche sera meilleure.`
    ]);
  }

  if(foundThisTurn >= 3 && foundThisTurn <= 5){
    return randomItem([
      `${teamName}, bon rythme !`,
      `${teamName}, belle manche.`,
      `${teamName}, ça joue bien !`
    ]);
  }

  return "";
}


function getAdaptiveDecisionContext(team){
  if(!team || !Array.isArray(state.teams) || state.teams.length < 2){
    return null;
  }

  const scores = state.teams.map(t => {
    return Object.values(t.found || {}).reduce((total, words) => {
      return total + (Array.isArray(words) ? words.length : 0);
    }, 0);
  });

  if(!scores.length) return null;

  const teamScore = Object.values(team.found || {}).reduce((total, words) => {
    return total + (Array.isArray(words) ? words.length : 0);
  }, 0);

  const bestScore = Math.max(...scores);
  const worstScore = Math.min(...scores);

  return {
    teamScore,
    bestScore,
    worstScore,
    teamBehind: bestScore - teamScore,
    teamAhead: teamScore - worstScore,
    teamTotalTurns: getTeamTotalTurnsPlayed(team),
    everyonePlayedAtLeastOnceInGame: hasEveryTeamPlayedAtLeastTotal(1)
  };
}

function canUseAdaptiveType(team, type){
  if(!team) return false;

  const currentTurnCount = getTeamTotalTurnsPlayed(team);
  const lastAnyTurnCount = parseInt(team.lastAdaptiveAnyTurnCount || 0, 10);
  const lastType = team.lastAdaptiveType || null;
  const lastTypeTurnCount = parseInt(team.lastAdaptiveTurnCount || 0, 10);

  // Règle humaine : pas d'intervention IA deux tours de suite pour la même équipe.
  // Une équipe doit rejouer au moins 2 vrais tours avant de recevoir un nouveau bonus/malus.
  if(lastAnyTurnCount > 0 && (currentTurnCount - lastAnyTurnCount) < 2){
    return false;
  }

  // Même type de correction : on garde aussi une respiration minimale.
  if(lastType === type && lastTypeTurnCount > 0 && (currentTurnCount - lastTypeTurnCount) < 2){
    return false;
  }

  return true;
}

function rememberAdaptiveType(team, type){
  if(!team) return;
  const currentTurnCount = getTeamTotalTurnsPlayed(team);
  team.lastAdaptiveType = type;
  team.lastAdaptiveTurnCount = currentTurnCount;
  team.lastAdaptiveAnyType = type;
  team.lastAdaptiveAnyTurnCount = currentTurnCount;
}

function clearAdaptiveBonuses(){
  if(!Array.isArray(state.teams)) return;

  state.teams.forEach(team => {
    if(!team) return;
    team.nextTurnBonusSeconds = 0;
    team.nextTurnBonusPasses = 0;
  });
}


function hasEveryTeamPlayedAtLeastInRound(round, minimumTurns){
  if(!Array.isArray(state.teams) || state.teams.length < 2) return false;

  return state.teams.every(t => {
    return (t.turnsPlayed?.[round] || 0) >= minimumTurns;
  });
}

function getTeamTotalTurnsPlayed(team){
  if(!team || !team.turnsPlayed) return 0;

  return Object.values(team.turnsPlayed).reduce((total, value) => {
    const n = parseInt(value || 0, 10);
    return total + (Number.isFinite(n) ? n : 0);
  }, 0);
}

function hasEveryTeamPlayedAtLeastTotal(minimumTurns){
  if(!Array.isArray(state.teams) || state.teams.length < 2) return false;

  return state.teams.every(team => {
    return getTeamTotalTurnsPlayed(team) >= minimumTurns;
  });
}



function getAdaptiveAheadThreshold(){
  const totalRounds = typeof TOTAL_ROUNDS !== "undefined" ? TOTAL_ROUNDS : 4;

  // IA plus humaine : en manche 4, on ne pénalise une équipe que si
  // elle domine vraiment. Un écart de 6 points reste encore rattrapable.
  return state && state.round >= totalRounds ? 10 : 6;
}

function isAdaptiveScoreAdjustmentLockedForEndgame(){
  if(!state || !Array.isArray(state.teams) || state.teams.length < 2) return false;

  const totalRounds = typeof TOTAL_ROUNDS !== "undefined" ? TOTAL_ROUNDS : 4;
  const remainingWords = Array.isArray(state.remaining) ? state.remaining.length : 999;
  const limit = state.teams.length * 2;

  // IA humaine : en toute fin de manche 4, le maître adaptatif garde ses phrases,
  // mais ne donne plus de bonus/malus pour ne pas influencer artificiellement le vainqueur.
  return state.round >= totalRounds && remainingWords <= limit;
}

function prepareAdaptiveBonusForTeam(team){
  if(state.adaptiveMaster === false) return;
  if(!team || !Array.isArray(state.teams) || state.teams.length < 2) return;

  const ctx = getAdaptiveDecisionContext(team);
  if(!ctx || !ctx.everyonePlayedAtLeastOnceInGame) return;

  const scoreAdjustmentLocked = isAdaptiveScoreAdjustmentLockedForEndgame();
  if(scoreAdjustmentLocked) return;

  // Si un bonus/malus est déjà prévu pour cette équipe, on le garde.
  if((team.nextTurnBonusSeconds || 0) !== 0 || (team.nextTurnBonusPasses || 0) !== 0){
    return;
  }

  // Aide score : seulement si l'équipe est vraiment distancée au score total.
  if(!scoreAdjustmentLocked && ctx.teamBehind >= 6 && ctx.teamAhead < 6 && canUseAdaptiveType(team, "behind")){
    team.nextTurnBonusSeconds = 10;
    team.nextTurnBonusPasses = 1;
    rememberAdaptiveType(team, "behind");
    return;
  }

  // Malus score : plus prudent. Il faut une avance réelle ET au moins 3 tours joués par cette équipe.
  if(!scoreAdjustmentLocked && ctx.teamAhead >= getAdaptiveAheadThreshold() && ctx.teamTotalTurns >= 3 && canUseAdaptiveType(team, "ahead")){
    const basePasses = clamp(parseInt($("passesPerTurn").value || "0", 10), 0, 10);
    team.nextTurnBonusSeconds = -5;
    team.nextTurnBonusPasses = basePasses > 0 ? -1 : 0;
    rememberAdaptiveType(team, "ahead");
  }
}

function prepareAdaptiveBonusForCurrentTeam(){
  prepareAdaptiveBonusForTeam(getCurrentTeam());
}

function buildAdaptiveTurnInfoHtml(team){
  if(!state.adaptiveMaster) return "";

  const bonusSeconds = team?.nextTurnBonusSeconds || 0;
  const bonusPasses = team?.nextTurnBonusPasses || 0;

  if(bonusSeconds !== 0 || bonusPasses !== 0){
    return `
      <div class="turnIaInfo">
        Maître adaptatif :
        ${bonusSeconds > 0 ? "+" : ""}${bonusSeconds} sec
        ${bonusPasses !== 0 ? ` / ${bonusPasses > 0 ? "+" : ""}${bonusPasses} pass` : ""}
      </div>
    `;
  }

  return `<div class="turnIaInfo">Maître adaptatif actif</div>`;
}

function announceAdaptiveMasterComment(foundThisTurn, passedThisTurn){
  if(state.adaptiveMaster === false) return;
  if(!state.inGame) return;
  if(state.pendingRoundEnd) return;
  if(!state.remaining || state.remaining.length === 0) return;
  if($("finalOverlay") && $("finalOverlay").style.display === "flex") return;

  const team = state.teams[state.lastTurn?.teamIndex ?? state.teamIndex];
  if(!team) return;

  const ctx = getAdaptiveDecisionContext(team);
  if(!ctx || !ctx.everyonePlayedAtLeastOnceInGame) return;

  const teamName = team.name || "Cette équipe";
  const scoreAdjustmentLocked = isAdaptiveScoreAdjustmentLockedForEndgame();

  // Équipe très en retard au score total : vraie aide.
  if(!scoreAdjustmentLocked && ctx.teamBehind >= 6 && ctx.teamAhead < 6 && canUseAdaptiveType(team, "behind")){
    team.nextTurnBonusSeconds = 10;
    team.nextTurnBonusPasses = 1;
    rememberAdaptiveType(team, "behind");

    speakChrono(randomItem([
      `${teamName}, petit coup de pouce ! Vous gagnez 10 secondes et un passe au prochain tour.`,
      `${teamName}, vous êtes un peu distancés ! On vous ajoute 10 secondes et un passe au prochain tour.`,
      `${teamName}, l’IA adaptative vous aide un peu ! Plus 10 secondes et un passe au prochain tour.`
    ]));
    return;
  }

  // Équipe très en avance au score total : petit malus, mais pas trop tôt.
  if(!scoreAdjustmentLocked && ctx.teamAhead >= getAdaptiveAheadThreshold() && ctx.teamTotalTurns >= 3 && canUseAdaptiveType(team, "ahead")){
    const canRemovePass = clamp(parseInt($("passesPerTurn").value || "0", 10), 0, 10) > 0;

    team.nextTurnBonusSeconds = -5;
    team.nextTurnBonusPasses = canRemovePass ? -1 : 0;
    rememberAdaptiveType(team, "ahead");

    speakChrono(randomItem(
      canRemovePass
        ? [
            `${teamName}, vous dominez la partie ! On retire 5 secondes et un passe au prochain tour.`,
            `${teamName}, vous avez pris de l’avance ! Le prochain tour sera un peu plus rapide, avec un passe en moins.`,
            `${teamName}, vous êtes devant ! Moins 5 secondes et un passe de moins au prochain tour.`
          ]
        : [
            `${teamName}, vous dominez la partie ! On retire seulement 5 secondes au prochain tour.`,
            `${teamName}, vous avez pris de l’avance ! Le prochain tour sera un peu plus rapide, avec 5 secondes en moins.`,
            `${teamName}, vous êtes devant ! Petit défi : 5 secondes en moins au prochain tour.`
          ]
    ));
    return;
  }

  // Tour très difficile : aide uniquement sur 0 ou 1 mot.
  // 2 mots + 3 passes ne donne plus de bonus automatique : cela évite que les joueurs abusent des passes.
  if(!scoreAdjustmentLocked && ctx.teamAhead < getAdaptiveAheadThreshold() && foundThisTurn <= 1 && canUseAdaptiveType(team, "weak")){
    team.nextTurnBonusSeconds = 10;
    team.nextTurnBonusPasses = 1;
    rememberAdaptiveType(team, "weak");

    speakChrono(randomItem([
      `${teamName}, tour difficile. 10 secondes et un passe bonus au prochain tour.`,
      `${teamName}, petit coup de pouce. 10 secondes et un passe bonus au prochain tour.`,
      `${teamName}, prochain tour un peu plus facile. 10 secondes et un passe bonus.`
    ]));
    return;
  }

  // Beaucoup de passes : encouragement seulement, pas d'avantage de jeu.
  if(foundThisTurn <= 2 && passedThisTurn >= 3){
    speakChrono(randomItem([
      `${teamName}, beaucoup de passes sur ce tour, mais rien n’est joué.`,
      `${teamName}, tour compliqué avec plusieurs passes. Le prochain peut tout changer.`,
      `${teamName}, on souffle un peu, et on repart au prochain tour.`
    ]));
    return;
  }

  // Très bon tour : commentaire positif seulement, le malus dépend uniquement de l'avance au score.
  if(foundThisTurn >= 6){
    speakChrono(randomItem([
      `${teamName}, quel rythme incroyable !`,
      `${teamName}, très gros tour !`,
      `${teamName}, le maître du jeu est impressionné !`
    ]));
    return;
  }

  // Bon rythme.
  if(foundThisTurn >= 3 && foundThisTurn <= 5){
    speakChrono(randomItem([
      `${teamName}, bon rythme !`,
      `${teamName}, belle manche.`,
      `${teamName}, ça joue bien !`
    ]));
  }
}

function announceTurnResult(){
  const lt = state.lastTurn;
  if(!lt) return;

  const foundCount = Math.max(0, (lt.words || []).length + (lt.adjDelta || 0));
  const remainingCount = state.remaining.length;
 const playerName = getSpokenPlayerName(String(lt.playerName || getCurrentPlayerName() || "").trim());
  const playerPrefix = playerName ? `${playerName}, ` : "";

  if(state.coop?.enabled){
    const used = getCoopTurnsUsed();
    const maxTurns = Math.max(1, Number(state.coop.maxTurns || getCoopBossMaxTurns()));
    const remainingTurns = Math.max(0, maxTurns - used);
    let bossPressureText = "";

    if(remainingTurns > 0 && remainingTurns < 5){
      bossPressureText = `Plus que ${remainingTurns} tour${remainingTurns > 1 ? "s" : ""} avant la victoire du Boss. `;
    }else if(remainingTurns > 0 && remainingTurns < 10){
      bossPressureText = `Attention, plus que ${remainingTurns} tour${remainingTurns > 1 ? "s" : ""} pour battre Chrono Boss. `;
    }

    speakChrono(
      `${foundCount} mot${foundCount > 1 ? "s" : ""} trouvé${foundCount > 1 ? "s" : ""}... ` +
      `${used} tours utilisés sur ${maxTurns}. ` +
      bossPressureText +
      `${remainingCount} mot${remainingCount > 1 ? "s" : ""} reste${remainingCount > 1 ? "nt" : ""} à faire deviner.`
    );
    return;
  }

  let resultText = "";

  if(foundCount === 0){
    resultText = `${playerPrefix}votre équipe n’a trouvé aucun mot pendant ce tour.`;
  }else if(foundCount === 1){
    resultText = `${playerPrefix}votre équipe a trouvé 1 mot pendant ce tour.`;
  }else{
    resultText = `${playerPrefix}votre équipe a trouvé ${foundCount} mots pendant ce tour.`;
  }

  speakChrono(
    `Fin du tour. ` +
    resultText + " " +
    `Il reste ${remainingCount} mot${remainingCount > 1 ? "s" : ""} à trouver.`
  );

  setTimeout(() => {
    announceAdaptiveMasterComment(foundCount, Number(state.passedThisTurn || 0));
  }, 7000);
}

function announceRoundEnd(roundJustFinished){
  recalcScores();

  if(state.coop?.enabled){
    const used = getCoopTurnsUsed();
    const maxTurns = Math.max(1, Number(state.coop.maxTurns || getCoopBossMaxTurns()));
    speakChrono(
      `Manche ${roundJustFinished} terminée... ` +
      `${used} tours utilisés sur ${maxTurns}. ` +
      `Préparez-vous pour la manche ${roundJustFinished + 1} : ${roundRuleShort(roundJustFinished + 1)}.`
    );
    return;
  }

  const ranked = state.teams
    .slice()
    .sort(compareTeams);

  const bestTeam = ranked[0];

  if(!bestTeam) return;

  const players = Array.isArray(bestTeam.players)
    ? bestTeam.players.map(p => getSpokenPlayerName(p)).filter(Boolean).join(" et ")
    : "";

  speakChrono(
    `Fin de la manche ${roundJustFinished}. ` +
    `En tête actuellement : ${getSpokenTeamName(bestTeam)}, avec ${bestTeam.total} point${bestTeam.total > 1 ? "s" : ""}. ` +
    `${players ? `Bravo à ${players}.` : ""}`
  );
}

function announceRoundFourChoice(){
  speakChrono(
    `Voulez-vous jouer la manche 4 ? ` +
    `La manche 4, Statue sonore, est souvent la plus drôle. ` +
    `Le joueur reste figé comme une statue et utilise uniquement des sons, bruitages ou fredonnements.`
  );
}

function announceFinalWinner(){
  recalcScores();

  const ranked = state.teams
    .map((t,i)=>({t,i}))
    .sort((a,b)=> compareTeams(a.t, b.t));

  if(!ranked.length) return;

  const best = ranked[0];

  const winners = ranked.filter(entry =>
    teamsAreReallyTied(entry.t, best.t)
  );

  if(winners.length > 1){
    const names = winners.map(entry => getSpokenTeamName(entry.t)).join(" et ");

    speakChrono(
      `Égalité parfaite entre ${names}, avec ${best.t.total} point${best.t.total > 1 ? "s" : ""}. ` +
      `Bravo à tous pour ce moment Chrono Party !`
    );

    return;
  }

  const players = Array.isArray(best.t.players)
  ? best.t.players.map(p => getSpokenPlayerName(p)).filter(Boolean).join(" et ")
  : "";

  speakChrono(
    `Victoire de ${getSpokenTeamName(best.t)}, avec ${best.t.total} point${best.t.total > 1 ? "s" : ""}. ` +
    `${players ? `Bravo à ${players}. ` : ""}` +
    `Bravo à tous pour ce moment Chrono Party !`
  );
}
/* VOIX D’ANNONCE — FIN */

function hapticFeedback(type){
  if(!("vibrate" in navigator)) return;

  try{
    if(type === "found"){
      navigator.vibrate([25, 20, 35]);
      return;
    }

    if(type === "pass"){
      navigator.vibrate(45);
      return;
    }

    navigator.vibrate(20);
  }catch(e){}
}

function animateWordAction(type){
  const card = document.querySelector(".wordCard");
  const word = $("fsWord");

  if(!card || !word) return;

  card.classList.remove("wordFoundFx", "wordPassFx");
  word.classList.remove("wordFoundTextFx", "wordPassTextFx");

  void card.offsetWidth;
  void word.offsetWidth;

  if(type === "found"){
    card.classList.add("wordFoundFx");
    word.classList.add("wordFoundTextFx");
  }else{
    card.classList.add("wordPassFx");
    word.classList.add("wordPassTextFx");
  }

  window.setTimeout(() => {
    card.classList.remove("wordFoundFx", "wordPassFx");
    word.classList.remove("wordFoundTextFx", "wordPassTextFx");
  }, 300);
}

function animateButtonPress(buttonId, event = null, type = "found"){
  const btn = $(buttonId);
  if(!btn) return;

  btn.classList.remove("touchFx");
  void btn.offsetWidth;
  btn.classList.add("touchFx");

  const rect = btn.getBoundingClientRect();

  let x = rect.width / 2;
  let y = rect.height / 2;

  if(event && typeof event.clientX === "number" && typeof event.clientY === "number"){
    x = event.clientX - rect.left;
    y = event.clientY - rect.top;
  }

  const ripple = document.createElement("span");
  ripple.className = `tapRipple ${type}`;
  ripple.style.left = `${x}px`;
  ripple.style.top = `${y}px`;

  btn.appendChild(ripple);

  window.setTimeout(() => {
    btn.classList.remove("touchFx");
  }, 200);

  window.setTimeout(() => {
    ripple.remove();
  }, 460);
}

document.addEventListener("pointerdown", ()=>{ ensureAudio(); }, { once:true });

function bindTap(el, fn){
  el.addEventListener("pointerdown", (e)=>{
    e.preventDefault();
    fn(e);
  }, { passive:false });

  el.addEventListener("keydown", (e)=>{
    if(e.key === "Enter" || e.key === " "){
      e.preventDefault();
      fn(e);
    }
  });
}

function lockTurnActions(){
  if(turnActionLocked) return false;
  turnActionLocked = true;
  return true;
}

function unlockTurnActions(delay = 140){
  window.setTimeout(()=>{
    turnActionLocked = false;
  }, delay);
}

function teamsAreReallyTied(a, b){
  const A = getTeamTieBreakData(a);
  const B = getTeamTieBreakData(b);

  return A.total === B.total
    && A.roundsWon === B.roundsWon
    && A.m4 === B.m4
    && A.m3 === B.m3
    && A.m2 === B.m2
    && A.m1 === B.m1;
}

function showSimpleModal(title, htmlText, okText = "OK", okHandler = null, cancelText = null, cancelHandler = null){
  $("modalBackdrop").classList.add("simpleModal");

  $("modalTitle").textContent = title;
  $("modalPill").textContent = "Chrono Party";
  $("modalPill").classList.remove("pillBig");
  $("modalText").innerHTML = `<div class="small" style="margin-top:8px">${htmlText}</div>`;

if(cancelText){
  $("modalCancel").classList.remove("hidden");
  $("modalCancel").textContent = cancelText;
  $("modalCancel").onclick = cancelHandler || hideTurnModal;
}else{
  $("modalCancel").classList.add("hidden");
  $("modalCancel").onclick = null;
}

$("modalOk").classList.remove("hidden");
$("modalOk").style.display = "";
$("modalOk").disabled = false;
$("modalOk").textContent = okText;

$("modalOk").onclick = () => {
  $("modalBackdrop").style.display = "none";
  $("modalBackdrop").classList.remove("simpleModal");

  if(typeof okHandler === "function"){
    okHandler();
  }
};

$("modalBackdrop").style.display = "flex";
}

function saveCurrentGame(){

  if(!state.inGame) return;

  localStorage.setItem(
    "chrono_saved_game",
    JSON.stringify({
      savedAt: Date.now(),
      state: {
        round: state.round,
        teams: state.teams,
        teamIndex: state.teamIndex,
        originalDeck: state.originalDeck,
        deck: state.deck,
        remaining: state.remaining,
        secondsPerTurn: state.secondsPerTurn,
        sound: state.sound,
        voice: state.voice,
        teamMode: state.teamMode,
        adaptiveMaster: state.adaptiveMaster
      }
    })
  );
}

function clearSavedGame(){
  localStorage.removeItem("chrono_saved_game");
}

function checkSavedGameOnLoad(){

  const saved = localStorage.getItem("chrono_saved_game");

  if(!saved) return;

showSimpleModal(
  "Partie en cours",
  "Une partie était en cours. Voulez-vous la reprendre ?",
  "Reprendre",
  resumeSavedGame,
  "Nouvelle partie",
  () => {
    clearSavedGame();
    hideTurnModal();
    forceGoHome();
  }
);
}

function resumeSavedGame(){

  try{

    const saved = JSON.parse(
      localStorage.getItem("chrono_saved_game")
    );

    if(!saved || !saved.state){
      clearSavedGame();
      return;
    }

    Object.assign(state, saved.state);

    state.inGame = true;

    state.currentWord = null;
    state.lastTurn = null;

    state.pendingRoundEnd = false;
    state.endedByTimer = false;

    state.timerId = null;

    state.timeLeft = state.secondsPerTurn;

    state.foundThisTurn = [];
    state.passedThisTurn = 0;

    showScreen("gameScreen");

    showTurnModal(
      "Reprise de partie",
      buildPremiumTurnChangeHtml(),
      "Démarrer le tour",
      true
    );

  }catch(e){

    console.error(e);

    clearSavedGame();
  }
}
function askHomeDuringGame(){
  showSimpleModal(
    "Retour à l’accueil",
    "La partie en cours sera quittée immédiatement. Voulez-vous vraiment revenir à l’accueil ?",
    "Oui, quitter",
    () => {
      trackChronoParty("quitter_vers_accueil", `manche=${state.round} | equipe=${getCurrentTeam()?.name || "-"}`);
      forceGoHome();
    },
    "Annuler",
    hideTurnModal
  );
}

function getSelectedThemes(){
  const checks = Array.from(document.querySelectorAll('input[name="themeCheck"]:checked'));
  return checks.map(c => c.value);
}

function updateThemeSummary(){
  const sel = getSelectedThemes();
  const total = (GAME_CONFIG?.themes?.length || Object.keys(WORD_DATA).length);
  $("themeSummary").textContent = sel.length ? `${sel.length}/${total} : ${sel.join(" + ")}` : `0/${total}`;
}

function updateThemeCountInfo(){
  const sel = getSelectedThemes();
  const total = (GAME_CONFIG?.themes?.length || Object.keys(WORD_DATA).length);
  $("themeCountInfo").textContent = `${sel.length}/${total} sélectionné(s)`;
  updateThemeSummary();
}

function getSelectedDifficulties(){
  const checks = Array.from(document.querySelectorAll('input[name="difficultyCheck"]:checked'));
  return checks.map(c => c.value);
}

function updateDifficultySummary(){
  const sel = getSelectedDifficulties();
  const total = 3;
  $("diffSummary").textContent = sel.length ? `${sel.length}/${total} : ${sel.join(" + ")}` : `0/${total}`;
}

function updateDifficultyCountInfo(){
  const sel = getSelectedDifficulties();
  const total = 3;
  $("diffCountInfo").textContent = `${sel.length}/${total} sélectionnée(s)`;
  updateDifficultySummary();
}

function parsePlayersList(text){
  return String(text || "")
    .split(/\n|,|;/)
    .map(x => x.trim())
    .filter(Boolean);
}

function isValidPlayerIdentity(name){
  const clean = String(name || "").trim();

  // Accepte :
  // Bruno D
  // Bruno D.
  // Jean Pierre D
  // Jean-Pierre D
  return /^[A-Za-zÀ-ÖØ-öø-ÿ]+(?:[\s-][A-Za-zÀ-ÖØ-öø-ÿ]+)*\s+[A-Za-zÀ-ÖØ-öø-ÿ]{1,2}\.?$/i.test(clean);
}

function parseCustomWords(){
  const input = $("customWordsInput");
  if(!input) return [];

  const words = String(input.value || "")
    .split(/\n|,|;/)
    .map(x => x.trim())
    .filter(Boolean);

  const seen = new Set();

  return words.filter(word => {
    const key = word.toLowerCase();
    if(seen.has(key)) return false;
    seen.add(key);
    return true;
  });
}

function updateCustomWordsInfo(){
  const info = $("customWordsInfo");
  if(!info) return;

  const words = parseCustomWords();

  if(words.length === 0){
    info.textContent = "Aucune liste personnalisée.";
    return;
  }

  info.textContent = `${words.length} mot${words.length > 1 ? "s" : ""} personnalisé${words.length > 1 ? "s" : ""} saisi${words.length > 1 ? "s" : ""}.`;
}
function updateRandomPlayersInfo(){
  const names = parsePlayersList($("randomPlayersInput").value);
  $("randomPlayersInfo").textContent = `${names.length} joueur${names.length > 1 ? "s" : ""} saisi${names.length > 1 ? "s" : ""}`;
}

function updateDataInfo(){
  try{
    updateThemeCountInfo();
    updateDifficultyCountInfo();
    updateRandomPlayersInfo();

    const selThemes = getSelectedThemes();
    const diffs = getSelectedDifficulties();
    const modeText = $("teamMode").value === "manual" ? "Mode manuel" : "Mode automatique";
    const maxWords = GAME_CONFIG?.maxWords || 80;

    $("dataInfo").textContent =
      `${modeText} • Thèmes : ${selThemes.join(" + ") || "—"} • Difficultés : ${diffs.join(" + ") || "—"} • max ${maxWords} mots / partie.`;
  }catch(e){
    console.error("Erreur updateDataInfo :", e);
    if($("dataInfo")){
      $("dataInfo").textContent = "Configuration en cours…";
    }
  }
}

function selectAllThemes(on){
  const checks = Array.from(document.querySelectorAll('input[name="themeCheck"]'));
  checks.forEach(c => c.checked = !!on);

  if(!on && checks[0]){
    checks[0].checked = true;
  }

  updateThemeCountInfo();

  if(typeof refreshSetupUX === "function"){
    refreshSetupUX();
  }else{
    updateDataInfo();
  }
}

function selectAllDifficulties(on){
  const checks = Array.from(document.querySelectorAll('input[name="difficultyCheck"]'));
  checks.forEach(c => c.checked = !!on);

  if(!on && checks[0]){
    checks[0].checked = true;
  }

  updateDifficultyCountInfo();

  if(typeof refreshSetupUX === "function"){
    refreshSetupUX();
  }else{
    updateDataInfo();
  }
}

function buildThemesUI(themeList = null, preselect = null){
  const box = $("themesBox");
  const themes = Array.isArray(themeList) ? themeList : Object.keys(WORD_DATA);
  box.innerHTML = "";
  const selected = Array.isArray(preselect) ? new Set(preselect) : null;

  themes.forEach((t, idx)=>{
    const id = "th_" + idx;
    const item = document.createElement("label");
    item.className = "themeItem";
    item.setAttribute("for", id);

    const input = document.createElement("input");
    input.type = "checkbox";
    input.name = "themeCheck";
    input.id = id;
    input.value = t;
    input.checked = selected ? selected.has(t) : true;

    input.addEventListener("change", ()=>{
      if(getSelectedThemes().length === 0) input.checked = true;
      updateThemeCountInfo();
      updateDataInfo();
    });

    const span = document.createElement("span");
    span.textContent = t;

    item.appendChild(input);
    item.appendChild(span);
    box.appendChild(item);
  });

   updateThemeCountInfo();
  updateDataInfo();

  if(typeof refreshSetupUX === "function"){
    refreshSetupUX();
  }
}

$("themeDropdownHead").addEventListener("click", ()=>{
  const dd = $("themeDropdown");
  dd.classList.toggle("open");
  $("themeChevron").textContent = dd.classList.contains("open") ? "▴" : "▾";
});

$("diffDropdownHead").addEventListener("click", ()=>{
  const dd = $("diffDropdown");
  dd.classList.toggle("open");
  $("diffChevron").textContent = dd.classList.contains("open") ? "▴" : "▾";
});

function getTeamsUiSnapshot(){
  const n = clamp(parseInt($("teamCount").value||"2",10), 2, 12);
  const out = [];

  for(let i=0;i<n;i++){
    const defaultName = getDefaultTeamName(i);
    const name = ($("teamName_"+i)?.value || defaultName).trim() || defaultName;
    const playerCount = clamp(parseInt($("playerCount_"+i)?.value || "2",10), 2, 12);
    const players = [];

    for(let j=0;j<playerCount;j++){
      const val = ($("playerName_"+i+"_"+j)?.value || `Joueur ${j+1}`).trim() || `Joueur ${j+1}`;
      players.push(val);
    }

    out.push({ name, playerCount, players });
  }

  return out;
}

function getRandomTeamNamesSnapshot(){
  const n = clamp(parseInt($("teamCount").value||"2",10), 2, 12);
  const out = [];

  for(let i=0;i<n;i++){
    const defaultName = getDefaultTeamName(i);
    out.push(($("randomTeamName_"+i)?.value || defaultName).trim() || defaultName);
  }

  return out;
}

function normalizeTeamConfig(cfg, idx){
  const playerCount = clamp(parseInt(cfg?.playerCount || "2",10), 2, 12);

  const players = Array.from({length: playerCount}, (_,j)=>{
    const savedValue = cfg?.players?.[j];

    if(savedValue && !/^Joueur\s*\d+$/i.test(String(savedValue).trim())){
      return String(savedValue).trim();
    }

    return "";
  });

  const defaultName = getDefaultTeamName(idx);

  return {
    name: (cfg?.name || defaultName).trim() || defaultName,
    playerCount,
    players
  };
}

function buildTeamsUI(preserveData = null){
  const box = $("teamsBox");
  const n = clamp(parseInt($("teamCount").value||"2",10), 2, 12);
  const snapshot = Array.isArray(preserveData) ? preserveData : getTeamsUiSnapshot();

  box.innerHTML = `<label>Équipes et joueurs</label>`;

  for(let i=0;i<n;i++){
    const cfg = normalizeTeamConfig(snapshot[i], i);

    const wrap = document.createElement("div");
    wrap.className = "teamCard";
    wrap.innerHTML = `
      <div class="teamCardTitle" id="teamCardTitle_${i}">
  ${escapeHtml(cfg.name || getDefaultTeamName(i))}
</div>

      <div class="grid2">
        <div>
          <label style="margin-top:0">Nom de l’équipe</label>
          <input id="teamName_${i}" />
        </div>
        <div>
          <label style="margin-top:0">Nombre de joueurs</label>
          <input id="playerCount_${i}" type="number" min="2" max="12" step="1" value="${cfg.playerCount}" />
        </div>
      </div>

      <div class="playersBox" id="playersBox_${i}">
        <div class="small" style="margin-bottom:8px; font-weight:900">
  Prénoms des joueurs <span style="opacity:.75; font-weight:700">(ex : Bruno D)</span>
</div>
        <div class="grid2" id="playersGrid_${i}"></div>
      </div>
    `;
    box.appendChild(wrap);

const teamNameInput = $("teamName_"+i);
teamNameInput.value = cfg.name;

teamNameInput.addEventListener("input", () => {
  currentPreset = "perso";

  const defaultName = getDefaultTeamName(i);
  const newName = teamNameInput.value.trim() || defaultName;

  const title = $("teamCardTitle_"+i);
  if(title){
    title.textContent = newName;
  }

  refreshSetupUX();
});
    const playersGrid = $("playersGrid_"+i);
    cfg.players.forEach((playerName, j)=>{
  const playerWrap = document.createElement("div");
  playerWrap.innerHTML = `
    <label>Joueur ${j+1} <span class="requiredStar">*</span></label>
    <input id="playerName_${i}_${j}" type="text" placeholder="Ex : Bruno D" />
  `;
  playersGrid.appendChild(playerWrap);

  $("playerName_"+i+"_"+j).value = playerName || "";
});

    $("playerCount_"+i).addEventListener("change", ()=>{
      const fresh = getTeamsUiSnapshot();
      fresh[i].playerCount = clamp(parseInt($("playerCount_"+i).value || "2",10), 2, 12);
      buildTeamsUI(fresh);
    });
  }

  initChronoManualPlayerSuggestions(box);
}

function buildRandomTeamNamesUI(preserveNames = null){
  const box = $("randomTeamNamesBox");
  const n = clamp(parseInt($("teamCount").value||"2",10), 2, 12);
  const names = Array.isArray(preserveNames) ? preserveNames : getRandomTeamNamesSnapshot();

  box.innerHTML = `<label>Noms des équipes</label><div class="grid2" id="randomTeamNamesGrid"></div>`;
  const grid = $("randomTeamNamesGrid");

  for(let i=0;i<n;i++){
    const defaultName = getDefaultTeamName(i);
    const currentName = names[i] || defaultName;

    const wrap = document.createElement("div");
    wrap.innerHTML = `
      <label id="randomTeamLabel_${i}">${escapeHtml(currentName)}</label>
      <input id="randomTeamName_${i}" />
    `;
    grid.appendChild(wrap);

    $("randomTeamName_"+i).value = currentName;

  $("randomTeamName_"+i).addEventListener("input", () => {
  currentPreset = "perso";

  const newName = $("randomTeamName_"+i).value.trim() || defaultName;

  const label = $("randomTeamLabel_"+i);
  if(label){
    label.textContent = newName;
  }

  if(typeof refreshSetupUX === "function"){
    refreshSetupUX();
  }
});
  }
}

function updateModeUI(){
  const mode = $("teamMode").value;
  $("manualModeBox").classList.toggle("hidden", mode !== "manual");
  $("randomModeBox").classList.toggle("hidden", mode !== "random");
  updateDataInfo();

  window.setTimeout(() => {
    if(typeof refreshSetupUX === "function"){
      refreshSetupUX();
    }else if(typeof updateStartButtonsState === "function"){
      updateStartButtonsState();
    }
  }, 0);
}

function createTeamObject(name, players){
  const cleanedPlayers = players.map(p => String(p).trim()).filter(Boolean);
  const found = {};
  const adjust = {};
  const perRound = {};
  const passed = {};

  allRoundsArray().forEach(r=>{
    found[r] = [];
    passed[r] = [];
    adjust[r] = 0;
    perRound[r] = 0;
  });

return {
  name: name.trim() || "Équipe",
  players: cleanedPlayers.length ? cleanedPlayers : ["Joueur 1"],
  playerIndex: 0,
  found,
  passed,
  turnStats: [],
  adjust,
  perRound,
  turnsPlayed: {},
  total: 0,
  nextTurnBonusSeconds: 0,
  nextTurnBonusPasses: 0
};
}

async function initTeamsManualMode(){
  const n = clamp(parseInt($("teamCount").value||"2",10), 2, 12);
  const teams = [];

  for(let i=0;i<n;i++){
    const defaultName = getDefaultTeamName(i);
    const name = ($("teamName_"+i)?.value || defaultName).trim() || defaultName;
    const playerCount = clamp(parseInt($("playerCount_"+i)?.value || "2",10), 2, 12);
    const players = [];

    for(let j=0;j<playerCount;j++){
      const playerName = formatPlayerIdentity(($("playerName_"+i+"_"+j)?.value || "").trim());
      
      if(!isValidPlayerIdentity(playerName)){
  showSimpleModal(
    "Nom incomplet",
    "Merci d’indiquer le prénom suivi de l’initiale du nom. Exemple : Bruno D",
    "OK"
  );
  return false;
}
      players.push(playerName);
    }

    try{
      await resolveChronoPlayerProfiles(players);
    }catch(error){
      showSimpleModal(
        "Profil joueur indisponible",
        "Impossible de vérifier les profils joueurs sur le serveur. Vérifiez la connexion puis réessayez.<br><br>" + escapeHtml(error.message || "Erreur inconnue"),
        "OK"
      );
      return false;
    }

    const team = createTeamObject(name, players);
    team.playerIndex = pickRandomIndex(team.players.length);
    teams.push(team);
  }

  state.teams = teams;
  return true;
}
async function initTeamsRandomMode(){
  const n = clamp(parseInt($("teamCount").value||"2",10), 2, 12);
  const allPlayers = parsePlayersList($("randomPlayersInput").value)
  .map(formatPlayerIdentity);
  const invalidPlayer = allPlayers.find(name => !isValidPlayerIdentity(name));

if(invalidPlayer){
  showSimpleModal(
    "Nom incomplet",
    `Merci d’indiquer le prénom suivi de l’initiale du nom. Exemple : Bruno D<br><br>À corriger : <b>${escapeHtml(invalidPlayer)}</b>`,
    "OK"
  );
  return false;
}
  const teamNames = getRandomTeamNamesSnapshot();

 if(allPlayers.length < n * 2){
  showSimpleModal(
    "Joueurs insuffisants",
    `Il faut au moins ${n * 2} joueurs pour répartir ${n} équipes avec minimum 2 joueurs par équipe.`,
    "OK"
  );
  return false;
}

  try{
    await resolveChronoPlayerProfiles(allPlayers);
  }catch(error){
    showSimpleModal(
      "Profil joueur indisponible",
      "Impossible de vérifier les profils joueurs sur le serveur. Vérifiez la connexion puis réessayez.<br><br>" + escapeHtml(error.message || "Erreur inconnue"),
      "OK"
    );
    return false;
  }

  const shuffledPlayers = shuffle(allPlayers);
  const teams = [];

  for(let i=0;i<n;i++){
    teams.push(createTeamObject(teamNames[i] || getDefaultTeamName(i), []));
    teams[i].players = [];
  }

  shuffledPlayers.forEach((playerName, idx)=>{
    teams[idx % n].players.push(playerName);
  });

  teams.forEach(team=>{
    if(!team.players.length) team.players = ["Joueur 1"];
    team.playerIndex = pickRandomIndex(team.players.length);
  });

  state.teams = teams;
  return true;
}

async function initTeams(){
  state.teamMode = $("teamMode").value;
  if(isCoopMode()) return await initTeamsCoopMode();
  if(state.teamMode === "manual") return await initTeamsManualMode();
  if(state.teamMode === "random") return await initTeamsRandomMode();
  return false;
}

function recalcScores(){
  state.teams.forEach(t=>{
    allRoundsArray().forEach(r=>{
      const base = (t.found[r] || []).length;
      const adj = (t.adjust[r] || 0);
      t.perRound[r] = base + adj;
    });
    t.total = allRoundsArray().reduce((sum,r)=>sum + (t.perRound[r] || 0), 0);
  });
}

function getTeamTieBreakData(team){
  let roundsWon = 0;

  for(let r = 1; r <= TOTAL_ROUNDS; r++){
    const myScore = team.perRound[r] || 0;
    const bestOther = Math.max(
      ...state.teams
        .filter(t => t !== team)
        .map(t => t.perRound[r] || 0),
      -Infinity
    );

    if(myScore > bestOther){
      roundsWon++;
    }
  }

  return {
    total: team.total || 0,
    roundsWon,
    m4: team.perRound[4] || 0,
    m3: team.perRound[3] || 0,
    m2: team.perRound[2] || 0,
    m1: team.perRound[1] || 0,
    name: team.name || ""
  };
}

function compareTeams(a, b){
  const A = getTeamTieBreakData(a);
  const B = getTeamTieBreakData(b);

  if(B.total !== A.total) return B.total - A.total;
  if(B.roundsWon !== A.roundsWon) return B.roundsWon - A.roundsWon;
  if(B.m4 !== A.m4) return B.m4 - A.m4;
  if(B.m3 !== A.m3) return B.m3 - A.m3;
  if(B.m2 !== A.m2) return B.m2 - A.m2;
  if(B.m1 !== A.m1) return B.m1 - A.m1;

  return A.name.localeCompare(B.name, "fr", { sensitivity: "base" });
}

window.scoreAdjust = function(teamIndex, round, delta){
  const t = state.teams?.[teamIndex];
  if(!t) return;

  t.adjust[round] = (t.adjust[round] || 0) + delta;
  const base = (t.found?.[round] || []).length;
  if(base + t.adjust[round] < 0) t.adjust[round] = -base;

  showScoresModal(true);
};

function getMedalMap(){
  recalcScores();
  const ranked = state.teams
    .map((t,i)=>({i, total:t.total}))
    .sort((a,b)=> compareTeams(state.teams[a.i], state.teams[b.i]));

  const medal = {};
  if(ranked[0]) medal[ranked[0].i] = "🥇";
  if(ranked[1]) medal[ranked[1].i] = "🥈";
  if(ranked[2]) medal[ranked[2].i] = "🥉";
  return medal;
}

function wordsListHtml(team){
  const mk = (arr) => arr.length ? `<ul style="margin:6px 0 0 18px;">${arr.map(w=>`<li>${escapeHtml(w)}</li>`).join("")}</ul>` : `<div class="small">—</div>`;
  const all = allRoundsArray().flatMap(r => team.found[r] || []);

  return `
    <div style="margin-top:12px; padding:12px; border-radius:16px; border:1px solid rgba(255,255,255,.12); background: rgba(0,0,0,.10)">
      <div style="font-weight:1000; margin-bottom:6px">📝 Mots trouvés (${all.length})</div>

      ${allRoundsArray().map(r=>`
        <details style="margin:6px 0">
          <summary style="cursor:pointer; font-weight:900">Manche ${r} (${(team.found[r] || []).length})</summary>
          ${mk(team.found[r] || [])}
        </details>
      `).join("")}
    </div>
  `;
}

function buildScoresHtml(){
  recalcScores();
  const medalMap = getMedalMap();

  const rows = state.teams.map((t, idx)=>`
    <tr>
      <td style="font-weight:900">${medalMap[idx] ? medalMap[idx] + " " : ""}${escapeHtml(t.name)}</td>

      ${allRoundsArray().map(r=>`
        <td>
          <div style="display:flex; align-items:center; justify-content:space-between; gap:8px">
            <div style="font-weight:900">${t.perRound[r]}</div>
            <div style="display:flex; gap:6px">
              <button class="ghost miniBtn" type="button" onclick="scoreAdjust(${idx},${r},-1)">-</button>
              <button class="ghost miniBtn" type="button" onclick="scoreAdjust(${idx},${r},1)">+</button>
            </div>
          </div>
          <div class="small">ajust. ${t.adjust[r] || 0}</div>
        </td>
      `).join("")}

      <td style="text-align:right; font-weight:1000">${t.total}</td>
    </tr>
  `).join("");

  const rankedTeams = state.teams
    .map((t,i)=>({t,i}))
    .sort((a,b)=> compareTeams(a.t, b.t));

  const podiumLines = rankedTeams.slice(0,3).map((x, pos)=>{
    const icon = pos===0 ? "🥇" : pos===1 ? "🥈" : "🥉";
    return `<div class="small" style="margin:2px 0">${icon} <b>${escapeHtml(x.t.name)}</b> — ${x.t.total} pts</div>`;
  }).join("");

  const wordsSections = state.teams.map(t => `
    <details style="margin:10px 0; padding:10px; border-radius:16px; border:1px solid rgba(255,255,255,.12); background: rgba(0,0,0,.10)">
      <summary style="cursor:pointer; font-weight:1000">${escapeHtml(t.name)} — 📝 mots</summary>
      ${wordsListHtml(t)}
    </details>
  `).join("");

  const mobileCards = rankedTeams.map((x)=>{
    const colorClass = getTeamColorClass(x.t, x.i);
    const playersLabel = getTeamPlayersLabel(x.t);

    return `
      <div class="finalMobileCard">
        <div class="finalMobileCardHeader">
          <div class="miniScoreMobileHeaderLeft">
            <div class="miniScoreMobileAvatar ${colorClass}">👥</div>

            <div class="finalMobileTeamName">
              <span>${escapeHtml(x.t.name)}</span>
              ${playersLabel ? `
                <span class="miniScoreMobilePlayers">
                  ${escapeHtml(playersLabel)}
                </span>
              ` : ``}
            </div>
          </div>

          <div class="finalMobileTotal">
            Total : ${x.t.total}
          </div>
        </div>

        <div class="finalMobileRows">
          ${allRoundsArray().map(r => `
            <div class="finalMobileRow" style="align-items:flex-start;">
              <div>
                <div>Manche ${r}</div>
                <div class="small">ajust. ${x.t.adjust[r] || 0}</div>
              </div>

              <div style="display:flex; align-items:center; gap:8px; flex-wrap:wrap; justify-content:flex-end;">
                <strong class="miniScoreNumber ${colorClass}">
                  ${x.t.perRound[r]}
                </strong>
                <button class="ghost miniBtn" type="button" onclick="scoreAdjust(${x.i},${r},-1)">-</button>
                <button class="ghost miniBtn" type="button" onclick="scoreAdjust(${x.i},${r},1)">+</button>
              </div>
            </div>
          `).join("")}

          <div class="finalMobileRow total">
            <span>Total</span>
            <strong class="miniScoreTotal ${colorClass}">
              ${x.t.total}
            </strong>
          </div>
        </div>
      </div>
    `;
  }).join("");

  return `
    <div style="display:flex; justify-content:space-between; align-items:center; gap:10px; margin:6px 0 10px">
      <div style="font-weight:1000">Scores (éditables)</div>
      <div class="small">${state.inGame ? `Manche ${state.round} en cours` : "Partie terminée"}</div>
    </div>

    ${state.teams.length > 1 ? `
      <div style="padding:10px; border-radius:14px; border:1px solid rgba(255,255,255,.12); background: rgba(0,0,0,.12); margin-bottom:10px">
        <div style="font-weight:1000; margin-bottom:6px">Podium</div>
        ${podiumLines || `<div class="small">—</div>`}
      </div>
    ` : ""}

    <div class="small" style="margin-bottom:10px">
      Tu peux corriger le score de <b>n'importe quelle manche</b> avec +/−.
    </div>

    <div class="scoresDesktopTable">
      <table class="scoreTable">
        <thead>
          <tr>
            <th>Équipe</th>
            ${allRoundsArray().map(r=>`<th>M${r}</th>`).join("")}
            <th>Total</th>
          </tr>
        </thead>
        <tbody>${rows}</tbody>
      </table>
    </div>

    <div class="scoresMobileCards">
      ${mobileCards}
    </div>

    <div class="small" style="margin-top:12px; font-weight:900">📌 Mots trouvés par équipe</div>
    ${wordsSections}
  `;
}
function getTeamColorClass(team, index){
  const name = String(team?.name || "").toLowerCase();

  if(name.includes("violet")) return "violet";
  if(name.includes("bleu")) return "bleu";
  if(name.includes("vert")) return "vert";
  if(name.includes("orange")) return "orange";
  if(name.includes("rouge")) return "rouge";
  if(name.includes("jaune")) return "jaune";
  if(name.includes("rose")) return "rose";
  if(name.includes("turquoise")) return "turquoise";

  const fallback = ["violet", "bleu", "vert", "orange", "rouge", "jaune", "rose", "turquoise"];
  return fallback[index % fallback.length] || "default";
}

function getTeamPlayersLabel(team){
  if(!team || !Array.isArray(team.players) || !team.players.length){
    return "";
  }

  return team.players
    .map(p => String(p).trim())
    .filter(Boolean)
    .join(" & ");
}
function buildMiniScoresHtml(){
  recalcScores();

  const ranked = state.teams
    .map((t,i)=>({t,i}))
    .sort((a,b)=> compareTeams(a.t, b.t));

  return `
    <div class="miniScoreBox miniScoreDesktop">
      <div class="miniScoreTitle">📊 Scores actuels</div>

      <table class="miniScoreTable">
        <thead>
          <tr>
            <th>Équipe (joueurs)</th>
            ${allRoundsArray().map(r=>`<th>M${r}</th>`).join("")}
            <th>Total</th>
          </tr>
        </thead>

        <tbody>
          ${ranked.map(x=>{
            const colorClass = getTeamColorClass(x.t, x.i);
            const playersLabel = getTeamPlayersLabel(x.t);

            return `
              <tr>
                <td>
                  <div class="miniScoreTeamCell">
                    <div class="miniScoreAvatar ${colorClass}">👥</div>

                    <div>
                      <div class="miniScoreTeamName">
                        ${escapeHtml(x.t.name)}
                      </div>

                      ${playersLabel ? `
                        <div class="miniScorePlayers ${colorClass}">
                          ${escapeHtml(playersLabel)}
                        </div>
                      ` : ``}
                    </div>
                  </div>
                </td>

                ${allRoundsArray().map(r=>`
                  <td class="miniScoreNumber ${colorClass}">
                    ${x.t.perRound[r]}
                  </td>
                `).join("")}

                <td class="miniScoreTotal ${colorClass}">
                  ${x.t.total}
                </td>
              </tr>
            `;
          }).join("")}
        </tbody>
      </table>
    </div>

    <div class="miniScoreMobile">
      ${ranked.map(x => {
        const colorClass = getTeamColorClass(x.t, x.i);
        const playersLabel = getTeamPlayersLabel(x.t);

        return `
          <div class="finalMobileCard">
            <div class="finalMobileCardHeader">
  <div class="miniScoreMobileHeaderLeft">
    <div class="miniScoreMobileAvatar ${colorClass}">👥</div>

    <div class="finalMobileTeamName">
      <span>${escapeHtml(x.t.name)}</span>
      ${playersLabel ? `
        <span class="miniScoreMobilePlayers">
          ${escapeHtml(playersLabel)}
        </span>
      ` : ``}
    </div>
  </div>

  <div class="finalMobileTotal">
    Total : ${x.t.total}
  </div>
</div>

            <div class="finalMobileRows">
              ${allRoundsArray().map(r => `
                <div class="finalMobileRow">
                  <span>Manche ${r}</span>
                  <strong class="miniScoreNumber ${colorClass}">
                    ${x.t.perRound[r]}
                  </strong>
                </div>
              `).join("")}

              <div class="finalMobileRow total">
                <span>Total</span>
                <strong class="miniScoreTotal ${colorClass}">
                  ${x.t.total}
                </strong>
              </div>
            </div>
          </div>
        `;
      }).join("")}
    </div>
  `;
}

function buildRandomTeamsRevealHtml(){
  if(state.teamMode !== "random" || !state.teams?.length) return "";

  return `
    <div style="margin-top:12px; padding:12px; border-radius:16px; border:1px solid rgba(255,255,255,.12); background: rgba(0,0,0,.12)">
      <div style="font-weight:1000; margin-bottom:10px">🎲 Répartition aléatoire des équipes</div>
      <div class="revealTeamsGrid">
        ${state.teams.map((team, index)=>{
          const colorClass = getTeamColorClass(team, index);

          return `
            <div class="revealTeamCard">
              <div class="revealTeamHeader">
                <div class="revealTeamAvatar ${colorClass}">👥</div>
                <div class="revealTeamTitle">${escapeHtml(team.name)}</div>
              </div>

              <div class="revealPlayerList">
                ${team.players.map(p => `
                  <div class="revealPlayerLine">
                    <span class="revealPlayerDot ${colorClass}"></span>
                    <span>${escapeHtml(p)}</span>
                  </div>
                `).join("")}
              </div>
            </div>
          `;
        }).join("")}
      </div>
    </div>
  `;
}
function announceRandomTeams(){
  if(!state.voice) return;
  if(!state.teams || !state.teams.length) return;

  if(state.coop?.enabled){
    const team = state.teams[0];
    const players = Array.isArray(team.players)
      ? team.players.map(p => getSpokenPlayerName(p)).filter(Boolean).join(" et ")
      : "";
    const maxTurns = Math.max(1, Number(state.coop.maxTurns || getCoopBossMaxTurns()));

    speakChrono(
      `Mode coopératif prêt. Tous les joueurs jouent ensemble contre le Boss Chrono. ` +
      `${players ? `Joueurs : ${players}. ` : ""}` +
      `Objectif : terminer les quatre manches en ${maxTurns} tours maximum.`
    );
    return;
  }

  const teamsText = state.teams.map(team => {
    const teamName = getSpokenTeamName(team);

    const players = Array.isArray(team.players)
      ? team.players
          .map(p => getSpokenPlayerName(p))
          .filter(Boolean)
          .join(" et ")
      : "";

    return `${teamName} : ${players}.`;
  }).join(" ");

  speakChrono(`Répartition des équipes effectuée. ${teamsText}`);
}

function showRandomTeamsStartModal(nextStep){
  $("modalBackdrop").classList.remove("simpleModal");

  const isCoop = !!state.coop?.enabled;
  const maxTurns = Math.max(1, Number(state.coop?.maxTurns || getCoopBossMaxTurns()));
  const level = COOP_BOSS_LEVELS[state.coop?.level || "standard"] || COOP_BOSS_LEVELS.standard;

  $("modalTitle").textContent = isCoop ? "🤝 Groupe coopératif" : "🎲 Répartition des équipes";
  $("modalPill").textContent = isCoop ? "Mode coopératif prêt" : "Tirage au sort effectué";
  $("modalPill").classList.remove("pillBig");

  $("modalText").innerHTML = `
    <div class="randomTeamsStartBox">
      <div class="randomTeamsStartTitle">
        ${isCoop ? "Voici les joueurs du groupe" : "Voici les équipes tirées au hasard"}
      </div>

      <div class="randomTeamsStartSubtitle">
        ${isCoop
          ? `Tous ensemble contre le Boss Chrono. Objectif : terminer les 4 manches en ${maxTurns} tours maximum.`
          : "Prenez quelques secondes pour vérifier qui joue avec qui."}
      </div>

      ${isCoop ? `
        <div class="coopBossHud" style="margin:14px auto; max-width:640px;">
          <div class="coopBossHudTop">
            <span>${level.icon} ${escapeHtml(level.label)}</span>
            <strong>0/${maxTurns} tours</strong>
          </div>
          <div class="coopBossHudBar"><span style="width:0%"></span></div>
          <div class="coopBossHudText">Limite à ne pas dépasser : ${maxTurns} tours.</div>
        </div>
      ` : ``}

      <div class="revealTeamsGrid ${isCoop ? "coopRevealSingle" : ""}">
        ${state.teams.map((team, index)=>{
          const colorClass = getTeamColorClass(team, index);

          return `
            <div class="revealTeamCard">
              <div class="revealTeamHeader">
                <div class="revealTeamAvatar ${colorClass}">👥</div>
                <div class="revealTeamTitle">${isCoop ? "Groupe Coop" : escapeHtml(team.name)}</div>
              </div>

              <div class="revealPlayerList">
                ${team.players.map(p => `
                  <div class="revealPlayerLine">
                    <span class="revealPlayerDot ${colorClass}"></span>
                    <span>${escapeHtml(p)}</span>
                  </div>
                `).join("")}
              </div>
            </div>
          `;
        }).join("")}
      </div>
    </div>
  `;

  $("modalCancel").classList.remove("hidden");
  $("modalCancel").textContent = "Retour configuration";
  $("modalCancel").onclick = () => {
    cancelGameAndReturnConfig();
  };

  $("modalOk").onpointerdown = null;
  $("modalOk").disabled = false;
  $("modalOk").style.display = "";
  $("modalOk").classList.remove("hidden");
  $("modalOk").textContent = "Démarrer la partie";

  $("modalOk").onclick = () => {
    hideTurnModal();

    if(typeof nextStep === "function"){
      nextStep();
    }
  };

  $("modalBackdrop").style.display = "flex";

  window.setTimeout(() => {
    announceRandomTeams();
  }, 900);

  requestAnimationFrame(() => {
    scrollModalToTop();
    window.dispatchEvent(new Event("resize"));
  });
}
function getScoresExportText(){
  recalcScores();

  const ranked = state.teams
    .map((t,i)=>({t,i}))
    .sort((a,b)=> compareTeams(a.t, b.t));

  let txt = "";
  txt += "CHRONO PARTY - SCORES\n";
  txt += "=========================\n\n";
  txt += `Date : ${new Date().toLocaleString("fr-FR")}\n`;
  txt += `Manche actuelle : ${state.round}\n`;
  txt += `Partie terminée : ${state.inGame ? "Non" : "Oui"}\n`;
  txt += `Mode équipes : ${state.teamMode === "manual" ? "manuel" : "aléatoire"}\n\n`;

  txt += "CLASSEMENT\n";
  txt += "----------\n";
  ranked.forEach((x, idx)=>{
    txt += `${idx+1}. ${x.t.name} - ${x.t.total} pts (${allRoundsArray().map(r=>`M${r}:${x.t.perRound[r]}`).join(" / ")})\n`;
  });

  txt += "\nDETAIL DES EQUIPES\n";
  txt += "------------------\n";
  state.teams.forEach(t=>{
    txt += `${t.name} : ${t.players.join(", ")}\n`;
  });

  txt += "\nDETAIL DES MOTS TROUVES\n";
  txt += "-----------------------\n";
  state.teams.forEach(t=>{
    txt += `\n${t.name}\n`;
    allRoundsArray().forEach(r=>{
      txt += `Manche ${r} (${t.found[r].length}) : ${t.found[r].join(", ") || "-"}\n`;
    });
  });

  return txt;
}

function showToast(message){
  const oldToast = document.getElementById("chronoToast");
  if(oldToast) oldToast.remove();

  const toast = document.createElement("div");
  toast.id = "chronoToast";
  toast.textContent = message;
  toast.style.position = "fixed";
  toast.style.left = "50%";
  toast.style.bottom = "24px";
  toast.style.transform = "translateX(-50%)";
  toast.style.zIndex = "9999";
  toast.style.padding = "12px 16px";
  toast.style.borderRadius = "999px";
  toast.style.background = "rgba(8,10,18,.94)";
  toast.style.color = "#fff";
  toast.style.border = "1px solid rgba(255,255,255,.18)";
  toast.style.boxShadow = "0 18px 50px rgba(0,0,0,.55)";
  toast.style.fontWeight = "900";
  toast.style.fontSize = "14px";
  toast.style.textAlign = "center";
  toast.style.maxWidth = "calc(100vw - 32px)";
  toast.style.pointerEvents = "none";

  document.body.appendChild(toast);

  window.setTimeout(() => {
    toast.remove();
  }, 1800);
}

function saveScoresToFile(){
  const txt = getScoresExportText();
  const blob = new Blob([txt], { type: "text/plain;charset=utf-8" });
  const url = URL.createObjectURL(blob);

  const a = document.createElement("a");
  const d = new Date();
  const pad = n => String(n).padStart(2,"0");
  const fileName = `chrono-party-scores-${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}-${pad(d.getHours())}h${pad(d.getMinutes())}.txt`;

  a.href = url;
  a.download = fileName;
  document.body.appendChild(a);
  a.click();
  a.remove();

  URL.revokeObjectURL(url);

  const history = JSON.parse(localStorage.getItem("chrono_scores_history") || "[]");

history.unshift({
  date: new Date().toLocaleString("fr-FR"),
  text: txt
});

localStorage.setItem("chrono_scores_history", JSON.stringify(history.slice(0, 20)));
localStorage.setItem("topchrono_last_scores", txt);

  showToast("Scores sauvegardés ✅");
}

function autoSaveFinishedGameStats(){
  try{
    if(!state || !Array.isArray(state.teams) || state.teams.length === 0) return;

    recalcScores();

    if (!state.gameSessionId) {
     state.gameSessionId = Date.now() + "-" + Math.random().toString(36).slice(2,8);
}

    const sessionId = state.gameSessionId;
    const history = JSON.parse(localStorage.getItem("chrono_scores_history") || "[]");

    if(history.some(item => item.sessionId === sessionId)) return;

    const txt = getScoresExportText();

    history.unshift({
      sessionId: sessionId,
      date: new Date().toLocaleString("fr-FR"),
      text: txt,
teams: state.teams.map(team => ({
  name: team.name,
  players: (team.players || []).map(playerName =>
    getOrCreateChronoPlayer(playerName)
  ),
  total: team.total || 0,
  perRound: team.perRound || {},

  found: team.found || {},
  passed: team.passed || {},
  turnStats: team.turnStats || []
}))
    });

    localStorage.setItem("chrono_scores_history", JSON.stringify(history.slice(0, 20)));
    localStorage.setItem("topchrono_last_scores", txt);

    sendPartyStatsToServer();

  }catch(error){
    console.warn("Sauvegarde automatique des scores impossible", error);
  }
}


function sendPartyStatsToServer(){
  try{
    if(window.CHRONO_DISABLE_SERVER_STATS === true) return;
    if(!state || !Array.isArray(state.teams) || state.teams.length === 0) return;

    recalcScores();

    const rankedTeams = state.teams
      .map((team, index) => ({ team, index }))
      .sort((a, b) => compareTeams(a.team, b.team));

    const winnerTeam = rankedTeams[0]?.team || null;
    const startedAt = Number(state.gameStartedAt || state.gameStartTime || Date.now());
    const clientInfo = window.CHRONO_PARTY_CLIENT || {};

    const themes = Array.from(new Set(
      (state.originalDeck || [])
        .map(item => item && item.theme)
        .filter(Boolean)
    ));

    const difficulties = Array.from(new Set(
      (state.originalDeck || [])
        .map(item => item && (item.difficulty || item.diff))
        .filter(Boolean)
    ));

    const payload = {
      source: clientInfo.access_type || window.CHRONO_ACCESS_TYPE || window.CHRONO_PARTY_SOURCE || "premium",
      client_id: clientInfo.client_id || "",
      client_name: clientInfo.client_name || "",
      gift_code: clientInfo.gift_code || "",
      session_id: state.gameSessionId || "",
      duration: Math.max(0, Math.floor((Date.now() - startedAt) / 1000)),
      players_count: state.teams.reduce((total, team) => total + ((team.players || []).length), 0),
      teams_count: state.teams.length,
      rounds_played: state.round || 1,
      game_mode: state.coop?.enabled ? "coop" : "classic",
      coop: getCoopPartyPayload(),
      winner: state.coop?.enabled ? ((getCoopPartyPayload()?.won) ? (getCoopPartyPayload()?.label + " vaincu") : (getCoopPartyPayload()?.label + " survivant")) : (winnerTeam?.name || ""),
      scores: state.teams.map(team => ({
        name: team.name,
        total: team.total || 0,
        perRound: team.perRound || {}
      })),
      teams: state.teams.map(team => ({
  name: team.name,
  players: (team.players || []).map(playerName =>
    getOrCreateChronoPlayer(playerName)
  ),
  total: team.total || 0,
  perRound: team.perRound || {},

  found: team.found || {},
  passed: team.passed || {},
  turnStats: team.turnStats || []
})),
      themes,
      difficulties
    };

fetch("/api/save-party.php", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify(payload)
})
.then(response => response.json())
.then(result => {
  if (result.success && result.public_url) {
    state.publicPartyUrl = result.public_url;
    state.savedPartyId = result.party_id;

    console.log("Partie sauvegardée :", result.public_url);
  }
})
.catch(error => {
  console.warn("Stats serveur non envoyées", error);
});

  }catch(error){
    console.warn("Préparation stats serveur impossible", error);
  }
}

function showScoresModal(refreshOnly=false){
  $("modalBackdrop").classList.remove("simpleModal");

  if(!state.teams.length){
    showSimpleModal("Scores", "Démarre une partie d’abord.", "OK");
    return;
  }
 const openedFromFinal = !state.inGame && $("finalOverlay").style.display === "flex";

if(!refreshOnly && !openedFromFinal){
  modalReturnStack.push({
    title: $("modalTitle").textContent,
    pill: $("modalPill").textContent,
    pillBig: $("modalPill").classList.contains("pillBig"),
    html: $("modalText").innerHTML,
    cancelHidden: $("modalCancel").classList.contains("hidden"),
    cancelText: $("modalCancel").textContent,
    okText: $("modalOk").textContent,
    okHandler: $("modalOk").onclick,
    cancelHandler: $("modalCancel").onclick
  });
}

  $("modalTitle").textContent = "📊 Scores";
  $("modalPill").textContent = "Cumul M1+M2+M3+M4";
  $("modalPill").classList.remove("pillBig");
  $("modalText").innerHTML = buildScoresHtml();

  const tools = document.createElement("div");
  tools.className = "modalTools";
  tools.innerHTML = `<button class="ghost miniBtn" type="button" id="btnSaveScoresModal">Sauver scores</button>`;
  $("modalText").appendChild(tools);
  $("btnSaveScoresModal").onclick = saveScoresToFile;

 $("modalCancel").classList.remove("hidden");
$("modalCancel").textContent = openedFromFinal ? "Retour au classement final" : "Retour à la partie";

$("modalCancel").onclick = ()=>{
  if(openedFromFinal){
    $("modalBackdrop").style.display = "none";
    $("modalBackdrop").classList.remove("simpleModal");
    modalReturnStack = [];
    $("finalOverlay").style.display = "flex";
    return;
  }

  const prev = modalReturnStack.pop();

  if(prev){
    $("modalTitle").textContent = prev.title;
    $("modalPill").textContent = prev.pill;
    $("modalPill").classList.toggle("pillBig", !!prev.pillBig);
    $("modalText").innerHTML = prev.html;

    $("modalOk").classList.remove("hidden");
    $("modalOk").textContent = prev.okText;
    $("modalOk").onclick = prev.okHandler;

    if(prev.cancelHidden){
      $("modalCancel").classList.add("hidden");
    }else{
      $("modalCancel").classList.remove("hidden");
      $("modalCancel").textContent = prev.cancelText;
      $("modalCancel").onclick = prev.cancelHandler;
    }
  }else{
    hideTurnModal();
  }
};

$("modalOk").classList.add("hidden");
$("modalOk").onclick = null;

  $("modalBackdrop").style.display = "flex";
}

function openTurnOverlay(){ $("turnOverlay").style.display = "flex"; }
function closeTurnOverlay(){ $("turnOverlay").style.display = "none"; }

function openQuickSetupModal(){
  if(!$("quickTeam1").value.trim()) $("quickTeam1").value = "Équipe violette";
  if(!$("quickTeam2").value.trim()) $("quickTeam2").value = "Équipe bleue";

  if($("quickTeam1Label")){
    $("quickTeam1Label").textContent = $("quickTeam1").value.trim() || "Équipe violette";
  }

  if($("quickTeam2Label")){
    $("quickTeam2Label").textContent = $("quickTeam2").value.trim() || "Équipe bleue";
  }

  $("quickSetupModal").style.display = "flex";
  initChronoQuickPlayerSuggestions($("quickSetupModal"));
  loadChronoKnownPlayers(true);
  validateQuickSetupForm();

  requestAnimationFrame(() => {
    const modalBody = document.querySelector("#quickSetupModal .modalBody");

    if(modalBody){
      modalBody.scrollTop = 0;
    }

    if($("quickSetupModal")){
      $("quickSetupModal").scrollTop = 0;
    }

    window.dispatchEvent(new Event("resize"));
  });
}

function closeQuickSetupModal(){
  $("quickSetupModal").style.display = "none";
}

function validateQuickSetupForm(){
  const p1 = $("quickPlayer1").value.trim();
  const p2 = $("quickPlayer2").value.trim();
  const p3 = $("quickPlayer3").value.trim();
  const p4 = $("quickPlayer4").value.trim();

  $("btnStartQuickSetup").disabled = !(p1 && p2 && p3 && p4);
}

function applyQuickSetupToConfig(){
  $("teamCount").value = String(QUICK_GAME_CONFIG.teamCount);
  $("teamMode").value = QUICK_GAME_CONFIG.teamMode;
  $("wordCount").value = String(QUICK_GAME_CONFIG.wordCount);
  $("turnSeconds").value = String(QUICK_GAME_CONFIG.turnSeconds);
  $("passesPerTurn").value = String(QUICK_GAME_CONFIG.passesPerTurn);
  $("soundToggle").value = "on";

  if($("customWordsInput")){
    $("customWordsInput").value = "";
    updateCustomWordsInfo();
  }

  if($("voiceToggle")){
    $("voiceToggle").value = "on";
  }

  buildTeamsUI([
    {
      name: $("quickTeam1").value.trim() || "Équipe violette",
      playerCount: 2,
      players: [
        $("quickPlayer1").value.trim(),
        $("quickPlayer2").value.trim()
      ]
    },
    {
      name: $("quickTeam2").value.trim() || "Équipe bleue",
      playerCount: 2,
      players: [
        $("quickPlayer3").value.trim(),
        $("quickPlayer4").value.trim()
      ]
    }
  ]);

  buildRandomTeamNamesUI();
  updateModeUI();
  updateRandomPlayersInfo();

  document.querySelectorAll('input[name="difficultyCheck"]').forEach(input=>{
    input.checked = input.value === QUICK_GAME_CONFIG.difficulty;
  });

  const themeChecks = Array.from(document.querySelectorAll('input[name="themeCheck"]'));
  themeChecks.forEach(input=>{
    input.checked = QUICK_GAME_CONFIG.quickThemes.includes(input.value);
  });

  if(themeChecks.every(input => !input.checked) && themeChecks[0]){
    themeChecks[0].checked = true;
  }

  updateThemeCountInfo();
  updateDifficultyCountInfo();
  updateDataInfo();
}

async function launchValidatedQuickStart(){
  try{
    showScreen("configScreen");
    await loadGameConfig();
    applyQuickSetupToConfig();
    closeQuickSetupModal();
    await startGame();
  }catch(e){
    console.error("Erreur launchValidatedQuickStart :", e);
    showSimpleModal(
      "Erreur",
      escapeHtml(e?.message || "Impossible de lancer la partie rapide."),
      "OK"
    );
  }
}

async function handleValidatedQuickStart(){
  if(isStandaloneMode()){
    await launchValidatedQuickStart();
    return;
  }

  /*
    Correction PWA : on ne lance plus l'installation depuis le bouton
    "Démarrer la partie".
    Sur Android / Chrome Windows, deferredPrompt.prompt() peut interrompre
    le lancement, installer l'application, puis rouvrir Chrono Party sur
    l'écran de configuration. L'installation reste donc uniquement sur le
    bouton "Installer Chrono Party" de la page d'accueil.
  */
  if(deferredPrompt || canShowIOSInstallHelp()){
    closeQuickSetupModal();

  showSimpleModal(
  "Installer Chrono Party",
  `
    Pour jouer plus confortablement, vous pouvez installer Chrono Party sur votre téléphone comme une vraie application.
    <br><br>
    <b>Sur Android :</b><br>
    depuis la page d’accueil, cliquez sur le bouton <b>“Installer Chrono Party”</b>.
    <br><br>
    <b>Sur iPhone / iPad :</b><br>
    depuis la page d’accueil, cliquez sur <b>“Comment installer l’application ?”</b>, puis suivez les étapes affichées.
    <br><br>
    Vous pouvez aussi continuer directement la partie maintenant, sans installer l’application.
  `,
  "Continuer la partie",
  async () => {
    await launchValidatedQuickStart();
  },
  "Retour aux prénoms",
  () => {
  hideTurnModal();
  openQuickSetupModal();
}
);

    return;
  }

  await launchValidatedQuickStart();
}


function updateFsCoopBossHud(){
  const hud = $("fsCoopBossHud");
  if(!hud) return;

  if(!state.coop?.enabled){
    hud.classList.add("hidden");
    return;
  }

  const turnsUsed = getCoopTurnsUsed();
  const maxTurns = Math.max(1, Number(state.coop.maxTurns || getCoopBossMaxTurns()));
  const pct = Math.min(100, Math.round((turnsUsed / maxTurns) * 100));
  const level = COOP_BOSS_LEVELS[state.coop.level] || COOP_BOSS_LEVELS.standard;

  hud.classList.remove("hidden");
  $("fsCoopBossLabel").textContent = `${level.icon} ${level.label}`;
  $("fsCoopBossTurns").textContent = `${turnsUsed}/${maxTurns} tours`;
$("fsCoopBossText").style.display = "none";
  $("fsCoopBossBar").style.width = `${pct}%`;
}

function updateFsHud(){
  const team = getCurrentTeam();
  const teamName = team?.name || "-";
  const playerName = getSpokenPlayerName(getCurrentPlayerName(team));
  const themeName = state.currentWord?.theme || "—";

  const turnSheet = $("turnSheet");
  if(turnSheet){
    turnSheet.classList.remove(
      "roundPlaying1",
      "roundPlaying2",
      "roundPlaying3",
      "roundPlaying4"
    );
    turnSheet.classList.add(`roundPlaying${state.round}`);
  }

  $("fsThemeHero").textContent = `Thème : ${themeName}`;
$("fsRoundOnly").textContent = `Manche ${state.round}`;
$("fsTopRound").textContent = `Manche ${state.round}`;
$("fsRuleOnly").textContent = `Règle : ${roundRuleShort(state.round)}`;

  const total = state.originalDeck?.length || state.deck?.length || 0;
  const remain = state.remaining.length;
  $("fsCardsOnly").textContent = total ? `Mots restants : ${remain}/${total}` : `Mots restants : ${remain}`;

  $("fsPassOnly").textContent = `Pass : ${state.passesLeft}`;

  const foundBtn = $("btnFoundFs");
  if(foundBtn){
    foundBtn.innerHTML = state.coop?.enabled
      ? `✅ Trouvé<br><span style="font-size:12px;font-weight:900;opacity:.78;">mot validé</span>`
      : `✅ Trouvé<br><span style="font-size:12px;font-weight:900;opacity:.78;">+1 point</span>`;
  }

  const passBtn = $("btnPassFs");
  if(passBtn){
    passBtn.disabled = state.passesLeft <= 0;
    passBtn.innerHTML = state.passesLeft <= 0
  ? `⛔ Plus de pass<br><span style="font-size:12px;font-weight:900;opacity:.78;">limite atteinte</span>`
  : `⏭️ Passer<br><span style="font-size:12px;font-weight:900;opacity:.78;">mot suivant</span>`;
  }

  $("fsPlayerHero").textContent = playerName;
  $("fsTeamHero").textContent = state.coop?.enabled ? "" : teamName;
$("fsTeamHero").style.display = state.coop?.enabled ? "none" : "";
  updateFsCoopBossHud();

  const t = Math.max(0, state.timeLeft);
  $("fsTimer").textContent = String(t);

  const pct = state.secondsPerTurn ? (t / state.secondsPerTurn) : 1;
  $("fsBar").style.width = `${clamp(pct,0,1)*100}%`;

  const timerEl = $("fsTimer");
  timerEl.classList.remove("warn","danger");
  $("fsBarWrap").classList.remove("flash");

  if(t <= 5 && t >= 3){
    timerEl.classList.add("warn");
  }

  if(t <= 2){
    timerEl.classList.add("danger");
    $("fsBarWrap").classList.add("flash");
  }
}
function validateConfiguredPlayers(){
  if(isCoopMode()){
    const players = getCoopConfiguredPlayersFromInputs();
    const invalidPlayer = players.find(name => !isValidPlayerIdentity(name));

    if(players.length < 2){
      showSimpleModal("Joueurs insuffisants", "Le mode coopératif nécessite au moins 2 joueurs.", "OK");
      return false;
    }

    if(invalidPlayer){
      showSimpleModal("Nom incomplet", `Merci d’indiquer le prénom suivi de l’initiale du nom. Exemple : Bruno D<br><br>À corriger : <b>${escapeHtml(invalidPlayer)}</b>`, "OK");
      return false;
    }

    return true;
  }

  if(isCoopMode()){
    return getCoopConfiguredPlayersFromInputs().length >= 2;
  }

  const mode = $("teamMode").value;

  if(mode === "random"){
    const players = parsePlayersList($("randomPlayersInput").value);
    const teamCount = clamp(parseInt($("teamCount").value || "2", 10), 2, 12);

    if(players.length < teamCount){
      showSimpleModal(
        "Prénoms obligatoires",
        `Veuillez indiquer au moins ${teamCount} prénoms pour répartir les joueurs dans ${teamCount} équipes.`,
        "OK",
        () => {
          $("randomPlayersInput")?.focus();
        }
      );
      return false;
    }

    return true;
  }

  const teamCount = clamp(parseInt($("teamCount").value || "2", 10), 2, 12);

  for(let i = 0; i < teamCount; i++){
    const playerCount = clamp(parseInt($("playerCount_" + i)?.value || "2", 10), 2, 12);

    for(let j = 0; j < playerCount; j++){
      const input = $("playerName_" + i + "_" + j);
      const value = input?.value.trim() || "";

      if(!value || /^Joueur\s*\d+$/i.test(value)){
        showSimpleModal(
          "Prénoms obligatoires",
          `Veuillez renseigner le prénom du joueur ${j + 1} dans l'équipe ${i + 1}.`,
          "OK",
          () => {
            input?.focus();
          }
        );
        return false;
      }
    }
  }

  return true;
}

function areConfiguredPlayersValidForButton(){
  const customWords = parseCustomWords();
  const useCustomWords = customWords.length > 0;

  if(!useCustomWords){
    if(!GAME_CONFIG || !Array.isArray(GAME_CONFIG.themes) || GAME_CONFIG.themes.length === 0){
      return false;
    }

    if(getSelectedThemes().length === 0 || getSelectedDifficulties().length === 0){
      return false;
    }
  }

  if(useCustomWords && customWords.length < 10){
    return false;
  }

  const mode = $("teamMode").value;

  if(mode === "random"){
    const players = parsePlayersList($("randomPlayersInput").value);
    const teamCount = clamp(parseInt($("teamCount").value || "2", 10), 2, 12);
    return players.length >= teamCount;
  }

  const teamCount = clamp(parseInt($("teamCount").value || "2", 10), 2, 12);

  for(let i = 0; i < teamCount; i++){
    const playerCount = clamp(parseInt($("playerCount_" + i)?.value || "2", 10), 2, 12);

    for(let j = 0; j < playerCount; j++){
      const input = $("playerName_" + i + "_" + j);
      const value = input?.value.trim() || "";

      if(!value || /^Joueur\s*\d+$/i.test(value)){
        return false;
      }
    }
  }

  return true;
}

function updateStartButtonsState(){
  const canStart = areConfiguredPlayersValidForButton();

  if($("btnStartGame")){
    $("btnStartGame").disabled = false;
    $("btnStartGame").classList.toggle("isStartDisabled", !canStart);
  }

  if($("btnStartGameMobile")){
    $("btnStartGameMobile").disabled = false;
    $("btnStartGameMobile").classList.toggle("isStartDisabled", !canStart);
  }

  const msg = $("startRequirementMessage");
  if(msg){
    msg.classList.toggle("show", !canStart);
  }
}
function getFirstMissingPlayerElement(){
  if(isCoopMode()){
    const players = getCoopConfiguredPlayersFromInputs();
    if(players.length < 2){
      return $("teamMode")?.value === "random" ? $("randomPlayersInput") : $("teamsBox");
    }
    return null;
  }

  const mode = $("teamMode").value;

  if(mode === "random"){
    const players = parsePlayersList($("randomPlayersInput").value);
    const teamCount = clamp(parseInt($("teamCount").value || "2", 10), 2, 12);

    if(players.length < teamCount){
      return $("randomPlayersInput");
    }

    return null;
  }

  const teamCount = clamp(parseInt($("teamCount").value || "2", 10), 2, 12);

  for(let i = 0; i < teamCount; i++){
    const playerCount = clamp(parseInt($("playerCount_" + i)?.value || "2", 10), 2, 12);

    for(let j = 0; j < playerCount; j++){
      const input = $("playerName_" + i + "_" + j);
      const value = input?.value.trim() || "";

      if(!value || /^Joueur\s*\d+$/i.test(value)){
        return input;
      }
    }
  }

  return null;
}

function scrollToMissingStartRequirement(){
  let target = getFirstMissingPlayerElement();

  if(!target){
    if(getSelectedThemes().length === 0){
      target = $("themesStep");
    }else{
      target = $("teamsStep");
    }
  }

  const section = target?.closest?.(".setupStep") || target || $("teamsStep");

  if(section){
    section.scrollIntoView({
      behavior: "smooth",
      block: "center"
    });
  }

  window.setTimeout(() => {
    target?.focus?.();

    if(target){
      target.classList.add("inputNeedAttention");

      window.setTimeout(() => {
        target.classList.remove("inputNeedAttention");
      }, 1200);
    }
  }, 450);
}

function handleStartButtonClick(){
  if(!areConfiguredPlayersValidForButton()){
    scrollToMissingStartRequirement();
    return;
  }

  startGame();
}
async function startGame(){
  const customWords = parseCustomWords();
  const useCustomWords = customWords.length > 0;

  if(!useCustomWords && (!GAME_CONFIG || !Array.isArray(GAME_CONFIG.themes) || GAME_CONFIG.themes.length === 0)){
    showSimpleModal(
      "Configuration indisponible",
      "La banque de mots n’a pas pu être chargée. Vérifiez la connexion ou reconnectez-vous.",
      "OK"
    );
    return;
  }

 if(useCustomWords && customWords.length < 10){
  showSimpleModal(
    "Liste personnalisée trop courte",
    "Indiquez au moins 10 mots personnalisés, ou videz la liste pour utiliser les thèmes.",
    "OK",
    () => {
      $("customWordsInput")?.focus();
    }
  );
  return;
}

if(!useCustomWords){
  if(getSelectedThemes().length === 0){
    showSimpleModal(
      "Thème obligatoire",
      "Veuillez sélectionner au moins un thème.",
      "OK"
    );
    return;
  }

  if(getSelectedDifficulties().length === 0){
    showSimpleModal(
      "Difficulté obligatoire",
      "Veuillez sélectionner au moins une difficulté.",
      "OK"
    );
    return;
  }
}

  if(!validateConfiguredPlayers()) return;

  state.secondsPerTurn = clamp(parseInt($("turnSeconds").value||"30",10), 15, 60);

    state.passesLeft = clamp(parseInt($("passesPerTurn").value||"2",10), 0, 10);
  state.sound = $("soundToggle").value === "on";
state.voice = $("voiceToggle") ? $("voiceToggle").value === "on" : true;
if(isCoopMode()){
  state.adaptiveMaster = false;
}
  const teamsOk = await initTeams();
  if(!teamsOk) return;

  const selectedThemes = getSelectedThemes();
const selectedDiffs = getSelectedDifficulties();
const requestedCount = parseInt($("wordCount").value || "40", 10);

if(useCustomWords){
  const servedWords = shuffle(customWords).slice(0, Math.min(requestedCount, customWords.length));

  const customDeck = servedWords.map(word => ({
    w: word,
    theme: "Liste personnalisée",
    difficulty: "personnalisée"
  }));

  state.originalDeck = customDeck.slice();
  state.deck = mixDeckByTheme(state.originalDeck);
  state.remaining = state.deck.slice();

  state.round = 1;
  state.teamIndex = Math.floor(Math.random() * state.teams.length);
  state.currentWord = null;
  state.inGame = true;
state.gameSessionId = Date.now() + "-" + Math.random().toString(36).slice(2,8);
state.gameStartedAt = Date.now();
initCoopState();
  
  saveCurrentGame();
  
  const mobileBtn = document.querySelector(".mobileStartButton");
  if(mobileBtn){
    mobileBtn.style.display = "none";
  }

  state.foundThisTurn = [];
  state.lastTurn = null;
  state.pendingRoundEnd = false;
  state.endedByTimer = false;
  state.timeLeft = state.secondsPerTurn;

  trackChronoParty(
    "demarrage_partie_liste_personnalisee",
    `round=1 | equipes=${state.teams.length} | mots=${state.originalDeck.length} | mode=${state.teamMode}`
  );

  $("remoteInfo").textContent = `✅ Partie personnalisée prête (${state.originalDeck.length} mots)`;

  const team = getCurrentTeam();
  const playerName = getSpokenPlayerName(getCurrentPlayerName(team));

const openFirstTurnModal = () => {
  showTurnModal(
    "Début de partie",
    `
      ${buildPremiumNextTurnHtml({
       badge: "Liste personnalisée",
icon: "📝",
title: state.coop?.enabled ? `👹 Boss Chrono` : team.name,
subtitle: `
  ${state.coop?.enabled ? `Objectif : terminer les 4 manches en ${state.coop.maxTurns} tours maximum.<br>` : ``}
  La partie commence avec votre propre liste de mots.
          <br>
          Premier joueur : <span style="font-size:clamp(28px, 5vw, 48px); font-weight:1100; color:#fff; text-shadow:0 10px 28px rgba(0,0,0,.45);">${escapeHtml(playerName)}</span>.
          <br>
          Préparez-vous, le chrono va bientôt démarrer.
        `,
        showScores: false
      })}
    `,
    "Démarrer le tour",
    true
  );
};

if(state.teamMode === "random"){
  showRandomTeamsStartModal(openFirstTurnModal);
}else{
  openFirstTurnModal();
}

  return;
}

try{
   $("btnStartGame").disabled = true;
$("btnStartGameMobile").disabled = true;
    $("remoteInfo").textContent = "Préparation de la partie…";

    const { response, data } = await requestJson(START_GAME_URL, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
 body: JSON.stringify({
  mode: GAME_CONFIG?.mode === "kids"
  ? "kids"
  : (GAME_CONFIG?.mode === "family" ? "family" : "premium"),
  themes: selectedThemes,
  difficulties: selectedDiffs,
  wordCount: requestedCount
})
    }, 15000);

    if(!response.ok) throw new Error((data && data.message) || ("HTTP " + response.status));
    if(!data || !data.ok || !Array.isArray(data.deck) || !data.deck.length){
      throw new Error(data.message || "Deck invalide");
    }

state.originalDeck = data.deck.slice();
state.deck = mixDeckByTheme(state.originalDeck);
state.remaining = state.deck.slice();

state.round = 1;
state.teamIndex = Math.floor(Math.random() * state.teams.length);
state.currentWord = null;
state.inGame = true;
state.gameSessionId = Date.now() + "-" + Math.random().toString(36).slice(2,8);
state.gameStartedAt = Date.now();
initCoopState();

// Sauvegarde immédiate de la partie prête
saveCurrentGame();

const mobileBtn = document.querySelector(".mobileStartButton");
if(mobileBtn){
  mobileBtn.style.display = "none";
}

state.foundThisTurn = [];
state.lastTurn = null;
state.pendingRoundEnd = false;
state.endedByTimer = false;
state.timeLeft = state.secondsPerTurn;

trackChronoParty(
  "demarrage_partie",
  `round=1 | equipes=${state.teams.length} | mots=${state.originalDeck.length} | mode=${state.teamMode}`
);

$("remoteInfo").textContent = `✅ Partie prête (${data.wordCountServed} mots)`;

const team = getCurrentTeam();
const playerName = getSpokenPlayerName(getCurrentPlayerName(team));

const openFirstTurnModal = () => {
  showTurnModal(
    "Début de partie",
    `
      ${buildPremiumNextTurnHtml({
        badge: "Début de partie",
        icon: "🚀",
        title: state.coop?.enabled ? `👹 Boss Chrono` : team.name,
        subtitle: `
          ${state.coop?.enabled ? `Objectif : terminer les 4 manches en ${state.coop.maxTurns} tours maximum.<br>` : ``}
          La partie commence maintenant.
          <br>
          Premier joueur : <span style="font-size:clamp(28px, 5vw, 48px); font-weight:1100; color:#fff; text-shadow:0 10px 28px rgba(0,0,0,.45);">${escapeHtml(playerName)}</span>.
          <br>
          Préparez-vous, le chrono va bientôt démarrer.
        `,
        showScores: false
      })}
    `,
    "Démarrer le tour",
    true
  );
};

if(state.teamMode === "random"){
  showRandomTeamsStartModal(openFirstTurnModal);
}else{
  openFirstTurnModal();
}

  }catch(e){
    console.error(e);
    if(isAuthenticationErrorMessage(e?.message || "")){
      showAccessError(e?.message || "");
      redirectToLogin();
      return;
    }
    showAccessError(e?.message || "");
    const accessErrorShown = accessErrorLocked;
    if(accessErrorLocked){
      const displayedMessage =
        e?.message === "Accès non authentifié"
          ? "Accès non authentifié. Reconnectez-vous."
          : (e?.message === "Session expirée"
              ? "Session expirée."
              : (e?.message || "Impossible de préparer la partie."));

      showSimpleModal("Erreur", escapeHtml(displayedMessage), "OK");
      return;
    }
    $("remoteInfo").textContent = "⚠️ Erreur préparation partie";
    const displayedMessage =
      e?.message === "Accès non authentifié"
        ? "Accès non authentifié. Reconnectez-vous."
        : (e?.message || "Impossible de préparer la partie.");

    showSimpleModal("Erreur", escapeHtml(displayedMessage), "OK");
    if(accessErrorShown){
      $("remoteInfo").textContent = `⚠️ ${e?.message || ""}`;
    }
}finally{
  if(!state.inGame){
    updateStartButtonsState();
  }
}
}
function nextWord(){
  if(state.remaining.length === 0){
    endRound();
    return;
  }
  state.currentWord = state.remaining[0];
  $("fsWord").textContent = state.currentWord?.w || "—";
  updateFsHud();
}

async function startTurn(){
  state.foundThisTurn = [];
  state.passedThisTurn = 0;
  state.turnStartedAt = Date.now();
  state.lastTurn = null;

  const currentTeam = getCurrentTeam();

  const bonusSeconds = currentTeam?.nextTurnBonusSeconds || 0;
  const bonusPasses = currentTeam?.nextTurnBonusPasses || 0;

  state.timeLeft = state.secondsPerTurn + bonusSeconds;

state.passesLeft = Math.max(
  0,
  clamp(parseInt($("passesPerTurn").value || "2", 10), 0, 10)
  + bonusPasses
);

  if(currentTeam){
    currentTeam.nextTurnBonusSeconds = 0;
    currentTeam.nextTurnBonusPasses = 0;
  }

  state.endedByTimer = false;
  ensureAudio();
  state.lastTenAnnounced = false;

  trackChronoParty(
    "debut_tour",
    `manche=${state.round} | equipe=${currentTeam?.name || "-"} | joueur=${getCurrentPlayerName(currentTeam)} | restants=${state.remaining.length} | bonusSecondes=${bonusSeconds} | bonusPass=${bonusPasses}`
  );

  nextWord();
  openTurnOverlay();
  updateFsHud();

  await runStartCountdown();

  if(!state.inGame || $("turnOverlay").style.display !== "flex"){
    return;
  }

  clearInterval(state.timerId);

  state.timerId = setInterval(()=>{
    state.timeLeft -= 1;

    if(state.timeLeft === 10 && !state.lastTenAnnounced){
      state.lastTenAnnounced = true;
      speakChrono("Plus que 10 secondes !");
    }

    if(state.timeLeft <= 5 && state.timeLeft >= 1){
      beep("tick");
    }

    if(state.timeLeft <= 0){
      state.timeLeft = 0;
      updateFsHud();
      beep("ding");
      announceTimeUp();
      state.endedByTimer = true;
      endTurn(true);
    }else{
      updateFsHud();
    }
  }, 1000);

  updateFsHud();
}

function markFound(event = null){
  ensureAudio();
  if(!state.inGame || !state.currentWord) return;
  if(!lockTurnActions()) return;

  try{
    beep("good");
hapticFeedback("found");
animateButtonPress("btnFoundFs", event, "found");
animateWordAction("found");
showFloatingPoint();

    const team = state.teams[state.teamIndex];
    const wordText = state.currentWord.w;

    team.found[state.round].push(wordText);
    state.foundThisTurn.push(wordText);

    state.remaining.shift();

    if(state.remaining.length === 0){
  state.pendingRoundEnd = true;

  window.setTimeout(() => {
    endTurn(false);
  }, 300);

  return;
}

    window.setTimeout(() => {
      nextWord();
      updateFsHud();
    }, 300);
saveCurrentGame();
} finally {
  unlockTurnActions(340);
}
}

function passWord(event = null){
  ensureAudio();
  if(!state.inGame || !state.currentWord) return;

  if(state.passesLeft <= 0){
    beep("bad");
    hapticFeedback("pass");
    animateButtonPress("btnPassFs", event, "pass");
    return;
  }

  if(!lockTurnActions()) return;

  try{
   beep("bad");
hapticFeedback("pass");
animateButtonPress("btnPassFs", event, "pass");
animateWordAction("pass");

   state.passesLeft -= 1;
state.passedThisTurn = Number(state.passedThisTurn || 0) + 1;

    const team = state.teams[state.teamIndex];
    const passedWordText = state.currentWord && state.currentWord.w ? String(state.currentWord.w) : "";
    if(team && passedWordText){
      team.passed = team.passed || {};
      team.passed[state.round] = Array.isArray(team.passed[state.round]) ? team.passed[state.round] : [];
      team.passed[state.round].push(passedWordText);
    }

    const wObj = state.remaining.shift();
    state.remaining.push(wObj);

    window.setTimeout(() => {
      nextWord();
      updateFsHud();
    }, 300);
saveCurrentGame();
} finally {
  unlockTurnActions(340);
}
}

async function runStartCountdown(){
  const overlay = $("countdownOverlay");
  const number = $("countdownNumber");
  const sheet = $("turnSheet");

  if(!overlay || !number || !sheet) return;

  sheet.classList.add("countdownActive");
  overlay.classList.add("show");

  const steps = ["3", "2", "1", "GO !"];

  for(const step of steps){
    number.textContent = step;
    number.style.animation = "none";
    number.offsetHeight;
    number.style.animation = "";

    if(step === "GO !"){
      beep("ding");
    }else{
      beep("tick");
    }

    await sleep(step === "GO !" ? 500 : 750);
  }

  overlay.classList.remove("show");
  sheet.classList.remove("countdownActive");
  announceGo();
}

function endTurn(byTimer=false){
  if(!state.inGame) return;
  if(state.lastTurn) return;
  turnActionLocked = false;
  clearInterval(state.timerId);
  state.timerId = null;

  if(byTimer && state.remaining.length > 1){
    const wObj = state.remaining.shift();
    state.remaining.push(wObj);
  }

  closeTurnOverlay();

  state.lastTurn = {
    teamIndex: state.teamIndex,
    playerName: getCurrentPlayerName(),
    round: state.round,
    words: state.foundThisTurn.slice(),
    passes: Number(state.passedThisTurn || 0),
    durationSeconds: Math.max(0, Math.round((Date.now() - Number(state.turnStartedAt || Date.now())) / 1000)),
    adjDelta: 0,
    remainingAfterTurn: state.remaining.length,
    endedByTimer: !!byTimer
  };

const playedTeam = state.teams[state.teamIndex];

if(playedTeam){
  playedTeam.turnsPlayed = playedTeam.turnsPlayed || {};
  playedTeam.turnsPlayed[state.round] = (playedTeam.turnsPlayed[state.round] || 0) + 1;
  playedTeam.turnStats = Array.isArray(playedTeam.turnStats) ? playedTeam.turnStats : [];
 playedTeam.turnStats.push({
  round: state.round,
  playerName: getCurrentPlayerName(playedTeam),
  found: state.foundThisTurn.slice(),
  foundCount: state.foundThisTurn.length,
  passed: (playedTeam.passed?.[state.round] || []).slice(-Number(state.passedThisTurn || 0)),
  passes: Number(state.passedThisTurn || 0),
  durationSeconds: Math.max(0, Math.round((Date.now() - Number(state.turnStartedAt || Date.now())) / 1000)),
  endedByTimer: !!byTimer
});
}

trackChronoParty(
    "fin_tour",
    `manche=${state.round} | equipe=${playedTeam?.name || "-"} | joueur=${getCurrentPlayerName()} | trouves=${state.foundThisTurn.length} | restants=${state.remaining.length} | timer=${byTimer ? "oui" : "non"}`
  );

  showTurnCorrectionModal();
}

function removeOneOccurrence(arr, value){
  if(!Array.isArray(arr)) return;
  const idx = arr.findIndex(x => x === value);
  if(idx >= 0) arr.splice(idx, 1);
}

function insertAtRandomPosition(arr, item){
  const pos = Math.floor(Math.random() * (arr.length + 1));
  arr.splice(pos, 0, item);
}

function showTurnCorrectionModal(){
  $("modalBackdrop").classList.remove("simpleModal");

  const lt = state.lastTurn;

 if(lt && !lt.voiceAnnounced){
  lt.voiceAnnounced = true;

  window.clearTimeout(voiceTimeoutId);

  voiceTimeoutId = window.setTimeout(() => {
    voiceTimeoutId = null;

    if(
      state.inGame &&
      state.lastTurn === lt &&
      $("modalBackdrop").style.display === "flex" &&
      $("turnOverlay").style.display !== "flex" &&
      $("finalOverlay").style.display !== "flex"
    ){
      announceTurnResult();
    }
  }, lt.endedByTimer ? 1800 : 500);
}

  if(!lt){
    if(state.pendingRoundEnd){
      state.pendingRoundEnd = false;
      endRound();
      return;
    }

    if(shouldEndCoopByTurns()){
      endCoopByTurns();
      return;
    }

    proceedToNextTeam();
    return;
  }

  const team = state.teams[lt.teamIndex];
  const words = lt.words || [];
  const displayPlayerName = getSpokenPlayerName(lt.playerName);
  const basePts = words.length;
  const totalTurnPts = basePts + lt.adjDelta;
  const coopTurnsUsed = getCoopTurnsUsed();
  const coopMaxTurns = Math.max(1, Number(state.coop?.maxTurns || getCoopBossMaxTurns()));
  const coopTurnsLeft = Math.max(0, coopMaxTurns - coopTurnsUsed);

  const list = words.length
  ? words.map((w, i)=>`
      <div class="turnCorrectionWordRow">
        <div class="turnCorrectionWordName">✅ ${escapeHtml(w)}</div>
        <button class="ghost miniBtn" type="button" onclick="removeWordFromLastTurn(${i})">
          Retirer
        </button>
      </div>
    `).join("")
  : `<div class="turnCorrectionEmpty">Aucun mot validé sur ce tour.</div>`;

  $("modalTitle").textContent = "Correction du tour";
  $("modalPill").textContent = `Manche ${lt.round} • Joueur : ${escapeHtml(displayPlayerName)}`;
  $("modalPill").classList.add("pillBig");

  $("modalText").innerHTML = `
  <div class="turnCorrectionHero">
    <div class="turnCorrectionInner">
      <div class="turnCorrectionBadge">
        Correction du tour
      </div>

      <div class="turnCorrectionIcon">
        📝
      </div>

      <div class="turnCorrectionLabel">
        ${escapeHtml(roundLabel(lt.round))}
      </div>

      <div class="turnCorrectionPlayer">
        ${escapeHtml(displayPlayerName)}
      </div>

      <div class="turnCorrectionTeam">
        👥 ${escapeHtml(team.name)}
      </div>

      <div class="turnCorrectionStats">
        ${state.coop?.enabled ? `
          <div class="turnCorrectionStat">
            <strong>${coopTurnsUsed}/${coopMaxTurns}</strong>
            <span>Tours utilisés</span>
          </div>

          <div class="turnCorrectionStat">
            <strong>${coopTurnsLeft}</strong>
            <span>Tours restants Boss</span>
          </div>

          <div class="turnCorrectionStat">
            <strong>${basePts}</strong>
            <span>Mots trouvés</span>
          </div>
        ` : `
          <div class="turnCorrectionStat">
            <strong>${totalTurnPts}</strong>
            <span>Points du tour</span>
          </div>

          <div class="turnCorrectionStat">
            <strong>${basePts}</strong>
            <span>Mots trouvés</span>
          </div>

          <div class="turnCorrectionStat">
            <strong>${lt.remainingAfterTurn}</strong>
            <span>Mots restants</span>
          </div>
        `}
      </div>

      ${lt.endedByTimer ? `
        <div style="margin-top:14px;">
          <span class="pill">⏱️ Temps écoulé</span>
        </div>
      ` : ``}

      ${state.coop?.enabled ? `` : `
        <div class="turnCorrectionAdjust">
          <div class="turnCorrectionAdjustTitle">
            Ajustement manuel du score
          </div>

          <div class="turnCorrectionAdjustButtons">
            <button class="ghost miniBtn" type="button" onclick="turnScoreAdjust(-1)">-1</button>
            <span class="pill">Base : ${basePts} mot${basePts > 1 ? "s" : ""} • Ajust. ${lt.adjDelta}</span>
            <button class="ghost miniBtn" type="button" onclick="turnScoreAdjust(+1)">+1</button>
          </div>
        </div>
      `}

      <div class="turnCorrectionWords">
        <div class="turnCorrectionWordsTitle">
          Mots validés pendant ce tour
        </div>

        ${list}
      </div>
    </div>
  </div>

  ${getCoopStatusHtml()}
  ${state.coop?.enabled ? "" : buildMiniScoresHtml()}

  <div class="modalTools">
    <button class="ghost miniBtn" type="button" onclick="undoLastFound()">↩ Annuler le dernier mot</button>
    ${state.coop?.enabled ? "" : `
      <button class="ghost miniBtn" type="button" onclick="showScoresModal()">Afficher scores</button>
      <button class="ghost miniBtn" type="button" onclick="saveScoresToFile()">Sauver scores</button>
    `}
  </div>
`;

  $("modalCancel").classList.add("hidden");

  resetModalOkButton();

  $("modalOk").textContent = state.pendingRoundEnd
    ? "✅ Valider la fin de manche"
    : "✅ Valider et passer au tour suivant";

  if(!lt.okButtonUnlocked){
    $("modalOk").style.display = "none";
    $("modalOk").disabled = true;

    window.setTimeout(() => {
      if($("modalBackdrop").style.display === "flex" && state.lastTurn === lt){
        lt.okButtonUnlocked = true;
        $("modalOk").style.display = "";
        $("modalOk").disabled = false;
      }
    }, 3000);
  }else{
    $("modalOk").style.display = "";
    $("modalOk").disabled = false;
  }

  $("modalOk").onclick = (e) => {
    if(e){
      e.preventDefault();
      e.stopPropagation();
    }

    const btn = $("modalOk");

    if(!btn || btn.disabled || !lt.okButtonUnlocked){
      return;
    }

    btn.disabled = true;

    const teamPlayedIndex = lt.teamIndex;
    advancePlayerForTeam(teamPlayedIndex);

    hideTurnModal();

    if(state.pendingRoundEnd){
      state.pendingRoundEnd = false;
      endRound();
      return;
    }

    if(shouldEndCoopByTurns()){
      endCoopByTurns();
      return;
    }

    proceedToNextTeam();
  };

  $("modalBackdrop").style.display = "flex";

requestAnimationFrame(() => {
  scrollModalToTop();
  window.dispatchEvent(new Event("resize"));
});


}

window.removeWordFromLastTurn = function(idx){
  const lt = state.lastTurn;
  if(!lt) return;

  const w = lt.words[idx];
  if(!w) return;

  lt.words.splice(idx, 1);

  const team = state.teams[lt.teamIndex];
  if(!team) return;

removeOneOccurrence(team.found?.[lt.round], w);

const turnStat = Array.isArray(team.turnStats)
  ? team.turnStats[team.turnStats.length - 1]
  : null;

if(turnStat && turnStat.round === lt.round && turnStat.playerName === lt.playerName){
  if(Array.isArray(turnStat.found)){
    removeOneOccurrence(turnStat.found, w);
  }

  turnStat.foundCount = Array.isArray(turnStat.found)
    ? turnStat.found.length
    : Math.max(0, Number(turnStat.foundCount || 0) - 1);
}

  const originalObj = state.originalDeck.find(x => x.w === w);
  if(originalObj){
    insertAtRandomPosition(state.remaining, originalObj);
  }

  lt.remainingAfterTurn = state.remaining.length;

  if(state.remaining.length > 0){
    state.pendingRoundEnd = false;
  }

  showTurnCorrectionModal();
};

window.undoLastFound = function(){
  const lt = state.lastTurn;
  if(!lt || !lt.words || lt.words.length === 0) return;
  window.removeWordFromLastTurn(lt.words.length - 1);
};

window.turnScoreAdjust = function(delta){
  const lt = state.lastTurn;
  if(!lt) return;
  const team = state.teams[lt.teamIndex];
  team.adjust[lt.round] = (team.adjust[lt.round] || 0) + delta;
  lt.adjDelta = (lt.adjDelta || 0) + delta;
  const base = (team.found?.[lt.round] || []).length;
  if(base + team.adjust[lt.round] < 0){
    team.adjust[lt.round] = -base;
    lt.adjDelta = team.adjust[lt.round];
  }
  showTurnCorrectionModal();
};

function proceedToNextTeam(){
  state.foundThisTurn = [];
  state.lastTurn = null;
  state.currentWord = null;

  state.teamIndex = (state.teamIndex + 1) % state.teams.length;
  prepareAdaptiveBonusForCurrentTeam();
  state.timeLeft = state.secondsPerTurn;
saveCurrentGame();
  showTurnModal(
    state.coop?.enabled ? "Joueur suivant" : "Changement d’équipe",
    buildPremiumTurnChangeHtml(),
    "Démarrer le tour",
    true
  );
}

function askRound4Confirmation(){
  $("modalBackdrop").classList.remove("simpleModal");

  recalcScores();

  $("modalTitle").textContent = "Fin de la manche 3";
  $("modalPill").textContent = "Choix de fin de partie";
  $("modalPill").classList.remove("pillBig");

  $("modalText").innerHTML = `
    <div class="round4ChoiceHero">
      <div class="round4ChoiceIcon">🎭🔊</div>

      <div class="round4ChoiceTitle">
        Voulez-vous jouer la manche 4 ?
      </div>

      <div class="round4ChoiceSubtitle">
        La manche 4, <b>“Statue sonore”</b>, est la manche bonus la plus drôle :
        le joueur reste figé comme une statue et peut uniquement faire des bruitages
        ou fredonner pour aider son équipe.
      </div>

      <div class="round4ChoiceCards">
        <div class="round4ChoiceCard">
          <strong>✅ Oui, on continue</strong>
          <span>
            Vous rejouez les mêmes mots dans un ordre mélangé, avec la règle
            “statue sonore”.
          </span>
        </div>

        <div class="round4ChoiceCard">
          <strong>🏁 Non, on s’arrête là</strong>
          <span>
            Le jeu affiche directement le classement final avec les scores
            des 3 premières manches.
          </span>
        </div>
      </div>

      <div class="round4ChoiceButtonsHint">
        Conseil : si tout le monde est encore dans l’ambiance, jouez la manche 4.
        C’est souvent celle qui provoque le plus de rires.
      </div>
    </div>

    ${buildMiniScoresHtml()}
  `;

  $("modalCancel").classList.remove("hidden");
  $("modalCancel").textContent = "🏁 Non, voir le classement final";
  $("modalCancel").onclick = () => {
    hideTurnModal();
    state.inGame = false;
    clearAdaptiveBonuses();
    trackChronoParty(
      "fin_partie_apres_manche_3",
      `scores_finaux=${state.teams.map(t => `${t.name}:${t.total}`).join(" ; ")}`
    );
    showFinalOverlay();
  };

  $("modalOk").textContent = "🎭 Oui, jouer la manche 4";
  $("modalOk").onclick = () => {
    hideTurnModal();

    const lastTeam = state.teamIndex;
    state.round = 4;
    state.teamIndex = (lastTeam + 1) % state.teams.length;
    state.deck = mixDeckByTheme(state.originalDeck);
    state.remaining = state.deck.slice();
    state.currentWord = null;
    if(!state.coop?.enabled){
      prepareAdaptiveBonusForCurrentTeam();
    }
    state.timeLeft = state.secondsPerTurn;

  showTurnModal(
  "Manche 4",
  buildPremiumRoundStartHtml(),
  "Démarrer la manche",
  true
);
  };

 $("modalBackdrop").style.display = "flex";

requestAnimationFrame(() => {
  scrollModalToTop();
  window.dispatchEvent(new Event("resize"));
});

scheduleVoiceAnnouncement(announceRoundFourChoice, 500);
}

function endRound(){
  state.pendingRoundEnd = false;
  clearInterval(state.timerId);
  recalcScores();

  const roundJustFinished = state.round;

  trackChronoParty(
    "fin_manche",
    `manche=${roundJustFinished} | scores=${state.teams.map(t => `${t.name}:${t.total}`).join(" ; ")}`
  );

  // Si la manche 4 vient de se terminer : affichage direct des résultats finaux
  if(roundJustFinished >= TOTAL_ROUNDS){
    state.inGame = false;
    clearAdaptiveBonuses();

    trackChronoParty(
      "fin_partie",
      `scores_finaux=${state.teams.map(t => `${t.name}:${t.total}`).join(" ; ")}`
    );

    hideTurnModal();

clearSavedGame();

showFinalOverlay();
return;
  }

  scheduleVoiceAnnouncement(() => {
  announceRoundEnd(roundJustFinished);
}, 500);

  $("modalBackdrop").classList.remove("simpleModal");

  $("modalTitle").textContent = `Fin de la manche ${roundJustFinished}`;
  $("modalPill").textContent = "Résumé de la manche";
  $("modalPill").classList.remove("pillBig");

  $("modalText").innerHTML = `
    ${buildPremiumRoundEndHtml(roundJustFinished)}
  `;

  $("modalCancel").classList.add("hidden");
resetModalOkButton();
$("modalOk").onpointerdown = null;
$("modalOk").disabled = false;
$("modalOk").style.display = "";
$("modalOk").classList.remove("hidden");

if(roundJustFinished === 3 && !state.coop?.enabled){
    $("modalOk").textContent = "Continuer";
    $("modalOk").onclick = () => {
      hideTurnModal();
      askRound4Confirmation();
    };

    $("modalBackdrop").style.display = "flex";

    requestAnimationFrame(() => {
      scrollModalToTop();
      window.dispatchEvent(new Event("resize"));
    });

    return;
  }

  $("modalOk").textContent = `Passer à la manche ${roundJustFinished + 1}`;
  $("modalOk").onclick = () => {
    hideTurnModal();

    const lastTeam = state.teamIndex;
    state.round = roundJustFinished + 1;
    state.teamIndex = (lastTeam + 1) % state.teams.length;
    state.deck = mixDeckByTheme(state.originalDeck);
    state.remaining = state.deck.slice();
    state.currentWord = null;
    if(!state.coop?.enabled){
      prepareAdaptiveBonusForCurrentTeam();
    }
    state.timeLeft = state.secondsPerTurn;

    showTurnModal(
      `Manche ${state.round}`,
      buildPremiumRoundStartHtml(),
      "Démarrer la manche",
      true
    );
  };

  $("modalBackdrop").style.display = "flex";

  requestAnimationFrame(() => {
    scrollModalToTop();
    window.dispatchEvent(new Event("resize"));
  });

  return;
}

 
function buildFinalMobileCards(ranked){
  return `
    <div class="finalMobileCards">
      ${ranked.map(x => {
        const colorClass = getTeamColorClass(x.t, x.i);
        const playersLabel = getTeamPlayersLabel(x.t);

        return `
          <div class="finalMobileCard">
            <div class="finalMobileCardHeader">
              <div class="miniScoreMobileHeaderLeft">
                <div class="miniScoreMobileAvatar ${colorClass}">👥</div>

                <div class="finalMobileTeamName">
                  <span>${escapeHtml(x.t.name)}</span>
                  ${playersLabel ? `
                    <span class="miniScoreMobilePlayers">
                      ${escapeHtml(playersLabel)}
                    </span>
                  ` : ``}
                </div>
              </div>

              <div class="finalMobileTotal">
                Total : ${x.t.total}
              </div>
            </div>

            <div class="finalMobileRows">
              ${playedRoundsArray().map(r => `
                <div class="finalMobileRow">
                  <span>Manche ${r}</span>
                  <strong class="miniScoreNumber ${colorClass}">
                    ${x.t.perRound[r] || 0}
                  </strong>
                </div>
              `).join("")}

              <div class="finalMobileRow total">
                <span>Total</span>
                <strong class="miniScoreTotal ${colorClass}">
                  ${x.t.total}
                </strong>
              </div>
            </div>
          </div>
        `;
      }).join("")}
    </div>
  `;
}

function getTeamTotalFound(team){
  return allRoundsArray().reduce((sum, r) => sum + ((team.found && Array.isArray(team.found[r])) ? team.found[r].length : 0), 0);
}

function getTeamTotalPasses(team){
  return allRoundsArray().reduce((sum, r) => sum + ((team.passed && Array.isArray(team.passed[r])) ? team.passed[r].length : 0), 0);
}

function getAllTurnStats(){
  const rows = [];
  state.teams.forEach((team, teamIndex) => {
    (Array.isArray(team.turnStats) ? team.turnStats : []).forEach(turn => {
      rows.push({
        team,
        teamIndex,
        round: Number(turn.round || 0),
        playerName: String(turn.playerName || "Joueur"),
        found: Number(turn.foundCount ?? (Array.isArray(turn.found) ? turn.found.length : turn.found) ?? 0),
        passes: Number(turn.passes || 0),
        durationSeconds: Number(turn.durationSeconds || state.secondsPerTurn || 30)
      });
    });
  });
  return rows;
}

function getBestPlayerStats(){
  const byPlayer = new Map();

  getAllTurnStats().forEach(turn => {
    const key = `${turn.team.name}|||${turn.playerName}`;
    if(!byPlayer.has(key)){
      byPlayer.set(key, {
        playerName: turn.playerName,
        teamName: turn.team.name,
        found: 0,
        passes: 0,
        turns: 0
      });
    }

    const row = byPlayer.get(key);
    row.found += turn.found;
    row.passes += turn.passes;
    row.turns += 1;
  });

  return Array.from(byPlayer.values()).sort((a,b) =>
    (b.found - a.found) || (a.passes - b.passes) || a.playerName.localeCompare(b.playerName)
  )[0] || null;
}

function getBestTurnStats(){
  return getAllTurnStats().sort((a,b) =>
    (b.found - a.found) || (a.passes - b.passes) || (a.durationSeconds - b.durationSeconds)
  )[0] || null;
}

function getHardestRoundStats(){
  const rounds = playedRoundsArray().map(r => {
    const total = state.teams.reduce((sum, team) => sum + ((team.found && Array.isArray(team.found[r])) ? team.found[r].length : 0), 0);
    const turns = state.teams.reduce((sum, team) => sum + ((team.turnsPlayed && team.turnsPlayed[r]) ? Number(team.turnsPlayed[r]) : 0), 0);
    return { round: r, total, turns, average: turns ? total / turns : total };
  }).filter(row => row.turns > 0 || row.total > 0);

  return rounds.sort((a,b) => (a.average - b.average) || (a.total - b.total))[0] || null;
}

function getMostPassedWordStats(){
  const counts = new Map();

  state.teams.forEach(team => {
    allRoundsArray().forEach(r => {
      const words = team.passed && Array.isArray(team.passed[r]) ? team.passed[r] : [];
      words.forEach(word => {
        const clean = String(word || "").trim();
        if(clean){
          counts.set(clean, (counts.get(clean) || 0) + 1);
        }
      });
    });
  });

  return Array.from(counts.entries())
    .map(([word, count]) => ({word, count}))
    .sort((a,b) => (b.count - a.count) || a.word.localeCompare(b.word))[0] || null;
}

function getMostRegularTeamStats(){
  const rows = state.teams.map(team => {
    const scores = playedRoundsArray().map(r => Number(team.perRound?.[r] || 0));
    if(scores.length <= 1){
      return null;
    }
    const average = scores.reduce((a,b) => a + b, 0) / scores.length;
    const spread = Math.max(...scores) - Math.min(...scores);
    const variance = scores.reduce((sum, value) => sum + Math.pow(value - average, 2), 0) / scores.length;
    return {team, spread, variance, average};
  }).filter(Boolean);

  return rows.sort((a,b) => (a.spread - b.spread) || (b.average - a.average) || (a.variance - b.variance))[0] || null;
}

function getFinalAiSummary(ranked, totalFound, totalPasses, bestTurn, hardestRound){
  const gap = ranked.length > 1 ? Math.abs(Number(ranked[0].t.total || 0) - Number(ranked[1].t.total || 0)) : 0;

  if(ranked.length > 1 && gap <= 2){
    return `Partie très serrée : seulement ${gap} point${gap > 1 ? "s" : ""} d’écart entre les deux premières équipes.`;
  }

  if(totalPasses >= Math.max(8, Math.round(totalFound * 0.45))){
    return `Partie chaotique et très drôle : ${totalPasses} passes au total, certains mots ont résisté.`;
  }

  if(bestTurn && bestTurn.found >= 6){
    return `Gros rythme sur cette partie : ${escapeHtml(bestTurn.playerName)} a réussi un tour à ${bestTurn.found} mots.`;
  }

  if(hardestRound){
    return `La manche ${hardestRound.round} a été la plus difficile avec une moyenne de ${hardestRound.average.toFixed(1).replace(".", ",")} mot${hardestRound.average >= 2 ? "s" : ""} par tour.`;
  }

  return `Belle partie équilibrée : ${totalFound} mots trouvés au total et ${totalPasses} passes.`;
}

function buildFinalAdvancedStatsHtml(ranked){
  const totalFound = state.teams.reduce((sum, team) => sum + getTeamTotalFound(team), 0);
  const totalPasses = state.teams.reduce((sum, team) => sum + getTeamTotalPasses(team), 0);
  const bestPlayer = getBestPlayerStats();
  const bestTurn = getBestTurnStats();
  const hardestRound = getHardestRoundStats();
  const mostPassedWord = getMostPassedWordStats();
  const regularTeam = getMostRegularTeamStats();
  const gap = ranked.length > 1 ? Math.abs(Number(ranked[0].t.total || 0) - Number(ranked[1].t.total || 0)) : 0;
  const aiSummary = getFinalAiSummary(ranked, totalFound, totalPasses, bestTurn, hardestRound);

  const statCards = [
    {
      icon: "⭐",
      label: "MVP de la partie",
      value: bestPlayer ? escapeHtml(bestPlayer.playerName) : "À découvrir",
      detail: bestPlayer ? `${bestPlayer.found} mot${bestPlayer.found > 1 ? "s" : ""} trouvés · ${escapeHtml(bestPlayer.teamName)}` : "Jouez une partie complète pour débloquer cette stat."
    },
    {
      icon: "🚀",
      label: "Meilleur tour",
      value: bestTurn ? `${bestTurn.found} mot${bestTurn.found > 1 ? "s" : ""}` : "—",
      detail: bestTurn ? `${escapeHtml(bestTurn.playerName)} · ${escapeHtml(bestTurn.team.name)} · Manche ${bestTurn.round}` : "Aucun tour enregistré."
    },
    {
      icon: "😈",
      label: "Mot le plus passé",
      value: mostPassedWord ? escapeHtml(mostPassedWord.word) : "Aucun",
      detail: mostPassedWord ? `Passé ${mostPassedWord.count} fois pendant la partie` : "Très efficace : aucun mot vraiment bloquant."
    },
    {
      icon: "🔥",
      label: "Manche la plus difficile",
      value: hardestRound ? `Manche ${hardestRound.round}` : "—",
      detail: hardestRound ? `Moyenne : ${hardestRound.average.toFixed(1).replace(".", ",")} mot${hardestRound.average >= 2 ? "s" : ""} par tour` : "Pas assez de données."
    },
    {
      icon: "⚔️",
      label: "Écart final",
      value: ranked.length > 1 ? `${gap} point${gap > 1 ? "s" : ""}` : "—",
      detail: ranked.length > 1 ? `Entre ${escapeHtml(ranked[0].t.name)} et ${escapeHtml(ranked[1].t.name)}` : "Une seule équipe en jeu."
    },
    {
      icon: "🎯",
      label: "Équipe la plus régulière",
      value: regularTeam ? escapeHtml(regularTeam.team.name) : "—",
      detail: regularTeam ? `Écart max entre manches : ${regularTeam.spread} point${regularTeam.spread > 1 ? "s" : ""}` : "Pas assez de manches jouées."
    }
  ];

const maxTotal = Math.max(1, ...ranked.map(entry => Number(entry.t.total || 0)));

  return `
    <section class="finalStatsPanel">
      <div class="finalStatsHeader">
        <div>
          <div class="finalStatsKicker">📊 Bilan de soirée</div>
          <h3>Les stats qui racontent la partie</h3>
        </div>
        <div class="finalStatsTotals">
          <span>${totalFound} mots trouvés</span>
          <span>${totalPasses} passes</span>
        </div>
      </div>

      <div class="finalStatsGrid">
        ${statCards.map(card => `
          <article class="finalStatCard">
            <div class="finalStatIcon">${card.icon}</div>
            <div class="finalStatLabel">${card.label}</div>
            <div class="finalStatValue">${card.value}</div>
            <div class="finalStatDetail">${card.detail}</div>
          </article>
        `).join("")}
      </div>

      <div class="finalAiBox">
        <div class="finalAiTitle">🤖 Analyse IA Chrono Party</div>
        <p>${aiSummary}</p>
      </div>

      <div class="finalProgressBox">
        <div class="finalProgressTitle">📈 Progression des équipes</div>
        ${ranked.map(entry => `
          <div class="finalProgressRow">
            <div class="finalProgressTeam">
              <span>${escapeHtml(entry.t.name)}</span>
              <strong>${entry.t.total} pts</strong>
            </div>
            <div class="finalProgressTrack">
              <div class="finalProgressBar" style="width:${Math.max(4, Math.round((Number(entry.t.total || 0) / maxTotal) * 100))}%"></div>
            </div>
            <div class="finalProgressRounds">
              ${playedRoundsArray().map(r => `<span>M${r} ${entry.t.perRound[r] || 0}</span>`).join("")}
            </div>
          </div>
        `).join("")}
      </div>
    </section>
  `;
}

function launchConfetti(){
  const oldCanvas = document.getElementById("confettiCanvas");
  if(oldCanvas){
    oldCanvas.remove();
  }

  const canvas = document.createElement("canvas");
  canvas.id = "confettiCanvas";
  canvas.style.position = "fixed";
  canvas.style.inset = "0";
  canvas.style.width = "100%";
  canvas.style.height = "100%";
  canvas.style.pointerEvents = "none";
  canvas.style.zIndex = "9998";
  document.body.appendChild(canvas);

  const ctx = canvas.getContext("2d");
  const dpr = window.devicePixelRatio || 1;

  function resize(){
    canvas.width = window.innerWidth * dpr;
    canvas.height = window.innerHeight * dpr;
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
  }

  resize();

  window.addEventListener("resize", resize);

  const isSmallScreen = window.innerWidth < 600;
  const piecesCount = isSmallScreen ? 90 : 150;

  const pieces = Array.from({length: piecesCount}, () => ({
    x: Math.random() * window.innerWidth,
    y: -20 - Math.random() * window.innerHeight,
    size: 6 + Math.random() * 8,
    speed: 2 + Math.random() * 5,
    rot: Math.random() * Math.PI,
    rotSpeed: -0.15 + Math.random() * 0.3,
    color: ["#7c5cff", "#00d4ff", "#3cff9a", "#ffd166", "#ff4d6d"][Math.floor(Math.random() * 5)]
  }));

  let frame = 0;
  const maxFrames = 180;

  function draw(){
    frame++;
    ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);

    pieces.forEach(p => {
      p.y += p.speed;
      p.x += Math.sin((frame + p.y) * 0.02) * 1.5;
      p.rot += p.rotSpeed;

      ctx.save();
      ctx.translate(p.x, p.y);
      ctx.rotate(p.rot);
      ctx.fillStyle = p.color;
      ctx.fillRect(-p.size / 2, -p.size / 2, p.size, p.size * 0.6);
      ctx.restore();
    });

    if(frame < maxFrames){
      requestAnimationFrame(draw);
    }else{
      window.removeEventListener("resize", resize);
      canvas.remove();
    }
  }

  draw();
}
function playedRoundsArray(){
  const maxRound = Math.max(
    1,
    ...state.teams.flatMap(team =>
      allRoundsArray().filter(r => (team.perRound[r] || 0) !== 0 || (team.found[r] || []).length > 0)
    )
  );

  return Array.from({length: maxRound}, (_, i) => i + 1);
}
function buildFinalChampionHeroHtml(ranked){
  if(!ranked || !ranked.length){
    return "";
  }

  const best = ranked[0];

  const winners = ranked.filter(entry =>
    teamsAreReallyTied(entry.t, best.t)
  );

  const isTie = winners.length > 1;

  const winnerTeamsHtml = winners.map(entry => {
    const players = Array.isArray(entry.t.players)
      ? entry.t.players.map(p => String(p).trim()).filter(Boolean)
      : [];

    return `
      <div style="margin:10px 0 18px;">
        <div class="finalChampionTeam">
          ${escapeHtml(entry.t.name)}
        </div>

        ${players.length ? `
          <div class="finalChampionPlayers">
            ${players.map(player => `
              <span class="finalChampionPlayer">
                ${escapeHtml(player)}
              </span>
            `).join("")}
          </div>
        ` : ``}
      </div>
    `;
  }).join("");

 const roundScores = playedRoundsArray().map(r => `
    <span class="finalChampionScore">
      M${r} : ${best.t.perRound[r] || 0}
    </span>
  `).join("");

  const podium = ranked.slice(0, 3).map((entry, index) => {
    const medal = index === 0 ? "🥇" : index === 1 ? "🥈" : "🥉";
    const players = getTeamPlayersLabel(entry.t);

    return `
      <div class="finalChampionPodiumItem">
        <div class="finalChampionPodiumLeft">
          <div class="finalChampionPodiumName">
            ${medal} ${escapeHtml(entry.t.name)}
          </div>

          ${players ? `
            <div class="finalChampionPodiumPlayers">
              ${escapeHtml(players)}
            </div>
          ` : ``}
        </div>

        <div class="finalChampionPodiumPoints">
          ${entry.t.total} pts
        </div>
      </div>
    `;
  }).join("");

  return `
    <div class="finalChampionHero">
      <div class="finalChampionInner">
        <div class="finalChampionBadge">
          ${isTie ? "🤝 Égalité parfaite" : "🎉 Partie terminée"}
        </div>

        <div class="finalChampionCup">
          ${isTie ? "🤝" : "🏆"}
        </div>

        <div class="finalChampionLabel">
          ${isTie ? "Équipes gagnantes" : "Équipe gagnante"}
        </div>

        ${winnerTeamsHtml}

        <div class="finalChampionScores">
          <span class="finalChampionScore total">
            Total : ${best.t.total} points
          </span>
          ${roundScores}
        </div>

        <div class="finalChampionMessage">
          ${isTie
            ? `Incroyable partie ! Plusieurs équipes terminent exactement à égalité.`
            : `Bravo à <b>${escapeHtml(best.t.name)}</b> ! Une superbe victoire dans cette partie de Chrono Party.`}
        </div>

        <div class="finalChampionPodium">
          ${podium}
        </div>
      </div>
    </div>
  `;
}

function getFinalShareText(){
  recalcScores();
  const ranked = state.teams
    .map((t,i)=>({t,i}))
    .sort((a,b)=> compareTeams(a.t, b.t));

  const totalFound = state.teams.reduce((sum, team) => {
    return sum + allRoundsArray().reduce((s, r) => s + ((team.found && team.found[r]) ? team.found[r].length : 0), 0);
  }, 0);

  let text = "🎉 Partie Chrono Party terminée !\n\n";
  ranked.forEach((entry, index) => {
    const medal = index === 0 ? "🏆" : index === 1 ? "🥈" : index === 2 ? "🥉" : "•";
    text += `${medal} ${entry.t.name} : ${entry.t.total} points\n`;
  });
  text += `\n${totalFound} mots trouvés au total 🔥\n\n`;
  text += "Jouez aussi à Chrono Party 🎉";
  return text;
}

function loadFinalShareImage(src){
  return new Promise((resolve) => {
    try{
      const img = new Image();
      img.onload = () => resolve(img);
      img.onerror = () => resolve(null);
      img.src = src;
    }catch(e){
      resolve(null);
    }
  });
}

function generateFinalShareImageBlob(){
  return new Promise(async (resolve) => {
    try{
      recalcScores();
      const ranked = state.teams
        .map((t,i)=>({t,i}))
        .sort((a,b)=> compareTeams(a.t, b.t));

      const totalFound = state.teams.reduce((sum, team) => sum + getTeamTotalFound(team), 0);
      const totalPasses = state.teams.reduce((sum, team) => sum + getTeamTotalPasses(team), 0);
      const bestPlayer = getBestPlayerStats();
      const bestTurn = getBestTurnStats();
      const hardestRound = getHardestRoundStats();
      const mostPassedWord = getMostPassedWordStats();
      const regularTeam = getMostRegularTeamStats();
      const aiSummary = getFinalAiSummary(ranked, totalFound, totalPasses, bestTurn, hardestRound);
      const gap = ranked.length > 1 ? Math.abs(Number(ranked[0].t.total || 0) - Number(ranked[1].t.total || 0)) : 0;
      const playedRounds = playedRoundsArray();
      const playedRoundsCount = playedRounds.length || 1;
      const totalRoundsLabel = `${playedRoundsCount}/${TOTAL_ROUNDS || 4}`;

      const canvas = document.createElement("canvas");
      canvas.width = 1080;
      canvas.height = 1920;
      const ctx = canvas.getContext("2d");

      function safeText(txt, max){
        txt = String(txt || "");
        return txt.length > max ? txt.slice(0, max - 1) + "…" : txt;
      }

      function drawCentered(text, x, y, size, weight, color){
        ctx.textAlign = "center";
        ctx.fillStyle = color || "#fff";
        ctx.font = `${weight || 900} ${size}px Arial`;
        ctx.fillText(String(text || ""), x, y);
      }

      function drawLeft(text, x, y, size, weight, color){
        ctx.textAlign = "left";
        ctx.fillStyle = color || "#fff";
        ctx.font = `${weight || 800} ${size}px Arial`;
        ctx.fillText(String(text || ""), x, y);
      }

      function drawMultiline(text, x, y, size, weight, color, lineHeight, maxLines){
        const words = String(text || "").split(/\s+/).filter(Boolean);
        const lines = [];
        let line = "";
        ctx.font = `${weight || 800} ${size}px Arial`;
        words.forEach(word => {
          const test = line ? line + " " + word : word;
          if(ctx.measureText(test).width > 330 && line){
            lines.push(line);
            line = word;
          }else{
            line = test;
          }
        });
        if(line) lines.push(line);
        lines.slice(0, maxLines || 3).forEach((l, i) => drawLeft(l, x, y + i * (lineHeight || size + 8), size, weight, color));
      }

      const finishedAt = new Date();
      const sessionStartMs = state.gameStartedAt || (String(state.gameSessionId || "").split("-")[0]);
      const parsedStartMs = Number(sessionStartMs);
      const durationMinutes = Number.isFinite(parsedStartMs) && parsedStartMs > 0
        ? Math.max(1, Math.round((finishedAt.getTime() - parsedStartMs) / 60000))
        : null;
      const shareDateLine = finishedAt.toLocaleDateString("fr-FR", {
        weekday: "long",
        day: "2-digit",
        month: "long",
        year: "numeric"
      }).replace(/^./, c => c.toUpperCase());
      const shareTimeLine = finishedAt.toLocaleTimeString("fr-FR", { hour: "2-digit", minute: "2-digit" }).replace(":", "h");
      const shareMomentLine = durationMinutes
        ? `📅 ${shareDateLine} • ${shareTimeLine}  ·  ⏱ ${durationMinutes} min`
        : `📅 ${shareDateLine} • ${shareTimeLine}`;

      const bg = ctx.createLinearGradient(0, 0, 1080, 1920);
      bg.addColorStop(0, "#05081f");
      bg.addColorStop(.35, "#09104a");
      bg.addColorStop(.70, "#070923");
      bg.addColorStop(1, "#02040f");
      ctx.fillStyle = bg;
      ctx.fillRect(0, 0, 1080, 1920);

      ctx.fillStyle = "rgba(124,92,255,.28)";
      ctx.beginPath(); ctx.arc(130, 140, 230, 0, Math.PI * 2); ctx.fill();
      ctx.fillStyle = "rgba(0,174,255,.22)";
      ctx.beginPath(); ctx.arc(960, 650, 260, 0, Math.PI * 2); ctx.fill();
      ctx.fillStyle = "rgba(255,216,77,.09)";
      ctx.beginPath(); ctx.arc(880, 1370, 310, 0, Math.PI * 2); ctx.fill();

      ctx.strokeStyle = "rgba(255,216,77,.90)";
      ctx.lineWidth = 6;
      roundRect(ctx, 24, 24, 1032, 1872, 34, false, true);
      ctx.strokeStyle = "rgba(124,92,255,.78)";
      ctx.lineWidth = 3;
      roundRect(ctx, 38, 38, 1004, 1844, 28, false, true);

      const colors = ["#ffd84d", "#7c5cff", "#00d4ff", "#ff4fb8", "#43f06d", "#ff7a45"];
      for(let i=0;i<86;i++){
        const x = 45 + Math.random() * 990;
        const y = 58 + Math.random() * 300;
        ctx.fillStyle = colors[i % colors.length];
        ctx.save(); ctx.translate(x,y); ctx.rotate(Math.random() * Math.PI); ctx.fillRect(-6,-9,12,18); ctx.restore();
      }

      drawCentered("🏆 CHRONO PARTY", 540, 140, 66, 1000, "#ffffff");
      drawCentered(shareMomentLine, 540, 193, 24, 900, "#ffd84d");
      drawCentered("PARTIE TERMINÉE ! 🎉", 540, 285, 62, 1000, "#ffffff");
      drawCentered("Merci à tous pour cette super partie !", 540, 338, 31, 900, "#ffd84d");

      const first = ranked[0];
      const second = ranked[1];
      const winnerName = first ? safeText(first.t.name.toUpperCase(), 24) : "ÉQUIPE GAGNANTE";

      const winGrad = ctx.createLinearGradient(155, 382, 925, 472);
      winGrad.addColorStop(0, "#6f1aff");
      winGrad.addColorStop(1, "#d632ff");
      ctx.fillStyle = winGrad;
      roundRect(ctx, 150, 372, 780, 88, 28, true, false);
      drawCentered("👑 ÉQUIPE GAGNANTE", 540, 407, 25, 1000, "#ffd84d");
      drawCentered(winnerName, 540, 450, 39, 1000, "#ffffff");

      function scoreCard(entry, left, top, accent){
        const cardGrad = ctx.createLinearGradient(left, top, left + 430, top + 235);
        cardGrad.addColorStop(0, "rgba(255,255,255,.12)");
        cardGrad.addColorStop(1, "rgba(0,0,0,.20)");
        ctx.fillStyle = cardGrad;
        ctx.strokeStyle = accent;
        ctx.lineWidth = 5;
        roundRect(ctx, left, top, 430, 225, 34, true, true);
        drawCentered(entry ? safeText(entry.t.name.toUpperCase(), 18) : "ÉQUIPE", left + 215, top + 55, 29, 1000, accent);
        drawCentered(entry ? String(entry.t.total || 0) : "0", left + 215, top + 148, 88, 1000, "#ffffff");
        drawCentered("points", left + 215, top + 195, 28, 900, accent);
      }
      scoreCard(first, 70, 505, "#b65cff");
      scoreCard(second, 580, 505, "#00aaff");
      ctx.fillStyle = "#111633";
      ctx.strokeStyle = "rgba(255,255,255,.22)";
      ctx.lineWidth = 3;
      ctx.beginPath(); ctx.arc(540, 618, 48, 0, Math.PI*2); ctx.fill(); ctx.stroke();
      drawCentered("VS", 540, 633, 35, 1000, "#ffffff");

      ctx.fillStyle = "rgba(255,255,255,.08)";
      ctx.strokeStyle = "rgba(124,92,255,.70)";
      ctx.lineWidth = 3;
      roundRect(ctx, 70, 760, 940, 185, 30, true, true);
      drawCentered("⭐ RÉSUMÉ DE LA PARTIE ⭐", 540, 812, 34, 1000, "#ffffff");
      const resumeStats = [
        ["🎯", "Manches", totalRoundsLabel, "#ffffff"],
        ["✅", "Mots trouvés", String(totalFound), "#43f06d"],
        ["⏭️", "Passes", String(totalPasses), "#ffffff"],
        ["⚡", "Meilleur tour", bestTurn ? String(bestTurn.found) : "0", "#66e6ff"]
      ];
      resumeStats.forEach((st, idx)=>{
        const x = 155 + idx * 255;
        drawCentered(st[0], x, 860, 36, 900, "#ffffff");
        drawCentered(st[1], x, 905, 21, 900, "#dfe7ff");
        drawCentered(st[2], x, 935, 31, 1000, st[3]);
        if(idx > 0){
          ctx.strokeStyle = "rgba(255,255,255,.14)";
          ctx.lineWidth = 2;
          ctx.beginPath(); ctx.moveTo(x-128, 842); ctx.lineTo(x-128, 930); ctx.stroke();
        }
      });

      ctx.fillStyle = "rgba(35,15,90,.62)";
      ctx.strokeStyle = "rgba(233,55,255,.95)";
      ctx.lineWidth = 4;
      roundRect(ctx, 70, 968, 940, 315, 30, true, true);
      drawCentered("LES STATS DE LA SOIRÉE 🎉", 540, 1010, 34, 1000, "#ffffff");

      const statCards = [
        ["👑", "MVP DE LA PARTIE", bestPlayer ? safeText(bestPlayer.playerName, 16) : "À découvrir", bestPlayer ? `${bestPlayer.found} mots trouvés` : "Pas assez de données"],
        ["🚀", "MEILLEUR TOUR", bestTurn ? `${bestTurn.found} mots` : "—", bestTurn ? `${safeText(bestTurn.playerName, 14)} · Manche ${bestTurn.round}` : "Aucun tour enregistré"],
        ["😈", "MOT IMPOSSIBLE", mostPassedWord ? safeText(mostPassedWord.word, 16) : "Aucun", mostPassedWord ? `passé ${mostPassedWord.count} fois` : "aucun mot bloquant"],
        ["🔥", "MANCHE LA PLUS DIFFICILE", hardestRound ? `Manche ${hardestRound.round}` : "—", hardestRound ? `moyenne : ${hardestRound.average.toFixed(1).replace(".", ",")} mots` : "Pas assez de données"],
        ["⚔️", "ÉCART FINAL", ranked.length > 1 ? `${gap} points` : "—", ranked.length > 1 ? "d’écart" : "Une seule équipe"],
        ["📊", "ÉQUIPE LA PLUS RÉGULIÈRE", regularTeam ? safeText(regularTeam.team.name, 17) : "—", regularTeam ? `écart max : ${regularTeam.spread} pts` : "Pas assez de manches"]
      ];

      function drawStatCell(card, idx){
        const col = idx % 3;
        const row = Math.floor(idx / 3);
        const cellW = 285;
        const cellH = 108;
        const cellX = 98 + col * 300;
        const cellY = 1038 + row * 118;
        const iconX = cellX + 22;
        const textX = cellX + 78;

        // Séparateurs fins, placés entre les cases pour ne jamais traverser le texte
        if(col > 0){
          ctx.strokeStyle = "rgba(255,255,255,.18)";
          ctx.lineWidth = 2;
          ctx.beginPath();
          ctx.moveTo(cellX - 23, cellY + 6);
          ctx.lineTo(cellX - 23, cellY + cellH - 2);
          ctx.stroke();
        }
        if(row === 0 && col === 0){
          ctx.strokeStyle = "rgba(255,255,255,.18)";
          ctx.lineWidth = 2;
          ctx.beginPath();
          ctx.moveTo(100, 1150);
          ctx.lineTo(980, 1150);
          ctx.stroke();
        }

        const title = safeText(card[1], 24);
        const value = safeText(card[2], 19);
        const detail = safeText(card[3], row === 1 ? 21 : 23);
        const valueSize = row === 1 ? 24 : 27;
        const detailSize = row === 1 ? 16 : 18;

        drawLeft(card[0], iconX, cellY + 66, 38, 900, "#ffffff");
        drawLeft(title, textX, cellY + 33, 16, 1000, "#ffb8ff");
        drawLeft(value, textX, cellY + 68, valueSize, 1000, "#ffffff");
        drawLeft(detail, textX, cellY + 98, detailSize, 700, "#ffffff");
      }

      statCards.forEach(drawStatCell);

      ctx.fillStyle = "rgba(10,25,80,.66)";
      ctx.strokeStyle = "rgba(182,92,255,.95)";
      ctx.lineWidth = 3;
      roundRect(ctx, 70, 1305, 560, 250, 26, true, true);
      drawCentered("ÉVOLUTION DES SCORES PAR MANCHE", 350, 1340, 23, 1000, "#ffffff");
      const chartX = 130, chartY = 1380, chartW = 455, chartH = 130;
      ctx.strokeStyle = "rgba(255,255,255,.14)";
      ctx.lineWidth = 2;
      ctx.beginPath(); ctx.moveTo(chartX, chartY + chartH); ctx.lineTo(chartX + chartW, chartY + chartH); ctx.stroke();
      const chartTeams = ranked.slice(0, 2);
      const maxTotal = Math.max(1, ...chartTeams.flatMap(entry => playedRounds.map(r => Number(entry.t.perRound?.[r] || 0))).concat(chartTeams.map(e => Number(e.t.total || 0))));
      chartTeams.forEach((entry, teamIdx)=>{
        const accent = teamIdx === 0 ? "#00b7ff" : "#d646ff";
        let cum = 0;
        const points = playedRounds.map((r, i)=>{
          cum += Number(entry.t.perRound?.[r] || 0);
          const x = chartX + (playedRounds.length <= 1 ? chartW / 2 : (i / (playedRounds.length - 1)) * chartW);
          const y = chartY + chartH - (cum / Math.max(1, Math.max(...chartTeams.map(e => Number(e.t.total || 0))))) * chartH;
          return {x,y,cum,r};
        });
        ctx.strokeStyle = accent;
        ctx.lineWidth = 4;
        ctx.beginPath();
        points.forEach((p,i)=> i ? ctx.lineTo(p.x,p.y) : ctx.moveTo(p.x,p.y));
        ctx.stroke();
        points.forEach(p=>{ ctx.fillStyle = accent; ctx.beginPath(); ctx.arc(p.x,p.y,7,0,Math.PI*2); ctx.fill(); drawCentered(String(p.cum), p.x, p.y-14, 18, 900, "#ffffff"); });
        drawLeft(`● ${safeText(entry.t.name, 18)}`, 110, 1380 + teamIdx * 28, 20, 800, teamIdx === 0 ? "#66dfff" : "#f16cff");
      });
      playedRounds.forEach((r,i)=>{
        const x = chartX + (playedRounds.length <= 1 ? chartW / 2 : (i / (playedRounds.length - 1)) * chartW);
        drawCentered(`M${r}`, x, 1536, 18, 800, "#ffffff");
      });

      ctx.fillStyle = "rgba(55,15,90,.70)";
      ctx.strokeStyle = "rgba(214,70,255,.95)";
      ctx.lineWidth = 3;
      roundRect(ctx, 650, 1305, 360, 250, 26, true, true);
      drawLeft("🤖", 690, 1375, 56, 900, "#ffffff");
      drawLeft("ANALYSE IA", 770, 1355, 28, 1000, "#ff98ff");
      drawLeft("CHRONO PARTY", 770, 1390, 28, 1000, "#ff98ff");
      drawMultiline("✅ " + aiSummary, 690, 1440, 23, 800, "#ffffff", 31, 4);

      ctx.fillStyle = "rgba(3,14,45,.84)";
      ctx.strokeStyle = "rgba(255,216,77,.90)";
      ctx.lineWidth = 4;
      roundRect(ctx, 70, 1582, 940, 285, 34, true, true);
      drawLeft("VOUS AVEZ AIMÉ LA PARTIE ?", 110, 1648, 34, 1000, "#ffd84d");
      drawLeft("Scannez ce QR code pour jouer à votre tour", 110, 1698, 27, 800, "#ffffff");
      drawLeft("⚡ Installation rapide", 110, 1750, 26, 800, "#ffffff");
      drawLeft("💜 Parfait pour vos soirées", 110, 1795, 26, 800, "#ffffff");
      drawLeft("🎁 Démo gratuite", 110, 1840, 26, 800, "#ffffff");

      ctx.strokeStyle = "#ffd84d";
      ctx.lineWidth = 4;
      ctx.beginPath();
      ctx.moveTo(640, 1745); ctx.quadraticCurveTo(690, 1685, 740, 1698); ctx.stroke();
      ctx.beginPath(); ctx.moveTo(740, 1698); ctx.lineTo(712, 1687); ctx.lineTo(724, 1718); ctx.stroke();

      const qrImg = await loadFinalShareImage("./assets/img/qr-chrono-party.png");
      if(qrImg){
        ctx.fillStyle = "#ffffff";
        roundRect(ctx, 760, 1625, 185, 185, 22, true, false);
        ctx.drawImage(qrImg, 772, 1637, 161, 161);
        ctx.strokeStyle = "#ffd84d";
        ctx.lineWidth = 5;
        roundRect(ctx, 760, 1625, 185, 185, 22, false, true);
      }
      ctx.fillStyle = "#ffd84d";
      roundRect(ctx, 690, 1810, 315, 52, 20, true, false);
      drawCentered("SCANNEZ POUR JOUER", 847, 1845, 24, 1000, "#06102c");

      ctx.fillStyle = "rgba(255,255,255,.08)";
      ctx.strokeStyle = "rgba(0,174,255,.60)";
      ctx.lineWidth = 3;
      roundRect(ctx, 250, 1872, 580, 38, 16, true, true);
      drawCentered("https://chrono-party.e-u.fr/demo2026/", 540, 1898, 21, 900, "#ffffff");

      canvas.toBlob(blob => resolve(blob), "image/png", .94);
    }catch(e){
      resolve(null);
    }
  });
}

function roundRect(ctx, x, y, w, h, r, fill, stroke){
  if(w < 2 * r) r = w / 2;
  if(h < 2 * r) r = h / 2;
  ctx.beginPath();
  ctx.moveTo(x + r, y);
  ctx.arcTo(x + w, y, x + w, y + h, r);
  ctx.arcTo(x + w, y + h, x, y + h, r);
  ctx.arcTo(x, y + h, x, y, r);
  ctx.arcTo(x, y, x + w, y, r);
  ctx.closePath();
  if(fill) ctx.fill();
  if(stroke) ctx.stroke();
}

async function shareFinalGame(){
  const text = getFinalShareText();
  const url = state.publicPartyUrl || "https://chrono-party.e-u.fr/demo2026/";

  try{
    const blob = await generateFinalShareImageBlob();
    if(blob && navigator.canShare && navigator.share){
      const file = new File([blob], "chrono-party-resultats.png", {type:"image/png"});
      if(navigator.canShare({files:[file]})){
        await navigator.share({
          title: "Résultats Chrono Party",
          text,
          url,
          files:[file]
        });
        showToast("Partage ouvert ✅");
        return;
      }
    }

    if(navigator.share){
      await navigator.share({
        title: "Résultats Chrono Party",
        text,
        url
      });
      showToast("Partage ouvert ✅");
      return;
    }

    await navigator.clipboard.writeText(text);
    showToast("Scores + lien copiés ✅");
  }catch(e){
    try{
      await navigator.clipboard.writeText(text);
      showToast("Scores + lien copiés ✅");
    }catch(err){
      showToast("Partage indisponible sur cet appareil");
    }
  }
}



function getCoopFinalPerformance(turnsLeft, success){
  if(!success){
    return { icon:"💀", label:"Défi perdu", className:"lost" };
  }

  if(turnsLeft >= 20){
    return { icon:"🏆", label:"Légende", className:"legend" };
  }
  if(turnsLeft >= 10){
    return { icon:"🥇", label:"Or", className:"gold" };
  }
  if(turnsLeft >= 5){
    return { icon:"🥈", label:"Argent", className:"silver" };
  }
  return { icon:"🥉", label:"Bronze", className:"bronze" };
}

function buildCoopFinalHtml(){
  recalcScores();

  const team = state.teams[0] || null;
  const turnsUsed = getCoopTurnsUsed();
  const maxTurns = Number(state.coop?.maxTurns || getCoopBossMaxTurns());
  const level = COOP_BOSS_LEVELS[state.coop?.level || "standard"] || COOP_BOSS_LEVELS.standard;
  const completedAllRounds = !state.coop?.endedByBoss && state.round >= TOTAL_ROUNDS;
  const success = completedAllRounds && turnsUsed <= maxTurns;
  const turnsLeft = Math.max(0, maxTurns - turnsUsed);
  const overTurns = Math.max(0, turnsUsed - maxTurns);
  const perf = getCoopFinalPerformance(turnsLeft, success);
  const wordsFound = team ? getTeamTotalFound(team) : 0;
  const passesUsed = team ? getTeamTotalPasses(team) : 0;
  const playersHtml = team && Array.isArray(team.players)
    ? team.players.map(p => `<span>${escapeHtml(p)}</span>`).join("")
    : "";

  return `
    <div class="coopFinalHero ${success ? "success" : "failed"}">
      <div class="coopBossBattle ${success ? "bossDefeated" : "bossWinner"}" aria-hidden="true">
        <div class="coopBossEmoji">👹</div>
        <div class="coopBossExplosion">💥</div>
        <div class="coopBossTrophy">${success ? "🏆" : "🔥"}</div>
      </div>

      <div class="coopFinalBadge">${level.icon} ${escapeHtml(level.label)}</div>
      <div class="coopFinalTitle">${success ? "BOSS CHRONO VAINCU" : "BOSS CHRONO SURVIVANT"}</div>
      <div class="coopFinalText">
        ${success
          ? `Le groupe a terminé les ${TOTAL_ROUNDS} manches avec ${turnsLeft} tour${turnsLeft > 1 ? "s" : ""} d'avance.`
          : `Le groupe était proche, mais le Boss a résisté${overTurns ? ` avec ${overTurns} tour${overTurns > 1 ? "s" : ""} de dépassement` : ""}.`}
      </div>

      <div class="coopFightSummary">
        <div class="coopFightSummaryHeader">👹 Résumé du combat</div>
        <div class="coopFightSummaryResult">${success ? "👹💥🏆" : "🔥👹🔥"}</div>
        <div class="coopFightSummaryTitle">${success ? "Boss Chrono vaincu" : "Le Boss Chrono gagne…"}</div>
      </div>

      <div class="coopFinalStats">
        <div><strong>${turnsUsed}/${maxTurns}</strong><span>Tours utilisés</span></div>
        <div><strong>${success ? turnsLeft : "+" + overTurns}</strong><span>${success ? "Tours restants" : "Dépassement"}</span></div>
        <div class="coopPerf ${perf.className}"><strong>${perf.icon} ${perf.label}</strong><span>Performance</span></div>
        <div><strong>${playedRoundsArray().length}/${TOTAL_ROUNDS}</strong><span>Manches</span></div>
      </div>

      <div class="coopFinalSmallStats">
        <span>✅ ${wordsFound} mots trouvés</span>
        <span>⏭️ ${passesUsed} pass utilisés</span>
      </div>
    </div>

    <div class="coopFinalPlayers">
      <strong>Joueurs coopératifs</strong>
      <div>${playersHtml}</div>
    </div>
  `;
}

function showCoopFinalOverlay(){
  recalcScores();
  autoSaveFinishedGameStats();
  $("finalContent").innerHTML = buildCoopFinalHtml();
  $("finalOverlay").style.display = "flex";
  const turnsUsed = getCoopTurnsUsed();
  const maxTurns = Number(state.coop?.maxTurns || getCoopBossMaxTurns());
  const success = !state.coop?.endedByBoss && state.round >= TOTAL_ROUNDS && turnsUsed <= maxTurns;
  playCoopFinalSound(success);
  if(success){
    window.setTimeout(launchConfetti, 950);
  }

  window.clearTimeout(voiceTimeoutId);
  voiceTimeoutId = window.setTimeout(() => {
    voiceTimeoutId = null;
    if($("finalOverlay").style.display === "flex"){
      const turnsUsed = getCoopTurnsUsed();
      const maxTurns = Number(state.coop?.maxTurns || getCoopBossMaxTurns());
      const success = !state.coop?.endedByBoss && state.round >= TOTAL_ROUNDS && turnsUsed <= maxTurns;
      const turnsLeft = Math.max(0, maxTurns - turnsUsed);
      const overTurns = Math.max(0, turnsUsed - maxTurns);
      const perf = getCoopFinalPerformance(turnsLeft, success);
      speakChrono(success
        ? `Bravo, Boss Chrono vaincu. ${turnsUsed} tours utilisés sur ${maxTurns}. Il reste ${turnsLeft} tours. Performance ${perf.label}.`
        : `Défi perdu. Le Boss Chrono gagne. ${turnsUsed} tours utilisés sur ${maxTurns}. Dépassement de ${overTurns} tours.`
      );
    }
  }, 700);
}

function showFinalOverlay(){
  if(state.coop?.enabled){
    showCoopFinalOverlay();
    return;
  }

  recalcScores();
  autoSaveFinishedGameStats();

  const ranked = state.teams
    .map((t,i)=>({t,i}))
    .sort((a,b)=> compareTeams(a.t, b.t));

  $("finalContent").innerHTML = `
    ${buildFinalChampionHeroHtml(ranked)}

    ${buildFinalAdvancedStatsHtml(ranked)}

    <div class="finalDesktopTable">
      <table class="scoreTable">
        <thead>
          <tr>
            <th>Équipe</th>
            ${playedRoundsArray().map(r=>`<th>M${r}</th>`).join("")}
            <th>Total</th>
          </tr>
        </thead>

        <tbody>
          ${ranked.map(x=>`
            <tr>
              <td>
                <b>${escapeHtml(x.t.name)}</b>
                <div class="small">
                  ${x.t.players.map(escapeHtml).join(", ")}
                </div>
              </td>

              ${playedRoundsArray().map(r=>`
  <td>${x.t.perRound[r] || 0}</td>
`).join("")}

              <td style="text-align:right">
                <b>${x.t.total}</b>
              </td>
            </tr>
          `).join("")}
        </tbody>
      </table>
    </div>

    ${buildFinalMobileCards(ranked)}

  `;

  $("finalOverlay").style.display = "flex";
launchConfetti();

window.clearTimeout(voiceTimeoutId);

voiceTimeoutId = window.setTimeout(() => {
  voiceTimeoutId = null;

  if($("finalOverlay").style.display === "flex"){
    announceFinalWinner();
  }
}, 700);
}

function hideFinalOverlay(){
$("finalOverlay").style.display = "none";
}
function getRoundIcon(r){
  if(r === 1) return "🟢";
  if(r === 2) return "🟡";
  if(r === 3) return "🔵";
  return "🟣";
}

function getRoundShortTitle(r){
  if(r === 1) return "Description libre";
  if(r === 2) return "Un seul mot";
  if(r === 3) return "Mime";
  return "Statue sonore";
}

function buildPremiumNextTurnHtml({
  badge = "À vous de jouer",
  icon = "🎯",
  title = "",
  subtitle = "",
  showScores = true
} = {}){
  const team = getCurrentTeam();
  const teamName = team?.name || "-";
  const playerName = getSpokenPlayerName(getCurrentPlayerName(team));
  const playersLabel = getTeamPlayersLabel(team);
  const total = state.originalDeck?.length || state.deck?.length || 0;
  const remain = state.remaining?.length || total || 0;

  return `
    <div class="premiumTransitionHero">
      <div class="premiumTransitionInner">
        <div class="premiumTransitionBadge">${badge}</div>

        <div class="premiumTransitionIcon">${icon}</div>

        <div class="premiumTransitionLabel">
          ${roundLabel(state.round)}
        </div>

        <div class="premiumTransitionTitle">
          ${escapeHtml(title || teamName)}
        </div>

        <div class="premiumTransitionSubtitle">
          ${subtitle || `C’est au tour de <b>${escapeHtml(playerName)}</b> de faire deviner les mots.`}
        </div>

        <div class="premiumTransitionPlayers">
          <span class="premiumTransitionPlayer">👥 ${escapeHtml(teamName)}</span>
          <span class="premiumTransitionPlayer">🎤 ${escapeHtml(playerName)}</span>
          ${playersLabel ? `<span class="premiumTransitionPlayer">👫 ${escapeHtml(playersLabel)}</span>` : ``}
        </div>

          <div class="premiumTransitionInfoGrid">
          <div class="premiumTransitionInfoCard">
            <strong>${state.round}</strong>
            <span>Manche</span>
          </div>

          <div class="premiumTransitionInfoCard">
            <strong>${remain}${total ? `/${total}` : ""}</strong>
            <span>Mots restants</span>
          </div>

          <div class="premiumTransitionInfoCard">
            <strong>${state.secondsPerTurn + (team?.nextTurnBonusSeconds || 0)}s</strong>
            <span>Chrono</span>
          </div>

          <div class="premiumTransitionInfoCard">
            <strong>${Math.max(0, clamp(parseInt($("passesPerTurn").value || "2", 10), 0, 10) + (team?.nextTurnBonusPasses || 0))}</strong>
            <span>Pass</span>
          </div>
        </div>

        ${getCoopStatusHtml()}
        ${state.coop?.enabled ? "" : buildAdaptiveTurnInfoHtml(team)}

      </div>
    </div>

    ${showScores ? buildMiniScoresHtml() : ""}
  `;
}

function getRoundPremiumHint(r){
  if(r === 1){
    return "Décrivez librement, mais attention : le mot exact est interdit.";
  }

  if(r === 2){
    return "Un seul mot autorisé. Choisissez-le bien, il peut tout changer.";
  }

  if(r === 3){
    return "Silence total. Tout passe par les gestes, les attitudes et les mimiques.";
  }

  return "Restez figé comme une statue, mais utilisez les sons, bruitages ou fredonnements.";
}

function buildPremiumRoundStartHtml(){
  const team = getCurrentTeam();
  const teamName = team?.name || "-";
  const playerName = getSpokenPlayerName(getCurrentPlayerName(team));
  const playersLabel = getTeamPlayersLabel(team);

  const total = state.originalDeck?.length || state.deck?.length || 0;
  const remain = state.remaining?.length || total || 0;

  const chronoValue = state.secondsPerTurn + (team?.nextTurnBonusSeconds || 0);

  const passValue = Math.max(
    0,
    clamp(parseInt($("passesPerTurn").value || "2", 10), 0, 10) + (team?.nextTurnBonusPasses || 0)
  );

  return `
    <div class="premiumTransitionHero roundStartFx roundTheme${state.round}">
      <div class="roundStartParticles">
        <span></span><span></span><span></span>
        <span></span><span></span><span></span>
      </div>

      <div class="premiumTransitionInner">
        <div class="premiumTransitionBadge">Nouvelle manche</div>

        <div class="premiumTransitionIcon">${getRoundIcon(state.round)}</div>

        <div class="premiumTransitionLabel">
          Manche ${state.round} sur ${TOTAL_ROUNDS}
        </div>

        <div class="premiumTransitionTitle">
          ${escapeHtml(getRoundShortTitle(state.round))}
        </div>

        <div class="premiumTransitionSubtitle">
          <span style="font-size:clamp(30px, 5.5vw, 54px); font-weight:1100; color:#fff; line-height:1.05; text-shadow:0 12px 34px rgba(0,0,0,.55), 0 0 30px rgba(124,92,255,.42);">
            ${state.coop?.enabled ? "Groupe Coop" : escapeHtml(teamName)}
          </span>
          <br>
          C’est au tour de
          <br>
          <span style="display:inline-block; margin-top:8px; font-size:clamp(30px, 5.5vw, 54px); font-weight:1100; color:#fff; line-height:1.05; text-shadow:0 12px 34px rgba(0,0,0,.55), 0 0 30px rgba(124,92,255,.42);">
            ${escapeHtml(playerName)}
          </span>
        </div>

        <div class="roundRulePremiumBox">
          <div class="roundRulePremiumLabel">Règle de la manche</div>
          <div class="roundRulePremiumTitle">${escapeHtml(roundRuleShort(state.round))}</div>
          <div class="roundRulePremiumText">${escapeHtml(roundDesc(state.round))}</div>
        </div>

        <div class="premiumTransitionPlayers">
          <span class="premiumTransitionPlayer">👥 ${state.coop?.enabled ? "Groupe Coop" : escapeHtml(teamName)}</span>
          <span class="premiumTransitionPlayer">🎤 ${escapeHtml(playerName)}</span>
          ${playersLabel ? `<span class="premiumTransitionPlayer">👫 ${escapeHtml(playersLabel)}</span>` : ``}
        </div>

        <div class="premiumTransitionInfoGrid">
          <div class="premiumTransitionInfoCard">
            <strong>${state.round}</strong>
            <span>Manche</span>
          </div>

          <div class="premiumTransitionInfoCard">
            <strong>${remain}${total ? `/${total}` : ""}</strong>
            <span>Mots restants</span>
          </div>

          <div class="premiumTransitionInfoCard">
            <strong>${chronoValue}s</strong>
            <span>Chrono</span>
          </div>

          <div class="premiumTransitionInfoCard">
            <strong>${passValue}</strong>
            <span>Pass</span>
          </div>
        </div>

        ${getCoopStatusHtml()}
        ${state.coop?.enabled ? "" : buildAdaptiveTurnInfoHtml(team)}

      </div>
    </div>
  `;
}
function buildPremiumTurnChangeHtml(){
  const team = getCurrentTeam();
  const teamName = team?.name || "-";
  const playerName = getSpokenPlayerName(getCurrentPlayerName(team));
  const total = state.originalDeck?.length || state.deck?.length || 0;
  const remain = state.remaining?.length || total || 0;

  return `
    <div class="premiumTransitionHero roundStartFx roundTheme${state.round}">
      <div class="roundStartParticles">
        <span></span><span></span><span></span>
        <span></span><span></span><span></span>
      </div>

      <div class="premiumTransitionInner">
        <div class="premiumTransitionBadge">${state.coop?.enabled ? "Joueur suivant" : "Changement d’équipe"}</div>

        <div class="premiumTransitionIcon">🎤</div>

        <div class="premiumTransitionLabel">
          Manche ${state.round} sur ${TOTAL_ROUNDS}
        </div>

        <div class="premiumTransitionTitle">
          ${state.coop?.enabled ? "Groupe Coop" : escapeHtml(teamName)}
        </div>

        <div class="premiumTransitionSubtitle">
          C’est au tour de
          <br>
          <span style="display:inline-block; margin-top:8px; font-size:clamp(30px, 5.5vw, 54px); font-weight:1100; color:#fff; line-height:1.05; text-shadow:0 12px 34px rgba(0,0,0,.55), 0 0 30px rgba(124,92,255,.42);">
            ${escapeHtml(playerName)}
          </span>
        </div>

        <div class="roundRulePremiumBox">
          <div class="roundRulePremiumLabel">
            Règle de la manche
          </div>

          <div class="roundRulePremiumText">
            ${escapeHtml(roundDesc(state.round))}
          </div>

          <div class="roundRulePremiumHint">
            ${escapeHtml(getRoundPremiumHint(state.round))}
          </div>
        </div>

        <div class="premiumTransitionPlayers">
          <span class="premiumTransitionPlayer">👥 ${escapeHtml(teamName)}</span>
          <span class="premiumTransitionPlayer">🎤 ${escapeHtml(playerName)}</span>
        </div>

        <div class="premiumTransitionInfoGrid">
          <div class="premiumTransitionInfoCard">
            <strong>${state.round}</strong>
            <span>Manche</span>
          </div>

          <div class="premiumTransitionInfoCard">
            <strong>${remain}${total ? `/${total}` : ""}</strong>
            <span>Mots restants</span>
          </div>

 <div class="premiumTransitionInfoCard">
            <strong>${state.secondsPerTurn + (team?.nextTurnBonusSeconds || 0)}s</strong>
            <span>Chrono</span>
          </div>

          <div class="premiumTransitionInfoCard">
            <strong>${Math.max(0, clamp(parseInt($("passesPerTurn").value || "2", 10), 0, 10) + (team?.nextTurnBonusPasses || 0))}</strong>
            <span>Pass</span>
          </div>
        </div>

        ${getCoopStatusHtml()}

        ${!state.coop?.enabled && state.adaptiveMaster && ((team?.nextTurnBonusSeconds || 0) !== 0 || (team?.nextTurnBonusPasses || 0) !== 0) ? `
          <div class="turnIaInfo">
            Maître adaptatif :
            ${(team?.nextTurnBonusSeconds || 0) > 0 ? "+" : ""}${team?.nextTurnBonusSeconds || 0} sec
            ${(team?.nextTurnBonusPasses || 0) !== 0 ? ` / ${(team?.nextTurnBonusPasses || 0) > 0 ? "+" : ""}${team?.nextTurnBonusPasses || 0} pass` : ""}
          </div>
        ` : state.adaptiveMaster ? `
          <div class="turnIaInfo">Maître adaptatif actif</div>
        ` : ""}

      </div>
    </div>

    ${state.coop?.enabled ? "" : buildMiniScoresHtml()}
  `;
}
function buildPremiumRoundEndHtml(roundJustFinished){
  recalcScores();

  if(state.coop?.enabled){
    const nextRound = roundJustFinished + 1;
    const maxTurns = Math.max(1, Number(state.coop.maxTurns || getCoopBossMaxTurns()));
    const used = getCoopTurnsUsed();
    const left = Math.max(0, maxTurns - used);
    const roundTitle = roundJustFinished >= TOTAL_ROUNDS
      ? "Partie terminée"
      : `Manche ${roundJustFinished} terminée`;

    const nextText = roundJustFinished >= TOTAL_ROUNDS
      ? "Le résultat du défi coopératif va être affiché."
      : `La manche ${nextRound} arrive avec les mêmes mots, mais une nouvelle règle.`;

    return `
      <div class="premiumTransitionHero roundStartFx roundTheme${roundJustFinished}">
        <div class="roundStartParticles">
          <span></span><span></span><span></span>
          <span></span><span></span><span></span>
        </div>

        <div class="premiumTransitionInner">
          <div class="premiumTransitionBadge">Résumé coopératif</div>
          <div class="premiumTransitionIcon">👹</div>

          <div class="premiumTransitionLabel">
            Manche ${roundJustFinished} sur ${TOTAL_ROUNDS}
          </div>

          <div class="premiumTransitionTitle">
            ${escapeHtml(roundTitle)}
          </div>

          <div class="premiumTransitionSubtitle">
            ${escapeHtml(nextText)}
          </div>

          <div class="premiumTransitionInfoGrid">
            <div class="premiumTransitionInfoCard">
              <strong>${used}/${maxTurns}</strong>
              <span>Tours utilisés</span>
            </div>

            <div class="premiumTransitionInfoCard">
              <strong>${left}</strong>
              <span>Tours restants Boss</span>
            </div>

            <div class="premiumTransitionInfoCard">
              <strong>${roundJustFinished}/${TOTAL_ROUNDS}</strong>
              <span>Manches terminées</span>
            </div>

            <div class="premiumTransitionInfoCard">
              <strong>${state.originalDeck?.length || 0}</strong>
              <span>Mots par manche</span>
            </div>
          </div>

          ${getCoopStatusHtml()}
        </div>
      </div>
    `;
  }

  const ranked = state.teams
    .map((t,i)=>({t,i}))
    .sort((a,b)=> compareTeams(a.t, b.t));

  const best = ranked[0];
  const bestTeam = best?.t || null;

  const nextRound = roundJustFinished + 1;

  const roundTitle = roundJustFinished >= TOTAL_ROUNDS
    ? "Partie terminée"
    : `Manche ${roundJustFinished} terminée`;

  let nextText = "";

if(roundJustFinished >= TOTAL_ROUNDS){
  nextText = "Le classement final va être affiché.";
}else if(roundJustFinished === 3){
  nextText = "Vous allez pouvoir choisir si vous voulez jouer la manche 4 ou afficher directement le classement final.";
}else{
  nextText = `La manche ${nextRound} arrive avec les mêmes mots, mais une nouvelle règle.`;
}

  return `
    <div class="premiumTransitionHero roundStartFx roundTheme${roundJustFinished}">
      <div class="roundStartParticles">
        <span></span><span></span><span></span>
        <span></span><span></span><span></span>
      </div>

      <div class="premiumTransitionInner">
        <div class="premiumTransitionBadge">Résumé de manche</div>

        <div class="premiumTransitionIcon">🏁</div>

        <div class="premiumTransitionLabel">
          Manche ${roundJustFinished} sur ${TOTAL_ROUNDS}
        </div>

        <div class="premiumTransitionTitle">
          ${escapeHtml(roundTitle)}
        </div>

        <div class="premiumTransitionSubtitle">
          ${escapeHtml(nextText)}
        </div>

        ${bestTeam ? `
  <div class="premiumTransitionScoreFocus">
    <div class="premiumTransitionScoreFocusTitle">
      En tête actuellement
    </div>

    <div class="premiumTransitionScoreFocusTeam">
      ${escapeHtml(bestTeam.name)}
    </div>

    ${getTeamPlayersLabel(bestTeam) ? `
      <div class="premiumTransitionMiniPodiumPlayers" style="margin-top:6px; font-size:15px;">
        ${escapeHtml(getTeamPlayersLabel(bestTeam))}
      </div>
    ` : ``}

    <div class="premiumTransitionScoreFocusPoints">
      ${bestTeam.total} point${bestTeam.total > 1 ? "s" : ""}
    </div>
  </div>
` : ``}

        <div class="premiumTransitionMiniPodium">
          ${ranked.slice(0, 3).map((entry, index) => {
            const medal = index === 0 ? "🥇" : index === 1 ? "🥈" : "🥉";
            const players = getTeamPlayersLabel(entry.t);

            return `
              <div class="premiumTransitionMiniPodiumItem">
                <div>
                  <div class="premiumTransitionMiniPodiumName">
                    ${medal} ${escapeHtml(entry.t.name)}
                  </div>

                  ${players ? `
                    <div class="premiumTransitionMiniPodiumPlayers">
                      ${escapeHtml(players)}
                    </div>
                  ` : ``}
                </div>

                <div class="premiumTransitionMiniPodiumPoints">
                  ${entry.t.total} pts
                </div>
              </div>
            `;
          }).join("")}
        </div>
      </div>
    </div>

    ${buildMiniScoresHtml()}
  `;
}
function showFloatingPoint(){
  const old = document.querySelector(".floatingPointFx");
  if(old) old.remove();

  const fx = document.createElement("div");
  fx.className = "floatingPointFx";
  fx.textContent = state.coop?.enabled ? "+1 mot" : "+1";

  document.body.appendChild(fx);

  window.setTimeout(() => {
    fx.remove();
  }, 700);
}
function showTurnModal(title, htmlText, okText, pillBig=false){
  $("modalBackdrop").classList.remove("simpleModal");

  const total = state.originalDeck?.length || state.deck?.length || 0;
  const remain = state.remaining?.length || total || 0;
  const team = getCurrentTeam();
  const teamName = team?.name || "-";
  const playerName = getSpokenPlayerName(getCurrentPlayerName(team));

  const isRandomStartScreen =
    state.teamMode === "random" &&
    title === "Début de partie";

  const isPremiumTransition =
    String(htmlText || "").includes("premiumTransitionHero");

  $("modalTitle").textContent = title;
  $("modalPill").textContent = `Manche ${state.round} • Joueur : ${playerName}`;
  $("modalPill").classList.toggle("pillBig", !!pillBig);

  $("modalText").innerHTML = isPremiumTransition
    ? `
      <div class="small" style="margin-top:12px">
        ${htmlText}
      </div>

      <div class="modalTools">
        <button class="ghost miniBtn" type="button" onclick="showScoresModal()">Afficher scores</button>
        <button class="ghost miniBtn" type="button" onclick="saveScoresToFile()">Sauver scores</button>
      </div>
    `
    : `
      ${modalRoundBoxHtml()}

      <div class="activePlayerHero" style="margin-top:12px; width:100%;">
        <div class="activePlayerLabel">Le joueur qui fait deviner est</div>
        <div class="activePlayerName">${escapeHtml(playerName)}</div>
        <div class="activePlayerTeam">${escapeHtml(teamName)}</div>
      </div>

      <div class="small" style="margin-top:12px">${htmlText}</div>

      <div style="display:flex; gap:10px; align-items:center; flex-wrap:wrap; margin-top:12px;">
        <span class="pill">📌 Mots restants : <b>${remain}${total ? ` / ${total}` : ""}</b></span>
        <span class="pill">📜 ${roundRuleShort(state.round)}</span>
      </div>

      ${isRandomStartScreen ? "" : buildMiniScoresHtml()}

      <div class="modalTools">
        <button class="ghost miniBtn" type="button" onclick="showScoresModal()">Afficher scores</button>
        <button class="ghost miniBtn" type="button" onclick="saveScoresToFile()">Sauver scores</button>
      </div>
    `;

  $("modalOk").classList.remove("hidden");
  $("modalCancel").classList.remove("hidden");
  $("modalCancel").textContent = "Quitter la partie";
  $("modalOk").textContent = okText || "Démarrer";

$("modalCancel").onclick = () => {
  showSimpleModal(
    "Quitter la partie",
    "La partie en cours sera quittée immédiatement. Voulez-vous vraiment continuer ?",
    "Oui, quitter",
    () => {
      cancelGameAndReturnConfig();
    },
    "Annuler",
    () => {
      $("modalBackdrop").style.display = "none";
      $("modalBackdrop").classList.remove("simpleModal");

      showTurnModal(
        title,
        htmlText,
        okText,
        pillBig
      );
    }
  );
};

$("modalOk").onpointerdown = null;
$("modalOk").disabled = false;
$("modalOk").style.display = "";
resetModalOkButton();
$("modalOk").onclick = ()=>{
  cancelVoiceAnnouncement();
  ensureAudio();
  hideTurnModal();
  startTurn();
};

$("modalBackdrop").style.display = "flex";

requestAnimationFrame(() => {
  scrollModalToTop();
  window.dispatchEvent(new Event("resize"));
});

scheduleVoiceAnnouncement(announceReadyToStart, 500);
}

function scrollModalToTop(){
  window.setTimeout(() => {
    const backdrop = $("modalBackdrop");
    const body = document.querySelector("#modalBackdrop .modalBody");

    if(backdrop){
      backdrop.scrollTop = 0;
    }

    if(body){
      body.scrollTo({
        top: 0,
        behavior: "auto"
      });
    }
  }, 0);
}
function hideTurnModal(){
  cancelVoiceAnnouncement();

  $("modalBackdrop").style.display = "none";
  $("modalBackdrop").classList.remove("simpleModal");
  modalReturnStack = [];
  $("modalPill").classList.remove("pillBig");
}

async function loadGameConfig(){
  try{
    $("remoteInfo").textContent = "Chargement de la configuration…";

    const { response, data } = await requestJson(CONFIG_URL + "?v=" + Date.now(), {}, 12000);

    if(!response.ok) throw new Error((data && data.message) || ("HTTP " + response.status));

    if(!data || !data.ok || !Array.isArray(data.themes)){
      throw new Error("Configuration invalide");
    }

    GAME_CONFIG = data;
GAME_CONFIG.mode = "premium";

WORD_DATA = {};
data.themes.forEach(theme => { WORD_DATA[theme] = {}; });

    buildThemesUI(data.themes);
    applySavedDifficulties(data.difficulties || ["facile","moyen","difficile"]);

    $("wordCount").max = String(GAME_CONFIG.maxWords || 80);

    if(parseInt($("wordCount").value || "40", 10) > (GAME_CONFIG.maxWords || 80)){
      $("wordCount").value = String(GAME_CONFIG.maxWords || 80);
    }

    updateDifficultyCountInfo();
    updateDataInfo();

    if(typeof applyPreset === "function" && currentPreset !== "perso"){
      applyPreset(currentPreset);
    }else if(typeof refreshSetupUX === "function"){
      refreshSetupUX();
    }

    $("remoteInfo").textContent = "✅ Configuration chargée";
  }catch(e){
    console.error("Erreur chargement game-config.php :", e);

    WORD_DATA = {};
    GAME_CONFIG = {
      themes: [],
      difficulties: ["facile","moyen","difficile"],
      maxWords: 0
    };

    const themesBox = $("themesBox");
    if(themesBox){
      themesBox.innerHTML = `
        <div class="small" style="padding:12px; border-radius:14px; border:1px solid rgba(255,77,109,.35); background:rgba(255,77,109,.10); color:#ffd166;">
          Impossible de charger les thèmes. Reconnectez-vous ou rechargez la page.
        </div>
      `;
    }

    if($("wordCount")){
      $("wordCount").max = "0";
    }

    updateDifficultyCountInfo();

    if($("themeCountInfo")) $("themeCountInfo").textContent = "0 thème chargé";
    if($("themeSummary")) $("themeSummary").textContent = "Configuration indisponible";
    if($("dataInfo")) $("dataInfo").textContent = "Configuration indisponible.";
    if($("remoteInfo")) $("remoteInfo").textContent = "⚠️ Configuration inaccessible.";

    if(typeof updateSetupSummary === "function"){
      updateSetupSummary();
    }

    updateStartButtonsState();

    if(isAuthenticationErrorMessage(e?.message || "")){
      showAccessError(e?.message || "");
      redirectToLogin();
    }
  }
}

async function loadKidsGameConfig(){
  try{
    $("remoteInfo").textContent = "Chargement du mode Enfants…";

    const { response, data } = await requestJson(KIDS_CONFIG_URL + "?v=" + Date.now(), {}, 12000);

    if(!response.ok) throw new Error((data && data.message) || ("HTTP " + response.status));

    if(!data || !data.ok || !Array.isArray(data.themes)){
      throw new Error("Configuration enfants invalide");
    }

  GAME_CONFIG = data;
GAME_CONFIG.mode = "kids";

WORD_DATA = {};
data.themes.forEach(theme => { WORD_DATA[theme] = {}; });

    buildThemesUI(data.themes);
    applySavedDifficulties(["facile"]);

    $("wordCount").max = String(GAME_CONFIG.maxWords || 80);

    if(parseInt($("wordCount").value || "25", 10) > (GAME_CONFIG.maxWords || 80)){
      $("wordCount").value = String(GAME_CONFIG.maxWords || 80);
    }

    updateDifficultyCountInfo();
    updateDataInfo();
    refreshSetupUX();

    $("remoteInfo").textContent = "✅ Mode Enfants chargé";
  }catch(e){
    console.error("Erreur chargement game-config-kids.php :", e);

    showSimpleModal(
      "Erreur",
      escapeHtml(e?.message || "Impossible de charger le mode Enfants."),
      "OK"
    );
  }
}
async function loadFamilyGameConfig(){
  try{
    $("remoteInfo").textContent = "Chargement de l’ambiance Famille…";

    const { response, data } = await requestJson(FAMILY_CONFIG_URL + "?v=" + Date.now(), {}, 12000);

    if(!response.ok) throw new Error((data && data.message) || ("HTTP " + response.status));

    if(!data || !data.ok || !data.themes || typeof data.themes !== "object"){
      throw new Error("Configuration famille invalide");
    }

    GAME_CONFIG = data;
    GAME_CONFIG.mode = "family";
    GAME_CONFIG.themes = Object.keys(data.themes);
    GAME_CONFIG.maxWords = GAME_CONFIG.maxWords || 80;

    WORD_DATA = data.themes;

    buildThemesUI(GAME_CONFIG.themes);
    applySavedDifficulties(["facile"]);

    $("wordCount").max = String(GAME_CONFIG.maxWords || 80);

    if(parseInt($("wordCount").value || "25", 10) > (GAME_CONFIG.maxWords || 80)){
      $("wordCount").value = String(GAME_CONFIG.maxWords || 80);
    }

    updateDifficultyCountInfo();
    updateDataInfo();
    refreshSetupUX();

    $("remoteInfo").textContent = "✅ Ambiance Famille chargée";
  }catch(e){
    console.error("Erreur chargement game-config-family.php :", e);

    showSimpleModal(
      "Erreur",
      escapeHtml(e?.message || "Impossible de charger l’ambiance Famille."),
      "OK"
    );
  }
}
function saveSettings(){
  const payload = {
    selectedThemes: getSelectedThemes(),
    selectedDifficulties: getSelectedDifficulties(),
    wordCount: $("wordCount").value,
    turnSeconds: $("turnSeconds").value,
    passesPerTurn: $("passesPerTurn").value,
    teamCount: $("teamCount").value,
    teamMode: $("teamMode").value,
    teamsConfig: getTeamsUiSnapshot(),
    randomTeamNames: getRandomTeamNamesSnapshot(),
    randomPlayersInput: $("randomPlayersInput").value,
    sound: $("soundToggle").value,
voice: $("voiceToggle") ? $("voiceToggle").value : "on",
customWordsInput: $("customWordsInput") ? $("customWordsInput").value : ""
  };
  localStorage.setItem("chronoparty_settings", JSON.stringify(payload));
  showSimpleModal("Réglages", "Réglages sauvegardés ✅", "OK");
}

function applySavedDifficulties(selectedDifficulties){
  const selected = Array.isArray(selectedDifficulties) && selectedDifficulties.length
    ? new Set(selectedDifficulties)
    : new Set(["facile"]);

  document.querySelectorAll('input[name="difficultyCheck"]').forEach(input=>{
    input.checked = selected.has(input.value);
  });

  if(getSelectedDifficulties().length === 0){
    $("diff_facile").checked = true;
  }

  updateDifficultyCountInfo();
}

function loadSettings(){
  const raw = localStorage.getItem("chronoparty_settings");
  if(!raw) return;

  try{
    const p = JSON.parse(raw);

    $("wordCount").value = p.wordCount || "40";
    $("turnSeconds").value = p.turnSeconds || "30";
    $("passesPerTurn").value = p.passesPerTurn || "2";
    $("teamCount").value = p.teamCount || "2";
    $("teamMode").value = p.teamMode || "manual";
    $("soundToggle").value = p.sound || "on";

    if($("voiceToggle")){
      $("voiceToggle").value = p.voice || "on";
    }
if($("customWordsInput")){
  $("customWordsInput").value = p.customWordsInput || "";
  updateCustomWordsInfo();
}
    $("randomPlayersInput").value = p.randomPlayersInput || "";

    if(Array.isArray(p.teamsConfig) && p.teamsConfig.length){
      buildTeamsUI(p.teamsConfig);
    }else{
      buildTeamsUI();
    }

    buildRandomTeamNamesUI(Array.isArray(p.randomTeamNames) ? p.randomTeamNames : null);
    buildThemesUI(GAME_CONFIG.themes || Object.keys(WORD_DATA), p.selectedThemes || null);
    applySavedDifficulties(p.selectedDifficulties || null);

    if(parseInt($("wordCount").value || "40", 10) > (GAME_CONFIG.maxWords || 80)){
      $("wordCount").value = String(GAME_CONFIG.maxWords || 80);
    }

    updateModeUI();
    updateRandomPlayersInfo();
    updateDataInfo();
    refreshSetupUX();

  }catch(e){
    console.error("Erreur loadSettings :", e);
  }
}

function askExitTurn(){
  if(!state.inGame) return;
  showSimpleModal(
    "Quitter le tour",
    "Quitter le tour en cours ? Vous passerez d’abord par l’écran de correction du tour.",
    "Oui, quitter",
    () => endTurn(false),
    "Annuler",
    hideTurnModal
  );
}

$("teamCount").addEventListener("change", ()=>{
  buildTeamsUI(getTeamsUiSnapshot());
  buildRandomTeamNamesUI(getRandomTeamNamesSnapshot());
});

$("teamMode").addEventListener("change", updateModeUI);
$("randomPlayersInput").addEventListener("input", updateRandomPlayersInfo);
$("randomPlayersInput")?.addEventListener("focus", () => refreshChronoKnownPlayersSuggestions(true));
$("customWordsInput")?.addEventListener("input", () => {
  currentPreset = "perso";
  updateCustomWordsInfo();
  refreshSetupUX();
});
$("btnAllThemes").addEventListener("click", ()=> selectAllThemes(true));
$("btnNoThemes").addEventListener("click", ()=> selectAllThemes(false));

$("btnAllDiffs").addEventListener("click", ()=> selectAllDifficulties(true));
$("btnNoDiffs").addEventListener("click", ()=> selectAllDifficulties(false));

document.querySelectorAll('input[name="difficultyCheck"]').forEach(input=>{
  input.addEventListener("change", ()=>{
    if(getSelectedDifficulties().length === 0) input.checked = true;
    updateDifficultyCountInfo();
    updateDataInfo();
  });
});

$("btnStartGame").addEventListener("click", handleStartButtonClick);
$("btnStartGameMobile").addEventListener("click", handleStartButtonClick);

// $("btnQuickStart").addEventListener("click", startQuickGame);
$("btnGoConfig").addEventListener("click", goConfig);
$("btnGoRules").addEventListener("click", goRules);
const btnVideo = document.getElementById("btnVideo");
const btnHomeStats = document.getElementById("btnHomeStats");
const btnHomeScores = document.getElementById("btnHomeScores");
const btnHomeGift = document.getElementById("btnHomeGift");

function showStatsModal(){
  const history = JSON.parse(localStorage.getItem("chrono_scores_history") || "[]");

  if(!history.length){
    showSimpleModal("Statistiques", "Aucune partie sauvegardée pour le moment.", "OK");
    return;
  }

  const stats = {
    totalGames: history.length,
    lastDate: history[0]?.date
  ? String(history[0].date).split(" ")[0]
  : "—",
    totalWords: 0,
    totalScores: 0,
    totalTeamResults: 0,
    bestTeamScore: null,
    biggestWin: null,
    closestGame: null,
    teamWins: {},
    teamScores: {},
    teamGames: {},
    playerWins: {}
  };

  function cleanName(name){
    return String(name || "").trim();
  }

  function parseGame(item){
    const lines = String(item.text || "").split("\n");

    const classementStart = lines.findIndex(l => l.includes("CLASSEMENT"));
    const detailStart = lines.findIndex(l => l.includes("DETAIL DES EQUIPES"));
    const wordsStart = lines.findIndex(l => l.includes("DETAIL DES MOTS TROUVES"));

    const classementLines = classementStart >= 0 && detailStart > classementStart
      ? lines.slice(classementStart + 2, detailStart).filter(l => l.trim())
      : [];

    const teamLines = detailStart >= 0 && wordsStart > detailStart
      ? lines.slice(detailStart + 2, wordsStart).filter(l => l.includes(":"))
      : [];

    const playersByTeam = {};

    teamLines.forEach(line => {
      const parts = line.split(":");
      const teamName = cleanName(parts[0]);
      const players = cleanName(parts.slice(1).join(":"))
        .split(",")
        .map(p => cleanName(p))
        .filter(Boolean);

      if(teamName){
        playersByTeam[teamName] = players;
      }
    });

    const results = classementLines.map(line => {
      const match = line.match(/^\d+\.\s*(.*?)\s*-\s*(\d+)\s*pts/i);
      if(!match) return null;

      const teamName = cleanName(match[1]);
      const score = parseInt(match[2], 10);
      const players = playersByTeam[teamName] || [];

      return {
        teamName,
        score,
        players,
        teamKey: players.length ? players.slice().sort().join(" + ") : teamName
      };
    }).filter(Boolean);

    const wordsCount = lines.filter(line =>
      /^Manche\s+\d+\s+\(\d+\)\s*:/i.test(line)
    ).reduce((sum, line) => {
      const match = line.match(/\((\d+)\)/);
      return sum + (match ? parseInt(match[1], 10) : 0);
    }, 0);

    return { results, wordsCount };
  }

  history.forEach(item => {
    const game = parseGame(item);
    const results = game.results;

    stats.totalWords += game.wordsCount;

    results.forEach(result => {
      stats.totalScores += result.score;
      stats.totalTeamResults++;

      stats.teamGames[result.teamKey] = (stats.teamGames[result.teamKey] || 0) + 1;
      stats.teamScores[result.teamKey] = (stats.teamScores[result.teamKey] || 0) + result.score;

      if(!stats.bestTeamScore || result.score > stats.bestTeamScore.score){
        stats.bestTeamScore = result;
      }
    });

    if(results.length){
      const winner = results[0];
      stats.teamWins[winner.teamKey] = (stats.teamWins[winner.teamKey] || 0) + 1;

      winner.players.forEach(player => {
        stats.playerWins[player] = (stats.playerWins[player] || 0) + 1;
      });

      if(results.length >= 2){
        const gap = results[0].score - results[1].score;

        if(!stats.biggestWin || gap > stats.biggestWin.gap){
          stats.biggestWin = { gap, winner: results[0], second: results[1] };
        }

        if(!stats.closestGame || gap < stats.closestGame.gap){
          stats.closestGame = { gap, winner: results[0], second: results[1] };
        }
      }
    }
  });

  function bestFromObject(obj){
    return Object.entries(obj).sort((a,b) => b[1] - a[1])[0] || null;
  }

  const bestTeamWins = bestFromObject(stats.teamWins);
  const mostPlayedTeam = bestFromObject(stats.teamGames);
  const bestPlayer = bestFromObject(stats.playerWins);

  const bestAverageTeam = Object.keys(stats.teamScores)
    .map(teamKey => ({
      teamKey,
      avg: stats.teamScores[teamKey] / stats.teamGames[teamKey]
    }))
    .sort((a,b) => b.avg - a.avg)[0];

  const topPlayers = Object.entries(stats.playerWins)
    .sort((a,b) => b[1] - a[1])
    .slice(0, 3);

  const averageScore = stats.totalTeamResults
    ? Math.round(stats.totalScores / stats.totalTeamResults)
    : 0;

  const topPlayersHtml = topPlayers.length
  ? topPlayers.map((entry, index) => `
      ${index + 1}. ${entry[0]}
      <br><span class="statResult"><strong>${entry[1]} victoire${entry[1] > 1 ? "s" : ""}</strong></span>
    `).join("<br><br>")
  : "—";

  showSimpleModal(
    "📊 Statistiques",
    `
      <div style="background:linear-gradient(135deg,rgba(124,92,255,.16),rgba(0,212,255,.08));border:1px solid rgba(255,255,255,.14);border-radius:24px;padding:18px;">
        <div style="font-size:20px;font-weight:1000;margin-bottom:14px;">Vue générale</div>

        <div class="statsGrid">
  <div class="statCard">
    <div class="statIcon">🎮</div>
    <b>${stats.totalGames}</b>
    <span>Parties sauvegardées</span>
  </div>

  <div class="statCard">
    <div class="statIcon">📅</div>
    <b>${stats.lastDate}</b>
    <span>Dernière partie</span>
  </div>

  <div class="statCard">
    <div class="statIcon">📝</div>
    <b>${stats.totalWords}</b>
    <span>Total mots trouvés</span>
  </div>

  <div class="statCard">
    <div class="statIcon">🎯</div>
    <b>${averageScore} pts</b>
    <span>Score moyen équipe</span>
  </div>
</div>

        <hr style="border:none;border-top:1px solid rgba(255,255,255,.12);margin:18px 0;">

        <div style="font-size:20px;font-weight:1000;margin-bottom:12px;">Records d’équipes</div>

        <div class="statLine">🏆 Équipe la plus victorieuse<br>
  <b>${bestTeamWins ? `${bestTeamWins[0]}<br><span class="statResult"><strong>${bestTeamWins[1]} victoire${bestTeamWins[1] > 1 ? "s" : ""}</strong></span>` : "—"}</b>
</div>

   <div class="statLine">🔥 Meilleur score d’équipe<br>
  <b>${stats.bestTeamScore ? `${stats.bestTeamScore.teamKey}<br><span class="statResult"><strong>${stats.bestTeamScore.score} pts</strong></span>` : "—"}</b>
</div>


        <div class="statLine">📊 Meilleure moyenne d’équipe<br>
  <b>${bestAverageTeam ? `${bestAverageTeam.teamKey}<br><span class="statResult"><strong>${Math.round(bestAverageTeam.avg)} pts de moyenne</strong></span>` : "—"}</b>
</div>

    <div class="statLine">🤝 Duo / équipe le plus joué<br>
  <b>${mostPlayedTeam ? `${mostPlayedTeam[0]}<br><span class="statResult"><strong>${mostPlayedTeam[1]} partie${mostPlayedTeam[1] > 1 ? "s" : ""}</strong></span>` : "—"}</b>
</div>

       <div class="statLine">⚔️ Partie la plus serrée<br>
  <b>${stats.closestGame ? `${stats.closestGame.winner.teamKey}<br><span class="statResult"><strong>+${stats.closestGame.gap} point${stats.closestGame.gap > 1 ? "s" : ""} d’écart</strong></span>` : "—"}</b>
</div>
       <div class="statLine">🚀 Plus grosse victoire<br>
  <b>${stats.biggestWin ? `${stats.biggestWin.winner.teamKey}<br><span class="statResult"><strong>+${stats.biggestWin.gap} pts</strong></span>` : "—"}</b>
</div>

        <hr style="border:none;border-top:1px solid rgba(255,255,255,.12);margin:18px 0;">

        <div style="font-size:20px;font-weight:1000;margin-bottom:12px;">Champions joueurs</div>

      <div class="statLine">🏆 Joueur le plus souvent gagnant<br>
  <b>${bestPlayer ? `${bestPlayer[0]}<br><span class="statResult"><strong>${bestPlayer[1]} victoire${bestPlayer[1] > 1 ? "s" : ""}</strong></span>` : "—"}</b>
</div>

       <div class="statLine">🥇 Top 3 des joueurs gagnants<br>
  <b>${topPlayersHtml}</b>
</div>

        <div style="margin-top:18px;">
          <button class="ghost" type="button" onclick="resetSavedScores()">
            🗑️ Réinitialiser les scores sauvegardés
          </button>
        </div>
      </div>
    `,
    "Fermer"
  );
}

function resetSavedScores(){
  showSimpleModal(
    "Réinitialiser les scores",
    "Voulez-vous vraiment supprimer tout l’historique des scores sauvegardés sur cet appareil ?",
    "Oui, supprimer",
    () => {
      localStorage.removeItem("chrono_scores_history");
      localStorage.removeItem("topchrono_last_scores");

      showSimpleModal(
        "Scores réinitialisés",
        "Tous les scores sauvegardés ont été supprimés ✅",
        "OK"
      );
    },
    "Annuler",
    hideTurnModal
  );
}

if(btnVideo){
  btnVideo.addEventListener("click", (e) => {
    e.preventDefault();
    e.stopPropagation();
    window.location.href = "/videos/";
  });
}

if(btnHomeStats){
  btnHomeStats.addEventListener("click", (e) => {
    e.preventDefault();
    e.stopPropagation();

    const playerId = localStorage.getItem("chrono_last_player_id") || "";

    window.location.href = playerId
      ? `/statistiques/?id=${encodeURIComponent(playerId)}`
      : "/statistiques/";
  });
}

if(btnHomeScores){
  btnHomeScores.addEventListener("click", (e) => {
    e.preventDefault();
    e.stopPropagation();

    const playerId =
      localStorage.getItem("chrono_personal_player_id")
      || localStorage.getItem("chrono_last_player_id")
      || "";

    window.location.href = playerId
      ? `/joueur/?id=${encodeURIComponent(playerId)}`
      : "/classement-joueurs/";
  });
}

if(btnHomeGift){
  btnHomeGift.addEventListener("click", (e) => {
    e.preventDefault();
    e.stopPropagation();
window.location.href = "/jeux/offrir-chrono-party/";
  });
}
$("btnRulesBackHome").addEventListener("click", goHome);
$("btnRulesGoConfig").addEventListener("click", goConfig);
$("btnBackHomeConfig").addEventListener("click", goHome);

bindTap($("btnFoundFs"), markFound);
bindTap($("btnPassFs"), passWord);

$("btnHomeFs").addEventListener("click", goHome);
$("btnExitFs").addEventListener("click", askExitTurn);
$("btnHomeFinal").addEventListener("click", goHome);
$("btnCloseFinal").addEventListener("click", forceGoHome);
$("btnFinalHome").addEventListener("click", goHome);
$("btnFinalScores").addEventListener("click", ()=> showScoresModal());
const btnShareFinalGame = $("btnShareFinalGame");
if(btnShareFinalGame){ btnShareFinalGame.addEventListener("click", shareFinalGame); }

$("btnSave").addEventListener("click", saveSettings);
$("btnLoad").addEventListener("click", loadSettings);
$("btnReset").addEventListener("click", ()=>{
  showSimpleModal(
    "Réinitialiser",
    "Voulez-vous vraiment recharger complètement l’application ?",
    "Oui",
    () => location.reload(),
    "Annuler",
    hideTurnModal
  );
});


["quickPlayer1","quickPlayer2","quickPlayer3","quickPlayer4","quickTeam1","quickTeam2"].forEach(id=>{
  $(id).addEventListener("input", () => {
    validateQuickSetupForm();

    const team1Name = $("quickTeam1").value.trim() || "Équipe violette";
    const team2Name = $("quickTeam2").value.trim() || "Équipe bleue";

    if($("quickTeam1Label")){
      $("quickTeam1Label").textContent = team1Name;
    }

    if($("quickTeam2Label")){
      $("quickTeam2Label").textContent = team2Name;
    }
  });
});

$("btnCloseQuickSetup").addEventListener("click", closeQuickSetupModal);
$("btnStartQuickSetup").addEventListener("click", handleValidatedQuickStart);

$("quickSetupModal").addEventListener("click", (e)=>{
  if(e.target === $("quickSetupModal")){
    closeQuickSetupModal();
  }
});

let currentPreset = "familiale";

function setInputValueAndNotify(inputId, value){
  const input = $(inputId);
  if(!input) return;

  const max = input.max ? parseInt(input.max, 10) : null;
  let finalValue = parseInt(value, 10);

  if(Number.isFinite(max)){
    finalValue = Math.min(finalValue, max);
  }

  input.value = String(finalValue);
  input.dispatchEvent(new Event("change", { bubbles:true }));
  input.dispatchEvent(new Event("input", { bubbles:true }));
}

function refreshChoiceButtons(){
  document.querySelectorAll(".choiceGroup").forEach(group=>{
    const targetId = group.dataset.target;
    const target = $(targetId);
    if(!target) return;

    const currentValue = String(target.value);

    group.querySelectorAll(".choiceBtn").forEach(btn=>{
      btn.classList.toggle("active", String(btn.dataset.value) === currentValue);
    });
  });

  document.querySelectorAll(".presetCard, .presetCustomBtn").forEach(btn=>{
  btn.classList.toggle("active", btn.dataset.preset === currentPreset);
});
}

function setDifficulties(values){
  const wanted = new Set(values);

  document.querySelectorAll('input[name="difficultyCheck"]').forEach(input=>{
    input.checked = wanted.has(input.value);
  });

  if(getSelectedDifficulties().length === 0){
    $("diff_facile").checked = true;
  }

  updateDifficultyCountInfo();
}


function getKidsAgeDifficulties(age){
  if(age === "8-9") return ["facile", "moyen"];
  if(age === "10+") return ["moyen", "difficile"];
  return ["facile"];
}

function getKidsAgeLabel(age){
  if(age === "8-9") return "8–9 ans";
  if(age === "10+") return "10 ans et +";
  return "6–7 ans";
}

function updateKidsAgeButtons(age){
  document.querySelectorAll('[data-kids-age]').forEach(btn=>{
    btn.classList.toggle("active", String(btn.dataset.kidsAge) === String(age || "6-7"));
  });
}

function updateKidsAgeBox(){
  const box = $("kidsAgeBox");
  if(!box) return;

  box.style.display = currentPreset === "kids" ? "block" : "none";
  updateKidsAgeButtons(window.currentKidsAge || "6-7");
}

function applyKidsAgeDifficulty(age){
  window.currentKidsAge = age || "6-7";
  setDifficulties(getKidsAgeDifficulties(window.currentKidsAge));
  updateKidsAgeButtons(window.currentKidsAge);
  refreshSetupUX?.();
}

window.getKidsAgeDifficulties = getKidsAgeDifficulties;
window.getKidsAgeLabel = getKidsAgeLabel;
window.applyKidsAgeDifficulty = applyKidsAgeDifficulty;

function selectPreferredThemes(themeNames){
  const checks = Array.from(document.querySelectorAll('input[name="themeCheck"]'));

  if(!checks.length) return;

  const wanted = new Set(themeNames);
  let checkedCount = 0;

  checks.forEach(input=>{
    input.checked = wanted.has(input.value);
    if(input.checked) checkedCount++;
  });

  if(checkedCount === 0){
    checks.slice(0, 4).forEach(input => input.checked = true);
  }

  updateThemeCountInfo();
}
function selectRandomThemes(count, preferredThemes = []){
  const checks = Array.from(document.querySelectorAll('input[name="themeCheck"]'));

  if(!checks.length) return;

  const availableValues = checks.map(input => input.value);

  const preferredAvailable = preferredThemes.filter(theme =>
    availableValues.includes(theme)
  );

  const otherAvailable = availableValues.filter(theme =>
    !preferredAvailable.includes(theme)
  );

  const finalThemes = [
    ...shuffle(preferredAvailable),
    ...shuffle(otherAvailable)
  ].slice(0, Math.min(count, availableValues.length));

  const wanted = new Set(finalThemes);

  checks.forEach(input => {
    input.checked = wanted.has(input.value);
  });

  if(getSelectedThemes().length === 0 && checks[0]){
    checks[0].checked = true;
  }

  updateThemeCountInfo();
}

function selectFirstAvailableThemes(count){
  const checks = Array.from(document.querySelectorAll('input[name="themeCheck"]'));

  if(!checks.length) return;

  checks.forEach((input, index)=>{
    input.checked = index < count;
  });

  if(getSelectedThemes().length === 0 && checks[0]){
    checks[0].checked = true;
  }

  updateThemeCountInfo();
}

function selectEveryTheme(){
  const checks = Array.from(document.querySelectorAll('input[name="themeCheck"]'));

  checks.forEach(input=>{
    input.checked = true;
  });

  updateThemeCountInfo();
}

function applyPreset(presetName){
  currentPreset = presetName;

  if(presetName === "kids"){
      
    
    setInputValueAndNotify("wordCount", 25);
    setInputValueAndNotify("turnSeconds", 45);
    setInputValueAndNotify("passesPerTurn", 10);
    window.currentKidsAge = window.currentKidsAge || "6-7";
    setDifficulties(getKidsAgeDifficulties(window.currentKidsAge));

 selectRandomThemes(8, [
  "🐶 Animaux",
  "🍕 Nourriture",
  "🏠 Objets de la maison",
  "🏃 Actions",
  "🚒 Métiers",
  "🚗 Véhicules",
  "🐉 Imaginaire",
  "🎒 École"
]);

    refreshSetupUX();
    return;
  }

if(presetName === "familiale"){
  setInputValueAndNotify("wordCount", 30);
  setInputValueAndNotify("turnSeconds", 30);
  setInputValueAndNotify("passesPerTurn", 5);
  setDifficulties(["facile"]);

selectRandomThemes(8, [
  "🐶 Animaux & nature",
  "🍕 Nourriture",
  "🏠 Maison & quotidien",
  "🏃 Actions",
  "🏖️ Vacances & loisirs",
  "🚒 Métiers",
  "🚗 Véhicules",
  "🐉 Imaginaire"
]);

  refreshSetupUX();
  return;
}

  if(presetName === "classique"){
      
    
    setInputValueAndNotify("wordCount", 40);
    setInputValueAndNotify("turnSeconds", 30);
    setInputValueAndNotify("passesPerTurn", 2);
    setDifficulties(["facile", "moyen"]);

selectRandomThemes(10, [
  "📦 Objets du quotidien",
  "🎬 Ciné & séries",
  "🎵 Musique",
  "✈️ Voyage",
  "🧑‍💼 Métiers",
  "🐾 Animaux",
  "⚽ Sport",
  "🍽 Cuisine",
  "🧍 Corps humain",
  "🙌 Actions du quotidien"
]);

}

  if(presetName === "challenge"){
      
    
    setInputValueAndNotify("wordCount", 50);
    setInputValueAndNotify("turnSeconds", 30);
    setInputValueAndNotify("passesPerTurn", 0);
    setDifficulties(["moyen", "difficile"]);
    selectEveryTheme();
  }

  if(presetName === "perso"){
      
    // On ne change rien : l’utilisateur garde ses réglages.
  }

  refreshSetupUX();
}

function countPlayersForSummary(){
  const mode = $("teamMode")?.value || "manual";

  if(mode === "random"){
    return parsePlayersList($("randomPlayersInput")?.value || "").length;
  }

  let total = 0;
  const n = clamp(parseInt($("teamCount").value || "2", 10), 2, 12);

  for(let i = 0; i < n; i++){
    const playerCount = clamp(parseInt($("playerCount_" + i)?.value || "2", 10), 2, 12);
    total += playerCount;
  }

  return total;
}

function updateSetupSummary(){
  const summary = $("setupSummaryText");
  if(!summary) return;

  const teamCount = clamp(parseInt($("teamCount")?.value || "2", 10), 2, 12);
  const playerCount = countPlayersForSummary();
  const wordCount = $("wordCount")?.value || "40";
  const turnSeconds = $("turnSeconds")?.value || "30";
  const passes = $("passesPerTurn")?.value || "2";
  const diffs = getSelectedDifficulties();
  const themes = getSelectedThemes();
  const mode = $("teamMode")?.value === "random" ? "répartition automatique" : "équipes choisies";

  summary.innerHTML = `
    <b>${teamCount}</b> équipe${teamCount > 1 ? "s" : ""} •
    <b>${playerCount}</b> joueur${playerCount > 1 ? "s" : ""} •
    <b>${wordCount}</b> mots •
    <b>${turnSeconds}</b> secondes •
    <b>${passes}</b> pass / tour<br>
    Niveau : <b>${diffs.join(" + ") || "—"}</b><br>
    Thèmes : <b>${themes.length ? themes.join(" + ") : "—"}</b><br>
    Mode : <b>${mode}</b>
  `;
}

function updateAdaptiveMasterButton(){
  const btn = $("toggleAdaptiveMaster");
  if(!btn) return;

  btn.classList.toggle("active", state.adaptiveMaster);
}

function refreshSetupUX(){
  refreshChoiceButtons();
  updateCustomWordsInfo();
  updateDataInfo();
  updateSetupSummary();
  updateStartButtonsState();
  updateAdaptiveMasterButton();
  updateKidsAgeBox();
}

function adjustTeamCount(delta){
  const input = $("teamCount");
  const current = clamp(parseInt(input.value || "2", 10), 2, 12);
  const next = clamp(current + delta, 2, 12);

  input.value = String(next);
  input.dispatchEvent(new Event("change", { bubbles:true }));

  refreshSetupUX();
}

document.addEventListener("click", (e) => {
  const btn = e.target.closest("#toggleAdaptiveMaster");
  if(!btn) return;

  e.preventDefault();
  e.stopPropagation();

  state.adaptiveMaster = !state.adaptiveMaster;
  updateAdaptiveMasterButton();
  updateKidsAgeBox();
});
document.addEventListener("click", (e) => {
  const btn = e.target.closest("#toggleAdaptiveMasterGuided");
  if(!btn) return;

  e.preventDefault();
  e.stopPropagation();

  state.adaptiveMaster = !state.adaptiveMaster;
  updateAdaptiveMasterButton();

  btn.classList.toggle("active", state.adaptiveMaster);
});

function attachSetupUxEvents(){
  document.querySelectorAll(".presetCard, .presetCustomBtn").forEach(btn=>{
    btn.addEventListener("click", async ()=>{
      const preset = btn.dataset.preset || "perso";

      if(preset === "kids"){
        await loadKidsGameConfig();
        applyPreset("kids");

      }else if(preset === "familiale"){
        await loadFamilyGameConfig();
        applyPreset("familiale");

      }else{
        if(preset !== "perso" && (GAME_CONFIG?.mode === "kids" || GAME_CONFIG?.mode === "family")){
          await loadGameConfig();
        }

        applyPreset(preset);
      }

      window.setTimeout(()=>{
        let target = null;

        if(preset === "perso"){
          target = $("themesStep");
        }else{
          const missingPlayer = getFirstMissingPlayerElement();

          if(missingPlayer){
            target = missingPlayer.closest(".setupStep") || $("teamsStep");
          }else{
            target = $("themesStep");
          }
        }

        if(target){
          target.scrollIntoView({
            behavior: "smooth",
            block: "start"
          });
        }
      }, 120);
    });
  });

  document.querySelectorAll(".choiceBtn").forEach(btn=>{
    btn.addEventListener("click", ()=>{
      const group = btn.closest(".choiceGroup");
      if(!group) return;

      if(btn.dataset.kidsAge){
        currentPreset = "kids";
        applyKidsAgeDifficulty(btn.dataset.kidsAge);
        return;
      }

      const targetId = group.dataset.target;
      const value = btn.dataset.value;

      currentPreset = "perso";
      setInputValueAndNotify(targetId, value);
      refreshSetupUX();
    });
  });

  $("btnTeamMinus")?.addEventListener("click", ()=> adjustTeamCount(-1));
  $("btnTeamPlus")?.addEventListener("click", ()=> adjustTeamCount(1));

  ["soundToggle", "teamMode", "teamCount", "randomPlayersInput"].forEach(id=>{
    $(id)?.addEventListener("input", refreshSetupUX);
    $(id)?.addEventListener("change", refreshSetupUX);
  });

  document.addEventListener("change", (e)=>{
    if(
      e.target?.matches?.('input[name="themeCheck"]') ||
      e.target?.matches?.('input[name="difficultyCheck"]') ||
      String(e.target?.id || "").startsWith("playerCount_") ||
      String(e.target?.id || "").startsWith("playerName_") ||
      String(e.target?.id || "").startsWith("teamName_")
    ){
      currentPreset = "perso";
      window.setTimeout(refreshSetupUX, 0);
    }
  });

  document.addEventListener("input", (e)=>{
    if(
      String(e.target?.id || "").startsWith("playerName_") ||
      String(e.target?.id || "").startsWith("teamName_") ||
      e.target?.id === "randomPlayersInput"
    ){
      window.setTimeout(refreshSetupUX, 0);
    }
  });

  $("btnLoad")?.addEventListener("click", ()=>{
    currentPreset = "perso";
    window.setTimeout(refreshSetupUX, 80);
  });
}
buildTeamsUI();
buildRandomTeamNamesUI();
updateModeUI();
updateRandomPlayersInfo();
updateCustomWordsInfo();
updateDifficultyCountInfo();
updateDataInfo();
attachSetupUxEvents();
attachCustomWordsToggle();
refreshSetupUX();
initChronoKnownPlayersSuggestions();
initChronoManualPlayerSuggestions();
initChronoQuickPlayerSuggestions();

window.addEventListener("load", async () => {

  await loadFamilyGameConfig();
applyPreset("familiale");

  setTimeout(checkSavedGameOnLoad, 800);

  trackChronoParty(
    "ouverture_jeu",
    "Chargement de l'application"
  );

  heartbeatTimerId = window.setInterval(() => {
    trackChronoParty("heartbeat", getTrackingContext());
  }, 25000);

});
window.addEventListener("pagehide", () => {
  sendTrackingBeacon("fermeture_page", getTrackingContext());
  if(heartbeatTimerId){
    window.clearInterval(heartbeatTimerId);
    heartbeatTimerId = null;
  }
});

showScreen("homeScreen");

function attachCustomWordsToggle(){
  const step = $("customWordsStep");
  const header = $("customWordsHeader");
  const chevron = $("customWordsChevron");

  if(!step || !header) return;

  header.addEventListener("click", () => {
    step.classList.toggle("customWordsOpen");

    if(chevron){
      chevron.textContent = step.classList.contains("customWordsOpen") ? "▴" : "▾";
    }
  });
}


/* =========================
   MODE COOP - UI CONFIGURATION
========================= */
(function initCoopConfigUI(){

  function updateCoopBossTurnLabels(){
    document.querySelectorAll("[data-coop-turns-info]").forEach(el => {
      const levelKey = el.dataset.coopTurnsInfo || "standard";
      const turns = getCoopBossMaxTurnsForLevel(levelKey);
      el.textContent = `${turns} tours max`;
    });
  }

function syncCoopPlayersWording(){
  const mode = $("gameMode")?.value || "classic";
  const isCoop = mode === "coop";
  document.body.classList.toggle("coopMode", isCoop);

  // IMPORTANT : dans ton HTML, le bloc s'appelle teamsStep et non playersStep.
  const playersStep = $("playersStep") || $("teamsStep");

  if(playersStep){
    const title = playersStep.querySelector("h2, .stepTitle, .sectionTitle, .setupStepTitle");
    if(title){
      title.textContent = isCoop
        ? "1. Joueurs du groupe Coop"
        : "1. Joueurs et équipes";
    }

    const hint = playersStep.querySelector(".setupStepHint, .stepSubtitle");
    if(hint){
      hint.style.display = isCoop ? "none" : "";
      if(!isCoop){
        hint.textContent = "Vous pouvez choisir les équipes vous-même ou laisser le jeu répartir les joueurs.";
      }
    }

    // Masque les zones de configuration d'équipes en Coop.
    const teamCount = $("teamCount");
    if(teamCount){
      const block = teamCount.closest(".grid2 > div, .formRow, .field, div");
      if(block) block.style.display = isCoop ? "none" : "";
    }

    const teamModeSelect = $("teamMode");
    if(teamModeSelect){
      const block = teamModeSelect.closest(".grid2 > div, .formRow, .field, div");
      if(block) block.style.display = isCoop ? "none" : "";
    }

    // Si toute la grille ne sert plus qu'aux équipes, on la masque en Coop.
    const firstGrid = playersStep.querySelector(".grid2");
    if(firstGrid){
      firstGrid.style.display = isCoop ? "none" : "";
    }

    const manualModeBox = $("manualModeBox");
    if(manualModeBox){
      manualModeBox.style.display = isCoop ? "none" : "";
    }

    const randomModeBox = $("randomModeBox");
    if(randomModeBox){
      randomModeBox.classList.remove("hidden");
      randomModeBox.style.display = "";
    }

    const randomTeamNamesBox = $("randomTeamNamesBox");
    if(randomTeamNamesBox){
      randomTeamNamesBox.style.display = isCoop ? "none" : "";
    }

    // Cache tous les textes qui parlent encore d'équipes / mode automatique.
    playersStep.querySelectorAll(".small, label, p").forEach(el => {
      const txt = (el.textContent || "").toLowerCase();
      const isTeamText =
        txt.includes("équipe") ||
        txt.includes("equipe") ||
        txt.includes("mode automatique") ||
        txt.includes("répartit les joueurs") ||
        txt.includes("repartit les joueurs") ||
        txt.includes("répartir les joueurs") ||
        txt.includes("repartir les joueurs") ||
        txt.includes("comment créer");

      if(isTeamText){
        el.style.display = isCoop ? "none" : "";
      }
    });

    // Ton label n'a pas de for="randomPlayersInput", donc on le récupère depuis le bloc automatique.
    const randomPlayersLabel = $("randomPlayersInput")?.previousElementSibling?.previousElementSibling?.tagName === "LABEL"
      ? $("randomPlayersInput").previousElementSibling.previousElementSibling
      : $("randomPlayersInput")?.parentElement?.querySelector("label");

    if(randomPlayersLabel){
      randomPlayersLabel.style.display = "";
      randomPlayersLabel.innerHTML = isCoop
        ? 'Prénoms et initiales des joueurs <span class="requiredStar">*</span>'
        : 'Liste complète des joueurs <span class="requiredStar">*</span>';
    }
  }

  const teamModeSelect = $("teamMode");
  if(isCoop && teamModeSelect && teamModeSelect.value !== "random"){
    teamModeSelect.value = "random";
    teamModeSelect.dispatchEvent(new Event("change", { bubbles:true }));
  }

  const randomPlayersInput = $("randomPlayersInput");
  if(randomPlayersInput){
    randomPlayersInput.placeholder = isCoop
      ? "Giovanni C\nSandrine C\nBruno D"
      : "Pierre\nSophie\nMarc\nJulie\nThomas";
  }

  const randomPlayersInfo = $("randomPlayersInfo");
  if(randomPlayersInfo){
    const count = parsePlayersList(randomPlayersInput?.value || "").length;
    randomPlayersInfo.textContent = isCoop
      ? `${count} joueur${count > 1 ? "s" : ""} dans le groupe Coop`
      : `${count} joueur${count > 1 ? "s" : ""} saisi${count > 1 ? "s" : ""}`;
  }

  if(typeof renderChronoKnownPlayersSuggestions === "function"){
    renderChronoKnownPlayersSuggestions();
  }
}

  function syncCoopUI(){
    const mode = $("gameMode")?.value || "classic";

    document.querySelectorAll(".gameModeCard").forEach(btn => {
      btn.classList.toggle("selected", btn.dataset.gameMode === mode);
    });

    const box = $("coopBossConfig");
    if(box){
      box.classList.toggle("hidden", mode !== "coop");
    }

    document.querySelectorAll(".coopBossLevelCard").forEach(btn => {
      btn.classList.toggle(
        "selected",
        btn.dataset.coopLevel === ($("coopBossLevel")?.value || "standard")
      );
    });

    updateCoopBossTurnLabels();
    syncCoopPlayersWording();

    if(typeof updateStartButtonsState === "function"){
      updateStartButtonsState();
    }
  }

  document.querySelectorAll(".gameModeCard").forEach(btn => {
    btn.addEventListener("click", () => {
      const selectedMode = btn.dataset.gameMode || "classic";

      if($("gameMode")){
        $("gameMode").value = selectedMode;
        $("gameMode").dispatchEvent(new Event("change", { bubbles:true }));
      }

      syncCoopUI();

      setTimeout(() => {
        if(selectedMode === "coop"){
          $("coopBossConfig")?.scrollIntoView({ behavior:"smooth", block:"start" });
        }else{
          $("presetStep")?.scrollIntoView({ behavior:"smooth", block:"start" });
        }
      }, 250);
    });
  });

  document.querySelectorAll(".coopBossLevelCard").forEach(btn => {
    btn.addEventListener("click", () => {
      if($("coopBossLevel")){
        $("coopBossLevel").value = btn.dataset.coopLevel || "standard";
        $("coopBossLevel").dispatchEvent(new Event("change", { bubbles:true }));
      }

      syncCoopUI();

      setTimeout(() => {
        $("presetStep")?.scrollIntoView({ behavior:"smooth", block:"start" });
      }, 250);
    });
  });

  ["wordCount", "turnSeconds", "passesPerTurn"].forEach(id => {
    const el = $(id);
    if(el){
      el.addEventListener("change", syncCoopUI);
      el.addEventListener("input", syncCoopUI);
    }
  });

  ["gameMode", "coopBossLevel"].forEach(id => {
    const el = $(id);
    if(el){
      el.addEventListener("change", syncCoopUI);
    }
  });

  syncCoopUI();

})();