#!/usr/bin/env node
/**
 * Scans repo for TODO/FIXME markers and emits:
 * - machine-readable JSON
 * - human-readable markdown summary
 *
 * Output:
 *  - doc/TODO-CLASSIFICATION.md (overwritten)
 *  - doc/todo-scan.json
 *
 * Note: This script itself may contain "TODO" or "FIXME" in comments or strings.
 * These are intentional and should be excluded from scan results.
 *
 * @author Matthew Raymer
 * @version 1.0.0
 */
const fs = require("fs");
const path = require("path");

const ROOT = process.cwd();

const TARGET_DIRS = [
  "src",
  "ios/Plugin",
  "ios/Tests",
  "android/src/main",
  "android/src/test",
  "scripts",
  "doc",
];

const EXCLUDE_DIR_NAMES = new Set([
  ".git",
  "node_modules",
  "dist",
  "build",
  ".venv",
  "venv",
  "__pycache__",
]);

const FILE_EXTS = new Set([
  ".ts", ".tsx", ".js", ".jsx",
  ".swift",
  ".java", ".kt",
  ".md",
  ".json", ".yml", ".yaml",
  ".py",
]);

const MARKERS = ["TODO", "FIXME"];

function walk(dir, out = []) {
  if (!fs.existsSync(dir)) return out;
  const st = fs.statSync(dir);
  if (!st.isDirectory()) return out;

  for (const name of fs.readdirSync(dir)) {
    if (EXCLUDE_DIR_NAMES.has(name)) continue;
    const p = path.join(dir, name);
    const s = fs.statSync(p);
    if (s.isDirectory()) walk(p, out);
    else out.push(p);
  }
  return out;
}

function scanFile(fp) {
  const ext = path.extname(fp);
  if (!FILE_EXTS.has(ext)) return [];
  const text = fs.readFileSync(fp, "utf8");
  const lines = text.split(/\r?\n/);

  const hits = [];
  const relPath = path.relative(ROOT, fp).replace(/\\/g, "/");
  const isThisScript = relPath === "scripts/todo-scan.js";
  
  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];
    for (const m of MARKERS) {
      // require marker as a token-ish substring
      if (line.includes(m + ":") || line.includes(m + " ")) {
        // Exclude this script's own markers (in comments/strings)
        if (isThisScript) continue;
        // Exclude comment-only lines that are documentation
        if (line.trim().startsWith("*") && line.includes("intentionally")) continue;
        hits.push({ line: i + 1, marker: m, text: line.trim() });
        break;
      }
    }
  }
  return hits;
}

function bucketForPath(rel) {
  if (rel.startsWith("ios/")) return "iOS";
  if (rel.startsWith("android/")) return "Android";
  if (rel.startsWith("src/")) return "TypeScript";
  if (rel.startsWith("doc/")) return "Docs";
  if (rel.startsWith("scripts/")) return "Scripts";
  return "Other";
}

function isCoreCode(rel) {
  // Core code directories (production code)
  return (
    rel.startsWith("ios/Plugin/") ||
    rel.startsWith("android/src/main/") ||
    rel.startsWith("src/") ||
    rel.startsWith("packages/") ||
    rel.startsWith("lib/") ||
    (rel.startsWith("scripts/") && !rel.includes("test"))
  );
}

function isDocsOrTestApp(rel) {
  // Documentation and test harness directories
  return (
    rel.startsWith("doc/") ||
    rel.startsWith("test-apps/") ||
    rel.startsWith("ios/Tests/") ||
    rel.startsWith("android/src/test/") ||
    rel.startsWith("tests/")
  );
}

function main() {
  const results = [];
  for (const d of TARGET_DIRS) {
    const dirAbs = path.join(ROOT, d);
    const files = walk(dirAbs);
    for (const fpAbs of files) {
      const rel = path.relative(ROOT, fpAbs).replace(/\\/g, "/");
      const hits = scanFile(fpAbs);
      for (const h of hits) results.push({ file: rel, ...h, bucket: bucketForPath(rel) });
    }
  }

  // sort stable: bucket, file, line
  results.sort((a, b) =>
    a.bucket.localeCompare(b.bucket) ||
    a.file.localeCompare(b.file) ||
    a.line - b.line
  );

  // Split core vs docs/test-apps
  const coreResults = results.filter(r => isCoreCode(r.file));
  const docsTestResults = results.filter(r => isDocsOrTestApp(r.file));
  const otherResults = results.filter(r => !isCoreCode(r.file) && !isDocsOrTestApp(r.file));

  // Enhanced JSON output with split counts
  const jsonOutput = {
    summary: {
      total: results.length,
      coreCount: coreResults.length,
      docsTestCount: docsTestResults.length,
      otherCount: otherResults.length,
      generatedAt: new Date().toISOString()
    },
    core: coreResults,
    docsTest: docsTestResults,
    other: otherResults,
    all: results
  };

  fs.writeFileSync(path.join(ROOT, "doc/todo-scan.json"), JSON.stringify(jsonOutput, null, 2), "utf8");

  // markdown
  const byBucket = new Map();
  for (const r of results) {
    if (!byBucket.has(r.bucket)) byBucket.set(r.bucket, []);
    byBucket.get(r.bucket).push(r);
  }

  let md = "";
  md += `# TODO Classification (auto-generated)\n\n`;
  md += `Generated by \`scripts/todo-scan.js\`\n\n`;
  md += `## Summary\n\n`;
  md += `- **Total markers:** ${results.length}\n`;
  md += `- **Core code (production):** ${coreResults.length} ⚠️\n`;
  md += `- **Docs/test-apps:** ${docsTestResults.length} ✅ (expected)\n`;
  md += `- **Other:** ${otherResults.length}\n\n`;
  md += `> **Note:** Core code TODOs should be near zero. Docs/test-app TODOs are expected and acceptable.\n\n`;
  md += `---\n\n`;

  for (const [bucket, items] of byBucket.entries()) {
    md += `## ${bucket} (${items.length})\n\n`;
    // group by file
    const byFile = new Map();
    for (const it of items) {
      if (!byFile.has(it.file)) byFile.set(it.file, []);
      byFile.get(it.file).push(it);
    }
    for (const [file, hits] of byFile.entries()) {
      md += `### ${file}\n\n`;
      for (const h of hits) {
        md += `- L${h.line}: **${h.marker}** — ${h.text}\n`;
      }
      md += `\n`;
    }
  }

  fs.writeFileSync(path.join(ROOT, "doc/TODO-CLASSIFICATION.md"), md, "utf8");

  // Console output with split summary
  console.log(`\n📊 TODO Scan Complete`);
  console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
  console.log(`Total markers:     ${results.length}`);
  console.log(`Core code:         ${coreResults.length} ${coreResults.length === 0 ? '✅' : '⚠️  (should be 0)'}`);
  console.log(`Docs/test-apps:    ${docsTestResults.length} ✅ (expected)`);
  console.log(`Other:             ${otherResults.length}`);
  console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`);
}

main();

