import { readFileSync } from "fs";
import { join } from "path";

type TocItem = {
  title: string;
  href: string;
};

type TechArticle = {
  contentHtml: string;
  toc: TocItem[];
};

const contentRoot = join(process.cwd(), "src", "content", "notes");

function decodeHtml(value: string) {
  return value
    .replace(/&lt;/g, "<")
    .replace(/&gt;/g, ">")
    .replace(/&amp;/g, "&")
    .replace(/&quot;/g, '"')
    .replace(/&#39;/g, "'");
}

function stripTags(value: string) {
  return decodeHtml(value.replace(/<[^>]+>/g, "")).trim();
}

const headingPrefixPattern =
  /^(?:\s|&nbsp;)*(?:(?:第?[一二三四五六七八九十百千万零〇]+|\d+)\s*[、.．)]|[（(]\s*(?:[一二三四五六七八九十百千万零〇]+|\d+)\s*[）)]|[①②③④⑤⑥⑦⑧⑨⑩])\s*/;

function normalizeHeadingTitle(value: string) {
  return value.replace(headingPrefixPattern, "").trim();
}

function normalizeHeadingInnerHtml(value: string) {
  let stripped = false;

  return value.replace(/(^|>)([^<]+)/g, (match, leadingToken: string, text: string) => {
    if (stripped) {
      return match;
    }

    if (!text.replace(/(?:\s|&nbsp;)+/g, "")) {
      return match;
    }

    const nextText = text.replace(headingPrefixPattern, "");
    stripped = nextText !== text;

    return `${leadingToken}${nextText}`;
  });
}

function normalizeArticleHeadings(contentHtml: string) {
  return contentHtml.replace(
    /(<(h[2-5])\b[^>]*>)([\s\S]*?)(<\/\2>)/g,
    (_match, openTag: string, _tagName: string, innerHtml: string, closeTag: string) =>
      `${openTag}${normalizeHeadingInnerHtml(innerHtml)}${closeTag}`,
  );
}

function getToc(contentHtml: string) {
  return [...contentHtml.matchAll(/<h3\b[^>]*id="([^"]+)"[^>]*>([\s\S]*?)<\/h3>/g)].map(
    ([, id, title]) => ({
      title: normalizeHeadingTitle(stripTags(title)),
      href: `#${id}`,
    }),
  );
}

export function getTechArticle(relativePath: string): TechArticle {
  const contentHtml = normalizeArticleHeadings(readFileSync(join(contentRoot, relativePath), "utf8"));

  return {
    contentHtml,
    toc: getToc(contentHtml),
  };
}
