"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.create = create;

var _fsExtra = _interopRequireDefault(require("fs-extra"));

var _path = _interopRequireDefault(require("path"));

var _execa = _interopRequireDefault(require("execa"));

var _ora = _interopRequireDefault(require("ora"));

var _chalk = _interopRequireDefault(require("chalk"));

var _sync = require("../sync");

var _getProjectName = require("./get-project-name");

var _getTemplateInfo = require("./get-template-info");

var _constants = require("../constants");

var _json = require("../file-types/json");

var _addInitialCommit = require("./add-initial-commit");

var _printCreateMessages = require("./print-create-messages");

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

/**
 * Creates and generates a new coat project.
 *
 * @param template The template that should be used for the new project
 * @param directoryInput The optional path to the directory that should be used
 * @param projectNameInput The optional project name that has been provided
 */
async function create(template, directoryInputUnsanitized, projectNameInputUnsanitized) {
  // Prints the coat logo and header
  (0, _printCreateMessages.printCreateHeader)();
  const directoryInput = directoryInputUnsanitized === null || directoryInputUnsanitized === void 0 ? void 0 : directoryInputUnsanitized.trim();
  const projectNameInput = projectNameInputUnsanitized === null || projectNameInputUnsanitized === void 0 ? void 0 : projectNameInputUnsanitized.trim(); // Resolve the target directory and project name for the new coat project
  // from the cli arguments if they are available

  let targetCwd;
  let projectName;

  if (!directoryInput) {
    // No input for the target directory has been provided,
    // therefore the user should be prompted for a project name that
    // will be used as the target directory.
    //
    // The projectNameInput argument will be ignored, since it could have
    // only been passed via an edge case, e.g. when running
    // `coat create template "" my-project` which should not be supported.
    projectName = await (0, _getProjectName.getProjectName)(); // The project name should be used as the target directory in the current
    // working directory

    targetCwd = _path.default.join(process.cwd(), projectName);
  } else {
    if (_path.default.isAbsolute(directoryInput)) {
      // If the directory input is a valid absolute path, it should be used directly
      targetCwd = directoryInput;
    } else {
      // Otherwise, the directory input should be treated as a relative path
      // to the current working directory
      targetCwd = _path.default.join(process.cwd(), directoryInput);
    }

    if (projectNameInput) {
      projectName = projectNameInput;
    } else {
      // Retrieve the trailing folder name of the target directory to use it
      // as the project name
      const suggestedProjectName = _path.default.basename(targetCwd);

      if (suggestedProjectName === directoryInput) {
        // If the suggested project name matches the directory input argument
        // it should be used directly without prompting the user.
        //
        // This should be the most common scenario where create is run like
        // `coat create template my-project`
        // where both the directoryInput and suggested project name are "my-project"
        projectName = suggestedProjectName;
      } else {
        // If the suggested project name does not directly match the directory input,
        // the user should be prompted for a project name and with the trailing folder
        // name as the suggestion.
        //
        // This will likely be a scenario where the user calls coat create like:
        // (process.cwd = /usr/source/some-project)
        // `coat create template .`
        // -> suggested project name will be some-project
        projectName = await (0, _getProjectName.getProjectName)(suggestedProjectName);
      }
    }
  } // Check if a coat manifest file already exists in the project


  let coatManifestExists = false;

  try {
    await _fsExtra.default.readFile(_path.default.join(targetCwd, _constants.COAT_MANIFEST_FILENAME)); // If this line is reached the coat manifest file already exists

    coatManifestExists = true;
  } catch (error) {
    if (error.code !== "ENOENT") {
      // If the error is not due to a missing file, but e.g. due
      // to missing permissions it should be thrown to the user
      throw error;
    } // The coat manifest file does not exist in the target directory

  }

  if (coatManifestExists) {
    console.error(`A coat manifest file already exists in the target directory.\n\nPlease install the template manually via npm and add the name of the template to the existing coat manifest file.`);
    throw new Error("coat manifest file already exists");
  } // Check if a package.json file already exists
  // in the target directory, otherwise create it


  let previousPackageJson;

  try {
    const previousPackageJsonRaw = await _fsExtra.default.readFile(_path.default.join(targetCwd, _constants.PACKAGE_JSON_FILENAME), "utf-8");
    previousPackageJson = JSON.parse(previousPackageJsonRaw);
  } catch (error) {
    if (error.code !== "ENOENT") {
      // If the error is not due to a missing file, but e.g. due
      // to missing permissions it should be thrown to the user
      throw error;
    } // package.json does not exit in the target directory

  }

  if (!previousPackageJson) {
    const packageJson = {
      name: projectName,
      version: "1.0.0"
    }; // Create the package.json file inside the
    // target directory.

    await _fsExtra.default.outputFile(_path.default.join(targetCwd, _constants.PACKAGE_JSON_FILENAME), // package.json does not need to be styled, since npm
    // will alter and format it while installing dependencies
    JSON.stringify(packageJson));
    previousPackageJson = packageJson;
  }

  console.log("\nCreating a new %s project in %s\n", _chalk.default.cyan("coat"), _chalk.default.green(targetCwd)); // Print getting started guidance for customization

  (0, _printCreateMessages.printCreateCustomizationHelp)();
  console.log("%s will install the project template and its dependencies into the project directory.\nThis might take a couple of minutes.\n", _chalk.default.cyan("coat"));
  const installSpinner = (0, _ora.default)("Installing template into project directory\n").start();

  try {
    // Run npm install to install the template and its dependencies
    await (0, _execa.default)("npm", ["install", "--save-exact", "--save-dev", template], {
      cwd: targetCwd
    }); // Templates should have a peerDependency on @coat/cli which could
    // differ from the version which is currently being run.
    //
    // In order to run setup and sync with the correct @coat/cli version
    // peerDependencies of the template should be retrieved and installed
    // as devDependencies in the project.
    //
    // In addition to the peerDependencies, the templateInfo is also required
    // to get the resolves template name, since the template string that has
    // been passed to coat could also be a local file path,
    // GitHub URL or npm tag

    const templateInfo = await (0, _getTemplateInfo.getTemplateInfo)(targetCwd, previousPackageJson, template, installSpinner);
    const peerDependenciesEntries = Object.entries(templateInfo.peerDependencies || {});

    if (peerDependenciesEntries.length) {
      const peerDependencyPackages = peerDependenciesEntries.map(([packageName, packageVersion]) => `${packageName}@${packageVersion}`);
      await (0, _execa.default)("npm", ["install", "--save-dev", ...peerDependencyPackages], {
        cwd: targetCwd
      });
    } else {// TODO: See #15
      // Warn that templates should have a peerDependency on @coat/cli
    }

    installSpinner.succeed(); // Write the coat manifest file

    const coatManifest = {
      name: projectName,
      extends: templateInfo.name
    };
    await _fsExtra.default.writeFile(_path.default.join(targetCwd, _constants.COAT_MANIFEST_FILENAME), (0, _json.polish)(coatManifest, _constants.COAT_MANIFEST_FILENAME));
  } catch (error) {
    installSpinner.fail(); // Re-throw error to provide feedback to the user

    throw error;
  } // Run setup and sync commands in the project directory
  //
  // Check whether @coat/cli is installed locally in the project


  const localCoatVersionTask = await (0, _execa.default)("npx", ["--no-install", "coat", "--version"], {
    cwd: targetCwd,
    reject: false
  });

  if (localCoatVersionTask.exitCode === 0) {
    // Log a message if the locally installed coat version differs
    // from the current version in case an error occurs to make
    // potential troubleshooting more straightforward
    const localCoatVersion = localCoatVersionTask.stdout;

    if (localCoatVersion !== _constants.COAT_CLI_VERSION) {
      console.log("Running %s with @coat/cli version %s\n", _chalk.default.cyan("coat sync"), localCoatVersion);
    } // setup will be triggered via sync


    await (0, _execa.default)("npx", ["--no-install", "coat", "sync"], {
      cwd: targetCwd,
      stdio: "inherit"
    });
  } else {
    // Run setup and sync directly with the currently running
    // @coat/cli version
    //
    // setup will be triggered implicitly via sync
    await (0, _sync.sync)(targetCwd);
  } // Initialize a git repository and add an initial commit
  // if the project was not created in an existing git repository


  await (0, _addInitialCommit.addInitialCommit)(targetCwd);
  console.log("🎊 Your new %s project has been successfully created! 🎊\n", _chalk.default.cyan("coat"));

  const relativePath = _path.default.relative(process.cwd(), targetCwd); // Only log if relativePath exists, i.e. the user is in a different
  // directory than the project target directory


  if (relativePath) {
    console.log("Get started by changing into your project folder: %s", _chalk.default.cyan(`cd ${relativePath}`));
  }

  console.log("You can ensure your generated files are up to date by running: %s\n", _chalk.default.cyan("coat sync"));
  console.log("⚡️ Happy hacking! ⚡️\n");
}