/* modes/database.js — Database & Explorer mode.
   Named databases, PGN import with progress, filtered + virtualized game
   list, selection/batch ops, duplicate detection, statistics, favorites,
   tags, game preview with mini board, export, open-in-analysis. */
'use strict';

import { h, clear, VirtualList, debounce } from '../ui/dom.js';
import { icon } from '../ui/icons.js';
import { showDialog, promptDialog, confirmDialog, toast, makePanel, emptyState, loadingState, attachContextMenu, showMenu } from '../ui/widgets.js';
import { Chess } from '../core/rules.js';
import { GameDoc } from '../core/doc.js';
import { gamedb } from '../svc/gamedb.js';
import { commands } from '../svc/commands.js';
import { Board } from '../board/board.js';
import { downloadText } from '../ui/appbus.js';

export const databaseMode = {
  id: 'database', title: 'Database', icon: 'database', kbd: 'Ctrl+3',

  mount(root, ctx) {
    this.ctx = ctx;
    this.filters = {};
    this.rows = [];
    this.selection = new Set();
    this.lastClicked = -1;
    this.currentDb = '';

    /* ---- toolbar ---- */
    this.dbSel = h('select.select', { style: { minWidth: '140px' }, onchange: () => { this.currentDb = this.dbSel.value; this.refresh(); } });
    this.toolbar = h('div.toolbar', {},
      this.dbSel,
      h('button.btn.btn-sm', { html: icon('plus') + '<span>New DB</span>', onclick: () => this.newDb() }),
      h('button.btn.btn-sm', { html: icon('upload') + '<span>Import PGN</span>', onclick: () => this.importDialog() }),
      h('div.toolbar-sep'),
      this.btnExport = h('button.btn.btn-sm', { html: icon('download') + '<span>Export</span>', title: 'Export selection (or all filtered)', onclick: () => this.exportGames() }),
      this.btnDelete = h('button.btn.btn-sm', { html: icon('trash') + '<span>Delete</span>', onclick: () => this.deleteSelected() }),
      h('div.toolbar-sep'),
      h('button.btn.btn-ghost.btn-sm', { html: icon('layers') + '<span>Duplicates</span>', onclick: () => this.findDups() }),
      h('button.btn.btn-ghost.btn-sm', { html: icon('graph') + '<span>Statistics</span>', onclick: () => this.showStats() }),
      h('div.toolbar-spacer'),
      this.countEl = h('span.mode-toolbar-title'),
    );

    /* ---- filter row ---- */
    const fiEls = {};
    const fi = (ph, key, width = 110) => {
      const el = h('input.input', {
        placeholder: ph, style: { width: width + 'px' },
        oninput: debounce(() => { this.filters[key] = el.value.trim().toLowerCase() || undefined; this.refresh(); }, 250),
      });
      fiEls[key] = el;
      return el;
    };
    fi('Player', 'player', 130); fi('Event', 'event'); fi('ECO', 'eco', 56); fi('Opening moves (e4 e5 ...)', 'opening', 170);
    fiEls.eco.oninput = debounce(() => { this.filters.eco = fiEls.eco.value.trim().toUpperCase() || undefined; this.refresh(); }, 250);
    this.resultSel = h('select.select', { onchange: () => { this.filters.result = this.resultSel.value || undefined; this.refresh(); } },
      h('option', { value: '' }, 'Any result'),
      ...['1-0', '0-1', '1/2-1/2', '*'].map(r => h('option', { value: r }, r)));
    this.minEloIn = h('input.input', { placeholder: 'Min Elo', type: 'number', style: { width: '80px' },
      oninput: debounce(() => { this.filters.minElo = +this.minEloIn.value || undefined; this.refresh(); }, 300) });
    this.favBtn = h('button.btn.btn-ghost.btn-sm', { html: icon('star'), title: 'Favorites only',
      onclick: () => { this.filters.fav = this.filters.fav ? undefined : true; this.favBtn.classList.toggle('active', !!this.filters.fav); this.refresh(); } });
    const filterBar = h('div.toolbar', { style: { height: '32px' } },
      fiEls.player, fiEls.event, fiEls.eco, fiEls.opening, this.resultSel, this.minEloIn, this.favBtn,
      h('button.btn.btn-ghost.btn-sm', { html: icon('close') + '<span>Clear</span>', onclick: () => {
        this.filters = {};
        Object.values(fiEls).forEach(e => e.value = '');
        this.resultSel.value = ''; this.minEloIn.value = '';
        this.favBtn.classList.remove('active');
        this.refresh();
      } }),
    );

    /* ---- game list (virtualized) ---- */
    const listWrap = h('div', { style: { flex: 1, overflowY: 'auto', position: 'relative' } });
    this.emptyEl = h('div', { style: { position: 'absolute', inset: 0, display: 'none' } });
    listWrap.append(this.emptyEl);
    this.vlist = new VirtualList(listWrap, {
      rowHeight: 26,
      render: (g, i) => this.renderRow(g, i),
    });

    /* ---- preview panel ---- */
    const prevBody = h('div', { style: { display: 'flex', flexDirection: 'column', height: '100%' } });
    this.prevBoardBox = h('div', { style: { padding: '8px', containerType: 'size', height: '46%', display: 'flex', justifyContent: 'center' } });
    this.prevBoard = new Board(this.prevBoardBox, { interactive: false });
    this.prevInfo = h('div', { style: { padding: '0 10px', fontSize: 'var(--fs-12)' } });
    this.prevMoves = h('div.selectable', { style: { flex: 1, overflowY: 'auto', padding: '8px 10px', fontSize: 'var(--fs-12)', lineHeight: 1.7, color: 'var(--text-secondary)' } });
    this.prevNav = h('div', { style: { display: 'flex', gap: '4px', padding: '6px 10px' } },
      h('button.btn.btn-sm', { html: icon('skipStart'), onclick: () => this.prevGo(0) }),
      h('button.btn.btn-sm', { html: icon('chevronL'), onclick: () => this.prevGo(this.prevPly - 1) }),
      h('button.btn.btn-sm', { html: icon('chevronR'), onclick: () => this.prevGo(this.prevPly + 1) }),
      h('button.btn.btn-sm', { html: icon('skipEnd'), onclick: () => this.prevGo(1e9) }),
      h('span.grow'),
      h('button.btn.btn-primary.btn-sm', { html: icon('microscope') + '<span>Open</span>', onclick: () => this.openSelected() }));
    prevBody.append(this.prevBoardBox, this.prevInfo, this.prevNav, this.prevMoves);
    const prevPanel = makePanel({ id: 'preview', title: 'Game preview', icon: 'eye', body: prevBody, collapsible: false });

    root.append(h('div.dock-row', {},
      h('div.dock-center', {}, h('div.dock-col', { style: { flex: 1, height: '100%' } },
        this.toolbar, filterBar,
        h('div.gl-row', { style: { cursor: 'default', background: 'var(--bg-app)', fontWeight: 600, color: 'var(--text-secondary)', height: '24px', flex: 'none' } },
          h('span.gl-players', {}, 'Players'), h('span.gl-result', {}, 'Result'),
          h('span.gl-event', {}, 'Event'), h('span.gl-date', {}, 'Date'), h('span.gl-eco', {}, 'ECO')),
        listWrap)),
      h('div.dock-side', { style: { width: '320px', borderLeft: '1px solid var(--border)' } }, prevPanel.el)));

    commands.registerAll([
      { id: 'db.import', title: 'Import PGN into database…', category: 'Database', icon: 'upload', kbd: 'Ctrl+O', run: () => { ctx.switchMode('database'); this.importDialog(); } },
      { id: 'db.stats', title: 'Database statistics', category: 'Database', icon: 'graph', run: () => { ctx.switchMode('database'); this.showStats(); } },
    ]);

    this.refreshDbs().then(() => this.refresh());
  },

  activate() { this.refreshDbs(); },
  getDoc() { return null; },

  async refreshDbs() {
    const dbs = await gamedb.listDatabases();
    const cur = this.currentDb;
    clear(this.dbSel);
    this.dbSel.append(h('option', { value: '' }, 'All databases'));
    for (const db of dbs)
      this.dbSel.append(h('option', { value: db.name, selected: db.name === cur }, `${db.name} (${db.count})`));
  },

  async refresh() {
    const f = { ...this.filters, db: this.currentDb || undefined };
    this.rows = await gamedb.query(f);
    this.rows.sort((a, b) => (b.date || '').localeCompare(a.date || '') || b.id - a.id);
    this.selection.clear();
    this.vlist.setItems(this.rows);
    this.countEl.textContent = `${this.rows.length} game${this.rows.length === 1 ? '' : 's'}`;
    this.emptyEl.style.display = this.rows.length ? 'none' : '';
    if (!this.rows.length) {
      clear(this.emptyEl);
      this.emptyEl.append(emptyState({
        icon: 'database', title: 'No games',
        hint: Object.keys(this.filters).length ? 'No games match the current filters.' : 'Import a PGN file to create your first database.',
        action: h('button.btn.btn-primary', { onclick: () => this.importDialog() }, 'Import PGN'),
      }));
    }
    this.paintButtons();
  },

  renderRow(g, i) {
    const row = h('div.gl-row', {
      class: this.selection.has(g.id) ? 'sel' : '',
      onclick: e => this.clickRow(g, i, e),
      ondblclick: () => this.openGame(g.id),
    },
      h('span.gl-players', {},
        g.fav ? h('span', { html: icon('star', 'ic-sm'), style: { color: 'var(--warn-text)', verticalAlign: '-2px', marginRight: '4px' } }) : null,
        `${g.white}${g.whiteElo ? ' (' + g.whiteElo + ')' : ''} - ${g.black}${g.blackElo ? ' (' + g.blackElo + ')' : ''}`),
      h('span.gl-result', {}, g.result),
      h('span.gl-event', {}, g.event),
      h('span.gl-date', {}, g.date),
      h('span.gl-eco', {}, g.eco || (g.sans || []).slice(0, 2).join(' ')));
    attachContextMenu(row, () => [
      { label: 'Open in Analysis', icon: 'microscope', action: () => this.openGame(g.id) },
      { label: g.fav ? 'Remove favorite' : 'Mark favorite', icon: 'star', action: async () => { await gamedb.updateGame(g.id, { fav: !g.fav }); this.refresh(); } },
      { label: 'Edit tags…', icon: 'filter', action: async () => {
        const t = await promptDialog({ title: 'Tags', label: 'Comma-separated tags', value: (g.tags || []).join(', ') });
        if (t !== null) { await gamedb.updateGame(g.id, { tags: t.split(',').map(x => x.trim()).filter(Boolean) }); this.refresh(); }
      } },
      { label: 'Copy PGN', icon: 'copy', action: async () => {
        const full = await gamedb.getGame(g.id);
        navigator.clipboard.writeText(full.pgn).then(() => toast('PGN copied', 'ok'));
      } },
      { sep: true },
      { label: 'Delete game', icon: 'trash', danger: true, action: async () => {
        if (await confirmDialog({ title: 'Delete game', message: 'Delete this game permanently?', confirmLabel: 'Delete', danger: true }))
          { await gamedb.deleteGames([g.id]); this.refresh(); this.refreshDbs(); }
      } },
    ]);
    return row;
  },

  clickRow(g, i, e) {
    if (e.shiftKey && this.lastClicked >= 0) {
      const [a, b] = [Math.min(this.lastClicked, i), Math.max(this.lastClicked, i)];
      for (let j = a; j <= b; j++) this.selection.add(this.rows[j].id);
    } else if (e.ctrlKey || e.metaKey) {
      this.selection.has(g.id) ? this.selection.delete(g.id) : this.selection.add(g.id);
      this.lastClicked = i;
    } else {
      this.selection.clear();
      this.selection.add(g.id);
      this.lastClicked = i;
      this.preview(g);
    }
    this.vlist.setItems(this.rows);
    this.paintButtons();
  },

  paintButtons() {
    const n = this.selection.size;
    this.btnDelete.disabled = n === 0;
    this.btnExport.disabled = this.rows.length === 0;
  },

  /* ---- preview ---- */
  async preview(g) {
    const full = await gamedb.getGame(g.id);
    if (!full) return;
    this.prevGame = full;
    this.prevPly = Math.min(16, full.sans.length);
    clear(this.prevInfo);
    this.prevInfo.append(
      h('div', { style: { fontWeight: 600 } }, `${full.white} - ${full.black}  ${full.result}`),
      h('div.faint', {}, `${full.event} · ${full.date}${full.eco ? ' · ' + full.eco : ''}`));
    this.prevMoves.textContent = full.sans.map((s, i) => (i % 2 === 0 ? Math.floor(i / 2 + 1) + '. ' : '') + s).join(' ');
    this.prevGo(this.prevPly);
  },

  prevGo(ply) {
    if (!this.prevGame) return;
    const sans = this.prevGame.sans;
    this.prevPly = Math.max(0, Math.min(sans.length, ply));
    try {
      const pos = new Chess(this.prevGame.fen || undefined);
      const norm = s => s.replace(/[+#]/g, '');
      for (let i = 0; i < this.prevPly; i++) {
        const legal = pos.legalMoves();
        const m = legal.find(x => norm(pos.san(x, legal)) === norm(sans[i]));
        if (!m) break;
        pos.makeMove(m);
      }
      this.prevBoard.setPosition(pos, { animate: false });
    } catch { /* corrupt game — preview only */ }
  },

  async openSelected() {
    if (this.prevGame) this.openGame(this.prevGame.id);
  },

  async openGame(id) {
    try {
      const doc = await gamedb.loadDoc(id);
      if (doc) this.ctx.openInAnalysis(doc);
    } catch (e) { toast('Could not open game: ' + e.message, 'err'); }
  },

  /* ---- db ops ---- */
  async newDb() {
    const name = await promptDialog({ title: 'New database', label: 'Database name', placeholder: 'e.g. My repertoire games' });
    if (!name || !name.trim()) return;
    await gamedb._bumpDb(name.trim(), 0);
    this.currentDb = name.trim();
    await this.refreshDbs();
    this.dbSel.value = this.currentDb;
    this.refresh();
  },

  importDialog() {
    const fileIn = h('input', { type: 'file', accept: '.pgn,.txt', style: { display: 'none' } });
    const nameIn = h('input.input', { value: this.currentDb || 'Imported', placeholder: 'Target database' });
    const textArea = h('textarea.input', { rows: 8, placeholder: 'Paste PGN here (any number of games)…' });
    const progress = h('div.faint', { style: { fontSize: 'var(--fs-12)', minHeight: '16px' } });
    let busy = false;

    const doImport = async text => {
      if (!text.trim()) return toast('Nothing to import', 'warn');
      if (busy) return;
      busy = true;
      progress.textContent = 'Importing…';
      try {
        const res = await gamedb.importPgn(nameIn.value.trim() || 'Imported', text, {
          onProgress: (done, total) => { progress.textContent = `Importing ${done}/${total}…`; },
        });
        progress.textContent = '';
        toast(`Imported ${res.added} games (${res.dups} duplicates skipped, ${res.failed} unreadable)`, 'ok', 4000);
        dlg.close();
        this.currentDb = nameIn.value.trim() || 'Imported';
        await this.refreshDbs();
        this.dbSel.value = this.currentDb;
        this.refresh();
      } catch (e) {
        progress.textContent = '';
        toast('Import failed: ' + e.message, 'err');
      } finally { busy = false; }
    };

    fileIn.onchange = async () => {
      const f = fileIn.files[0];
      if (f) doImport(await f.text());
    };

    const dlg = showDialog({
      title: 'Import PGN',
      body: h('div', { style: { display: 'flex', flexDirection: 'column', gap: '10px', minWidth: '420px' } },
        h('div.field', {}, h('label', {}, 'Into database'), nameIn),
        h('button.btn', { html: icon('folder') + '<span>Choose .pgn file…</span>', onclick: () => fileIn.click() }),
        h('div.faint', { style: { textAlign: 'center', fontSize: 'var(--fs-11)' } }, '- or -'),
        textArea, progress, fileIn),
      footer: [
        h('button.btn', { onclick: () => dlg.close() }, 'Cancel'),
        h('button.btn.btn-primary', { onclick: () => doImport(textArea.value) }, 'Import pasted text'),
      ],
    });
  },

  async exportGames() {
    const ids = this.selection.size ? [...this.selection] : this.rows.map(r => r.id);
    if (!ids.length) return;
    let out = '';
    for (const id of ids) {
      const g = await gamedb.getGame(id);
      if (g) out += g.pgn.trim() + '\n\n';
    }
    downloadText((this.currentDb || 'games') + '.pgn', out);
    toast(`Exported ${ids.length} games`, 'ok');
  },

  async deleteSelected() {
    const n = this.selection.size;
    if (!n) return;
    const ok = await confirmDialog({ title: 'Delete games', message: `Delete ${n} selected game${n > 1 ? 's' : ''} permanently?`, confirmLabel: 'Delete', danger: true });
    if (!ok) return;
    await gamedb.deleteGames([...this.selection]);
    this.refresh();
    this.refreshDbs();
  },

  async findDups() {
    const dups = await gamedb.findDuplicates(this.currentDb || null);
    if (!dups.length) return toast('No duplicates found', 'ok');
    const ok = await confirmDialog({
      title: 'Duplicates found',
      message: `${dups.length} duplicate game${dups.length > 1 ? 's' : ''} found (same moves and result). Delete the copies and keep one of each?`,
      confirmLabel: 'Delete duplicates', danger: true,
    });
    if (!ok) return;
    await gamedb.deleteGames(dups.map(d => d.dup));
    toast(`Removed ${dups.length} duplicates`, 'ok');
    this.refresh();
    this.refreshDbs();
  },

  async showStats() {
    const dlgBody = loadingState('Computing statistics…');
    const dlg = showDialog({ title: `Statistics — ${this.currentDb || 'all databases'}`, body: dlgBody });
    const s = await gamedb.stats(this.currentDb || null);
    const pct = n => s.games ? (n / s.games * 100).toFixed(1) + '%' : '-';
    dlgBody.replaceWith(h('div', { style: { display: 'flex', flexDirection: 'column', gap: '10px', minWidth: '360px' } },
      h('div.stat-strip', {},
        h('div.stat-cell', {}, h('div.sc-label', {}, 'Games'), h('div.sc-value', {}, String(s.games))),
        h('div.stat-cell', {}, h('div.sc-label', {}, 'Players'), h('div.sc-value', {}, String(s.players))),
        h('div.stat-cell', {}, h('div.sc-label', {}, 'Events'), h('div.sc-value', {}, String(s.events))),
        h('div.stat-cell', {}, h('div.sc-label', {}, 'Avg Elo'), h('div.sc-value', {}, s.avgElo ? String(s.avgElo) : '-'))),
      h('div.stat-strip', {},
        h('div.stat-cell', {}, h('div.sc-label', {}, 'White wins'), h('div.sc-value', {}, pct(s.white))),
        h('div.stat-cell', {}, h('div.sc-label', {}, 'Draws'), h('div.sc-value', {}, pct(s.draw))),
        h('div.stat-cell', {}, h('div.sc-label', {}, 'Black wins'), h('div.sc-value', {}, pct(s.black))))));
  },
};
