import fs from "node:fs/promises";
import path from "node:path";
import fg from "fast-glob";
import type { CheckError, SyncCheckResult } from "../../lib/sync-check/shared";

/**
 * Model sync check matches db files against the `### Models` section inside
 * each model doc (many db files → one doc file).
 */
export interface ModelCategoryConfig {
  name: "model";
  sourcePattern: string;
  docPattern: string;
  exclusions: RegExp[];
}

function shouldExclude(fileName: string, exclusions: RegExp[]): boolean {
  return exclusions.some((pattern) => pattern.test(fileName));
}

export async function runModelSyncCheck(
  config: ModelCategoryConfig,
  cwd: string,
): Promise<SyncCheckResult> {
  const errors: CheckError[] = [];
  const sources = await fg(config.sourcePattern, { cwd });
  const docs = await fg(config.docPattern, { cwd });

  const sourceBasenames = new Map<string, string>();
  const docModelNames = new Map<string, string>();

  for (const sourcePath of sources) {
    const fileName = path.basename(sourcePath);
    if (shouldExclude(fileName, config.exclusions)) continue;
    const basename = path.basename(sourcePath, path.extname(sourcePath));
    sourceBasenames.set(basename.toLowerCase(), sourcePath);
  }

  for (const docPath of docs) {
    const content = await fs.readFile(path.join(cwd, docPath), "utf-8");
    for (const name of parseModelsSection(content)) {
      docModelNames.set(name.toLowerCase(), docPath);
    }
  }

  for (const [basename, sourcePath] of sourceBasenames) {
    if (!docModelNames.has(basename)) {
      errors.push({
        type: "missing-doc",
        category: config.name,
        sourcePath,
        expectedBasename: basename,
      });
    }
  }

  for (const [basename, docPath] of docModelNames) {
    if (!sourceBasenames.has(basename)) {
      errors.push({
        type: "orphaned-doc",
        category: config.name,
        docPath,
        expectedBasename: basename,
      });
    }
  }

  return { errors, filesChecked: sourceBasenames.size + docs.length };
}

/**
 * Parse the `### Models` section from a markdown doc and return the listed
 * model names.  Each line matching `- ModelName` within the section is
 * collected until the next heading or end of file.
 */
export function parseModelsSection(content: string): string[] {
  const lines = content.split("\n");
  let inSection = false;
  const names: string[] = [];
  for (const line of lines) {
    if (/^### Models\s*$/.test(line)) {
      inSection = true;
      continue;
    }
    if (inSection) {
      if (/^#{1,3}\s/.test(line)) break;
      const m = /^-\s+(\S+)/.exec(line);
      if (m) names.push(m[1]);
    }
  }
  return names;
}
