// play-inline.jsx — in-place "Play this position vs Stockfish".
//
// A self-contained board that plays a given FEN against the in-browser
// Stockfish at the user's profile default level. No level / think-time
// controls — the profile default is the point (change it in Profile). Just
// board + a status strip + Undo / Reset / Stop.
//
// Host screens hold a `playOn` boolean and render <window.PlayFromHere> in
// place of their normal board when it's on, plus <window.PlayFromHerePill> as
// the entry control when it's off. Reuses window.PlayGame / PlayEngine /
// AtelierBoard. Loaded eagerly (needs to be available the moment a learner
// taps the pill on any already-rendered screen).
//
// See docs/PLAY_FROM_HERE.md.

const { useState: useSPI, useEffect: useEPI, useRef: useRPI } = React;

// Strength presets — mirror PLAY_SKILL_PRESETS in atelier-play.jsx.
window.SF_LEVELS = [
  { label: 'Casual', level: 3,  desc: 'Loose. Good for warm-ups.' },
  { label: 'Club',   level: 8,  desc: 'Club-strength baseline.' },
  { label: 'Strong', level: 12, desc: 'Punishes loose play.' },
  { label: 'Master', level: 16, desc: 'Very few mistakes.' },
  { label: 'Max',    level: 20, desc: 'Engine at full strength.' },
];

// Nearest preset label for an arbitrary skill number.
window.sfLevelLabel = function (n) {
  const presets = window.SF_LEVELS;
  let best = presets[1];
  for (const p of presets) if (Math.abs(p.level - n) < Math.abs(best.level - n)) best = p;
  return best.label;
};

// Resolve the user's default play level from tweaks (default Club / 8).
window.sfDefaultLevel = function (tweaks) {
  const n = tweaks && typeof tweaks.stockfishLevel === 'number' ? tweaks.stockfishLevel : 8;
  return Math.max(0, Math.min(20, n));
};

// Build a FEN from a { 'e4': 'wP', ... } position map + side to move. Used by
// surfaces that carry a position map instead of a FEN (drills). Castling/en
// passant are dropped (— —), which is fine for playing on from a mid-game spot.
window.posToFen = function (pos, side) {
  if (!pos) return null;
  const FILES = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
  const rows = [];
  for (let r = 8; r >= 1; r--) {
    let row = '', empty = 0;
    for (const f of FILES) {
      const code = pos[f + r]; // e.g. 'wP'
      if (!code) { empty++; continue; }
      if (empty) { row += empty; empty = 0; }
      const color = code[0], type = code[1];
      row += color === 'w' ? type.toUpperCase() : type.toLowerCase();
    }
    if (empty) row += empty;
    rows.push(row);
  }
  const stm = String(side || 'white').toLowerCase().charAt(0) === 'b' ? 'b' : 'w';
  return rows.join('/') + ' ' + stm + ' - - 0 1';
};

// ── Entry pill ────────────────────────────────────────────────────────────────
function PlayFromHerePill({ palette, onClick, label, quiet }) {
  const C = palette;
  if (quiet) {
    // Low-emphasis text link — used mid-puzzle so it never competes with solving.
    return (
      <button
        onClick={onClick}
        style={{
          appearance: 'none', background: 'none', border: 'none', padding: 0,
          cursor: 'pointer', textAlign: 'left',
          fontFamily: window.ATELIER_TYPE.display, fontStyle: 'italic', fontSize: 13,
          color: C.umberSoft, opacity: 0.85,
          textDecoration: 'underline', textUnderlineOffset: 3, textDecorationColor: C.umberHair,
        }}
      >{label || 'Or just play this position →'}</button>
    );
  }
  return (
    <button
      onClick={onClick}
      style={{
        appearance: 'none', cursor: 'pointer',
        display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 8,
        minHeight: 44, padding: '0 18px', borderRadius: 99,
        border: `0.6px solid ${C.umberHair}`,
        background: window.withAlpha(C.brassDeep, 0.08),
        color: C.umber,
        fontFamily: window.ATELIER_TYPE.ui, fontSize: 10, letterSpacing: 2,
        textTransform: 'uppercase',
      }}
    >
      <span aria-hidden="true" style={{ fontSize: 13, lineHeight: 1 }}>♟</span>
      {label || 'Play this position'}
    </button>
  );
}

// ── In-place play board ─────────────────────────────────────────────────────────
function PlayFromHere({ palette, fen, sideToMove, level, onExit, setRoute, moves }) {
  const C = palette;
  // Normalize case-insensitively — callers pass 'black'/'white' (lessons) but
  // also 'Black'/'White' or 'Black to move' (drill sideLabel). A bare ===
  // 'black' check would flip the player's color and board orientation.
  const userColor = String(sideToMove || '').toLowerCase().charAt(0) === 'b' ? 'b' : 'w';
  const engineColor = userColor === 'w' ? 'b' : 'w';
  const lvl = typeof level === 'number' ? level : 8;

  const [position, setPosition] = useSPI(null);
  const [selected, setSelected] = useSPI(null);
  const [legalDests, setLegalDests] = useSPI([]);
  const [thinking, setThinking] = useSPI(false);
  const [statusMsg, setStatusMsg] = useSPI('Loading engine…');
  const [gameOver, setGameOver] = useSPI(null);
  const [ply, setPly] = useSPI(0); // bump to re-enable Undo button after a move

  const engineRef = useRPI(null);
  const gameRef = useRPI(null);
  const seqRef = useRPI(0);

  // Build game + engine when the position changes (or on mount).
  useEPI(() => {
    if (!fen) return undefined;
    let cancelled = false;
    let eng = null;
    (async () => {
      const g = await window.PlayGame.create(fen);
      if (cancelled) return;
      // Optional opening moves (e.g. the puzzle's solution line) so play
      // continues from after them rather than the bare FEN.
      if (Array.isArray(moves)) {
        for (const m of moves) { if (m) g.moveFromUci(m); }
      }
      gameRef.current = g;
      setPosition(g.positionMap());
      setSelected(null); setLegalDests([]); setGameOver(null); setPly(0);
      try {
        eng = window.PlayEngine.start();
        await eng.ready();
      } catch (err) {
        if (!cancelled) setStatusMsg('Engine failed to load. Tap Stop and try again.');
        return;
      }
      if (cancelled) { eng.terminate(); return; }
      engineRef.current = eng;
      eng.newGame();
      eng.setSkillLevel(lvl);
      setStatusMsg('Your move.');
      if (g.turn() === engineColor && !g.isGameOver()) askEngine(g, eng);
    })();
    return () => {
      cancelled = true;
      // Invalidate any in-flight engine reply so a late bestmove can't
      // setState after Stop / unmount / a position change.
      seqRef.current++;
      if (eng) eng.terminate();
      engineRef.current = null;
    };
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [fen]);

  // Keep the running engine's strength in sync if the profile default changes
  // while a session is open (otherwise the label and the engine diverge).
  useEPI(() => {
    if (engineRef.current) engineRef.current.setSkillLevel(lvl);
  }, [lvl]);

  function askEngine(g, eng) {
    if (!g || !eng || g.isGameOver()) return;
    const my = ++seqRef.current;
    setThinking(true);
    setStatusMsg('Stockfish is thinking…');
    const moves = g.history().map((m) => m.from + m.to + (m.promotion || ''));
    eng.setPosition(g.initialFen, moves);
    eng.requestMove({ movetime: 800 })
      .then(({ bestmove }) => {
        if (my !== seqRef.current) return;
        if (!bestmove || bestmove === '(none)') { setThinking(false); finish(g); return; }
        g.moveFromUci(bestmove);
        setPosition(g.positionMap());
        setPly((p) => p + 1);
        setThinking(false);
        if (g.isGameOver()) finish(g);
        else setStatusMsg('Your move.');
      })
      .catch((err) => {
        if (my !== seqRef.current) return;
        setThinking(false);
        setStatusMsg('Engine error.');
      });
  }

  function finish(g) {
    const s = g.status();
    setGameOver(s);
    if (s === 'checkmate') {
      const loser = g.turn();
      setStatusMsg(loser === userColor ? 'Checkmate — Stockfish won.' : 'Checkmate — you won!');
    } else if (s === 'stalemate') {
      setStatusMsg('Stalemate — a draw.');
    } else {
      setStatusMsg('Draw.');
    }
  }

  function tryMove(from, to) {
    const g = gameRef.current, eng = engineRef.current;
    if (!g || thinking || gameOver) return;
    if (g.turn() !== userColor) return;
    const code = g.positionMap()[from];
    if (!code || code[0] !== userColor) return;
    const dests = g.legalMovesFrom(from).map((m) => m.to);
    if (!dests.includes(to)) { setSelected(from); setLegalDests(dests); return; }
    const uci = g.uciFor(from, to);
    if (!uci) { setSelected(null); setLegalDests([]); return; }
    g.moveFromUci(uci);
    setPosition(g.positionMap());
    setSelected(null); setLegalDests([]);
    setPly((p) => p + 1);
    if (g.isGameOver()) { finish(g); return; }
    if (eng) askEngine(g, eng);
  }

  function onSelect(sq) {
    const g = gameRef.current;
    if (!g || thinking || gameOver) return;
    if (g.turn() !== userColor) return;
    if (sq == null) { setSelected(null); setLegalDests([]); return; }
    if (selected && legalDests.includes(sq)) { tryMove(selected, sq); return; }
    const code = g.positionMap()[sq];
    if (!code || code[0] !== userColor) { setSelected(null); setLegalDests([]); return; }
    setSelected(sq);
    setLegalDests(g.legalMovesFrom(sq).map((m) => m.to));
  }

  function onSquareClick(sq) {
    if (!selected) return;
    if (!legalDests.includes(sq)) { setSelected(null); setLegalDests([]); return; }
    tryMove(selected, sq);
  }

  function undo() {
    const g = gameRef.current, eng = engineRef.current;
    if (!g || thinking) return;
    seqRef.current++; // cancel any pending engine reply
    // Take back to the user's most recent move: pop until it's the user's turn
    // again (usually engine reply + user move = 2 plies).
    let popped = 0;
    while (g.history().length > 0 && popped < 2) {
      if (typeof g.undo === 'function') g.undo();
      popped++;
      if (g.turn() === userColor) break;
    }
    setPosition(g.positionMap());
    setSelected(null); setLegalDests([]); setGameOver(null); setThinking(false);
    setPly((p) => p + 1);
    setStatusMsg('Your move.');
  }

  function reset() {
    const eng = engineRef.current;
    seqRef.current++;
    setThinking(false);
    window.PlayGame.create(fen).then((ng) => {
      if (Array.isArray(moves)) { for (const m of moves) { if (m) ng.moveFromUci(m); } }
      gameRef.current = ng;
      setPosition(ng.positionMap());
      setSelected(null); setLegalDests([]); setGameOver(null); setPly(0);
      if (eng) { eng.newGame(); eng.setSkillLevel(lvl); }
      if (ng.turn() === engineColor && !ng.isGameOver()) askEngine(ng, eng);
      else setStatusMsg('Your move.');
    });
  }

  const canUndo = ply > 0 && !thinking && !!(gameRef.current && typeof gameRef.current.undo === 'function');

  const chipStyle = {
    appearance: 'none', cursor: 'pointer',
    // 44px min height — these are the active in-play controls; match the app's
    // primary mobile control sizing (sticky CTA 52, lesson CTA 44-48) so they're
    // a comfortable tap target, not the 30px a chip would otherwise be.
    minHeight: 44, padding: '0 18px', borderRadius: 99,
    display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
    border: `0.6px solid ${C.umberHair}`, background: C.cream,
    color: C.umberSoft,
    fontFamily: window.ATELIER_TYPE.ui, fontSize: 10, letterSpacing: 2, textTransform: 'uppercase',
  };

  return (
    <div style={{ width: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 12 }}>
      {/* Status strip */}
      <div style={{
        width: '100%', maxWidth: 560, display: 'flex', alignItems: 'center', gap: 12,
        flexWrap: 'wrap',
      }}>
        <span aria-hidden="true" style={{
          width: 8, height: 8, borderRadius: 99, flexShrink: 0,
          background: thinking ? C.brassDeep : (C.celadon || C.brass),
        }} />
        <span style={{
          fontFamily: window.ATELIER_TYPE.ui, fontSize: 10, letterSpacing: 1.5,
          textTransform: 'uppercase', color: C.umberFaint,
        }}>
          You: {userColor === 'w' ? 'White' : 'Black'} ·{' '}
          <button
            onClick={() => setRoute && setRoute('profile')}
            title="Change your default strength in Profile"
            style={{
              appearance: 'none', background: 'none', border: 'none', padding: 0, cursor: 'pointer',
              fontFamily: window.ATELIER_TYPE.ui, fontSize: 10, letterSpacing: 1.5,
              textTransform: 'uppercase', color: C.brassDeep, textDecoration: 'underline',
              textUnderlineOffset: 2,
            }}
          >Stockfish {window.sfLevelLabel(lvl)}</button>
        </span>
        <span style={{ flex: 1 }} />
        <span style={{ fontFamily: window.ATELIER_TYPE.display, fontStyle: 'italic', fontWeight: 400, fontSize: 14, color: C.umber }}>
          {statusMsg}
        </span>
      </div>

      {/* Board */}
      <div style={{
        background: C.boneDeep, padding: 12, borderRadius: 8,
        boxShadow: '0 30px 50px -28px rgba(42,34,27,0.45)',
        width: 'min(100%, 560px)', aspectRatio: '1 / 1', boxSizing: 'border-box',
      }}>
        {position && (
          <window.AtelierBoard
            palette={C}
            layers={{ heatmap: false, hero: false, trajectory: false, tension: false }}
            selected={selected}
            onSelect={onSelect}
            onSquareClick={onSquareClick}
            onDragMove={tryMove}
            legalMoves={selected ? legalDests : null}
            size={2048}
            noTrajectoryOnSelect={true}
            positionOverride={position}
            flipped={userColor === 'b'}
            showCoords={userColor !== 'b'}
          />
        )}
      </div>

      {/* Controls — minimal: undo, reset, stop. No level slider by design. */}
      <div style={{ width: '100%', maxWidth: 560, display: 'flex', gap: 8, justifyContent: 'center', flexWrap: 'wrap' }}>
        <button style={{ ...chipStyle, opacity: canUndo ? 1 : 0.4, pointerEvents: canUndo ? 'auto' : 'none' }} onClick={undo}>↶ Undo</button>
        <button style={chipStyle} onClick={reset}>↻ Reset</button>
        <button
          style={{ ...chipStyle, borderColor: C.umber, color: C.umber }}
          onClick={() => { seqRef.current++; if (engineRef.current) { try { engineRef.current.terminate(); } catch (_) {} engineRef.current = null; } onExit && onExit(); }}
        >✕ Stop</button>
      </div>
    </div>
  );
}

Object.assign(window, { PlayFromHere, PlayFromHerePill });
