/**
 * 文章目录（由旧 post-toc.js 迁移）
 */
const HEADING_SEL = "h2, h3, h4";

function headingText(h: Element): string {
  return (h.textContent || "").replace(/\s+/g, " ").trim();
}

function makeHeadingIds(headings: Element[]): void {
  const used = new Set<string>();
  headings.forEach((h) => {
    if (h.id && !used.has(h.id)) {
      used.add(h.id);
      return;
    }
    const raw = headingText(h);
    let base =
      raw
        .toLowerCase()
        .replace(/\s+/g, "-")
        .replace(/[^a-z0-9\u2e80-\u9fff-]/gi, "-")
        .replace(/-+/g, "-")
        .replace(/^-|-$/g, "") || "section";
    let id = base;
    let n = 0;
    while (used.has(id) || document.getElementById(id)) id = `${base}-${++n}`;
    h.id = id;
    used.add(id);
  });
}

function buildTocOl(headings: Element[]) {
  const ol = document.createElement("ol");
  ol.className = "toc-tree";
  const links: HTMLAnchorElement[] = [];
  const path: { level: number; li: HTMLLIElement }[] = [];

  for (const h of headings) {
    const level = parseInt(h.tagName[1], 10);
    if (level < 2 || level > 6) continue;

    const li = document.createElement("li");
    li.className = "toc-item";
    const a = document.createElement("a");
    a.className = "toc-link";
    a.href = "#" + h.id;
    a.textContent = headingText(h);
    a.dataset.targetId = h.id;
    li.appendChild(a);
    links.push(a);

    while (path.length && path[path.length - 1].level >= level) path.pop();

    if (path.length === 0) {
      ol.appendChild(li);
    } else {
      const parentLi = path[path.length - 1].li;
      let sub = parentLi.querySelector(":scope > ol.toc-sub");
      if (!sub) {
        sub = document.createElement("ol");
        sub.className = "toc-sub";
        parentLi.appendChild(sub);
      }
      sub.appendChild(li);
    }
    path.push({ level, li });
  }

  return { ol, links };
}

function setActive(
  linkGroups: HTMLAnchorElement[][],
  activeId: string | null
): void {
  for (const links of linkGroups) {
    for (const a of links) {
      const on = a.dataset.targetId === activeId;
      a.classList.toggle("toc-link--active", !!on);
    }
  }
}

function bindScrollSpy(
  headings: Element[],
  linkGroups: HTMLAnchorElement[][],
  desktopAside: HTMLElement | null,
  mobileFab: HTMLElement | null,
  scope: Element | null | undefined
): void {
  const OFFSET = 100;

  function update() {
    const inArticle = scope ? scope.getBoundingClientRect().bottom > 120 : true;
    if (desktopAside)
      desktopAside.classList.toggle("post-toc--hidden", !inArticle);
    if (mobileFab) mobileFab.classList.toggle("toc-fab--hidden", !inArticle);

    const y = window.scrollY + OFFSET;
    let current: string | null = headings[0]?.id || null;
    for (const h of headings) {
      if (h.getBoundingClientRect().top + window.scrollY <= y)
        current = h.id || null;
    }
    if (current) setActive(linkGroups, current);
  }

  let ticking = false;
  function onScroll() {
    if (ticking) return;
    ticking = true;
    requestAnimationFrame(() => {
      update();
      ticking = false;
    });
  }

  window.addEventListener("scroll", onScroll, { passive: true });
  window.addEventListener("resize", onScroll, { passive: true });
  update();
}

function bindTocLinks(
  container: HTMLElement,
  onNavigate?: (() => void) | null
): void {
  container.addEventListener("click", (e) => {
    const t = e.target as HTMLElement | null;
    const a = t?.closest("a.toc-link") as HTMLAnchorElement | null;
    if (!a) return;
    const id = a.getAttribute("href")?.slice(1);
    if (!id) return;
    const target = document.getElementById(id);
    if (target) {
      e.preventDefault();
      target.scrollIntoView({ behavior: "smooth", block: "start" });
      history.replaceState(null, "", "#" + id);
      onNavigate?.();
    }
  });
}

function initDesktop(
  aside: HTMLElement,
  nav: HTMLElement,
  headings: Element[]
): HTMLAnchorElement[] {
  const { ol, links } = buildTocOl(headings);
  nav.innerHTML = "";
  nav.appendChild(ol);
  aside.hidden = false;
  bindTocLinks(nav, null);
  return links;
}

function initMobile(headings: Element[]) {
  if (document.getElementById("toc-fab")) return { links: [] as HTMLAnchorElement[], fab: null as HTMLElement | null };

  const { ol, links } = buildTocOl(headings);

  const panel = document.createElement("div");
  panel.id = "toc-panel";
  panel.setAttribute("aria-label", "文章目录");
  panel.setAttribute("role", "navigation");
  const panelTitle = document.createElement("p");
  panelTitle.className = "toc-panel__title";
  panelTitle.textContent = "目录";
  panel.appendChild(panelTitle);
  panel.appendChild(ol);
  document.body.appendChild(panel);

  const fab = document.createElement("button");
  fab.id = "toc-fab";
  fab.type = "button";
  fab.setAttribute("aria-label", "打开文章目录");
  fab.setAttribute("aria-controls", "toc-panel");
  fab.setAttribute("aria-expanded", "false");
  fab.innerHTML = `
    <span class="toc-fab__icon" aria-hidden="true">
      <span></span><span></span><span></span>
    </span>`;
  document.body.appendChild(fab);

  function openPanel() {
    panel.classList.add("toc-panel--open");
    fab.setAttribute("aria-expanded", "true");
    fab.setAttribute("aria-label", "关闭文章目录");
    fab.classList.add("toc-fab--open");
  }
  function closePanel() {
    panel.classList.remove("toc-panel--open");
    fab.setAttribute("aria-expanded", "false");
    fab.setAttribute("aria-label", "打开文章目录");
    fab.classList.remove("toc-fab--open");
  }

  fab.addEventListener("click", () => {
    panel.classList.contains("toc-panel--open") ? closePanel() : openPanel();
  });

  document.addEventListener("keydown", (e) => {
    if (e.key === "Escape") closePanel();
  });

  bindTocLinks(panel, closePanel);

  return { links, fab };
}

export function teardownPostTocUi(): void {
  document.getElementById("toc-fab")?.remove();
  document.getElementById("toc-panel")?.remove();
}

export function initPostToc(markdownBody: HTMLElement | null): void {
  const aside = document.getElementById("post-toc");
  const nav = document.getElementById("post-toc-nav");
  if (!markdownBody) return;

  const headings = [...markdownBody.querySelectorAll(HEADING_SEL)];
  if (headings.length === 0) {
    if (aside) aside.hidden = true;
    return;
  }

  makeHeadingIds(headings);
  const scope = markdownBody.closest(".post-with-toc");

  let desktopLinks: HTMLAnchorElement[] = [];
  if (aside && nav) {
    desktopLinks = initDesktop(aside, nav, headings);
  }

  const { links: mobileLinks, fab: mobileElem } = initMobile(headings);

  const fab = mobileElem || document.getElementById("toc-fab");
  bindScrollSpy(headings, [desktopLinks, mobileLinks], aside, fab, scope);

  if (location.hash.length > 1) {
    const id = decodeURIComponent(location.hash.slice(1));
    if (headings.some((h) => h.id === id)) {
      setActive([desktopLinks, mobileLinks], id);
    }
  }
}
