import "server-only";

import { cache } from "react";
import { readdirSync, readFileSync } from "node:fs";
import { join } from "node:path";
import GithubSlugger from "github-slugger";

export interface DocsSearchRecord {
  /** Page-level title (h1). */
  pageTitle: string;
  /** Heading text when the record points at a section, else the page title. */
  title: string;
  /** Locale-less href, e.g. /docs/quickstart or /docs/quickstart#a-section. */
  href: string;
  /** Searchable text. */
  content: string;
  /** Locale this record belongs to ("en" | "fr"). */
  lang: string;
}

const DOCS_DIR = join(process.cwd(), "app", "[lang]", "docs");
const LOCALE_FILE = /\/(en|fr)\.mdx$/;

function findLocaleFiles(dir: string): string[] {
  const out: string[] = [];
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
    const full = join(dir, entry.name);
    if (entry.isDirectory()) out.push(...findLocaleFiles(full));
    else if (entry.name === "en.mdx" || entry.name === "fr.mdx")
      out.push(full);
  }
  return out;
}

function hrefFromFile(file: string): string {
  // app/[lang]/docs/quickstart/en.mdx -> /docs/quickstart ; root -> /docs
  const rel = file.slice(DOCS_DIR.length).replace(LOCALE_FILE, "");
  return `/docs${rel}`;
}

// Strip inline markdown so the slug matches the *rendered* heading id.
// rehype-slug (see next.config.ts / components/blog/post-body.tsx) slugs the
// heading's text content *after* MDX has stripped formatting, so `## A **b**`
// renders id "a-b", not "a-b-1" from raw `**`. Mirror that here. Aligned with
// the visible-label cleanup in lib/blog/toc.ts, extended to links/strikethrough.
function stripInlineMarkdown(heading: string): string {
  return heading
    .replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1") // images ![alt](src) -> alt
    .replace(/\[([^\]]*)\]\([^)]*\)/g, "$1") // links [text](url) -> text
    .replace(/`+/g, "") // inline code backticks
    .replace(/~~/g, "") // strikethrough
    .replace(/\*\*|__/g, "") // bold
    .replace(/[*_]/g, "") // italic
    .trim();
}

// Build a flat list of searchable records (page + each h2/h3 section) from the
// per-locale docs MDX (en.mdx / fr.mdx). Each record carries its `lang` so the
// consumer can filter to the active locale. Runs server-side at build time;
// results are passed to the client Fuse index. Memoised per request via
// React `cache()` so layout + page calls in the same render scan files once.
export const buildDocsSearchIndex = cache((): DocsSearchRecord[] => {
  const records: DocsSearchRecord[] = [];

  for (const file of findLocaleFiles(DOCS_DIR)) {
    const lang = file.match(LOCALE_FILE)?.[1] ?? "en";
    const raw = readFileSync(file, "utf8");
    const baseHref = hrefFromFile(file);

    const pageTitle =
      raw.match(/^#\s+(.+)$/m)?.[1].trim() ??
      raw.match(/title:\s*"([^"—]+)/)?.[1].trim() ??
      "Documentation";
    const description = raw.match(/description:\s*"([^"]+)"/)?.[1] ?? "";

    records.push({
      pageTitle,
      title: pageTitle,
      href: baseHref,
      content: `${pageTitle} ${description}`,
      lang,
    });

    const slugger = new GithubSlugger();
    const headingRe = /^#{2,3}\s+(.+)$/gm;
    let match: RegExpExecArray | null;
    while ((match = headingRe.exec(raw)) !== null) {
      const heading = stripInlineMarkdown(match[1]);
      records.push({
        pageTitle,
        title: heading,
        href: `${baseHref}#${slugger.slug(heading)}`,
        content: `${heading} ${pageTitle}`,
        lang,
      });
    }
  }

  return records;
});
