/**
 * Based on webpack-node-externals under the MIT license v 1.6.0 (590dad4d6b59bb0904746c6fc15e10e72259e48b)
 *
 * Changes include:
 * * making linter happy.
 * * adding modulesDirs to support multiple node_modules locations.
 */

var fs = require('fs');
var path = require('path');
var pattern = '@[a-zA-Z0-9][\\w-.]+/[a-zA-Z0-9][\\w-.]+([a-zA-Z0-9./]+)?';

var scopedModuleRegex = new RegExp(pattern, 'g');
var atPrefix = new RegExp('^@', 'g');
function contains(arr, val) {
  return arr && arr.indexOf(val) !== -1;
}

function readDir(dirName) {
  try {
    return fs
      .readdirSync(dirName)
      .map(function(module) {
        if (atPrefix.test(module)) {
          // reset regexp
          atPrefix.lastIndex = 0;
          try {
            return fs
              .readdirSync(path.join(dirName, module))
              .map(function(scopedMod) {
                return `${module}/${scopedMod}`;
              });
          } catch (e) {
            return [module];
          }
        }
        return module;
      })
      .reduce(function(prev, next) {
        return prev.concat(next);
      }, []);
  } catch (e) {
    console.log(e);
    return [];
  }
}

function readFromPackageJson() {
  var packageJson;
  try {
    var path = path.join(process.cwd(), './package.json');
    var packageJsonString = fs.readFileSync(path, 'utf8');
    packageJson = JSON.parse(packageJsonString);
  } catch (e) {
    return [];
  }
  var sections = [
    'dependencies',
    'devDependencies',
    'peerDependencies',
    'optionalDependencies',
  ];
  var deps = {};
  sections.forEach(function(section) {
    Object.keys(packageJson[section] || {}).forEach(function(dep) {
      deps[dep] = true;
    });
  });
  return Object.keys(deps);
}

function containsPattern(arr, val) {
  return (
    arr &&
    arr.some(function(pattern) {
      if (pattern instanceof RegExp) {
        return pattern.test(val);
      } else if (typeof pattern === 'function') {
        return pattern(val);
      } else {
        return pattern === val;
      }
    })
  );
}

function getModuleName(request, includeAbsolutePaths) {
  var req = request;
  var delimiter = '/';

  if (includeAbsolutePaths) {
    req = req.replace(/^.*?\/node_modules\//, '');
  }
  // check if scoped module
  if (scopedModuleRegex.test(req)) {
    // reset regexp
    scopedModuleRegex.lastIndex = 0;
    return req.split(delimiter, 2).join(delimiter);
  }
  return req.split(delimiter)[0];
}

module.exports = function nodeExternals(maybeOptions) {
  const options = maybeOptions || {};
  const whitelist = [].concat(options.whitelist || []);
  const binaryDirs = [].concat(options.binaryDirs || ['.bin']);
  const importType = options.importType || 'commonjs';
  const modulesDirs = options.modulesDirs || [
    options.modulesDir || 'node_modules',
  ];
  const modulesFromFile = !!options.modulesFromFile;
  const includeAbsolutePaths = !!options.includeAbsolutePaths;

  // helper function
  function isNotBinary(x) {
    return !contains(binaryDirs, x);
  }

  // create the node modules list
  const combine = (modules, modulesDir) => [...modules, ...readDir(modulesDir)];
  var nodeModules = modulesFromFile
    ? readFromPackageJson()
    : modulesDirs.reduce(combine, []).filter(isNotBinary);

  // return an externals function
  return function(context, request, callback) {
    var moduleName = getModuleName(request, includeAbsolutePaths);
    if (
      contains(nodeModules, moduleName) &&
      !containsPattern(whitelist, request)
    ) {
      // mark this module as external
      // https://webpack.github.io/docs/configuration.html#externals
      return callback(null, `${importType} ${request}`);
    }
    callback();
  };
};
