import { createRequire } from "node:module";
import { cp, mkdir, readFile, realpath, rm } from "node:fs/promises";
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";

async function readPackageManifest(path) {
  return JSON.parse(await readFile(path, "utf8"));
}

async function resolvePackageManifest(name, requireFrom) {
  const entry = await realpath(createRequire(requireFrom).resolve(name));
  let directory = dirname(entry);
  for (;;) {
    const manifestPath = join(directory, "package.json");
    try {
      const manifest = await readPackageManifest(manifestPath);
      if (manifest.name === name) return { directory, manifest, manifestPath };
    } catch (error) {
      if (error?.code !== "ENOENT") throw error;
    }
    const parent = dirname(directory);
    if (parent === directory) throw new Error(`Unable to resolve production package ${name}.`);
    directory = parent;
  }
}

function productionDependencies(manifest) {
  const dependencies = new Map();
  for (const name of Object.keys(manifest.dependencies ?? {})) dependencies.set(name, false);
  for (const name of Object.keys(manifest.optionalDependencies ?? {})) dependencies.set(name, true);
  for (const name of Object.keys(manifest.peerDependencies ?? {})) {
    const optional = manifest.peerDependenciesMeta?.[name]?.optional === true;
    if (!dependencies.has(name)) dependencies.set(name, optional);
  }
  return dependencies;
}

function stagedPackagePath(root, target, source) {
  const sourceRelative = relative(root, source);
  if (isAbsolute(sourceRelative) || sourceRelative.startsWith(`..${sep}`) || sourceRelative === "..") {
    throw new Error("Production dependency resolved outside the repository install tree.");
  }
  const segments = sourceRelative.split(sep);
  const nodeModulesIndex = segments.indexOf("node_modules");
  if (nodeModulesIndex < 0) throw new Error("Production dependency is not installed below node_modules.");
  return join(target, ...segments.slice(nodeModulesIndex));
}

export async function stageWindowsBackendRuntime({ root, target }) {
  const repositoryRoot = await realpath(resolve(root));
  const backendRoot = join(repositoryRoot, "apps", "backend");
  const backendManifestPath = join(backendRoot, "package.json");
  const backendManifest = await readPackageManifest(backendManifestPath);
  const stagedPackages = new Map();
  const visited = new Set();

  await rm(target, { recursive: true, force: true });
  await mkdir(target, { recursive: true });
  await cp(join(backendRoot, "src"), join(target, "src"), {
    recursive: true,
    filter: (source) => !/\.test\.(?:m?js)$/.test(basename(source))
  });
  await cp(backendManifestPath, join(target, "package.json"));
  await cp(join(repositoryRoot, "packages", "shared", "src"), resolve(target, "..", "..", "packages", "shared", "src"), {
    recursive: true,
    filter: (source) => !/\.test\.(?:m?js)$/.test(basename(source))
  });

  async function visit(name, requireFrom, optional = false) {
    let resolved;
    try {
      resolved = await resolvePackageManifest(name, requireFrom);
    } catch (error) {
      if (optional && error?.code === "MODULE_NOT_FOUND") return;
      throw error;
    }
    const source = await realpath(resolved.directory);
    if (visited.has(source)) return;
    visited.add(source);

    const destination = stagedPackagePath(repositoryRoot, target, source);
    await mkdir(dirname(destination), { recursive: true });
    await cp(source, destination, { recursive: true });
    stagedPackages.set(`${resolved.manifest.name}@${resolved.manifest.version}`, true);

    for (const [dependency, dependencyOptional] of productionDependencies(resolved.manifest)) {
      await visit(dependency, resolved.manifestPath, dependencyOptional);
    }
  }

  for (const [dependency, optional] of productionDependencies(backendManifest)) {
    await visit(dependency, backendManifestPath, optional);
  }

  return [...stagedPackages.keys()].sort();
}

const invokedPath = process.argv[1] ? pathToFileURL(resolve(process.argv[1])).href : "";
if (invokedPath === import.meta.url) {
  const [root, target] = process.argv.slice(2);
  if (!root || !target) {
    console.error("Usage: node stage-windows-backend-runtime.mjs ROOT TARGET");
    process.exitCode = 1;
  } else {
    try {
      await stageWindowsBackendRuntime({ root, target });
    } catch {
      console.error("Windows Backend runtime staging failed.");
      process.exitCode = 1;
    }
  }
}
