/* modes/broadcast.js — Broadcast & spectator: load a round's PGN (many
   games), watch them replay on a board wall with configurable move delay,
   feature any board full-size with live evaluation, results list,
   presentation-friendly fullscreen. */
'use strict';

import { h, clear } from '../ui/dom.js';
import { icon } from '../ui/icons.js';
import { promptDialog, toast, makePanel, emptyState } from '../ui/widgets.js';
import { GameDoc, parsePgnGame, splitPgn } from '../core/doc.js';
import { Chess } from '../core/rules.js';
import { engines, whiteScore } from '../svc/engines.js';
import { commands } from '../svc/commands.js';
import { Board } from '../board/board.js';
import { BoardSession } from '../panels/session.js';
import { createNotationPanel } from '../panels/notation.js';

export const broadcastMode = {
  id: 'broadcast', title: 'Broadcast', icon: 'radio', kbd: 'Ctrl+0',

  mount(root, ctx) {
    this.ctx = ctx;
    this.root = root;
    this.games = [];            // [{doc, tip, cursor, board, tile, finishedShown}]
    this.featured = -1;
    this.playing = false;
    this.delayMs = 1200;
    this.presentation = false;
    this._timer = null;

    this.gridEl = h('div.bc-grid');
    this.emptyWrap = h('div', { style: { height: '100%' } });

    /* featured view */
    this.featuredEl = h('div', { style: { flex: 1, display: 'none', minHeight: 0 } });

    this.toolbar = h('div.toolbar', {},
      h('button.btn.btn-primary.btn-sm', { html: icon('upload') + '<span>Load round PGN</span>', onclick: () => this.loadDialog() }),
      h('div.toolbar-sep'),
      this.btnPlay = h('button.btn.btn-sm', { html: icon('play') + '<span>Play</span>', onclick: () => this.togglePlay(), disabled: true }),
      h('span.toolbar-label', {}, 'Delay'),
      this.delaySel = h('select.select', { style: { width: '90px' }, onchange: () => { this.delayMs = +this.delaySel.value; } },
        ...[[400, 'fast'], [1200, 'normal'], [2500, 'slow'], [5000, 'very slow']].map(([v, l]) =>
          h('option', { value: v, selected: v === 1200 }, l))),
      h('div.toolbar-sep'),
      this.btnBack = h('button.btn.btn-ghost.btn-sm', { html: icon('grid') + '<span>All boards</span>', style: { display: 'none' }, onclick: () => this.showGrid() }),
      this.btnPresent = h('button.btn.btn-ghost.btn-sm', { html: icon('expand') + '<span>Presentation</span>', onclick: () => this.togglePresentation(), disabled: true }),
      h('div.toolbar-spacer'),
      this.statusEl = h('span.mode-toolbar-title', {}, 'Load a PGN file with one or more games'),
    );

    this.centerEl = h('div', { style: { flex: 1, minHeight: 0, position: 'relative' } }, this.emptyWrap, this.gridEl, this.featuredEl);
    this.gridEl.style.display = 'none';
    root.append(h('div', { style: { display: 'flex', flexDirection: 'column', height: '100%' } }, this.toolbar, this.centerEl));
    this.paintEmpty();

    commands.register({ id: 'broadcast.load', title: 'Broadcast: load round PGN', category: 'Broadcast', icon: 'radio', run: () => { ctx.switchMode('broadcast'); this.loadDialog(); } });
  },

  activate() {},
  deactivate() { this.stopPlay(); },
  getDoc() { return this.featured >= 0 ? this.games[this.featured]?.doc : null; },
  getSession() { return this.session || null; },

  paintEmpty() {
    clear(this.emptyWrap);
    this.emptyWrap.append(emptyState({
      icon: 'radio', title: 'No broadcast loaded',
      hint: 'Load a PGN with a round\'s games (or any set of games) to replay them side by side, tournament-hall style. Engine matches can be watched live in Engine match mode.',
      action: h('button.btn.btn-primary', { onclick: () => this.loadDialog() }, 'Load PGN'),
    }));
  },

  async loadDialog() {
    const fileIn = h('input', { type: 'file', accept: '.pgn,.txt', style: { display: 'none' } });
    fileIn.onchange = async () => {
      const f = fileIn.files[0];
      if (f) this.load(await f.text());
      fileIn.remove();
    };
    document.body.append(fileIn);
    const text = await promptDialog({ title: 'Load broadcast round', label: 'Paste PGN with any number of games — or leave empty to pick a file', multiline: true, confirmLabel: 'Load' });
    if (text === null) { fileIn.remove(); return; }
    if (text.trim()) { this.load(text); fileIn.remove(); }
    else fileIn.click();
  },

  load(text) {
    this.stopPlay();
    const chunks = splitPgn(text);
    const games = [];
    for (const chunk of chunks.slice(0, 24)) {
      try {
        const doc = parsePgnGame(chunk);
        const tip = doc.mainline();
        if (!tip.length) continue;
        games.push({ doc, line: tip, cursor: 0, finishedShown: false });
      } catch { /* skip unreadable */ }
    }
    if (!games.length) return toast('No readable games in that PGN', 'err');
    this.games = games;
    this.featured = -1;
    this.buildGrid();
    this.btnPlay.disabled = false;
    this.btnPresent.disabled = false;
    this.statusEl.textContent = `${games.length} boards · round loaded`;
    toast(`Broadcast loaded: ${games.length} games`, 'ok');
  },

  buildGrid() {
    clear(this.gridEl);
    this.emptyWrap.style.display = 'none';
    this.featuredEl.style.display = 'none';
    this.gridEl.style.display = '';
    this.games.forEach((g, i) => {
      const boardBox = h('div.bc-tile-board');
      const tile = h('div.bc-tile', { onclick: () => this.feature(i) },
        h('div.bc-tile-head', {},
          h('span.grow', { style: { overflow: 'hidden', textOverflow: 'ellipsis' } },
            `${g.doc.headers.White} – ${g.doc.headers.Black}`),
          h('span.mono', {}, g.doc.headers.Result || '*')),
        boardBox,
        h('div.bc-tile-head', { style: { borderTop: '1px solid var(--border)', borderBottom: 'none' } },
          g.moveLabel = h('span.grow'),
          g.evalLabel = h('span.mono')));
      g.board = new Board(boardBox, { interactive: false });
      g.tile = tile;
      this.gridEl.append(tile);
      this.syncBoard(g);
    });
  },

  syncBoard(g) {
    const pos = new Chess(g.doc.startFen);
    for (let i = 0; i < g.cursor; i++) {
      const m = pos.findUci(g.line[i].uci);
      if (!m) break;
      pos.makeMove(m);
    }
    let lastMove = null;
    if (g.cursor > 0) {
      const u = g.line[g.cursor - 1].uci;
      lastMove = { from: (u.charCodeAt(1) - 49) * 16 + (u.charCodeAt(0) - 97), to: (u.charCodeAt(3) - 49) * 16 + (u.charCodeAt(2) - 97) };
    }
    const check = pos.inCheck(pos.turn) ? pos.kingSquare(pos.turn) : -1;
    g.board.setPosition(pos, { lastMove, check, animate: true });
    if (g.moveLabel) {
      const n = g.cursor ? g.line[g.cursor - 1] : null;
      g.moveLabel.textContent = n ? `${Math.ceil(g.cursor / 2)}${g.cursor % 2 ? '.' : '…'} ${n.san}` : 'start';
      const node = n;
      if (node && (node.evalCp != null || node.evalMate != null))
        g.evalLabel.textContent = node.evalMate != null ? 'M' + node.evalMate : (node.evalCp / 100).toFixed(1);
    }
    return pos;
  },

  /* ---- playback ---- */
  togglePlay() {
    this.playing ? this.stopPlay() : this.startPlay();
  },
  startPlay() {
    if (!this.games.length) return;
    this.playing = true;
    this.btnPlay.innerHTML = icon('pause') + '<span>Pause</span>';
    this._timer = setInterval(() => this.tick(), this.delayMs);
  },
  stopPlay() {
    this.playing = false;
    if (this._timer) { clearInterval(this._timer); this._timer = null; }
    if (this.btnPlay) this.btnPlay.innerHTML = icon('play') + '<span>Play</span>';
  },

  tick() {
    if (this._timer && this.delayMs !== this._lastDelay) {
      this._lastDelay = this.delayMs;
      clearInterval(this._timer);
      this._timer = setInterval(() => this.tick(), this.delayMs);
    }
    let moved = false;
    for (const g of this.games) {
      if (g.cursor < g.line.length) {
        // stagger: each board advances with slight randomness like a real hall
        if (Math.random() < 0.75) {
          g.cursor++;
          moved = true;
          if (this.featured < 0) this.syncBoard(g);
          else if (this.games[this.featured] === g) this.syncFeatured();
        }
      } else if (!g.finishedShown) {
        g.finishedShown = true;
        if (g.tile) g.tile.querySelector('.mono').textContent = g.doc.headers.Result || '*';
      }
    }
    const done = this.games.filter(g => g.cursor >= g.line.length).length;
    this.statusEl.textContent = `${this.games.length} boards · ${done} finished`;
    if (!moved && done === this.games.length) {
      this.stopPlay();
      this.statusEl.textContent = 'Round complete';
    }
  },

  /* ---- featured board ---- */
  feature(i) {
    this.featured = i;
    const g = this.games[i];
    clear(this.featuredEl);
    this.gridEl.style.display = 'none';
    this.featuredEl.style.display = 'flex';
    this.btnBack.style.display = '';

    const viewDoc = new GameDoc({ fen: g.doc.startFen, headers: { ...g.doc.headers } });
    this.session = new BoardSession({ doc: viewDoc, userTurn: () => null });
    this.session.setPlayers({
      w: { name: g.doc.headers.White, rating: g.doc.headers.WhiteElo },
      b: { name: g.doc.headers.Black, rating: g.doc.headers.BlackElo },
    });
    this.notation = createNotationPanel(g.doc, {});
    this.featuredEl.append(
      h('div.dock-center', {}, this.session.el),
      h('div.dock-side', { style: { width: '300px', borderLeft: '1px solid var(--border)' } },
        this.notation.panel.el));
    this.syncFeatured();
  },

  async syncFeatured() {
    const g = this.games[this.featured];
    if (!g || !this.session) return;
    // rebuild session doc up to cursor
    const doc = this.session.doc;
    doc.root.children = [];
    doc.current = doc.root;
    doc._invalidate();
    for (let i = 0; i < g.cursor; i++) doc.playMove(g.line[i].uci);
    this.session.update(false);
    const node = g.cursor ? g.line[g.cursor - 1] : null;
    if (node && (node.evalCp != null || node.evalMate != null)) {
      this.session.setEval(node.evalMate != null ? { mate: node.evalMate } : { cp: node.evalCp });
    } else if (engines.available().length && !this._evalBusy) {
      // live evaluation for the featured board
      this._evalBusy = true;
      const res = await engines.go({ engine: engines.has('stockfish') ? 'stockfish' : 'rush', instance: 'bc', fen: doc.position.fen(), movetime: 300 });
      this._evalBusy = false;
      if (res) this.session.setEval(whiteScore(res, doc.position.turn === 1 ? 'w' : 'b'));
    }
  },

  showGrid() {
    this.featured = -1;
    this.featuredEl.style.display = 'none';
    this.btnBack.style.display = 'none';
    this.buildGrid();
  },

  togglePresentation() {
    this.presentation = !this.presentation;
    const app = document.getElementById('app');
    app.style.gridTemplateColumns = this.presentation ? '0 1fr' : '';
    app.style.gridTemplateRows = this.presentation ? '0 1fr 0' : '';
    this.toolbar.style.display = this.presentation ? 'none' : '';
    if (this.presentation) {
      document.documentElement.requestFullscreen?.().catch(() => {});
      toast('Presentation mode — press Esc or use the command palette to exit', 'info');
      const exit = e => {
        if (e.key === 'Escape') {
          document.removeEventListener('keydown', exit);
          if (this.presentation) this.togglePresentation();
        }
      };
      document.addEventListener('keydown', exit);
    } else {
      document.exitFullscreen?.().catch(() => {});
    }
  },
};
