/* modes/training.js — Training: coordinates trainer, guess-the-move from
   database games, tactics mined from your own games (engine-verified),
   with ratings, streaks, accuracy and session history. Serious tone. */
'use strict';

import { h, clear } from '../ui/dom.js';
import { icon } from '../ui/icons.js';
import { showDialog, toast, makePanel, emptyState, loadingState } from '../ui/widgets.js';
import { GameDoc } from '../core/doc.js';
import { Chess, algebraic } from '../core/rules.js';
import { settings } from '../svc/settings.js';
import { engines, whiteScore } from '../svc/engines.js';
import { gamedb } from '../svc/gamedb.js';
import { idb } from '../svc/idb.js';
import { commands } from '../svc/commands.js';
import { sound } from '../svc/sound.js';
import { BoardSession } from '../panels/session.js';

const TYPES = [
  { id: 'tactics', label: 'Tactics from your games', icon: 'target', desc: 'Positions where a game in your database went wrong. Find the punishing move — verified by the engine.' },
  { id: 'guess', label: 'Guess the move', icon: 'question', desc: 'Replay a database game and predict the next move that was played.' },
  { id: 'coords', label: 'Coordinates', icon: 'grid', desc: 'Click the named square before time runs out. Trains board vision in both orientations.' },
];

export const trainingMode = {
  id: 'training', title: 'Training', icon: 'target', kbd: 'Ctrl+5',

  mount(root, ctx) {
    this.ctx = ctx;
    this.stats = null;
    this.active = null;         // active exercise type id
    this.state = null;

    this.homeEl = h('div', { style: { flex: 1, overflowY: 'auto', padding: '16px', display: 'flex', flexDirection: 'column', gap: '14px', maxWidth: '720px', margin: '0 auto', width: '100%' } });
    this.exerciseEl = h('div', { style: { flex: 1, display: 'none', minHeight: 0, flexDirection: 'column' } });
    root.append(h('div', { style: { display: 'flex', flexDirection: 'column', height: '100%' } }, this.homeEl, this.exerciseEl));

    commands.register({ id: 'training.start', title: 'Start training session', category: 'Training', icon: 'target', run: () => { ctx.switchMode('training'); this.showHome(); } });

    this.loadStats().then(() => this.showHome());
  },

  activate() {},
  deactivate() { this.session?.update && this.session.update(false); },
  getDoc() { return this.state?.doc || null; },
  getSession() { return this.session || null; },

  async loadStats() {
    this.stats = (await idb.get('training', 'stats')) || {
      id: 'stats',
      tactics: { rating: 1200, done: 0, correct: 0, streak: 0, best: 0 },
      guess: { rating: 1200, done: 0, correct: 0, streak: 0, best: 0 },
      coords: { best: 0, sessions: 0, total: 0 },
      history: [],
    };
  },
  async saveStats() { await idb.put('training', this.stats); },

  showHome() {
    this.exerciseEl.style.display = 'none';
    this.homeEl.style.display = '';
    clear(this.homeEl);
    const s = this.stats;
    this.homeEl.append(
      h('div', { style: { fontSize: 'var(--fs-16)', fontWeight: 700 } }, 'Training'),
      h('div.stat-strip', {},
        h('div.stat-cell', {}, h('div.sc-label', {}, 'Tactics rating'), h('div.sc-value', {}, String(Math.round(s.tactics.rating)))),
        h('div.stat-cell', {}, h('div.sc-label', {}, 'Guess rating'), h('div.sc-value', {}, String(Math.round(s.guess.rating)))),
        h('div.stat-cell', {}, h('div.sc-label', {}, 'Best streak'), h('div.sc-value', {}, String(Math.max(s.tactics.best, s.guess.best)))),
        h('div.stat-cell', {}, h('div.sc-label', {}, 'Coords best'), h('div.sc-value', {}, s.coords.best + ' /30s'))),
    );
    for (const t of TYPES) {
      const st = s[t.id];
      const acc = st.done ? Math.round(st.correct / st.done * 100) + '%' : '—';
      this.homeEl.append(h('div', { style: { border: '1px solid var(--border)', background: 'var(--bg-panel)', display: 'flex', alignItems: 'center', gap: '14px', padding: '14px' } },
        h('span', { html: icon(t.icon), style: { color: 'var(--accent-text)' } }),
        h('div.grow', {},
          h('div', { style: { fontWeight: 600 } }, t.label),
          h('div.faint', { style: { fontSize: 'var(--fs-12)', marginTop: '2px' } }, t.desc),
          t.id !== 'coords' ? h('div.faint', { style: { fontSize: 'var(--fs-11)', marginTop: '4px' } }, `${st.done} attempted · accuracy ${acc} · streak ${st.streak}`) : null),
        h('button.btn.btn-primary', { onclick: () => this.start(t.id) }, 'Start')));
    }
    if (s.history.length) {
      this.homeEl.append(h('div.settings-group-title', {}, 'Recent sessions'),
        ...s.history.slice(-8).reverse().map(x =>
          h('div.list-row', { style: { cursor: 'default' } },
            h('span.grow', {}, `${TYPES.find(t => t.id === x.type)?.label || x.type}`),
            h('span.muted', {}, x.result),
            h('span.faint', {}, new Date(x.at).toLocaleDateString()))));
    }
  },

  openExercise() {
    this.homeEl.style.display = 'none';
    this.exerciseEl.style.display = 'flex';
    clear(this.exerciseEl);
  },

  async start(type) {
    this.active = type;
    if (type === 'coords') return this.startCoords();
    if (type === 'guess') return this.startGuess();
    if (type === 'tactics') return this.startTactics();
  },

  buildExerciseFrame(title, controls) {
    this.openExercise();
    const toolbar = h('div.toolbar', {},
      h('button.btn.btn-ghost.btn-sm', { html: icon('chevronL') + '<span>Back</span>', onclick: () => { this.state = null; this.showHome(); } }),
      h('span.mode-toolbar-title', {}, title),
      h('div.toolbar-sep'),
      ...(controls || []),
      h('div.toolbar-spacer'),
      this.exStatus = h('span.mode-toolbar-title'));
    const boardWrap = h('div', { style: { flex: 1, minHeight: 0 } });
    this.exerciseEl.append(toolbar, boardWrap);
    return boardWrap;
  },

  /* ================= coordinates ================= */
  startCoords() {
    const wrap = this.buildExerciseFrame('Coordinates — click the named square');
    const targetEl = h('div', { style: { fontSize: '42px', fontWeight: 800, textAlign: 'center', padding: '6px', fontFamily: 'var(--font-mono)' } }, '—');
    const doc = new GameDoc();
    this.state = { doc, score: 0, timeLeft: 30, target: null, running: false };
    this.session = new BoardSession({
      doc, playerBars: false, evalBar: false,
      userTurn: () => null,
    });
    wrap.append(targetEl, this.session.el);
    // click handling on the raw board
    this.session.board.el.addEventListener('pointerdown', e => {
      const st = this.state;
      if (!st || !st.running || st.target == null) return;
      const sq = this.session.board._fromPoint(e.clientX, e.clientY);
      if (sq < 0) return;
      if (algebraic(sq) === st.target) { st.score++; sound.correct(); }
      else { st.timeLeft = Math.max(0, st.timeLeft - 2); sound.wrong(); }
      st.target = this.randSquare();
      targetEl.textContent = st.target;
      this.exStatus.textContent = `Score ${st.score} · ${Math.ceil(st.timeLeft)}s`;
    });
    const startBtn = h('button.btn.btn-primary', {
      style: { margin: '0 auto 12px', display: 'block' },
      onclick: () => {
        const st = this.state;
        st.running = true; st.score = 0; st.timeLeft = 30;
        st.target = this.randSquare();
        targetEl.textContent = st.target;
        if (Math.random() < 0.5) this.session.board.setOrientation(Math.random() < 0.5 ? 'w' : 'b');
        startBtn.style.display = 'none';
        const timer = setInterval(async () => {
          if (!this.state || this.state !== st) return clearInterval(timer);
          st.timeLeft -= 0.1;
          this.exStatus.textContent = `Score ${st.score} · ${Math.max(0, st.timeLeft).toFixed(0)}s`;
          if (st.timeLeft <= 0) {
            clearInterval(timer);
            st.running = false;
            targetEl.textContent = `Final: ${st.score}`;
            startBtn.style.display = 'block';
            startBtn.textContent = 'Again';
            const cs = this.stats.coords;
            cs.sessions++; cs.total += st.score;
            if (st.score > cs.best) { cs.best = st.score; toast('New coordinates record: ' + st.score, 'ok'); }
            this.stats.history.push({ type: 'coords', result: st.score + ' squares', at: Date.now() });
            await this.saveStats();
          }
        }, 100);
      },
    }, 'Start 30-second run');
    wrap.append(startBtn);
    this.exStatus.textContent = 'Press start';
  },

  randSquare() {
    return 'abcdefgh'[Math.floor(Math.random() * 8)] + (1 + Math.floor(Math.random() * 8));
  },

  /* ================= guess the move ================= */
  async startGuess() {
    const games = await gamedb.query({ limit: 4000 });
    const usable = games.filter(g => g.plyCount >= 24);
    if (!usable.length) {
      this.openExercise();
      this.exerciseEl.append(emptyState({
        icon: 'database', title: 'No games available',
        hint: 'Guess-the-move replays games from your databases. Import PGN in Database mode first.',
        action: h('button.btn.btn-primary', { onclick: () => this.ctx.switchMode('database') }, 'Go to Database'),
      }));
      return;
    }
    const wrap = this.buildExerciseFrame('Guess the move', [
      this.btnHint = h('button.btn.btn-sm', { html: icon('bulb') + '<span>Hint</span>', style: { display: settings.get('training.hints') ? '' : 'none' }, onclick: () => this.guessHint() }),
      this.btnSkip = h('button.btn.btn-sm', { html: icon('chevronR') + '<span>Skip</span>', onclick: () => this.nextGuess() }),
    ]);
    this.state = { usable, doc: new GameDoc(), score: 0, total: 0 };
    this.session = new BoardSession({
      doc: this.state.doc,
      userTurn: () => this.state?.awaiting ? this.state.sideToGuess : null,
      onUserMove: uci => this.onGuess(uci),
    });
    wrap.append(this.session.el);
    this.nextGuess();
  },

  async nextGuess() {
    const st = this.state;
    if (!st) return;
    const g = st.usable[Math.floor(Math.random() * st.usable.length)];
    const full = await gamedb.getGame(g.id);
    if (!full) return this.nextGuess();
    const doc = new GameDoc();
    const pos = new Chess();
    const norm = s => s.replace(/[+#]/g, '');
    const ply = 8 + Math.floor(Math.random() * Math.min(40, full.sans.length - 10));
    let ok = true;
    for (let i = 0; i < ply; i++) {
      const legal = pos.legalMoves();
      const m = legal.find(x => norm(pos.san(x, legal)) === norm(full.sans[i]));
      if (!m) { ok = false; break; }
      doc.playMove(m);
      pos.makeMove(m);
    }
    if (!ok) return this.nextGuess();
    const legal = pos.legalMoves();
    const answer = legal.find(x => norm(pos.san(x, legal)) === norm(full.sans[ply]));
    if (!answer) return this.nextGuess();
    st.doc = doc;
    st.answerUci = pos.uci(answer);
    st.answerSan = pos.san(answer);
    st.game = full;
    st.sideToGuess = pos.turn === 1 ? 'w' : 'b';
    st.awaiting = true;
    this.session.bindDoc(doc);
    this.session.board.setOrientation(st.sideToGuess);
    this.session.setPlayers({ w: { name: full.white }, b: { name: full.black } });
    this.exStatus.textContent = `${full.white} – ${full.black} (${full.event}). What did ${st.sideToGuess === 'w' ? 'White' : 'Black'} play? · Score ${st.score}/${st.total}`;
  },

  guessHint() {
    const st = this.state;
    if (!st?.awaiting) return;
    const from = st.answerUci.slice(0, 2);
    const sq = (from.charCodeAt(1) - 49) * 16 + (from.charCodeAt(0) - 97);
    this.session.board.setArrows([{ from: sq, to: sq, color: 'engine' }]);
    this.session.board.userShapes = [{ sq, color: 'blue' }];
    this.session.board._paintOverlays();
  },

  async onGuess(uci) {
    const st = this.state;
    if (!st?.awaiting) return;
    st.awaiting = false;
    st.total++;
    const s = this.stats.guess;
    s.done++;
    const exact = uci === st.answerUci;
    let good = exact;
    if (!exact && engines.available().length) {
      // engine-check: is the guess nearly as strong as the game move?
      const fen = st.doc.position.fen();
      const [a, b] = await Promise.all([
        engines.go({ engine: engines.has('stockfish') ? 'stockfish' : 'rush', instance: 'train', fen, moves: [uci], movetime: 250 }),
        engines.go({ engine: engines.has('stockfish') ? 'stockfish' : 'rush', instance: 'train', fen, moves: [st.answerUci], movetime: 250 }),
      ]);
      if (a && b && a.scoreCp != null && b.scoreCp != null) good = (b.scoreCp - a.scoreCp) <= 30;
    }
    if (good) {
      st.score++;
      s.correct++; s.streak++; s.best = Math.max(s.best, s.streak);
      s.rating += 8;
      sound.correct();
      this.exStatus.textContent = exact ? `Correct: ${st.answerSan}` : `Good move! The game went ${st.answerSan}.`;
    } else {
      s.streak = 0;
      s.rating = Math.max(400, s.rating - 6);
      sound.wrong();
      this.exStatus.textContent = `The game move was ${st.answerSan}`;
      const m = st.doc.position.findUci(st.answerUci);
      if (m) this.session.board.setArrows([{ from: m.from, to: m.to, color: 'green' }]);
    }
    await this.saveStats();
    setTimeout(() => {
      if (this.state === st && settings.get('training.autoNext')) this.nextGuess();
    }, good ? 900 : 2200);
  },

  /* ================= tactics from your games ================= */
  async startTactics() {
    if (!engines.available().length) {
      this.openExercise();
      this.exerciseEl.append(emptyState({ icon: 'cpu', title: 'Engines offline', hint: 'Tactics mining verifies solutions with an engine. Start server.py first.' }));
      return;
    }
    const games = await gamedb.query({ limit: 4000 });
    const usable = games.filter(g => g.plyCount >= 16);
    if (!usable.length) {
      this.openExercise();
      this.exerciseEl.append(emptyState({
        icon: 'database', title: 'No games to mine',
        hint: 'Tactics are mined from games in your databases (including your own played games). Import or play some first.',
        action: h('button.btn.btn-primary', { onclick: () => this.ctx.switchMode('database') }, 'Go to Database'),
      }));
      return;
    }
    const wrap = this.buildExerciseFrame('Tactics — find the best move', [
      this.btnHint = h('button.btn.btn-sm', { html: icon('bulb') + '<span>Hint</span>', style: { display: settings.get('training.hints') ? '' : 'none' }, onclick: () => this.tacticsHint() }),
      this.btnSkip = h('button.btn.btn-sm', { html: icon('chevronR') + '<span>Skip</span>', onclick: () => this.mineTactic() }),
    ]);
    this.state = { usable, doc: new GameDoc(), score: 0, total: 0 };
    this.session = new BoardSession({
      doc: this.state.doc,
      userTurn: () => this.state?.awaiting ? this.state.side : null,
      onUserMove: uci => this.onTacticMove(uci),
    });
    this.mineBox = h('div', { style: { display: 'none' } });
    wrap.append(this.session.el);
    this.mineTactic();
  },

  async mineTactic() {
    const st = this.state;
    if (!st) return;
    st.awaiting = false;
    this.exStatus.textContent = 'Mining a tactic from your games…';
    const engine = engines.has('stockfish') ? 'stockfish' : 'rush';
    const norm = s => s.replace(/[+#]/g, '');
    for (let attempt = 0; attempt < 6; attempt++) {
      const g = st.usable[Math.floor(Math.random() * st.usable.length)];
      const full = await gamedb.getGame(g.id);
      if (!full) continue;
      // replay, probing random plies for eval swings
      const pos = new Chess(full.fen || undefined);
      const evals = [];
      let broke = false;
      const probeStart = Math.floor(Math.random() * Math.max(1, full.sans.length - 14));
      for (let i = 0; i < full.sans.length && i < probeStart + 12; i++) {
        const legal = pos.legalMoves();
        const m = legal.find(x => norm(pos.san(x, legal)) === norm(full.sans[i]));
        if (!m) { broke = true; break; }
        if (i >= probeStart) {
          const res = await engines.go({ engine, instance: 'train', fen: pos.fen(), movetime: 180 });
          if (!res) { broke = true; break; }
          const sc = whiteScore(res, pos.turn === 1 ? 'w' : 'b');
          evals.push({ ply: i, fenBefore: pos.fen(), cp: sc ? (sc.mate != null ? Math.sign(sc.mate) * 5000 : sc.cp) : 0 });
        }
        pos.makeMove(m);
        if (this.state !== st) return;
      }
      if (broke || evals.length < 3) continue;
      // find a swing: eval before move i fine, after move i lost ≥ 250cp for the mover
      for (let j = 0; j + 1 < evals.length; j++) {
        const before = evals[j], after = evals[j + 1];
        const moverWhite = before.fenBefore.split(' ')[1] === 'w';
        const loss = moverWhite ? before.cp - after.cp : after.cp - before.cp;
        if (loss >= 250 && Math.abs(before.cp) < 400) {
          // puzzle: position AFTER the blunder; solver punishes it
          const res = await engines.go({ engine, instance: 'train', fen: after.fenBefore, movetime: 600 });
          if (!res?.bestmove) break;
          const doc = new GameDoc({ fen: after.fenBefore });
          st.doc = doc;
          st.fen = after.fenBefore;
          st.bestUci = res.bestmove;
          st.bestCp = res.scoreCp ?? null;
          st.engine = engine;
          st.side = after.fenBefore.split(' ')[1] === 'w' ? 'w' : 'b';
          st.game = full;
          st.awaiting = true;
          this.session.bindDoc(doc);
          this.session.board.setOrientation(st.side);
          this.session.setPlayers({ w: { name: full.white }, b: { name: full.black } });
          this.exStatus.textContent = `${st.side === 'w' ? 'White' : 'Black'} to move and punish. (${full.white} – ${full.black}) · Score ${st.score}/${st.total}`;
          return;
        }
      }
    }
    this.exStatus.textContent = 'No tactic found this pass — try again';
    if (this.state === st) setTimeout(() => this.state === st && !st.awaiting && this.mineTactic(), 400);
  },

  tacticsHint() {
    const st = this.state;
    if (!st?.awaiting) return;
    const m = st.doc.position.findUci(st.bestUci);
    if (m) { this.session.board.userShapes = [{ sq: m.from, color: 'blue' }]; this.session.board._paintOverlays(); }
  },

  async onTacticMove(uci) {
    const st = this.state;
    if (!st?.awaiting) return;
    st.awaiting = false;
    st.total++;
    const s = this.stats.tactics;
    s.done++;
    let good = uci === st.bestUci;
    if (!good) {
      const [mine, best] = await Promise.all([
        engines.go({ engine: st.engine, instance: 'train', fen: st.fen, moves: [uci], movetime: 300 }),
        engines.go({ engine: st.engine, instance: 'train', fen: st.fen, moves: [st.bestUci], movetime: 300 }),
      ]);
      if (mine && best && mine.scoreCp != null && best.scoreCp != null)
        good = (best.scoreCp - mine.scoreCp) <= 40;   // alternative winning move accepted
    }
    if (good) {
      st.score++;
      s.correct++; s.streak++; s.best = Math.max(s.best, s.streak);
      s.rating += 10;
      sound.correct();
      this.exStatus.textContent = 'Correct!';
    } else {
      s.streak = 0;
      s.rating = Math.max(400, s.rating - 8);
      sound.wrong();
      const m = st.doc.position.findUci(st.bestUci);
      const san = m ? st.doc.position.san(m) : st.bestUci;
      this.exStatus.textContent = `The winning move was ${san}`;
      if (m) this.session.board.setArrows([{ from: m.from, to: m.to, color: 'green' }]);
    }
    this.stats.history.push({ type: 'tactics', result: good ? 'solved' : 'missed', at: Date.now() });
    if (this.stats.history.length > 100) this.stats.history = this.stats.history.slice(-100);
    await this.saveStats();
    setTimeout(() => { if (this.state === st && settings.get('training.autoNext')) this.mineTactic(); }, good ? 900 : 2400);
  },
};
