import * as fs from 'node:fs';
import * as path from 'node:path';
import { fileURLToPath } from 'node:url';

function loadTemplate(name) {
  let templateRootPromise;
  return {
    async copy(to, handleFile) {
      templateRootPromise ??= templateDirectory(name);
      const templateRoot = await templateRootPromise;
      const targetRoot = path.resolve(to);
      const files = fs.readdirSync(templateRoot).filter(file => !path.basename(file).startsWith('.'));
      for (const file of files) {
        if (handleFile) {
          if (!handleFile(file)) {
            continue;
          }
        }
        const targetPath = path.join(targetRoot, file.startsWith('_') ? `.${file.slice(1)}` : file);
        copy(path.join(templateRoot, file), targetPath);
      }
    },
    async read(file) {
      templateRootPromise ??= templateDirectory(name);
      const templateRoot = await templateRootPromise;
      return fs.readFileSync(path.join(templateRoot, file), {
        encoding: 'utf8'
      });
    },
    async has(file) {
      templateRootPromise ??= templateDirectory(name);
      const templateRoot = await templateRootPromise;
      return fs.existsSync(path.join(templateRoot, file));
    }
  };
}
function createOutputTarget(target) {
  return {
    root: target,
    read(file) {
      return fs.promises.readFile(path.resolve(target, file), {
        encoding: 'utf8'
      });
    },
    async write(file, content) {
      await writeFile(path.resolve(target, file), content);
    }
  };
}
let packageRootPromise;
async function templateDirectory(name) {
  return path.join(await getPackageRoot(), 'templates', name);
}
async function getPackageRoot() {
  if (!packageRootPromise) {
    packageRootPromise = (async () => {
      const {
        packageDirectory
      } = await import('./node_modules/.pnpm/pkg-dir@6.0.1/node_modules/pkg-dir/index.mjs');
      return packageDirectory({
        cwd: path.dirname(fileURLToPath(import.meta.url))
      });
    })();
  }
  return packageRootPromise;
}
function toValidPackageName(projectName) {
  return projectName.trim().toLowerCase().replace(/\s+/g, '-').replace(/^[._]/, '').replace(/[^a-z0-9-~@/]+/g, '-');
}
function copy(source, destination) {
  const stat = fs.statSync(source);
  if (stat.isDirectory()) {
    copyDirectory(source, destination);
  } else {
    fs.copyFileSync(source, destination);
  }
}
async function copyDirectory(source, destination) {
  fs.mkdirSync(destination, {
    recursive: true
  });
  for (const file of fs.readdirSync(source)) {
    const srcFile = path.resolve(source, file);
    const destFile = path.resolve(destination, file);
    copy(srcFile, destFile);
  }
}
async function writeFile(file, content) {
  await fs.promises.writeFile(file, content);
}
async function isEmpty(path) {
  return fs.readdirSync(path).length === 0;
}
async function emptyDirectory(dir) {
  if (!fs.existsSync(dir)) {
    return;
  }
  for (const file of fs.readdirSync(dir)) {
    fs.rmSync(path.resolve(dir, file), {
      force: true,
      recursive: true
    });
  }
}
function relativeDirectoryForDisplay(relativeDirectory) {
  return relativeDirectory.startsWith('.') ? relativeDirectory : `.${path.sep}${relativeDirectory}`;
}
async function format(content, {
  as: parser
}) {
  const [{
    format: rootFormat,
    default: prettier
  }, babel, typescript, yaml, estree] = await Promise.all([import('./node_modules/.pnpm/prettier@3.0.0/node_modules/prettier/standalone.mjs'), import('./node_modules/.pnpm/prettier@3.0.0/node_modules/prettier/plugins/babel.mjs'), import('./node_modules/.pnpm/prettier@3.0.0/node_modules/prettier/plugins/typescript.mjs'), import('./node_modules/.pnpm/prettier@3.0.0/node_modules/prettier/plugins/yaml.mjs'),
  // @ts-expect-error Types are not generated correctly for this entry
  import('./node_modules/.pnpm/prettier@3.0.0/node_modules/prettier/plugins/estree.mjs')]);

  // CJS workaround
  const format = rootFormat ?? prettier.format;
  return format(content, {
    arrowParens: 'always',
    bracketSpacing: false,
    singleQuote: true,
    trailingComma: 'all',
    parser,
    plugins: [babel, typescript, yaml, estree]
  });
}
function mergeDependencies(first = {}, second = {}) {
  const all = {
    ...first,
    ...second
  };
  const merged = {};
  for (const [key, value] of Object.entries(all).sort(([keyOne], [keyTwo]) => keyOne.localeCompare(keyTwo))) {
    merged[key] = value;
  }
  return merged;
}
const PACKAGE_JSON_DEPENDENCY_KEYS = new Set(['dependencies', 'devDependencies', 'peerDependencies', 'peerDependenciesMeta']);

// Merges a project and workspace package.json together, with the following nitpicky preferences:
//
// - Take all the project’s fields in the order they appear by default
// - Merge the relevant dependencies together
// - Projects don’t come with `scripts` by default, but that should go before the first dependency list
// - If there are other keys in the workspace package.json, they should go last, in the order they appeared
function mergeWorkspaceAndProjectPackageJsons(projectPackageJson, workspacePackageJson) {
  const newPackageJson = {};
  const seenKeys = new Set();
  let hasHandledScriptsField = false;
  for (const [key, value] of Object.entries(projectPackageJson)) {
    seenKeys.add(key);
    const isDependencyKey = PACKAGE_JSON_DEPENDENCY_KEYS.has(key);
    if (key === 'scripts' || isDependencyKey && !hasHandledScriptsField) {
      newPackageJson.scripts = {
        ...workspacePackageJson.scripts,
        ...projectPackageJson.scripts
      };
      hasHandledScriptsField = true;
      if (key === 'scripts') continue;
    }
    if (isDependencyKey) {
      newPackageJson[key] = mergeDependencies(value, workspacePackageJson[key]);
    } else {
      newPackageJson[key] = value;
    }
  }
  for (const [key, value] of Object.entries(workspacePackageJson)) {
    if (seenKeys.has(key)) continue;
    // Merged workspace + project package.json means we are not in a monorepo
    if (key === 'workspaces') continue;
    newPackageJson[key] = value;
  }
  return newPackageJson;
}

export { createOutputTarget, emptyDirectory, format, isEmpty, loadTemplate, mergeDependencies, mergeWorkspaceAndProjectPackageJsons, relativeDirectoryForDisplay, toValidPackageName, writeFile };
