import * as fs from 'node:fs';
import * as path from 'node:path';
import parseArguments from './_virtual/index.mjs';
import { cyan, dim, underline, magenta, bold } from './node_modules/.pnpm/colorette@2.0.19/node_modules/colorette/index.mjs';
import './node_modules/.pnpm/common-tags@1.8.2/node_modules/common-tags/es/TemplateTag/TemplateTag.mjs';
import './node_modules/.pnpm/common-tags@1.8.2/node_modules/common-tags/es/commaLists/commaLists.mjs';
import './node_modules/.pnpm/common-tags@1.8.2/node_modules/common-tags/es/commaListsAnd/commaListsAnd.mjs';
import './node_modules/.pnpm/common-tags@1.8.2/node_modules/common-tags/es/commaListsOr/commaListsOr.mjs';
import './node_modules/.pnpm/common-tags@1.8.2/node_modules/common-tags/es/html/html.mjs';
import './node_modules/.pnpm/common-tags@1.8.2/node_modules/common-tags/es/safeHtml/safeHtml.mjs';
import './node_modules/.pnpm/common-tags@1.8.2/node_modules/common-tags/es/oneLine/oneLine.mjs';
import './node_modules/.pnpm/common-tags@1.8.2/node_modules/common-tags/es/oneLineTrim/oneLineTrim.mjs';
import './node_modules/.pnpm/common-tags@1.8.2/node_modules/common-tags/es/oneLineCommaLists/oneLineCommaLists.mjs';
import './node_modules/.pnpm/common-tags@1.8.2/node_modules/common-tags/es/oneLineCommaListsOr/oneLineCommaListsOr.mjs';
import './node_modules/.pnpm/common-tags@1.8.2/node_modules/common-tags/es/oneLineCommaListsAnd/oneLineCommaListsAnd.mjs';
import './node_modules/.pnpm/common-tags@1.8.2/node_modules/common-tags/es/inlineLists/inlineLists.mjs';
import './node_modules/.pnpm/common-tags@1.8.2/node_modules/common-tags/es/oneLineInlineLists/oneLineInlineLists.mjs';
import stripIndent from './node_modules/.pnpm/common-tags@1.8.2/node_modules/common-tags/es/stripIndent/stripIndent.mjs';
import './node_modules/.pnpm/common-tags@1.8.2/node_modules/common-tags/es/stripIndents/stripIndents.mjs';
import { printHelp } from './help.mjs';
import { toValidPackageName, emptyDirectory, loadTemplate, relativeDirectoryForDisplay, createOutputTarget, format, mergeWorkspaceAndProjectPackageJsons, isEmpty } from './shared.mjs';
import { getInWorkspace, getCreateAsMonorepo, getExtrasToSetup, getShouldInstall, getPackageManager } from './shared/prompts.mjs';
import { addToTsConfig } from './shared/tsconfig.mjs';
import { addToPackageManagerWorkspaces } from './shared/package-manager.mjs';
import { prompt } from './packages/cli-kit/build/esnext/prompt.esnext.mjs';

async function createModule() {
  const argv = getArgv();
  if (argv['--help']) {
    const additionalOptions = stripIndent`
      ${cyan(`--entry [entry]`)}
      The entry file for your module. This should be a relative path from the root
      of the project, and should include the file extension.

      ${cyan(`--react`)}
      Whether this package will use React. Defaults to false.
    `;
    printHelp({
      kind: 'module',
      packageManager: argv['--package-manager']?.toLowerCase(),
      options: additionalOptions
    });
    return;
  }
  const inWorkspace = await getInWorkspace(argv);
  const name = await getName(argv);
  const directory = await getDirectory(argv, {
    name
  });
  const entry = await getEntry(argv, {
    name
  });
  const useReact = await getReact(argv);
  const createAsMonorepo = !inWorkspace && (await getCreateAsMonorepo(argv, {
    type: 'module',
    default: false
  }));
  const setupExtras = await getExtrasToSetup(argv, {
    inWorkspace
  });
  const shouldInstall = await getShouldInstall(argv);
  const packageManager = await getPackageManager(argv, {
    root: directory
  });
  const partOfMonorepo = inWorkspace || createAsMonorepo;
  const moduleDirectory = createAsMonorepo ? path.join(directory, toValidPackageName(name)) : directory;
  if (fs.existsSync(directory)) {
    await emptyDirectory(directory);
    if (moduleDirectory !== directory) {
      fs.mkdirSync(moduleDirectory, {
        recursive: true
      });
    }
  } else {
    fs.mkdirSync(moduleDirectory, {
      recursive: true
    });
  }
  const rootDirectory = inWorkspace ? process.cwd() : directory;
  const outputRoot = createOutputTarget(rootDirectory);
  const moduleTemplate = loadTemplate('module');
  const workspaceTemplate = loadTemplate('workspace');
  if (!inWorkspace) {
    await workspaceTemplate.copy(directory, file => {
      // We will adjust the package.json before writing it
      return file !== 'package.json';
    });
    if (setupExtras.has('github')) {
      await loadTemplate('github').copy(directory);
    }
    if (setupExtras.has('vscode')) {
      await loadTemplate('vscode').copy(directory);
    }
  }
  if (createAsMonorepo) {
    const workspacePackageJson = JSON.parse(await workspaceTemplate.read('package.json'));
    workspacePackageJson.name = `${toValidPackageName(name)}-workspace`;
    const moduleRelativeToRoot = relativeDirectoryForDisplay(path.relative(directory, moduleDirectory));
    await outputRoot.write('pnpm-workspace.yaml', await format(`
            packages:
            - './packages/*'
            - '${moduleRelativeToRoot}'
          `, {
      as: 'yaml'
    }));
    await outputRoot.write('package.json', await format(JSON.stringify(workspacePackageJson), {
      as: 'json-stringify'
    }));
  }
  await moduleTemplate.copy(moduleDirectory, file => {
    // We will adjust the package.json before writing it, and we will rename the root
    // module file name to the entry file name
    return file !== 'package.json' && file !== 'module.ts';
  });
  await outputRoot.write(path.join(moduleDirectory, entry), await moduleTemplate.read('module.ts'));
  if (partOfMonorepo) {
    // Add the package to the workspace configuration files
    await Promise.all([addToTsConfig(moduleDirectory, outputRoot), addToPackageManagerWorkspaces(moduleDirectory, outputRoot, packageManager.type)]);

    // Write the package’s package.json (the root one was already created)
    const projectPackageJson = JSON.parse(await moduleTemplate.read('package.json'));
    adjustPackageJson(projectPackageJson, {
      name: toValidPackageName(name),
      react: useReact,
      entry
    });
    await outputRoot.write(path.join(moduleDirectory, 'package.json'), await format(JSON.stringify(projectPackageJson), {
      as: 'json-stringify'
    }));
  } else {
    // Write the package’s package.json by combining elements of the root and
    // package templates
    const [projectPackageJson, workspacePackageJson] = await Promise.all([moduleTemplate.read('package.json').then(content => JSON.parse(content)), workspaceTemplate.read('package.json').then(content => JSON.parse(content))]);
    const mergedPackageJson = mergeWorkspaceAndProjectPackageJsons(projectPackageJson, workspacePackageJson);
    adjustPackageJson(mergedPackageJson, {
      name: toValidPackageName(name),
      react: useReact,
      entry
    });
    await outputRoot.write('package.json', await format(JSON.stringify(mergedPackageJson), {
      as: 'json-stringify'
    }));
  }
  if (shouldInstall) {
    console.log();
    // TODO: better loading, handle errors
    await packageManager.install();
  }
  const commands = [];
  if (!inWorkspace && directory !== process.cwd()) {
    commands.push(`cd ${cyan(relativeDirectoryForDisplay(path.relative(process.cwd(), directory)))} ${dim('# Move into your new module’s directory')}`);
  }
  if (!shouldInstall) {
    commands.push(`${packageManager.commands.install()} ${dim('# Install all your dependencies')}`);
  }
  if (commands.length === 0) {
    console.log();
    console.log('Your new module is ready to go!');
  } else {
    const whatsNext = stripIndent`
      Your new module is ready to go! There’s just ${commands.length > 1 ? 'a few more steps' : 'one more step'} you’ll need to take
      in order to start developing:
    `;
    console.log();
    console.log(whatsNext);
    console.log();
    console.log(commands.map(command => `  ${command}`).join('\n'));
  }
  const followUp = stripIndent`
    Quilt can also help you build, test, lint, and type-check your new module.
    You can learn more about building modules with Quilt by reading the documentation:
    ${underline(magenta('https://github.com/lemonmade/quilt/tree/main/documentation'))}

    Have fun! 🎉
  `;
  console.log();
  console.log(followUp);
}

// Argument handling

function getArgv() {
  const argv = parseArguments({
    '--yes': Boolean,
    '-y': '--yes',
    '--name': String,
    '--directory': String,
    '--entry': String,
    '--install': Boolean,
    '--no-install': Boolean,
    '--monorepo': Boolean,
    '--no-monorepo': Boolean,
    '--package-manager': String,
    '--extras': [String],
    '--no-extras': Boolean,
    '--react': Boolean,
    '--help': Boolean,
    '-h': '--help'
  }, {
    permissive: true
  });
  return argv;
}
async function getName(args) {
  const {
    _,
    '--name': nameArgument
  } = args;
  let name = nameArgument ?? _[1];
  if (name == null) {
    name = await prompt({
      type: 'text',
      message: 'What would you like to name your new module?',
      initial: 'my-module'
    });
  }
  return name;
}
async function getEntry(argv, {
  name
}) {
  if (argv['--entry']) {
    return argv['--entry'];
  }
  const defaultEntry = `${toValidPackageName(name)}.ts`;
  if (argv['--yes']) {
    return defaultEntry;
  }
  const entry = await prompt({
    type: 'text',
    message: 'What do you want to name your entry file?',
    initial: defaultEntry
  });
  return entry;
}
async function getDirectory(argv, {
  name
}) {
  let directory = path.resolve(argv['--directory'] ?? toValidPackageName(name));
  while (!argv['--yes']) {
    if (fs.existsSync(directory) && !(await isEmpty(directory))) {
      const relativeDirectory = path.relative(process.cwd(), directory);
      const empty = await prompt({
        type: 'confirm',
        message: `Directory ${bold(relativeDirectoryForDisplay(relativeDirectory))} is not empty, is it safe to empty it?`,
        initial: true
      });
      if (empty) break;
      const promptDirectory = await prompt({
        type: 'text',
        message: 'What directory do you want to create your new module in?'
      });
      directory = path.resolve(promptDirectory);
    } else {
      break;
    }
  }
  return directory;
}
async function getReact(args) {
  return Boolean(args['--react']);
}
function adjustPackageJson(packageJson, {
  name,
  entry,
  react
}) {
  packageJson.name = name;
  packageJson.exports['.'] = `./${entry}`;
  if (!react) {
    delete packageJson.devDependencies['@types/react'];
    delete packageJson.devDependencies['@types/react-dom'];
    delete packageJson.devDependencies['preact'];
    delete packageJson.devDependencies['react'];
    delete packageJson.devDependencies['react-dom'];
  }
  return packageJson;
}

export { createModule };
