import { createHash } from "node:crypto";
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import {
  validateInstalledPluginPayload,
  validatePluginManifestV2,
} from "@thortiq/plugin-api";
import { build } from "esbuild";

const projectDirectory = dirname(fileURLToPath(new URL("../manifest.json", import.meta.url)));
const outputDirectory = join(projectDirectory, "dist");

const manifest = JSON.parse(await readFile(join(projectDirectory, "manifest.json"), "utf8"));
const validation = validatePluginManifestV2(manifest);
if (!validation.ok) {
  throw new Error(`manifest.json is invalid: ${validation.issues.map(i => i.code).join(", ")}`);
}
if (manifest.runtime !== "sandboxed-page") throw new Error("This build script targets a sandboxed-page manifest.");

await rm(outputDirectory, { recursive: true, force: true });
await mkdir(outputDirectory, { recursive: true });

const scriptAsset = (manifest.assets ?? []).find(path => path.endsWith(".js"));
if (!scriptAsset) throw new Error("Declare the bundled page script under manifest.assets.");
await build({
  entryPoints: [join(projectDirectory, "src", "index.ts")],
  outfile: join(outputDirectory, scriptAsset),
  bundle: true,
  format: "esm",
  platform: "browser",
  target: "es2022",
  legalComments: "none",
  treeShaking: true,
});

const outputs = new Map();
outputs.set(scriptAsset, await readFile(join(outputDirectory, scriptAsset)));
for (const path of [manifest.entry, ...(manifest.styles ?? []), ...(manifest.assets ?? [])]) {
  if (path === scriptAsset || outputs.has(path)) continue;
  const bytes = await readFile(join(projectDirectory, path));
  await mkdir(dirname(join(outputDirectory, path)), { recursive: true });
  await writeFile(join(outputDirectory, path), bytes);
  outputs.set(path, bytes);
}

const integrity = {};
const payloadFiles = [];
for (const [path, bytes] of [...outputs.entries()].sort(([a], [b]) => (a < b ? -1 : 1))) {
  const digest = `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
  integrity[path] = digest;
  payloadFiles.push({ path, kind: "file", sizeBytes: bytes.byteLength, sha256: digest });
}
const closed = { ...manifest, integrity };
const manifestBytes = Buffer.from(`${JSON.stringify(closed, null, 2)}\n`, "utf8");
const payload = validateInstalledPluginPayload(closed, [
  { path: "manifest.json", kind: "file", sizeBytes: manifestBytes.byteLength },
  ...payloadFiles,
]);
if (!payload.ok) {
  throw new Error(`Closed payload is invalid: ${payload.issues.map(i => i.code).join(", ")}`);
}
await writeFile(join(outputDirectory, "manifest.json"), manifestBytes);
console.log(`Built ${manifest.id}: ${payloadFiles.length + 1} closed files under dist/.`);
