// lib/ui.js — toasts, command palette, helpers.

const toastsEl = () => document.getElementById('toasts');

export function toast({ kind = 'info', title = '', body = '', ttl = 3800 } = {}) {
  const host = toastsEl();
  if (!host) return;
  const el = document.createElement('div');
  el.className = `toast ${kind}`;
  el.innerHTML = `<div class="ttl"></div><div class="body"></div>`;
  el.querySelector('.ttl').textContent = title || ({
    ok: 'Success', warn: 'Warning', err: 'Error', info: 'Info',
  }[kind] || 'Info');
  el.querySelector('.body').textContent = body || '';
  host.appendChild(el);
  setTimeout(() => {
    el.style.opacity = '0';
    el.style.transform = 'translateY(8px)';
    el.style.transition = 'all 200ms';
    setTimeout(() => el.remove(), 220);
  }, ttl);
}

export const ok   = (body, title) => toast({ kind: 'ok',   body, title });
export const warn = (body, title) => toast({ kind: 'warn', body, title });
export const err  = (body, title) => toast({ kind: 'err',  body, title });

// ---------- Command palette ----------
const paletteCommands = [];
let paletteOpen = false;
let paletteFiltered = [];
let paletteIndex = 0;

export function registerCommand(cmd) {
  // cmd: { id, label, group, run(), keywords?: string }
  paletteCommands.push(cmd);
}

function renderPalette() {
  const list = document.getElementById('palette-list');
  if (!list) return;
  list.innerHTML = paletteFiltered.map((c, i) => `
    <li class="${i === paletteIndex ? 'active' : ''}" data-i="${i}">
      <svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
      <span class="grow">${escapeHtml(c.label)}</span>
      <span class="group">${escapeHtml(c.group || '')}</span>
    </li>
  `).join('');
  list.querySelectorAll('li').forEach(li => {
    li.addEventListener('click', () => {
      const idx = parseInt(li.dataset.i, 10);
      if (!Number.isNaN(idx)) runFiltered(idx);
    });
  });
}

function filterPalette(q) {
  q = (q || '').trim().toLowerCase();
  if (!q) paletteFiltered = paletteCommands.slice(0, 30);
  else {
    paletteFiltered = paletteCommands.filter(c => {
      const hay = (c.label + ' ' + (c.group || '') + ' ' + (c.keywords || '')).toLowerCase();
      return hay.includes(q);
    }).slice(0, 40);
  }
  paletteIndex = 0;
  renderPalette();
}

function runFiltered(i) {
  const c = paletteFiltered[i];
  if (!c) return;
  closePalette();
  try { c.run(); } catch (e) { err(String(e)); }
}

export function openPalette(prefill = '') {
  const bg = document.getElementById('palette-bg');
  const inp = document.getElementById('palette-input');
  if (!bg || !inp) return;
  paletteOpen = true;
  bg.classList.add('open');
  bg.setAttribute('aria-hidden', 'false');
  inp.value = prefill;
  filterPalette(prefill);
  setTimeout(() => inp.focus(), 10);
}
export function closePalette() {
  const bg = document.getElementById('palette-bg');
  if (!bg) return;
  paletteOpen = false;
  bg.classList.remove('open');
  bg.setAttribute('aria-hidden', 'true');
}

export function initPalette() {
  const trigger = document.getElementById('cmd-trigger');
  const bg = document.getElementById('palette-bg');
  const inp = document.getElementById('palette-input');
  if (!trigger || !bg || !inp) return;
  trigger.addEventListener('click', () => openPalette());
  bg.addEventListener('click', (e) => { if (e.target === bg) closePalette(); });
  inp.addEventListener('input', (e) => filterPalette(e.target.value));
  inp.addEventListener('keydown', (e) => {
    if (e.key === 'Escape') return closePalette();
    if (e.key === 'ArrowDown') { e.preventDefault(); paletteIndex = Math.min(paletteFiltered.length - 1, paletteIndex + 1); renderPalette(); }
    if (e.key === 'ArrowUp')   { e.preventDefault(); paletteIndex = Math.max(0, paletteIndex - 1); renderPalette(); }
    if (e.key === 'Enter')     { e.preventDefault(); runFiltered(paletteIndex); }
  });
  window.addEventListener('keydown', (e) => {
    const k = (e.key || '').toLowerCase();
    if ((e.metaKey || e.ctrlKey) && k === 'k') { e.preventDefault(); paletteOpen ? closePalette() : openPalette(); }
    if (paletteOpen && e.key === 'Escape') closePalette();
  });
}

// ---------- helpers ----------
export function escapeHtml(s) {
  return String(s ?? '').replace(/[&<>"']/g, c => ({
    '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
  }[c]));
}

export function skeleton(lines = 3) {
  return Array.from({ length: lines }).map((_, i) =>
    `<div class="skel" style="height: ${i === 0 ? 18 : 12}px; width: ${100 - i * 12}%; margin-top: ${i ? 8 : 0}px;"></div>`
  ).join('');
}

export function fmtJSON(v) {
  if (v == null) return '';
  if (typeof v === 'string') return v;
  try { return JSON.stringify(v, null, 2); } catch { return String(v); }
}

export function setHealthPill(state, text) {
  const pill = document.getElementById('health-pill');
  const txt  = document.getElementById('health-text');
  if (!pill || !txt) return;
  pill.classList.remove('ok', 'warn', 'err', 'info', 'muted');
  pill.classList.add(state || 'muted');
  txt.textContent = text || state || '—';
}

export function setBrandVer(text) {
  const el = document.getElementById('brand-ver');
  if (el) el.textContent = text || '—';
}
export function setFooterBuild(text) {
  const el = document.getElementById('footer-build');
  if (el) el.textContent = text || 'build —';
}

// ---------- additional reusable helpers ----------

export async function copyToClipboard(text, label = 'copied') {
  try {
    await navigator.clipboard.writeText(text);
    ok(label);
  } catch (_) {
    // fallback for non-secure contexts
    const ta = document.createElement('textarea');
    ta.value = text;
    document.body.appendChild(ta);
    ta.select();
    try { document.execCommand('copy'); ok(label); } catch { err('copy failed'); }
    ta.remove();
  }
}

// Wraps any `<pre>` so it gets a copy button on hover.
export function withCopy(html, id = '') {
  const safeId = id || `c${Math.random().toString(36).slice(2, 7)}`;
  return `<div class="code-wrap">
    <pre class="code" id="${safeId}">${html}</pre>
    <button class="copy-btn" data-copy-target="${safeId}" title="Copy to clipboard">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg>
    </button>
  </div>`;
}
// Wire up any .copy-btn[data-copy-target] inside `host` after rendering.
export function bindCopyButtons(host) {
  host.querySelectorAll('.copy-btn[data-copy-target]').forEach(btn => {
    if (btn.dataset.bound) return;
    btn.dataset.bound = '1';
    btn.addEventListener('click', () => {
      const t = host.querySelector('#' + btn.dataset.copyTarget);
      if (t) copyToClipboard(t.textContent || '');
    });
  });
}

// Renders a tiny sparkline (svg) for an array of numbers (0..N values).
export function sparkline(values, { w = 220, h = 36 } = {}) {
  if (!values || values.length === 0) return `<svg class="spark" viewBox="0 0 ${w} ${h}"></svg>`;
  const min = Math.min(...values);
  const max = Math.max(...values);
  const span = max - min || 1;
  const dx = w / Math.max(1, values.length - 1);
  const pts = values.map((v, i) => `${(i * dx).toFixed(1)},${(h - ((v - min) / span) * (h - 4) - 2).toFixed(1)}`);
  const line = `M ${pts.join(' L ')}`;
  const area = `${line} L ${w},${h} L 0,${h} Z`;
  return `<svg class="spark" viewBox="0 0 ${w} ${h}" preserveAspectRatio="none">
    <path class="a" d="${area}" />
    <path class="l" d="${line}" />
  </svg>`;
}

// Render a horizontal meter row (0..100). State: ok/warn/err/none.
export function meterRow(label, pct, value, state = '') {
  const safe = Math.max(0, Math.min(100, Number(pct) || 0));
  return `<div class="gauge">
    <span class="gname">${escapeHtml(label)}</span>
    <div class="meter ${escapeHtml(state)}"><span style="width:${safe}%"></span></div>
    <span class="gval">${escapeHtml(value)}</span>
  </div>`;
}

// Pretty-print numbers (bytes / counts).
export function fmtBytes(n) {
  if (!n && n !== 0) return '—';
  const u = ['B','KB','MB','GB','TB'];
  let i = 0, v = +n;
  while (v >= 1024 && i < u.length - 1) { v /= 1024; i++; }
  return `${v.toFixed(v >= 100 || i === 0 ? 0 : v >= 10 ? 1 : 2)} ${u[i]}`;
}
export function fmtNum(n) {
  if (n == null || Number.isNaN(+n)) return '—';
  return (+n).toLocaleString();
}

// Tiny localStorage-backed history of items (most recent first).
export function historyStore(key, max = 25) {
  const read = () => {
    try { return JSON.parse(localStorage.getItem(key) || '[]'); } catch { return []; }
  };
  return {
    list: read,
    push(item) {
      const arr = read();
      arr.unshift({ ...item, _ts: Date.now() });
      while (arr.length > max) arr.pop();
      try { localStorage.setItem(key, JSON.stringify(arr)); } catch {}
    },
    clear() { try { localStorage.removeItem(key); } catch {} },
  };
}

// Generate a quick HTML form input set from a JSON Schema (best-effort, flat).
export function inputFromSchema(name, schema) {
  const lbl = name + (schema?.description ? ` — ${schema.description}` : '');
  if (!schema) return `<label class="field"><span class="lbl">${escapeHtml(name)}</span><input class="input" data-k="${escapeHtml(name)}"></label>`;
  if (schema.enum?.length) {
    const opts = schema.enum.map(v => `<option value="${escapeHtml(String(v))}">${escapeHtml(String(v))}</option>`).join('');
    return `<label class="field"><span class="lbl">${escapeHtml(lbl)}</span><select class="input" data-k="${escapeHtml(name)}">${opts}</select></label>`;
  }
  if (schema.type === 'boolean') {
    return `<label class="check" style="margin-top:8px"><input type="checkbox" data-k="${escapeHtml(name)}" data-kind="bool" ${schema.default ? 'checked' : ''}/> ${escapeHtml(lbl)}</label>`;
  }
  if (schema.type === 'integer' || schema.type === 'number') {
    return `<label class="field"><span class="lbl">${escapeHtml(lbl)}</span>
      <input class="input" type="number" data-k="${escapeHtml(name)}" data-kind="num"${schema.default != null ? ` value="${schema.default}"` : ''}/></label>`;
  }
  if (schema.type === 'object' || schema.type === 'array') {
    return `<label class="field"><span class="lbl">${escapeHtml(lbl)} <span class="muted xsmall">(JSON)</span></span>
      <textarea class="input" rows="3" data-k="${escapeHtml(name)}" data-kind="json">${schema.default != null ? escapeHtml(JSON.stringify(schema.default)) : ''}</textarea></label>`;
  }
  return `<label class="field"><span class="lbl">${escapeHtml(lbl)}</span>
    <input class="input" data-k="${escapeHtml(name)}"${schema.default != null ? ` value="${escapeHtml(String(schema.default))}"` : ''}/></label>`;
}
export function collectSchemaForm(container) {
  const out = {};
  container.querySelectorAll('[data-k]').forEach(el => {
    const k = el.dataset.k;
    const kind = el.dataset.kind || (el.type === 'number' ? 'num' : '');
    let v;
    if (el.type === 'checkbox') v = el.checked;
    else v = el.value;
    if (v === '' || v == null) return;
    if (kind === 'num') v = Number(v);
    else if (kind === 'json') { try { v = JSON.parse(v); } catch { /* leave as string */ } }
    out[k] = v;
  });
  return out;
}
