import {
  createMdxContentService,
  type MdxCompileResult as CoreMdxCompileResult,
} from "@docubook/core";
import { cache } from "react";
import path from "path";
import { execFile } from "child_process";
import { promisify } from "util";
import { page_routes, ROUTES } from "./routes";
import type { TocItem } from "./toc";
import { mdxComponents as components } from "./mdx-components";
import { toIsoDateOnly } from "./utils";

export type MdxCompileResult<Frontmatter> = CoreMdxCompileResult<Frontmatter>;

export type DocsForSlugResult = MdxCompileResult<BaseMdxFrontmatter> & {
  filePath: string;
  tocs: TocItem[];
};

// Shared frontmatter shape used by docs pages.
// Keep this close to exported result types for easier discovery.
export type BaseMdxFrontmatter = {
  title: string;
  description: string;
  image: string;
  date?: string | Date;
};

// `React.cache` deduplicates calls within a single server-render pass.
// Keep request-level cache in app layer, while markdown pipeline lives in core.

const FILE_GIT_DATE_CACHE_MAX = 1000;
const fileGitDateCache = new Map<string, Date | undefined>();

function cacheSet(key: string, value: Date | undefined) {
  if (fileGitDateCache.size >= FILE_GIT_DATE_CACHE_MAX) {
    const firstKey = fileGitDateCache.keys().next().value!;
    fileGitDateCache.delete(firstKey);
  }
  fileGitDateCache.set(key, value);
}
let repoLastCommitDateCache: Date | undefined | null = null;
let gitRootCache: string | undefined;
const execFileAsync = promisify(execFile);

async function getGitRootDir(): Promise<string | undefined> {
  if (gitRootCache !== undefined) return gitRootCache;

  try {
    const { stdout } = await execFileAsync("git", ["rev-parse", "--show-toplevel"]);
    gitRootCache = stdout.trim() || "";
    return gitRootCache || undefined;
  } catch {
    gitRootCache = "";
    return undefined;
  }
}

async function getFileLastCommitDate(absoluteFilePath: string): Promise<Date | undefined> {
  if (fileGitDateCache.has(absoluteFilePath)) {
    return fileGitDateCache.get(absoluteFilePath);
  }

  try {
    const gitRoot = await getGitRootDir();
    if (!gitRoot) {
      cacheSet(absoluteFilePath, undefined);
      return undefined;
    }

    const relativePath = path.relative(gitRoot, absoluteFilePath);
    if (
      relativePath.startsWith("..") ||
      !/^[a-zA-Z0-9\-_/.\s]+$/.test(relativePath) ||
      /(^|\/)\.\.($|\/)/.test(relativePath)
    ) {
      cacheSet(absoluteFilePath, undefined);
      return undefined;
    }

    const { stdout } = await execFileAsync(
      "git",
      ["log", "-1", "--format=%cI", "--", relativePath],
      { cwd: gitRoot }
    );

    const rawDate = stdout.trim();
    if (!rawDate) {
      cacheSet(absoluteFilePath, undefined);
      return undefined;
    }

    const parsed = new Date(rawDate);
    if (Number.isNaN(parsed.getTime())) {
      cacheSet(absoluteFilePath, undefined);
      return undefined;
    }

    cacheSet(absoluteFilePath, parsed);
    return parsed;
  } catch {
    cacheSet(absoluteFilePath, undefined);
    return undefined;
  }
}

async function getRepoLastCommitDate(): Promise<Date | undefined> {
  if (repoLastCommitDateCache !== null) {
    return repoLastCommitDateCache ?? undefined;
  }

  try {
    const gitRoot = await getGitRootDir();
    if (!gitRoot) {
      repoLastCommitDateCache = undefined;
      return undefined;
    }

    const { stdout } = await execFileAsync("git", ["log", "-1", "--format=%cI"], {
      cwd: gitRoot,
    });

    const rawDate = stdout.trim();
    if (!rawDate) {
      repoLastCommitDateCache = undefined;
      return undefined;
    }

    const parsed = new Date(rawDate);
    if (Number.isNaN(parsed.getTime())) {
      repoLastCommitDateCache = undefined;
      return undefined;
    }

    repoLastCommitDateCache = parsed;
    return parsed;
  } catch {
    repoLastCommitDateCache = undefined;
    return undefined;
  }
}

const docsService = createMdxContentService<BaseMdxFrontmatter, TocItem>({
  parseOptions: { components },
  cacheFn: cache,
  frontmatterEnricher: async (frontmatter, absoluteFilePath) => {
    if (!frontmatter.date) {
      const gitDate = await getFileLastCommitDate(absoluteFilePath);
      if (gitDate) return { ...frontmatter, date: toIsoDateOnly(gitDate) };

      const repoDate = await getRepoLastCommitDate();
      if (repoDate) return { ...frontmatter, date: toIsoDateOnly(repoDate) };

      // Keep `date` non-empty even outside git context.
      return { ...frontmatter, date: "12-12-2025" };
    }
    return frontmatter;
  },
});

// Return frontmatter only for a docs slug.
export async function getDocsFrontmatterForSlug(
  slug: string
): Promise<BaseMdxFrontmatter | undefined> {
  try {
    return await docsService.getFrontmatterForSlug(slug);
  } catch (error) {
    console.error(error);
  }
}

/**
 * Get full compiled documentation (public API)
 * Returns: JSX content, frontmatter, TOCs, file path
 */
export async function getDocsForSlug(slug: string): Promise<DocsForSlugResult | undefined> {
  try {
    return await docsService.getCompiledForSlug(slug);
  } catch (error) {
    console.error(error);
  }
}

/**
 * Get only table of contents for a slug
 */
export async function getDocsTocs(slug: string) {
  try {
    return await docsService.getTocsForSlug(slug);
  } catch {
    return [];
  }
}

// Build static params for the docs route segment.
export function getDocsStaticParams() {
  // Ensure the root docs page (docs/index.mdx) is statically generated at /docs.
  // This page is not part of the generated docs tree routes by default.
  // Filter out routes marked as noLink (parent categories that don't have their own page)
  return [
    { slug: [] },
    ...page_routes
      .map((page) => ({
        slug: page.href.split("/").filter(Boolean),
      }))
      .filter(({ slug }) => slug.length > 0), // Exclude empty slugs
  ];
}

// Get previous and next page entries from the route list.
export function getPreviousNext(path: string) {
  const index = page_routes.findIndex(({ href }) => href == `/${path}`);
  return {
    prev: page_routes[index - 1],
    next: page_routes[index + 1],
  };
}

// Collect frontmatter of direct child pages under the given docs path.
export async function getAllChilds(pathString: string) {
  const items = pathString.split("/").filter((it) => it !== "");
  let nestedRoutes = ROUTES;

  // Resolve the nested route branch for the requested path.
  let resolvedHref = "";
  for (const it of items) {
    const found = nestedRoutes.find((innerIt) => innerIt.href === `/${it}`);
    if (!found) break;
    resolvedHref += found.href;
    nestedRoutes = found.items ?? [];
  }
  if (!resolvedHref) return [];

  const children = await Promise.all(
    nestedRoutes.map(async (it) => {
      const slug = `${resolvedHref}${it.href}`.replace(/^\/+/, "");
      const frontmatter = await docsService.getFrontmatterForSlug(slug).catch(() => undefined);

      if (!frontmatter) {
        return null;
      }

      return {
        ...frontmatter,
        href: `/docs${resolvedHref}${it.href}`,
      };
    })
  );

  return children.filter((child): child is BaseMdxFrontmatter & { href: string } => child !== null);
}
