const fs = require("fs");
const path = require("path");
const katex = require("katex");

function extractMath(markdown) {
  const spans = [];
  const blockRe = /\$\$([\s\S]*?)\$\$/g;
  let masked = markdown;
  let match;
  while ((match = blockRe.exec(markdown)) !== null) {
    spans.push({ displayMode: true, tex: match[1].trim(), index: match.index });
  }
  masked = masked.replace(blockRe, "");

  for (let i = 0; i < masked.length; i++) {
    if (masked[i] !== "$" || isEscaped(masked, i)) continue;
    let end = -1;
    for (let j = i + 1; j < masked.length; j++) {
      if (masked[j] === "\n") break;
      if (masked[j] === "$" && !isEscaped(masked, j)) {
        end = j;
        break;
      }
    }
    if (end > i + 1) {
      spans.push({ displayMode: false, tex: masked.slice(i + 1, end).trim(), index: i });
      i = end;
    }
  }
  return spans;
}

function isEscaped(text, index) {
  let slashCount = 0;
  for (let i = index - 1; i >= 0 && text[i] === "\\"; i--) {
    slashCount++;
  }
  return slashCount % 2 === 1;
}

function validateFile(filePath) {
  const markdown = fs.readFileSync(filePath, "utf8");
  const spans = extractMath(markdown);
  const failures = [];
  for (const span of spans) {
    try {
      katex.renderToString(span.tex, {
        displayMode: span.displayMode,
        throwOnError: true,
        strict: "ignore",
        trust: false,
      });
    } catch (error) {
      failures.push({
        index: span.index,
        displayMode: span.displayMode,
        tex: span.tex.slice(0, 500),
        error: error.message,
      });
    }
  }
  return {
    file: filePath,
    math_spans: spans.length,
    failures: failures.length,
    failure_samples: failures.slice(0, 50),
  };
}

function main() {
  const target = process.argv[2];
  if (!target) {
    console.error("Usage: node validate_katex.js <markdown-file-or-directory>");
    process.exit(2);
  }
  const stat = fs.statSync(target);
  const files = [];
  if (stat.isDirectory()) {
    for (const entry of fs.readdirSync(target, { recursive: true })) {
      const full = path.join(target, entry);
      if (fs.existsSync(full) && fs.statSync(full).isFile() && full.endsWith(".md")) {
        files.push(full);
      }
    }
  } else {
    files.push(target);
  }
  const results = files.map(validateFile);
  console.log(JSON.stringify({ files: results.length, results }, null, 2));
  process.exit(results.some((result) => result.failures > 0) ? 1 : 0);
}

main();
