/* ============================================================
   LAUNCHPAD.FAIL — app orchestrator
   Phases: idle -> countdown -> blastoff -> flying -> (eject-anim->win | lose)
   Owns rAF loop, boost mechanic, 3 skill models, dev controls.
   ============================================================ */
(function () {
  const { useState, useRef, useEffect } = React;
  const E = window.Engine;
  const { Scene, RocketSprite, Parachutist, AltAxis, HeroMult, CheckpointBurst, WagerChip, BalancePill, BoostGauge, ControlButtons,
    Arena, Marquee, useBots, BetPick, Countdown, WinOverlay, LoseOverlay } = window;

  const START_BAL = 1250000;

  function cadenceLabel(s) {
    if (s.power > E.BOOST.greenHi + 8) return 'EASE OFF';
    if (s.power >= E.BOOST.greenLo && s.power <= E.BOOST.greenHi) return 'PERFECT';
    if (s.power < E.BOOST.greenLo) return 'MORE THRUST';
    return 'HOLD';
  }

  /* rocket vertical position: starts very low, rises slowly with multiplier */
  function computeRiseY(v) {
    if (v.phase === 'countdown') return 30;    // on the pad
    if (v.phase === 'blastoff') return 60;
    if (v.phase === 'eject-anim') return 980;  // shoots off top
    const p = Math.min(1, v.mult / 6);
    return 60 + Math.pow(p, 0.7) * 360;
  }

  function App() {
    const { botsRef, recentRef, hofRef, stepBots, recordPlayer } = useBots(8);

    const g = useRef({
      phase: 'idle', wagerSol: 0.05, crash: 2.0, devCrash: 2.0, cdNum: 3,
      t0: 0, raw: 0, mult: 0, alt: 0, speed: 0, boostersOff: false,
      power: 0, combo: 1, lastTap: 0, lastFrame: 0,
      greenMsTotal: 0, segGreenMs: 0, segMs: 0, lastCp: 0, lostAccum: 0, bankMs: 0,
      skill: 'checkpoint', capLabel: '', capUntil: 0, pulse: false, burst: null,
      balance: START_BAL, result: null, winStep: 0, rippleKey: 0,
    });
    const [v, setV] = useState(() => snap(g.current));
    const sync = () => setV(snap(g.current));

    function snap(s) {
      return {
        phase: s.phase, wagerSol: s.wagerSol, mult: s.mult, alt: s.alt, speed: s.speed,
        boostersOff: s.boostersOff, power: s.power, cadence: cadenceLabel(s), greenTimeMs: s.bankMs,
        hot: s.power > E.BOOST.greenHi + 8, inGreen: s.power >= E.BOOST.greenLo && s.power <= E.BOOST.greenHi,
        capLabel: s.capLabel, pulse: s.pulse, burst: s.burst,
        balance: s.balance, result: s.result, winStep: s.winStep, rippleKey: s.rippleKey,
        skill: s.skill, devCrash: s.devCrash, cdNum: s.cdNum, camT: s.camT || 0, moon: E.moonPayout(s.wagerSol, Math.max(s.mult, 0)),
      };
    }

    /* ---------- main loop ---------- */
    useEffect(() => {
      let raf;
      const tick = (now) => {
        const s = g.current;
        const dt = s.lastFrame ? Math.min(0.05, (now - s.lastFrame) / 1000) : 0;
        s.lastFrame = now;
        stepBots(now);   // bots animate on the single host loop

        if (s.phase === 'flying') {
          const t = (now - s.t0) / 1000;
          s.camT = t;
          s.raw = E.multAt(t);
          s.alt = E.altAt(t);
          s.speed = E.speedAt(t);
          s.boostersOff = s.raw >= 1.0;

          s.power = Math.max(0, s.power - E.BOOST.decayPerSec * dt);
          const inGreen = s.power >= E.BOOST.greenLo && s.power <= E.BOOST.greenHi;
          s.segMs += dt * 1000;
          if (inGreen) { s.segGreenMs += dt * 1000; s.greenMsTotal += dt * 1000; if (s.skill === 'delay') s.bankMs += dt * 1000 * 0.2; }

          const nc = E.nextCheckpoint(s.lastCp);
          if (nc && s.raw >= nc.m) {
            const greenFrac = s.segMs > 0 ? s.segGreenMs / s.segMs : 1;
            if (s.skill === 'checkpoint') {
              const nominal = nc.m - s.lastCp;
              s.lostAccum += nominal * 0.2 * (1 - greenFrac);
            }
            s.lastCp = nc.m; s.segMs = 0; s.segGreenMs = 0;
            if (nc.type === 'major') {
              s.capLabel = nc.label; s.capUntil = now + 1700; s.pulse = true; setTimeout(() => { g.current.pulse = false; }, 200);
              // payout burst: MOON gained since the last major landmark
              const gained = E.moonPayout(s.wagerSol, nc.m) - E.moonPayout(s.wagerSol, s.lastBurstMult || 0);
              s.burst = { key: now | 0, amount: Math.max(0, gained), label: nc.label };
              s.lastBurstMult = nc.m;
            }
          }
          if (now > s.capUntil) s.capLabel = '';

          s.mult = Math.max(0, s.raw - (s.skill === 'checkpoint' ? s.lostAccum : 0));

          const bankSec = s.skill === 'delay' ? Math.min(0.8, s.bankMs / 1000) : 0;
          const tCrashEff = E.tForMult(s.crash) + bankSec;
          if (t >= tCrashEff) explode();
        } else if (s.falling) {
          // world reverses: camera eases back down (the astronaut's fall)
          const fp = Math.min(1, (now - s.fallStart) / E.TIMING.fallMs);
          const ease = 1 - Math.pow(1 - fp, 2.2);
          s.camT = s.fallFrom * (1 - 0.74 * ease);
          if (fp >= 1) s.falling = false;
        }

        sync();
        raf = requestAnimationFrame(tick);
      };
      raf = requestAnimationFrame(tick);
      return () => cancelAnimationFrame(raf);
    }, []);

    /* ---------- transitions ---------- */
    function startCountdown() {
      const s = g.current;
      if (!['idle', 'win', 'lose'].includes(s.phase)) return;
      clearTimeout(s.cdTimer);   // guard: never leave a stale countdown chain running
      s.crash = s.devCrash;
      Object.assign(s, { phase: 'countdown', cdNum: E.TIMING.countdown, raw: 0, mult: 0, alt: 0, speed: 0, boostersOff: false, camT: 0, falling: false,
        power: 0, combo: 1, greenMsTotal: 0, segGreenMs: 0, segMs: 0, lastCp: 0, lostAccum: 0, bankMs: 0,
        capLabel: '', result: null, winStep: 0, burst: null, lastBurstMult: 0 });
      sync(); runCountdown(E.TIMING.countdown);
    }
    function runCountdown(n) {
      const s = g.current;
      if (s.phase !== 'countdown') return; // tap-to-skip cancels the chain
      s.cdNum = n; sync();
      if (n <= 0) return blastoff();
      s.cdTimer = setTimeout(() => runCountdown(n - 1), 1000);
    }
    function skipCountdown() {
      const s = g.current;
      if (s.phase !== 'countdown') return;
      clearTimeout(s.cdTimer);
      blastoff();
    }
    function blastoff() {
      g.current.phase = 'blastoff'; sync();
      setTimeout(() => { g.current.phase = 'flying'; g.current.t0 = performance.now(); g.current.lastFrame = 0; sync(); }, E.TIMING.blastoffMs);
    }

    function boost() {
      const s = g.current; if (s.phase !== 'flying') return;
      const now = performance.now();
      const gap = now - s.lastTap;
      s.combo = gap < E.BOOST.comboWindow ? Math.min(E.BOOST.comboMax, s.combo + E.BOOST.comboGain) : 1;
      s.lastTap = now;
      s.power = Math.min(E.BOOST.max, s.power + E.BOOST.baseStep * s.combo);
      s.rippleKey = (s.rippleKey + 1) & 0xffff;
      if (navigator.vibrate) { try { navigator.vibrate(8); } catch (e) {} }
      sync();
    }

    function eject() {
      const s = g.current; if (s.phase !== 'flying') return;
      const endMult = s.mult;
      const payout = E.moonPayout(s.wagerSol, endMult);
      const nearMiss = E.isNearMiss(endMult, s.crash);
      s.result = { endMult, payout, win: true, nearMiss, crash: s.crash };
      recordPlayer(endMult, 'ejected');   // player joins recent + HOF
      s.phase = 'eject-anim'; s.winStep = 0;
      // begin the fall: world reverses from current camera height back down
      s.falling = true; s.fallFrom = s.camT; s.fallStart = performance.now();
      sync();
      setTimeout(() => { g.current.phase = 'win'; g.current.winStep = 1; sync(); }, E.TIMING.winBigMs);
      setTimeout(() => { g.current.winStep = 2; sync(); }, E.TIMING.winPayMs);
      setTimeout(() => { g.current.winStep = 3; g.current.balance += payout; sync(); }, E.TIMING.winCoinsMs);
    }

    function explode() {
      const s = g.current; if (s.phase !== 'flying') return;
      s.result = { endMult: s.crash, win: false };
      recordPlayer(s.crash, 'dead');   // player's wreck joins recent
      s.phase = 'lose'; sync();
    }

    const flying = v.phase === 'flying';
    const showRocket = ['countdown', 'blastoff', 'flying', 'eject-anim'].includes(v.phase);
    const riseY = computeRiseY(v);
    const ignition = v.phase === 'countdown'
      ? Math.max(0, Math.min(1, (E.TIMING.igniteAt - v.cdNum) / E.TIMING.igniteAt))
      : 0;

    return (
      <div className="host">
        <div className="phone-scale" id="pscale">
          <div className={'phone' + (v.phase === 'lose' ? ' shake' : '') + (v.phase === 'eject-anim' ? ' lurch' : '')}>
            <div className="game">
              <Scene t={v.camT} mult={v.mult} phase={v.phase} boostersOff={v.boostersOff} />

              {showRocket && (
                <RocketSprite mult={v.mult} phase={v.phase} riseY={riseY}
                  tilt={v.phase === 'eject-anim' ? -6 : 0} boostersOff={v.boostersOff}
                  ejecting={v.phase === 'eject-anim'} flameOn={['blastoff', 'flying'].includes(v.phase)} ignition={ignition} />
              )}

              {v.phase === 'eject-anim' && <div className="eject-flash" />}
              {['eject-anim', 'win'].includes(v.phase) && <Parachutist />}

              {(flying || v.phase === 'eject-anim') && (
                <div className="hud">
                  <HeroMult mult={v.mult} moon={v.moon} capLabel={v.capLabel} pulse={v.pulse} />
                  <CheckpointBurst burst={v.burst} />
                  <AltAxis mult={v.mult} altKm={v.alt} speed={v.speed} />
                  <WagerChip wagerSol={v.wagerSol} />
                </div>
              )}

              <Arena live={botsRef.current} hof={hofRef.current} />
              <BalancePill balance={v.balance} />
              <Marquee recent={recentRef.current} />

              {flying && (
                <div className="controls">
                  <BoostGauge power={v.power} cadence={v.cadence} greenTimeMs={v.greenTimeMs} hot={v.hot} inGreen={v.inGreen} />
                  <ControlButtons onBoost={boost} onEject={eject} flying={flying} rippleKey={v.rippleKey} />
                </div>
              )}

              {v.phase === 'idle' && (
                <div className="cta-center">
                  <BetPick wagerSol={v.wagerSol} onPick={(b) => { g.current.wagerSol = b; sync(); }} />
                  <button className="cta" onClick={startCountdown}>🚀 Initiate launch</button>
                </div>
              )}

              {v.phase === 'countdown' && <Countdown n={v.cdNum} onSkip={skipCountdown} />}
              {v.phase === 'blastoff' && <div className="countdown"><div className="blastoff">BLAST OFF!</div></div>}

              {v.phase === 'win' && <WinOverlay result={v.result} step={v.winStep} balance={v.balance} onAgain={() => { g.current.phase = 'idle'; sync(); }} />}
              {v.phase === 'lose' && <LoseOverlay result={v.result} onAgain={() => { g.current.phase = 'idle'; sync(); }} />}
            </div>
          </div>
        </div>

        <DevPanel g={g} v={v} sync={sync}
          onExplode={() => explode()} onLaunch={startCountdown}
          onWin={(m) => { if (g.current.phase === 'flying') { g.current.mult = m; eject(); } }} />
      </div>
    );
  }

  /* ---------- dev controls ---------- */
  function DevPanel({ g, v, sync, onExplode, onLaunch, onWin }) {
    const s = g.current;
    const [open, setOpen] = useState(true);
    const [float, setFloat] = useState(false);
    useEffect(() => {
      const check = () => setFloat(window.innerWidth <= 1180);
      check(); window.addEventListener('resize', check);
      return () => window.removeEventListener('resize', check);
    }, []);
    if (float && !open) return <button className="dev-toggle" onClick={() => setOpen(true)}>🛠 DEV</button>;
    return (
      <div className={'dev' + (float ? ' dev--float' : ' dev--docked')}>
        <h4>🛠 Dev controls {float && <button className="dev-btn" style={{ flex: 'none', marginLeft: 'auto', padding: '3px 9px' }} onClick={() => setOpen(false)}>✕</button>}</h4>
        <p className="hint">Predetermined crash is set at launch. Use these to test every phase.</p>

        <div className="grp">
          <label>Crash multiplier — {v.devCrash.toFixed(2)}x</label>
          <div className="row">
            <input type="range" min="0.1" max="15" step="0.05" value={v.devCrash}
              onChange={(e) => { g.current.devCrash = +e.target.value; sync(); }} />
            <span className="val">{v.devCrash.toFixed(2)}x</span>
          </div>
        </div>

        <div className="grp">
          <label>Skill model</label>
          <div className="seg">
            {[['checkpoint', 'Checkpoint %'], ['delay', 'Crash delay'], ['flavor', 'Flavor only']].map(([k, lab]) => (
              <button key={k} className={s.skill === k ? 'on' : ''} onClick={() => { g.current.skill = k; sync(); }}>{lab}</button>
            ))}
          </div>
        </div>

        <div className="grp">
          <label>Jump to phase</label>
          <div className="dev-btns">
            <button className="dev-btn go" onClick={onLaunch}>Launch</button>
            <button className="dev-btn" onClick={onExplode}>💥 Explode now</button>
          </div>
          <div className="dev-btns" style={{ marginTop: 8 }}>
            <button className="dev-btn" onClick={() => onWin(1.8)}>Win @1.8x</button>
            <button className="dev-btn" onClick={() => onWin(3.0)}>Win @3.0x</button>
            <button className="dev-btn" onClick={() => onWin(7.5)}>Win @7.5x</button>
          </div>
        </div>

        <div className="grp">
          <label>Quick crash presets</label>
          <div className="dev-btns">
            <button className="dev-btn" onClick={() => { g.current.devCrash = 0.6; sync(); }}>Boost fail 0.6</button>
            <button className="dev-btn" onClick={() => { g.current.devCrash = 1.8; sync(); }}>Space 1.8</button>
            <button className="dev-btn" onClick={() => { g.current.devCrash = 3.0; sync(); }}>Moon 3.0</button>
          </div>
        </div>
      </div>
    );
  }

  /* ---------- scale phone to viewport ---------- */
  function fitScale() {
    const el = document.getElementById('pscale');
    if (!el) return;
    const margin = 40;
    const sh = (window.innerHeight - margin) / 844;
    const sw = (window.innerWidth - (window.innerWidth > 1180 ? 360 : margin)) / 390;
    const sc = Math.min(sh, sw, 1.1);
    el.style.transform = `scale(${sc})`;
  }
  window.addEventListener('resize', fitScale);
  setTimeout(fitScale, 50);

  ReactDOM.createRoot(document.getElementById('root')).render(<App />);
  setTimeout(fitScale, 100);
})();
