// const fs = require('fs-extra');
// const path = require('path');

// const ini = require('ini');
// const minimist = require('minimist');
// const LRU = require('lru-cache');

// const stripAnsi = require('strip-ansi');

// const {
//   chalk,
//   execa,
//   semver,
//   request,

//   resolvePkg,
//   loadModule,

//   hasYarn,
//   hasProjectYarn,
//   hasPnpm3OrLater,
//   hasPnpmVersionOrLater,
//   hasProjectPnpm,
//   hasProjectNpm,

//   isOfficialPlugin,
//   resolvePluginId,

//   log,
//   warn,
//   error
// } = require('@vue/cli-shared-utils');
const execa = require('execa');
// const { loadOptions } = require('../options');
// const { executeCommand } = require('./executeCommand');

// const registries = require('./registries');
// const shouldUseTaobao = require('./shouldUseTaobao');

// const metadataCache = new LRU({
//   max: 200,
//   maxAge: 1000 * 60 * 30 // 30 min.
// });

// const isTestOrDebug = process.env.VUE_CLI_TEST || process.env.VUE_CLI_DEBUG;

// const SUPPORTED_PACKAGE_MANAGERS = ['yarn', 'pnpm', 'npm'];
// const PACKAGE_MANAGER_PNPM4_CONFIG = {
//   install: ['install', '--reporter', 'silent', '--shamefully-hoist'],
//   add: ['install', '--reporter', 'silent', '--shamefully-hoist'],
//   upgrade: ['update', '--reporter', 'silent'],
//   remove: ['uninstall', '--reporter', 'silent']
// };
// const PACKAGE_MANAGER_PNPM3_CONFIG = {
//   install: ['install', '--loglevel', 'error', '--shamefully-flatten'],
//   add: ['install', '--loglevel', 'error', '--shamefully-flatten'],
//   upgrade: ['update', '--loglevel', 'error'],
//   remove: ['uninstall', '--loglevel', 'error']
// };
const PACKAGE_MANAGER_CONFIG = {
  npm: {
    install: ['install', '--loglevel', 'error'],
    add: ['install', '--loglevel', 'error'],
    upgrade: ['update', '--loglevel', 'error'],
    remove: ['uninstall', '--loglevel', 'error']
  },
  // pnpm: hasPnpmVersionOrLater('4.0.0')
  //   ? PACKAGE_MANAGER_PNPM4_CONFIG
  //   : PACKAGE_MANAGER_PNPM3_CONFIG,
  yarn: {
    install: [],
    add: ['add'],
    upgrade: ['upgrade'],
    remove: ['remove']
  }
};

// extract the package name 'xx' from the format 'xx@1.1'
function stripVersion(packageName) {
  const nameRegExp = /^(@?[^@]+)(@.*)?$/;
  const result = packageName.match(nameRegExp);

  if (!result) {
    throw new Error(`Invalid package name ${packageName}`);
  }

  return result[1];
}

// extract the package scope from the full package name
// the result includes the initial @ character
function extractPackageScope(packageName) {
  const scopedNameRegExp = /^(@[^/]+)\/.*$/;
  const result = packageName.match(scopedNameRegExp);

  if (!result) {
    return undefined;
  }

  return result[1];
}

class PackageManager {
  constructor({ context, forcePackageManager } = {}) {
    this.context = context || process.cwd();
    // this._registries = {};

    if (forcePackageManager) {
      this.bin = forcePackageManager;
    }

    // else if (context) {
    //   if (hasProjectYarn(context)) {
    //     this.bin = 'yarn';
    //   } else if (hasProjectPnpm(context)) {
    //     this.bin = 'pnpm';
    //   } else if (hasProjectNpm(context)) {
    //     this.bin = 'npm';
    //   }
    // }

    // // if no package managers specified, and no lockfile exists
    // if (!this.bin) {
    //   this.bin =
    //     loadOptions().packageManager ||
    //     (hasYarn() ? 'yarn' : hasPnpm3OrLater() ? 'pnpm' : 'npm');
    // }

    // if (this.bin === 'npm') {
    //   // npm doesn't support package aliases until v6.9
    //   const MIN_SUPPORTED_NPM_VERSION = '6.9.0';
    //   const npmVersion = stripAnsi(execa.sync('npm', ['--version']).stdout);

    //   if (semver.lt(npmVersion, MIN_SUPPORTED_NPM_VERSION)) {
    //     throw new Error(
    //       'You are using an outdated version of NPM.\n' +
    //         'It does not support some core functionalities of Vue CLI.\n' +
    //         'Please upgrade your NPM version.'
    //     );
    //   }

    //   if (semver.gte(npmVersion, '7.0.0')) {
    //     this.needsPeerDepsFix = true;
    //   }
    // }

    // if (!SUPPORTED_PACKAGE_MANAGERS.includes(this.bin)) {
    //   log();
    //   warn(
    //     `The package manager ${chalk.red(this.bin)} is ${chalk.red(
    //       'not officially supported'
    //     )}.\n` +
    //       `It will be treated like ${chalk.cyan(
    //         'npm'
    //       )}, but compatibility issues may occur.\n` +
    //       `See if you can use ${chalk.cyan('--registry')} instead.`
    //   );
    //   PACKAGE_MANAGER_CONFIG[this.bin] = PACKAGE_MANAGER_CONFIG.npm;
    // }

    // // Plugin may be located in another location if `resolveFrom` presents.
    // const projectPkg = resolvePkg(this.context);
    // const resolveFrom =
    //   projectPkg && projectPkg.vuePlugins && projectPkg.vuePlugins.resolveFrom;

    // // Logically, `resolveFrom` and `context` are distinct fields.
    // // But in Vue CLI we only care about plugins.
    // // So it is fine to let all other operations take place in the `resolveFrom` directory.
    // if (resolveFrom) {
    //   this.context = path.resolve(context, resolveFrom);
    // }
  }

  // // Any command that implemented registry-related feature should support
  // // `-r` / `--registry` option
  // async getRegistry(scope) {
  //   const cacheKey = scope || '';
  //   if (this._registries[cacheKey]) {
  //     return this._registries[cacheKey];
  //   }

  //   const args = minimist(process.argv, {
  //     alias: {
  //       r: 'registry'
  //     }
  //   });

  //   let registry;
  //   if (args.registry) {
  //     registry = args.registry;
  //   } else if (!process.env.VUE_CLI_TEST && (await shouldUseTaobao(this.bin))) {
  //     registry = registries.taobao;
  //   } else {
  //     try {
  //       if (scope) {
  //         registry = (
  //           await execa(this.bin, ['config', 'get', scope + ':registry'])
  //         ).stdout;
  //       }
  //       if (!registry || registry === 'undefined') {
  //         registry = (await execa(this.bin, ['config', 'get', 'registry']))
  //           .stdout;
  //       }
  //     } catch (e) {
  //       // Yarn 2 uses `npmRegistryServer` instead of `registry`
  //       registry = (
  //         await execa(this.bin, ['config', 'get', 'npmRegistryServer'])
  //       ).stdout;
  //     }
  //   }

  //   this._registries[cacheKey] = stripAnsi(registry).trim();
  //   return this._registries[cacheKey];
  // }

  // async getAuthConfig(scope) {
  //   // get npmrc (https://docs.npmjs.com/configuring-npm/npmrc.html#files)
  //   const possibleRcPaths = [
  //     path.resolve(this.context, '.npmrc'),
  //     path.resolve(require('os').homedir(), '.npmrc')
  //   ];
  //   if (process.env.PREFIX) {
  //     possibleRcPaths.push(path.resolve(process.env.PREFIX, '/etc/npmrc'));
  //   }
  //   // there's also a '/path/to/npm/npmrc', skipped for simplicity of implementation

  //   let npmConfig = {};
  //   for (const loc of possibleRcPaths) {
  //     if (fs.existsSync(loc)) {
  //       try {
  //         // the closer config file (the one with lower index) takes higher precedence
  //         npmConfig = Object.assign(
  //           {},
  //           ini.parse(fs.readFileSync(loc, 'utf-8')),
  //           npmConfig
  //         );
  //       } catch (e) {
  //         // in case of file permission issues, etc.
  //       }
  //     }
  //   }

  //   const registry = await this.getRegistry(scope);
  //   const registryWithoutProtocol = registry
  //     .replace(/https?:/, '') // remove leading protocol
  //     .replace(/([^/])$/, '$1/'); // ensure ending with slash
  //   const authTokenKey = `${registryWithoutProtocol}:_authToken`;
  //   const authUsernameKey = `${registryWithoutProtocol}:username`;
  //   const authPasswordKey = `${registryWithoutProtocol}:_password`;

  //   const auth = {};
  //   if (authTokenKey in npmConfig) {
  //     auth.token = npmConfig[authTokenKey];
  //   }
  //   if (authPasswordKey in npmConfig) {
  //     auth.username = npmConfig[authUsernameKey];
  //     auth.password = Buffer.from(
  //       npmConfig[authPasswordKey],
  //       'base64'
  //     ).toString();
  //   }
  //   return auth;
  // }

  // async setRegistryEnvs() {
  //   const registry = await this.getRegistry();

  //   process.env.npm_config_registry = registry;
  //   process.env.YARN_NPM_REGISTRY_SERVER = registry;

  //   this.setBinaryMirrors();
  // }

  // // set mirror urls for users in china
  // async setBinaryMirrors() {
  //   const registry = await this.getRegistry();

  //   if (registry !== registries.taobao) {
  //     return;
  //   }

  //   try {
  //     // chromedriver, etc.
  //     const binaryMirrorConfigMetadata = await this.getMetadata(
  //       'binary-mirror-config',
  //       { full: true }
  //     );
  //     const latest =
  //       binaryMirrorConfigMetadata['dist-tags'] &&
  //       binaryMirrorConfigMetadata['dist-tags'].latest;
  //     const mirrors = binaryMirrorConfigMetadata.versions[latest].mirrors.china;
  //     for (const key in mirrors.ENVS) {
  //       process.env[key] = mirrors.ENVS[key];
  //     }

  //     // Cypress
  //     const cypressMirror = mirrors.cypress;
  //     const defaultPlatforms = {
  //       darwin: 'osx64',
  //       linux: 'linux64',
  //       win32: 'win64'
  //     };
  //     const platforms = cypressMirror.newPlatforms || defaultPlatforms;
  //     const targetPlatform = platforms[require('os').platform()];
  //     // Do not override user-defined env variable
  //     // Because we may construct a wrong download url and an escape hatch is necessary
  //     if (targetPlatform && !process.env.CYPRESS_INSTALL_BINARY) {
  //       const projectPkg = resolvePkg(this.context);
  //       if (
  //         projectPkg &&
  //         projectPkg.devDependencies &&
  //         projectPkg.devDependencies.cypress
  //       ) {
  //         const wantedCypressVersion = await this.getRemoteVersion(
  //           'cypress',
  //           projectPkg.devDependencies.cypress
  //         );
  //         process.env.CYPRESS_INSTALL_BINARY = `${cypressMirror.host}/${wantedCypressVersion}/${targetPlatform}/cypress.zip`;
  //       }
  //     }
  //   } catch (e) {
  //     // get binary mirror config failed
  //   }
  // }

  // // https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md
  // async getMetadata(packageName, { full = false } = {}) {
  //   const scope = extractPackageScope(packageName);
  //   const registry = await this.getRegistry(scope);

  //   const metadataKey = `${this.bin}-${registry}-${packageName}`;
  //   let metadata = metadataCache.get(metadataKey);

  //   if (metadata) {
  //     return metadata;
  //   }

  //   const headers = {};
  //   if (!full) {
  //     headers.Accept =
  //       'application/vnd.npm.install-v1+json;q=1.0, application/json;q=0.9, */*;q=0.8';
  //   }

  //   const authConfig = await this.getAuthConfig(scope);
  //   if ('password' in authConfig) {
  //     const credentials = Buffer.from(
  //       `${authConfig.username}:${authConfig.password}`
  //     ).toString('base64');
  //     headers.Authorization = `Basic ${credentials}`;
  //   }
  //   if ('token' in authConfig) {
  //     headers.Authorization = `Bearer ${authConfig.token}`;
  //   }

  //   const url = `${registry.replace(/\/$/g, '')}/${packageName}`;
  //   try {
  //     metadata = await request.get(url, { headers });
  //     if (metadata.error) {
  //       throw new Error(metadata.error);
  //     }
  //     metadataCache.set(metadataKey, metadata);
  //     return metadata;
  //   } catch (e) {
  //     error(`Failed to get response from ${url}`);
  //     throw e;
  //   }
  // }

  // async getRemoteVersion(packageName, versionRange = 'latest') {
  //   const metadata = await this.getMetadata(packageName);
  //   if (Object.keys(metadata['dist-tags']).includes(versionRange)) {
  //     return metadata['dist-tags'][versionRange];
  //   }
  //   const versions = Array.isArray(metadata.versions)
  //     ? metadata.versions
  //     : Object.keys(metadata.versions);
  //   return semver.maxSatisfying(versions, versionRange);
  // }

  // getInstalledVersion(packageName) {
  //   // for first level deps, read package.json directly is way faster than `npm list`
  //   try {
  //     const packageJson = loadModule(
  //       `${packageName}/package.json`,
  //       this.context,
  //       true
  //     );
  //     return packageJson.version;
  //   } catch (e) {}
  // }

  async runCommand(command, args) {
    // const prevNodeEnv = process.env.NODE_ENV;
    // // In the use case of Vue CLI, when installing dependencies,
    // // the `NODE_ENV` environment variable does no good;
    // // it only confuses users by skipping dev deps (when set to `production`).
    // delete process.env.NODE_ENV;
    // await this.setRegistryEnvs();
    // await executeCommand(
    //   this.bin,
    //   [...PACKAGE_MANAGER_CONFIG[this.bin][command], ...(args || [])],
    //   this.context
    // );
    // if (prevNodeEnv) {
    //   process.env.NODE_ENV = prevNodeEnv;
    // }

    await execa(
      this.bin,
      [...PACKAGE_MANAGER_CONFIG[this.bin][command], ...(args || [])],
      {
        cwd: this.context,
        stdio: ['inherit', 'inherit', command === 'yarn' ? 'pipe' : 'inherit']
        // stdio: ['ignore']
      }
    );
  }

  async install() {
    const args = [];

    // if (this.needsPeerDepsFix) {
    //   args.push('--legacy-peer-deps');
    // }

    // if (process.env.VUE_CLI_TEST) {
    //   args.push('--silent', '--no-progress');
    // }

    return await this.runCommand('install', args);
  }

  // async add(packageName, { tilde = false, dev = true } = {}) {
  //   const args = dev ? ['-D'] : [];
  //   if (tilde) {
  //     if (this.bin === 'yarn') {
  //       args.push('--tilde');
  //     } else {
  //       process.env.npm_config_save_prefix = '~';
  //     }
  //   }

  //   if (this.needsPeerDepsFix) {
  //     args.push('--legacy-peer-deps');
  //   }

  //   return await this.runCommand('add', [packageName, ...args]);
  // }

  // async remove(packageName) {
  //   return await this.runCommand('remove', [packageName]);
  // }

  // async upgrade(packageName) {
  //   // manage multiple packages separated by spaces
  //   const packageNamesArray = [];

  //   for (const packname of packageName.split(' ')) {
  //     const realname = stripVersion(packname);
  //     if (
  //       isTestOrDebug &&
  //       (packname === '@vue/cli-service' ||
  //         isOfficialPlugin(resolvePluginId(realname)))
  //     ) {
  //       // link packages in current repo for test
  //       const src = path.resolve(__dirname, `../../../../${realname}`);
  //       const dest = path.join(this.context, 'node_modules', realname);
  //       await fs.remove(dest);
  //       await fs.symlink(src, dest, 'dir');
  //     } else {
  //       packageNamesArray.push(packname);
  //     }
  //   }

  //   if (packageNamesArray.length)
  //     return await this.runCommand('add', packageNamesArray);
  // }
}

module.exports = PackageManager;
