/**
 * @fileoverview Disallow mixed spaces and tabs for indentation
 * @author Jary Niebur
 * @deprecated in ESLint v8.53.0
 */
"use strict";

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

/** @type {import('../types').Rule.RuleModule} */
module.exports = {
	meta: {
		deprecated: {
			message: "Formatting rules are being moved out of ESLint core.",
			url: "https://eslint.org/blog/2023/10/deprecating-formatting-rules/",
			deprecatedSince: "8.53.0",
			availableUntil: "10.0.0",
			replacedBy: [
				{
					message:
						"ESLint Stylistic now maintains deprecated stylistic core rules.",
					url: "https://eslint.style/guide/migration",
					plugin: {
						name: "@stylistic/eslint-plugin-js",
						url: "https://eslint.style/packages/js",
					},
					rule: {
						name: "no-mixed-spaces-and-tabs",
						url: "https://eslint.style/rules/js/no-mixed-spaces-and-tabs",
					},
				},
			],
		},
		type: "layout",

		docs: {
			description: "Disallow mixed spaces and tabs for indentation",
			recommended: false,
			url: "https://eslint.org/docs/latest/rules/no-mixed-spaces-and-tabs",
		},

		schema: [
			{
				enum: ["smart-tabs", true, false],
			},
		],

		messages: {
			mixedSpacesAndTabs: "Mixed spaces and tabs.",
		},
	},

	create(context) {
		const sourceCode = context.sourceCode;

		let smartTabs;

		switch (context.options[0]) {
			case true: // Support old syntax, maybe add deprecation warning here
			case "smart-tabs":
				smartTabs = true;
				break;
			default:
				smartTabs = false;
		}

		//--------------------------------------------------------------------------
		// Public
		//--------------------------------------------------------------------------

		return {
			"Program:exit"(node) {
				const lines = sourceCode.lines,
					comments = sourceCode.getAllComments(),
					ignoredCommentLines = new Set();

				// Add all lines except the first ones.
				comments.forEach(comment => {
					for (
						let i = comment.loc.start.line + 1;
						i <= comment.loc.end.line;
						i++
					) {
						ignoredCommentLines.add(i);
					}
				});

				/*
				 * At least one space followed by a tab
				 * or the reverse before non-tab/-space
				 * characters begin.
				 */
				let regex = /^(?=( +|\t+))\1(?:\t| )/u;

				if (smartTabs) {
					/*
					 * At least one space followed by a tab
					 * before non-tab/-space characters begin.
					 */
					regex = /^(?=(\t*))\1(?=( +))\2\t/u;
				}

				lines.forEach((line, i) => {
					const match = regex.exec(line);

					if (match) {
						const lineNumber = i + 1;
						const loc = {
							start: {
								line: lineNumber,
								column: match[0].length - 2,
							},
							end: {
								line: lineNumber,
								column: match[0].length,
							},
						};

						if (!ignoredCommentLines.has(lineNumber)) {
							const containingNode =
								sourceCode.getNodeByRangeIndex(
									sourceCode.getIndexFromLoc(loc.start),
								);

							if (
								!(
									containingNode &&
									["Literal", "TemplateElement"].includes(
										containingNode.type,
									)
								)
							) {
								context.report({
									node,
									loc,
									messageId: "mixedSpacesAndTabs",
								});
							}
						}
					}
				});
			},
		};
	},
};
