"use strict";

// from `create-react-app`:
// https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/scripts/init.js

// Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will
// terminate the Node.js process with a non-zero exit code.
process.on("unhandledRejection", (err) => {
  throw err;
});

const path = require("path");
const fs = require("@acanto/workflow-utils/fs");
const chalk = require("@acanto/workflow-utils/chalk");
const spawn = require("@acanto/workflow-utils/crossSpawn");
const execSync = require("child_process").execSync;
const {
  inGitRepo,
  tryGitInit,
  tryGitCommit,
} = require("@acanto/workflow-utils/git");
const os = require("os");

module.exports = function (
  appPath,
  appName,
  verbose,
  originalDirectory,
  templateName
) {
  const appPackage = require(path.join(appPath, "package.json"));
  const useYarn = fs.existsSync(path.join(appPath, "yarn.lock"));

  if (!templateName) {
    console.log("");
    console.error(
      `A template was not provided. This is likely because you're using an outdated version of ${chalk.cyan(
        "@acanto/create-laravel-app"
      )}.`
    );
    console.error(
      `Please note that global installs of ${chalk.cyan(
        "@acanto/create-laravel-app"
      )} are no longer supported.`
    );
    console.error(
      `You can fix this by running ${chalk.cyan(
        "npm uninstall -g @acanto/create-laravel-app"
      )} or ${chalk.cyan(
        "yarn global remove @acanto/create-laravel-app"
      )} before using ${chalk.cyan("@acanto/create-laravel-app")} again.`
    );
    return;
  }

  const templatePath = path.dirname(
    require.resolve(`${templateName}/package.json`, { paths: [appPath] })
  );

  const templateJsonPath = path.join(templatePath, "template.json");

  let templateJson = {};
  if (fs.existsSync(templateJsonPath)) {
    templateJson = require(templateJsonPath);
  }

  const templatePackage = templateJson.package || {};

  if (templateJson.dependencies) {
    templatePackage.dependencies = templateJson.dependencies;
  }
  if (templateJson.scripts) {
    templatePackage.scripts = templateJson.scripts;
  }

  // Keys to ignore in templatePackage
  const templatePackageBlacklist = [
    "name",
    "version",
    "description",
    "keywords",
    "bugs",
    // "license",
    // "author",
    "contributors",
    "files",
    "browser",
    "bin",
    "man",
    "directories",
    "repository",
    "peerDependencies",
    "bundledDependencies",
    "optionalDependencies",
    "engineStrict",
    "os",
    "cpu",
    "preferGlobal",
    "private",
    "publishConfig",
  ];

  // Keys from templatePackage that will be merged with appPackage
  const templatePackageToMerge = ["dependencies", "scripts"];

  // Keys from templatePackage that will be added to appPackage,
  // replacing any existing entries.
  const templatePackageToReplace = Object.keys(templatePackage).filter(
    (key) => {
      return (
        !templatePackageBlacklist.includes(key) &&
        !templatePackageToMerge.includes(key)
      );
    }
  );

  // Copy over some of the devDependencies
  appPackage.dependencies = appPackage.dependencies || {};

  // Add templatePackage keys/values to appPackage, replacing existing entries
  templatePackageToReplace.forEach((key) => {
    appPackage[key] = templatePackage[key];
  });

  fs.writeFileSync(
    path.join(appPath, "package.json"),
    JSON.stringify(appPackage, null, 2) + os.EOL
  );

  const readmeExists = fs.existsSync(path.join(appPath, "README.md"));
  if (readmeExists) {
    fs.renameSync(
      path.join(appPath, "README.md"),
      path.join(appPath, "README.old.md")
    );
  }

  // Copy the files for the user
  const templateDir = path.join(templatePath, "template");
  if (fs.existsSync(templateDir)) {
    fs.copySync(templateDir, appPath);
  } else {
    console.error(
      `Could not locate supplied template: ${chalk.green(templateDir)}`
    );
    return;
  }

  // modifies README.md commands based on user used package manager.
  if (useYarn) {
    try {
      const readme = fs.readFileSync(path.join(appPath, "README.md"), "utf8");
      fs.writeFileSync(
        path.join(appPath, "README.md"),
        readme.replace(/(npm run |npm )/g, "yarn "),
        "utf8"
      );
    } catch (err) {
      // Silencing the error. As it fall backs to using default npm commands.
    }
  }

  // Initialize git repo
  let initializedGit = false;

  if (tryGitInit()) {
    initializedGit = true;
    console.log();
    console.log("Initialized a git repository.");
  }

  let command;
  let remove;
  let args;

  if (useYarn) {
    command = "yarnpkg";
    remove = "remove";
    args = ["add"];
  } else {
    command = "npm";
    remove = "uninstall";
    args = ["install", "--save", verbose ? "--verbose" : "--silent"].filter((e) => e);
  }

  // Install additional template dependencies, if present.
  const dependenciesToInstall = Object.entries({
    ...templatePackage.dependencies,
    ...templatePackage.devDependencies,
  });
  if (dependenciesToInstall.length) {
    args = args.concat(
      dependenciesToInstall.map(([dependency, version]) => {
        return `${dependency}@${version}`;
      })
    );
  }

  // Install template dependencies
  if (templateName && args.length > 1) {
    console.log();
    console.log(`Installing template dependencies using ${command}...`);

    const proc = spawn.sync(command, args, { stdio: "inherit" });
    if (proc.status !== 0) {
      console.error(`\`${command} ${args.join(" ")}\` failed`);
      return;
    }
  }

  // Remove template
  console.log(`Removing template package using ${command}...`);
  console.log();

  const proc = spawn.sync(command, [remove, templateName], {
    stdio: "inherit",
  });
  if (proc.status !== 0) {
    console.error(`\`${command} ${args.join(" ")}\` failed`);
    return;
  }

  // Create git commit if git repo was initialized
  if (
    (inGitRepo() || initializedGit) &&
    tryGitCommit(
      appPath,
      `Initialize project using template '${templateName}' for @acanto/create-laravel-app`
    )
  ) {
    console.log();
    console.log("Created git commit.");
  }

  // Display the most elegant way to cd.
  // This needs to handle an undefined originalDirectory for
  // backward compatibility with old global-cli's.
  let cdpath;
  if (originalDirectory && path.join(originalDirectory, appName) === appPath) {
    cdpath = appName;
  } else {
    cdpath = appPath;
  }

  // Change displayed command to yarn instead of yarnpkg
  const displayedCommand = useYarn ? "yarn" : "npm";

  const commandExistsSync = require("command-exists").sync;

  // run composer
  if (commandExistsSync("composer")) {
    execSync(`composer install --no-interaction --no-progress`);
  }

  // run proper init task automatically
  execSync("npx acanto init");

  // run valet
  // let valetLinked = false;
  // if (commandExistsSync("valet")) {
  //   execSync(`valet link`);
  //   valetLinked = true;
  // }

  console.log();
  console.log(
    `Success! Created ${appName} at ${appPath}, now enter that folder with`
  );
  console.log(chalk.cyan("  cd"), cdpath);
  console.log(
    `You should now open the ${chalk.cyan(
      ".env"
    )} file to ensure everything is set up properly.`
  );
  console.log();
  console.log("Inside this new directory you can run several commands:");
  console.log(chalk.cyan(`  ${displayedCommand} ${useYarn ? "" : "run "}help`));
  console.log("    Show all available commands.");
  console.log();
  // if (valetLinked) {
    console.log();
    console.log(
      chalk.green(`If you have valet your app should be available at http://${appName}.test`)
    );
  // }
  if (readmeExists) {
    console.log();
    console.log(
      chalk.yellow(
        "PS: You had a `README.md` file, we renamed it to `README.old.md`"
      )
    );
  }
  console.log();
  console.log("Happy hacking!");
};
