#!/usr/bin/env node
import fs from "node:fs";
import path from "node:path";
import { validateManifest } from "./validate-manifest.mjs";

const project = path.resolve(process.argv[2] || ".");
const reportPath = path.resolve(process.argv[3] || path.join(project, "audit-static.json"));
const findings = [];
const add = (code, message, file = "") => findings.push({severity: "error", code, message, file});
const textFiles = [];

function walk(directory) {
  for (const entry of fs.readdirSync(directory, {withFileTypes: true})) {
    if ([".git", "node_modules", ".playwright", "screenshots", "dist"].includes(entry.name)) continue;
    const full = path.join(directory, entry.name);
    if (entry.isDirectory()) walk(full);
    else if (/\.(?:html|css|js|mjs|json|svg)$/i.test(entry.name) && full !== reportPath) textFiles.push(full);
  }
}

let manifest;
try {
  manifest = JSON.parse(fs.readFileSync(path.join(project, "course-manifest.json"), "utf8"));
  for (const message of validateManifest(manifest)) add("manifest", message, "course-manifest.json");
} catch (error) {
  add("manifest", `cannot read course-manifest.json: ${error.message}`, "course-manifest.json");
}

if (fs.existsSync(project)) walk(project);
for (const file of textFiles) {
  const relative = path.relative(project, file);
  const source = fs.readFileSync(file, "utf8");
  if (/(?:src|href)=["']https?:\/\/|@import\s+(?:url\()?['"]?https?:\/\/|url\(\s*['"]?https?:\/\//i.test(source)) add("remote-dependency", "remote runtime dependency found; classroom delivery must be local-first", relative);
  if (/(?:sk-[A-Za-z0-9_-]{8,}|api[_-]?key\s*[:=]\s*["'][^"']{8,})/i.test(source)) add("embedded-secret", "possible embedded API credential", relative);
  if (/\b(?:YOUR_API_KEY|REPLACE_ME|LOREM IPSUM)\b/i.test(source)) add("sample-placeholder", "unresolved sample placeholder", relative);
  if (/\b(?:fung\d+|wechat[_-]?id|internal[_-]?id)\b/i.test(source)) add("private-identifier", "possible private identifier", relative);
  if (/\.html$/i.test(file)) {
    const ids = [...source.matchAll(/\bid=["']([^"']+)["']/g)].map(match => match[1]);
    const duplicates = [...new Set(ids.filter((id, index) => ids.indexOf(id) !== index))];
    for (const id of duplicates) add("duplicate-id", `duplicate DOM id: ${id}`, relative);
  }
}

const htmlPath = path.join(project, "index.html");
if (!fs.existsSync(htmlPath)) add("missing-entry", "index.html is required", "index.html");
else {
  const html = fs.readFileSync(htmlPath, "utf8");
  const htmlPages = [...html.matchAll(/data-page-id=["']([^"']+)["']/g)].map(match => match[1]);
  const manifestPages = manifest?.pages?.map(page => page.id) || [];
  if (htmlPages.length && JSON.stringify(htmlPages) !== JSON.stringify(manifestPages)) add("page-sync", "HTML page order does not match manifest", "index.html");
  if (!/@media\s+print/.test(textFiles.filter(file => /\.css$/i.test(file)).map(file => fs.readFileSync(file, "utf8")).join("\n"))) add("print-css", "missing @media print rules", "styles.css");
  if (!/assistant/i.test(html)) add("assistant", "page assistant shell is missing", "index.html");
}

const report = {ok: findings.length === 0, generatedAt: new Date().toISOString(), project: path.basename(project), pageCount: manifest?.pages?.length || 0, checkedFiles: textFiles.length, findings};
fs.mkdirSync(path.dirname(reportPath), {recursive: true});
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2) + "\n");
console.log(JSON.stringify(report, null, 2));
process.exitCode = report.ok ? 0 : 1;
