const path = require('path');

const eslint = require('eslint');
const glob = require('glob');
const isAbsolute = require('path-is-absolute');
const difference = require('./array-diff');
const getSortedRules = require('./sort-rules');
const normalizePluginName = require('./normalize-plugin-name');

function _getConfigFile(specifiedFile) {
  if (specifiedFile) {
    if (isAbsolute(specifiedFile)) {
      return specifiedFile;
    }
    return path.join(process.cwd(), specifiedFile);
  }
  // This is not being called with an arg. Use the package.json `main`
  return require(path.join(process.cwd(), 'package.json')).main;
}

function _getConfigs(configFile, files) {
  const cliEngine = new eslint.CLIEngine({
    // Ignore any config applicable depending on the location on the filesystem
    useEslintrc: false,
    // Point to the particular config
    configFile
  });
  return new Set(files
                 .map(filePath => cliEngine.isPathIgnored(filePath) ? false : cliEngine.getConfigForFile(filePath))
                 .filter(Boolean));
}

function _getConfig(configFile, files) {
  return Array.from(_getConfigs(configFile, files)).reduce((prev, item) => {
    return Object.assign(prev, item, {rules: Object.assign({}, prev.rules, item.rules)});
  }, {});
}

function _getCurrentNamesRules(config) {
  return config.rules ? Object.keys(config.rules) : [];
}

function _isDeprecated(rule) {
  return rule && rule.meta && rule.meta.deprecated;
}

function _notDeprecated(rule) {
  return !_isDeprecated(rule);
}

function _getPluginRules(config) {
  const pluginRules = new Map();
  const plugins = config.plugins;
  /* istanbul ignore else */
  if (plugins) {
    plugins.forEach(plugin => {
      const normalized = normalizePluginName(plugin);
      const pluginConfig = require(normalized.module);
      const rules = pluginConfig.rules === undefined ? {} : pluginConfig.rules;

      Object.keys(rules).forEach(ruleName =>
        pluginRules.set(`${normalized.prefix}/${ruleName}`, rules[ruleName])
      );
    });
  }
  return pluginRules;
}

function _getCoreRules() {
  return eslint.linter.getRules();
}

function _filterRuleNames(ruleNames, rules, predicate) {
  return ruleNames.filter(ruleName => predicate(rules.get(ruleName)));
}

function _isNotCore(rule) {
  return rule.indexOf('/') !== '-1';
}

function _escapeRegExp(str) {
  return str.replace(/[\\^$.*+?()[\]{}|]/ug, '\\$&');
}

function _createExtensionRegExp(extensions) {
  const normalizedExts = extensions.map(ext => _escapeRegExp(
    ext.startsWith(".")
      ? ext.slice(1)
      : ext
  ));

  return new RegExp(
    `.\\.(?:${normalizedExts.join("|")})$`,
    "u"
  );
}

function RuleFinder(specifiedFile, {omitCore, includeDeprecated, ext = ['.js']}) {
  const configFile = _getConfigFile(specifiedFile);

  const extensionRegExp = _createExtensionRegExp(ext);
  const files = glob.sync(`**/*`, {dot: true, matchBase: true})
    .filter(file => extensionRegExp.test(file));

  const config = _getConfig(configFile, files);
  let currentRuleNames = _getCurrentNamesRules(config);
  if (omitCore) {
    currentRuleNames = currentRuleNames.filter(_isNotCore);
  }

  const pluginRules = _getPluginRules(config); // eslint-disable-line vars-on-top
  const coreRules = _getCoreRules();
  const allRules = omitCore ? pluginRules : new Map([...coreRules, ...pluginRules]);

  let allRuleNames = [...allRules.keys()];
  let pluginRuleNames = [...pluginRules.keys()];
  if (!includeDeprecated) {
    allRuleNames = _filterRuleNames(allRuleNames, allRules, _notDeprecated);
    pluginRuleNames = _filterRuleNames(pluginRuleNames, pluginRules, _notDeprecated);
  }
  const deprecatedRuleNames = _filterRuleNames(currentRuleNames, allRules, _isDeprecated);
  const dedupedRuleNames = [...new Set(allRuleNames)];
  const unusedRuleNames = difference(dedupedRuleNames, currentRuleNames);

  // Get all the current rules instead of referring the extended files or documentation
  this.getCurrentRules = () => getSortedRules(currentRuleNames);

  // Get all the current rules' particular configuration
  this.getCurrentRulesDetailed = () => config.rules;

  // Get all the plugin rules instead of referring the extended files or documentation
  this.getPluginRules = () => getSortedRules(pluginRuleNames);

  // Get all the available rules instead of referring eslint and plugin packages or documentation
  this.getAllAvailableRules = () => getSortedRules(dedupedRuleNames);

  this.getUnusedRules = () => getSortedRules(unusedRuleNames);

  // Get all the current rules that are deprecated
  this.getDeprecatedRules = () => getSortedRules(deprecatedRuleNames);
}

module.exports = function (specifiedFile, options = {}) {
  return new RuleFinder(specifiedFile, options);
};

@@ -2395,17 +2395,16 @@
 %5B%5C%5D%7B%7D%7C%5D/
-u
 g, '%5C%5C$&
@@ -2602,21 +2602,16 @@
  RegExp(
-%0A    
 %60.%5C%5C.(?:
@@ -2644,20 +2644,8 @@
 %7D)$%60
-,%0A    %22u%22%0A  
 );%0A%7D

drop unicode flag because of node 4 support