// src/lib/actions/bionicReading.js

function applyBionic(node: HTMLElement) {
  const walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT);
  const textNodes: Text[] = [];
  let current: Node | null;
  while ((current = walker.nextNode())) {
    if (current.nodeType === Node.TEXT_NODE) textNodes.push(current as Text);
  }

  for (const textNode of textNodes) {
    if (textNode.textContent == null) continue;
    const words = textNode.textContent.split(/(\s+)/);
    if (words.length <= 1 && !words[0].trim()) continue;

    const fragment = document.createDocumentFragment();
    for (const word of words) {
      if (!word.trim() || word.length < 2) {
        fragment.appendChild(document.createTextNode(word));
        continue;
      }
      const boldLen = Math.min(3, Math.ceil(word.length / 2));
      const b = document.createElement('b');
      b.textContent = word.slice(0, boldLen);
      fragment.appendChild(b);
      fragment.appendChild(document.createTextNode(word.slice(boldLen)));
    }
    textNode.parentNode?.replaceChild(fragment, textNode);
  }
}

function stripBionic(node: HTMLElement) {
  const bolds = node.querySelectorAll("b");
  for (const b of bolds) {
    const text = document.createTextNode(b.textContent ?? "");
    b.parentNode?.replaceChild(text, b);
  }
  node.normalize(); // merge adjacent text nodes
}

export function bionicReading(node: HTMLElement, enabled: boolean) {
  let observer: MutationObserver | null = null;

  function refresh() {
    // Disconnect observer while we mutate, to avoid infinite loop
    if (observer) observer.disconnect();
    stripBionic(node);
    if (enabled) applyBionic(node);
    if (observer) observe();
  }

  function observe() {
    if (!observer) return;
    observer.observe(node, { childList: true, characterData: true, subtree: true });
  }

  // MutationObserver catches Svelte re-rendering DOM content underneath us
  // (new transcript segments, task name edits, etc.)
  observer = new MutationObserver(refresh);

  if (enabled) applyBionic(node);
  observe();

  return {
    update(newEnabled: boolean) {
      enabled = newEnabled;
      refresh();
    },
    destroy() {
      if (observer) observer.disconnect();
      stripBionic(node);
    }
  };
}
