// game-hilo.jsx — Higher / Lower card prediction · 99% RTP / 1% house edge
// One card is dealt face-up; player predicts whether the next card from a
// fresh i.i.d. infinite deck will rank higher or lower. Ties refund the
// stake. Per-rank multiplier scales inversely with win probability so the
// EV is held at 0.99 regardless of which card and side the player chose.
//
// §5.66 — server-resolved. The Worker owns both the current and the
// pre-committed reveal card; browser sees the current at start but never
// sees the reveal until the player picks. POST /play/hilo/start returns
// {roundId, currentCard}; /play/hilo/pick returns the outcome + reveal;
// /play/hilo/continue pulls a fresh roll for the next reveal; /play/hilo/
// cashout cashes the chain. Per-rank multipliers + probabilities are still
// computed client-side for the UI display, but the actual outcome is the
// server's call.

const { useState: useStateHL, useEffect: useEffectHL, useRef: useRefHL } = React;

const HL_RANKS = ['A','2','3','4','5','6','7','8','9','10','J','Q','K'];
// rankValue maps a rank string to its integer value 1..13. A is always low.
function hlRankValue(rank) {
  const i = HL_RANKS.indexOf(rank);
  return i === -1 ? 0 : i + 1;
}

function HiloPage({ palette: M, wallet, fairness, onConnect }) {
  // Game phases: idle = nothing dealt; picking = current card shown, awaiting
  // higher/lower pick; resolving = reveal animation; won-pending = chain
  // win, asking cash-out or continue; done = chain ended.
  const [phase, setPhase] = useStateHL('idle');
  const [current, setCurrent] = useStateHL(null);
  const [reveal, setReveal] = useStateHL(null);
  const [last, setLast] = useStateHL(null);
  const [error, setError] = useBetError();
  const { bet, setBet, betStr, setBetStr } = useBetInput(0.1);
  // Synchronous re-entry lock (audit HIGH-9).
  const busyRef = useRefHL(false);
  // §5.66: round identity. Server holds current + reveal in memory keyed by
  // (uid, roundId); browser only knows the id. roundHexRef is populated from
  // the server's response on tie / loss / cashout for the logged hex display.
  const roundIdRef  = useRefHL(null);
  const roundHexRef = useRefHL('');

  // Chain state — accumulated across consecutive wins in a single bet.
  // chainMult multiplies through each round's per-rank mult; chainSteps
  // counts wins so far. Server is authoritative; these mirror the server
  // for display, and also seed the 404-forced-cashout refund amount.
  const [chainMult, setChainMult]   = useStateHL(1);
  const [chainSteps, setChainSteps] = useStateHL(0);
  const chainBetRef = useRefHL(0);

  const MIN_BET = coinMinBet(wallet.activeCoin);
  const MAX_BET_SOL = 30;
  const maxBet = maxBetForCoin(MAX_BET_SOL, wallet.activeCoin);

  // Clamp the bet into [MIN_BET, maxBet] on coin switch — same pattern as
  // the other games, so a SOL 0.1 default doesn't fall below USDC's 1 floor.
  useEffectHL(() => {
    if (bet < MIN_BET) setBet(MIN_BET);
    else if (bet > maxBet) setBet(maxBet);
  }, [wallet.activeCoin]); // eslint-disable-line

  // Per-rank multipliers. With current rank r and reveal uniform over [1,13]:
  //   P(reveal > r) = (13-r)/13, P(reveal == r) = 1/13, P(reveal < r) = (r-1)/13
  // For 99% RTP (EV = 0.99) with ties pushing (1× refund on 1/13):
  //   m * (13-r)/13 + 1/13 = 0.99  ⇒  m = (0.99·13 - 1) / (13 - r) = 11.87 / (13 - r)
  //   (and symmetric on the LOWER side: m = 11.87 / (r - 1))
  // Dealer card range is 2..Q (rank 2..12) so neither divisor is zero.
  function multHigher(r) { return r >= 13 ? 0 : 11.87 / (13 - r); }
  function multLower(r)  { return r <= 1  ? 0 : 11.87 / (r - 1); }
  function fmtMult(m)    { return m >= 10 ? m.toFixed(1) : m.toFixed(2); }

  // 404 fallback: a lost server round forces a cashout-equivalent refund of
  // the locally-tracked chainMult × stake. chainMult=1 (initial pick) means a
  // bare stake refund. Logs the result, resets to idle.
  function forceRefund(reason) {
    const stake = chainBetRef.current;
    const payout = stake * chainMult;
    const refundId = 'hilo-refund-' + (roundIdRef.current || Date.now());
    if (payout > 0) wallet.credit(wallet.activeCoin, payout, refundId);
    setLast({
      outcome: chainSteps > 0 ? 'cashed' : 'tie',
      payout, mult: chainMult, steps: chainSteps, bet: stake,
      hex: (roundHexRef.current || refundId).slice(0, 12),
    });
    wallet.logPlay({
      game:'Hilo', bet: stake, payout,
      win: payout > stake, coin: wallet.activeCoin,
      hex: (roundHexRef.current || refundId).slice(0, 16),
      mult: chainMult, outcome: chainSteps > 0 ? 'cashed' : 'tie', steps: chainSteps,
    });
    roundIdRef.current = null;
    setPhase('done');
    setError('round lost (server reset); refunded ' + payout.toFixed(wallet.activeCoin === 'SOL' ? 3 : 2) + ' ' + wallet.activeCoin + ' — ' + reason);
  }

  async function deal() {
    if (busyRef.current) return;
    if (phase !== 'idle' && phase !== 'done') 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 hi-lo 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);
    if (!wallet.debit(wallet.activeCoin, bet)) {
      busyRef.current = false;
      setError(`bag changed under the bet -- try again`);
      return;
    }

    let server;
    try {
      server = await callServerGame('/play/hilo/start', {
        bet,
        clientSeed: fairness.clientSeed,
        coin: wallet.activeCoin,
        user: wallet.username,
      });
    } catch (err) {
      wallet.credit(wallet.activeCoin, bet, 'hilo-refund-' + Date.now());
      busyRef.current = false;
      setError('server error: ' + (err && err.message ? err.message : String(err)));
      return;
    }

    roundIdRef.current = server.roundId;
    roundHexRef.current = String(server.serverHash || '');
    chainBetRef.current = bet;
    setChainMult(1);
    setChainSteps(0);
    setCurrent(server.currentCard || null);
    setReveal(null);
    setLast(null);
    setPhase('picking');
    busyRef.current = false;
    playHitSound();
  }

  async function pick(side) {
    if (busyRef.current) return;
    if (phase !== 'picking') return;
    if (!roundIdRef.current) return;
    busyRef.current = true;
    setPhase('resolving');
    const stake = chainBetRef.current;
    const priorMult  = chainMult;
    const priorSteps = chainSteps;

    let server;
    try {
      server = await callServerGame('/play/hilo/pick', {
        roundId: roundIdRef.current,
        side,
      });
    } catch (err) {
      const msg = err && err.message ? err.message : String(err);
      if (msg.includes('round not found')) {
        busyRef.current = false;
        forceRefund(msg);
        return;
      }
      busyRef.current = false;
      setPhase('picking');
      setError('server error: ' + msg);
      return;
    }

    const outcome = String(server.outcome || '');
    const revealCard = server.revealCard || null;
    const hex = String(server.hex || roundHexRef.current || '');
    if (hex) roundHexRef.current = hex;

    // Brief flip delay so the reveal feels animated. The server already
    // resolved the round; this just paces the UI so the player sees the
    // back-of-card flip before the result lands.
    setTimeout(() => {
      setReveal(revealCard);
      if (outcome === 'win') {
        const newMult  = Number(server.chainMult)  || priorMult;
        const newSteps = Number(server.chainSteps) || priorSteps + 1;
        const roundMult = Number(server.roundMult) || 0;
        setChainMult(newMult);
        setChainSteps(newSteps);
        setPhase('won-pending');
        busyRef.current = false;
        if (roundMult >= 5) playStreakSound();
        else                playOutcomeSound(true);
      } else if (outcome === 'tie') {
        const payout = Number(server.payout) || 0;
        const mult   = Number(server.mult)   || priorMult;
        const steps  = Number(server.chainSteps) || priorSteps;
        if (payout > 0) wallet.credit(wallet.activeCoin, payout, 'hilo-tie-' + roundIdRef.current);
        setLast({ outcome:'tie', payout, mult, steps, bet: stake, hex: hex.slice(0, 12) });
        wallet.logPlay({
          game:'Hilo', bet: stake, payout,
          win: payout > stake, coin: wallet.activeCoin,
          hex: hex.slice(0, 16),
          mult, outcome: 'tie', steps,
        });
        // NOTE: server wrote /plays already (rules step6 blocks browser writes for Hilo).
        roundIdRef.current = null;
        setPhase('done');
        busyRef.current = false;
        playOutcomeSound(false);
      } else {
        // loss
        const mult  = Number(server.mult)  || priorMult;
        const steps = Number(server.chainSteps) || priorSteps;
        setLast({ outcome:'loss', payout:0, mult, steps, bet: stake, hex: hex.slice(0, 12) });
        wallet.logPlay({
          game:'Hilo', bet: stake, payout: 0,
          win: false, coin: wallet.activeCoin,
          hex: hex.slice(0, 16),
          mult, outcome: 'loss', steps,
        });
        roundIdRef.current = null;
        setPhase('done');
        busyRef.current = false;
        playOutcomeSound(false);
      }
    }, 400);
  }

  async function cashOut() {
    if (busyRef.current) return;
    if (phase !== 'won-pending') return;
    if (!roundIdRef.current) return;
    busyRef.current = true;

    let server;
    try {
      server = await callServerGame('/play/hilo/cashout', {
        roundId: roundIdRef.current,
      });
    } catch (err) {
      const msg = err && err.message ? err.message : String(err);
      if (msg.includes('round not found')) {
        busyRef.current = false;
        forceRefund(msg);
        return;
      }
      busyRef.current = false;
      setError('server error: ' + msg);
      return;
    }

    const stake  = chainBetRef.current;
    const payout = Number(server.payout) || 0;
    const mult   = Number(server.mult)   || chainMult;
    const steps  = Number(server.chainSteps) || chainSteps;
    const hex    = String(server.hex || roundHexRef.current || '');
    if (payout > 0) wallet.credit(wallet.activeCoin, payout, 'hilo-cash-' + roundIdRef.current);
    setLast({ outcome:'cashed', payout, mult, steps, bet: stake, hex: hex.slice(0, 12) });
    wallet.logPlay({
      game:'Hilo', bet: stake, payout,
      win: true, coin: wallet.activeCoin,
      hex: hex.slice(0, 16),
      mult, outcome: 'cashed', steps,
    });
    roundIdRef.current = null;
    setPhase('done');
    busyRef.current = false;
    if (mult >= 5) playStreakSound();
    else           playOutcomeSound(true);
  }

  async function continueChain() {
    if (busyRef.current) return;
    if (phase !== 'won-pending') return;
    if (!roundIdRef.current) return;
    busyRef.current = true;

    // Server already moved its internal `current` to the prior reveal on the
    // last winning pick. /continue just pulls a fresh roll and commits the
    // next reveal server-side; the browser's `current` flips to the just-
    // revealed card (which we already have in React state).
    try {
      await callServerGame('/play/hilo/continue', {
        roundId: roundIdRef.current,
        clientSeed: fairness.clientSeed,
      });
    } catch (err) {
      const msg = err && err.message ? err.message : String(err);
      if (msg.includes('round not found')) {
        busyRef.current = false;
        forceRefund(msg);
        return;
      }
      busyRef.current = false;
      setError('server error: ' + msg);
      return;
    }

    const newCurrent = reveal;
    setCurrent(newCurrent);
    setReveal(null);
    setPhase('picking');
    busyRef.current = false;
    playHitSound();
  }

  const currentRank = current ? hlRankValue(current.r) : 0;
  const mH  = currentRank ? multHigher(currentRank) : 0;
  const mL  = currentRank ? multLower(currentRank)  : 0;
  // Conditional probabilities of HIGHER / LOWER given the current rank.
  // (The remaining 1/13 ≈ 7.7% is the tie probability — push.)
  const pH = currentRank ? (13 - currentRank) / 13 : 0;
  const pL = currentRank ? (currentRank - 1)  / 13 : 0;
  const fmtPct = p => `${(p * 100).toFixed(1)}%`;
  const canDeal = phase === 'idle' || phase === 'done';
  // Per-coin decimals — 3 on SOL because consolation rounding lives there.
  const payoutDp = wallet.activeCoin === 'SOL' ? 3 : 2;
  const accumValue = chainBetRef.current * chainMult;

  return (
    <div className="rc-page" style={{flex:1, padding:'18px 26px 40px', overflowY:'auto'}}>
      <h1 className="rc-h-page" style={{fontSize:48, color:M.cyan, margin:'4px 0', textShadow:`3px 3px 0 ${M.hot}`}}>HI-LO</h1>
      <div className="rc-h-sub" style={{color:M.ink2, fontSize:18, marginBottom:14}}>
        <span style={{color:M.green}}>99% RTP</span> · <span style={{color:M.hot}}>1% edge</span> · predict the next card · ties push · multiplier per rank · max bet {maxBet.toFixed(2)} {wallet.activeCoin}
      </div>

      <div className="rc-hilo-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'}}>
        <Frame title="bet" accent={M.yellow}>
          <div className="rc-bet-panel rc-hilo-bet" style={{opacity: canDeal ? 1 : 0.4, pointerEvents: canDeal ? 'auto' : 'none', transition:'opacity 0.15s'}}>
            <div className="rc-bet-inputs">
              <div style={{fontSize:14, color:M.ink2, marginBottom:4}}>bet ({wallet.activeCoin})</div>
              <input type="number" step={0.01} max={maxBet} value={betStr} onChange={e=>setBetStr(e.target.value)} disabled={!canDeal} style={{
                width:'100%', background:M.bg, border:`2px solid ${M.green}`, color:M.green, padding:'8px 10px', fontFamily:'inherit', fontSize:22, outline:'none',
              }}/>
              <div style={{marginTop:6}}>
                <QuickBetButtons current={bet} onPick={v=>setBet(v)} palette={M} disabled={!canDeal}/>
              </div>
              <div style={{display:'flex', gap:4, marginTop:6}}>
                <button onClick={()=>setBet(b=>Math.max(MIN_BET, +(b/2).toFixed(6)))} disabled={!canDeal} style={hlBtnSec(M)}>½</button>
                <button onClick={()=>setBet(b=>+Math.min(b*2, maxBet).toFixed(6))} disabled={!canDeal} style={hlBtnSec(M)}>2×</button>
                <button onClick={()=>setBet(+(Math.min(wallet.balance[wallet.activeCoin] || 0, maxBet)).toFixed(6))} disabled={!canDeal} title={`max bet: ${maxBet.toFixed(2)} ${wallet.activeCoin}`} style={hlBtnSec(M)}>max</button>
              </div>
            </div>
            <button className="rc-bet-action" onClick={deal} disabled={!canDeal} style={{
              marginTop:14, width:'100%', background:M.hot, color:M.bg, border:`3px solid ${M.bg}`, fontFamily:'inherit', fontSize:22, padding:'10px 0', fontWeight:700, cursor:canDeal?'pointer':'wait', boxShadow:`5px 5px 0 ${M.green}`,
            }}>{phase === 'idle'        ? '► DEAL'
              : phase === 'done'        ? '► DEAL AGAIN'
              : phase === 'won-pending' ? 'chain in progress'
              :                           'dealing…'}</button>
          </div>
        </Frame>

        <Frame title="the deal" accent={M.green} style={{minHeight:380, display:'flex', flexDirection:'column', alignItems:'center', justifyContent:'center', gap:12, padding:'20px 20px'}}>
          {/* Chain banner — fixed-height slot that shows the running chain
              when one is active, an idle prompt otherwise. Keeps the card
              row anchored vertically across phase changes. flex-start so the
              banner text doesn't leap up by a few px when a 24px mult value
              replaces a 14px prompt line. */}
          <div className="rc-resolve-slot-row" style={{minHeight:42, display:'flex', alignItems:'flex-start', justifyContent:'center', width:'100%'}}>
            {phase === 'idle' && (
              <div style={{color:M.ink2, fontSize:14, textAlign:'center'}}>place a bet and click DEAL to start.</div>
            )}
            {chainSteps > 0 && phase !== 'idle' && (
              <div style={{display:'flex', gap:14, alignItems:'baseline'}}>
                <span style={{fontSize:13, color:M.ink2, letterSpacing:'0.05em'}}>CHAIN · STEP {chainSteps}</span>
                <span style={{fontSize:24, color:M.yellow, fontWeight:700, fontFamily:'JetBrains Mono, monospace'}}>{fmtMult(chainMult)}×</span>
                <span style={{fontSize:13, color:M.ink2}}>
                  locked: +{accumValue.toFixed(payoutDp)} {wallet.activeCoin}
                </span>
              </div>
            )}
            {chainSteps === 0 && phase !== 'idle' && (
              <div style={{fontSize:13, color:M.ink2, letterSpacing:'0.05em'}}>pick a side to start the chain.</div>
            )}
          </div>

          {/* Card row — ALWAYS rendered after the first deal so the action
              slot below doesn't slide when the reveal flips. Idle phase
              renders nothing here to keep the prompt visible above. */}
          {phase !== 'idle' && (
            <div style={{display:'flex', alignItems:'center', justifyContent:'center', gap:18, minHeight:170}}>
              <HiloCard card={current} M={M} accent={M.cyan}/>
              <span style={{fontSize:32, color:M.ink2}}>vs</span>
              {reveal
                ? <HiloCard
                    card={reveal} M={M}
                    accent={phase === 'won-pending' ? M.green : last ? (last.outcome === 'cashed' ? M.green : last.outcome === 'tie' ? M.yellow : M.red) : M.ink2}
                  />
                : <HiloCardBack M={M}/>}
            </div>
          )}

          {/* Action / result slot — fixed minimum so the cards above don't
              shift. Holds HIGHER/LOWER during picking, CASH OUT/CONTINUE
              during won-pending, and the final outcome block on done.
              flex-start anchors the visible content so swaps between phases
              don't visually jump within the slot. */}
          <div className="rc-resolve-slot-col" style={{minHeight:96, display:'flex', flexDirection:'column', alignItems:'center', justifyContent:'flex-start', gap:6, width:'100%'}}>
            {phase === 'picking' && (
              <>
                <div className="rc-row-stack" style={{display:'flex', gap:10}}>
                  <button onClick={()=>pick('H')} disabled={mH <= 0} style={{
                    background:M.green, color:M.bg, border:`3px solid ${M.bg}`, fontFamily:'inherit', fontSize:18, padding:'10px 18px',
                    fontWeight:700, cursor: mH > 0 ? 'pointer' : 'not-allowed', boxShadow:`4px 4px 0 ${M.bg}`, opacity: mH > 0 ? 1 : 0.4, letterSpacing:'0.05em',
                  }}>▲ HIGHER · {fmtPct(pH)} · {fmtMult(mH)}×</button>
                  <button onClick={()=>pick('L')} disabled={mL <= 0} style={{
                    background:M.red, color:M.bg, border:`3px solid ${M.bg}`, fontFamily:'inherit', fontSize:18, padding:'10px 18px',
                    fontWeight:700, cursor: mL > 0 ? 'pointer' : 'not-allowed', boxShadow:`4px 4px 0 ${M.bg}`, opacity: mL > 0 ? 1 : 0.4, letterSpacing:'0.05em',
                  }}>▼ LOWER · {fmtPct(pL)} · {fmtMult(mL)}×</button>
                </div>
                <div style={{fontSize:11, color:M.ink2, letterSpacing:'0.05em'}}>tie {fmtPct(1/13)} pushes the chain</div>
              </>
            )}
            {phase === 'won-pending' && (
              <>
                <div className="rc-row-stack" style={{display:'flex', gap:10}}>
                  <button onClick={cashOut} style={{
                    background:M.green, color:M.bg, border:`3px solid ${M.bg}`, fontFamily:'inherit', fontSize:18, padding:'10px 18px',
                    fontWeight:700, cursor:'pointer', boxShadow:`4px 4px 0 ${M.bg}`, letterSpacing:'0.05em',
                  }}>✓ CASH OUT · +{accumValue.toFixed(payoutDp)} {wallet.activeCoin}</button>
                  <button onClick={continueChain} style={{
                    background:M.yellow, color:M.bg, border:`3px solid ${M.bg}`, fontFamily:'inherit', fontSize:18, padding:'10px 18px',
                    fontWeight:700, cursor:'pointer', boxShadow:`4px 4px 0 ${M.bg}`, letterSpacing:'0.05em',
                  }}>► CONTINUE · {fmtMult(chainMult)}× → ?</button>
                </div>
                <div style={{fontSize:11, color:M.ink2, letterSpacing:'0.05em'}}>continue risks the accumulated stake on the next round</div>
              </>
            )}
            {phase === 'done' && last && (
              <div style={{textAlign:'center'}}>
                <div style={{fontSize:30, fontWeight:700, color: last.outcome === 'cashed' ? M.green : last.outcome === 'tie' ? M.yellow : M.red, textShadow:`2px 2px 0 ${M.bg}`}}>
                  {last.outcome === 'cashed' ? `CASHED · ${fmtMult(last.mult)}×` :
                   last.outcome === 'tie'    ? 'TIE — CHAIN PUSHED' :
                                               'CHAIN BROKEN'}
                </div>
                <div style={{fontSize:14, color:M.ink2, marginTop:2}}>
                  {last.steps} step{last.steps===1?'':'s'} · final mult {fmtMult(last.mult)}×
                </div>
                <div style={{fontSize:16, marginTop:4, color: last.outcome === 'cashed' ? M.green : last.outcome === 'tie' ? (last.payout > last.bet ? M.green : M.yellow) : M.red, fontWeight:700}}>
                  {last.outcome === 'cashed' ? `+${(last.payout - last.bet).toFixed(payoutDp)} ${wallet.activeCoin} (net)` :
                   last.outcome === 'tie'    ? (last.payout > last.bet
                     ? `+${(last.payout - last.bet).toFixed(payoutDp)} ${wallet.activeCoin} (chain locked at ${fmtMult(last.mult)}× then pushed)`
                     : `±0 ${wallet.activeCoin} (push on first round)`) :
                                                `−${last.bet.toFixed(payoutDp)} ${wallet.activeCoin}`}
                </div>
                <div style={{fontSize:12, color:M.ink2, marginTop:2, fontFamily:'JetBrains Mono, monospace'}}>hash: {last.hex}…</div>
              </div>
            )}
          </div>
        </Frame>
      </div>

      <HiloStatsPanel palette={M} wallet={wallet}/>
      <HashPanel palette={M} fairness={fairness}/>
    </div>
  );
}

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

function HiloCard({ card, M, accent }) {
  if (!card) return null;
  const red = card.s === '♥' || card.s === '♦';
  return (
    <div style={{
      width:120, height:170, background:M.ink, color: red ? '#cc0000' : '#000',
      border:`3px solid ${accent}`, padding:10, position:'relative', fontFamily:'serif',
      boxShadow:`5px 5px 0 ${M.bg}`, transition:'border-color 0.15s',
    }}>
      <div style={{position:'absolute', top:8, left:10, fontSize:24, fontWeight:700, lineHeight:1}}>{card.r}</div>
      <div style={{position:'absolute', top:32, left:10, fontSize:18}}>{card.s}</div>
      <div style={{position:'absolute', bottom:8, right:10, fontSize:24, fontWeight:700, transform:'rotate(180deg)', lineHeight:1}}>{card.r}</div>
      <div style={{position:'absolute', bottom:32, right:10, fontSize:18, transform:'rotate(180deg)'}}>{card.s}</div>
      <div style={{position:'absolute', top:'50%', left:'50%', transform:'translate(-50%,-50%)', fontSize:48}}>{card.s}</div>
    </div>
  );
}

function HiloCardBack({ M }) {
  return (
    <div style={{
      width:120, height:170, background:M.hot,
      border:`3px solid ${M.ink2}`, boxShadow:`5px 5px 0 ${M.bg}`,
      display:'flex', alignItems:'center', justifyContent:'center', color:M.bg, fontSize:48, fontWeight:700,
    }}>?</div>
  );
}

function HiloStatsPanel({ palette: M, wallet }) {
  const coin = wallet.activeCoin;
  const rounds = (wallet.history || []).filter(h => h.game === 'Hilo' && h.coin === coin);
  const total = rounds.length;
  const wins  = rounds.filter(r => r.win).length;
  const winRate = total ? (wins / total) * 100 : 0;
  const wagered = rounds.reduce((s, r) => s + (r.bet || 0), 0);
  const netPL   = rounds.reduce((s, r) => s + ((r.payout || 0) - (r.bet || 0)), 0);
  const bestWin = rounds.reduce((m, r) => Math.max(m, (r.payout || 0) - (r.bet || 0)), 0);
  const bestMult = rounds.reduce((m, r) => Math.max(m, r.mult || 0), 0);
  const dp = coin === 'SOL' ? 3 : 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="hi-lo stats" accent={M.cyan} style={{marginTop:18}}>
      {total === 0 ? (
        <div style={{color:M.ink2, fontSize:14, padding:'4px 0'}}>{'>'} no rounds yet in {coin}. play a hand.</div>
      ) : (
        <div className="rc-stat-6" style={{display:'grid', gridTemplateColumns:'repeat(6, 1fr)', gap:14}}>
          {tile('rounds',        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('best mult',     `${bestMult.toFixed(2)}×`,          M.hot)}
        </div>
      )}
    </Frame>
  );
}

Object.assign(window, { HiloPage });
