// game-flip.jsx — coin flip · 99% RTP / 1% house edge
// True 50/50 coin with a 1% commission baked into the win payout (1.98× return
// instead of 2×). Net effect: RTP = 0.5 * 1.98 = 99%.

const { useState: useStateF, useEffect: useEffectF, useRef: useRefF } = React;

function FlipPage({ palette: M, wallet, fairness, onConnect, heroVariant }) {
  const [pick, setPick] = useStateF('H');
  const { bet, setBet, betStr, setBetStr } = useBetInput(0.1);
  const [spinning, setSpinning] = useStateF(false);
  const [result, setResult] = useStateF(null);
  const [history, setHistory] = useStateF([]);
  const [displaySide, setDisplaySide] = useStateF('H'); // visual side shown when idle/post-spin
  const [error, setError] = useBetError();
  // Synchronous re-entry lock — closes the race where two clicks fire before
  // setSpinning(true) commits (audit HIGH-9). React state lags one render
  // behind any setter; a ref is read+written immediately on the call stack.
  const busyRef = useRefF(false);

  const WIN_PROB = 0.50;
  const MIN_BET = coinMinBet(wallet.activeCoin);
  // Per-coin max (30 SOL ≈ 5052.6 USDC/USDT).
  const MAX_BET_SOL = 30;
  const maxBet = maxBetForCoin(MAX_BET_SOL, wallet.activeCoin);

  // Clamp the bet into [MIN_BET, maxBet] when switching coins so USDC/USDT
  // (1-unit floor) don't leave the 0.1 SOL default below the minimum.
  useEffectF(() => {
    if (bet < MIN_BET) setBet(MIN_BET);
    else if (bet > maxBet) setBet(maxBet);
  }, [wallet.activeCoin]); // eslint-disable-line

  async function flip() {
    if (busyRef.current) return;
    if (spinning) return;
    if (!wallet.canPlay) { onConnect(); return; }
    const bag = wallet.balance[wallet.activeCoin] || 0;
    if (bet < MIN_BET) {
      setError(`minimum bet is ${MIN_BET} ${wallet.activeCoin}`);
      return;
    }
    if (bet > maxBet) {
      setError(`max bet on coin flip is ${maxBet.toFixed(2)} ${wallet.activeCoin}`);
      return;
    }
    if (bet > bag) {
      setError(`bet ${bet.toFixed(3)} ${wallet.activeCoin} exceeds bag (${bag.toFixed(3)} ${wallet.activeCoin} available)`);
      return;
    }
    busyRef.current = true;
    setError(null);
    setResult(null);
    if (!wallet.debit(wallet.activeCoin, bet)) {
      busyRef.current = false;
      setError(`bag changed under the bet -- try again`);
      return;
    }

    // §5.62: flip is server-resolved. The Worker owns RNG + outcome and
    // writes /plays as admin. Browser sends bet/pick/clientSeed; gets back
    // {landed, userWins, payout, hex, nonce, serverHash}. The old browser
    // fairness.roll('flip') + hashToFloats path is gone for this game.
    let server;
    try {
      server = await callServerGame('/play/flip', {
        bet,
        pick: pick === 'H' ? 'heads' : 'tails',
        clientSeed: fairness.clientSeed,
        coin: wallet.activeCoin,
        user: wallet.username,
      });
    } catch (err) {
      // Round didn't happen — refund the bet and surface the error.
      wallet.credit(wallet.activeCoin, bet, 'flip-refund-' + Date.now());
      busyRef.current = false;
      setError('server error: ' + (err && err.message ? err.message : String(err)));
      return;
    }

    const userWins  = !!server.userWins;
    const landed    = server.landed === 'heads' ? 'H' : 'T';
    const payout    = Number(server.payout) || 0;
    const serverHex = String(server.hex || '');
    const nonce     = Number(server.nonce) || 0;

    // Set the visual target side FIRST so the spin animation chosen below uses
    // the correct keyframes (coinFlipH lands heads-up; coinFlipT lands tails-up).
    // Then trigger the spin. Both updates batch into one React 18 render so the
    // prior idle frame doesn't flicker the wrong face.
    setDisplaySide(landed);
    setSpinning(true);
    playHitSound();

    await new Promise(res => setTimeout(res, 2400));
    if (userWins) wallet.credit(wallet.activeCoin, payout, serverHex);
    wallet.logPlay({ game:'Flip', side: landed, pick, bet, payout, win: userWins, coin: wallet.activeCoin, hex: serverHex.slice(0,16), nonce });
    // NOTE: no logPlayServer call here — the Worker wrote /plays as admin
    // (Firebase rules step3 reject browser writes for game === 'Flip').

    const out = { win: userWins, side: landed, mult: 1.98, payout, hex: serverHex.slice(0,16), nonce };
    setResult(out);
    setHistory(h => [{ side:landed, win:userWins }, ...h].slice(0,28));
    setSpinning(false);
    busyRef.current = false;
    playOutcomeSound(userWins);
  }

  return (
    <div className="rc-page" style={{flex:1, padding:'18px 26px 40px', overflowY:'auto'}}>
      <div style={{display:'flex', alignItems:'baseline', gap:12, marginBottom:6}}>
        <h1 className="rc-h-page" style={{fontSize:48, color:M.yellow, margin:'4px 0', textShadow:`3px 3px 0 ${M.hot}`}}>COIN FLIP</h1>
        <span style={{color:M.ink2, fontSize:14}}>~/games/flip/</span>
      </div>
      <div className="rc-h-sub" style={{color:M.ink2, fontSize:14, marginBottom:14}}>
        <span style={{color:M.green}}>99% RTP</span> · <span style={{color:M.hot}}>1% edge</span> · 50/50 odds · 1.98× payout · max bet {maxBet.toFixed(2)} {wallet.activeCoin} (max win {(maxBet*1.98).toFixed(2)})
      </div>

      <div className="rc-flip-playmode">
        <PlayModeBar palette={M} wallet={wallet} onConnect={onConnect}/>
      </div>
      <BetErrorToast error={error} onClose={()=>setError(null)} palette={M}/>

      <div className="rc-game-grid" style={{display:'grid', gridTemplateColumns:'320px 1fr', gap:18, alignItems:'start'}}>
        {/* Bet panel */}
        <Frame title="bet.cfg" accent={M.green}>
          {/* Greyout the entire bet panel while spinning — side, bet input,
              quick buttons, ½/2×/max, payout, and RUN IT all locked until
              the coin resolves so the player can't change anything mid-flip. */}
          <div className="rc-bet-panel rc-flip-bet" style={{opacity: spinning ? 0.4 : 1, pointerEvents: spinning ? 'none' : 'auto', transition: 'opacity 0.15s'}}>
            <div style={{fontSize:13, color:M.ink2, marginBottom:4}}>side</div>
            <div style={{display:'flex', gap:6, marginBottom:14}}>
              {['H','T'].map(s => (
                <button key={s} onClick={()=>{ setPick(s); setDisplaySide(s); }} disabled={spinning} style={{
                  flex:1, background:pick===s?M.yellow:M.bg, color:pick===s?M.bg:M.yellow,
                  border:`2px solid ${M.yellow}`, fontFamily:'inherit', fontSize:18, padding:'10px 0',
                  cursor: spinning?'not-allowed':'pointer',
                  fontWeight:700, boxShadow: pick===s ? `4px 4px 0 ${M.bg}` : 'none', letterSpacing:'0.05em',
                }}>{s==='H'?'HEADS':'TAILS'}</button>
              ))}
            </div>

            <div className="rc-bet-inputs">
              <div style={{fontSize:13, color:M.ink2, marginBottom:4}}>bet ({wallet.activeCoin})</div>
              <input type="number" step={0.01} min={0} value={betStr} onChange={e=>setBetStr(e.target.value)} disabled={spinning} style={{
                width:'100%', background:M.bg, border:`2px solid ${M.green}`, color:M.green, padding:'8px 10px',
                fontFamily:'inherit', fontSize:18, outline:'none',
              }}/>
              <div style={{marginTop:6}}>
                <QuickBetButtons current={bet} onPick={v=>setBet(v)} palette={M} disabled={spinning}/>
              </div>
              <div style={{display:'flex', gap:4, marginTop:6, marginBottom:14}}>
                <button onClick={()=>setBet(b=>Math.max(MIN_BET, +(b/2).toFixed(6)))} disabled={spinning} style={btnSecondary(M)}>½</button>
                <button onClick={()=>setBet(b=>+Math.min(b*2, maxBet).toFixed(6))} disabled={spinning} style={btnSecondary(M)}>2×</button>
                <button onClick={()=>setBet(+(Math.min(wallet.balance[wallet.activeCoin] || 0, maxBet)).toFixed(6))} disabled={spinning} title={`max bet: ${maxBet.toFixed(2)} ${wallet.activeCoin}`} style={btnSecondary(M)}>max</button>
              </div>

              <div style={{fontSize:13, color:M.ink2, marginBottom:4}}>potential payout</div>
              <div style={{fontSize:24, color:M.green, fontWeight:700, marginBottom:14}}>+{(bet*1).toFixed(3)} {wallet.activeCoin}</div>
            </div>

            <button className="rc-bet-action" onClick={flip} disabled={spinning} style={{
              width:'100%', background: spinning ? M.ink2 : M.hot, color:M.bg,
              border:`3px solid ${M.bg}`, fontFamily:'inherit', fontSize:20, fontWeight:700, padding:'12px 0',
              cursor: spinning?'wait':'pointer', boxShadow:`6px 6px 0 ${M.green}`, letterSpacing:'0.05em',
            }}>{spinning ? 'flipping…' : '► RUN IT'}</button>
          </div>
        </Frame>

        {/* Coin stage */}
        <div style={{display:'flex', flexDirection:'column', gap:18, minWidth:0}}>
          <Frame title="the coin" accent={M.yellow} style={{padding:'24px 20px'}}>
            <div className="rc-flip-stage" style={{display:'flex', flexDirection:'column', alignItems:'center', justifyContent:'center', minHeight:340, gap:18}}>
              {/* Coin stage — overflow:hidden so the flip arc/scale can never escape the yellow frame */}
              <div style={{
                height:300, width:'100%',
                display:'flex', alignItems:'center', justifyContent:'center',
                overflow:'hidden', position:'relative',
              }}>
                <Coin3D spinning={spinning} side={displaySide} M={M}/>
              </div>

              <div className="rc-resolve-slot-row" style={{minHeight:90, display:'flex', alignItems:'flex-start', justifyContent:'center', textAlign:'center'}}>
                {result && !spinning && (
                  <div>
                    <div style={{fontSize:32, color:result.win?M.green:M.red, fontWeight:700, lineHeight:1}}>
                      {result.win?'★ WINNER ★':'✗ REKT ✗'}
                    </div>
                    <div style={{fontSize:14, color:M.ink, marginTop:8}}>
                      landed <span style={{color:M.yellow, fontWeight:700}}>{result.side==='H'?'HEADS':'TAILS'}</span> ·{' '}
                      <span style={{color: result.win ? M.green : M.red, fontWeight:700}}>
                        {result.win ? `+${result.payout.toFixed(2)}` : `−${bet.toFixed(2)}`} {wallet.activeCoin}
                      </span>
                    </div>
                    <div style={{fontSize:11, color:M.ink2, marginTop:4}}>
                      hash: {result.hex} · nonce: {result.nonce}
                    </div>
                  </div>
                )}
                {!result && !spinning && (
                  <div className="rc-hide-mobile" style={{fontSize:14, color:M.ink2}}>{'>'} pick a side, set a bet, run it.</div>
                )}
                {spinning && (
                  <div style={{fontSize:16, color:M.yellow, animation:'flipBlink 0.8s infinite'}}>computing sha-256…</div>
                )}
              </div>
            </div>
          </Frame>

          {/* History strip */}
          <Frame title="last 28 flips" accent={M.cyan} className="rc-history rc-flip-history">
            <div style={{display:'flex', gap:5, flexWrap:'wrap'}}>
              {history.length === 0 && <span style={{color:M.ink2, fontSize:13}}>no flips yet</span>}
              {history.map((h,i)=>{
                // Color by SIDE — heads always green, tails always red.
                // Win = filled cell, loss = outlined cell, so the win/loss
                // distinction is still visible at a glance.
                const sideColor = h.side === 'H' ? M.green : M.red;
                return (
                  <div key={i} title={`${h.side==='H'?'heads':'tails'} · ${h.win?'win':'loss'}`} style={{
                    width:32, height:32, display:'flex', alignItems:'center', justifyContent:'center',
                    background: h.win ? sideColor : M.bg,
                    color: h.win ? M.bg : sideColor,
                    border: `2px solid ${sideColor}`,
                    fontWeight:700, fontSize:14,
                  }}>{h.side}</div>
                );
              })}
            </div>
          </Frame>
        </div>
      </div>

      <FlipStatsPanel palette={M} wallet={wallet}/>

      <HashPanel palette={M} fairness={fairness}/>

      <style>{`
        @keyframes flipBlink { 50% { opacity: 0.3; } }
        /* Pure-axis flip — only rotateX, no translation or scale.
           coinFlipH ends at 2160° (mod 360 = 0°) → heads forward.
           coinFlipT ends at 2340° (mod 360 = 180°) → tails forward. */
        @keyframes coinFlipH {
          from { transform: rotateX(0deg);    }
          to   { transform: rotateX(2160deg); }
        }
        @keyframes coinFlipT {
          from { transform: rotateX(0deg);    }
          to   { transform: rotateX(2340deg); }
        }
      `}</style>
    </div>
  );
}

function btnSecondary(M) {
  return { flex:1, background:M.bg, color:M.green, border:`1px solid ${M.green}`, fontFamily:'inherit', fontSize:14, padding:'4px 0', cursor:'pointer' };
}

// Flip stats — derived from wallet.history filtered to game==='Flip' and the
// user's active coin. Re-computed on every render of the flip page; cheap.
function FlipStatsPanel({ palette: M, wallet }) {
  const coin = wallet.activeCoin;
  const flips = (wallet.history || []).filter(h => h.game === 'Flip' && h.coin === coin);
  const total = flips.length;
  const wins  = flips.filter(f => f.win).length;
  const winRate = total ? (wins / total) * 100 : 0;
  const wagered = flips.reduce((s, f) => s + (f.bet || 0), 0);
  const netPL   = flips.reduce((s, f) => s + ((f.payout || 0) - (f.bet || 0)), 0);
  const bestWin = flips.reduce((m, f) => Math.max(m, (f.payout || 0) - (f.bet || 0)), 0);
  // Longest run of consecutive wins (order in history doesn't affect max-run-length).
  let longestStreak = 0, runLen = 0;
  for (const f of flips) {
    if (f.win) { runLen++; if (runLen > longestStreak) longestStreak = runLen; }
    else runLen = 0;
  }
  // Win/loss amounts use 2 decimals everywhere on this page.
  const dp = 2;
  const fmt = n => `${n>=0?'+':''}${n.toFixed(dp)} ${coin}`;
  const fmtAbs = n => `${n.toFixed(dp)} ${coin}`;
  const tile = (label, value, color) => (
    <div style={{textAlign:'center', padding:'4px 0'}}>
      <div style={{fontSize:11, color:M.ink2, textTransform:'uppercase', letterSpacing:'0.05em'}}>{label}</div>
      <div style={{fontSize:22, color, fontWeight:700, lineHeight:1.1, marginTop:4, fontFamily:'JetBrains Mono, monospace'}}>{value}</div>
    </div>
  );
  return (
    <Frame title="flip stats" accent={M.cyan} className="rc-flip-stats" style={{marginTop:18}}>
      {total === 0 ? (
        <div style={{color:M.ink2, fontSize:14, padding:'4px 0'}}>{'>'} no flips yet in {coin}. play a round.</div>
      ) : (
        <>
          <div className="rc-stat-6" style={{display:'grid', gridTemplateColumns:'repeat(6, 1fr)', gap:14}}>
            {tile('flips',          total,                                                M.green)}
            {tile('win rate',       `${winRate.toFixed(1)}%`,                             M.cyan)}
            {tile('total wagered',  fmtAbs(wagered),                                      M.yellow)}
            {tile('net P/L',        fmt(netPL),                                           netPL >= 0 ? M.green : M.red)}
            {tile('best win',       `+${bestWin.toFixed(dp)} ${coin}`,                    M.green)}
            {tile('longest win streak', `${longestStreak}`,                               M.hot)}
          </div>
          <LastRoundsTable M={M} rounds={flips} headers={['landed', 'pick', 'bet', 'payout', 'P/L']} renderRow={f => {
            const pl = (f.payout || 0) - (f.bet || 0);
            const plColor = pl > 0 ? M.green : pl === 0 ? M.yellow : M.red;
            const plText  = pl > 0 ? `+${pl.toFixed(dp)}` : pl === 0 ? '±0.00' : `−${Math.abs(pl).toFixed(dp)}`;
            return [
              { value: f.side === 'H' ? 'HEADS' : 'TAILS', color: f.side === 'H' ? M.yellow : M.cyan, fontWeight: 700 },
              { value: f.pick === 'H' ? 'H' : 'T',         color: M.ink2 },
              { value: (f.bet || 0).toFixed(dp),           color: M.ink },
              { value: f.payout > 0 ? (f.payout || 0).toFixed(dp) : '—', color: f.payout > 0 ? M.green : M.ink2 },
              { value: plText,                             color: plColor, fontWeight: 700 },
            ];
          }}/>
        </>
      )}
    </Frame>
  );
}

// ─── 3D coin with proper heads & tails faces ──────────────────────
function Coin3D({ spinning, side, M }) {
  // Spin: animate rotateX from 0 to 2160° (heads) or 2340° (tails).
  // Idle: hold the static face — no bounce, no shift.
  const spinAnim = side === 'T'
    ? 'coinFlipT 2.4s cubic-bezier(0.22, 0.61, 0.36, 1) forwards'
    : 'coinFlipH 2.4s cubic-bezier(0.22, 0.61, 0.36, 1) forwards';
  const idleTransform = side === 'T' ? 'rotateX(180deg)' : 'rotateX(0deg)';

  return (
    <div style={{
      perspective:'1200px',
      width:200, height:200,
      position:'relative',
      display:'flex', alignItems:'center', justifyContent:'center',
    }}>
      <div style={{
        position:'relative', width:200, height:200,
        transformStyle:'preserve-3d',
        // While spinning, the keyframe animation drives the transform.
        // While idle, hold the static face transform — no animation, no shift.
        transform: spinning ? undefined : idleTransform,
        animation: spinning ? spinAnim : 'none',
      }}>
        {/* Halo lives INSIDE the rotating container so it stays attached to
            the coin in 3D — tilts and foreshortens with it. Centered glow
            (no offset) so no directional dark border ever appears. The
            border-radius:50% keeps the box-shadow source circular even when
            3D-projected. translateZ(-1px) puts it just behind the faces so
            the SVG art stays in front. */}
        <div style={{
          position:'absolute', inset:0,
          borderRadius:'50%',
          boxShadow: `0 0 40px ${M.yellow}66, 0 0 80px ${M.yellow}33`,
          pointerEvents:'none',
          transform: 'translateZ(-1px)',
        }}/>
        {/* Heads face (front, 0deg) */}
        <CoinFaceHeads M={M}/>
        {/* Tails face (back, rotated 180 around X) */}
        <CoinFaceTails M={M}/>
      </div>
    </div>
  );
}

// Shared face — flat orange disc, thick black outline.
// Big HEADS / TAILS dominates the face; "RUN!" branding sits as a small accent.
// Centering: dominantBaseline="central" anchors at the visual mid-line, and we
// drop letterSpacing on multi-char text because text-anchor:middle does NOT
// compensate for trailing letterSpacing (would shift text right of center).
function CoinFaceShared({ side, M }) {
  const label = side === 'H' ? 'HEADS' : 'TAILS';
  return (
    <svg viewBox="0 0 200 200" width={200} height={200}
         preserveAspectRatio="xMidYMid meet"
         style={{display:'block'}}>
      {/* outer rim — same coin color as the face, so the rim reads as one
          continuous gold disc with black ridge separators on top. */}
      <circle cx="100" cy="100" r="96" fill="#f5a623"/>
      {/* coin-edge ridges — 60 thin black radial ticks dividing the gold
          rim into knurled segments. Reads as ridge texture during rotation
          and a stippled border face-on. */}
      {Array.from({length: 60}, (_, i) => {
        const angle = (i * 6) * Math.PI / 180;
        const r1 = 89, r2 = 95;
        const x1 = 100 + Math.cos(angle) * r1;
        const y1 = 100 + Math.sin(angle) * r1;
        const x2 = 100 + Math.cos(angle) * r2;
        const y2 = 100 + Math.sin(angle) * r2;
        return (
          <line key={i} x1={x1.toFixed(2)} y1={y1.toFixed(2)} x2={x2.toFixed(2)} y2={y2.toFixed(2)}
                stroke="#0a0a0a" strokeWidth="1.4"/>
        );
      })}
      {/* orange disc */}
      <circle cx="100" cy="100" r="88" fill="#f5a623"/>
      {/* thin black border ring — separates the ridge band from the inner
          face so the gold rim doesn't blur into the gold disc. */}
      <circle cx="100" cy="100" r="88" fill="none" stroke="#0a0a0a" strokeWidth="1.2"/>
      {/* inner black ring stroke */}
      <circle cx="100" cy="100" r="74" fill="none" stroke="#0a0a0a" strokeWidth="4"/>
      {/* inner orange face */}
      <circle cx="100" cy="100" r="70" fill="#f5a623"/>
      {/* RUN! branding small at top */}
      <text x="100" y="56" textAnchor="middle" dominantBaseline="central"
            fontFamily='"VT323", monospace' fontSize="22" fontWeight="700"
            fill="#0a0a0a" opacity="0.7">RUN!</text>
      {/* HEADS / TAILS dominant centered text */}
      <text x="100" y="104" textAnchor="middle" dominantBaseline="central"
            fontFamily='"VT323", monospace' fontSize="40" fontWeight="700"
            fill="#0a0a0a">{label}</text>
      {/* three stars at bottom — manually placed for true centering */}
      <g fill="#0a0a0a" opacity="0.55" fontFamily='"VT323", monospace' fontSize="16">
        <text x="80"  y="150" textAnchor="middle" dominantBaseline="central">★</text>
        <text x="100" y="150" textAnchor="middle" dominantBaseline="central">★</text>
        <text x="120" y="150" textAnchor="middle" dominantBaseline="central">★</text>
      </g>
    </svg>
  );
}

function CoinFaceHeads({ M }) {
  return (
    <div style={{position:'absolute', inset:0, transform:'translateZ(8px)', backfaceVisibility:'hidden'}}>
      <CoinFaceShared side="H" M={M}/>
    </div>
  );
}

function CoinFaceTails({ M }) {
  return (
    <div style={{position:'absolute', inset:0, transform:'rotateX(180deg) translateZ(8px)', backfaceVisibility:'hidden'}}>
      <CoinFaceShared side="T" M={M}/>
    </div>
  );
}

Object.assign(window, { FlipPage, Coin3D });
