/* ============================================================
   app.jsx — game orchestration for GTO Heads-Up
   ============================================================ */
const { useState, useEffect, useRef, useCallback } = React;
const Eng = window.Engine;
const G = window.GTO;
const { Seat, Board, Pot, ActionBar, FeedbackOverlay, Toast } = window.UI;
const { Header, CoachRail, SettingsModal } = window.UI2;
const Sfx = window.Sfx || { forAction() {}, deal() {}, win() {}, lose() {}, click() {}, isEnabled: () => false, setEnabled() {} };

const LS = "gto_hu_v1";
function loadPrefs() {
  try { return JSON.parse(localStorage.getItem(LS)) || {}; } catch (e) { return {}; }
}
function savePrefs(p) { try { localStorage.setItem(LS, JSON.stringify(p)); } catch (e) {} }

const blankSession = () => ({ hands: 0, decisions: 0, optimal: 0, bb: 0, evLost: 0, tally: { good: 0, ok: 0, warn: 0, bad: 0 } });

function normalizeAction(hand, a) {
  const legal = Eng.legalActions(hand).map((x) => x.type);
  if (!legal.includes(a.type)) {
    if (a.type === "bet" && legal.includes("check")) return { type: "check" };
    if (a.type === "raise" && legal.includes("call")) return { type: "call", to: Eng.streetInvest(hand, 1 - hand.toAct) };
    if (legal.includes("check")) return { type: "check" };
    if (legal.includes("call")) return { type: "call", to: Eng.streetInvest(hand, 1 - hand.toAct) };
    return { type: "fold" };
  }
  return a;
}

function computeSpot(hand, heroIdx, showEquity) {
  const i = heroIdx;
  const me = hand.players[i];
  const myBet = Eng.streetInvest(hand, i);
  const oppBet = Eng.streetInvest(hand, 1 - i);
  const toCall = Math.max(0, oppBet - myBet);
  const pot = Eng.potNow(hand);
  const potOdds = toCall > 0 ? (toCall / (pot + toCall)) * 100 : 0;
  const spr = pot > 0 ? me.stack / pot : null;
  let equity = null;
  if (showEquity) {
    // Use the SAME adaptive villain range the grader uses (it tightens as they show
    // aggression), so the equity shown on the table matches how the decision is graded.
    const oppPct = G.villainRangePct ? G.villainRangePct(hand, i) : 0.3;
    equity = Math.round(G.liveEquity(me.hole, hand.board, oppPct, [...me.hole, ...hand.board]) * 1000) / 10;
  }
  return { equity, potOdds, pot, spr, showEquity };
}

function App({ onMenu }) {
  const prefs = loadPrefs();
  const [ready, setReady] = useState(false);
  const [mode, setMode] = useState(prefs.mode || "study");
  const [styleKey, setStyleKey] = useState(prefs.styleKey || "gto");
  const [stack, setStack] = useState(prefs.stack || 100);
  const [session, setSession] = useState(prefs.session || blankSession());

  const [, setTick] = useState(0);
  const rerender = useCallback(() => setTick((t) => t + 1), []);
  const handRef = useRef(null);
  const buttonRef = useRef(0);
  const botTimer = useRef(null);
  const keyRef = useRef(null);
  const sizeRef = useRef(null);

  const [busy, setBusy] = useState(false);
  const [waitingNext, setWaitingNext] = useState(false);
  const [pendingFb, setPendingFb] = useState(null); // {fb, action}
  const [lastFb, setLastFb] = useState(null);
  const [reviews, setReviews] = useState([]); // graded-decision history for the leak finder
  const [toast, setToast] = useState(null);
  const [railOpen, setRailOpen] = useState(false);
  const [railTab, setRailTab] = useState("Spot");
  const [settings, setSettings] = useState(false);

  const modeRef = useRef(mode); modeRef.current = mode;
  const styleRef = useRef(styleKey); styleRef.current = styleKey;
  const sessionRef = useRef(session); sessionRef.current = session;

  // build preflop strength table once
  useEffect(() => {
    setTimeout(() => { G.buildStrength(); setReady(true); }, 30);
  }, []);

  useEffect(() => { savePrefs({ mode, styleKey, stack, session }); }, [mode, styleKey, stack, session]);

  // start first hand when ready
  useEffect(() => { if (ready && !handRef.current) newHand(); /* eslint-disable-next-line */ }, [ready]);

  // keyboard shortcuts: F fold, C call/check, A all-in, N next/continue
  useEffect(() => {
    const onKey = (e) => {
      const t = e.target;
      if (t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.isContentEditable)) return;
      if (e.metaKey || e.ctrlKey || e.altKey) return;
      const k = e.key === " " || e.key === "Enter" ? "n" : e.key.toLowerCase();
      if (["f", "c", "a", "r", "n"].includes(k)) { e.preventDefault(); keyRef.current && keyRef.current(k); }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, []);

  const heroIdx = 0, botIdx = 1;
  const [muted, setMuted] = useState(!Sfx.isEnabled());
  const toggleSound = () => { const on = !Sfx.isEnabled(); Sfx.setEnabled(on); setMuted(!on); if (on) Sfx.click(); };

  function newHand() {
    if (botTimer.current) clearTimeout(botTimer.current);
    setWaitingNext(false); setPendingFb(null); setToast(null);
    buttonRef.current = sessionRef.current.hands % 2; // alternate button
    const st = { names: ["You", "Ivey"], stacks: [stack, stack], button: buttonRef.current };
    const h = Eng.startHand(st);
    h.heroIdx = heroIdx;
    handRef.current = h;
    Sfx.deal();
    setSession((s) => ({ ...s, hands: s.hands + 1 }));
    rerender();
    afterApply();
  }

  // Preset training spots: deal a hand and script the action up to a target decision for the hero.
  function startSpot(key) {
    if (key === "random") { newHand(); return; }
    if (botTimer.current) clearTimeout(botTimer.current);
    setWaitingNext(false); setPendingFb(null); setToast(null); setSettings(false);
    const heroIsSB = key === "sb_open" || key === "sb_vs_3bet";
    buttonRef.current = heroIsSB ? heroIdx : botIdx;
    const st = { names: ["You", "Ivey"], stacks: [stack, stack], button: buttonRef.current };
    const h = Eng.startHand(st);
    h.heroIdx = heroIdx;
    handRef.current = h;
    setSession((s) => ({ ...s, hands: s.hands + 1 }));
    try {
      if (key === "bb_vs_open") {
        Eng.applyAction(h, { type: "raise", to: 2.5 });             // bot (SB) opens
      } else if (key === "sb_vs_3bet") {
        Eng.applyAction(h, { type: "raise", to: 2.5 });             // hero (SB) auto-opens
        Eng.applyAction(h, { type: "raise", to: 9 });               // bot (BB) 3-bets
      } else if (key === "cbet_defense") {
        Eng.applyAction(h, { type: "raise", to: 2.5 });             // bot (SB) opens
        Eng.applyAction(h, { type: "call", to: 2.5 });              // hero (BB) calls -> flop
        if (h.street === "flop" && h.toAct === heroIdx) Eng.applyAction(h, { type: "check" }); // hero checks to the raiser
        if (h.street === "flop" && h.toAct === botIdx && !h.finished) {
          const pot = Eng.potNow(h);
          const to = Eng.streetInvest(h, botIdx) + Math.min(h.players[botIdx].stack, pot * 0.5);
          Eng.applyAction(h, { type: "bet", to });                  // bot c-bets ~50% pot
        }
      }
    } catch (e) { console.warn("spot setup failed", e); }
    rerender();
    afterApply();
  }

  function afterApply() {
    const h = handRef.current;
    if (!h) return;
    if (h.finished) { finishHand(); return; }
    if (h.toAct === botIdx) { scheduleBot(); }
    rerender();
  }

  function scheduleBot() {
    setBusy(true); rerender();
    const delay = 600 + Math.random() * 700;
    botTimer.current = setTimeout(() => {
      const h = handRef.current;
      if (!h || h.finished || h.toAct !== botIdx) { setBusy(false); return; }
      let a = G.botDecide(h, styleRef.current);
      a = normalizeAction(h, a);
      Eng.applyAction(h, a);
      Sfx.forAction(a.type, h.players[botIdx].allIn && (a.type === "raise" || a.type === "bet"));
      setBusy(false);
      afterApply();
    }, delay);
  }

  function finishHand() {
    const h = handRef.current;
    const delta = h.result ? h.result.delta[heroIdx] : 0;
    if (h.result) { if (h.result.winner === heroIdx) Sfx.win(); else if (h.result.type === "showdown" && h.result.winner === botIdx) Sfx.lose(); }
    setSession((s) => ({ ...s, bb: Math.round((s.bb + delta) * 10) / 10 }));
    setWaitingNext(true);
    setBusy(false);
    rerender();
  }

  function recordDecision(fb) {
    setSession((s) => {
      const t = { ...s.tally };
      t[fb.color] = (t[fb.color] || 0) + 1;
      const isOptimal = fb.color === "good" || fb.color === "ok";
      return {
        ...s,
        decisions: s.decisions + 1,
        optimal: s.optimal + (isOptimal ? 1 : 0),
        evLost: Math.round((s.evLost + fb.evLoss) * 100) / 100,
        tally: t,
      };
    });
  }

  // Human-readable label for the spot a decision was made in (drives leak buckets).
  function situationLabel(h, fb) {
    if (h.street === "preflop") {
      return ({
        SB_open: "SB open (RFI)", BB_vs_open: "BB vs open", SB_vs_3bet: "SB vs 3-bet",
        BB_vs_4bet: "BB vs 4-bet", SB_vs_jam: "SB vs jam", BB_option: "BB option",
      })[fb.node] || "Preflop";
    }
    const inPos = h.button === heroIdx;
    const facing = Eng.streetInvest(h, botIdx) - Eng.streetInvest(h, heroIdx) > 0;
    const street = h.street.charAt(0).toUpperCase() + h.street.slice(1);
    return `${street} ${inPos ? "IP" : "OOP"}${facing ? " vs bet" : ""}`;
  }

  function pushReview(h, action, fb) {
    const rev = {
      id: Date.now() + "-" + Math.random().toString(36).slice(2, 6),
      situation: situationLabel(h, fb),
      hole: [...h.players[heroIdx].hole],
      chosenLabel: fb.chosenLabel, optimalLabel: fb.optimalLabel,
      color: fb.color, ratingLabel: fb.ratingLabel, evLoss: fb.evLoss, correct: fb.correct,
    };
    setReviews((rs) => [rev, ...rs].slice(0, 200));
  }

  function heroAct(action) {
    const h = handRef.current;
    if (!h || h.toAct !== heroIdx) return;
    const fb = G.evaluateDecision(h, heroIdx, action, styleRef.current);
    recordDecision(fb);
    pushReview(h, action, fb);
    setLastFb(fb);
    if (modeRef.current === "study") {
      setPendingFb({ fb, action });
    } else {
      setToast(fb);
      setTimeout(() => setToast(null), 1700);
      applyHero(action);
    }
  }

  function applyHero(action) {
    const h = handRef.current;
    setPendingFb(null);
    Eng.applyAction(h, action);
    Sfx.forAction(action.type, h.players[heroIdx].allIn && (action.type === "raise" || action.type === "bet"));
    afterApply();
  }

  function continueFromStudy() {
    if (!pendingFb) return;
    const action = pendingFb.action;
    setPendingFb(null);
    applyHero(action);
  }

  function resetSession() {
    setSession(blankSession());
    setLastFb(null);
    setReviews([]);
  }

  if (!ready) {
    return (
      <div style={{ height: "100%", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 16 }}>
        <div className="brand" style={{ alignItems: "center" }}>
          <div className="name" style={{ fontSize: 22 }}><span className="dot" />GTO HEADS-UP</div>
        </div>
        <div className="mono" style={{ color: "var(--text-3)", fontSize: 13 }}>solving preflop ranges…</div>
      </div>
    );
  }

  const h = handRef.current;
  const heroToAct = h && !h.finished && h.toAct === heroIdx && !pendingFb;
  const reveal = h && h.finished && h.result && h.result.type === "showdown";
  const spot = h && heroToAct ? computeSpot(h, heroIdx, mode === "study") : (lastFb ? null : null);
  const spotForRail = h && heroToAct ? computeSpot(h, heroIdx, mode === "study") : null;

  const heroPos = h ? (h.button === heroIdx ? "BTN" : "BB") : "BB";
  const botPos = h ? (h.button === botIdx ? "BTN" : "BB") : "BB";
  const streetLabel = h ? ({ preflop: "PRE-FLOP", flop: "FLOP", turn: "TURN", river: "RIVER", showdown: "SHOWDOWN", done: "" })[h.street] : "";

  keyRef.current = (k) => {
    if (pendingFb) { if (k === "n" || k === "c") continueFromStudy(); return; }
    if (waitingNext) { if (k === "n" || k === "c") newHand(); return; }
    if (!h || h.finished || h.toAct !== heroIdx) return;
    const legal = Eng.legalActions(h).map((a) => a.type);
    const me = h.players[heroIdx];
    const oppBet = Eng.streetInvest(h, botIdx);
    if (k === "f") heroAct(legal.includes("fold") ? { type: "fold" } : { type: "check" });
    else if (k === "c") heroAct(legal.includes("call") ? { type: "call", to: oppBet } : { type: "check" });
    else if (k === "r" && (legal.includes("raise") || legal.includes("bet")) && sizeRef.current != null) heroAct({ type: legal.includes("raise") ? "raise" : "bet", to: sizeRef.current });
    else if (k === "a" && (legal.includes("raise") || legal.includes("bet"))) heroAct({ type: legal.includes("raise") ? "raise" : "bet", to: Eng.streetInvest(h, heroIdx) + me.stack });
  };

  return (
    <div className="app">
      <Header mode={mode} setMode={setMode} styleKey={styleKey} session={session} onSettings={() => setSettings(true)} onMenu={onMenu} muted={muted} onToggleSound={toggleSound} />
      <div className="main">
        <div className="tablewrap">
          <div className="felt">
            {h && <div className="street-flag">{streetLabel}</div>}
            {h && spotForRail && spotForRail.showEquity && (
              <div className="live-eq">
                <div className="v" style={{ color: "var(--accent)" }}>{window.UI.fmt(spotForRail.equity)}%</div>
                <div className="l">your equity</div>
              </div>
            )}
            {h && (
              <Seat player={h.players[botIdx]} isHero={false} active={!h.finished && h.toAct === botIdx}
                position={botPos} reveal={reveal} bet={Eng.streetInvest(h, botIdx)} />
            )}
            {h && (
              <div className="center">
                <Board board={h.board} />
                <Pot pot={Eng.potNow(h)} />
                {h.finished && h.result && (
                  <div className="mono" style={{ fontSize: 13, color: "var(--text-2)", textAlign: "center" }}>
                    {h.result.type === "fold"
                      ? (h.result.winner === heroIdx ? `${h.players[botIdx].name} folds \u2014 you win` : "You fold")
                      : (h.result.winner === heroIdx ? `You win \u2014 ${h.result.hands[heroIdx]}`
                        : h.result.winner === botIdx ? `${h.players[botIdx].name} wins \u2014 ${h.result.hands[botIdx]}`
                        : "Split pot")}
                  </div>
                )}
              </div>
            )}
            {h && (
              <Seat player={h.players[heroIdx]} isHero active={!h.finished && h.toAct === heroIdx}
                position={heroPos} reveal bet={Eng.streetInvest(h, heroIdx)} />
            )}
            {pendingFb && <FeedbackOverlay fb={pendingFb.fb} onContinue={continueFromStudy} />}
            {toast && <Toast fb={toast} />}
            <button className="coach-fab" onClick={() => setRailOpen(true)}>COACH</button>
          </div>
          {h && (
            <ActionBar hand={h} hero={heroIdx} onAction={heroAct} busy={busy}
              waitingNext={waitingNext} onNext={newHand} mode={mode} sizeRef={sizeRef} />
          )}
        </div>
        <CoachRail open={railOpen} onClose={() => setRailOpen(false)} tab={railTab} setTab={setRailTab}
          spot={spotForRail} lastFb={lastFb} session={session} hand={h} mode={mode} reviews={reviews} />
      </div>
      <SettingsModal open={settings} onClose={() => setSettings(false)} styleKey={styleKey} setStyle={setStyleKey}
        stack={stack} setStack={setStack} onReset={resetSession} onDrill={startSpot} />
    </div>
  );
}

function Root() {
  const [screen, setScreen] = useState("menu");
  if (screen === "menu") return <window.TournamentUI.Menu onPick={setScreen} />;
  if (screen === "tourney") return <window.TournamentUI.TournamentApp onMenu={() => setScreen("menu")} />;
  return <App onMenu={() => setScreen("menu")} />;
}
ReactDOM.createRoot(document.getElementById("root")).render(<Root />);
