"use client";

import { useCallback, useEffect, useMemo, useRef, useState } from "react";

type Screen = "home" | "settings" | "packs" | "playerCount" | "names" | "teamCount" | "teams" | "rules" | "turnIntro" | "playing" | "turnResults" | "roundResults" | "final";
type Pack = { id: string; name: string; emoji: string; words: string[]; custom?: boolean };
type Attempt = { id: string; word: string; valid: boolean };

const OBJECTS = [
  "Chaise",
  "Lampe",
  "Ordinateur",
  "Radio",
  "Horloge",
  "Cahier",
  "Crayon",
  "Agrafeuse",
  "Valise",
  "Portefeuille",
  "Cadenas",
  "Casserole",
  "Poêle",
  "Grille-pain",
  "Micro-ondes",
  "Aspirateur",
  "Balai",
  "Éponge",
  "Savon",
  "Miroir",
  "Parapluie",
  "Chemise",
  "Écouteurs",
  "Batterie",
  "Tournevis",
  "Perceuse",
  "Trottinette",
  "Camion",
  "Bateau",
  "Ballon",
  "Raquette",
  "Poupée",
  "Puzzle",
  "Dé",
  "Bougie",
  "Armoire",
  "Ventilateur",
  "Radiateur",
  "Extincteur",
  "Jumelles",
  "Boussole",
  "Canapé",
  "Tabouret",
  "Imprimante",
  "Calculatrice",
  "Trombone",
  "Palette",
  "Thermomètre",
  "Réveil",
  "Ventouse",
  "Entonnoir",
  "Passoire",
  "Fouet",
  "Tire-bouchon",
  "Sifflet",
  "Hamac",
  "Ancre",
  "Parachute",
  "Skateboard",
  "Frisbee",
  "Aimant",
  "Anneau",
  "Aquarium",
  "Arrosoir",
  "Baignoire",
  "Balançoire",
  "Berceau",
  "Brosse à dents",
  "Casquette",
  "Chariot",
  "Coffre",
  "Colle",
  "Corbeille",
  "Couvercle",
  "Écharpe",
  "Flûte",
  "Gourde",
  "Hameçon",
  "Loupe",
  "Médaille",
  "Moulin",
  "Parasol",
  "Sablier",
  "Sonnette",
  "Trépied",
  "Tuyau",
  "Cloche",
  "Enclume",
  "Moustiquaire",
  "Tirelire",
  "Bidon",
  "Seringue",
  "Béquille",
  "Épingle",
  "Ruban",
  "Écrou",
  "Ressort",
  "Paillasson",
  "Éventail",
  "Microphone",
  "Enceinte",
  "Projecteur",
  "Console",
  "Manette",
  "Trépied photo",
  "Clé USB",
  "Multiprise",
  "Casque de moto",
  "Sac à dos",
  "Boîte à outils",
  "Saladier",
  "Mixeur",
  "Cafetière",
  "Boîte aux lettres",
  "Antivol",
  "Gilet",
  "Blouson",
  "Bottes",
  "Gants",
  "Cravate",
  "Tondeuse",
  "Sacoche",
  "Porte-clés",
  "Cintre",
  "Poubelle",
  "Serpillière",
  "Plumeau",
  "Vaporisateur",
  "Ouvre-boîte",
  "Remorque",
  "Gyrophare",
  "Rétroviseur",
  "Quille",
  "Palme",
  "Tuba",
  "Harmonica",
  "Accordéon",
  "Interphone",
  "Badge",
  "Passeport",
  "Calendrier",
  "Balance",
  "Pèse-personne",
  "Glacière",
  "Bâche",
  "Piquet",
  "Élastique",
  "Torchon",
  "Nappe",
  "Sèche-cheveux",
  "Pince à linge",
  "Bouée",
  "Biberon",
  "Trompette",
  "Violon",
];

const DIFFICULT_OBJECTS = [
  "Sextant",
  "Vilebrequin",
  "Fuseau",
  "Kalimba",
  "Ocarina",
  "Alambic",
  "Amphore",
  "Monocle",
  "Périscope",
  "Sarbacane",
  "Catapulte",
  "Girouette",
  "Mousqueton",
  "Gourdin",
  "Carquois",
  "Étrier",
  "Menottes",
  "étau",
  "lanterne",
  "chandelier",
  "baudrier",
  "Mortier",
  "Flacon",
  "Mappemonde",
  "Cocotte",
  "Chausse-pied",
  "Hublot",
  "Balise",
  "Épuisette",
  "Coquetier",
  "Marque-page",
  "Encrier",
  "Canif",
  "Rapporteur",
  "Hachette",
  "Gouge",
  "Ciseau à bois",
  "Sécateur",
  "Xylophone",
  "Haltère",
  "Parpaing",
  "Spatule",
  "Thermos",
];

const CAREERS = [
  "Boulanger",
  "Pâtissier",
  "Cuisinier",
  "Serveur",
  "Boucher",
  "Charcutier",
  "Poissonnier",
  "Fromager",
  "Épicier",
  "Agriculteur",
  "Éleveur",
  "Viticulteur",
  "Jardinier",
  "Fleuriste",
  "Paysagiste",
  "Maçon",
  "Menuisier",
  "Charpentier",
  "Plombier",
  "Électricien",
  "Peintre",
  "Carreleur",
  "Serrurier",
  "Soudeur",
  "Vitrier",
  "Architecte",
  "Géomètre",
  "Mécanicien",
  "Carrossier",
  "Garagiste",
  "Chauffeur",
  "Routier",
  "Livreur",
  "Pilote",
  "Marin",
  "Pêcheur",
  "Capitaine",
  "Médecin",
  "Chirurgien",
  "Infirmier",
  "Pharmacien",
  "Dentiste",
  "Vétérinaire",
  "Kinésithérapeute",
  "Psychologue",
  "Orthophoniste",
  "Ambulancier",
  "Sage-femme",
  "Opticien",
  "Pompier",
  "Policier",
  "Gendarme",
  "Militaire",
  "Avocat",
  "Juge",
  "Notaire",
  "Huissier",
  "Greffier",
  "Professeur",
  "Instituteur",
  "Éducateur",
  "Bibliothécaire",
  "Chercheur",
  "Archéologue",
  "Historien",
  "Traducteur",
  "Interprète",
  "Journaliste",
  "Écrivain",
  "Photographe",
  "Cameraman",
  "Réalisateur",
  "Acteur",
  "Chanteur",
  "Musicien",
  "Danseur",
  "Sculpteur",
  "Graphiste",
  "Informaticien",
  "Technicien",
  "Ingénieur",
  "Électronicien",
  "Comptable",
  "Banquier",
  "Assureur",
  "Secrétaire",
  "Réceptionniste",
  "Vendeur",
  "Caissier",
  "Commercial",
  "Coiffeur",
  "Barbier",
  "Esthéticien",
  "Tatoueur",
  "Couturier",
  "Cordonnier",
  "Horloger",
  "Bijoutier",
  "Facteur",
  "Éboueur",
];

const ANIMALS = [
  "Cheval",
  "Âne",
  "Vache",
  "Taureau",
  "Mouton",
  "Chèvre",
  "Cochon",
  "Lapin",
  "Hamster",
  "Cochon d’Inde",
  "Furet",
  "Souris",
  "Rat",
  "Hérisson",
  "Écureuil",
  "Castor",
  "Marmotte",
  "Lièvre",
  "Renard",
  "Loup",
  "Ours brun",
  "Ours polaire",
  "Panda géant",
  "Panda roux",
  "Lion",
  "Tigre",
  "Léopard",
  "Guépard",
  "Jaguar",
  "Lynx",
  "Puma",
  "Panthère noire",
  "Hyène",
  "Coyote",
  "Éléphant",
  "Rhinocéros",
  "Hippopotame",
  "Girafe",
  "Zèbre",
  "Gazelle",
  "Antilope",
  "Bison",
  "Buffle",
  "Dromadaire",
  "Chameau",
  "Lama",
  "Renne",
  "Cerf",
  "Chevreuil",
  "Élan",
  "Sanglier",
  "Suricate",
  "Blaireau",
  "Loutre",
  "Belette",
  "Raton laveur",
  "Gorille",
  "Chimpanzé",
  "Orang-outan",
  "Babouin",
  "Macaque",
  "Ouistiti",
  "Lémurien",
  "Kangourou",
  "Wallaby",
  "Koala",
  "Chauve-souris",
  "Taupe",
  "Bouquetin",
  "Yak",
  "Autruche",
  "Aigle",
  "Faucon",
  "Vautour",
  "Hibou",
  "Chouette",
  "Corbeau",
  "Pie",
  "Perroquet",
  "Perruche",
  "Toucan",
  "Colibri",
  "Flamant rose",
  "Pélican",
  "Mouette",
  "Goéland",
  "Pingouin",
  "Manchot",
  "Canard",
  "Oie",
  "Cygne",
  "Poule",
  "Coq",
  "Dinde",
  "Paon",
  "Faisan",
  "Pigeon",
  "Tourterelle",
  "Moineau",
  "Rouge-gorge",
  "Mésange",
  "Hirondelle",
  "Merle",
  "Pic-vert",
  "Coucou",
  "Cigogne",
  "Héron",
  "Grue",
  "Martin-pêcheur",
  "Dauphin",
  "Orque",
  "Baleine bleue",
  "Baleine à bosse",
  "Cachalot",
  "Phoque",
  "Otarie",
  "Morse",
  "Requin blanc",
  "Requin-marteau",
  "Requin-baleine",
  "Espadon",
  "Thon",
  "Saumon",
  "Truite",
  "Sardine",
  "Anchois",
  "Maquereau",
  "Morue",
  "Merlu",
  "Bar",
  "Dorade",
  "Brochet",
  "Carpe",
  "Anguille",
  "Poisson-clown",
  "Poulpe",
  "Calmar",
  "Méduse",
  "Étoile de mer",
  "Oursin",
  "Crabe",
  "Homard",
  "Langouste",
  "Crevette",
  "Tortue",
  "Crocodile",
  "Alligator",
  "Caïman",
  "Iguane",
  "Caméléon",
  "Serpent",
  "Cobra",
  "Python",
  "Grenouille",
  "Crapaud",
];

const DIFFICULT_ANIMALS = [
  "Gnou",
  "Alpaga",
  "Paresseux",
  "Fourmilier",
  "Tatou",
  "Mangouste",
  "Capucin",
  "Opossum",
  "Ornithorynque",
  "Porc-épic",
  "Albatros",
  "Béluga",
  "Narval",
  "Marsouin",
  "Silure",
  "Hippocampe",
  "Poisson-globe",
  "Murène",
  "Barracuda",
  "Seiche",
  "Gecko",
  "Salamandre",
  "Axolotl",
  "Raie manta",
  "Chacal",
  "Mante religieuse",
  "Veuve noire",
  "Scolopendre",
  "Concombre de mer",
  "Tyrannosaure",
];

const CHARACTERS = [
  "Leonardo DiCaprio",
  "Tom Hanks",
  "Brad Pitt",
  "Johnny Depp",
  "Will Smith",
  "Morgan Freeman",
  "Jean Dujardin",
  "Omar Sy",
  "Louis de Funès",
  "Jennifer Lawrence",
  "Michael Jackson",
  "Elton John",
  "Bob Marley",
  "Madonna",
  "Beyoncé",
  "Lady Gaga",
  "Taylor Swift",
  "Rihanna",
  "Eminem",
  "Ed Sheeran",
  "Céline Dion",
  "Mylène Farmer",
  "Johnny Hallyday",
  "Jean-Jacques Goldman",
  "Stromae",
  "Soprano",
  "Pablo Picasso",
  "Vincent van Gogh",
  "Léonard de Vinci",
  "Barack Obama",
  "Donald Trump",
  "Emmanuel Macron",
  "Vladimir Poutine",
  "Squeezie",
  "Inoxtag",
  "Tibo InShape",
  "MrBeast",
  "Mickey Mouse",
  "Donald Duck",
  "Bugs Bunny",
  "Bob l’éponge",
  "Homer Simpson",
  "Scooby-Doo",
  "Shrek",
  "Simba",
  "Aladdin",
  "La Reine des neiges",
  "Buzz l’Éclair",
  "Ratatouille",
  "Pikachu",
  "Naruto",
  "Spider-Man",
  "Batman",
  "Superman",
  "Harry Potter",
  "Jackie Chan",
  "Shakira",
  "Kylian Mbappé",
  "Astérix",
  "Jim Carrey",
  "Arnold Schwarzenegger",
  "Jamel Debbouze",
  "Gad Elmaleh",
  "Aya Nakamura",
  "Zinédine Zidane",
  "Lionel Messi",
  "Teddy Riner",
  "Thomas Pesquet",
  "Tintin",
  "Peter Pan",
  "Dark Vador",
  "David Guetta",
  "Lucky Luke",
  "Iron-Man",
  "Max verstappen",
];

const VIDEO_GAMES = [
  "Minecraft",
  "Fortnite",
  "Grand Theft Auto",
  "Mario Kart",
  "Super Mario Bros.",
  "The Legend of Zelda: Breath of the Wild",
  "Call of Duty : Warzone",
  "League of Legends",
  "Counter-Strike",
  "Valorant",
  "Overwatch",
  "World of Warcraft",
  "The Sims",
  "Animal Crossing: New Horizons",
  "Among Us",
  "Fall Guys",
  "Rocket League",
  "Red Dead Redemption",
  "The Last of Us",
  "God of War",
  "Assassin’s Creed II",
  "Far Cry",
  "Resident Evil",
  "Silent Hill",
  "Tomb Raider",
  "Uncharted",
  "Elden Ring",
  "Dark Souls",
  "Cyberpunk 2077",
  "The Witcher",
  "Skyrim",
  "Portal",
  "Half-Life",
  "Pac-Man",
  "Tetris",
  "Sonic the Hedgehog",
  "Street Fighter II",
  "Dead by Daylight",
  "Tekken",
  "Gran Turismo",
];

const BUILT_IN_PACKS: Pack[] = [
  { id: "objects", name: "Pack Squiz'Zup - Objet", emoji: "📦", words: OBJECTS },
  { id: "objects-difficult", name: "Pack Squiz'Zup - Objet (Difficile)", emoji: "🧭", words: DIFFICULT_OBJECTS },
  { id: "careers", name: "Pack Squiz'Zup - Métier", emoji: "👷", words: CAREERS },
  { id: "animals", name: "Pack Squiz'Zup - Animaux", emoji: "🦁", words: ANIMALS },
  { id: "animals-difficult", name: "Pack Squiz'Zup - Animaux (Difficile)", emoji: "🦖", words: DIFFICULT_ANIMALS },
  { id: "characters", name: "Pack Squiz'Zup - Personnage", emoji: "🎭", words: CHARACTERS },
  { id: "video-games", name: "Pack Squiz'Zup - Jeux-Vidéo", emoji: "🎮", words: VIDEO_GAMES }
];
const BUILT_IN_PACK_IDS = BUILT_IN_PACKS.map((pack) => pack.id);

const TEAM_STYLES = [
  { name: "Équipe Azur", color: "#48b8ff", dark: "#087dc2", shellStart: "#249fdf", shellMiddle: "#0877c3", shellBottom: "#07478e", shellGlow: "rgba(104,218,255,.32)", shellAction: "rgba(7,71,142,.97)" },
  { name: "Équipe Corail", color: "#ff6b6b", dark: "#c5304a", shellStart: "#ee5968", shellMiddle: "#bf3049", shellBottom: "#71162f", shellGlow: "rgba(255,172,184,.32)", shellAction: "rgba(113,22,47,.97)" },
  { name: "Équipe Soleil", color: "#ffd84d", dark: "#b88600", shellStart: "#e6a928", shellMiddle: "#bd7811", shellBottom: "#714007", shellGlow: "rgba(255,232,137,.32)", shellAction: "rgba(113,64,7,.97)" },
  { name: "Équipe Menthe", color: "#62df91", dark: "#168a4b", shellStart: "#32b983", shellMiddle: "#16825f", shellBottom: "#09503d", shellGlow: "rgba(155,255,215,.28)", shellAction: "rgba(9,80,61,.97)" }
];

const ROUND_INFO = [
  { verb: "Décrivez !", short: "Faites deviner librement", icon: "💬", rule: "Décrivez les mots sans les prononcer. Les mots de la même famille, ceux qui sonnent pareil, les traductions et l’épellation sont interdits." },
  { verb: "Un seul mot !", short: "Un indice, pas un de plus", icon: "☝️", rule: "Faites deviner chaque mot en ne prononçant qu’un seul mot. Choisissez bien votre indice : vous n’aurez pas de deuxième chance." },
  { verb: "Mimez !", short: "Place au jeu d’acteur", icon: "🎭", rule: "Faites deviner les mots uniquement en les mimant. Aucun mot, son ou bruitage n’est autorisé." }
];

const shuffle = <T,>(items: T[]) => {
  const copy = [...items];
  for (let i = copy.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [copy[i], copy[j]] = [copy[j], copy[i]];
  }
  return copy;
};

const normalizeWord = (word: string) => word
  .normalize("NFKC")
  .trim()
  .toLocaleLowerCase("fr-FR")
  .replace(/[’`´]/g, "'")
  .replace(/[‐‑–—]/g, "-")
  .replace(/\s+/g, " ");

const uniqueWords = (words: string[]) => {
  const seen = new Set<string>();
  return words.filter((word) => {
    const key = normalizeWord(word);
    if (!key || seen.has(key)) return false;
    seen.add(key);
    return true;
  });
};
const teamOptionsFor = (players: number) => {
  if (players <= 5) return [2];
  if (players <= 7) return [2, 3];
  if (players <= 10) return [2, 3, 4];
  if (players <= 15) return [3, 4];
  return [4];
};

const wordCountForPlayers = (players: number) => players + 26;

const toCsvUrl = (value: string) => {
  const trimmed = value.trim();
  const match = trimmed.match(/spreadsheets\/d\/([a-zA-Z0-9-_]+)/);
  if (!match) return trimmed;
  const gid = trimmed.match(/[?#&]gid=(\d+)/)?.[1] ?? "0";
  return `https://docs.google.com/spreadsheets/d/${match[1]}/export?format=csv&gid=${gid}`;
};

const parseCsvWords = (text: string) => text
  .split(/\r?\n/)
  .map((line) => line.split(",")[0].replace(/^\s*"|"\s*$/g, "").replace(/""/g, '"').trim())
  .filter(Boolean);

function IconButton({ label, onClick, children }: { label: string; onClick: () => void; children: React.ReactNode }) {
  return <button className="icon-button" type="button" aria-label={label} onClick={onClick}>{children}</button>;
}

function AppHeader({ title, onBack, detail }: { title: string; onBack?: () => void; detail?: string }) {
  return (
    <header className="app-header">
      {onBack ? <button className="icon-button back-button" type="button" aria-label="Revenir en arrière" onClick={onBack}><img src="/project/SquizZup/out/back.svg" alt="" draggable={false} /></button> : <span className="header-spacer" />}
      <div>{detail && <span className="eyebrow">{detail}</span>}<h1>{title}</h1></div>
      <span className="header-spacer" />
    </header>
  );
}

function PrimaryButton({ children, onClick, disabled = false }: { children: React.ReactNode; onClick: () => void; disabled?: boolean }) {
  return <button className="primary-button" type="button" onClick={onClick} disabled={disabled}><span>{children}</span><b aria-hidden="true"><img src="/project/SquizZup/out/ico-next.svg" alt="" draggable={false} /></b></button>;
}

function BackgroundSpiral() {
  const canvasRef = useRef<HTMLCanvasElement>(null);

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;

    const draw = () => {
      const context = canvas.getContext("2d");
      if (!context) return;

      const bounds = canvas.getBoundingClientRect();
      const width = Math.max(1, bounds.width);
      const height = Math.max(1, bounds.height);
      const pixelRatio = Math.min(window.devicePixelRatio || 1, 2);

      canvas.width = Math.round(width * pixelRatio);
      canvas.height = Math.round(height * pixelRatio);
      context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
      context.clearRect(0, 0, width, height);

      const centerX = width / 2;
      const centerY = height / 2;
      const turnGap = Math.max(44, Math.min(59, width * 0.14));
      const radiusPerRadian = turnGap / (Math.PI * 2);
      const maxRadius = Math.hypot(width, height) + turnGap * 3;
      const maxAngle = maxRadius / radiusPerRadian;
      const angleOffset = -Math.PI / 2 - 0.2;

      context.beginPath();
      for (let angle = 0; angle <= maxAngle; angle += 0.025) {
        const radius = radiusPerRadian * angle;
        const x = centerX + Math.cos(angle + angleOffset) * radius;
        const y = centerY + Math.sin(angle + angleOffset) * radius;

        if (angle === 0) context.moveTo(x, y);
        else context.lineTo(x, y);
      }

      context.strokeStyle = "rgba(255,255,255,.09)";
      context.lineWidth = Math.max(28, Math.min(40, width * 0.085));
      context.lineCap = "round";
      context.lineJoin = "round";
      context.stroke();
    };

    draw();
    window.addEventListener("resize", draw);
    window.visualViewport?.addEventListener("resize", draw);

    return () => {
      window.removeEventListener("resize", draw);
      window.visualViewport?.removeEventListener("resize", draw);
    };
  }, []);

  return <canvas ref={canvasRef} className="spiral-pattern" aria-hidden="true" />;
}

export default function Home() {
  const [screen, setScreen] = useState<Screen>("home");
  const [settingsReturnScreen, setSettingsReturnScreen] = useState<Screen>("home");
  const [confirmCancel, setConfirmCancel] = useState(false);
  const [customPacks, setCustomPacks] = useState<Pack[]>([]);
  const [selectedPackIds, setSelectedPackIds] = useState<string[]>(() => [...BUILT_IN_PACK_IDS]);
  const [showPackEditor, setShowPackEditor] = useState(false);
  const [previewPackId, setPreviewPackId] = useState<string | null>(null);
  const [newPackName, setNewPackName] = useState("");
  const [newPackWords, setNewPackWords] = useState("");
  const [sheetUrl, setSheetUrl] = useState("");
  const [importing, setImporting] = useState(false);
  const [packError, setPackError] = useState("");
  const [playerCount, setPlayerCount] = useState(4);
  const [playerNames, setPlayerNames] = useState<string[]>(Array.from({ length: 4 }, (_, i) => `Joueur ${i + 1}`));
  const [teamCount, setTeamCount] = useState(2);
  const [assignments, setAssignments] = useState<number[]>(Array(4).fill(-1));
  const [selectedPlayer, setSelectedPlayer] = useState<number | null>(0);
  const [round, setRound] = useState(0);
  const [turnIndex, setTurnIndex] = useState(0);
  const [turnDuration, setTurnDuration] = useState(40);
  const [timeLeft, setTimeLeft] = useState(40);
  const [gameStarted, setGameStarted] = useState(false);
  const [gameWords, setGameWords] = useState<string[]>([]);
  const [deck, setDeck] = useState<string[]>([]);
  const [turnQueue, setTurnQueue] = useState<string[]>([]);
  const [attempts, setAttempts] = useState<Attempt[]>([]);
  const [roundScores, setRoundScores] = useState<number[][]>([]);
  const [soundOn, setSoundOn] = useState(true);
  const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
  const timedOutRef = useRef(false);
  const currentWordRef = useRef<string | undefined>(undefined);
  const handledWordRef = useRef<string | null>(null);
  const validationAudioRef = useRef<HTMLAudioElement | null>(null);
  const skipAudioRef = useRef<HTMLAudioElement | null>(null);

  const packs = useMemo(() => [...BUILT_IN_PACKS, ...customPacks], [customPacks]);
  const previewPack = packs.find((pack) => pack.id === previewPackId) ?? null;
  const selectedWords = useMemo(() => uniqueWords(packs.filter((pack) => selectedPackIds.includes(pack.id)).flatMap((pack) => pack.words)), [packs, selectedPackIds]);
  const requiredWordCount = wordCountForPlayers(playerCount);
  const hasEnoughSelectedWords = selectedWords.length >= requiredWordCount;

  useEffect(() => {
    try {
      const saved = localStorage.getItem("squizzup-custom-packs");
      if (saved) {
        const storedPacks = JSON.parse(saved) as Pack[];
        if (Array.isArray(storedPacks)) {
          queueMicrotask(() => setCustomPacks(storedPacks.map((pack) => ({
            ...pack,
            words: uniqueWords(Array.isArray(pack.words) ? pack.words : [])
          }))));
        }
      }
    } catch {}
    if ("serviceWorker" in navigator) navigator.serviceWorker.register("/project/SquizZup/out/sw.js").catch(() => undefined);
  }, []);

  useEffect(() => {
    try { localStorage.setItem("squizzup-custom-packs", JSON.stringify(customPacks)); } catch {}
  }, [customPacks]);

  useEffect(() => {
    if (!previewPackId) return;
    const closePreview = (event: KeyboardEvent) => {
      if (event.key === "Escape") setPreviewPackId(null);
    };
    window.addEventListener("keydown", closePreview);
    return () => window.removeEventListener("keydown", closePreview);
  }, [previewPackId]);

  useEffect(() => {
    if (screen !== "packs") return;
    const titles = Array.from(document.querySelectorAll<HTMLElement>(".pack-card .pack-copy b"));
    const positionCounters = () => {
      titles.forEach((title) => {
        const card = title.closest<HTMLElement>(".pack-card");
        if (!card) return;
        const meta = card.querySelector<HTMLElement>(".pack-meta");
        const titleHeight = title.getBoundingClientRect().height;
        const metaHeight = meta?.getBoundingClientRect().height ?? 27;
        const cardHeight = card.getBoundingClientRect().height;
        const groupTop = Math.max(0, (cardHeight - titleHeight - metaHeight - 3) / 2);
        card.style.setProperty("--pack-title-height", `${titleHeight}px`);
        card.style.setProperty("--pack-group-top", `${groupTop}px`);
      });
    };
    positionCounters();
    if (typeof ResizeObserver === "undefined") {
      window.addEventListener("resize", positionCounters);
      return () => window.removeEventListener("resize", positionCounters);
    }
    const observer = new ResizeObserver(positionCounters);
    titles.forEach((title) => observer.observe(title));
    return () => observer.disconnect();
  }, [packs, screen]);
  const beep = useCallback((frequency = 540, duration = 0.08) => {
    if (!soundOn) return;
    try {
      const AudioContextClass = window.AudioContext || (window as typeof window & { webkitAudioContext: typeof AudioContext }).webkitAudioContext;
      const context = new AudioContextClass();
      const oscillator = context.createOscillator();
      const gain = context.createGain();
      oscillator.frequency.value = frequency;
      gain.gain.setValueAtTime(0.12, context.currentTime);
      gain.gain.exponentialRampToValueAtTime(0.001, context.currentTime + duration);
      oscillator.connect(gain).connect(context.destination);
      oscillator.start();
      oscillator.stop(context.currentTime + duration);
    } catch {}
  }, [soundOn]);

  const playValidationSound = useCallback(() => {
    if (!soundOn) return;
    try {
      const audio = validationAudioRef.current ?? new Audio("/project/SquizZup/out/validation-confirmation-v2.wav");
      validationAudioRef.current = audio;
      audio.currentTime = 0;
      void audio.play().catch(() => undefined);
    } catch {}
  }, [soundOn]);

  const playSkipSound = useCallback(() => {
    if (!soundOn) return;
    try {
      const audio = skipAudioRef.current ?? new Audio("/project/SquizZup/out/skip-error-game.wav");
      skipAudioRef.current = audio;
      audio.currentTime = 0;
      void audio.play().catch(() => undefined);
    } catch {}
  }, [soundOn]);

  const turnOrder = useMemo(() => {
    const order: number[] = [];
    const byTeam = Array.from({ length: teamCount }, (_, team) => playerNames.map((_, index) => index).filter((index) => assignments[index] === team));
    const maxTeamSize = Math.max(0, ...byTeam.map((team) => team.length));
    for (let position = 0; position < maxTeamSize; position++) {
      byTeam.forEach((team) => { if (team[position] !== undefined) order.push(team[position]); });
    }
    return order;
  }, [assignments, playerNames, teamCount]);

  const currentPlayerIndex = turnOrder[turnIndex % Math.max(1, turnOrder.length)] ?? 0;
  const currentTeam = assignments[currentPlayerIndex] >= 0 ? assignments[currentPlayerIndex] : 0;
  const isTeamTurnScreen = screen === "turnIntro" || screen === "playing" || screen === "turnResults";
  const activeTeamTheme = isTeamTurnScreen ? TEAM_STYLES[currentTeam] : null;
  const currentWord = turnQueue[0];
  currentWordRef.current = currentWord;

  const stopTimer = useCallback(() => {
    if (timerRef.current) clearInterval(timerRef.current);
    timerRef.current = null;
  }, []);

  const finishTurn = useCallback((includeCurrent = false) => {
    if (timedOutRef.current) return;
    timedOutRef.current = true;
    stopTimer();
    const wordAtTimeout = currentWordRef.current;
    if (includeCurrent && wordAtTimeout) setAttempts((current) => [...current, { id: crypto.randomUUID(), word: wordAtTimeout, valid: false }]);
    setTurnQueue([]);
    beep(180, 0.35);
    if (navigator.vibrate) navigator.vibrate([120, 70, 120]);
    setScreen("turnResults");
  }, [beep, stopTimer]);

  useEffect(() => {
    if (screen !== "playing") return;
    timedOutRef.current = false;
    stopTimer();
    timerRef.current = setInterval(() => {
      setTimeLeft((value) => {
        if (value <= 1) {
          setTimeout(() => finishTurn(true), 0);
          return 0;
        }
        if (value <= 6) beep(260, 0.04);
        return value - 1;
      });
    }, 1000);
    return stopTimer;
  }, [beep, finishTurn, screen, stopTimer]);

  const resetSetup = () => {
    stopTimer();
    setScreen("home");
    setSettingsReturnScreen("home");
    setConfirmCancel(false);
    setGameStarted(false);
    setTurnDuration(40);
    setTimeLeft(40);
    setSelectedPackIds([...BUILT_IN_PACK_IDS]);
    setRound(0);
    setTurnIndex(0);
    setAttempts([]);
    setGameWords([]);
    setDeck([]);
    setTurnQueue([]);
    setRoundScores([]);
  };

  const openSettings = () => {
    setSettingsReturnScreen(screen);
    setConfirmCancel(false);
    setScreen("settings");
  };

  const closeSettings = () => {
    setConfirmCancel(false);
    setScreen(settingsReturnScreen);
  };

  const changePlayerCount = (count: number) => {
    setPlayerCount(count);
    setPlayerNames((current) => Array.from({ length: count }, (_, i) => current[i] || `Joueur ${i + 1}`));
    setAssignments(Array(count).fill(-1));
    setTeamCount(teamOptionsFor(count)[0]);
  };

  const prepareAssignments = () => {
    setAssignments(Array(playerCount).fill(-1));
    setSelectedPlayer(0);
    setScreen("teams");
  };

  const autoAssign = () => {
    const players = shuffle(playerNames.map((_, index) => index));
    const next = Array(playerCount).fill(-1);
    players.forEach((player, index) => { next[player] = index % teamCount; });
    setAssignments(next);
    setSelectedPlayer(null);
  };

  const assignPlayer = (team: number) => {
    if (selectedPlayer === null) return;
    const nextAssignments = assignments.map((value, index) => index === selectedPlayer ? team : value);
    setAssignments(nextAssignments);
    const next = nextAssignments.findIndex((value) => value < 0);
    setSelectedPlayer(next >= 0 ? next : null);
    beep(620);
  };

  const teamsBalanced = useMemo(() => {
    const counts = Array.from({ length: teamCount }, (_, team) => assignments.filter((value) => value === team).length);
    return assignments.every((team) => team >= 0) && counts.every((count) => count > 0) && Math.max(...counts) - Math.min(...counts) <= 1;
  }, [assignments, teamCount]);

  const startGame = () => {
    if (!hasEnoughSelectedWords) {
      setScreen("playerCount");
      return;
    }
    const nextGameWords = shuffle(selectedWords).slice(0, requiredWordCount);
    setGameStarted(true);
    setTimeLeft(turnDuration);
    setRound(0);
    setTurnIndex(0);
    setGameWords(nextGameWords);
    setDeck(nextGameWords);
    setTurnQueue([]);
    setAttempts([]);
    setRoundScores(Array.from({ length: 3 }, () => Array(teamCount).fill(0)));
    setScreen("rules");
  };

  const startTurn = () => {
    setTurnQueue([...deck]);
    handledWordRef.current = null;
    setAttempts([]);
    setTimeLeft(turnDuration);
    timedOutRef.current = false;
    setScreen("playing");
  };

  const markWord = (valid: boolean) => {
    if (!currentWord || timedOutRef.current || handledWordRef.current === currentWord) return;
    handledWordRef.current = currentWord;
    setAttempts((current) => [...current, { id: crypto.randomUUID(), word: currentWord, valid }]);
    const remainingForTurn = turnQueue.slice(1);
    setTurnQueue(remainingForTurn);
    if (valid) {
      const next = deck.slice(1);
      setDeck(next);
      playValidationSound();
      if (navigator.vibrate) navigator.vibrate(35);
    } else {
      setDeck((current) => current.length > 1 ? [...current.slice(1), current[0]] : current);
      playSkipSound();
    }
    if (remainingForTurn.length === 0) setTimeout(() => finishTurn(false), 60);
  };

  const toggleAttempt = (id: string) => {
    const attempt = attempts.find((item) => item.id === id);
    if (!attempt) return;
    if (attempt.valid) {
      setDeck((current) => [...current, attempt.word]);
    } else {
      setDeck((current) => {
        const index = current.indexOf(attempt.word);
        return index >= 0 ? current.filter((_, wordIndex) => wordIndex !== index) : current;
      });
    }
    setAttempts((current) => current.map((item) => item.id === id ? { ...item, valid: !item.valid } : item));
  };

  const confirmTurn = () => {
    const earned = attempts.filter((attempt) => attempt.valid).length;
    setRoundScores((scores) => scores.map((row, roundIndex) => roundIndex === round ? row.map((score, team) => team === currentTeam ? score + earned : score) : row));
    setAttempts([]);
    if (deck.length === 0) setScreen("roundResults");
    else {
      setTurnIndex((value) => value + 1);
      setScreen("turnIntro");
    }
  };

  const nextRound = () => {
    if (round >= 2) {
      setGameStarted(false);
      setScreen("final");
      return;
    }
    setRound((value) => value + 1);
    setTurnIndex(0);
    setDeck(shuffle(gameWords));
    setTurnQueue([]);
    setAttempts([]);
    setScreen("rules");
  };

  const saveCustomPack = (wordsOverride?: string[]) => {
    const name = newPackName.trim();
    const words = wordsOverride ?? newPackWords.split(/[\n;,]+/).map((word) => word.trim()).filter(Boolean);
    const deduplicatedWords = uniqueWords(words);
    if (!name || deduplicatedWords.length < 3) {
      setPackError("Donnez un nom au pack et ajoutez au moins 3 mots.");
      return;
    }
    const pack: Pack = { id: `custom-${Date.now()}`, name, emoji: "✨", words: deduplicatedWords, custom: true };
    setCustomPacks((current) => [...current, pack]);
    setSelectedPackIds((current) => [...current, pack.id]);
    setNewPackName("");
    setNewPackWords("");
    setSheetUrl("");
    setPackError("");
    setShowPackEditor(false);
  };

  const importSheet = async () => {
    if (!newPackName.trim() || !sheetUrl.trim()) {
      setPackError("Ajoutez un nom et le lien de votre Google Sheet.");
      return;
    }
    setImporting(true);
    setPackError("");
    try {
      const response = await fetch(toCsvUrl(sheetUrl));
      if (!response.ok) throw new Error("fetch");
      const words = parseCsvWords(await response.text());
      if (words.length < 3) throw new Error("empty");
      saveCustomPack(words);
    } catch {
      setPackError("Import impossible. Vérifiez que la feuille est publique et que les mots sont dans la première colonne.");
    } finally {
      setImporting(false);
    }
  };

  const deletePack = (id: string) => {
    setCustomPacks((current) => current.filter((pack) => pack.id !== id));
    setSelectedPackIds((current) => current.filter((packId) => packId !== id));
  };

  const renderHome = () => (
    <section className="screen home-screen">
      <div className="hero-copy">
        <p className="kicker">Le jeu qui met l’ambiance</p>
        <div className="logo">
          <img src="/project/SquizZup/out/SquizZup_LogoV2.svg" alt="Squiz'Zup" draggable={false} />
        </div>
        <p>Trois façons de jouer.<br />Une seule mission : faire deviner.</p>
      </div>
      <div className="mode-list">
        <button className="mode-card active" type="button" onClick={() => setScreen("packs")}><span className="mode-number">01</span><span><b>Squiz’Zup</b><small>Décrivez · Un mot · Mimez</small></span></button>
        <div className="mode-card disabled"><span className="mode-number">02</span><span><b>Squiz’Zik</b><small>Blind test</small></span><em>Bientôt</em></div>
        <div className="mode-card disabled"><span className="mode-number">03</span><span><b>Squiz’Zap</b><small>Culture générale</small></span><em>Bientôt</em></div>
      </div>
      <button className="home-next" type="button" onClick={() => setScreen("packs")}><span>Commencer</span><b aria-hidden="true"><img src="/project/SquizZup/out/ico-next.svg" alt="" draggable={false} /></b></button>
      <p className="home-note">À jouer en équipe · 4 à 16 joueurs</p>
    </section>
  );

  const renderSettings = () => {
    const canCancelGame = settingsReturnScreen !== "home";
    return (
      <section className="screen content-screen settings-screen">
        <AppHeader title="Paramètres" onBack={closeSettings} />
        <p className="section-lead">{"R\u00e9glez le chrono, le son ou quittez la partie en cours."}</p>
        <div className="settings-list">
          <article className={`settings-card timer-settings-card${gameStarted ? " locked" : ""}`}>
            <span className="settings-icon" aria-hidden="true">{"\u23f1"}</span>
            <div className="settings-copy"><b>{"Dur\u00e9e du chrono"}</b><small>{gameStarted ? "Verrouill\u00e9e jusqu\u2019\u00e0 la fin de la partie." : "Choisissez entre 15 et 90 secondes."}</small></div>
            <output className="timer-setting-value" htmlFor="turn-duration">{turnDuration}<small>s</small></output>
            <div className="timer-setting-control">
              <button type="button" aria-label={"R\u00e9duire la dur\u00e9e de 5 secondes"} onClick={() => setTurnDuration((value) => Math.max(15, value - 5))} disabled={gameStarted || turnDuration <= 15}>{"\u2212"}</button>
              <input id="turn-duration" type="range" min={15} max={90} step={1} value={turnDuration} onChange={(event) => setTurnDuration(Number(event.target.value))} disabled={gameStarted} aria-label={"Dur\u00e9e du chrono"} aria-valuetext={`${turnDuration} secondes`} />
              <button type="button" aria-label={"Augmenter la dur\u00e9e de 5 secondes"} onClick={() => setTurnDuration((value) => Math.min(90, value + 5))} disabled={gameStarted || turnDuration >= 90}>+</button>
            </div>
          </article>
          <article className="settings-card">
            <span className="settings-icon" aria-hidden="true">♫</span>
            <div className="settings-copy"><b>Musique et sons</b><small>Active ou coupe tous les sons du jeu.</small></div>
            <button className={`sound-toggle${soundOn ? " active" : ""}`} type="button" role="switch" aria-checked={soundOn} onClick={() => setSoundOn((value) => !value)}>
              <span aria-hidden="true" />
              <em>{soundOn ? "Activés" : "Coupés"}</em>
            </button>
          </article>
          <article className="settings-card settings-danger">
            <div className="settings-danger-title"><span className="settings-icon" aria-hidden="true">×</span><div className="settings-copy"><b>Partie en cours</b><small>Revenez au menu principal et effacez la progression actuelle.</small></div></div>
            {!confirmCancel ? (
              <button className="cancel-game-button" type="button" onClick={() => setConfirmCancel(true)} disabled={!canCancelGame}>Annuler la partie</button>
            ) : (
              <div className="cancel-confirmation" role="alert">
                <p>La progression de cette partie sera perdue.</p>
                <div><button className="confirm-cancel-button" type="button" onClick={resetSetup}>Oui, annuler</button><button className="keep-game-button" type="button" onClick={() => setConfirmCancel(false)}>Garder la partie</button></div>
              </div>
            )}
            {!canCancelGame && <small className="no-active-game">Aucune partie en cours.</small>}
          </article>
        </div>
      </section>
    );
  };

  const renderPacks = () => (
    <section className="screen content-screen packs-screen">
      <AppHeader title="Choisissez vos packs" detail="Étape 1 sur 5" onBack={() => setScreen("home")} />
      <div className="intro-row"><p>Mélangez les thèmes qui vous plaisent.</p><span className="word-total">{selectedWords.length} mots</span></div>
      <div className="pack-grid">
        {packs.map((pack) => {
          const active = selectedPackIds.includes(pack.id);
          return (
            <article className={`pack-card${active ? " selected" : ""}`} key={pack.id}>
              <button type="button" onClick={() => setSelectedPackIds((current) => active ? current.filter((id) => id !== pack.id) : [...current, pack.id])}><span className="pack-emoji">{pack.emoji}</span><span className="pack-copy"><b>{pack.name}</b></span><i>{active ? "✓" : "+"}</i></button>
              <div className="pack-meta"><small>{pack.words.length} mots</small><button className="view-pack-words" type="button" onClick={() => setPreviewPackId(pack.id)} aria-label={`Voir les ${pack.words.length} mots de ${pack.name}`}><span aria-hidden="true">👁</span></button></div>
              {pack.custom && <button className="delete-pack" aria-label={`Supprimer ${pack.name}`} type="button" onClick={() => deletePack(pack.id)}>×</button>}
            </article>
          );
        })}
        <button className="add-pack-card" type="button" onClick={() => setShowPackEditor(true)}><span>＋</span><b>Créer un pack</b><small>Saisie libre ou Google Sheets</small></button>
      </div>
      <div className="sticky-action"><PrimaryButton onClick={() => setScreen("playerCount")} disabled={selectedWords.length < 3}>Continuer avec {selectedWords.length} mots</PrimaryButton></div>
      {showPackEditor && (
        <div className="modal-backdrop" role="presentation" onMouseDown={() => setShowPackEditor(false)}>
          <div className="bottom-sheet" role="dialog" aria-modal="true" aria-labelledby="pack-title" onMouseDown={(event) => event.stopPropagation()}>
            <div className="sheet-handle" />
            <div className="sheet-title"><div><span className="eyebrow">Pack personnalisé</span><h2 id="pack-title">Ajoutez vos mots</h2></div><IconButton label="Fermer" onClick={() => setShowPackEditor(false)}>×</IconButton></div>
            <label>Nom du pack<input value={newPackName} onChange={(event) => setNewPackName(event.target.value)} placeholder="Ex. Années 90" /></label>
            <label>Mots ou expressions<textarea value={newPackWords} onChange={(event) => setNewPackWords(event.target.value)} placeholder={"Un mot par ligne\nGame Boy\nLes Spice Girls\nTitanic"} /></label>
            <button className="secondary-button" type="button" onClick={() => saveCustomPack()}>Ajouter ce pack</button>
            <div className="or"><span>ou importer</span></div>
            <label>Lien Google Sheets<input inputMode="url" value={sheetUrl} onChange={(event) => setSheetUrl(event.target.value)} placeholder="https://docs.google.com/spreadsheets/..." /></label>
            {packError && <p className="form-error">{packError}</p>}
            <button className="sheet-button" type="button" onClick={importSheet} disabled={importing}>{importing ? "Import en cours…" : "Importer la première colonne"}</button>
          </div>
        </div>
      )}
      {previewPack && (
        <div className="modal-backdrop" role="presentation" onMouseDown={() => setPreviewPackId(null)}>
          <div className="bottom-sheet word-list-sheet" role="dialog" aria-modal="true" aria-labelledby="word-list-title" onMouseDown={(event) => event.stopPropagation()}>
            <div className="sheet-handle" />
            <div className="sheet-title word-list-title">
              <span className="pack-emoji" aria-hidden="true">{previewPack.emoji}</span>
              <div><span className="eyebrow">{previewPack.words.length} mots</span><h2 id="word-list-title">{previewPack.name}</h2></div>
              <IconButton label="Fermer la liste" onClick={() => setPreviewPackId(null)}>×</IconButton>
            </div>
            <ol className="pack-word-list">
              {previewPack.words.map((word, index) => <li key={`${word}-${index}`}>{word}</li>)}
            </ol>
          </div>
        </div>
      )}
    </section>
  );

  const renderPlayerCount = () => (
    <section className="screen centered-screen">
      <AppHeader title="Combien jouez-vous ?" detail="Étape 2 sur 5" onBack={() => setScreen("packs")} />
      {!hasEnoughSelectedWords && <p className="form-error" role="alert">Il manque {requiredWordCount - selectedWords.length} mot{requiredWordCount - selectedWords.length > 1 ? "s" : ""}. Revenez aux packs pour en ajouter.</p>}
      <div className="count-stage">
        <button type="button" aria-label="Un joueur de moins" onClick={() => changePlayerCount(Math.max(4, playerCount - 1))} disabled={playerCount <= 4}>−</button>
        <div className="big-count"><strong>{playerCount}</strong><span>joueurs</span></div>
        <button type="button" aria-label="Un joueur de plus" onClick={() => changePlayerCount(Math.min(16, playerCount + 1))} disabled={playerCount >= 16}>＋</button>
      </div>
      <p className="helper-text">Minimum 4 · Maximum 16 · <b>{requiredWordCount} mots pour la partie</b></p>
      <div className="dots-scale" aria-hidden="true">{Array.from({ length: 13 }, (_, i) => <i className={i + 4 === playerCount ? "active" : ""} key={i} />)}</div>
      <div className="bottom-action"><PrimaryButton onClick={() => setScreen("names")} disabled={!hasEnoughSelectedWords}>Donner les prénoms</PrimaryButton></div>
    </section>
  );

  const renderNames = () => (
    <section className="screen content-screen">
      <AppHeader title="Qui joue ?" detail="Étape 3 sur 5" onBack={() => setScreen("playerCount")} />
      <p className="section-lead">Entrez les prénoms. Ils serviront à annoncer chaque tour.</p>
      <div className="names-list">
        {playerNames.map((name, index) => <label className="name-field" key={index}><span>{String(index + 1).padStart(2, "0")}</span><input value={name} maxLength={18} aria-label={`Nom du joueur ${index + 1}`} onFocus={(event) => event.currentTarget.select()} onChange={(event) => setPlayerNames((current) => current.map((value, nameIndex) => nameIndex === index ? event.target.value : value))} /></label>)}
      </div>
      <div className="sticky-action"><PrimaryButton onClick={() => setScreen("teamCount")} disabled={playerNames.some((name) => !name.trim())}>Créer les équipes</PrimaryButton></div>
    </section>
  );

  const renderTeamCount = () => (
    <section className="screen centered-screen team-count-screen">
      <AppHeader title="Combien d’équipes ?" detail="Étape 4 sur 5" onBack={() => setScreen("names")} />
      <div className="team-count-grid">
        {teamOptionsFor(playerCount).map((count) => <button className={teamCount === count ? "selected" : ""} type="button" key={count} onClick={() => setTeamCount(count)}><strong>{count}</strong><span>équipes</span><small>≈ {Math.floor(playerCount / count)} joueurs / équipe</small></button>)}
      </div>
      <p className="helper-text">Les choix proposés gardent des équipes équilibrées.</p>
      <div className="bottom-action"><PrimaryButton onClick={prepareAssignments}>Répartir les joueurs</PrimaryButton></div>
    </section>
  );

  const renderTeams = () => (
    <section className="screen content-screen teams-screen">
      <AppHeader title="Composez les équipes" detail="Étape 5 sur 5" onBack={() => setScreen("teamCount")} />
      <div className="team-toolbar"><p>{selectedPlayer === null ? "Touchez un prénom pour le déplacer." : `Où va ${playerNames[selectedPlayer]} ?`}</p><button type="button" onClick={autoAssign}>↻ Mélanger</button></div>
      <div className="unassigned-row">
        {playerNames.map((name, index) => assignments[index] < 0 && <button className={selectedPlayer === index ? "active" : ""} type="button" key={index} onClick={() => setSelectedPlayer(index)}>{name}</button>)}
        {!assignments.some((team) => team < 0) && <span>Tout le monde a une équipe ✓</span>}
      </div>
      <div className="teams-grid">
        {Array.from({ length: teamCount }, (_, team) => (
          <article className="team-panel" style={{ "--team-color": TEAM_STYLES[team].color, "--team-dark": TEAM_STYLES[team].dark } as React.CSSProperties} key={team}>
            <button className="team-panel-title" type="button" onClick={() => assignPlayer(team)}><span>{team + 1}</span><b>{TEAM_STYLES[team].name}</b><i>＋</i></button>
            <div className="team-members">
              {playerNames.map((name, index) => assignments[index] === team && <button className={selectedPlayer === index ? "active" : ""} type="button" key={index} onClick={() => setSelectedPlayer(index)}>{name}<span>↗</span></button>)}
              {!assignments.includes(team) && <small>Choisissez un joueur puis touchez ici</small>}
            </div>
          </article>
        ))}
      </div>
      {!teamsBalanced && assignments.every((team) => team >= 0) && <p className="balance-warning">Rééquilibrez les équipes pour avoir au maximum un joueur d’écart.</p>}
      <div className="sticky-action"><PrimaryButton onClick={startGame} disabled={!teamsBalanced || !hasEnoughSelectedWords}>Voir les règles</PrimaryButton></div>
    </section>
  );

  const renderRules = () => (
    <section className="screen rules-screen">
      <AppHeader title={`Manche ${round + 1}`} onBack={round === 0 ? () => setScreen("teams") : undefined} />
      <div className="round-progress" aria-label={`Manche ${round + 1} sur 3`}><i className={round >= 0 ? "done" : ""} /><i className={round >= 1 ? "done" : ""} /><i className={round >= 2 ? "done" : ""} /></div>
      <div className="rule-card"><span className="rule-icon">{ROUND_INFO[round].icon}</span><p className="kicker">Règle de la manche {round + 1}</p><h2>{ROUND_INFO[round].verb}</h2><p>{ROUND_INFO[round].rule}</p><div className="rule-tip"><b>Le bon réflexe</b><span>Validez avec ✓ ou passez avec ×. Les mots passés reviendront plus tard.</span></div></div>
      <div className="bottom-action"><PrimaryButton onClick={() => setScreen("turnIntro")}>C’est compris</PrimaryButton></div>
    </section>
  );

  const renderTurnIntro = () => (
    <section className="screen turn-intro-screen" style={{ "--team-color": TEAM_STYLES[currentTeam].color, "--team-dark": TEAM_STYLES[currentTeam].dark } as React.CSSProperties}>
      <div className="round-chip">Manche {round + 1} · {ROUND_INFO[round].short}</div>
      <div className="turn-card"><div className="team-orbit"><span>{currentTeam + 1}</span></div><p>Au tour de</p><h2>{playerNames[currentPlayerIndex]}</h2><div className="team-line"><i />{TEAM_STYLES[currentTeam].name}</div><small>Passez le téléphone à {playerNames[currentPlayerIndex]}.</small></div>
      <div className="turn-stats"><span><b>{deck.length}</b> mots restants</span><span><b>{turnDuration}</b> secondes</span></div>
      <div className="bottom-action"><PrimaryButton onClick={startTurn}>Lancer le chrono</PrimaryButton></div>
    </section>
  );

  const renderPlaying = () => (
    <section className={`screen play-screen${timeLeft <= 5 ? " hurry" : ""}`} style={{ "--team-color": TEAM_STYLES[currentTeam].color } as React.CSSProperties}>
      <div className="play-top"><div className="mini-team"><i />{TEAM_STYLES[currentTeam].name}</div></div>
      <div className="timer" aria-live="polite"><strong>{timeLeft}</strong><svg viewBox="0 0 120 120" aria-hidden="true"><circle cx="60" cy="60" r="54" /><circle className="progress" cx="60" cy="60" r="54" pathLength="100" style={{ strokeDashoffset: 100 - (timeLeft / turnDuration) * 100 }} /></svg></div>
      <p className="remaining"><span>{deck.length}</span> mot{deck.length > 1 ? "s" : ""} restant{deck.length > 1 ? "s" : ""}</p>
      <div className="word-card"><span>À faire deviner</span><h2>{currentWord}</h2></div>
      <div className="game-actions"><button className="skip-button" type="button" aria-label="Passer" onClick={() => markWord(false)}><img src="/project/SquizZup/out/annulation.svg" alt="" draggable={false} /></button><button className="valid-button" type="button" aria-label="Trouvé !" onClick={() => markWord(true)}><img src="/project/SquizZup/out/validation.svg" alt="" draggable={false} /></button></div>
    </section>
  );

  const renderTurnResults = () => {
    const found = attempts.filter((attempt) => attempt.valid).length;
    return (
      <section className="screen content-screen results-screen">
        <AppHeader title="Résultat du tour" detail={`${playerNames[currentPlayerIndex]} · ${TEAM_STYLES[currentTeam].name}`} />
        <div className="score-hero"><span>＋{found}</span><div><b>mot{found > 1 ? "s" : ""} trouvé{found > 1 ? "s" : ""}</b><small>Vérifiez avant de continuer</small></div></div>
        <div className="attempt-list">{attempts.length === 0 && <div className="empty-result">Aucun mot joué pendant ce tour.</div>}{attempts.map((attempt) => <button className={attempt.valid ? "valid" : "missed"} type="button" key={attempt.id} onClick={() => toggleAttempt(attempt.id)}><span>{attempt.valid ? "✓" : "×"}</span><b>{attempt.word}</b><small>Modifier</small></button>)}</div>
        <p className="tap-hint">Touchez un mot pour corriger son résultat.</p>
        <div className="sticky-action"><PrimaryButton onClick={confirmTurn}>{deck.length === 0 ? "Voir le classement" : "Joueur suivant"}</PrimaryButton></div>
      </section>
    );
  };

  const renderRoundResults = () => {
    const scores = roundScores[round] ?? Array(teamCount).fill(0);
    const ranked = scores.map((score, team) => ({ score, team })).sort((a, b) => b.score - a.score);
    return (
      <section className="screen content-screen leaderboard-screen">
        <AppHeader title={`Manche ${round + 1} terminée`} detail={ROUND_INFO[round].verb} />
        <div className="celebration">✦</div><p className="section-lead center">Tous les mots ont été trouvés. Voici le score de cette manche.</p>
        <div className="leaderboard">{ranked.map(({ team, score }, index) => <div className="leader-row" style={{ "--team-color": TEAM_STYLES[team].color } as React.CSSProperties} key={team}><span>{index + 1}</span><i /><b>{TEAM_STYLES[team].name}</b><strong>{score}<small> pts</small></strong></div>)}</div>
        <div className="bottom-action"><PrimaryButton onClick={nextRound}>{round < 2 ? `Passer à la manche ${round + 2}` : "Voir le résultat final"}</PrimaryButton></div>
      </section>
    );
  };

  const renderFinal = () => {
    const totals = Array.from({ length: teamCount }, (_, team) => roundScores.reduce((sum, scores) => sum + (scores[team] ?? 0), 0));
    const ranked = totals.map((score, team) => ({ score, team })).sort((a, b) => b.score - a.score);
    return (
      <section className="screen final-screen">
        <div className="confetti" aria-hidden="true">✦ · ★ · ✦</div><p className="kicker">Partie terminée</p><h1>Bravo !</h1>
        <div className="winner-card" style={{ "--team-color": TEAM_STYLES[ranked[0].team].color, "--team-dark": TEAM_STYLES[ranked[0].team].dark } as React.CSSProperties}><span>🏆</span><p>La victoire revient à</p><h2>{TEAM_STYLES[ranked[0].team].name}</h2><strong>{ranked[0].score} points</strong></div>
        <div className="final-scores">{ranked.map(({ team, score }, index) => <div key={team}><span>{index + 1}</span><i style={{ background: TEAM_STYLES[team].color }} /><b>{TEAM_STYLES[team].name}</b><strong>{score}</strong></div>)}</div>
        <div className="round-breakdown">
          <table>
            <caption>Détail des scores par manche</caption>
            <thead>
              <tr>
                <th scope="col">Équipe</th>
                {[0, 1, 2].map((roundIndex) => <th scope="col" aria-label={`Manche ${roundIndex + 1}`} key={roundIndex}>M{roundIndex + 1}</th>)}
                <th scope="col">Total</th>
              </tr>
            </thead>
            <tbody>
              {ranked.map(({ team, score }) => (
                <tr style={{ "--team-color": TEAM_STYLES[team].color } as React.CSSProperties} key={team}>
                  <th scope="row"><i />{TEAM_STYLES[team].name}</th>
                  {[0, 1, 2].map((roundIndex) => <td key={roundIndex}><span>{roundScores[roundIndex]?.[team] ?? 0}</span></td>)}
                  <td className="round-total">{score}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
        <button className="primary-button" type="button" onClick={resetSetup}><span>Rejouer</span><b aria-hidden="true"><img src="/project/SquizZup/out/ico-reload.svg" alt="" draggable={false} /></b></button>
        <button className="text-button" type="button" onClick={() => { resetSetup(); setScreen("packs"); }}>Changer les packs</button>
      </section>
    );
  };

  const screens: Record<Screen, () => React.ReactNode> = {
    home: renderHome, settings: renderSettings, packs: renderPacks, playerCount: renderPlayerCount, names: renderNames,
    teamCount: renderTeamCount, teams: renderTeams, rules: renderRules, turnIntro: renderTurnIntro,
    playing: renderPlaying, turnResults: renderTurnResults, roundResults: renderRoundResults, final: renderFinal
  };

  return (
    <main className="app-shell" style={activeTeamTheme ? {
      "--shell-start": activeTeamTheme.shellStart,
      "--shell-middle": activeTeamTheme.shellMiddle,
      "--shell-bottom": activeTeamTheme.shellBottom,
      "--shell-glow": activeTeamTheme.shellGlow,
      "--shell-action": activeTeamTheme.shellAction,
      "--team-color": activeTeamTheme.color,
      "--team-dark": activeTeamTheme.dark
    } as React.CSSProperties : undefined}>
      <BackgroundSpiral />
      <div className="ambient ambient-one" />
      <div className="ambient ambient-two" />
      {screen !== "home" && screen !== "settings" && <button className="icon-button settings-shortcut" type="button" aria-label="Ouvrir les paramètres" onClick={openSettings}><img src="/project/SquizZup/out/icone_engrenage_relief.svg" alt="" draggable={false} /></button>}
      {screens[screen]()}
    </main>
  );
}
