/* widgets.js — shared UI primitives: menus, context menus, dialogs,
   confirmations, toasts, command palette, tab strip, empty states. */
'use strict';

import { h, clear, append, esc } from './dom.js';
import { icon } from './icons.js';

/* ================= menus ================= */
let openMenuEl = null;

export function closeMenus() {
  if (openMenuEl) { openMenuEl.remove(); openMenuEl = null; }
  document.removeEventListener('pointerdown', onDocDown, true);
  document.removeEventListener('keydown', onMenuKey, true);
}
function onDocDown(e) { if (openMenuEl && !openMenuEl.contains(e.target)) closeMenus(); }
function onMenuKey(e) { if (e.key === 'Escape') { closeMenus(); e.stopPropagation(); } }

/** items: [{label, icon, kbd, checked, disabled, danger, action} | {sep:true} | {head}] */
export function showMenu(items, x, y, opts = {}) {
  closeMenus();
  const menu = h('div.menu', { role: 'menu' });
  for (const it of items) {
    if (!it) continue;
    if (it.sep) { menu.append(h('div.menu-sep')); continue; }
    if (it.head) { menu.append(h('div.menu-head', {}, it.head)); continue; }
    const btn = h('button.menu-item', {
      role: 'menuitem', disabled: !!it.disabled,
      class: it.checked ? 'checked' : '',
      onclick: () => { closeMenus(); it.action && it.action(); },
    },
      h('span', { html: it.checked ? icon('check') : (it.icon ? icon(it.icon) : '<span style="width:14px"></span>') }),
      h('span.mi-label', { style: it.danger ? { color: 'var(--err-text)' } : null }, it.label),
      it.kbd ? h('span.mi-kbd', {}, it.kbd) : null,
    );
    menu.append(btn);
  }
  document.body.append(menu);
  // position within viewport
  const r = menu.getBoundingClientRect();
  const vx = Math.min(x, window.innerWidth - r.width - 6);
  const vy = Math.min(y, window.innerHeight - r.height - 6);
  menu.style.left = Math.max(4, vx) + 'px';
  menu.style.top = Math.max(4, vy) + 'px';
  openMenuEl = menu;
  setTimeout(() => {
    document.addEventListener('pointerdown', onDocDown, true);
    document.addEventListener('keydown', onMenuKey, true);
  });
  return menu;
}

export function attachContextMenu(el, buildItems) {
  el.addEventListener('contextmenu', e => {
    const items = buildItems(e);
    if (!items || !items.length) return;
    e.preventDefault();
    e.stopPropagation();
    showMenu(items, e.clientX, e.clientY);
  });
}

export function menuButton(el, buildItems, align = 'left') {
  el.addEventListener('click', e => {
    e.stopPropagation();
    if (openMenuEl) { closeMenus(); return; }
    const r = el.getBoundingClientRect();
    el.classList.add('open');
    const m = showMenu(buildItems(), align === 'right' ? r.right - 180 : r.left, r.bottom + 2);
    const mo = new MutationObserver(() => { if (!document.body.contains(m)) { el.classList.remove('open'); mo.disconnect(); } });
    mo.observe(document.body, { childList: true });
  });
}

/* ================= dialogs ================= */
export function showDialog({ title, body, footer, width, onClose, closable = true }) {
  const veil = h('div.veil');
  const dlg = h('div.dialog', { role: 'dialog', 'aria-label': title, style: width ? { width } : null });
  const close = () => { veil.remove(); document.removeEventListener('keydown', onKey, true); onClose && onClose(); };
  const onKey = e => { if (e.key === 'Escape' && closable) { close(); e.stopPropagation(); } };
  append(dlg, [
    h('div.dialog-header', {},
      h('span.grow', {}, title),
      closable ? h('button.icon-btn', { html: icon('close'), 'aria-label': 'Close', onclick: close }) : null,
    ),
    h('div.dialog-body', {}, body),
    footer ? h('div.dialog-footer', {}, footer) : null,
  ]);
  veil.append(dlg);
  veil.addEventListener('pointerdown', e => { if (e.target === veil && closable) close(); });
  document.addEventListener('keydown', onKey, true);
  document.body.append(veil);
  const focusable = dlg.querySelector('input, textarea, select, button:not(.icon-btn)');
  if (focusable) focusable.focus();
  return { close, el: dlg };
}

export function confirmDialog({ title, message, confirmLabel = 'OK', danger = false }) {
  return new Promise(resolve => {
    let settled = false;
    const settle = v => { if (!settled) { settled = true; resolve(v); } };
    const dlg = showDialog({
      title,
      body: h('div', { style: { maxWidth: '380px' } }, message),
      footer: [
        h('button.btn', { onclick: () => { settle(false); dlg.close(); } }, 'Cancel'),
        h(`button.btn.${danger ? 'btn-danger' : 'btn-primary'}`, {
          onclick: () => { settle(true); dlg.close(); },
        }, confirmLabel),
      ],
      onClose: () => settle(false),
    });
  });
}

export function promptDialog({ title, label, value = '', placeholder = '', multiline = false, confirmLabel = 'OK' }) {
  return new Promise(resolve => {
    let settled = false;
    const settle = v => { if (!settled) { settled = true; resolve(v); } };
    const input = multiline
      ? h('textarea.input', { rows: 8, placeholder, value })
      : h('input.input', { placeholder, value });
    const ok = () => { settle(input.value); dlg.close(); };
    if (!multiline) input.addEventListener('keydown', e => { if (e.key === 'Enter') ok(); });
    const dlg = showDialog({
      title,
      body: h('div.field', {}, label ? h('label', {}, label) : null, input),
      footer: [
        h('button.btn', { onclick: () => { settle(null); dlg.close(); } }, 'Cancel'),
        h('button.btn.btn-primary', { onclick: ok }, confirmLabel),
      ],
      onClose: () => settle(null),
      width: multiline ? '520px' : undefined,
    });
    input.focus(); input.select();
  });
}

/* ================= toasts ================= */
let toastRoot = null;
export function toast(message, kind = 'info', ms = 2800) {
  if (!toastRoot) { toastRoot = h('div.toasts'); document.body.append(toastRoot); }
  const icons = { info: 'info', ok: 'check', err: 'warning', warn: 'warning' };
  const t = h('div.toast', { class: kind === 'info' ? '' : kind },
    h('span', { html: icon(icons[kind] || 'info'), style: { color: `var(--${kind === 'ok' ? 'ok' : kind === 'err' ? 'err' : kind === 'warn' ? 'warn' : 'accent'}-text, var(--text-secondary))` } }),
    h('span.selectable', {}, message));
  toastRoot.append(t);
  setTimeout(() => { t.style.opacity = '0'; t.style.transition = 'opacity .3s'; setTimeout(() => t.remove(), 320); }, ms);
}

/* ================= empty / loading states ================= */
export function emptyState({ icon: ic = 'info', title, hint, action }) {
  return h('div.empty-state', {},
    h('span', { html: icon(ic) }),
    title ? h('div.es-title', {}, title) : null,
    hint ? h('div.es-hint', {}, hint) : null,
    action || null,
  );
}

export function loadingState(label = 'Loading…') {
  return h('div.empty-state', {}, h('div.spinner'), h('div.es-hint', {}, label));
}

/* ================= command palette ================= */
export function showPalette({ commands, onRun, placeholder = 'Type a command…' }) {
  const veil = h('div.palette-veil');
  const listEl = h('div.palette-list');
  const input = h('input.palette-input', { placeholder, 'aria-label': 'Command palette' });
  const pal = h('div.palette', {}, input, listEl);
  let filtered = [];
  let sel = 0;

  const close = () => { veil.remove(); pal.remove(); document.removeEventListener('keydown', onKey, true); };

  function score(cmd, q) {
    if (!q) return 1;
    const t = cmd.title.toLowerCase(), c = (cmd.category || '').toLowerCase();
    const qi = q.toLowerCase();
    if (t.startsWith(qi)) return 100;
    if (t.includes(qi)) return 50;
    if (c.includes(qi)) return 20;
    // fuzzy: all chars in order
    let i = 0;
    for (const ch of t) if (ch === qi[i]) i++;
    return i === qi.length ? 10 : 0;
  }

  function paint() {
    const q = input.value.trim();
    filtered = commands
      .map(c => ({ c, s: score(c, q) }))
      .filter(x => x.s > 0 && !x.c.hidden)
      .sort((a, b) => b.s - a.s || a.c.title.localeCompare(b.c.title))
      .slice(0, 40)
      .map(x => x.c);
    sel = Math.min(sel, Math.max(0, filtered.length - 1));
    clear(listEl);
    if (!filtered.length) { listEl.append(h('div.palette-empty', {}, 'No matching commands')); return; }
    filtered.forEach((c, i) => {
      const item = h('div.palette-item', {
        class: i === sel ? 'sel' : '',
        onclick: () => { close(); onRun(c); },
        onpointermove: () => { if (sel !== i) { sel = i; paint(); } },
      },
        h('span', { html: icon(c.icon || 'chevronR') }),
        h('span.pi-label', { html: hilite(c.title, q) }),
        c.category ? h('span.pi-cat', {}, c.category) : null,
        c.kbd ? h('span.pi-kbd', {}, c.kbd) : null,
      );
      listEl.append(item);
    });
  }
  function hilite(title, q) {
    if (!q) return esc(title);
    const i = title.toLowerCase().indexOf(q.toLowerCase());
    if (i < 0) return esc(title);
    return esc(title.slice(0, i)) + '<b>' + esc(title.slice(i, i + q.length)) + '</b>' + esc(title.slice(i + q.length));
  }
  const onKey = e => {
    if (e.key === 'Escape') { close(); e.stopPropagation(); }
    else if (e.key === 'ArrowDown') { sel = Math.min(sel + 1, filtered.length - 1); paint(); e.preventDefault(); }
    else if (e.key === 'ArrowUp') { sel = Math.max(sel - 1, 0); paint(); e.preventDefault(); }
    else if (e.key === 'Enter') { const c = filtered[sel]; if (c) { close(); onRun(c); } e.preventDefault(); }
  };
  input.addEventListener('input', () => { sel = 0; paint(); });
  veil.addEventListener('pointerdown', () => close());
  document.addEventListener('keydown', onKey, true);
  document.body.append(veil, pal);
  input.focus();
  paint();
  return { close };
}

/* ================= panel factory ================= */
export function makePanel({ id, title, icon: ic, actions = [], body, collapsible = true, onContext }) {
  const bodyEl = h('div.panel-body');
  if (body) bodyEl.append(body);
  const actionEls = actions.map(a =>
    h('button.icon-btn', { html: icon(a.icon), title: a.title, 'aria-label': a.title, onclick: a.onClick }));
  const collapseBtn = collapsible
    ? h('button.icon-btn', { html: icon('chevronD'), title: 'Collapse', 'aria-label': 'Collapse panel' })
    : null;
  const panel = h('div.panel', { dataset: { panelId: id } },
    h('div.panel-header', {},
      ic ? h('span', { html: icon(ic) }) : null,
      h('span.panel-title', {}, title),
      h('span.panel-actions', {}, ...actionEls, collapseBtn),
    ),
    bodyEl,
  );
  if (collapseBtn) collapseBtn.onclick = () => {
    panel.classList.toggle('collapsed');
    collapseBtn.innerHTML = icon(panel.classList.contains('collapsed') ? 'chevronR' : 'chevronD');
  };
  if (onContext) attachContextMenu(panel.querySelector('.panel-header'), onContext);
  return { el: panel, body: bodyEl, setTitle: t => { panel.querySelector('.panel-title').textContent = t; } };
}
