text: 'use strict';
  
  const _ = require('lodash');
  
  const COMMAND_PREFIX = 'stylelint-';
  const disableCommand = `${COMMAND_PREFIX}disable`;
  const enableCommand = `${COMMAND_PREFIX}enable`;
  const disableLineCommand = `${COMMAND_PREFIX}disable-line`;
  const disableNextLineCommand = `${COMMAND_PREFIX}disable-next-line`;
  const ALL_RULES = 'all';
  
  /** @typedef {import('postcss/lib/comment')} PostcssComment */
  /** @typedef {import('postcss').Root} PostcssRoot */
  /** @typedef {import('stylelint').PostcssResult} PostcssResult */
  /** @typedef {import('stylelint').DisabledRangeObject} DisabledRangeObject */
  /** @typedef {import('stylelint').DisabledRange} DisabledRange */
  
  /**
   * @param {number} start
   * @param {boolean} strictStart
   * @param {number} [end]
   * @param {boolean} [strictEnd]
   * @returns {DisabledRange}
   */
  function createDisableRange(start, strictStart, end, strictEnd) {
  	return {
  		start,
  		end: end || undefined,
  		strictStart,
  		strictEnd: typeof strictEnd === 'boolean' ? strictEnd : undefined,
  	};
  }
  
  /**
   * Run it like a plugin ...
   * @param {PostcssRoot} root
   * @param {PostcssResult} result
   * @returns {PostcssResult}
   */
  module.exports = function (root, result) {
  	result.stylelint = result.stylelint || {
  		disabledRanges: {},
  		ruleSeverities: {},
  		customMessages: {},
  	};
  
  	/**
  	 * Most of the functions below work via side effects mutating this object
  	 * @type {DisabledRangeObject}
  	 */
  	const disabledRanges = {
  		all: [],
  	};
  
  	result.stylelint.disabledRanges = disabledRanges;
  
  	// Work around postcss/postcss-scss#109 by merging adjacent `//` comments
  	// into a single node before passing to `checkComment`.
  
  	/** @type {PostcssComment?} */
  	let inlineEnd;
  
  	root.walkComments((/** @type {PostcssComment} */ comment) => {
  		if (inlineEnd) {
  			// Ignore comments already processed by grouping with a previous one.
  			if (inlineEnd === comment) inlineEnd = null;
  		} else if (isInlineComment(comment)) {
  			const fullComment = comment.clone();
  			let next = comment.next();
  
  			while (next && next.type === 'comment') {
  				/** @type {PostcssComment} */
  				const current = next;
  
  				if (!isInlineComment(current)) break;
  
  				fullComment.text += `\n${current.text}`;
  
  				if (fullComment.source && current.source) {
  					fullComment.source.end = current.source.end;
  				}
  
  				inlineEnd = current;
  				next = current.next();
  			}
  			checkComment(fullComment);
  		} else {
  			checkComment(comment);
  		}
  	});
  
  	return result;
  
  	/**
  	 * @param {PostcssComment} comment
  	 */
  	function isInlineComment(comment) {
  		// We check both here because the Sass parser uses `raws.inline` to indicate
  		// inline comments, while the Less parser uses `inline`.
  		return comment.inline || comment.raws.inline;
  	}
  
  	/**
  	 * @param {PostcssComment} comment
  	 */
  	function processDisableLineCommand(comment) {
  		if (comment.source && comment.source.start) {
  			const line = comment.source.start.line;
  
  			getCommandRules(disableLineCommand, comment.text).forEach((ruleName) => {
  				disableLine(line, ruleName, comment);
  			});
  		}
  	}
  
  	/**
  	 * @param {PostcssComment} comment
  	 */
  	function processDisableNextLineCommand(comment) {
  		if (comment.source && comment.source.end) {
  			const line = comment.source.end.line;
  
  			getCommandRules(disableNextLineCommand, comment.text).forEach((ruleName) => {
  				disableLine(line + 1, ruleName, comment);
  			});
  		}
  	}
  
  	/**
  	 * @param {number} line
  	 * @param {string} ruleName
  	 * @param {PostcssComment} comment
  	 */
  	function disableLine(line, ruleName, comment) {
  		if (ruleIsDisabled(ALL_RULES)) {
  			throw comment.error('All rules have already been disabled', {
  				plugin: 'stylelint',
  			});
  		}
  
  		if (ruleName === ALL_RULES) {
  			Object.keys(disabledRanges).forEach((disabledRuleName) => {
  				if (ruleIsDisabled(disabledRuleName)) return;
  
  				const strict = disabledRuleName === ALL_RULES;
  
  				startDisabledRange(line, disabledRuleName, strict);
  				endDisabledRange(line, disabledRuleName, strict);
  			});
  		} else {
  			if (ruleIsDisabled(ruleName)) {
  				throw comment.error(`"${ruleName}" has already been disabled`, {
  					plugin: 'stylelint',
  				});
  			}
  
  			startDisabledRange(line, ruleName, true);
  			endDisabledRange(line, ruleName, true);
  		}
  	}
  
  	/**
  	 * @param {PostcssComment} comment
  	 */
  	function processDisableCommand(comment) {
  		getCommandRules(disableCommand, comment.text).forEach((ruleToDisable) => {
  			const isAllRules = ruleToDisable === ALL_RULES;
  
  			if (ruleIsDisabled(ruleToDisable)) {
  				throw comment.error(
  					isAllRules
  						? 'All rules have already been disabled'
  						: `"${ruleToDisable}" has already been disabled`,
  					{
  						plugin: 'stylelint',
  					},
  				);
  			}
  
  			if (comment.source && comment.source.start) {
  				const line = comment.source.start.line;
  
  				if (isAllRules) {
  					Object.keys(disabledRanges).forEach((ruleName) => {
  						startDisabledRange(line, ruleName, ruleName === ALL_RULES);
  					});
  				} else {
  					startDisabledRange(line, ruleToDisable, true);
  				}
  			}
  		});
  	}
  
  	/**
  	 * @param {PostcssComment} comment
  	 */
  	function processEnableCommand(comment) {
  		getCommandRules(enableCommand, comment.text).forEach((ruleToEnable) => {
  			// TODO TYPES
  			// need fallback if endLine will be undefined
  			const endLine = /** @type {number} */ (comment.source &&
  				comment.source.end &&
  				comment.source.end.line);
  
  			if (ruleToEnable === ALL_RULES) {
  				if (
  					Object.values(disabledRanges).every(
  						(ranges) => ranges.length === 0 || typeof ranges[ranges.length - 1].end === 'number',
  					)
  				) {
  					throw comment.error('No rules have been disabled', {
  						plugin: 'stylelint',
  					});
  				}
  
  				Object.keys(disabledRanges).forEach((ruleName) => {
  					if (!_.get(_.last(disabledRanges[ruleName]), 'end')) {
  						endDisabledRange(endLine, ruleName, ruleName === ALL_RULES);
  					}
  				});
  
  				return;
  			}
  
  			if (ruleIsDisabled(ALL_RULES) && disabledRanges[ruleToEnable] === undefined) {
  				// Get a starting point from the where all rules were disabled
  				if (!disabledRanges[ruleToEnable]) {
  					disabledRanges[ruleToEnable] = disabledRanges.all.map(({ start, end }) =>
  						createDisableRange(start, false, end, false),
  					);
  				} else {
  					const range = _.last(disabledRanges[ALL_RULES]);
  
  					if (range) {
  						disabledRanges[ruleToEnable].push({ ...range });
  					}
  				}
  
  				endDisabledRange(endLine, ruleToEnable, true);
  
  				return;
  			}
  
  			if (ruleIsDisabled(ruleToEnable)) {
  				endDisabledRange(endLine, ruleToEnable, true);
  
  				return;
  			}
  
  			throw comment.error(`"${ruleToEnable}" has not been disabled`, {
  				plugin: 'stylelint',
  			});
  		});
  	}
  
  	/**
  	 * @param {PostcssComment} comment
  	 */
  	function checkComment(comment) {
  		const text = comment.text;
  
  		// Ignore comments that are not relevant commands
  
  		if (text.indexOf(COMMAND_PREFIX) !== 0) {
  			return result;
  		}
  
  		if (text.startsWith(disableLineCommand)) {
  			processDisableLineCommand(comment);
  		} else if (text.startsWith(disableNextLineCommand)) {
  			processDisableNextLineCommand(comment);
  		} else if (text.startsWith(disableCommand)) {
  			processDisableCommand(comment);
  		} else if (text.startsWith(enableCommand)) {
  			processEnableCommand(comment);
  		}
  	}
  
  	/**
  	 * @param {string} command
  	 * @param {string} fullText
  	 * @returns {string[]}
  	 */
  	function getCommandRules(command, fullText) {
  		const rules = fullText
  			.slice(command.length)
  			.split(/\s-{2,}\s/u)[0] // Allow for description (f.e. /* stylelint-disable a, b -- Description */).
  			.trim()
  			.split(',')
  			.filter(Boolean)
  			.map((r) => r.trim());
  
  		if (_.isEmpty(rules)) {
  			return [ALL_RULES];
  		}
  
  		return rules;
  	}
  
  	/**
  	 * @param {number} line
  	 * @param {string} ruleName
  	 * @param {boolean} strict
  	 */
  	function startDisabledRange(line, ruleName, strict) {
  		const rangeObj = createDisableRange(line, strict);
  
  		ensureRuleRanges(ruleName);
  		disabledRanges[ruleName].push(rangeObj);
  	}
  
  	/**
  	 * @param {number} line
  	 * @param {string} ruleName
  	 * @param {boolean} strict
  	 */
  	function endDisabledRange(line, ruleName, strict) {
  		const lastRangeForRule = _.last(disabledRanges[ruleName]);
  
  		if (!lastRangeForRule) {
  			return;
  		}
  
  		// Add an `end` prop to the last range of that rule
  		lastRangeForRule.end = line;
  		lastRangeForRule.strictEnd = strict;
  	}
  
  	/**
  	 * @param {string} ruleName
  	 */
  	function ensureRuleRanges(ruleName) {
  		if (!disabledRanges[ruleName]) {
  			disabledRanges[ruleName] = disabledRanges.all.map(({ start, end }) =>
  				createDisableRange(start, false, end, false),
  			);
  		}
  	}
  
  	/**
  	 * @param {string} ruleName
  	 * @returns {boolean}
  	 */
  	function ruleIsDisabled(ruleName) {
  		if (disabledRanges[ruleName] === undefined) return false;
  
  		if (_.last(disabledRanges[ruleName]) === undefined) return false;
  
  		if (_.get(_.last(disabledRanges[ruleName]), 'end') === undefined) return true;
  
  		return false;
  	}
  };
  