// Name the XI — guess all 22 starters from an iconic match

// ────── Name matching (fuzzy, handles accents + abbreviations) ──────
function normalizeNameForMatch(s) {
  if (!s) return '';
  // Decompose accents, strip them, lowercase, remove punctuation
  return s.normalize('NFKD')
    .replace(/[\u0300-\u036f]/g, '')
    .toLowerCase()
    .replace(/[.'"`]/g, '')
    .replace(/-/g, ' ')
    .replace(/\s+/g, ' ')
    .trim();
}

// Does user's typed string `guess` match the stored player name?
// Accepts: last name alone, "firstname lastname", "f. lastname", full long name
function isNameMatch(guess, storedName) {
  const g = normalizeNameForMatch(guess);
  const s = normalizeNameForMatch(storedName);
  if (!g || !s) return false;
  if (g === s) return true;

  const gParts = g.split(' ');
  const sParts = s.split(' ');

  // Exact last-name match (when guess is a single word)
  if (gParts.length === 1) {
    return sParts[sParts.length - 1] === g || sParts.includes(g);
  }

  // Multi-word guess: check last names match, and first names are compatible
  const gLast = gParts[gParts.length - 1];
  const sLast = sParts[sParts.length - 1];
  if (gLast !== sLast) return false;

  const gFirst = gParts[0];
  const sFirst = sParts[0];
  // Exact first name match
  if (gFirst === sFirst) return true;
  // First name is abbreviated on either side (e.g. "J" vs "Jamie")
  if (gFirst.length <= 2 && sFirst.startsWith(gFirst)) return true;
  if (sFirst.length <= 2 && gFirst.startsWith(sFirst)) return true;
  // User typed full name and stored is "J. Vardy"-style → sFirst might be "j"
  return false;
}

// ────── Landing ──────
function XiLanding({ onStart, onBack, tokens, cost }) {
  const T = window.T;
  const canAfford = tokens === null || tokens >= cost; // null = still loading, allow optimistically
  return (
    <Content>
      <div style={{display:'flex', alignItems:'center', justifyContent:'space-between', padding:'12px 0 6px'}}>
        <button onClick={onBack} style={{background:'transparent',border:'none',color:T.text,fontSize:22,cursor:'pointer'}}>‹</button>
        <TokenPill amount={tokens ?? '—'}/>
      </div>

      <div style={{textAlign:'center', marginBottom:18, marginTop:8}}>
        <div style={{fontFamily:'Bakbak One, sans-serif', fontSize:28, letterSpacing:'0.04em', color:T.text, display:'inline-flex', alignItems:'center', gap:8}}>
          NAME THE XI <span style={{fontSize:22}}>📋</span>
        </div>
        <div style={{fontSize:14, color:T.textSec, marginTop:4, fontWeight:500}}>Iconic Matches</div>
      </div>

      {/* Vertical pitch preview with red/blue dots */}
      <div style={{
        position:'relative', margin:'0 auto',
        width:290, height:380, borderRadius:14,
        background:'linear-gradient(180deg, #2D8C4E, #1F6638)',
        border:'1px solid rgba(255,255,255,0.1)', overflow:'hidden',
      }}>
        <div style={{position:'absolute', inset:8, border:'2px solid rgba(255,255,255,0.35)', borderRadius:4}}/>
        <div style={{position:'absolute', top:'50%', left:8, right:8, height:2, background:'rgba(255,255,255,0.35)'}}/>
        <div style={{position:'absolute', left:'50%', top:'50%', width:60, height:60, transform:'translate(-50%,-50%)', border:'2px solid rgba(255,255,255,0.35)', borderRadius:'50%'}}/>
        <div style={{position:'absolute', left:'30%', right:'30%', top:8, height:40, border:'2px solid rgba(255,255,255,0.35)', borderTop:'none'}}/>
        <div style={{position:'absolute', left:'30%', right:'30%', bottom:8, height:40, border:'2px solid rgba(255,255,255,0.35)', borderBottom:'none'}}/>
        {[{x:50,y:8},{x:20,y:18},{x:40,y:22},{x:60,y:22},{x:80,y:18},{x:32,y:33},{x:50,y:36},{x:68,y:33},{x:25,y:44},{x:50,y:46},{x:75,y:44}].map((d,i)=>(
          <div key={'r'+i} style={{position:'absolute', left:`${d.x}%`, top:`${d.y}%`, width:16, height:16, borderRadius:'50%', background:'#E87A7A', transform:'translate(-50%,-50%)', border:'2px solid rgba(255,255,255,0.2)'}}/>
        ))}
        {[{x:50,y:92},{x:20,y:82},{x:40,y:78},{x:60,y:78},{x:80,y:82},{x:32,y:67},{x:50,y:64},{x:68,y:67},{x:25,y:56},{x:50,y:54},{x:75,y:56}].map((d,i)=>(
          <div key={'b'+i} style={{position:'absolute', left:`${d.x}%`, top:`${d.y}%`, width:16, height:16, borderRadius:'50%', background:'#5B9FE8', transform:'translate(-50%,-50%)', border:'2px solid rgba(255,255,255,0.2)'}}/>
        ))}
      </div>

      <button onClick={canAfford ? onStart : undefined} style={{
        display:'block', margin:'22px auto 0', padding:'16px 40px', borderRadius:14,
        background: canAfford ? 'linear-gradient(180deg, #F5C842, #C99A10)' : T.card,
        color: canAfford ? '#1A1200' : T.textMute,
        border: canAfford ? 'none' : `1px solid ${T.border}`,
        cursor: canAfford ? 'pointer' : 'not-allowed',
        fontFamily:'Bakbak One, sans-serif', fontSize:20, letterSpacing:'0.06em',
        boxShadow: canAfford ? '0 10px 24px rgba(232,184,32,0.35)' : 'none',
        display:'flex', alignItems:'center', gap:8,
      }}>
        {canAfford ? `START GAME` : 'NOT ENOUGH TOKENS'}
        {canAfford && (
          <span style={{fontSize:13, fontFamily:'DM Sans', color:'rgba(26,18,0,0.6)', letterSpacing:'0.05em'}}>
            ⬡{cost}
          </span>
        )}
      </button>

      <div style={{display:'flex', justifyContent:'center', gap:28, marginTop:28}}>
        {[{icon:'⚽', title:'2006-2026', sub:'Random Match'},{icon:'👥', title:'22 Players', sub:'Pick the Players'},{icon:'⏱', title:'Beat the Timer', sub:'2 Minutes'}].map((c,i)=>(
          <div key={i} style={{textAlign:'center'}}>
            <div style={{width:42, height:42, borderRadius:'50%', background:'rgba(232,184,32,0.12)', border:`1px solid ${T.gold600}`, display:'flex', alignItems:'center', justifyContent:'center', margin:'0 auto 8px', color:T.gold400, fontSize:16}}>{c.icon}</div>
            <div style={{fontSize:11, fontWeight:700, color:T.text}}>{c.title}</div>
            <div style={{fontSize:11, color:T.textMute, fontFamily:'DM Sans', fontWeight:500, marginTop:3}}>{c.sub}</div>
          </div>
        ))}
      </div>
    </Content>
  );
}

// ────── Staged reveal (year → league → teams → match) ──────
function XiReveal({ selectedMatch, onContinue }) {
  const T = window.T;
  // Phases: year-spin, year-locked, league-spin, league-locked, teams-spin, teams-locked, complete
  const [phase, setPhase] = React.useState('year-spin');
  const [displayYear, setDisplayYear] = React.useState(2015);
  const [displayLeague, setDisplayLeague] = React.useState('Premier League');
  const [displayHome, setDisplayHome] = React.useState('Arsenal');
  const [displayAway, setDisplayAway] = React.useState('Liverpool');

  const targetYear = new Date(selectedMatch.match_date).getFullYear();
  const targetLeague = selectedMatch.competition;
  const targetHome = selectedMatch.home_club_name;
  const targetAway = selectedMatch.away_club_name;

  const leagues = ['Premier League', 'La Liga', 'Serie A', 'Bundesliga', 'Ligue 1', 'UEFA Champions League'];

  // League-specific club pools. After the league locks, we cycle through teams that
  // actually play in that league so the reveal feels authentic.
  const CLUBS_BY_LEAGUE = {
    'Premier League':          ['Arsenal', 'Liverpool', 'Chelsea', 'Manchester United', 'Manchester City', 'Tottenham', 'Leicester', 'Everton', 'West Ham'],
    'La Liga':                 ['Real Madrid', 'Barcelona', 'Atlético Madrid', 'Sevilla', 'Valencia', 'Villarreal', 'Real Betis', 'Athletic Bilbao'],
    'Serie A':                 ['Juventus', 'Inter', 'Milan', 'Roma', 'Napoli', 'Lazio', 'Atalanta', 'Fiorentina'],
    'Bundesliga':              ['Bayern Munich', 'Borussia Dortmund', 'Bayer Leverkusen', 'RB Leipzig', 'Schalke 04', 'Borussia Mönchengladbach', 'Wolfsburg', 'Frankfurt'],
    'Ligue 1':                 ['PSG', 'Marseille', 'Lyon', 'Monaco', 'Lille', 'Nice', 'Rennes', 'Saint-Étienne'],
    'UEFA Champions League':   ['Real Madrid', 'Barcelona', 'Bayern Munich', 'Manchester City', 'Liverpool', 'PSG', 'Juventus', 'Inter', 'Milan', 'Chelsea', 'Arsenal', 'Borussia Dortmund', 'Benfica'],
    'FIFA World Cup':          ['Brazil', 'Germany', 'France', 'Argentina', 'Spain', 'Italy', 'England', 'Portugal', 'Netherlands', 'Uruguay', 'Belgium', 'Croatia'],
    'FIFA World Cup Qualification': ['Brazil', 'Germany', 'France', 'Argentina', 'Spain', 'Italy', 'England', 'Portugal', 'Netherlands', 'Belgium'],
    'International Friendly':  ['Brazil', 'Germany', 'France', 'Argentina', 'Spain', 'Italy', 'England', 'Portugal', 'Netherlands', 'Belgium', 'Sweden', 'Mexico', 'Uruguay'],
  };
  // Fallback pool for any unlisted competition
  const fallbackClubs = ['Arsenal', 'Liverpool', 'Barcelona', 'Real Madrid', 'Bayern Munich', 'PSG', 'Inter', 'Milan', 'Chelsea', 'Man City'];
  // Filter out the target teams from the spin pool so the reveal isn't spoiled mid-cycle.
  // If filtering empties the pool (tiny league), fall back to the full list.
  const rawPool = CLUBS_BY_LEAGUE[targetLeague] || fallbackClubs;
  const filteredPool = rawPool.filter(c => c !== targetHome && c !== targetAway);
  const targetClubPool = filteredPool.length >= 2 ? filteredPool : rawPool;

  React.useEffect(() => {
    let mounted = true;
    let cleanup = [];

    const spin = (setter, values, totalTicks, onDone) => {
      let tick = 0;
      const step = () => {
        if (!mounted) return;
        tick += 1;
        const progress = tick / totalTicks;
        const eased = 1 - Math.pow(1 - progress, 3);
        const delay = 40 + eased * 150;
        if (tick < totalTicks) {
          setter(values[Math.floor(Math.random() * values.length)]);
          const t = setTimeout(step, delay);
          cleanup.push(t);
        } else {
          onDone();
        }
      };
      const t = setTimeout(step, 40);
      cleanup.push(t);
    };

    const yearValues = [];
    for (let y = 2006; y <= 2026; y++) yearValues.push(y);

    spin(setDisplayYear, yearValues, 18, () => {
      if (!mounted) return;
      setDisplayYear(targetYear);
      setPhase('year-locked');
      const t1 = setTimeout(() => {
        if (!mounted) return;
        setPhase('league-spin');
        spin(setDisplayLeague, leagues, 14, () => {
          if (!mounted) return;
          setDisplayLeague(targetLeague);
          setPhase('league-locked');
          const t2 = setTimeout(() => {
            if (!mounted) return;
            setPhase('teams-spin');
            spin(setDisplayHome, targetClubPool, 14, () => {
              if (!mounted) return;
              setDisplayHome(targetHome);
            });
            spin(setDisplayAway, targetClubPool, 14, () => {
              if (!mounted) return;
              setDisplayAway(targetAway);
              setPhase('teams-locked');
              const t3 = setTimeout(() => {
                if (!mounted) return;
                setPhase('complete');
              }, 600);
              cleanup.push(t3);
            });
          }, 450);
          cleanup.push(t2);
        });
      }, 450);
      cleanup.push(t1);
    });

    return () => { mounted = false; cleanup.forEach(t => clearTimeout(t)); };
  }, []);

  const showYear = phase !== 'year-spin';
  const showLeague = ['league-locked', 'teams-spin', 'teams-locked', 'complete'].includes(phase);
  const showTeams = ['teams-locked', 'complete'].includes(phase);
  const complete = phase === 'complete';

  // Club logo placeholder — shows first letter inside a colored circle using team's real colors
  const matchColors = window.getMatchColors(targetHome, targetAway);
  const ClubCrest = ({ name, isHome }) => {
    const initial = (name || '?').trim()[0]?.toUpperCase() || '?';
    const colors = isHome ? matchColors.home : matchColors.away;
    // Determine text color based on primary brightness (light primary → dark text)
    const isLight = /^#?([fF][8-9a-fA-F])|^#?([eE][cdefCDEF])|^#?[fF][fF]/.test(colors.primary);
    const textColor = isLight ? '#1A2236' : '#FFFFFF';
    return (
      <div style={{
        width: 78, height: 78, borderRadius:'50%',
        background: colors.primary,
        border: `3px solid ${colors.secondary}`,
        display:'flex', alignItems:'center', justifyContent:'center',
        color: textColor,
        fontFamily:'Bakbak One, sans-serif', fontSize: 30, letterSpacing:'0.02em',
        boxShadow:'0 8px 24px rgba(0,0,0,0.4)',
        margin: '0 auto',
      }}>{initial}</div>
    );
  };

  return (
    <Content>
      <div style={{textAlign:'center', padding:'20px 0 8px'}}>
        <div style={{fontFamily:'Bakbak One, sans-serif', fontSize:22, letterSpacing:'0.06em', color:T.text, display:'inline-flex', alignItems:'center', gap:8}}>
          YOUR MATCH <span style={{fontSize:18}}>👉</span>
        </div>
        <div style={{fontSize:13, color:T.textSec, marginTop:4}}>Guess the players from this game</div>
      </div>

      {/* Year */}
      <div style={{
        textAlign:'center', marginTop: 20, marginBottom: 10,
        fontFamily:'Bakbak One, sans-serif',
        fontSize: showYear ? 40 : 120,
        letterSpacing:'0.02em',
        color: showYear ? T.text : 'rgba(136,150,176,0.35)',
        opacity: phase === 'year-spin' ? 0.9 : 1,
        transition:'font-size 500ms cubic-bezier(0.2,0.9,0.3,1), color 500ms ease',
        lineHeight:1,
      }}>{displayYear}</div>

      {/* League + stage */}
      <div style={{
        textAlign:'center', marginBottom: 18,
        fontFamily:'Bakbak One, sans-serif',
        fontSize: showLeague ? 20 : 56,
        letterSpacing:'0.02em',
        color: showLeague ? T.text : 'rgba(136,150,176,0.35)',
        opacity: phase === 'year-locked' ? 0 : (phase === 'year-spin' ? 0 : 1),
        transition: 'all 500ms cubic-bezier(0.2,0.9,0.3,1)',
        minHeight: 28, lineHeight: 1.2,
      }}>
        {phase !== 'year-spin' && phase !== 'year-locked' ? displayLeague : ''}
        {selectedMatch.stage && showLeague ? (
          <div style={{fontSize: 14, marginTop: 4, color: T.text}}>{selectedMatch.stage}</div>
        ) : null}
      </div>

      {/* Teams row — only renders once teams spin starts */}
      <div style={{
        display:'grid', gridTemplateColumns:'1fr auto 1fr',
        alignItems:'center', gap: 12,
        marginTop: 8,
        opacity: (phase === 'teams-spin' || showTeams) ? 1 : 0,
        transition: 'opacity 400ms ease',
      }}>
        {/* Home team */}
        <div style={{textAlign:'center'}}>
          <div style={{
            fontFamily:'Bakbak One, sans-serif', fontSize: 18,
            color: showTeams ? T.text : 'rgba(136,150,176,0.35)',
            transition:'color 500ms ease',
            marginBottom: 10,
            whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis',
          }}>{displayHome}</div>
          {showTeams && <ClubCrest name={targetHome} isHome={true}/>}
          {showTeams && selectedMatch.home_manager && (
            <div style={{
              fontSize: 11, color: T.textSec, marginTop: 8,
              whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis',
              opacity: complete ? 1 : 0,
              transition: 'opacity 500ms ease 200ms',
            }}>{selectedMatch.home_manager}</div>
          )}
        </div>

        {/* Vertical gold divider */}
        <div style={{
          width: 2, height: 100,
          background: showTeams ? T.gold500 : 'transparent',
          transition:'background 400ms ease 200ms',
          borderRadius: 2,
        }}/>

        {/* Away team */}
        <div style={{textAlign:'center'}}>
          <div style={{
            fontFamily:'Bakbak One, sans-serif', fontSize: 18,
            color: showTeams ? T.text : 'rgba(136,150,176,0.35)',
            transition:'color 500ms ease',
            marginBottom: 10,
            whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis',
          }}>{displayAway}</div>
          {showTeams && <ClubCrest name={targetAway} isHome={false}/>}
          {showTeams && selectedMatch.away_manager && (
            <div style={{
              fontSize: 11, color: T.textSec, marginTop: 8,
              whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis',
              opacity: complete ? 1 : 0,
              transition: 'opacity 500ms ease 200ms',
            }}>{selectedMatch.away_manager}</div>
          )}
        </div>
      </div>

      {/* Score + venue — only after complete */}
      <div style={{
        textAlign:'center', marginTop: 22,
        opacity: complete ? 1 : 0,
        transform: complete ? 'translateY(0)' : 'translateY(8px)',
        transition:'all 500ms cubic-bezier(0.2,0.9,0.3,1) 300ms',
      }}>
        <div style={{
          fontFamily:'Bakbak One, sans-serif', fontSize: 32,
          letterSpacing:'0.04em', color: T.text, lineHeight: 1,
        }}>
          {selectedMatch.home_score}–{selectedMatch.away_score}
        </div>
        {selectedMatch.venue && (
          <div style={{
            fontFamily:'Bakbak One, sans-serif', fontSize: 15,
            letterSpacing:'0.02em', color: T.text,
            marginTop: 12,
          }}>{selectedMatch.venue}</div>
        )}
        {selectedMatch.city && (
          <div style={{fontSize: 12, color: T.textSec, marginTop: 2}}>
            {selectedMatch.city}
          </div>
        )}
      </div>

      <div style={{position:'absolute', left:20, right:20, bottom:38}}>
        <button onClick={onContinue} disabled={!complete} style={{
          width:'100%', padding:'16px', borderRadius:14,
          background: complete ? 'linear-gradient(180deg, #F5C842, #C99A10)' : T.card,
          color: complete ? '#1A1200' : T.textMute,
          border: complete ? 'none' : `1px solid ${T.border}`,
          cursor: complete ? 'pointer' : 'default',
          fontFamily:'Bakbak One, sans-serif', fontSize:16, letterSpacing:'0.06em',
          display:'inline-flex', alignItems:'center', justifyContent:'center', gap:8,
          opacity: complete ? 1 : 0.6,
          transform: complete ? 'translateY(0)' : 'translateY(14px)',
          transition: 'all 400ms cubic-bezier(0.2,0.9,0.3,1)',
        }}>START GAME <span>›</span></button>
      </div>
    </Content>
  );
}

// ────── In-game: pitch + search bar + keyboard ──────
// Lineups rendered as dots, with position_label controlling placement.
// Guessed dots turn green and show jersey number.

// ────── Formation-driven pitch layout ──────
// Each formation defines slot coordinates for home (bottom half) and away (top half)
// Slots listed from defense to attack, left-to-right within each line.
// Template uses home-side coords; away is mirrored.
//
// Each slot has: id (for matching), x/y (% coords), and a "role" bucket used to
// assign lineup players into slots in a sensible order.

function mirrorY(y) { return 100 - y; }

const FORMATION_TEMPLATES = {
  '4-3-3': {
    slots: [
      { id:'gk',  role:'gk',  x:50, y:90 },
      { id:'lb',  role:'def', x:18, y:80 },
      { id:'lcb', role:'def', x:38, y:82 },
      { id:'rcb', role:'def', x:62, y:82 },
      { id:'rb',  role:'def', x:82, y:80 },
      { id:'lcm', role:'mid', x:30, y:66 },
      { id:'cm',  role:'mid', x:50, y:68 },
      { id:'rcm', role:'mid', x:70, y:66 },
      { id:'lw',  role:'att', x:22, y:54 },
      { id:'st',  role:'att', x:50, y:52 },
      { id:'rw',  role:'att', x:78, y:54 },
    ],
  },
  '4-4-2': {
    slots: [
      { id:'gk',  role:'gk',  x:50, y:90 },
      { id:'lb',  role:'def', x:18, y:80 },
      { id:'lcb', role:'def', x:38, y:82 },
      { id:'rcb', role:'def', x:62, y:82 },
      { id:'rb',  role:'def', x:82, y:80 },
      { id:'lm',  role:'mid', x:18, y:64 },
      { id:'lcm', role:'mid', x:40, y:66 },
      { id:'rcm', role:'mid', x:60, y:66 },
      { id:'rm',  role:'mid', x:82, y:64 },
      { id:'lst', role:'att', x:38, y:54 },
      { id:'rst', role:'att', x:62, y:54 },
    ],
  },
  '4-2-3-1': {
    slots: [
      { id:'gk',   role:'gk',   x:50, y:90 },
      { id:'lb',   role:'def',  x:18, y:80 },
      { id:'lcb',  role:'def',  x:38, y:82 },
      { id:'rcb',  role:'def',  x:62, y:82 },
      { id:'rb',   role:'def',  x:82, y:80 },
      { id:'lcdm', role:'cdm',  x:38, y:70 },
      { id:'rcdm', role:'cdm',  x:62, y:70 },
      { id:'lam',  role:'cam',  x:22, y:60 },
      { id:'cam',  role:'cam',  x:50, y:58 },
      { id:'ram',  role:'cam',  x:78, y:60 },
      { id:'st',   role:'att',  x:50, y:52 },
    ],
  },
  '4-2-2-2': {
    slots: [
      { id:'gk',   role:'gk',   x:50, y:90 },
      { id:'lb',   role:'def',  x:18, y:80 },
      { id:'lcb',  role:'def',  x:38, y:82 },
      { id:'rcb',  role:'def',  x:62, y:82 },
      { id:'rb',   role:'def',  x:82, y:80 },
      { id:'lcdm', role:'cdm',  x:38, y:70 },
      { id:'rcdm', role:'cdm',  x:62, y:70 },
      { id:'lam',  role:'cam',  x:30, y:60 },
      { id:'ram',  role:'cam',  x:70, y:60 },
      { id:'lst',  role:'att',  x:38, y:52 },
      { id:'rst',  role:'att',  x:62, y:52 },
    ],
  },
  '4-4-1-1': {
    slots: [
      { id:'gk',   role:'gk',   x:50, y:90 },
      { id:'lb',   role:'def',  x:18, y:80 },
      { id:'lcb',  role:'def',  x:38, y:82 },
      { id:'rcb',  role:'def',  x:62, y:82 },
      { id:'rb',   role:'def',  x:82, y:80 },
      { id:'lm',   role:'mid',  x:18, y:68 },
      { id:'lcm',  role:'mid',  x:40, y:70 },
      { id:'rcm',  role:'mid',  x:60, y:70 },
      { id:'rm',   role:'mid',  x:82, y:68 },
      { id:'cam',  role:'cam',  x:50, y:58 },
      { id:'st',   role:'att',  x:50, y:52 },
    ],
  },
  '3-5-2': {
    slots: [
      { id:'gk',   role:'gk',   x:50, y:90 },
      { id:'lcb',  role:'def',  x:28, y:82 },
      { id:'cb',   role:'def',  x:50, y:84 },
      { id:'rcb',  role:'def',  x:72, y:82 },
      { id:'lwb',  role:'wb',   x:12, y:70 },
      { id:'lcm',  role:'mid',  x:34, y:70 },
      { id:'cdm',  role:'cdm',  x:50, y:72 },
      { id:'rcm',  role:'mid',  x:66, y:70 },
      { id:'rwb',  role:'wb',   x:88, y:70 },
      { id:'lst',  role:'att',  x:38, y:54 },
      { id:'rst',  role:'att',  x:62, y:54 },
    ],
  },
  '3-4-2-1': {
    slots: [
      { id:'gk',   role:'gk',   x:50, y:90 },
      { id:'lcb',  role:'def',  x:28, y:82 },
      { id:'cb',   role:'def',  x:50, y:84 },
      { id:'rcb',  role:'def',  x:72, y:82 },
      { id:'lwb',  role:'wb',   x:12, y:70 },
      { id:'lcm',  role:'mid',  x:38, y:72 },
      { id:'rcm',  role:'mid',  x:62, y:72 },
      { id:'rwb',  role:'wb',   x:88, y:70 },
      { id:'lam',  role:'cam',  x:34, y:60 },
      { id:'ram',  role:'cam',  x:66, y:60 },
      { id:'st',   role:'att',  x:50, y:52 },
    ],
  },
  '3-4-3': {
    slots: [
      { id:'gk',   role:'gk',   x:50, y:90 },
      { id:'lcb',  role:'def',  x:28, y:82 },
      { id:'cb',   role:'def',  x:50, y:84 },
      { id:'rcb',  role:'def',  x:72, y:82 },
      { id:'lm',   role:'mid',  x:12, y:68 },
      { id:'lcm',  role:'mid',  x:38, y:70 },
      { id:'rcm',  role:'mid',  x:62, y:70 },
      { id:'rm',   role:'mid',  x:88, y:68 },
      { id:'lw',   role:'att',  x:22, y:54 },
      { id:'st',   role:'att',  x:50, y:52 },
      { id:'rw',   role:'att',  x:78, y:54 },
    ],
  },
  '4-1-2-1-2': {
    slots: [
      { id:'gk',   role:'gk',   x:50, y:90 },
      { id:'lb',   role:'def',  x:18, y:80 },
      { id:'lcb',  role:'def',  x:38, y:82 },
      { id:'rcb',  role:'def',  x:62, y:82 },
      { id:'rb',   role:'def',  x:82, y:80 },
      { id:'cdm',  role:'cdm',  x:50, y:74 },
      { id:'lcm',  role:'mid',  x:28, y:64 },
      { id:'rcm',  role:'mid',  x:72, y:64 },
      { id:'cam',  role:'cam',  x:50, y:58 },
      { id:'lst',  role:'att',  x:38, y:52 },
      { id:'rst',  role:'att',  x:62, y:52 },
    ],
  },
  '4-3-1-2': {
    slots: [
      { id:'gk',   role:'gk',   x:50, y:90 },
      { id:'lb',   role:'def',  x:18, y:80 },
      { id:'lcb',  role:'def',  x:38, y:82 },
      { id:'rcb',  role:'def',  x:62, y:82 },
      { id:'rb',   role:'def',  x:82, y:80 },
      { id:'lcm',  role:'mid',  x:30, y:70 },
      { id:'cdm',  role:'cdm',  x:50, y:72 },
      { id:'rcm',  role:'mid',  x:70, y:70 },
      { id:'cam',  role:'cam',  x:50, y:60 },
      { id:'lst',  role:'att',  x:38, y:52 },
      { id:'rst',  role:'att',  x:62, y:52 },
    ],
  },
};

// Classify a single position label into a role bucket for slot matching
function positionToRole(label) {
  const p = (label || '').toUpperCase();
  if (p === 'GK') return 'gk';
  if (['CB','LB','RB'].includes(p)) return 'def';
  if (['LWB','RWB'].includes(p)) return 'wb';
  if (p === 'CDM') return 'cdm';
  if (['CAM','LAM','RAM'].includes(p)) return 'cam';
  if (['LM','RM','CM'].includes(p)) return 'mid';
  if (['ST','LW','RW','CF','LF','RF'].includes(p)) return 'att';
  return 'mid';
}

// Sort hint: within a line, left-to-right preference
function horizontalHint(label) {
  const p = (label || '').toUpperCase();
  if (p.startsWith('L')) return 0;
  if (p === 'CB' || p === 'CM' || p === 'CAM' || p === 'CDM' || p === 'ST' || p === 'GK' || p === 'CF') return 1;
  if (p.startsWith('R')) return 2;
  return 1;
}

// Map a team's lineup onto formation slots, returning enriched player objects
// with _x/_y coords for rendering. If formation unknown, falls back to position-based.
function layoutFormation(lineup, formationKey, side) {
  const starters = lineup.filter(r => r.role === 'starter');
  const template = FORMATION_TEMPLATES[formationKey];

  // Fallback: if we don't have this formation defined, place by individual position
  if (!template) {
    return starters.map((p, i) => {
      const role = positionToRole(p.position_label);
      // Simple fallback coords by role bucket
      const roleY = { gk: 90, def: 80, wb: 72, cdm: 70, mid: 66, cam: 60, att: 54 }[role] || 65;
      const y = side === 'away' ? 100 - roleY : roleY;
      return { ...p, _x: 50, _y: y };
    });
  }

  // Group slots and players by role, then match greedy left-to-right
  const slotsByRole = {};
  template.slots.forEach(s => {
    if (!slotsByRole[s.role]) slotsByRole[s.role] = [];
    slotsByRole[s.role].push(s);
  });

  const playersByRole = {};
  starters.forEach(p => {
    const r = positionToRole(p.position_label);
    if (!playersByRole[r]) playersByRole[r] = [];
    playersByRole[r].push(p);
  });

  // Within each role, sort slots by x, sort players by horizontalHint
  Object.values(slotsByRole).forEach(slots => slots.sort((a,b) => a.x - b.x));
  Object.values(playersByRole).forEach(players => {
    players.sort((a,b) => horizontalHint(a.position_label) - horizontalHint(b.position_label));
  });

  // Assign players to slots
  const placed = [];
  const usedSlots = new Set();

  // Pass 1: exact role match
  Object.entries(playersByRole).forEach(([role, players]) => {
    const slots = (slotsByRole[role] || []).filter(s => !usedSlots.has(s.id));
    for (let i = 0; i < Math.min(players.length, slots.length); i++) {
      const slot = slots[i];
      const player = players[i];
      const y = side === 'away' ? mirrorY(slot.y) : slot.y;
      const x = side === 'away' ? 100 - slot.x : slot.x;
      placed.push({ ...player, _x: x, _y: y });
      usedSlots.add(slot.id);
      player._placed = true;
    }
  });

  // Pass 2: any unplaced players fall into remaining slots (nearest role bucket)
  const unplacedPlayers = starters.filter(p => !p._placed);
  const remainingSlots = template.slots.filter(s => !usedSlots.has(s.id));
  unplacedPlayers.forEach((player, i) => {
    if (i < remainingSlots.length) {
      const slot = remainingSlots[i];
      const y = side === 'away' ? mirrorY(slot.y) : slot.y;
      const x = side === 'away' ? 100 - slot.x : slot.x;
      placed.push({ ...player, _x: x, _y: y });
      usedSlots.add(slot.id);
    } else {
      // True fallback: center the pitch
      const y = side === 'away' ? 30 : 70;
      placed.push({ ...player, _x: 50, _y: y });
    }
  });

  return placed;
}

function XiGame({ match, lineups, autocompleteNames, onEnd }) {
  const T = window.T;
  const matchColors = window.getMatchColors(match.home_club_name, match.away_club_name);
  const [guess, setGuess] = React.useState('');
  const [guessed, setGuessed] = React.useState(new Set()); // lineup row ids
  const [timeLeft, setTimeLeft] = React.useState(120); // 2 minutes
  const [shake, setShake] = React.useState(false);
  const [sessionId, setSessionId] = React.useState(null);
  const [inputFocused, setInputFocused] = React.useState(false);
  const sessionIdRef = React.useRef(null);
  const startTimeRef = React.useRef(Date.now());
  const inputRef = React.useRef(null);

  const homeStarters = React.useMemo(() => layoutFormation(lineups.filter(l => l.team_side === 'home'), match.home_formation, 'home'), [lineups, match.home_formation]);
  const awayStarters = React.useMemo(() => layoutFormation(lineups.filter(l => l.team_side === 'away'), match.away_formation, 'away'), [lineups, match.away_formation]);
  const allStarters = [...homeStarters, ...awayStarters];
  const totalStarters = allStarters.length;

  // Create game_sessions row on mount (only if signed in)
  React.useEffect(() => {
    let mounted = true;
    (async () => {
      if (!await window.isSignedIn()) return;
      try {
        const sess = await window.createGameSession('xi', {
          match_id: match.id,
          home_club: match.home_club_name,
          away_club: match.away_club_name,
          match_date: match.match_date,
          competition: match.competition,
          total_starters: totalStarters,
        });
        if (mounted) {
          setSessionId(sess.id);
          sessionIdRef.current = sess.id;
        }
      } catch (e) {
        console.error('Failed to create game session:', e);
      }
    })();
    return () => { mounted = false; };
  }, []);

  // Timer
  React.useEffect(() => {
    if (timeLeft <= 0) {
      finishGame('timeout');
      return;
    }
    const t = setTimeout(() => setTimeLeft(x => x - 1), 1000);
    return () => clearTimeout(t);
  }, [timeLeft]);

  // End early when all guessed
  React.useEffect(() => {
    if (guessed.size === totalStarters && totalStarters > 0) {
      finishGame('complete');
    }
  }, [guessed, totalStarters]);

  // Skip / give up — two-tap pattern to prevent accidents
  const [skipConfirm, setSkipConfirm] = React.useState(false);
  const skipConfirmTimerRef = React.useRef(null);

  const onSkipPress = () => {
    if (skipConfirm) {
      // Second tap = confirm
      clearTimeout(skipConfirmTimerRef.current);
      finishGame('gave_up');
    } else {
      setSkipConfirm(true);
      // Auto-reset if user doesn't tap again in 3s
      clearTimeout(skipConfirmTimerRef.current);
      skipConfirmTimerRef.current = setTimeout(() => setSkipConfirm(false), 3000);
    }
  };

  const finishGame = async (reason) => {
    // reason: 'complete' | 'timeout' | 'gave_up'
    const outOfTime = reason === 'timeout';
    const gaveUp = reason === 'gave_up';
    const id = sessionIdRef.current;
    if (id) {
      try {
        await window.endGameSession(id, {
          match_id: match.id,
          home_club: match.home_club_name,
          away_club: match.away_club_name,
          match_date: match.match_date,
          competition: match.competition,
          total_starters: totalStarters,
          guessed_count: guessed.size,
          accuracy: totalStarters > 0 ? guessed.size / totalStarters : 0,
          out_of_time: outOfTime,
          gave_up: gaveUp,
          time_left: timeLeft,
        }, true);
      } catch (e) {
        console.error('Failed to end session:', e);
      }
    }
    // Fire GA4 event so we can see completion funnel
    if (typeof window.trackEvent === 'function') {
      const acc = totalStarters > 0 ? guessed.size / totalStarters : 0;
      window.trackEvent('xi_game_completed', {
        guessed: guessed.size,
        total: totalStarters,
        accuracy_pct: Math.round(acc * 100),
        outcome: gaveUp ? 'gave_up' : outOfTime ? 'timeout' : 'complete',
        signed_in: !!sessionIdRef.current,
      });
    }
    onEnd({ guessed, allStarters, outOfTime, gaveUp });

    // Award tokens for performance and check achievements (only if signed in)
    (async () => {
      if (!await window.isSignedIn()) return;
      try {
        const all = await window.fetchMySessions();
        const latest = all[0];
        // Conservative token award — achievements are the big earners
        const tokenBonus = window.XI_GAME_TOKENS ? window.XI_GAME_TOKENS(latest) : 3;
        await window.awardTokens(tokenBonus);
        // Achievement check
        const newlyUnlocked = await window.checkAchievements(all);
        if (newlyUnlocked.length > 0) {
          window._pendingAchievements = (window._pendingAchievements || []).concat(newlyUnlocked);
        }
      } catch (e) {
        console.error('Post-game awards failed:', e);
      }
    })();
  };

  const submitGuess = async (text) => {
    const trimmed = text.trim();
    if (!trimmed) return;

    const hit = allStarters.find(p =>
      !guessed.has(p.id) && isNameMatch(trimmed, p.player_name)
    );

    const secondsElapsed = Math.round((Date.now() - startTimeRef.current) / 1000);

    if (sessionIdRef.current) {
      window.logXiGuess(
        sessionIdRef.current,
        match.id,
        trimmed,
        !!hit,
        hit?.player_id || null,
        secondsElapsed
      ).catch(e => console.error('Log guess failed:', e));
    }

    if (hit) {
      setGuessed(prev => new Set([...prev, hit.id]));
      setGuess('');
    } else {
      setShake(true);
      setTimeout(() => setShake(false), 300);
      setGuess('');
    }
  };

  const handleKey = (e) => {
    if (e.key === 'Enter') submitGuess(guess);
  };

  const closeKeyboard = () => {
    if (inputRef.current) inputRef.current.blur();
    setInputFocused(false);
  };

  const openKeyboard = () => {
    if (inputRef.current) inputRef.current.focus();
  };

  // ─── Autocomplete suggestions ───
  // Filters the full name pool to entries that match what the user has typed.
  // Returns up to 5 suggestions, prioritizing exact starts-with over substring matches.
  const suggestions = React.useMemo(() => {
    const trimmed = guess.trim();
    if (trimmed.length < 2 || !autocompleteNames || autocompleteNames.length === 0) return [];
    const q = normalizeNameForMatch(trimmed);

    const startsWith = [];
    const contains = [];
    for (const name of autocompleteNames) {
      const n = normalizeNameForMatch(name);
      if (!n) continue;
      if (n.startsWith(q)) {
        startsWith.push(name);
      } else if (n.includes(q)) {
        // also check if ANY word in the name starts with q (handles "ronaldo" → "Cristiano Ronaldo")
        const words = n.split(' ');
        if (words.some(w => w.startsWith(q))) {
          startsWith.push(name);
        } else {
          contains.push(name);
        }
      }
      if (startsWith.length >= 10) break; // early exit for perf
    }
    // Dedupe just in case (names could appear as both "Messi" from match_lineups and "Lionel Messi" from player_seasons)
    const seen = new Set();
    const combined = [...startsWith, ...contains];
    const deduped = [];
    for (const n of combined) {
      const key = normalizeNameForMatch(n);
      if (!seen.has(key)) {
        seen.add(key);
        deduped.push(n);
      }
      if (deduped.length >= 5) break;
    }
    return deduped;
  }, [guess, autocompleteNames]);

  const selectSuggestion = (name) => {
    submitGuess(name);
    // Keep keyboard open for rapid-fire guessing
  };

  const year = new Date(match.match_date).getFullYear();
  const mm = Math.floor(timeLeft / 60);
  const ss = String(timeLeft % 60).padStart(2, '0');

  return (
    <div style={{
      flex: 1, display:'flex', flexDirection:'column',
      height:'100%', position:'relative',
      background: T.navy950,
    }}>
      {/* Compact top bar: progress + match info + timer — all in one row */}
      <div style={{
        padding:'10px 16px 8px',
        borderBottom:`1px solid ${T.border}`,
        flexShrink: 0,
      }}>
        <div style={{display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:6}}>
          <div style={{fontFamily:'DM Sans', fontSize: 12, fontWeight: 700, color: T.text}}>
            {guessed.size}/{totalStarters} Players
          </div>
          <div style={{fontFamily:'Bakbak One, sans-serif', fontSize: 11, letterSpacing:'0.04em', color: T.textSec, flex: 1, textAlign:'center', padding:'0 8px', whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis'}}>
            {year} · {match.home_club_name} vs {match.away_club_name}
          </div>
          <div style={{display:'inline-flex', alignItems:'center', gap: 6}}>
            <button onClick={onSkipPress} style={{
              display:'inline-flex', alignItems:'center', justifyContent:'center',
              padding: skipConfirm ? '3px 10px' : '3px 6px',
              borderRadius: 100,
              background: skipConfirm ? 'rgba(232,122,122,0.15)' : 'transparent',
              border: `1px solid ${skipConfirm ? '#E87A7A' : T.border}`,
              color: skipConfirm ? '#E87A7A' : T.textSec,
              cursor: 'pointer',
              fontFamily: 'DM Sans',
              fontSize: 10, fontWeight: 700,
              transition: 'all 200ms',
            }}>
              {skipConfirm ? 'GIVE UP?' : (
                <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
                  <polygon points="5 4 15 12 5 20 5 4"/>
                  <line x1="19" y1="5" x2="19" y2="19"/>
                </svg>
              )}
            </button>
            <div style={{
              display:'inline-flex', alignItems:'center', gap: 4,
              padding:'3px 8px', borderRadius: 100,
              background: timeLeft <= 20 ? 'rgba(232,122,122,0.15)' : 'rgba(232,184,32,0.12)',
              border: `1px solid ${timeLeft <= 20 ? '#E87A7A' : T.gold600}`,
              color: timeLeft <= 20 ? '#E87A7A' : T.gold400,
            }}>
              <span style={{fontSize: 11, fontWeight: 700, fontFamily:'DM Sans', letterSpacing:'0.04em'}}>{mm}:{ss}</span>
            </div>
          </div>
        </div>
        <div style={{height:2, background:T.border, borderRadius:100, overflow:'hidden'}}>
          <div style={{width:`${(guessed.size/totalStarters)*100}%`, height:'100%', background:T.gold400, transition:'width 300ms'}}/>
        </div>
      </div>

      {/* Pitch — fills remaining space */}
      <div style={{
        flex: 1, position:'relative', overflow:'hidden',
        background:'linear-gradient(180deg, #2D8C4E, #1F6638)',
      }}>
        {/* Pitch markings */}
        <div style={{position:'absolute', inset:8, border:'2px solid rgba(255,255,255,0.35)', borderRadius:4}}/>
        <div style={{position:'absolute', top:'50%', left:8, right:8, height:2, background:'rgba(255,255,255,0.35)'}}/>
        <div style={{position:'absolute', left:'50%', top:'50%', width:70, height:70, transform:'translate(-50%,-50%)', border:'2px solid rgba(255,255,255,0.35)', borderRadius:'50%'}}/>
        <div style={{position:'absolute', left:'30%', right:'30%', top:8, height:40, border:'2px solid rgba(255,255,255,0.35)', borderTop:'none'}}/>
        <div style={{position:'absolute', left:'30%', right:'30%', bottom:8, height:40, border:'2px solid rgba(255,255,255,0.35)', borderBottom:'none'}}/>

        {/* Player dots */}
        {allStarters.map(p => {
          const isGuessed = guessed.has(p.id);
          const isAway = p.team_side === 'away';
          const colors = isAway ? matchColors.away : matchColors.home;
          return (
            <div key={p.id} style={{
              position:'absolute', left:`${p._x}%`, top:`${p._y}%`,
              transform:'translate(-50%,-50%)',
              width: 30, height: 30, borderRadius:'50%',
              background: isGuessed ? '#37A85F' : colors.primary,
              border: isGuessed
                ? '2px solid rgba(255,255,255,0.4)'
                : `2px solid ${colors.secondary}`,
              display:'flex', alignItems:'center', justifyContent:'center',
              fontFamily:'DM Sans', fontSize: 11, fontWeight: 800,
              color: isGuessed ? '#fff' : 'transparent',
              boxShadow: isGuessed ? '0 0 14px rgba(55,168,95,0.6)' : 'none',
              transition:'all 300ms cubic-bezier(0.2,0.9,0.3,1)',
              zIndex: 2,
            }}>
              {isGuessed && (p.jersey_number || '')}
            </div>
          );
        })}
      </div>

      {/* Search bar — sits directly below the pitch in the flex layout (not overlaid) */}
      <div style={{
        flexShrink: 0,
        background: T.navy950,
        padding: '8px 14px 12px',
        position: 'relative',
      }}>
        {/* Autocomplete suggestions — floats above the search bar */}
        {suggestions.length > 0 && (
          <div style={{
            position:'absolute',
            bottom:'100%',
            left: 14, right: 14,
            marginBottom: 6,
            background: T.card,
            border: `1px solid ${T.border}`,
            borderRadius: 14,
            overflow:'hidden',
            boxShadow: '0 -8px 24px rgba(0,0,0,0.3)',
            zIndex: 11,
          }}>
            {suggestions.map((name, i) => (
              <button
                key={name}
                onMouseDown={e => {
                  e.preventDefault();
                  selectSuggestion(name);
                }}
                onTouchStart={e => {
                  e.preventDefault();
                  selectSuggestion(name);
                }}
                style={{
                  display:'block', width:'100%',
                  padding:'10px 14px',
                  background:'transparent',
                  border:'none',
                  borderBottom: i < suggestions.length - 1 ? `1px solid ${T.border}` : 'none',
                  color: T.text,
                  fontFamily:'DM Sans', fontSize: 14, fontWeight: 500,
                  textAlign:'left',
                  cursor:'pointer',
                }}
              >{name}</button>
            ))}
          </div>
        )}

        <div style={{
          padding:'12px 16px',
          background: T.card, borderRadius: 100,
          border:`1px solid ${shake ? '#E87A7A' : T.border}`,
          display:'flex', alignItems:'center', gap: 10,
          animation: shake ? 'shake 300ms' : 'none',
        }}>
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke={T.textSec} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
            <circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/>
          </svg>
          <input
            ref={inputRef}
            value={guess}
            onChange={e => setGuess(e.target.value)}
            onKeyDown={handleKey}
            onFocus={() => setInputFocused(true)}
            onBlur={() => setInputFocused(false)}
            placeholder="Type a player's name…"
            type="text"
            name="futball-guess"
            autoComplete="one-time-code"
            autoCorrect="off"
            autoCapitalize="off"
            spellCheck={false}
            data-form-type="other"
            data-lpignore="true"
            data-1p-ignore="true"
            inputMode="text"
            enterKeyHint="go"
            style={{
              flex:1, background:'transparent', border:'none', outline:'none',
              color: T.text, fontFamily:'DM Sans', fontSize: 16, fontWeight: 500,
              minWidth: 0,
            }}
          />
          {guess && (
            <button onClick={() => submitGuess(guess)} style={{
              background: T.gold500, border:'none', borderRadius: 100,
              padding:'4px 10px', color: T.navy950, fontSize: 11, fontWeight: 700,
              cursor:'pointer', flexShrink: 0,
            }}>Enter</button>
          )}
        </div>
      </div>

      <style>{`
        @keyframes shake {
          0%, 100% { transform: translateX(0); }
          25% { transform: translateX(-4px); }
          75% { transform: translateX(4px); }
        }
      `}</style>
    </div>
  );
}

// ────── Result screen ──────
function XiResult({ match, guessed, allStarters, outOfTime, gaveUp, onReveal, onDone, revealed }) {
  const T = window.T;
  const resultColors = window.getMatchColors(match.home_club_name, match.away_club_name);
  const year = new Date(match.match_date).getFullYear();
  const got = allStarters.filter(p => guessed.has(p.id));
  const missed = allStarters.filter(p => !guessed.has(p.id));

  const homeGot = got.filter(p => p.team_side === 'home').sort((a,b) => (a.jersey_number||0) - (b.jersey_number||0));
  const awayGot = got.filter(p => p.team_side === 'away').sort((a,b) => (a.jersey_number||0) - (b.jersey_number||0));
  const homeMissed = missed.filter(p => p.team_side === 'home').sort((a,b) => (a.jersey_number||0) - (b.jersey_number||0));
  const awayMissed = missed.filter(p => p.team_side === 'away').sort((a,b) => (a.jersey_number||0) - (b.jersey_number||0));

  // Headline state: complete > timeout > gave up
  const statusText = gaveUp ? 'GAVE UP' : outOfTime ? "TIME'S UP" : 'COMPLETE';
  const statusColor = gaveUp ? '#E87A7A' : T.textMute;

  return (
    <Content>
      <div style={{textAlign:'center', padding:'20px 0 8px'}}>
        <div style={{fontSize: 11, color: statusColor, fontFamily:'DM Sans', letterSpacing:'0.2em'}}>
          {statusText}
        </div>
        <div style={{fontFamily:'Bakbak One, sans-serif', fontSize: 28, color: T.text, letterSpacing:'0.04em', marginTop: 6}}>
          {got.length} / {allStarters.length}
        </div>
        <div style={{fontSize: 13, color: T.textSec, marginTop: 4}}>
          {year} · {match.home_club_name} vs. {match.away_club_name}
        </div>
      </div>

      {/* Score pill */}
      <div style={{
        display:'flex', justifyContent:'center', marginBottom: 16, marginTop: 8,
      }}>
        <div style={{
          display:'inline-flex', alignItems:'center', gap: 10,
          padding:'8px 18px', borderRadius: 100,
          background: 'rgba(55,168,95,0.15)', border:'1px solid rgba(55,168,95,0.4)',
          color: '#37A85F', fontFamily:'DM Sans', fontSize: 12, fontWeight: 700, letterSpacing:'0.08em',
        }}>
          {Math.round((got.length / allStarters.length) * 100)}% ACCURACY
        </div>
      </div>

      {/* Home team */}
      <div style={{marginBottom: 16}}>
        <div style={{
          fontFamily:'Bakbak One, sans-serif', fontSize: 14, color: T.text,
          letterSpacing:'0.04em', marginBottom: 8,
          display:'flex', alignItems:'center', gap:8,
        }}>
          <div style={{width:10, height:10, borderRadius:'50%', background: resultColors.home.primary, border:`1.5px solid ${resultColors.home.secondary}`}}/>
          {match.home_club_name}
        </div>
        {homeGot.map(p => (
          <div key={p.id} style={{display:'flex', alignItems:'center', gap: 10, padding:'6px 0'}}>
            <div style={{width: 24, fontFamily:'DM Sans', fontSize: 10, color: T.gold400, textAlign:'center'}}>
              {p.jersey_number || '—'}
            </div>
            <div style={{fontSize: 13, color: T.text, flex: 1, fontWeight: 500}}>{p.player_name}</div>
            <svg width="14" height="14" viewBox="0 0 24 24" fill="#37A85F"><path d="M9 16.2l-3.5-3.5a1 1 0 011.4-1.4L9 13.4l7.1-7.1a1 1 0 011.4 1.4L9 16.2z"/></svg>
          </div>
        ))}
        {revealed && homeMissed.map(p => (
          <div key={p.id} style={{display:'flex', alignItems:'center', gap: 10, padding:'6px 0', opacity: 0.6}}>
            <div style={{width: 24, fontFamily:'DM Sans', fontSize: 10, color: T.textMute, textAlign:'center'}}>
              {p.jersey_number || '—'}
            </div>
            <div style={{fontSize: 13, color: T.textMute, flex: 1, fontStyle:'italic'}}>{p.player_name}</div>
            <svg width="14" height="14" viewBox="0 0 24 24" fill="#E87A7A"><path d="M18.3 5.7a1 1 0 00-1.4 0L12 10.6 7.1 5.7a1 1 0 10-1.4 1.4l4.9 4.9-4.9 4.9a1 1 0 101.4 1.4l4.9-4.9 4.9 4.9a1 1 0 001.4-1.4L13.4 12l4.9-4.9a1 1 0 000-1.4z"/></svg>
          </div>
        ))}
      </div>

      {/* Away team */}
      <div style={{marginBottom: 20}}>
        <div style={{
          fontFamily:'Bakbak One, sans-serif', fontSize: 14, color: T.text,
          letterSpacing:'0.04em', marginBottom: 8,
          display:'flex', alignItems:'center', gap:8,
        }}>
          <div style={{width:10, height:10, borderRadius:'50%', background: resultColors.away.primary, border:`1.5px solid ${resultColors.away.secondary}`}}/>
          {match.away_club_name}
        </div>
        {awayGot.map(p => (
          <div key={p.id} style={{display:'flex', alignItems:'center', gap: 10, padding:'6px 0'}}>
            <div style={{width: 24, fontFamily:'DM Sans', fontSize: 10, color: T.gold400, textAlign:'center'}}>
              {p.jersey_number || '—'}
            </div>
            <div style={{fontSize: 13, color: T.text, flex: 1, fontWeight: 500}}>{p.player_name}</div>
            <svg width="14" height="14" viewBox="0 0 24 24" fill="#37A85F"><path d="M9 16.2l-3.5-3.5a1 1 0 011.4-1.4L9 13.4l7.1-7.1a1 1 0 011.4 1.4L9 16.2z"/></svg>
          </div>
        ))}
        {revealed && awayMissed.map(p => (
          <div key={p.id} style={{display:'flex', alignItems:'center', gap: 10, padding:'6px 0', opacity: 0.6}}>
            <div style={{width: 24, fontFamily:'DM Sans', fontSize: 10, color: T.textMute, textAlign:'center'}}>
              {p.jersey_number || '—'}
            </div>
            <div style={{fontSize: 13, color: T.textMute, flex: 1, fontStyle:'italic'}}>{p.player_name}</div>
            <svg width="14" height="14" viewBox="0 0 24 24" fill="#E87A7A"><path d="M18.3 5.7a1 1 0 00-1.4 0L12 10.6 7.1 5.7a1 1 0 10-1.4 1.4l4.9 4.9-4.9 4.9a1 1 0 101.4 1.4l4.9-4.9 4.9 4.9a1 1 0 001.4-1.4L13.4 12l4.9-4.9a1 1 0 000-1.4z"/></svg>
          </div>
        ))}
      </div>

      {/* Action buttons */}
      <div style={{position:'absolute', left:20, right:20, bottom:38, display:'flex', flexDirection:'column', gap:10}}>
        {!revealed && missed.length > 0 && (
          <button onClick={onReveal} style={{
            width:'100%', padding:'14px', borderRadius:14,
            background: T.card, border:`1px solid ${T.border}`, color: T.text,
            cursor:'pointer', fontFamily:'DM Sans', fontSize: 14, fontWeight: 700,
          }}>Reveal Answers</button>
        )}
        <button onClick={onDone} style={{
          width:'100%', padding:'14px', borderRadius:14,
          background:'linear-gradient(180deg, #F5C842, #C99A10)',
          color:'#1A1200', border:'none', cursor:'pointer',
          fontFamily:'Bakbak One, sans-serif', fontSize: 15, letterSpacing:'0.06em',
        }}>DONE</button>
      </div>
    </Content>
  );
}

// ────── Orchestrator ──────
function XiFlow({ onExit }) {
  const [stage, setStage] = React.useState('landing');
  const [match, setMatch] = React.useState(null);
  const [lineups, setLineups] = React.useState([]);
  const [loadError, setLoadError] = React.useState(null);
  const [result, setResult] = React.useState(null);
  const [revealed, setRevealed] = React.useState(false);
  const [autocompleteNames, setAutocompleteNames] = React.useState(null);
  const [tokens, setTokens] = React.useState(null);
  const [tokenError, setTokenError] = React.useState(false);
  const GAME_COST = 2;

  React.useEffect(() => {
    let mounted = true;
    window.fetchAutocompleteNames()
      .then(names => { if (mounted) setAutocompleteNames(names); })
      .catch(() => { if (mounted) setAutocompleteNames([]); });
    // Load token balance only if signed in
    window.isSignedIn().then(signedIn => {
      if (!signedIn || !mounted) return;
      window.fetchTokens()
        .then(t => { if (mounted) setTokens(t?.balance ?? 0); })
        .catch(() => {});
    });
    return () => { mounted = false; };
  }, []);

  const start = async () => {
    const signedIn = await window.isSignedIn();
    if (signedIn) {
      // Check balance before loading match
      const tok = await window.fetchTokens();
      const balance = tok?.balance ?? 0;
      if (balance < GAME_COST) {
        setTokenError(true);
        return;
      }
    }
    setStage('loading');
    setLoadError(null);
    try {
      const matches = await window.fetchAllMatches();
      if (!matches || matches.length === 0) { setLoadError('No matches available'); return; }
      const chosen = matches[Math.floor(Math.random() * matches.length)];
      const chosenLineups = await window.fetchMatchLineups(chosen.id);
      setMatch(chosen);
      setLineups(chosenLineups);
      setStage('reveal');
    } catch (e) {
      setLoadError(e.message || 'Failed to load match');
    }
  };

  const startGame = async () => {
    const signedIn = await window.isSignedIn();
    if (signedIn) {
      // Deduct tokens when user taps START GAME on reveal screen
      try {
        const newBalance = await window.spendTokens(GAME_COST);
        setTokens(newBalance);
      } catch (e) {
        if (e.message === 'insufficient_tokens') { setTokenError(true); return; }
        console.error('Failed to deduct tokens:', e);
      }
    }
    setStage('game');
    trackPage('/play/xi/game', 'XI Game');
  };

  const onEnd = (r) => {
    setResult(r);
    setRevealed(false);
    setStage('result');
  };

  // Not enough tokens screen
  if (tokenError) {
    const T = window.T;
    return (
      <Content>
        <div style={{textAlign:'center', padding:'60px 20px'}}>
          <div style={{fontSize:56, marginBottom:16}}>⬡</div>
          <div style={{fontFamily:'Bakbak One, sans-serif', fontSize:26, color:T.text, letterSpacing:'0.04em', marginBottom:8}}>
            NOT ENOUGH TOKENS
          </div>
          <div style={{fontSize:14, color:T.textSec, lineHeight:1.6, marginBottom:32}}>
            You need <span style={{color:T.gold400, fontWeight:700}}>{GAME_COST} tokens</span> to play.<br/>
            Earn more by completing games<br/>and unlocking achievements.
          </div>
          <button onClick={onExit} style={{
            width:'100%', padding:'14px', borderRadius:12,
            background:T.card, border:`1px solid ${T.border}`,
            color:T.text, fontFamily:'DM Sans', fontSize:14, fontWeight:600, cursor:'pointer',
          }}>← Back</button>
        </div>
      </Content>
    );
  }

  if (stage === 'landing') return <XiLanding onStart={start} onBack={onExit} tokens={tokens} cost={GAME_COST}/>;
  if (stage === 'loading') {
    const T = window.T;
    return (
      <Content>
        <div style={{textAlign:'center', padding:'60px 0'}}>
          <div style={{fontFamily:'Bakbak One, sans-serif', fontSize: 22, color: T.text}}>Loading match…</div>
          {loadError && <div style={{color: T.red, marginTop: 10, fontSize: 12}}>{loadError}</div>}
          <button onClick={onExit} style={{marginTop: 20, padding:'8px 16px', background: T.card, border:`1px solid ${T.border}`, borderRadius: 8, color: T.text, cursor:'pointer'}}>Cancel</button>
        </div>
      </Content>
    );
  }
  if (stage === 'reveal' && match) return <XiReveal selectedMatch={match} onContinue={startGame}/>;
  if (stage === 'game' && match) return <XiGame match={match} lineups={lineups} autocompleteNames={autocompleteNames || []} onEnd={onEnd}/>;
  if (stage === 'result' && match && result) {
    return (
      <XiResult
        match={match}
        guessed={result.guessed}
        allStarters={result.allStarters}
        outOfTime={result.outOfTime}
        gaveUp={result.gaveUp}
        revealed={revealed}
        onReveal={() => setRevealed(true)}
        onDone={onExit}
      />
    );
  }
  return null;
}

window.XiFlow = XiFlow;
