// Info: this file has been generated by Yarn with the approval of the
// `resolve` maintainers. Bugs caused by a code located here should be
// opened against the Yarn repository.

const path = require(`path`);

module.exports = function (_, opts) {
  opts = opts || {};

  if (opts.forceNodeResolution || !process.versions.pnp)
    return opts;

  // It would be nice if we could throw, but that would break the transparent
  // compatibility with packages that use `resolve` today (such as Gulp). Since
  // it's the whole point of this patch, we don't.
  //
  // if (opts.packageIterator || opts.paths)
  //   throw new Error(`The "packageIterator" and "paths" options cannot be used in PnP environments. Set "forceNodeResolution: true" if absolutely needed, or branch on process.versions.pnp otherwise.`);

  const {findPnpApi} = require(`module`);

  const runPnpResolution = (request, basedir) => {
    // Extract the name of the package being requested (1=package name, 2=internal path)
    const parts = request.match(/^((?:@[^/]+\/)?[^/]+)(\/.*)?/);
    if (!parts)
      throw new Error(`Assertion failed: Expected the "resolve" package to call the "paths" callback with package names only (got "${request}")`);

    // Make sure that basedir ends with a slash
    if (basedir.charAt(basedir.length - 1) !== `/`)
      basedir = path.join(basedir, `/`);

    const api = findPnpApi(basedir);
    if (api === null)
      return undefined;

    // This is guaranteed to return the path to the "package.json" file from the given package
    let manifestPath;
    try {
      manifestPath = api.resolveToUnqualified(`${parts[1]}/package.json`, basedir, {considerBuiltins: false});
    } catch (err) {
      return null;
    }

    if (manifestPath === null)
      throw new Error(`Assertion failed: The resolution thinks that "${parts[1]}" is a Node builtin`);

    // Strip the package.json to get the package folder
    const packagePath = path.dirname(manifestPath);

    // Attach the internal path to the resolved package directory
    const unqualifiedPath = typeof parts[2] !== `undefined`
      ? path.join(packagePath, parts[2])
      : packagePath;

    return {packagePath, unqualifiedPath};
  };

  const runPnpResolutionOnArray = (request, paths) => {
    for (let i = 0; i < paths.length; i++) {
      const resolution = runPnpResolution(request, paths[i]);
      if (resolution || i === paths.length - 1) {
        return resolution;
      }
    }

    return null;
  };

  const originalPaths = Array.isArray(opts.paths) ? opts.paths : [];

  const packageIterator = (request, basedir, getCandidates, opts) => {
    const pathsToTest = [basedir].concat(originalPaths);
    const resolution = runPnpResolutionOnArray(request, pathsToTest);
    if (resolution == null)
      return getCandidates();

    return [resolution.unqualifiedPath];
  };

  const paths = (request, basedir, getNodeModulePaths, opts) => {
    const pathsToTest = [basedir].concat(originalPaths);
    const resolution = runPnpResolutionOnArray(request, pathsToTest);
    if (resolution == null)
      return getNodeModulePaths().concat(originalPaths);

    // Stip the local named folder
    let nodeModules = path.dirname(resolution.packagePath);

    // Strip the scope named folder if needed
    if (request.match(/^@[^/]+\//))
      nodeModules = path.dirname(nodeModules);

    return [nodeModules];
  };

  // We need to keep track whether we're in `packageIterator` or not so that
  // the code is compatible with both `resolve` 1.9+ and `resolve` 1.15+
  let isInsideIterator = false;

  if (!opts.__skipPackageIterator) {
    opts.packageIterator = function (request, basedir, getCandidates, opts) {
      isInsideIterator = true;
      try {
        return packageIterator(request, basedir, getCandidates, opts);
      } finally {
        isInsideIterator = false;
      }
    };
  }

  opts.paths = function (request, basedir, getNodeModulePaths, opts) {
    if (isInsideIterator)
      return getNodeModulePaths().concat(originalPaths);

    return paths(request, basedir, getNodeModulePaths, opts);
  };

  return opts;
};
