text: 'use strict';
  
  const configurationError = require('./utils/configurationError');
  const getModulePath = require('./utils/getModulePath');
  const globjoin = require('globjoin');
  const micromatch = require('micromatch');
  const normalizeAllRuleSettings = require('./normalizeAllRuleSettings');
  const normalizePath = require('normalize-path');
  const path = require('path');
  
  /** @typedef {import('stylelint').ConfigPlugins} StylelintConfigPlugins */
  /** @typedef {import('stylelint').ConfigProcessor} StylelintConfigProcessor */
  /** @typedef {import('stylelint').ConfigProcessors} StylelintConfigProcessors */
  /** @typedef {import('stylelint').ConfigRules} StylelintConfigRules */
  /** @typedef {import('stylelint').ConfigOverride} StylelintConfigOverride */
  /** @typedef {import('stylelint').InternalApi} StylelintInternalApi */
  /** @typedef {import('stylelint').Config} StylelintConfig */
  /** @typedef {import('stylelint').CosmiconfigResult} StylelintCosmiconfigResult */
  
  /**
   * - Merges config and stylelint options
   * - Makes all paths absolute
   * - Merges extends
   * @param {StylelintInternalApi} stylelint
   * @param {StylelintConfig} config
   * @param {string} configDir
   * @param {boolean} [allowOverrides]
   * @param {string} [filePath]
   * @returns {Promise<StylelintConfig>}
   */
  async function augmentConfigBasic(stylelint, config, configDir, allowOverrides, filePath) {
  	let augmentedConfig = config;
  
  	if (allowOverrides) {
  		augmentedConfig = addOptions(stylelint, augmentedConfig);
  	}
  
  	augmentedConfig = await extendConfig(stylelint, augmentedConfig, configDir);
  
  	if (filePath) {
  		while (augmentedConfig.overrides) {
  			augmentedConfig = applyOverrides(augmentedConfig, configDir, filePath);
  			augmentedConfig = await extendConfig(stylelint, augmentedConfig, configDir);
  		}
  	}
  
  	return absolutizePaths(augmentedConfig, configDir);
  }
  
  /**
   * Extended configs need to be run through augmentConfigBasic
   * but do not need the full treatment. Things like pluginFunctions
   * will be resolved and added by the parent config.
   * @param {StylelintInternalApi} stylelint
   * @param {StylelintCosmiconfigResult} [cosmiconfigResult]
   * @returns {Promise<StylelintCosmiconfigResult>}
   */
  async function augmentConfigExtended(stylelint, cosmiconfigResult) {
  	if (!cosmiconfigResult) {
  		return null;
  	}
  
  	const configDir = path.dirname(cosmiconfigResult.filepath || '');
  	const { config } = cosmiconfigResult;
  
  	const augmentedConfig = await augmentConfigBasic(stylelint, config, configDir);
  
  	return {
  		config: augmentedConfig,
  		filepath: cosmiconfigResult.filepath,
  	};
  }
  
  /**
   * @param {StylelintInternalApi} stylelint
   * @param {string} [filePath]
   * @param {StylelintCosmiconfigResult} [cosmiconfigResult]
   * @returns {Promise<StylelintCosmiconfigResult>}
   */
  async function augmentConfigFull(stylelint, filePath, cosmiconfigResult) {
  	if (!cosmiconfigResult) {
  		return null;
  	}
  
  	const config = cosmiconfigResult.config;
  	const filepath = cosmiconfigResult.filepath;
  
  	const configDir = stylelint._options.configBasedir || path.dirname(filepath || '');
  
  	let augmentedConfig = await augmentConfigBasic(stylelint, config, configDir, true, filePath);
  
  	augmentedConfig = addPluginFunctions(augmentedConfig);
  	augmentedConfig = addProcessorFunctions(augmentedConfig);
  
  	if (!augmentedConfig.rules) {
  		throw configurationError(
  			'No rules found within configuration. Have you provided a "rules" property?',
  		);
  	}
  
  	augmentedConfig = normalizeAllRuleSettings(augmentedConfig);
  
  	return {
  		config: augmentedConfig,
  		filepath: cosmiconfigResult.filepath,
  	};
  }
  
  /**
   * Make all paths in the config absolute:
   * - ignoreFiles
   * - plugins
   * - processors
   * (extends handled elsewhere)
   * @param {StylelintConfig} config
   * @param {string} configDir
   * @returns {StylelintConfig}
   */
  function absolutizePaths(config, configDir) {
  	if (config.ignoreFiles) {
  		config.ignoreFiles = [config.ignoreFiles].flat().map((glob) => {
  			if (path.isAbsolute(glob.replace(/^!/, ''))) return glob;
  
  			return globjoin(configDir, glob);
  		});
  	}
  
  	if (config.plugins) {
  		config.plugins = [config.plugins].flat().map((lookup) => getModulePath(configDir, lookup));
  	}
  
  	if (config.processors) {
  		config.processors = absolutizeProcessors(config.processors, configDir);
  	}
  
  	return config;
  }
  
  /**
   * Processors are absolutized in their own way because
   * they can be and return a string or an array
   * @param {StylelintConfigProcessors} processors
   * @param {string} configDir
   * @return {StylelintConfigProcessors}
   */
  function absolutizeProcessors(processors, configDir) {
  	const normalizedProcessors = Array.isArray(processors) ? processors : [processors];
  
  	return normalizedProcessors.map((item) => {
  		if (typeof item === 'string') {
  			return getModulePath(configDir, item);
  		}
  
  		return [getModulePath(configDir, item[0]), item[1]];
  	});
  }
  
  /**
   * @param {StylelintInternalApi} stylelint
   * @param {StylelintConfig} config
   * @param {string} configDir
   * @return {Promise<StylelintConfig>}
   */
  async function extendConfig(stylelint, config, configDir) {
  	if (config.extends === undefined) {
  		return config;
  	}
  
  	const { extends: configExtends, ...originalWithoutExtends } = config;
  	const normalizedExtends = [configExtends].flat();
  
  	let resultConfig = originalWithoutExtends;
  
  	for (const extendLookup of normalizedExtends) {
  		const extendResult = await loadExtendedConfig(stylelint, configDir, extendLookup);
  
  		if (extendResult) {
  			resultConfig = mergeConfigs(resultConfig, extendResult.config);
  		}
  	}
  
  	return mergeConfigs(resultConfig, originalWithoutExtends);
  }
  
  /**
   * @param {StylelintInternalApi} stylelint
   * @param {string} configDir
   * @param {string} extendLookup
   * @return {Promise<StylelintCosmiconfigResult>}
   */
  function loadExtendedConfig(stylelint, configDir, extendLookup) {
  	const extendPath = getModulePath(configDir, extendLookup);
  
  	return stylelint._extendExplorer.load(extendPath);
  }
  
  /**
   * When merging configs (via extends)
   * - plugin and processor arrays are joined
   * - rules are merged via Object.assign, so there is no attempt made to
   *   merge any given rule's settings. If b contains the same rule as a,
   *   b's rule settings will override a's rule settings entirely.
   * - Everything else is merged via Object.assign
   * @param {StylelintConfig} a
   * @param {StylelintConfig} b
   * @returns {StylelintConfig}
   */
  function mergeConfigs(a, b) {
  	/** @type {{plugins: StylelintConfigPlugins}} */
  	const pluginMerger = {};
  
  	if (a.plugins || b.plugins) {
  		pluginMerger.plugins = [];
  
  		if (a.plugins) {
  			pluginMerger.plugins = pluginMerger.plugins.concat(a.plugins);
  		}
  
  		if (b.plugins) {
  			pluginMerger.plugins = [...new Set(pluginMerger.plugins.concat(b.plugins))];
  		}
  	}
  
  	/** @type {{processors: StylelintConfigProcessors}} */
  	const processorMerger = {};
  
  	if (a.processors || b.processors) {
  		processorMerger.processors = [];
  
  		if (a.processors) {
  			processorMerger.processors = processorMerger.processors.concat(a.processors);
  		}
  
  		if (b.processors) {
  			processorMerger.processors = [...new Set(processorMerger.processors.concat(b.processors))];
  		}
  	}
  
  	/** @type {{overrides: StylelintConfigOverride[]}} */
  	const overridesMerger = {};
  
  	if (a.overrides || b.overrides) {
  		overridesMerger.overrides = [];
  
  		if (a.overrides) {
  			overridesMerger.overrides = overridesMerger.overrides.concat(a.overrides);
  		}
  
  		if (b.overrides) {
  			overridesMerger.overrides = [...new Set(overridesMerger.overrides.concat(b.overrides))];
  		}
  	}
  
  	const rulesMerger = {};
  
  	if (a.rules || b.rules) {
  		rulesMerger.rules = { ...a.rules, ...b.rules };
  	}
  
  	const result = {
  		...a,
  		...b,
  		...processorMerger,
  		...pluginMerger,
  		...overridesMerger,
  		...rulesMerger,
  	};
  
  	return result;
  }
  
  /**
   * @param {StylelintConfig} config
   * @returns {StylelintConfig}
   */
  function addPluginFunctions(config) {
  	if (!config.plugins) {
  		return config;
  	}
  
  	const normalizedPlugins = [config.plugins].flat();
  
  	/** @type {{[k: string]: Function}} */
  	const pluginFunctions = {};
  
  	for (const pluginLookup of normalizedPlugins) {
  		let pluginImport = require(pluginLookup);
  
  		// Handle either ES6 or CommonJS modules
  		pluginImport = pluginImport.default || pluginImport;
  
  		// A plugin can export either a single rule definition
  		// or an array of them
  		const normalizedPluginImport = [pluginImport].flat();
  
  		normalizedPluginImport.forEach((pluginRuleDefinition) => {
  			if (!pluginRuleDefinition.ruleName) {
  				throw configurationError(
  					`stylelint requires plugins to expose a ruleName. The plugin "${pluginLookup}" is not doing this, so will not work with stylelint. Please file an issue with the plugin.`,
  				);
  			}
  
  			if (!pluginRuleDefinition.ruleName.includes('/')) {
  				throw configurationError(
  					`stylelint requires plugin rules to be namespaced, i.e. only \`plugin-namespace/plugin-rule-name\` plugin rule names are supported. The plugin rule "${pluginRuleDefinition.ruleName}" does not do this, so will not work. Please file an issue with the plugin.`,
  				);
  			}
  
  			pluginFunctions[pluginRuleDefinition.ruleName] = pluginRuleDefinition.rule;
  		});
  	}
  
  	config.pluginFunctions = pluginFunctions;
  
  	return config;
  }
  
  /**
   * Given an array of processors strings, we want to add two
   * properties to the augmented config:
   * - codeProcessors: functions that will run on code as it comes in
   * - resultProcessors: functions that will run on results as they go out
   *
   * To create these properties, we need to:
   * - Find the processor module
   * - Initialize the processor module by calling its functions with any
   *   provided options
   * - Push the processor's code and result processors to their respective arrays
   * @type {Map<string, string | Object>}
   */
  const processorCache = new Map();
  
  /**
   * @param {StylelintConfig} config
   * @return {StylelintConfig}
   */
  function addProcessorFunctions(config) {
  	if (!config.processors) return config;
  
  	/** @type {Array<Function>} */
  	const codeProcessors = [];
  	/** @type {Array<Function>} */
  	const resultProcessors = [];
  
  	[config.processors].flat().forEach((processorConfig) => {
  		const processorKey = JSON.stringify(processorConfig);
  
  		let initializedProcessor;
  
  		if (processorCache.has(processorKey)) {
  			initializedProcessor = processorCache.get(processorKey);
  		} else {
  			const processorLookup =
  				typeof processorConfig === 'string' ? processorConfig : processorConfig[0];
  			const processorOptions = typeof processorConfig === 'string' ? undefined : processorConfig[1];
  			let processor = require(processorLookup);
  
  			processor = processor.default || processor;
  			initializedProcessor = processor(processorOptions);
  			processorCache.set(processorKey, initializedProcessor);
  		}
  
  		if (initializedProcessor && initializedProcessor.code) {
  			codeProcessors.push(initializedProcessor.code);
  		}
  
  		if (initializedProcessor && initializedProcessor.result) {
  			resultProcessors.push(initializedProcessor.result);
  		}
  	});
  
  	config.codeProcessors = codeProcessors;
  	config.resultProcessors = resultProcessors;
  
  	return config;
  }
  
  /**
   * @param {StylelintConfig} fullConfig
   * @param {string} configDir
   * @param {string} filePath
   * @return {StylelintConfig}
   */
  function applyOverrides(fullConfig, configDir, filePath) {
  	let { overrides, ...config } = fullConfig;
  
  	if (!overrides) {
  		return config;
  	}
  
  	if (!Array.isArray(overrides)) {
  		throw new TypeError(
  			'The `overrides` configuration property should be an array, e.g. { "overrides": [{ "files": "*.css", "rules": {} }] }.',
  		);
  	}
  
  	for (const override of overrides) {
  		const { files, ...configOverrides } = override;
  
  		if (!files) {
  			throw new Error(
  				'Every object in the `overrides` configuration property should have a `files` property with globs, e.g. { "overrides": [{ "files": "*.css", "rules": {} }] }.',
  			);
  		}
  
  		const filesGlobs = [files]
  			.flat()
  			.map((glob) => {
  				if (path.isAbsolute(glob.replace(/^!/, ''))) {
  					return glob;
  				}
  
  				return globjoin(configDir, glob);
  			})
  			// Glob patterns for micromatch should be in POSIX-style
  			.map((s) => normalizePath(s));
  
  		if (micromatch.isMatch(filePath, filesGlobs, { dot: true })) {
  			config = mergeConfigs(config, configOverrides);
  		}
  	}
  
  	return config;
  }
  
  /**
   * Add options to the config
   *
   * @param {StylelintInternalApi} stylelint
   * @param {StylelintConfig} config
   *
   * @returns {StylelintConfig}
   */
  function addOptions(stylelint, config) {
  	const augmentedConfig = {
  		...config,
  	};
  
  	if (stylelint._options.ignoreDisables) {
  		augmentedConfig.ignoreDisables = stylelint._options.ignoreDisables;
  	}
  
  	if (stylelint._options.quiet) {
  		augmentedConfig.quiet = stylelint._options.quiet;
  	}
  
  	if (stylelint._options.reportNeedlessDisables) {
  		augmentedConfig.reportNeedlessDisables = stylelint._options.reportNeedlessDisables;
  	}
  
  	if (stylelint._options.reportInvalidScopeDisables) {
  		augmentedConfig.reportInvalidScopeDisables = stylelint._options.reportInvalidScopeDisables;
  	}
  
  	if (stylelint._options.reportDescriptionlessDisables) {
  		augmentedConfig.reportDescriptionlessDisables =
  			stylelint._options.reportDescriptionlessDisables;
  	}
  
  	if (stylelint._options.customSyntax) {
  		augmentedConfig.customSyntax = stylelint._options.customSyntax;
  	}
  
  	return augmentedConfig;
  }
  
  module.exports = { augmentConfigExtended, augmentConfigFull, applyOverrides };
  