'use strict';

const { resolve, join, basename } = require('path');
const os = require('os');
const fse = require('fs-extra');
const ora = require('ora');
const ciEnv = require('ci-info');
const chalk = require('chalk');

const { generateNewApp } = require('@strapi/generate-new');

const hasYarn = require('./has-yarn');
const { runInstall, runApp, initGit } = require('./child-process');
const { getStarterPackageInfo, downloadNpmStarter } = require('./fetch-npm-starter');
const logger = require('./logger');
const stopProcess = require('./stop-process');

/**
 * @param  {string} - filePath Path to starter.json file
 */
function readStarterJson(filePath, starter) {
  try {
    const data = fse.readFileSync(filePath);
    return JSON.parse(data);
  } catch (err) {
    stopProcess(`Could not find ${chalk.yellow('starter.json')} in ${chalk.yellow(starter)}`);
  }
}

/**
 * @param {string} rootPath - Path to the project directory
 * @param {string} projectName - Name of the project
 * @param {Object} options
 * @param {boolean} options.useYarn - Use yarn instead of npm
 */
async function initPackageJson(rootPath, projectName, { useYarn } = {}) {
  const packageManager = useYarn ? 'yarn --cwd' : 'npm run --prefix';

  try {
    await fse.writeJson(
      join(rootPath, 'package.json'),
      {
        name: projectName,
        private: true,
        version: '0.0.0',
        scripts: {
          'develop:backend': `${packageManager} backend develop`,
          'develop:frontend': `wait-on http://localhost:1337/admin && ${packageManager} frontend develop`,
          develop: 'cross-env FORCE_COLOR=1 npm-run-all -l -p develop:*',
        },
        devDependencies: {
          'npm-run-all': '4.1.5',
          'wait-on': '5.2.1',
          'cross-env': '7.0.3',
        },
      },
      {
        spaces: 2,
      }
    );
  } catch (err) {
    stopProcess(`Failed to create ${chalk.yellow(`package.json`)} in ${chalk.yellow(rootPath)}`);
  }
}

/**
 * @param {string} path The directory path for install
 * @param {Object} options
 * @param {boolean} options.useYarn Use yarn instead of npm
 */
async function installWithLogs(path, options) {
  const installPrefix = chalk.yellow('Installing dependencies:');
  const loader = ora(installPrefix).start();
  const logInstall = (chunk = '') => {
    loader.text = `${installPrefix} ${chunk
      .toString()
      .split('\n')
      .join(' ')}`;
  };

  const runner = runInstall(path, options);
  runner.stdout.on('data', logInstall);
  runner.stderr.on('data', logInstall);

  await runner;

  loader.stop();
  console.log(`Dependencies installed ${chalk.green('successfully')}.`);
}

/**
 * @param {string} starter The name of the starter as provided by the user
 * @param {Object} options
 * @param {boolean} options.useYarn Use yarn instead of npm
 */
async function getStarterInfo(starter, { useYarn } = {}) {
  const isLocalStarter = ['./', '../', '/'].some(filePrefix => starter.startsWith(filePrefix));

  let starterPath;
  let starterParentPath;
  let starterPackageInfo = {};

  if (isLocalStarter) {
    // Starter is a local directory
    console.log('Installing local starter.');
    starterPath = resolve(starter);
  } else {
    // Starter should be an npm package. Fetch starter info
    starterPackageInfo = await getStarterPackageInfo(starter, { useYarn });
    console.log(`Installing ${chalk.yellow(starterPackageInfo.name)} starter.`);

    // Download starter repository to a temporary directory
    starterParentPath = await fse.mkdtemp(join(os.tmpdir(), 'strapi-'));
    starterPath = await downloadNpmStarter(starterPackageInfo, starterParentPath, { useYarn });
  }

  return { isLocalStarter, starterPath, starterParentPath, starterPackageInfo };
}

/**
 * @param {Object} projectArgs - The arguments for create a project
 * @param {string|null} projectArgs.projectName - The name/path of project
 * @param {string|null} projectArgs.starter - The npm package of the starter
 * @param {Object} program - Commands for generating new application
 */
module.exports = async function buildStarter({ projectName, starter }, program) {
  const hasYarnInstalled = await hasYarn();
  const {
    isLocalStarter,
    starterPath,
    starterParentPath,
    starterPackageInfo,
  } = await getStarterInfo(starter, { useYarn: hasYarnInstalled });

  // Project directory
  const rootPath = resolve(projectName);
  const projectBasename = basename(rootPath);
  const starterJson = readStarterJson(join(starterPath, 'starter.json'), starter);

  try {
    await fse.ensureDir(rootPath);
  } catch (error) {
    stopProcess(`Failed to create ${chalk.yellow(rootPath)}: ${error.message}`);
  }

  // Copy the downloaded frontend folder to the project folder
  const frontendPath = join(rootPath, 'frontend');

  try {
    await fse.copy(join(starterPath, 'starter'), frontendPath, {
      overwrite: true,
      recursive: true,
    });
  } catch (error) {
    stopProcess(`Failed to create ${chalk.yellow(frontendPath)}: ${error.message}`);
  }

  // Delete the starter directory if it was downloaded
  if (!isLocalStarter) {
    await fse.remove(starterParentPath);
  }

  // Set command options for Strapi app
  const generateStrapiAppOptions = {
    ...program,
    starter: starterPackageInfo.name,
    run: false,
  };
  if (starterJson.template.version) {
    generateStrapiAppOptions.template = `${starterJson.template.name}@${starterJson.template.version}`;
  } else {
    generateStrapiAppOptions.template = starterJson.template.name;
  }

  // Create strapi app using the template
  await generateNewApp(join(rootPath, 'backend'), generateStrapiAppOptions);

  // Install frontend dependencies
  console.log(`Creating Strapi starter frontend at ${chalk.yellow(frontendPath)}.`);
  console.log('Installing frontend dependencies');
  await installWithLogs(frontendPath, { useYarn: hasYarnInstalled });

  // Setup monorepo
  initPackageJson(rootPath, projectBasename, { useYarn: hasYarnInstalled });

  // Add gitignore
  try {
    const gitignore = join(__dirname, '..', 'resources', 'gitignore');
    await fse.copy(gitignore, join(rootPath, '.gitignore'));
  } catch (err) {
    logger.warn(`Failed to create file: ${chalk.yellow('.gitignore')}`);
  }

  await installWithLogs(rootPath, { useYarn: hasYarnInstalled });

  if (!ciEnv.isCI) {
    await initGit(rootPath);
  }

  console.log(chalk.green('Starting the app'));
  await runApp(rootPath, { useYarn: hasYarnInstalled });
};
