/* ==========================================================================
   UI builders. Plain DOM, no framework - the dashboard is small enough that a
   framework would cost more than it saves, and this keeps the payload tiny for
   a phone on the robot's Wi-Fi.
   ========================================================================== */

import { confirmDialog } from './core.js';

/** el('div.card', {id:'x'}, child, child) - tag string supports .class and #id */
export function el(spec, props = {}, ...children) {
  const [tagPart, ...classParts] = String(spec).split('.');
  const [tag, id] = tagPart.split('#');
  const node = document.createElement(tag || 'div');
  if (id) node.id = id;
  if (classParts.length) node.className = classParts.join(' ');

  for (const [key, value] of Object.entries(props || {})) {
    if (value === null || value === undefined || value === false) continue;
    if (key === 'class') node.className = `${node.className} ${value}`.trim();
    else if (key === 'text') node.textContent = value;
    else if (key === 'html') node.innerHTML = value;
    else if (key === 'style' && typeof value === 'object') Object.assign(node.style, value);
    else if (key === 'dataset') Object.assign(node.dataset, value);
    else if (key.startsWith('on') && typeof value === 'function') {
      node.addEventListener(key.slice(2).toLowerCase(), value);
    } else if (key in node && key !== 'list' && typeof value !== 'object') {
      try { node[key] = value; } catch { node.setAttribute(key, value); }
    } else {
      node.setAttribute(key, value === true ? '' : value);
    }
  }

  append(node, children);
  return node;
}

export function append(parent, children) {
  for (const child of children.flat(4)) {
    if (child === null || child === undefined || child === false) continue;
    parent.appendChild(child instanceof Node ? child : document.createTextNode(String(child)));
  }
  return parent;
}

export function clear(node) {
  while (node.firstChild) node.removeChild(node.firstChild);
  return node;
}

/**
 * Replace a node's children, dropping null / undefined / false entries.
 *
 * The native replaceChildren() stringifies anything that is not a Node, so a
 * conditional child written as `cond ? el(...) : null` renders the literal word
 * "null" on the page. This keeps the same forgiving semantics as el().
 */
export function setChildren(node, ...children) {
  clear(node);
  append(node, children);
  return node;
}

export function icon(name, cls = 'ico') {
  const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  svg.setAttribute('class', cls);
  svg.setAttribute('aria-hidden', 'true');
  const use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
  use.setAttribute('href', `#i-${name}`);
  svg.appendChild(use);
  return svg;
}

/* -- Page ----------------------------------------------------------------- */

export function pageHead(title, description, actions = []) {
  return el('div.page-head', {},
    el('div', {},
      el('h1', { text: title }),
      description ? el('p', { text: description }) : null,
    ),
    actions.length ? el('div.page-head-actions', {}, ...actions) : null,
  );
}

/* -- Card ----------------------------------------------------------------- */

export function card(title, { sub, actions = [], flush = false, foot } = {}, ...body) {
  const head = title
    ? el('div.card-head', {},
        el('h3', { text: title }),
        sub ? el('span.sub', { text: sub }) : null,
        actions.length ? el('div.card-head-actions', {}, ...actions) : null,
      )
    : null;

  return el('section.card', {},
    head,
    el('div.card-body', { class: flush ? 'flush' : '' }, ...body),
    foot ? el('div.card-foot', {}, foot) : null,
  );
}

/* -- Buttons -------------------------------------------------------------- */

export function button(label, onClick, { style = '', iconName, size = '', disabled = false,
                                         confirm = null, title = '', block = false } = {}) {
  const classes = ['btn'];
  if (style === 'primary') classes.push('btn-primary');
  else if (style === 'danger') classes.push('btn-danger');
  else if (style === 'warn') classes.push('btn-warn');
  else if (style === 'ghost') classes.push('btn-ghost');
  if (size === 'sm') classes.push('btn-sm');
  if (block) classes.push('btn-block');

  const node = el('button', { class: classes.join(' '), disabled, title, type: 'button' },
    iconName ? icon(iconName) : null,
    label ? el('span', { text: label }) : null,
  );

  node.addEventListener('click', async () => {
    if (confirm) {
      const ok = await confirmDialog('Confirm', confirm, { danger: style === 'danger' });
      if (!ok) return;
    }
    onClick?.(node);
  });
  return node;
}

/* -- Fields --------------------------------------------------------------- */

export function field(label, control, hint) {
  return el('label.field', {},
    label ? el('span', { text: label, style: { fontSize: '12px', fontWeight: '550', color: 'var(--text-2)' } }) : null,
    control,
    hint ? el('span.hint', { text: hint }) : null,
  );
}

export function input(props = {}) {
  return el('input.input', { type: 'text', ...props });
}

export function textarea(props = {}) {
  return el('textarea.textarea', props);
}

export function select(options, { value, onChange, ...rest } = {}) {
  const node = el('select.select', rest);
  for (const option of options) {
    const opt = typeof option === 'object' ? option : { value: option, label: String(option) };
    node.appendChild(el('option', { value: opt.value, text: opt.label ?? String(opt.value) }));
  }
  if (value !== undefined && value !== null) node.value = String(value);
  if (onChange) node.addEventListener('change', () => onChange(node.value, node));
  return node;
}

export function toggle(label, checked, onChange) {
  const box = el('input', { type: 'checkbox', checked });
  box.addEventListener('change', () => onChange?.(box.checked));
  return el('label.switch', {}, box, el('span.switch-track'), el('span.switch-label', { text: label }));
}

export function range({ min = 0, max = 1, step = 0.01, value = 0, unit = '',
                        precision = 2, onInput, onChange } = {}) {
  const slider = el('input', { type: 'range', min, max, step, value });
  const readout = el('span.range-value', { text: `${Number(value).toFixed(precision)}${unit}` });

  slider.addEventListener('input', () => {
    const v = Number(slider.value);
    readout.textContent = `${v.toFixed(precision)}${unit}`;
    onInput?.(v);
  });
  slider.addEventListener('change', () => onChange?.(Number(slider.value)));

  const wrap = el('div.range', {}, slider, readout);
  wrap.setValue = (v) => {
    slider.value = v;
    readout.textContent = `${Number(v).toFixed(precision)}${unit}`;
  };
  wrap.getValue = () => Number(slider.value);
  return wrap;
}

export function segmented(options, value, onChange) {
  const node = el('div.segmented', { role: 'group' });
  const buttons = [];
  for (const option of options) {
    const opt = typeof option === 'object' ? option : { value: option, label: String(option) };
    const btn = el('button', { type: 'button', text: opt.label,
                               'aria-pressed': String(opt.value === value) });
    btn.addEventListener('click', () => {
      buttons.forEach((b) => b.setAttribute('aria-pressed', String(b === btn)));
      onChange?.(opt.value);
    });
    buttons.push(btn);
    node.appendChild(btn);
  }
  node.setValue = (v) => {
    options.forEach((option, i) => {
      const optValue = typeof option === 'object' ? option.value : option;
      buttons[i].setAttribute('aria-pressed', String(optValue === v));
    });
  };
  return node;
}

/* -- Display -------------------------------------------------------------- */

export function stat(label, value, { unit = '', sub = '', tone = 'default', spark = null } = {}) {
  return el('div.stat', { dataset: { tone } },
    el('div.stat-label', { text: label }),
    el('div.stat-value', {}, String(value), unit ? el('span.unit', { text: unit }) : null),
    sub ? el('div.stat-sub', { text: sub }) : null,
    spark,
  );
}

export function badge(text, tone = 'default') {
  return el('span.badge', { text, dataset: { tone } });
}

export function meter(fraction, tone = 'default') {
  const pct = Math.max(0, Math.min(1, fraction || 0)) * 100;
  return el('div.meter', {}, el('div.meter-fill', { dataset: { tone }, style: { width: `${pct}%` } }));
}

export function note(text, level = 'default', iconGlyph = 'ⓘ') {
  return el('div.note', { dataset: { level } },
    el('span.note-icon', { text: iconGlyph }),
    el('div', { text }),
  );
}

export function kv(pairs) {
  const list = el('dl.kv');
  for (const [key, value] of pairs) {
    if (value === undefined) continue;
    list.appendChild(el('dt', { text: key }));
    list.appendChild(value instanceof Node ? el('dd', {}, value) : el('dd', { text: String(value ?? '—') }));
  }
  return list;
}

export function table(columns, rows, { empty = 'No data' } = {}) {
  if (!rows.length) return emptyState(empty);

  const head = el('tr');
  for (const column of columns) {
    head.appendChild(el('th', { text: column.label,
      style: column.align === 'right' ? { textAlign: 'right' } : {} }));
  }

  const body = el('tbody');
  for (const row of rows) {
    const tr = el('tr');
    for (const column of columns) {
      const value = column.get ? column.get(row) : row[column.key];
      const cls = column.align === 'right' ? 'num' : (column.key === 'name' ? 'name' : '');
      tr.appendChild(value instanceof Node
        ? el('td', { class: cls }, value)
        : el('td', { class: cls, text: value === null || value === undefined ? '—' : String(value) }));
    }
    body.appendChild(tr);
  }

  return el('div.table-wrap', {}, el('table.data', {}, el('thead', {}, head), body));
}

export function emptyState(title, detail, action) {
  return el('div.empty', {},
    el('div.empty-title', { text: title }),
    detail
      ? el('div', { text: detail, style: { maxWidth: '52ch', whiteSpace: 'pre-line' } })
      : null,
    action,
  );
}

export function keyHint(keys, description) {
  return el('div.row.tight', { style: { fontSize: '11.5px', color: 'var(--text-3)' } },
    el('div.keys', {}, ...keys.map((k) => el('kbd', { text: k }))),
    el('span', { text: description }),
  );
}
