import { createHash } from "node:crypto";
import {
  copyFileSync,
  existsSync,
  mkdirSync,
  readFileSync,
  readdirSync,
  rmSync,
  writeFileSync,
} from "node:fs";
import { dirname, relative, resolve, sep } from "node:path";
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";

const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const websiteRoot = resolve(projectRoot, "windows-dist");
const outputRoot = resolve(projectRoot, "release/windows");
const windowsArchive = resolve(
  projectRoot,
  "release/factorio-calculator-windows-1.0.0.zip",
);
const configRoot = resolve(projectRoot, ".windows-sea");
const builder = process.env.NODE_SEA_BUILDER || process.execPath;
const windowsNode = process.env.NODE_SEA_WINDOWS || (process.platform === "win32" ? process.execPath : "");

if (!existsSync(websiteRoot)) {
  throw new Error("Run npm run build:windows-site before building the Windows executables.");
}
if (!windowsNode || !existsSync(windowsNode)) {
  throw new Error("Set NODE_SEA_WINDOWS to an official Windows x64 node.exe (Node 25.5 or newer)." );
}

rmSync(outputRoot, { recursive: true, force: true });
mkdirSync(outputRoot, { recursive: true });
mkdirSync(configRoot, { recursive: true });

function filesBelow(directory) {
  return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
    const path = resolve(directory, entry.name);
    return entry.isDirectory() ? filesBelow(path) : [path];
  });
}

function buildExecutable({ name, main, assets = {} }) {
  const output = resolve(outputRoot, name);
  const configPath = resolve(configRoot, `${name}.json`);
  writeFileSync(configPath, JSON.stringify({
    main,
    mainFormat: "commonjs",
    executable: windowsNode,
    output,
    disableExperimentalSEAWarning: true,
    useSnapshot: false,
    useCodeCache: false,
    execArgvExtension: "none",
    assets,
  }, null, 2));
  const result = spawnSync(builder, ["--build-sea", configPath], {
    cwd: projectRoot,
    stdio: "inherit",
  });
  if (result.status !== 0 || !existsSync(output)) {
    throw new Error(`Failed to create ${name}.`);
  }
  return output;
}

if (!existsSync(resolve(websiteRoot, "downloads/factorio-calculator-source-1.0.0.zip"))) {
  throw new Error("The portable website must contain its matching source download. Run package:release, then build:windows-site.");
}

const websiteAssets = Object.fromEntries(
  filesBelow(websiteRoot).map((path) => [
    `site/${relative(websiteRoot, path).split(sep).join("/")}`,
    path,
  ]),
);

const serverExe = buildExecutable({
  name: "FactorioCalculator-ServerBuild.exe",
  main: "windows/portable-server.cjs",
  assets: websiteAssets,
});
const sourceExe = buildExecutable({
  name: "FactorioCalculator-SourceTools.exe",
  main: "windows/source-tools.cjs",
});

const nodeLicense = process.env.NODE_SEA_LICENSE || resolve(dirname(windowsNode), "LICENSE");
if (!existsSync(nodeLicense)) {
  throw new Error("The official Node.js LICENSE file must accompany the Windows executable.");
}
copyFileSync(nodeLicense, resolve(outputRoot, "NODEJS-LICENSE.txt"));
copyFileSync(
  resolve(projectRoot, "vendor/icons-factorio.GPL-3.0.txt"),
  resolve(outputRoot, "PROJECT-LICENSE-GPL-3.0.txt"),
);
copyFileSync(
  resolve(websiteRoot, "THIRD_PARTY_NOTICES.txt"),
  resolve(outputRoot, "THIRD_PARTY_NOTICES.txt"),
);
const windowsAddendumPath = resolve(
  projectRoot,
  "BLUEPRINT_GENERATOR.md",
);
const windowsReadme = [
  readFileSync(resolve(projectRoot, "WINDOWS_EXECUTABLES.md"), "utf8").trim(),
  ...(existsSync(windowsAddendumPath)
    ? [readFileSync(windowsAddendumPath, "utf8").trim()]
    : []),
].join("\n\n");
writeFileSync(resolve(outputRoot, "README-WINDOWS.md"), `${windowsReadme}\n`);
const publicSourceArchive = resolve(
  projectRoot,
  "release/factorio-calculator-source-1.0.0.zip",
);
if (!existsSync(publicSourceArchive)) throw new Error("Build the matching source with npm run package:release first.");
if (existsSync(publicSourceArchive)) {
  copyFileSync(
    publicSourceArchive,
    resolve(outputRoot, "factorio-calculator-source-1.0.0.zip"),
  );
}

function sha256(path) {
  return createHash("sha256").update(readFileSync(path)).digest("hex");
}

writeFileSync(
  resolve(outputRoot, "SHA256SUMS-WINDOWS.txt"),
  `${sha256(serverExe)}  ${serverExe.split(sep).at(-1)}\n${sha256(sourceExe)}  ${sourceExe.split(sep).at(-1)}\n`,
);

rmSync(windowsArchive, { force: true });
const zipResult = spawnSync(
  "zip",
  ["-q", "-X", "-r", windowsArchive, "windows"],
  { cwd: resolve(projectRoot, "release"), stdio: "inherit" },
);
if (zipResult.status !== 0 || !existsSync(windowsArchive)) {
  throw new Error("Failed to create the complete Windows ZIP.");
}

const rootChecksums = resolve(projectRoot, "release/SHA256SUMS.txt");
const previousLines = existsSync(rootChecksums)
  ? readFileSync(rootChecksums, "utf8")
      .split(/\r?\n/)
      .filter(Boolean)
      .filter((line) => !line.endsWith("factorio-calculator-windows-1.0.0.zip"))
  : [];
previousLines.push(
  `${sha256(windowsArchive)}  factorio-calculator-windows-1.0.0.zip`,
);
writeFileSync(rootChecksums, `${previousLines.join("\n")}\n`);

console.log(`Windows executables and ZIP created at ${outputRoot}`);
