/* commands.js — central command registry + configurable keyboard shortcuts.
   Commands power the palette, menus, and key bindings. */
'use strict';

import { showPalette, toast } from '../ui/widgets.js';

const LS_KEYS = 'rc.shortcuts.v1';

class CommandRegistry {
  constructor() {
    this.commands = new Map();   // id -> {id,title,category,icon,kbd,run,when,hidden}
    this.overrides = {};
    try { this.overrides = JSON.parse(localStorage.getItem(LS_KEYS) || '{}'); } catch {}
    this._bound = false;
  }

  register(cmd) {
    this.commands.set(cmd.id, cmd);
    return cmd;
  }
  registerAll(cmds) { cmds.forEach(c => this.register(c)); }
  unregister(id) { this.commands.delete(id); }

  get(id) { return this.commands.get(id); }

  kbdOf(cmd) { return this.overrides[cmd.id] !== undefined ? this.overrides[cmd.id] : cmd.kbd; }

  setShortcut(id, kbd) {
    this.overrides[id] = kbd;   // null clears the binding
    localStorage.setItem(LS_KEYS, JSON.stringify(this.overrides));
  }
  resetShortcuts() {
    this.overrides = {};
    localStorage.removeItem(LS_KEYS);
  }

  run(id, ...args) {
    const c = this.commands.get(id);
    if (!c) { console.warn('unknown command', id); return; }
    if (c.when && !c.when()) return;
    try { return c.run(...args); }
    catch (e) { console.error(e); toast(`Command failed: ${e.message}`, 'err'); }
  }

  list() {
    return [...this.commands.values()].map(c => ({ ...c, kbd: this.kbdOf(c) }));
  }

  openPalette() {
    showPalette({
      commands: this.list().filter(c => !c.when || c.when()),
      onRun: c => this.run(c.id),
    });
  }

  /* ---- key handling ---- */
  bindKeys() {
    if (this._bound) return;
    this._bound = true;
    document.addEventListener('keydown', e => {
      // let inputs handle their own keys except palette/escape-level chords
      const inInput = /^(INPUT|TEXTAREA|SELECT)$/.test(e.target.tagName) || e.target.isContentEditable;
      const combo = comboOf(e);
      if (!combo) return;
      if (inInput && !combo.includes('Ctrl') && !combo.includes('F')) return;
      for (const c of this.commands.values()) {
        const kbd = this.kbdOf(c);
        if (!kbd || kbd !== combo) continue;
        if (c.when && !c.when()) continue;
        e.preventDefault();
        e.stopPropagation();
        this.run(c.id);
        return;
      }
    });
  }
}

export function comboOf(e) {
  const mods = [];
  if (e.ctrlKey || e.metaKey) mods.push('Ctrl');
  if (e.altKey) mods.push('Alt');
  if (e.shiftKey) mods.push('Shift');
  let key = e.key;
  if (key === ' ') key = 'Space';
  if (key.length === 1) key = key.toUpperCase();
  if (['Control', 'Shift', 'Alt', 'Meta'].includes(e.key)) return null;
  if (!mods.length && key.length === 1 && !/^[A-Z0-9]$/.test(key)) return null;
  return [...mods, key].join('+');
}

export const commands = new CommandRegistry();
