/**
 * Modern hover/focus tooltip action.
 *
 * Renders a styled bubble appended to <body> with fixed positioning, so it is
 * never clipped by an overflow:hidden ancestor (e.g. the topbar). Themes via the
 * shared design tokens — see `.app-tooltip` in styles/theme.css.
 *
 * Usage: <button use:tooltip={'Switch to dark mode'}>…</button>
 *        <button use:tooltip={{ label: 'What’s new', placement: 'bottom' }}>…</button>
 */
type Placement = 'top' | 'bottom';

type TooltipOptions = string | { label: string; placement?: Placement; delay?: number };

const GAP = 8;
const DEFAULT_DELAY = 300;

function normalize(options: TooltipOptions): { label: string; placement: Placement; delay: number } {
  if (typeof options === 'string') {
    return { label: options, placement: 'bottom', delay: DEFAULT_DELAY };
  }
  return {
    label: options.label,
    placement: options.placement ?? 'bottom',
    delay: options.delay ?? DEFAULT_DELAY
  };
}

export function tooltip(node: HTMLElement, options: TooltipOptions) {
  let current = normalize(options);
  let bubble: HTMLDivElement | null = null;
  let showTimer: ReturnType<typeof setTimeout> | null = null;

  function position() {
    if (!bubble) return;
    const anchor = node.getBoundingClientRect();
    const tip = bubble.getBoundingClientRect();

    let left = anchor.left + anchor.width / 2 - tip.width / 2;
    left = Math.max(GAP, Math.min(left, window.innerWidth - tip.width - GAP));

    const top =
      current.placement === 'top'
        ? anchor.top - tip.height - GAP
        : anchor.bottom + GAP;

    bubble.style.left = `${Math.round(left)}px`;
    bubble.style.top = `${Math.round(top)}px`;
  }

  function show() {
    if (bubble || !current.label) return;
    bubble = document.createElement('div');
    bubble.className = 'app-tooltip';
    bubble.setAttribute('role', 'tooltip');
    bubble.dataset.placement = current.placement;
    bubble.textContent = current.label;
    document.body.appendChild(bubble);
    position();
    // Trigger the fade/translate transition on the next frame.
    requestAnimationFrame(() => bubble?.classList.add('is-visible'));
  }

  function scheduleShow() {
    clearTimeout(showTimer ?? undefined);
    showTimer = setTimeout(show, current.delay);
  }

  function hide() {
    clearTimeout(showTimer ?? undefined);
    showTimer = null;
    bubble?.remove();
    bubble = null;
  }

  node.addEventListener('mouseenter', scheduleShow);
  node.addEventListener('mouseleave', hide);
  node.addEventListener('focus', show);
  node.addEventListener('blur', hide);
  node.addEventListener('click', hide);

  return {
    update(next: TooltipOptions) {
      current = normalize(next);
      if (bubble) {
        bubble.textContent = current.label;
        bubble.dataset.placement = current.placement;
        position();
      }
    },
    destroy() {
      hide();
      node.removeEventListener('mouseenter', scheduleShow);
      node.removeEventListener('mouseleave', hide);
      node.removeEventListener('focus', show);
      node.removeEventListener('blur', hide);
      node.removeEventListener('click', hide);
    }
  };
}
