'use strict';
const path = require('path');
const debug = require('debug')('yeoman:environment:package-manager');
const preferredPm = require('preferred-pm');

module.exports = cls => class extends cls {
  /**
   * @private
   * Get the destination package.json file.
   * @return {Vinyl | undefined} a Vinyl file.
   */
  getDestinationPackageJson() {
    return this.sharedFs.get(path.resolve(this.cwd, 'package.json'));
  }

  /**
   * @private
   * Get the destination package.json commit status.
   * @return {boolean} package.json commit status.
   */
  isDestinationPackageJsonCommitted() {
    const file = this.getDestinationPackageJson();
    return file && file.committed;
  }

  /**
   * @private
   * Detect the package manager based on files or use the passed one.
   * @return {string} package manager.
   */
  async detectPackageManager() {
    if (this.options.nodePackageManager) {
      return this.options.nodePackageManager;
    }
    const pm = await preferredPm(this.cwd);
    return pm && pm.name;
  }

  /**
   * Executes package manager install.
   * - checks if package.json was committed.
   * - uses a preferred package manager or try to detect.
   * @return {Promise<boolean>} Promise true if the install execution suceeded.
   */
  async packageManagerInstallTask() {
    if (!this.getDestinationPackageJson()) {
      return Promise.resolve(false);
    }

    if (this.compatibilityMode === 'v4') {
      debug('Running in generator < 5 compatibility. Package manager install is done by the generator.');
      return Promise.resolve(false);
    }
    const customInstallTask = this.findGeneratorCustomInstallTask();
    if (customInstallTask && typeof customInstallTask !== 'function') {
      debug('Install disabled by customInstallTask');
      return Promise.resolve(false);
    }

    if (!this.isDestinationPackageJsonCommitted()) {
      this.adapter.log(`
No change to package.json was detected. No package manager install will be executed.`);
      return Promise.resolve(false);
    }

    this.adapter.log(`
Changes to package.json were detected.`);

    if (this.options.skipInstall) {
      this.adapter.log(`Skipping package manager install.
`);
      return Promise.resolve(false);
    }

    let packageManagerName = await this.detectPackageManager();

    const execPackageManager = () => {
      if (!packageManagerName) {
        packageManagerName = 'npm';
        this.adapter.log('Error detecting the package manager. Falling back to npm.');
      }

      if (!['npm', 'yarn', 'pnpm'].includes(packageManagerName)) {
        this.adapter.log(`${packageManagerName} is not a supported package manager. Run it by yourself.`);
        return Promise.resolve(false);
      }

      this.adapter.log(`
Running ${packageManagerName} install for you to install the required dependencies.`);
      return this.spawnCommand(packageManagerName, ['install']).then(() => true);
    };

    if (customInstallTask) {
      const result = customInstallTask(packageManagerName, execPackageManager);
      if (!result || !result.then) {
        return Promise.resolve(true);
      }
      return result;
    }

    return execPackageManager();
  }
};