/** Tiny hyperscript helper. `h('div.card', { onclick }, ...children)`. */
type Child = Node | string | number | null | undefined | false | Child[];
type Props = Record<string, any>;

export function h(tag: string, props?: Props | Child, ...children: Child[]): HTMLElement {
  // parse "tag.class-a.class-b#id" — also tolerates space-separated class lists
  // in a single segment, e.g. "button.btn btn-primary btn-sm".
  let tagName = 'div';
  const classes: string[] = [];
  let id: string | undefined;
  for (const part of tag.split(/(?=[.#])/)) {
    if (!part) continue;
    if (part[0] === '.') classes.push(...part.slice(1).split(/\s+/).filter(Boolean));
    else if (part[0] === '#') id = part.slice(1).split(/\s+/)[0];
    else tagName = part.trim() || 'div';
  }
  const el = document.createElement(tagName);
  if (id) el.id = id;
  if (classes.length) el.classList.add(...classes);

  // props can be omitted; if first arg is a child, shift it
  let realProps: Props | undefined;
  if (props != null && typeof props === 'object' && !(props instanceof Node) && !Array.isArray(props)) {
    realProps = props as Props;
  } else if (props != null) {
    children.unshift(props as Child);
  }

  if (realProps) {
    for (const [k, v] of Object.entries(realProps)) {
      if (v == null || v === false) continue;
      if (k === 'style' && typeof v === 'object') {
        Object.assign(el.style, v);
      } else if (k === 'class' || k === 'className') {
        el.className += (el.className ? ' ' : '') + v;
      } else if (k === 'dataset' && typeof v === 'object') {
        Object.assign((el as HTMLElement).dataset, v);
      } else if (k.startsWith('on') && typeof v === 'function') {
        el.addEventListener(k.slice(2).toLowerCase(), v as EventListener);
      } else if (k === 'html') {
        el.innerHTML = v;
      } else if (k in el && k !== 'list' && k !== 'type') {
        try { (el as any)[k] = v; } catch { el.setAttribute(k, String(v)); }
      } else {
        el.setAttribute(k, v === true ? '' : String(v));
      }
    }
  }
  appendChildren(el, children);
  return el;
}

function appendChildren(el: Element, children: Child[]) {
  for (const c of children) {
    if (c == null || c === false) continue;
    if (Array.isArray(c)) appendChildren(el, c);
    else if (c instanceof Node) el.appendChild(c);
    else el.appendChild(document.createTextNode(String(c)));
  }
}

/** Query helper. */
export function $<T extends Element = HTMLElement>(sel: string, root: ParentNode = document): T | null {
  return root.querySelector<T>(sel);
}
export function $$<T extends Element = HTMLElement>(sel: string, root: ParentNode = document): T[] {
  return Array.from(root.querySelectorAll<T>(sel));
}

/** Remove all children of a node. */
export function clear(el: Element) {
  while (el.firstChild) el.removeChild(el.firstChild);
}

/** Mount a node (replacing) into a root. */
export function mount(root: Element, node: Node) {
  clear(root);
  root.appendChild(node);
}

/** Raw SVG string → element. */
export function svg(markup: string): SVGElement {
  const wrap = document.createElement('div');
  wrap.innerHTML = markup.trim();
  return wrap.firstElementChild as SVGElement;
}
