import { existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs';
import { cp } from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const rootDir = path.resolve(
  path.dirname(fileURLToPath(import.meta.url)),
  '..',
);
const rootNodeModules = path.join(rootDir, 'node_modules');
const standaloneNodeModules = path.join(
  rootDir,
  'apps/next/.next/standalone/node_modules',
);

const seedPackages = [
  '@payloadcms/db-postgres',
  '@payloadcms/graphql',
  '@payloadcms/next',
  '@payloadcms/richtext-lexical',
  '@payloadcms/ui',
  'payload',
];

const packageJsonPathFor = (packageName, startDir) => {
  let current = startDir;

  while (true) {
    const candidate = path.join(
      current,
      'node_modules',
      packageName,
      'package.json',
    );

    if (existsSync(candidate)) {
      return candidate;
    }

    const parent = path.dirname(current);

    if (parent === current) {
      return null;
    }

    current = parent;
  }
};

const targetDirFor = (packageName) =>
  path.join(standaloneNodeModules, ...packageName.split('/'));

const copyPackage = async (packageName, startDir, seen) => {
  if (seen.has(packageName)) {
    return;
  }

  seen.add(packageName);

  const packageJsonPath = packageJsonPathFor(packageName, startDir);

  if (!packageJsonPath) {
    throw new Error(`Unable to resolve runtime package "${packageName}"`);
  }

  const packageDir = path.dirname(packageJsonPath);
  const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
  const targetDir = targetDirFor(packageName);

  rmSync(targetDir, { force: true, recursive: true });
  mkdirSync(path.dirname(targetDir), { recursive: true });
  await cp(packageDir, targetDir, {
    dereference: false,
    errorOnExist: false,
    force: true,
    recursive: true,
  });

  const dependencies = {
    ...packageJson.dependencies,
  };
  const optionalDependencies = {
    ...packageJson.optionalDependencies,
  };

  for (const dependencyName of [
    ...Object.keys(dependencies).sort(),
    ...Object.keys(optionalDependencies).sort(),
  ]) {
    if (dependencyName.startsWith('@types/')) {
      continue;
    }

    try {
      await copyPackage(dependencyName, packageDir, seen);
    } catch (error) {
      if (dependencyName in optionalDependencies) {
        continue;
      }

      throw error;
    }
  }
};

mkdirSync(standaloneNodeModules, { recursive: true });

const seen = new Set();

for (const packageName of seedPackages) {
  await copyPackage(packageName, rootDir, seen);
}

console.log(
  `Copied ${seen.size} Payload runtime packages into Next standalone output.`,
);
