/* ============================================================
   FWIIP — moteur de jeu jouable (prototype)
   Modes : vs Bots · Pass & Play
   ============================================================ */
const { useReducer, useRef, useEffect } = React;

// value -> representative card image
const VIMG = {1:'c1',2:'c2',3:'c3',4:'c20',5:'c5',6:'c30',7:'c7','X':'c16'};
const imgOf = (v)=>`assets/cards/${VIMG[v]}.png`;
const BOT_NAMES = ['Lina','Marco','Zoé','Tariq','Nina','Hugo'];
const AVATARS = ['#E5231F','#F0892C','#F4A833','#EA4A26','#BE1813','#1A1411'];

let _uid = 0;
// Composition réelle du jeu : 42 cartes jouables (6 par joueur jusqu'à 7 joueurs)
// 35 cartes numérotées + 7 jokers X
const DECK_COMPO = {1:4, 2:4, 3:4, 4:5, 5:8, 6:5, 7:5};
function makeDeck(){
  const d=[];
  for(const v in DECK_COMPO){ for(let k=0;k<DECK_COMPO[v];k++) d.push({id:_uid++, value:Number(v)}); }
  for(let k=0;k<7;k++) d.push({id:_uid++, value:'X'});
  // shuffle
  for(let i=d.length-1;i>0;i--){const j=Math.floor(Math.random()*(i+1)); [d[i],d[j]]=[d[j],d[i]];}
  return d;
}

function freshGame(mode, nPlayers){
  const deck = makeDeck();
  const players = [];
  // 42 cartes : 6 par joueur jusqu'à 7 joueurs (sans pioche)
  const dealN = Math.min(6, Math.floor(deck.length / Math.max(2, nPlayers)));
  for(let i=0;i<nPlayers;i++){
    const isBot = mode==='bots' && i!==0;
    players.push({
      id:i,
      name: mode==='bots' ? (i===0?'Vous':BOT_NAMES[i-1]) : `Joueur ${i+1}`,
      isBot, alive:true, hand: deck.splice(0,dealN).sort((a,b)=>val(a)-val(b)),
      color: AVATARS[i%AVATARS.length],
    });
  }
  return {
    mode, players, V:0, dir:1, turn:0, lastPlacer:null, rally:0, bestRally:0,
    pile:[], discard:[], phase:'play', winner:null, selected:[], xPanel:false, sparks:[], shuttle:null,
    curtain: mode==='passplay', revealReady:false,
    log:[{t:`Nouvelle manche · ${nPlayers} joueurs. ${players[0].name} commence.`}],
    flash:null,
  };
}
const val = (c)=> c.value==='X' ? 99 : c.value;

// ---- WebAudio SFX : petits sons "sportifs" synthétisés (pas d'asset externe) ----
const Sound = {
  ctx:null, on: (localStorage.getItem('fwiip_sound')!=='off'),
  ensure(){ if(!this.ctx){ try{ this.ctx = new (window.AudioContext||window.webkitAudioContext)(); }catch(e){} } if(this.ctx&&this.ctx.state==='suspended') this.ctx.resume(); return this.ctx; },
  toggle(){ this.on=!this.on; localStorage.setItem('fwiip_sound', this.on?'on':'off'); if(this.on) this.ensure(); return this.on; },
  // percussion "pock" du volant frappé
  pock(freq=520, gain=.5){
    if(!this.on) return; const ctx=this.ensure(); if(!ctx) return;
    const t=ctx.currentTime;
    const o=ctx.createOscillator(), g=ctx.createGain();
    o.type='triangle'; o.frequency.setValueAtTime(freq,t); o.frequency.exponentialRampToValueAtTime(freq*0.4,t+0.09);
    g.gain.setValueAtTime(gain,t); g.gain.exponentialRampToValueAtTime(0.0001,t+0.13);
    o.connect(g).connect(ctx.destination); o.start(t); o.stop(t+0.14);
    // petit bruit de corde
    const nb=ctx.createBufferSource(), buf=ctx.createBuffer(1,1200,ctx.sampleRate); const d=buf.getChannelData(0);
    for(let i=0;i<d.length;i++) d[i]=(Math.random()*2-1)*Math.pow(1-i/d.length,3);
    nb.buffer=buf; const ng=ctx.createGain(); ng.gain.setValueAtTime(gain*0.4,t); ng.gain.exponentialRampToValueAtTime(0.0001,t+0.08);
    nb.connect(ng).connect(ctx.destination); nb.start(t);
  },
  smash(){ this.pock(760,.6); setTimeout(()=>this.pock(420,.35),40); },
  reset(){ this.pock(300,.5); },
  reverse(){ if(!this.on) return; const ctx=this.ensure(); if(!ctx) return; const t=ctx.currentTime;
    const o=ctx.createOscillator(), g=ctx.createGain(); o.type='sawtooth';
    o.frequency.setValueAtTime(300,t); o.frequency.linearRampToValueAtTime(620,t+0.18);
    g.gain.setValueAtTime(.3,t); g.gain.exponentialRampToValueAtTime(0.0001,t+0.2);
    o.connect(g).connect(ctx.destination); o.start(t); o.stop(t+0.2); },
  fanfare(){ if(!this.on) return; const ctx=this.ensure(); if(!ctx) return; [0,4,7,12].forEach((s,i)=>{ const t=ctx.currentTime+i*0.11;
    const o=ctx.createOscillator(), g=ctx.createGain(); o.type='triangle'; o.frequency.setValueAtTime(330*Math.pow(2,s/12),t);
    g.gain.setValueAtTime(.0001,t); g.gain.linearRampToValueAtTime(.35,t+0.02); g.gain.exponentialRampToValueAtTime(0.0001,t+0.4);
    o.connect(g).connect(ctx.destination); o.start(t); o.stop(t+0.42); }); },
  out(){ if(!this.on) return; const ctx=this.ensure(); if(!ctx) return; const t=ctx.currentTime;
    const o=ctx.createOscillator(), g=ctx.createGain(); o.type='sawtooth';
    o.frequency.setValueAtTime(380,t); o.frequency.exponentialRampToValueAtTime(90,t+0.4);
    g.gain.setValueAtTime(.3,t); g.gain.exponentialRampToValueAtTime(0.0001,t+0.42);
    o.connect(g).connect(ctx.destination); o.start(t); o.stop(t+0.42); },
};

// ---- legality helpers (for a given hand vs current value V) ----
function singlesBeating(hand, V){ return hand.filter(c=>c.value!=='X' && c.value>V); }
function equalCards(hand, V){ return V>0 ? hand.filter(c=>c.value!=='X' && c.value===V) : []; }
function hasReset(hand){ return hand.some(c=>c.value===1); }
function hasX(hand){ return hand.some(c=>c.value==='X'); }
function fusions(hand, V){
  const nums = hand.filter(c=>c.value!=='X');
  const res=[];
  for(let i=0;i<nums.length;i++) for(let j=i+1;j<nums.length;j++){
    const s = nums[i].value+nums[j].value;
    if(s>V) res.push({cards:[nums[i],nums[j]], sum:s});
  }
  return res.sort((a,b)=>a.sum-b.sum);
}
function canAct(hand, V){
  if(V===0) return hand.length>0;
  return singlesBeating(hand,V).length || equalCards(hand,V).length || hasReset(hand) || fusions(hand,V).length || hasX(hand);
}

// =====================================================================
// Vitesse de réflexion des IA (ms avant qu'un bot joue son coup)
const SPEED_MS = { lent:2000, normal:1050, rapide:480 };
const SPEED_LABEL = { lent:'Lent', normal:'Normal', rapide:'Rapide' };
function readSpeed(){ try{ return localStorage.getItem('fwiip_speed')||'normal'; }catch(e){ return 'normal'; } }
function botDelay(){ return SPEED_MS[readSpeed()] || SPEED_MS.normal; }

function Game(){
  const G = useRef(null);
  const [,force] = useReducer(x=>x+1,0);
  const timer = useRef(null);
  const scores = useRef({});
  if(G.current===null){
    let savedMode='bots', savedN=3, savedSpeed='normal';
    try{ savedMode=localStorage.getItem('fwiip_mode')||'bots'; savedN=parseInt(localStorage.getItem('fwiip_nplayers'),10)||3; savedSpeed=localStorage.getItem('fwiip_speed')||'normal'; }catch(e){}
    G.current={phase:'setup', mode:savedMode, nPlayers:savedN, botSpeed:savedSpeed};
  }
  const g = G.current;
  const commit = ()=>force();

  function log(t){ const g=G.current; g.log.unshift({t}); if(g.log.length>30) g.log.pop(); }
  function flash(t){ const g=G.current; g.flash=t; commit(); setTimeout(()=>{ if(G.current.flash===t){G.current.flash=null; commit();} }, 1100); }
  function shake(){ const g=G.current; g.shake=true; commit(); setTimeout(()=>{ if(G.current){G.current.shake=false; commit();} }, 340); }

  // ---- spark burst : éclats projetés depuis le point d'impact ----
  function burst(kind='smash'){
    const g=G.current; if(!g.sparks) g.sparks=[];
    const n = kind==='reset' ? 10 : (kind==='big'?20:14);
    const palette = kind==='reset' ? ['#FF4A3D','#fff','#F0892C'] : ['#FFD23F','#FF7A1A','#FF4A3D','#fff'];
    const id = ++_uid;
    const batch = Array.from({length:n}).map((_,i)=>{
      const ang = (Math.PI*2*i)/n + (Math.random()-.5)*0.5;
      const dist = (kind==='big'?120:88) + Math.random()*60;
      return { id:id*100+i,
        dx: Math.cos(ang)*dist, dy: Math.sin(ang)*dist,
        sz: 5+Math.random()*7, col: palette[i%palette.length],
        rot: (Math.random()*180-90)|0 };
    });
    g.sparks = g.sparks.concat(batch);
    commit();
    setTimeout(()=>{ if(G.current){ const ids=new Set(batch.map(b=>b.id)); G.current.sparks=(G.current.sparks||[]).filter(s=>!ids.has(s.id)); commit(); } }, 620);
  }

  function alivePlayers(){ return G.current.players.filter(p=>p.alive); }
  function nextAliveIdx(from, dir){
    const g=G.current;
    let i=from; const n=g.players.length;
    for(let k=0;k<n;k++){ i=(i+dir+n)%n; if(g.players[i].alive) return i; }
    return from;
  }

  function startGame(){
    scores.current = {};
    Sound.ensure();
    G.current = freshGame(g.mode, g.nPlayers);
    G.current.botSpeed = readSpeed();
    try{ G.current.tuto = !localStorage.getItem('fwiip_tuto_done'); }catch(e){ G.current.tuto=false; }
    commit();
    scheduleNext();
  }
  function newRound(){
    const m=g.mode, n=g.players.length;
    G.current = freshGame(m, n);
    G.current.botSpeed = readSpeed();
    commit();
    scheduleNext();
  }

  // ---- apply a placement (single/fusion/reset/equal) ----
  function place(player, cards, newV, {reverse=false, reset=false, label}={}){
    const g=G.current;
    cards.forEach(c=>{ player.hand = player.hand.filter(h=>h.id!==c.id); });
    // la pile précédente part dans la défausse centrale (qui reste au centre et s'accumule)
    if(g.pile && g.pile.length){
      if(!g.discard) g.discard=[];
      g.pile.forEach(pc=>{
        g.discard.push({ id:pc.id, value:pc.value,
          dx:(Math.random()*34-17)|0, dy:(Math.random()*26-13)|0,
          rot:(Math.random()*26-13)|0 });
      });
      // pile précédente compacte derrière la carte active
      if(g.discard.length>7) g.discard = g.discard.slice(-7);
    }
    g.pile = cards.map(c=>({...c}));
    g.V = newV;
    g.lastPlacer = player.id;
    // volant : traverse le court depuis le côté du joueur
    const sid = ++_uid;
    g.shuttle = { id:sid, side:(g.mode==='bots' && player.id===0)?'bot':'top' };
    setTimeout(()=>{ if(G.current && G.current.shuttle && G.current.shuttle.id===sid){ G.current.shuttle=null; commit(); } }, 460);
    if(reverse){ g.dir*=-1; }
    // rally streak : grimpe à chaque échange tenu, retombe sur un RESET
    if(reset){ g.rally=0; }
    else { g.rally=(g.rally||0)+1; if(g.rally>(g.bestRally||0)) g.bestRally=g.rally; }
    // SFX + feedback
    if(reset){ Sound.reset(); burst('reset'); }
    else if(reverse){ Sound.reverse(); }
    else if(cards.length===2){ Sound.smash(); shake(); burst('big'); }
    else if(newV>=6){ Sound.smash(); shake(); burst('big'); }
    else { Sound.pock(440+newV*40, .5); if(newV>=4) burst('smash'); }
    log(`${player.name} ${label}.`);
  }

  function checkWinner(){
    const g=G.current;
    const alive = alivePlayers();
    if(alive.length<=1){
      g.phase='roundover';
      g.winner = alive[0]||null;
      if(alive[0]) scores.current[alive[0].id]=(scores.current[alive[0].id]||0)+1;
      g.curtain=false;
      Sound.fanfare();
      log(alive[0] ? `🏆 ${alive[0].name} remporte la manche !` : 'Manche terminée.');
      commit();
      return true;
    }
    return false;
  }

  function eliminate(player, reason){
    const g=G.current;
    player.alive=false;
    Sound.out();
    // option 4+ : give strongest card to lastPlacer
    if(g.players.length>=4 && g.lastPlacer!=null && player.hand.length){
      const lp = g.players[g.lastPlacer];
      if(lp && lp.alive){
        const strongest = player.hand.slice().sort((a,b)=>val(b)-val(a))[0];
        lp.hand.push(strongest); lp.hand.sort((a,b)=>val(a)-val(b));
        log(`${player.name} est éliminé·e et donne son ${strongest.value==='X'?'X':strongest.value} à ${lp.name}.`);
      } else log(`${player.name} est éliminé·e (${reason}).`);
    } else {
      log(`${player.name} est éliminé·e (${reason}).`);
    }
    player.hand=[];
  }

  // ---- advance to next player & drive bot/auto turns ----
  function advance(){
    const g=G.current;
    g.selected=[]; g.xPanel=false;
    if(checkWinner()) return;
    g.turn = nextAliveIdx(g.turn, g.dir);
    scheduleNext();
  }

  function scheduleNext(){
    const g=G.current;
    commit();
    const p = g.players[g.turn];
    if(!p || g.phase!=='play') return;
    if(p.isBot){
      clearTimeout(timer.current);
      timer.current = setTimeout(()=>botTurn(p), botDelay());
    } else {
      // human turn
      if(g.mode==='passplay'){ g.curtain=true; commit(); return; }
      // can the human act?
      if(!canAct(p.hand, g.V)){
        flash(`${p.name} ne peut pas jouer !`);
        eliminate(p, 'ne peut pas suivre');
        clearTimeout(timer.current);
        timer.current=setTimeout(()=>advance(), 1200);
      }
      commit();
    }
  }

  // ---- BOT ----
  function botTurn(p){
    const g=G.current;
    if(g.phase!=='play' || !p.alive) return;
    const V=g.V;
    if(V===0){
      // lead with lowest numeric >=2 if possible, else lowest
      const nums=p.hand.filter(c=>c.value!=='X').sort((a,b)=>a.value-b.value);
      const pick = nums.find(c=>c.value>=2) || nums[0];
      if(pick){ place(p,[pick],pick.value,{label:`ouvre avec un ${pick.value}`}); flash(`${p.name} ouvre · ${pick.value}`); return advance(); }
      // only X
      const x=p.hand.find(c=>c.value==='X'); if(x){ place(p,[x],2,{label:'ouvre avec un X (valeur 2)'}); return advance(); }
      eliminate(p,'main vide'); return advance();
    }
    const singles=singlesBeating(p.hand,V).sort((a,b)=>a.value-b.value);
    const eq=equalCards(p.hand,V);
    const fus=fusions(p.hand,V);
    if(singles.length){ const c=singles[0]; place(p,[c],c.value,{label:`pose un ${c.value}`}); flash(`${p.name} · ${c.value}`); return advance(); }
    if(eq.length){ const c=eq[0]; place(p,[c],V,{reverse:true,label:`pose un ${V} — choc d'égaux, sens inversé !`}); flash(`↺ Inversion !`); return advance(); }
    if(fus.length){ const f=fus[0]; place(p,f.cards,f.sum,{label:`fusionne ${f.cards[0].value}+${f.cards[1].value}=${f.sum}`}); flash(`${p.name} fusionne · ${f.sum}`); return advance(); }
    if(hasReset(p.hand)){ const c=p.hand.find(h=>h.value===1); place(p,[c],1,{reset:true,label:'joue un RESET (valeur → 1)'}); flash(`${p.name} · RESET`); return advance(); }
    if(hasX(p.hand)){ const x=p.hand.find(h=>h.value==='X'); p.hand=p.hand.filter(h=>h.id!==x.id); log(`${p.name} passe avec un X.`); flash(`${p.name} passe (X)`); return advance(); }
    eliminate(p,'ne peut pas suivre'); flash(`${p.name} éliminé·e`); return advance();
  }

  // ---- HUMAN actions ----
  function toggleSelect(card){
    const g=G.current;
    const p=g.players[g.turn]; if(p.isBot) return;
    const idx=g.selected.indexOf(card.id);
    if(idx>=0) g.selected.splice(idx,1);
    else { if(g.selected.length>=2) g.selected.shift(); g.selected.push(card.id); }
    g.xPanel=false; g.xMode=null; commit();
  }

  function humanPlay(){
    const g=G.current;
    const p=g.players[g.turn]; if(p.isBot) return;
    const sel = g.selected.map(id=>p.hand.find(h=>h.id===id)).filter(Boolean);
    if(!sel.length) return;
    const V=g.V;
    if(sel.length===1){
      const c=sel[0];
      if(c.value==='X'){ g.xMode='single'; g.xPanel=true; commit(); return; }
      if(c.value===1){ place(p,[c],1,{reset:true,label:'joue un RESET (valeur → 1)'}); flash('RESET · valeur → 1'); return advance(); }
      if(c.value>V){ place(p,[c],c.value,{label:`pose un ${c.value}`}); return advance(); }
      if(V>0 && c.value===V){ place(p,[c],V,{reverse:true,label:`pose un ${V} — choc d'égaux !`}); flash('↺ Sens inversé !'); return advance(); }
      flash('Trop faible — il faut dépasser '+V); return;
    }
    if(sel.length===2){
      const xs=sel.filter(c=>c.value==='X');
      if(xs.length===0){
        const sum=sel[0].value+sel[1].value;
        if(sum>V){ place(p,sel,sum,{label:`fusionne ${sel[0].value}+${sel[1].value}=${sum}`}); flash('Fusion · '+sum); return advance(); }
        flash(`Fusion ${sum} ≤ ${V}, trop faible`); return;
      }
      if(xs.length===1){ g.xMode='fusion'; g.xPanel=true; commit(); return; }
      flash('Fusion : un seul X à la fois'); return;
    }
  }
  function playX(value){ // value = 'pass' or 2..7
    const g=G.current;
    const p=g.players[g.turn];
    if(g.xMode==='fusion'){
      const sel=g.selected.map(id=>p.hand.find(h=>h.id===id)).filter(Boolean);
      const x=sel.find(c=>c.value==='X'); const other=sel.find(c=>c.value!=='X');
      if(!x||!other){ g.xPanel=false; g.xMode=null; commit(); return; }
      const sum=other.value+value;
      if(sum>g.V){ place(p,[x,other],sum,{label:`fusionne X(${value})+${other.value}=${sum}`}); flash('Fusion · '+sum); g.xPanel=false; g.xMode=null; return advance(); }
      flash(`Fusion ${sum} ≤ ${g.V}, trop faible`); return;
    }
    const x=p.hand.find(h=>h.id===g.selected[0]) || p.hand.find(h=>h.value==='X');
    if(!x) return;
    if(value==='pass'){ p.hand=p.hand.filter(h=>h.id!==x.id); log(`${p.name} passe avec un X.`); flash('Vous passez (X)'); g.xPanel=false; g.xMode=null; return advance(); }
    place(p,[x],value,{label:`joue un X comme ${value}`}); flash('X joué · '+value); g.xPanel=false; g.xMode=null; return advance();
  }
  function humanPass(){ // when stuck offers explicit elimination
    const g=G.current;
    const p=g.players[g.turn];
    if(hasX(p.hand)){ playX('pass'); return; }
    eliminate(p,'abandon'); advance();
  }
  function revealHand(){ const g=G.current; g.curtain=false; 
    const p=g.players[g.turn];
    if(!canAct(p.hand,g.V)){ flash(`${p.name} ne peut pas jouer !`); eliminate(p,'ne peut pas suivre'); setTimeout(()=>advance(),1200); }
    commit();
  }

  useEffect(()=>()=>clearTimeout(timer.current),[]);

  // ---- keyboard controls ----
  useEffect(()=>{
    function onKey(e){
      const g=G.current;
      if(!g||g.phase!=='play'||g.curtain) return;
      const me=g.players[g.turn]; if(!me||me.isBot) return;
      // X panel value pick with number keys
      if(g.xPanel){
        if(e.key>='2'&&e.key<='7'){ playX(parseInt(e.key,10)); e.preventDefault(); }
        else if(e.key==='Escape'){ g.xPanel=false; g.xMode=null; commit(); }
        else if((e.key==='p'||e.key==='P')&&g.xMode!=='fusion'){ playX('pass'); }
        return;
      }
      if(e.key>='1'&&e.key<='9'){
        const idx=parseInt(e.key,10)-1;
        if(me.hand[idx]){ toggleSelect(me.hand[idx]); e.preventDefault(); }
      } else if(e.key==='Enter'){ if(g.selected.length){ humanPlay(); e.preventDefault(); } }
      else if(e.key==='Escape'){ if(g.selected.length){ g.selected=[]; commit(); } }
      else if(e.key==='p'||e.key==='P'){ humanPass(); }
    }
    window.addEventListener('keydown', onKey);
    return ()=>window.removeEventListener('keydown', onKey);
  },[]);

  // =================== RENDER ===================
  if(g.phase==='setup') return <Setup g={g} commit={commit} start={startGame} />;

  const me = g.players[g.turn];
  const isHumanTurn = me && !me.isBot && g.phase==='play' && !g.curtain;
  const human = g.mode==='bots' ? g.players[0] : me;

  return (
    <div className={'game'+(g.shake?' shake':'')}>
      <div className="spotlight" aria-hidden="true"></div>
      <Arena />
      <Opponents g={g} scores={scores.current} />
      <Court g={g} />
      <div className="hand-zone">
        {g.phase==='play' && (
          <HandBar g={g} human={human} isHumanTurn={isHumanTurn}
            toggleSelect={toggleSelect} humanPlay={humanPlay} humanPass={humanPass} playX={playX} />
        )}
      </div>
      {g.flash && <div className="flash"><span>{g.flash}</span></div>}
      {g.tuto && g.phase==='play' && !g.curtain && me && !me.isBot && (
        <Coach onClose={()=>{ G.current.tuto=false; try{localStorage.setItem('fwiip_tuto_done','1');}catch(e){} commit(); }} />
      )}
      {g.curtain && <Curtain g={g} reveal={revealHand} />}
      {g.phase==='roundover' && <RoundOver g={g} scores={scores.current} again={newRound} home={()=>{G.current={phase:'setup',mode:g.mode,nPlayers:g.players.length}; commit();}} />}
      <Log g={g} />
      <CheatSheet />
      <SoundToggle />
      {g.phase!=='setup' && g.mode==='bots' && <SpeedToggle />}
    </div>
  );
}

// ---------- sub components ----------
function Arena(){
  return (
    <div className="arena" aria-hidden="true">
      <div className="ar-half top"></div>
      <div className="ar-half bot"></div>
      <div className="ln tram left"></div>
      <div className="ln tram right"></div>
      <div className="ln serv top"></div>
      <div className="ln serv bot"></div>
      <div className="ln mid"></div>
      <div className="net"><span></span></div>
    </div>
  );
}

function Setup({g, commit, start}){
  return (
    <div className="setup">
      <div className="setup-card">
        <div className="heatbar"></div>
        <div className="sc-body">
        <span className="kicker" style={{color:'var(--red)'}}>Salle de jeu</span>
        <h2 className="display" style={{fontSize:'2.8rem', lineHeight:1, margin:'8px 0 14px'}}>Lance ta partie</h2>
        <p style={{color:'var(--muted)', fontWeight:500, marginTop:0}}>Prototype jouable — le vrai multijoueur en ligne arrive plus tard.</p>

        <div className="opt-row">
          <div className="opt-label">Mode</div>
          <div className="seg">
            <button className={g.mode==='bots'?'on':''} onClick={()=>{g.mode='bots'; try{localStorage.setItem('fwiip_mode','bots');}catch(e){} commit();}}>Contre des bots</button>
            <button className={g.mode==='passplay'?'on':''} onClick={()=>{g.mode='passplay'; try{localStorage.setItem('fwiip_mode','passplay');}catch(e){} commit();}}>Pass &amp; Play</button>
          </div>
        </div>
        <div className="opt-row">
          <div className="opt-label">Joueurs</div>
          <div className="seg">
            {[2,3,4,5,6,7].map(n=>(
              <button key={n} className={g.nPlayers===n?'on':''} onClick={()=>{g.nPlayers=n; try{localStorage.setItem('fwiip_nplayers',String(n));}catch(e){} commit();}}>{n}</button>
            ))}
          </div>
        </div>
        <p className="hint">{g.mode==='bots' ? 'Vous + '+(g.nPlayers-1)+' bot(s).' : g.nPlayers+' joueurs sur le même appareil, chacun son tour.'}</p>

        {g.mode==='bots' && <div className="opt-row">
          <div className="opt-label">Vitesse des bots</div>
          <div className="seg">
            {['lent','normal','rapide'].map(s=>(
              <button key={s} className={g.botSpeed===s?'on':''} onClick={()=>{g.botSpeed=s; try{localStorage.setItem('fwiip_speed',s);}catch(e){} commit();}}>
                {s==='lent'?'🐢 ':s==='rapide'?'⚡ ':'🎾 '}{SPEED_LABEL[s]}
              </button>
            ))}
          </div>
        </div>}
        {g.mode==='bots' && <p className="hint">{g.botSpeed==='lent'?'Idéal pour apprendre — les bots prennent leur temps.':g.botSpeed==='rapide'?'Partie nerveuse pour joueurs aguerris.':'Rythme équilibré.'}</p>}

        <button className="btn btn-red" style={{marginTop:18, width:'100%', justifyContent:'center'}} onClick={start}>Commencer la partie <span className="arr">→</span></button>
        <a href="regles.html" className="setup-link">Revoir les règles d'abord →</a>
        </div>
      </div>
    </div>
  );
}

function Opponents({g, scores}){
  const opps = g.mode==='bots' ? g.players.slice(1) : g.players.filter((p,i)=>i!==g.turn);
  return (
    <div className="opponents">
      {opps.map(p=>{
        const n=Math.min(p.hand.length,7);
        return (
        <div key={p.id} className={'opp'+(g.turn===p.id?' active':'')+(p.alive?'':' dead')}>
          <div className="opp-fan" aria-hidden="true">
            {p.alive
              ? Array.from({length:n}).map((_,i)=>(
                  <div key={i} className="cback" style={{transform:`translateX(${(i-(n-1)/2)*9}px) rotate(${(i-(n-1)/2)*5}deg)`}}></div>))
              : <div className="cback out"></div>}
          </div>
          <div className="opp-row">
            <div className="opp-av" style={{background:p.color}}>{p.name[0]}{g.turn===p.id && <span className="serve-dot"></span>}</div>
            <div className="opp-meta">
              <div className="opp-name">{p.name}</div>
              <div className="opp-cards">{p.alive? `${p.hand.length} carte${p.hand.length>1?'s':''}` : 'éliminé·e'}{(scores&&scores[p.id])?` · ${scores[p.id]} ★`:''}</div>
            </div>
          </div>
        </div>);
      })}
    </div>
  );
}

function Court({g}){
  const fromTop = g.mode==='bots' ? (g.lastPlacer!==0) : true;
  const leader = g.lastPlacer!=null ? g.players[g.lastPlacer] : null;
  const heat = (g.V>=1 && g.V<=7) ? `var(--h${g.V})` : 'var(--red)';
  return (
    <div className="court">
      <div className="court-info">
        <div className={'emblem'+(g.pile.length?' lead':'')+(g.V>=6?' blaze':'')} style={{'--heat':heat}} key={'emb'+(g.pile[0]?.id||'x')}>
          <div className="e-lab">À battre</div>
          <div className={'e-val'+(g.V?'':' empty')}>{g.V||'—'}</div>
          <div className="e-dir">{g.dir===1?'↻ sens horaire':'↺ sens inversé'}</div>
        </div>
        {leader && g.pile.length>0
          ? <span className="lead-chip"><i style={{background:leader.color}}></i>{leader.name} mène l'échange</span>
          : <span className="lead-chip">Échange ouvert</span>}
        {g.rally>=2 && <div className={'rally-chip'+(g.rally>=5?' hot':'')} key={'r'+g.rally}>
          <span className="rc-flame">{g.rally>=5?'🔥':'⚡'}</span>
          <span className="rc-lab">Échange</span>
          <span className="rc-num">×{g.rally}</span>
        </div>}
      </div>
      <div className="pile">
        {g.pile.length===0 && (!g.discard||g.discard.length===0) && <div className="pile-empty">À toi d'ouvrir<br/>l'échange</div>}
        {(g.discard||[]).map((d,i)=>(
          <div className="discard-card" key={'d'+d.id} style={{
            transform:`translate(-50%,-50%) translate(${d.dx}px,${d.dy}px) rotate(${d.rot}deg)`,
            zIndex:i+1, opacity:Math.max(.22, 0.5-(g.discard.length-1-i)*0.05)}}>
            <img src={imgOf(d.value)} alt="" aria-hidden="true"/>
          </div>
        ))}
        {g.pile.length>0 && <div className="impact" key={'imp'+g.pile[0].id} style={{zIndex:0}}></div>}
        {g.shuttle && <span key={'sh'+g.shuttle.id} className={'shuttle '+g.shuttle.side} aria-hidden="true">🏸</span>}
        {(g.sparks||[]).map(s=>(
          <span key={s.id} className="spark" style={{
            '--dx':s.dx+'px','--dy':s.dy+'px','--rot':s.rot+'deg',
            width:s.sz, height:s.sz, background:s.col}}></span>
        ))}
        {g.pile.map((c,i)=>(
          <div className="pile-slot" key={c.id} style={{transform:`translate(-50%,-50%) translateX(${(i-(g.pile.length-1)/2)*54}px) rotate(${(i-(g.pile.length-1)/2)*6}deg)`, zIndex:30+i}}>
            <img className={'pile-card '+(fromTop?'from-top':'from-bot')} src={imgOf(c.value)} alt={c.value}/>
          </div>
        ))}
      </div>
    </div>
  );
}

function HandBar({g, human, isHumanTurn, toggleSelect, humanPlay, humanPass, playX}){
  if(!human) return null;
  const V=g.V;
  const stuck = isHumanTurn && !canAct(human.hand, V);
  const selCards = g.selected.map(id=>human.hand.find(h=>h.id===id)).filter(Boolean);
  const otherNum = selCards.find(c=>c.value!=='X');
  const base = otherNum ? otherNum.value : 0;
  // ---- coach : coups légaux disponibles maintenant ----
  const beats = isHumanTurn ? [...new Set(singlesBeating(human.hand,V).map(c=>c.value))].sort((a,b)=>a-b) : [];
  const eqs   = isHumanTurn ? equalCards(human.hand,V) : [];
  const fus   = isHumanTurn ? fusions(human.hand,V) : [];
  const canReset = isHumanTurn && hasReset(human.hand);
  const canX  = isHumanTurn && hasX(human.hand);
  const hints = [];
  if(isHumanTurn){
    if(V===0){ hints.push({k:'open', t:'Ouvre l\'échange — pose la carte de ton choix'}); }
    else {
      if(beats.length) hints.push({k:'beat', t:`Dépasser · ${beats.join(' ')}`});
      if(eqs.length) hints.push({k:'eq', t:`Choc d'égaux · pose un ${V} (inverse le sens)`});
      if(fus.length) hints.push({k:'fus', t:`Fusion · ${fus[0].cards[0].value}+${fus[0].cards[1].value}=${fus[0].sum}`});
      if(canReset) hints.push({k:'reset', t:'Reset · le 1 ramène la valeur à 1'});
      if(canX) hints.push({k:'x', t:'X · joker (2→7) ou passe en sécurité'});
    }
  }
  return (
    <div className={'handbar'+(isHumanTurn?' your-turn':'')}>
      <div className="hb-status">
        {isHumanTurn
          ? <span className="hb-turn">À vous de jouer — {V>0?`dépassez ${V}`:'ouvrez l\'échange'}</span>
          : <span className="hb-wait">En attente — {g.players[g.turn]?.name} joue…</span>}
      </div>
      {isHumanTurn && hints.length>0 && (
        <div className="coach-hints">
          {hints.map(h=><span key={h.k} className={'ch ch-'+h.k}>{h.t}</span>)}
        </div>
      )}
      <div className="hand">
        {human.hand.map(c=>{
          const sel=g.selected.includes(c.id);
          const playable = isHumanTurn && (V===0 || c.value==='X' || c.value===1 || c.value>V || c.value===V);
          const reason = !isHumanTurn ? '' :
            (V===0 ? 'Carte jouable' :
             c.value==='X' ? 'Joker — toujours jouable' :
             c.value===1 ? 'Reset — toujours jouable' :
             c.value>V ? `Dépasse ${V}` :
             c.value===V ? `Égal à ${V} — choc d'égaux` :
             `Trop faible — il faut dépasser ${V}`);
          return (
            <button key={c.id} className={'pcard'+(sel?' sel':'')+(playable?' can':' dim')}
              disabled={!isHumanTurn} title={reason} onClick={()=>toggleSelect(c)}>
              <img src={imgOf(c.value)} alt={c.value}/>
            </button>
          );
        })}
      </div>
      {isHumanTurn && (
        <div className="hb-actions">
          {!g.xPanel && <>
            <button className="btn btn-red" disabled={!g.selected.length} onClick={humanPlay}>
              {g.selected.length===2?'Fusionner & poser':'Poser'}
            </button>
            <button className="btn btn-ghost" onClick={humanPass}>{hasX(human.hand)?'Passer (X)':'Je ne peux pas'}</button>
          </>}
          {g.xPanel && g.xMode==='fusion' && <div className="xpanel">
            <span className="xp-lab">X + {base} — valeur du X :</span>
            {[2,3,4,5,6,7].map(v=>(
              <button key={v} className="xp-btn" disabled={!((base+v)>V)} onClick={()=>playX(v)}>{v}</button>
            ))}
          </div>}
          {g.xPanel && g.xMode!=='fusion' && <div className="xpanel">
            <span className="xp-lab">Jouer le X comme :</span>
            <button onClick={()=>playX('pass')} className="xp-btn pass">Passer</button>
            {[2,3,4,5,6,7].map(v=>(
              <button key={v} className="xp-btn" disabled={!(v>V)} onClick={()=>playX(v)}>{v}</button>
            ))}
          </div>}
        </div>
      )}
      {stuck && !g.xPanel && <div className="stuck-note">Aucun coup possible… vous allez être éliminé·e.</div>}
    </div>
  );
}

function Curtain({g, reveal}){
  const p=g.players[g.turn];
  return (
    <div className="curtain">
      <div className="curtain-in">
        <div className="opp-av big" style={{background:p.color}}>{p.name[0]}</div>
        <h2 className="display" style={{fontSize:'2.6rem', margin:'14px 0 4px'}}>{p.name}</h2>
        <p style={{color:'rgba(255,255,255,.7)', fontWeight:500, margin:0}}>Passez l'appareil. Personne d'autre ne doit voir votre main.</p>
        <button className="btn btn-red" style={{marginTop:22}} onClick={reveal}>Voir ma main <span className="arr">→</span></button>
      </div>
    </div>
  );
}

function RoundOver({g, scores, again, home}){
  const w=g.winner;
  const board = g.players.map(p=>({name:p.name, w:(scores&&scores[p.id])||0})).filter(x=>x.w>0).sort((a,b)=>b.w-a.w);
  return (
    <div className="curtain">
      {w && <Confetti/>}
      <div className="curtain-in">
        <div className="trophy">🏆</div>
        <span className="kicker" style={{color:'var(--red-soft)'}}>Manche terminée</span>
        <h2 className="display" style={{fontSize:'3.4rem', margin:'8px 0 0'}}>{w?`${w.name} gagne !`:'Égalité'}</h2>
        <p style={{color:'rgba(255,255,255,.7)', fontWeight:500}}>Dernier·e à tenir le court. Beau smash.</p>
        {g.bestRally>=3 && <p className="best-rally">Meilleur échange de la manche : <b>×{g.bestRally}</b></p>}
        {board.length>0 && <div className="scoreboard">{board.map((b,i)=>(
          <span key={i} className="sb"><b>{b.w} ★</b> {b.name}</span>
        ))}</div>}
        <div style={{display:'flex', gap:12, marginTop:18, flexWrap:'wrap', justifyContent:'center'}}>
          <button className="btn btn-red" onClick={again}>Nouvelle manche <span className="arr">↻</span></button>
          <button className="btn btn-ghost" style={{borderColor:'#fff', color:'#fff'}} onClick={home}>Changer de mode</button>
        </div>
      </div>
    </div>
  );
}

function Coach({onClose}){
  return (
    <div className="coach-overlay" onClick={onClose}>
      <div className="coach-bubble" onClick={e=>e.stopPropagation()}>
        <span className="kicker" style={{color:'var(--red-soft)'}}>Comment jouer</span>
        <h3>Sois plus fort que le centre</h3>
        <ul>
          <li><b>Pose une carte plus forte</b> que la valeur affichée au centre, puis appuie sur <b>Poser</b>.</li>
          <li>Sous ta main, le <b>coach</b> liste tes coups possibles à chaque tour (dépasser, fusion, reset, X…).</li>
          <li>Les cartes <b>en relief</b> sont jouables ; les <b>grisées</b> sont trop faibles.</li>
        </ul>
        <button className="btn btn-red" onClick={onClose} style={{justifyContent:'center'}}>C'est parti <span className="arr">→</span></button>
      </div>
    </div>
  );
}

function Confetti(){
  const COLS=['#E5231F','#FF4A3D','#F0892C','#F4A833','#F6D44E','#fff'];
  const pieces = React.useMemo(()=>Array.from({length:70}).map((_,i)=>({
    id:i, left:Math.random()*100, delay:Math.random()*0.7, dur:1.8+Math.random()*1.8,
    col:COLS[i%COLS.length], sz:6+Math.random()*8, rot:(Math.random()*360)|0,
    drift:(Math.random()*120-60)|0, round:Math.random()>0.6,
  })),[]);
  return (
    <div className="confetti" aria-hidden="true">
      {pieces.map(p=>(
        <span key={p.id} style={{
          left:p.left+'%', width:p.sz, height:p.sz*(p.round?1:1.6),
          background:p.col, borderRadius:p.round?'50%':'1px',
          '--drift':p.drift+'px','--rot':p.rot+'deg',
          animationDelay:p.delay+'s', animationDuration:p.dur+'s'}}></span>
      ))}
    </div>
  );
}

function Log({g}){  return (
    <div className="gamelog">
      {g.log.slice(0,6).map((l,i)=><div key={i} className="lg" style={{opacity:1-i*0.14}}>{l.t}</div>)}
    </div>
  );
}

const MOVES = [
  {g:'↑', t:'Supérieur', d:'Pose une carte de valeur strictement plus haute.'},
  {g:'=', t:"Choc d'égaux", d:'Même valeur : passe le tour ET inverse le sens.'},
  {g:'+', t:'Fusion', d:'Combine 2 cartes ; leur somme doit dépasser la valeur.'},
  {g:'1', t:'Reset', d:'La carte 1 redescend le jeu à 1. Imparable.'},
  {g:'X', t:'Joker', d:'Vaut 2 à 7 au choix, ou passe ton tour.'},
];
function CheatSheet(){
  const [open,setOpen]=React.useState(false);
  return (
    <React.Fragment>
      <button className="cheat-fab" onClick={()=>setOpen(o=>!o)} title="Rappel des coups">
        {open?'✕':'?'}
      </button>
      {open && (
        <div className="cheat-panel">
          <div className="cheat-head">Rappel des coups</div>
          {MOVES.map((m,i)=>(
            <div className="cheat-row" key={i}>
              <span className="cheat-g">{m.g}</span>
              <div><b>{m.t}</b><span>{m.d}</span></div>
            </div>
          ))}
          <div className="cheat-keys">⌨ <b>1–9</b> sélectionner · <b>Entrée</b> poser · <b>P</b> passer · <b>Échap</b> annuler</div>
        </div>
      )}
    </React.Fragment>
  );
}

function SoundToggle(){
  const [on,setOn]=React.useState(Sound.on);
  return (
    <button className={'sound-fab'+(on?'':' off')} onClick={()=>setOn(Sound.toggle())} title={on?'Couper le son':'Activer le son'}>
      {on?'🔊':'🔇'}
    </button>
  );
}

function SpeedToggle(){
  const order=['lent','normal','rapide'];
  const [sp,setSp]=React.useState(readSpeed());
  const icon = sp==='lent'?'🐢':sp==='rapide'?'⚡':'🎾';
  function cycle(){
    const next = order[(order.indexOf(sp)+1)%order.length];
    try{ localStorage.setItem('fwiip_speed', next); }catch(e){}
    setSp(next);
  }
  return (
    <button className="speed-fab" onClick={cycle} title={'Vitesse des bots : '+SPEED_LABEL[sp]+' — cliquez pour changer'}>
      <span>{icon}</span><span className="sf-lab">{SPEED_LABEL[sp]}</span>
    </button>
  );
}

ReactDOM.createRoot(document.getElementById('game')).render(<Game/>);
