import { build as esbuild } from "esbuild";
import { build as viteBuild } from "vite";
import { rm, readFile, writeFile } from "fs/promises";
import { execSync } from "child_process";

// server deps to bundle to reduce openat(2) syscalls
// which helps cold start times
const allowlist = [
  "@google/generative-ai",
  "axios",
  "connect-pg-simple",
  "cors",
  "date-fns",
  "drizzle-orm",
  "drizzle-zod",
  "express",
  "express-rate-limit",
  "express-session",
  "jsonwebtoken",
  "memorystore",
  "multer",
  "nanoid",
  "nodemailer",
  "openai",
  "passport",
  "passport-local",
  "pg",
  "stripe",
  "uuid",
  "ws",
  "xlsx",
  "zod",
  "zod-validation-error",
];

async function buildAll() {
  await rm("dist", { recursive: true, force: true });

  console.log("building client...");
  await viteBuild();

  console.log("building server...");
  const pkg = JSON.parse(await readFile("package.json", "utf-8"));
  const allDeps = [
    ...Object.keys(pkg.dependencies || {}),
    ...Object.keys(pkg.devDependencies || {}),
  ];
  const externals = allDeps.filter((dep) => !allowlist.includes(dep));

  await esbuild({
    entryPoints: ["server/index.ts"],
    platform: "node",
    bundle: true,
    format: "cjs",
    outfile: "dist/index.cjs",
    define: {
      "process.env.NODE_ENV": '"production"',
    },
    minify: true,
    external: externals,
    logLevel: "info",
  });

  await bakeVulnScan();
}

// Build-time vulnerability scan, baked into dist/ as a provenance-stamped artifact that the runtime
// compliance check reads (server/lib/compliance-checks.ts → VULNERABILITY_SCAN). Runs `npm audit` against
// the lockfile here, where the npm advisory DB is reachable; the deployed image may have no egress, so
// the verdict must be captured at build and travel inside the image (via the Dockerfile dist/ COPY).
// FAIL-SOFT: if the scan can't run, write an honest { available: false } artifact so the runtime check
// lands on NOT_ASSESSED — never a fabricated pass, never the rejected static CVE seed.
async function bakeVulnScan() {
  console.log("baking vulnerability scan (npm audit)...");
  let vulnerabilities: unknown = null;
  let available = false;
  try {
    let out: string;
    try {
      // execSync goes through a shell so `npm` resolves cross-platform (npm.cmd on Windows,
      // npm on the Alpine builder). Fixed literal command — no interpolation, no injection surface.
      out = execSync("npm audit --package-lock-only --json", {
        encoding: "utf-8",
        stdio: ["ignore", "pipe", "ignore"],
        maxBuffer: 64 * 1024 * 1024,
      });
    } catch (e: any) {
      // npm audit exits NON-ZERO when advisories are found — the JSON is still on stdout.
      out = typeof e?.stdout === "string" ? e.stdout : (e?.stdout?.toString?.() ?? "");
    }
    const parsed = JSON.parse(out);
    if (parsed?.metadata?.vulnerabilities) {
      vulnerabilities = parsed.metadata.vulnerabilities;
      available = true;
    }
  } catch {
    available = false;
  }
  const artifact = {
    source: "npm audit --package-lock-only --json",
    capturedAt: new Date().toISOString(),
    available,
    ...(available ? { vulnerabilities } : {}),
  };
  await writeFile("dist/vuln-scan.json", JSON.stringify(artifact, null, 2));
  console.log(`baked dist/vuln-scan.json (available=${available})`);
}

buildAll().catch((err) => {
  console.error(err);
  process.exit(1);
});
