// 777 Strike — root app, slot game state machine.

const { useState, useEffect, useRef, useMemo } = React;
const { IOSDevice } = window;
const { Reel, PaytableStrip, Icons, CrownBgPattern, GreenPill, SpinDisc, WinNumber, CoinRain,
        SidebarMenu, StakeModal, AutoplayModal, SplashOverlay, PaytableScreen, LobbyScreen,
        buildStrip, SYMBOL_PX, SYMBOL_ORDER, LogoBadge } = window;
const SYM = window.SYMBOLS;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "theme": "emerald",
  "outcome": "random",
  "bonus_enabled": true,
  "reel_speed": 1.0,
  "frame": "iphone"
}/*EDITMODE-END*/;

const THEMES = {
  emerald: {
    name: 'Emerald',
    bg1: '#0e3a22', bg2: '#14502e', bg3: '#197a3e',
    btnA: '#1a6e3e', btnB: '#0d4827',
    accent: '#f4c243',
  },
  noir: {
    name: 'Noir Gold',
    bg1: '#0a0a14', bg2: '#1a1a2e', bg3: '#2a2a4a',
    btnA: '#3a3a5e', btnB: '#1a1a2e',
    accent: '#f4c243',
  },
  ruby: {
    name: 'Ruby',
    bg1: '#3a0a10', bg2: '#6a1424', bg3: '#8a1f2e',
    btnA: '#7a2a3a', btnB: '#4a0a14',
    accent: '#f4c243',
  },
};

// Convert a server spin result into the {stripIndex, highlight} board the Reel
// component renders. The server is the source of truth — this is pure mapping.
function boardFromServerResult(result) {
  const rows = [[], [], [], [], []];
  for (const w of (result.wins || [])) {
    for (const { reel, row } of (w.rows || [])) {
      if (!rows[reel].includes(row)) rows[reel].push(row);
    }
  }
  return result.reelStops.map((stripIndex, i) => ({ stripIndex, highlight: rows[i] }));
}

// Classify a spin's "win type" for the UI overlay variants.
function classifyWin(result) {
  if (result.bonus && result.bonus.triggered) return 'bonus-trigger';
  if (result.totalWin <= 0) return null;
  if (result.totalWin >= result.stake * 20) return 'big';
  return 'normal';
}

function SlotGame({ tweaks, setTweak, inFrame }) {
  const theme = THEMES[tweaks.theme] || THEMES.emerald;
  const [balance, setBalance] = useState(0);
  const [stake, setStake] = useState(1.00);
  const [spinning, setSpinning] = useState(false);
  const [showWin, setShowWin] = useState(null); // amount or null
  const [winType, setWinType] = useState(null); // 'normal' | 'big' | 'bonus-trigger'
  const [board, setBoard] = useState(null); // current displayed board
  const [screen, setScreen] = useState('splash'); // 'splash' | 'main'
  const [overlay, setOverlay] = useState(null); // 'paytable' | 'lobby' | null
  const [modal, setModal] = useState(null); // 'stake' | 'auto' | 'menu' | null
  const [sound, setSound] = useState(true);
  const [turbo, setTurbo] = useState(false);
  const [splashSlide, setSplashSlide] = useState(0);
  const [autoCount, setAutoCount] = useState(25);
  const [autoRemaining, setAutoRemaining] = useState(0);
  const [freeSpinsRemaining, setFreeSpinsRemaining] = useState(0);
  const [freeSpinsBank, setFreeSpinsBank] = useState(0);
  const [spinNonce, setSpinNonce] = useState(0);
  const [inFreeSpins, setInFreeSpins] = useState(false);
  const [serverConfig, setServerConfig] = useState(null);
  const [errorMsg, setErrorMsg] = useState(null);

  // Reels come from the server (sent on /init); fall back to local buildStrip
  // for the very first paint before init resolves.
  const reels = useMemo(() => {
    if (!serverConfig) return Array.from({ length: 5 }, (_, i) => buildStrip(i + 1));
    const base = serverConfig.reels.base;
    return [base[1], base[2], base[3], base[4], base[5]];
  }, [serverConfig]);

  // Fetch config + balance + restore active bonus session on mount.
  useEffect(() => {
    let cancelled = false;
    (async () => {
      // client.js may not have run yet under exotic script-ordering — poll briefly.
      for (let i = 0; i < 40 && !window.GameAPI && !cancelled; i++) {
        await new Promise(r => setTimeout(r, 50));
      }
      if (cancelled) return;
      if (!window.GameAPI) { setErrorMsg('Client script failed to load — refresh'); return; }
      try {
        const cfg = await window.GameAPI.ready;
        if (cancelled) return;
        setServerConfig(cfg);
        setBalance(cfg.player?.balance ?? 0);
        const resume = await window.GameAPI.resume();
        if (cancelled) return;
        if (resume.activeBonus) {
          setInFreeSpins(true);
          setFreeSpinsRemaining(resume.activeBonus.spinsRemaining);
          setFreeSpinsBank(resume.activeBonus.accumulatedWin);
          setStake(resume.activeBonus.baseStake);
        }
      } catch (err) {
        if (!cancelled) setErrorMsg(`Connect to server failed — ${err.message || 'refresh'}`);
      }
    })();
    return () => { cancelled = true; };
  }, []);

  // Initialize board placeholder (random middle row) while waiting for first spin.
  useEffect(() => {
    if (!board && reels.length) {
      setBoard(reels.map(s => ({ stripIndex: Math.floor(Math.random() * s.length), highlight: [] })));
    }
  }, [board, reels]);

  // Spin animation timing.
  const baseDuration = 1600;
  const speedMul = tweaks.reel_speed || 1.0;
  const turboMul = turbo ? 0.4 : 1.0;
  const spinDur = baseDuration / speedMul * turboMul;

  // Server-driven spin. Animation runs in parallel with the network round-trip;
  // we await both before settling. All outcome decisions come from the server.
  async function spin() {
    if (spinning) return;
    if (!serverConfig) return;
    const inBonus = inFreeSpins && freeSpinsRemaining > 0;
    if (!inBonus && balance < stake) return;

    setShowWin(null);
    setSpinning(true);
    setSpinNonce(n => n + 1);
    setErrorMsg(null);

    // Optimistic balance debit for base spins so the UI feels snappy;
    // server response will reconcile.
    const previousBalance = balance;
    if (!inBonus) setBalance(b => b - stake);

    const animationDelay = 4 * 120 + spinDur + 100;
    const start = Date.now();
    let result;
    try {
      result = inBonus
        ? await window.GameAPI.bonusSpin()
        : await window.GameAPI.spin(stake);
    } catch (err) {
      // Roll back optimistic debit; restore previous board.
      setBalance(previousBalance);
      setSpinning(false);
      setErrorMsg(err.message || 'spin failed');
      return;
    }

    const elapsed = Date.now() - start;
    const remaining = Math.max(0, animationDelay - elapsed);

    setTimeout(() => {
      setSpinning(false);
      setBoard(boardFromServerResult(result));
      setBalance(result.balance);

      const winType = classifyWin(result);
      setWinType(winType);

      if (result.totalWin > 0) {
        setShowWin(result.totalWin);
        if (inBonus) setFreeSpinsBank(b => b + result.totalWin);
      }

      // Drive bonus state from the server response.
      if (result.bonus && result.bonus.triggered && !inBonus) {
        // Base game triggered Free Spins.
        setTimeout(() => {
          setInFreeSpins(true);
          setFreeSpinsRemaining(result.bonus.spinsAwarded);
          setFreeSpinsBank(0);
        }, 1400);
      } else if (inBonus && result.bonus) {
        setFreeSpinsRemaining(result.bonus.spinsRemaining);
        if (result.bonus.exhausted) {
          setTimeout(() => setInFreeSpins(false), 2000);
        }
      }

      if (result.totalWin > 0) {
        setTimeout(() => setShowWin(null), winType === 'big' ? 3000 : 2000);
      }
    }, remaining);
  }

  // Autoplay
  useEffect(() => {
    if (autoRemaining > 0 && !spinning && screen === 'main') {
      const t = setTimeout(() => {
        spin();
        setAutoRemaining(n => n - 1);
      }, 600);
      return () => clearTimeout(t);
    }
  }, [autoRemaining, spinning, screen]);

  // Auto-spin during free spins
  useEffect(() => {
    if (freeSpinsRemaining > 0 && !spinning && screen === 'main' && inFreeSpins) {
      const t = setTimeout(() => spin(), 1200);
      return () => clearTimeout(t);
    }
  }, [freeSpinsRemaining, spinning, screen, inFreeSpins]);

  // ── Render ──
  const bgStyle = {
    background: `linear-gradient(180deg, ${theme.bg1} 0%, ${theme.bg2} 40%, ${theme.bg3} 100%)`,
  };

  // Reels area
  const reelsBlock = (
    <div style={{
      position: 'relative',
      margin: '0 6px',
      padding: 3,
      borderRadius: 6,
      background: 'linear-gradient(180deg, #d0c89a 0%, #8a6a26 50%, #d0c89a 100%)',
      boxShadow: '0 4px 14px rgba(0,0,0,0.4)',
    }}>
      <div style={{
        display: 'flex',
        gap: 0,
        overflow: 'hidden',
        borderRadius: 3,
        position: 'relative',
        background: '#1a1a1a',
      }}>
        {reels.map((strip, i) => (
          <Reel
            key={`${i}-${spinNonce}`}
            strip={strip}
            finalIndex={board ? board[i].stripIndex : 0}
            spinning={spinning}
            delay={i * 120}
            duration={spinDur}
            highlightRows={!spinning && board ? board[i].highlight : []}
          />
        ))}
        {/* Win line overlay */}
        {!spinning && winType === 'normal' && board && board.every(r => r.highlight.includes(1)) && (
          <div style={{
            position: 'absolute',
            top: '50%',
            left: 0, right: 0,
            height: 2.5,
            background: 'linear-gradient(90deg, transparent 0%, #f4c243 10%, #fff8c0 50%, #f4c243 90%, transparent 100%)',
            transform: 'translateY(-50%)',
            zIndex: 4,
            filter: 'drop-shadow(0 0 6px rgba(255,200,60,0.8))',
            animation: 'goldPulse 0.8s ease-in-out infinite',
          }}/>
        )}
        {/* Win number overlay (smaller, normal wins) */}
        {showWin !== null && winType === 'normal' && (
          <WinNumber amount={showWin}/>
        )}
      </div>
    </div>
  );

  // Header (balance + sound + menu).
  const header = (
    <div style={{
      display: 'flex', alignItems: 'center', justifyContent: 'space-between',
      padding: '14px 16px 6px',
    }}>
      <div>
        <div style={{ fontSize: 11, color: theme.accent, fontWeight: 700, letterSpacing: 1.5 }}>BALANCE</div>
        <div style={{ fontSize: 20, color: '#fff', fontWeight: 800, fontFamily: 'Inter', letterSpacing: 0.3 }}>
          {balance.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
        </div>
      </div>
      <div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
        <button onClick={() => setSound(s => !s)} style={{
          background: 'transparent', border: 'none', padding: 6, cursor: 'pointer',
        }}>{sound ? <Icons.speakerOn/> : <Icons.speakerOff/>}</button>
        <button onClick={() => setModal('menu')} style={{
          background: 'transparent', border: 'none', padding: 6, cursor: 'pointer',
        }}><Icons.menu/></button>
      </div>
    </div>
  );

  // Logo + paytable strip row.
  const logoRow = (
    <div style={{
      display: 'flex', alignItems: 'center', gap: 4,
      padding: '0 6px',
      marginBottom: 4,
    }}>
      <div style={{ width: 92, height: 60, flexShrink: 0, marginLeft: -8 }}>
        <LogoBadge/>
      </div>
      <PaytableStrip onClick={() => setOverlay('paytable')}/>
    </div>
  );

  // Bottom controls.
  const bottomControls = (
    <div style={{
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      gap: 14, padding: '20px 16px 8px',
      position: 'relative',
    }}>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 10, width: 90 }}>
        <button onClick={() => setModal('stake')} disabled={spinning || autoRemaining > 0} style={{
          padding: '8px 10px',
          borderRadius: 999,
          border: 'none',
          background: 'linear-gradient(180deg, #1a6e3e 0%, #0d4827 100%)',
          color: '#fff', cursor: spinning ? 'default' : 'pointer',
          opacity: spinning || autoRemaining > 0 ? 0.5 : 1,
          boxShadow: '0 2px 0 rgba(0,0,0,0.4), 0 0 0 1.5px rgba(255,220,140,0.25), inset 0 1px 0 rgba(255,255,255,0.15)',
          fontFamily: 'Inter',
          lineHeight: 1.1,
        }}>
          <div style={{ fontSize: 9, color: theme.accent, letterSpacing: 1.5, fontWeight: 700 }}>STAKE</div>
          <div style={{ fontSize: 14, color: '#fff', fontWeight: 800 }}>{stake.toFixed(2)}</div>
        </button>
        <button onClick={() => autoRemaining > 0 ? setAutoRemaining(0) : setModal('auto')}
          disabled={spinning && autoRemaining === 0}
          style={{
            padding: '10px 10px',
            borderRadius: 999,
            border: 'none',
            background: autoRemaining > 0
              ? 'linear-gradient(180deg, #e21f2c 0%, #7a0a14 100%)'
              : 'linear-gradient(180deg, #1a6e3e 0%, #0d4827 100%)',
            color: '#fff',
            cursor: 'pointer',
            opacity: spinning && autoRemaining === 0 ? 0.5 : 1,
            boxShadow: '0 2px 0 rgba(0,0,0,0.4), 0 0 0 1.5px rgba(255,220,140,0.25), inset 0 1px 0 rgba(255,255,255,0.15)',
            fontFamily: 'Inter',
            fontSize: 10, fontWeight: 800, letterSpacing: 1.5,
        }}>
          {autoRemaining > 0 ? `STOP (${autoRemaining})` : 'AUTO'}
        </button>
      </div>
      <SpinDisc onClick={spin} spinning={spinning} freeSpinsLeft={freeSpinsRemaining}/>
      <div style={{ width: 90 }}>
        <button onClick={() => setTurbo(t => !t)} style={{
          padding: '12px 10px', width: '100%',
          borderRadius: 999, border: 'none',
          background: turbo
            ? 'linear-gradient(180deg, #f0c040 0%, #c98a1a 100%)'
            : 'linear-gradient(180deg, #1a6e3e 0%, #0d4827 100%)',
          color: turbo ? '#3a2410' : '#fff',
          cursor: 'pointer',
          boxShadow: '0 2px 0 rgba(0,0,0,0.4), 0 0 0 1.5px rgba(255,220,140,0.25), inset 0 1px 0 rgba(255,255,255,0.15)',
          fontFamily: 'Inter',
          fontSize: 11, fontWeight: 800, letterSpacing: 1.5,
          display: 'flex', alignItems: 'center', gap: 5, justifyContent: 'center',
        }}>
          <Icons.turbo/> TURBO
        </button>
      </div>
    </div>
  );

  // Footer: PAYS | HELP | 777 STRIKE | ID.
  const footer = (
    <div style={{
      display: 'flex', alignItems: 'center', gap: 8,
      padding: '6px 18px 10px',
      fontFamily: 'Inter', fontSize: 11, color: '#fff', opacity: 0.7,
    }}>
      <span onClick={() => setOverlay('paytable')} style={{ textDecoration: 'underline', cursor: 'pointer', fontWeight: 700 }}>PAYS</span>
      <span style={{ opacity: 0.4 }}>|</span>
      <span style={{ textDecoration: 'underline', fontWeight: 700, cursor: 'pointer' }}>HELP</span>
      <span style={{ opacity: 0.4 }}>|</span>
      <span style={{ fontWeight: 700 }}>777 STRIKE</span>
      <span style={{ opacity: 0.4 }}>|</span>
      <span>ID: 97973988</span>
    </div>
  );

  // Bottom nav.
  const bottomNav = (
    <div style={{
      display: 'flex',
      background: '#000',
      padding: '10px 0 14px',
    }}>
      {[
        { icon: 'home', label: 'Red Tiger' },
        { icon: 'dice', label: 'Games' },
        { icon: 'news', label: 'Posts' },
        { icon: 'menuBottom', label: 'Menu' },
      ].map((it, i) => {
        const Icon = Icons[it.icon];
        return (
          <button key={i} onClick={() => it.label === 'Games' ? setOverlay('lobby') : null} style={{
            flex: 1, background: 'transparent', border: 'none', cursor: 'pointer',
            display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 4,
            padding: '4px 0',
          }}>
            <Icon/>
            <span style={{ color: '#aaa', fontSize: 10, fontFamily: 'Inter' }}>{it.label}</span>
          </button>
        );
      })}
    </div>
  );

  // Free spins HUD strip.
  const freeSpinsHud = inFreeSpins ? (
    <div style={{
      position: 'absolute', top: 60, left: 0, right: 0,
      display: 'flex', alignItems: 'center', justifyContent: 'space-around',
      padding: '8px 16px',
      background: 'linear-gradient(90deg, rgba(244,194,67,0.2) 0%, rgba(244,194,67,0.4) 50%, rgba(244,194,67,0.2) 100%)',
      borderTop: '1px solid rgba(255,220,140,0.5)',
      borderBottom: '1px solid rgba(255,220,140,0.5)',
      zIndex: 5,
    }}>
      <div style={{ textAlign: 'center', flex: 1 }}>
        <div style={{ fontSize: 9, color: theme.accent, letterSpacing: 1.5, fontWeight: 700 }}>FREE SPINS</div>
        <div style={{ fontSize: 22, color: '#fff', fontWeight: 900, fontFamily: 'Bowlby One' }}>{freeSpinsRemaining}</div>
      </div>
      <div style={{ width: 1, height: 30, background: 'rgba(255,220,140,0.4)' }}/>
      <div style={{ textAlign: 'center', flex: 1 }}>
        <div style={{ fontSize: 9, color: theme.accent, letterSpacing: 1.5, fontWeight: 700 }}>TOTAL WIN</div>
        <div style={{ fontSize: 22, color: '#fff', fontWeight: 900, fontFamily: 'Bowlby One' }}>{freeSpinsBank.toFixed(2)}</div>
      </div>
    </div>
  ) : null;

  // Big win overlay (full takeover).
  const bigWinOverlay = winType === 'big' && showWin !== null ? (
    <div style={{
      position: 'absolute', inset: 0, zIndex: 92,
      background: 'radial-gradient(ellipse at center, rgba(20,80,46,0.9) 0%, rgba(0,0,0,0.95) 100%)',
      display: 'flex', flexDirection: 'column',
      alignItems: 'center', justifyContent: 'center',
      pointerEvents: 'none',
    }}>
      <CoinRain active={true}/>
      <div style={{
        fontFamily: 'Cinzel, serif',
        fontSize: 38, fontWeight: 900, fontStyle: 'italic',
        background: 'linear-gradient(180deg, #fff4a8 0%, #f0c040 50%, #c98a1a 100%)',
        WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent',
        backgroundClip: 'text',
        filter: 'drop-shadow(0 3px 0 rgba(90,58,10,0.7))',
        letterSpacing: 5,
        marginBottom: 8,
        animation: 'bigWinPop 0.6s cubic-bezier(0.34, 1.56, 0.64, 1)',
      }}>BIG WIN</div>
      <div style={{
        fontFamily: "'Bowlby One', sans-serif",
        fontSize: 92, fontWeight: 900,
        background: 'linear-gradient(180deg, #fff4a8 0%, #f0c040 30%, #c98a1a 55%, #fff3c0 75%, #c98a1a 100%)',
        WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent',
        backgroundClip: 'text',
        filter: 'drop-shadow(0 6px 0 rgba(90,58,10,0.85)) drop-shadow(0 0 30px rgba(255,200,60,0.7))',
        letterSpacing: 3,
        animation: 'bigWinPop 0.7s cubic-bezier(0.34, 1.56, 0.64, 1)',
      }}>{showWin.toFixed(2)}</div>
      <div style={{
        marginTop: 12, fontSize: 12, color: '#fff', opacity: 0.85,
        letterSpacing: 4, fontWeight: 600,
      }}>{(showWin / stake).toFixed(0)}x YOUR STAKE</div>
    </div>
  ) : null;

  // Bonus trigger overlay.
  const bonusTriggerOverlay = winType === 'bonus-trigger' && !spinning && !inFreeSpins ? (
    <div style={{
      position: 'absolute', inset: 0, zIndex: 92,
      background: 'radial-gradient(ellipse at center, rgba(20,80,46,0.92) 0%, rgba(0,20,10,0.97) 100%)',
      display: 'flex', flexDirection: 'column',
      alignItems: 'center', justifyContent: 'center',
      animation: 'bigWinPop 0.6s ease-out',
    }}>
      <CoinRain active={true}/>
      <div style={{ display: 'flex', gap: 12, marginBottom: 24, filter: 'drop-shadow(0 6px 12px rgba(0,0,0,0.5))' }}>
        {[0,1,2].map(i => (
          <div key={i} style={{ width: 80, height: 80, animation: `bigWinPop 0.5s ease-out ${i * 0.12}s both` }}>
            <SYM.FS.render/>
          </div>
        ))}
      </div>
      <div style={{
        fontFamily: 'Cinzel, serif',
        fontSize: 32, fontWeight: 900, fontStyle: 'italic',
        background: 'linear-gradient(180deg, #fff4a8 0%, #f0c040 50%, #c98a1a 100%)',
        WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent',
        backgroundClip: 'text',
        filter: 'drop-shadow(0 3px 0 rgba(90,58,10,0.7))',
        letterSpacing: 3, marginBottom: 8,
      }}>10 FREE SPINS</div>
      <div style={{ fontSize: 13, color: '#fff', opacity: 0.85, marginBottom: 20, letterSpacing: 2 }}>
        AWARDED · GET READY!
      </div>
      <button onClick={() => { setWinType(null); }} style={{
        padding: '12px 40px', borderRadius: 999, border: 'none', cursor: 'pointer',
        background: 'linear-gradient(180deg, #fff4a8 0%, #f0c040 50%, #c98a1a 100%)',
        color: '#3a2410', fontWeight: 900, fontSize: 14, letterSpacing: 3,
        boxShadow: '0 4px 0 rgba(0,0,0,0.5)',
      }}>START</button>
    </div>
  ) : null;

  return (
    <div className="noselect" style={{
      width: '100%', height: '100%',
      position: 'relative', overflow: 'hidden',
      ...bgStyle,
      color: '#fff',
      display: 'flex', flexDirection: 'column',
      paddingTop: inFrame ? 50 : 8, // make room for status bar in iPhone frame
      boxSizing: 'border-box',
    }}>
      <CrownBgPattern opacity={0.05}/>
      <div style={{ position: 'relative', zIndex: 1 }}>{header}</div>
      {/* spacer above logo+reels */}
      <div style={{ flex: 0.6, position: 'relative', zIndex: 1 }}/>
      <div style={{ position: 'relative', zIndex: 1 }}>{logoRow}</div>
      <div style={{ position: 'relative', zIndex: 1, marginTop: 4 }}>{reelsBlock}</div>
      {freeSpinsHud}
      {/* spacer below reels */}
      <div style={{ flex: 1, position: 'relative', zIndex: 1 }}/>
      <div style={{ position: 'relative', zIndex: 1 }}>{bottomControls}</div>
      <div style={{ position: 'relative', zIndex: 1 }}>{footer}</div>
      <div style={{ position: 'relative', zIndex: 1 }}>{bottomNav}</div>
      
      {screen === 'splash' && (
        <SplashOverlay
          slide={splashSlide}
          onPrev={() => setSplashSlide(s => (s - 1 + 3) % 3)}
          onNext={() => setSplashSlide(s => (s + 1) % 3)}
          onPlay={() => setScreen('main')}
        />
      )}
      {overlay === 'paytable' && <PaytableScreen onClose={() => setOverlay(null)}/>}
      {overlay === 'lobby' && <LobbyScreen onClose={() => setOverlay(null)}/>}
      <SidebarMenu
        open={modal === 'menu'}
        onClose={() => setModal(null)}
        onNav={(t) => { setModal(null); setOverlay(t); }}
        sound={sound}
        onSound={() => setSound(s => !s)}
      />
      <StakeModal
        open={modal === 'stake'}
        value={stake}
        onChange={setStake}
        onClose={() => setModal(null)}
      />
      <AutoplayModal
        open={modal === 'auto'}
        value={autoCount}
        onChange={setAutoCount}
        onStart={() => { setAutoRemaining(autoCount); setModal(null); }}
        onClose={() => setModal(null)}
      />
      {bigWinOverlay}
      {bonusTriggerOverlay}
      {errorMsg && (
        <div style={{
          position: 'absolute', top: 16, left: 16, right: 16, zIndex: 200,
          background: 'rgba(180,20,20,0.95)', color: '#fff',
          padding: '10px 14px', borderRadius: 8,
          fontFamily: 'Inter', fontSize: 12, fontWeight: 600,
          textAlign: 'center',
          boxShadow: '0 4px 12px rgba(0,0,0,0.5)',
        }}>{errorMsg}</div>
      )}
    </div>
  );
}

// Tweaks panel.
function MyTweaks({ tweaks, setTweak }) {
  const { TweaksPanel, TweakSection, TweakRadio, TweakSelect, TweakToggle, TweakSlider } = window;
  return (
    <TweaksPanel title="Tweaks">
      <TweakSection label="Theme">
        <TweakSelect
          label="Color scheme"
          value={tweaks.theme}
          options={[
            { label: 'Emerald (default)', value: 'emerald' },
            { label: 'Noir Gold', value: 'noir' },
            { label: 'Ruby', value: 'ruby' },
          ]}
          onChange={(v) => setTweak('theme', v)}
        />
        <TweakRadio
          label="Device frame"
          value={tweaks.frame}
          options={[
            { label: 'iPhone', value: 'iphone' },
            { label: 'Off', value: 'off' },
          ]}
          onChange={(v) => setTweak('frame', v)}
        />
      </TweakSection>
      <TweakSection label="Game state">
        <TweakSelect
          label="Force outcome"
          value={tweaks.outcome}
          options={[
            { label: 'Random (realistic)', value: 'random' },
            { label: 'Force loss', value: 'loss' },
            { label: 'Force small win', value: 'win' },
            { label: 'Force BIG WIN', value: 'bigwin' },
            { label: 'Force Free Spins', value: 'bonus' },
          ]}
          onChange={(v) => setTweak('outcome', v)}
        />
        <TweakToggle
          label="Bonus enabled"
          value={tweaks.bonus_enabled}
          onChange={(v) => setTweak('bonus_enabled', v)}
        />
      </TweakSection>
      <TweakSection label="Reels">
        <TweakSlider
          label="Reel speed"
          value={tweaks.reel_speed}
          min={0.3} max={3.0} step={0.1} unit="x"
          onChange={(v) => setTweak('reel_speed', v)}
        />
      </TweakSection>
    </TweaksPanel>
  );
}

function App() {
  const { useTweaks } = window;
  const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const useFrame = tweaks.frame === 'iphone';

  const game = (
    <SlotGame tweaks={tweaks} setTweak={setTweak} inFrame={useFrame}/>
  );

  return (
    <div style={{
      width: '100vw', minHeight: '100vh',
      background: '#0a0a0e',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      padding: '20px 0',
      boxSizing: 'border-box',
    }}>
      {useFrame ? (
        <div style={{ position: 'relative', filter: 'drop-shadow(0 20px 50px rgba(0,0,0,0.7))' }}>
          <IOSDevice width={402} height={874} dark={true}>
            <div style={{ height: '100%', width: '100%', overflow: 'hidden' }}>
              {game}
            </div>
          </IOSDevice>
        </div>
      ) : (
        <div style={{
          width: 402, height: 874,
          borderRadius: 24, overflow: 'hidden',
          boxShadow: '0 20px 50px rgba(0,0,0,0.7)',
          position: 'relative',
        }}>
          {game}
        </div>
      )}
      <MyTweaks tweaks={tweaks} setTweak={setTweak}/>
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App/>);
