const createSVGElement = (tag) => document.createElementNS("http://www.w3.org/2000/svg", tag);

const createExpanderIcon = () => {
  const svg = createSVGElement("svg");
  svg.setAttribute("viewBox", "0 0 13 10");
  svg.setAttribute("width", "13");
  svg.setAttribute("height", "13");
  const polygon = createSVGElement("polygon");
  polygon.setAttribute("points", "2 1, 12 1, 7 9");
  svg.append(polygon);
  return svg;
};

const createTOCItemElement = (list, map, onclick) => {
  let count = 0;
  const makeID = () => `toc-element-${count++}`;
  const createItem = ({ label, href, subitems }, depth = 0) => {
    const a = document.createElement(href ? "a" : "span");
    a.innerText = label;
    a.setAttribute("role", "treeitem");
    a.tabIndex = -1;
    a.style.paddingInlineStart = `${(depth + 1) * 24}px`;
    list.push(a);
    if (href) {
      if (!map.has(href)) map.set(href, a);
      a.href = href;
      a.onclick = (event) => {
        event.preventDefault();
        onclick(href);
      };
    } else a.onclick = (event) => a.firstElementChild?.onclick(event);

    const li = document.createElement("li");
    li.setAttribute("role", "none");
    li.append(a);
    if (subitems?.length) {
      a.setAttribute("aria-expanded", "false");
      const expander = createExpanderIcon();
      expander.onclick = (event) => {
        event.preventDefault();
        event.stopPropagation();
        const expanded = a.getAttribute("aria-expanded");
        a.setAttribute("aria-expanded", expanded === "true" ? "false" : "true");
      };
      a.prepend(expander);
      const ol = document.createElement("ol");
      ol.id = makeID();
      ol.setAttribute("role", "group");
      a.setAttribute("aria-owns", ol.id);
      ol.replaceChildren(...subitems.map((item) => createItem(item, depth + 1)));
      li.append(ol);
    }
    return li;
  };
  return createItem;
};

// https://www.w3.org/TR/wai-aria-practices-1.2/examples/treeview/treeview-navigation.html
export const createTOCView = (toc, onclick) => {
  const $toc = document.createElement("ol");
  $toc.setAttribute("role", "tree");
  const list = [];
  const map = new Map();
  const createItem = createTOCItemElement(list, map, onclick);
  $toc.replaceChildren(...toc.map((item) => createItem(item)));

  const isTreeItem = (item) => item?.getAttribute("role") === "treeitem";
  const getParents = function* (el) {
    for (let parent = el.parentNode; parent !== $toc; parent = parent.parentNode) {
      const item = parent.previousElementSibling;
      if (isTreeItem(item)) yield item;
    }
  };

  let currentItem, currentVisibleParent;
  $toc.addEventListener("focusout", () => {
    if (!currentItem) return;
    // reset parent focus from last time
    if (currentVisibleParent) currentVisibleParent.tabIndex = -1;
    // if current item is visible, let it have the focus
    if (currentItem.offsetParent) {
      currentItem.tabIndex = 0;
      return;
    }
    // current item is hidden; give focus to the nearest visible parent
    for (const item of getParents(currentItem)) {
      if (item.offsetParent) {
        item.tabIndex = 0;
        currentVisibleParent = item;
        break;
      }
    }
  });

  const setCurrentHref = (href) => {
    if (currentItem) {
      currentItem.removeAttribute("aria-current");
      currentItem.tabIndex = -1;
    }
    const el = map.get(href);
    if (!el) {
      currentItem = list[0];
      currentItem.tabIndex = 0;
      return;
    }
    for (const item of getParents(el)) item.setAttribute("aria-expanded", "true");
    el.setAttribute("aria-current", "page");
    el.tabIndex = 0;
    el.scrollIntoView({ behavior: "smooth", block: "center" });
    currentItem = el;
  };

  const acceptNode = (node) =>
    isTreeItem(node) && node.offsetParent ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
  const iter = document.createTreeWalker($toc, 1, { acceptNode });
  const getIter = (current) => ((iter.currentNode = current), iter);

  for (const el of list)
    el.addEventListener("keydown", (event) => {
      let stop = false;
      const { currentTarget, key } = event;
      switch (key) {
        case " ":
        case "Enter":
          currentTarget.click();
          stop = true;
          break;
        case "ArrowDown":
          getIter(currentTarget).nextNode()?.focus();
          stop = true;
          break;
        case "ArrowUp":
          getIter(currentTarget).previousNode()?.focus();
          stop = true;
          break;
        case "ArrowLeft":
          if (currentTarget.getAttribute("aria-expanded") === "true")
            currentTarget.setAttribute("aria-expanded", "false");
          else getParents(currentTarget).next()?.value?.focus();
          stop = true;
          break;
        case "ArrowRight":
          if (currentTarget.getAttribute("aria-expanded") === "true")
            getIter(currentTarget).nextNode()?.focus();
          else if (currentTarget.getAttribute("aria-owns"))
            currentTarget.setAttribute("aria-expanded", "true");
          stop = true;
          break;
        case "Home":
          list[0].focus();
          stop = true;
          break;
        case "End": {
          const last = list[list.length - 1];
          if (last.offsetParent) last.focus();
          else getIter(last).previousNode()?.focus();
          stop = true;
          break;
        }
      }
      if (stop) {
        event.preventDefault();
        event.stopPropagation();
      }
    });

  return { element: $toc, setCurrentHref };
};
