/**
 * @fileoverview Rule to
 * @author Toru Nagashima
 */

"use strict";

//------------------------------------------------------------------------------
// Types
//------------------------------------------------------------------------------

/** @typedef {import("eslint-scope").Scope} Scope */
/** @typedef {import("eslint-scope").Variable} Variable */
/** @typedef {import("eslint-scope").Reference} Reference */

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

/**
 * Gets the variable object of `arguments` which is defined implicitly.
 * @param {Scope} scope A scope to get.
 * @returns {Variable} The found variable object.
 */
function getVariableOfArguments(scope) {
	const variables = scope.variables;

	for (let i = 0; i < variables.length; ++i) {
		const variable = variables[i];

		if (variable.name === "arguments") {
			/*
			 * If there was a parameter which is named "arguments", the implicit "arguments" is not defined.
			 * So does fast return with null.
			 */
			return variable.identifiers.length === 0 ? variable : null;
		}
	}

	/* c8 ignore next */
	return null;
}

/**
 * Checks if the given reference is not normal member access.
 *
 * - arguments         .... true    // not member access
 * - arguments[i]      .... true    // computed member access
 * - arguments[0]      .... true    // computed member access
 * - arguments.length  .... false   // normal member access
 * @param {Reference} reference The reference to check.
 * @returns {boolean} `true` if the reference is not normal member access.
 */
function isNotNormalMemberAccess(reference) {
	const id = reference.identifier;
	const parent = id.parent;

	return !(
		parent.type === "MemberExpression" &&
		parent.object === id &&
		!parent.computed
	);
}

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

/** @type {import('../types').Rule.RuleModule} */
module.exports = {
	meta: {
		type: "suggestion",

		docs: {
			description: "Require rest parameters instead of `arguments`",
			recommended: false,
			url: "https://eslint.org/docs/latest/rules/prefer-rest-params",
		},

		schema: [],

		messages: {
			preferRestParams: "Use the rest parameters instead of 'arguments'.",
		},
	},

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

		/**
		 * Reports a given reference.
		 * @param {Reference} reference A reference to report.
		 * @returns {void}
		 */
		function report(reference) {
			context.report({
				node: reference.identifier,
				loc: reference.identifier.loc,
				messageId: "preferRestParams",
			});
		}

		/**
		 * Reports references of the implicit `arguments` variable if exist.
		 * @param {ASTNode} node The node representing the function.
		 * @returns {void}
		 */
		function checkForArguments(node) {
			const argumentsVar = getVariableOfArguments(
				sourceCode.getScope(node),
			);

			if (argumentsVar) {
				argumentsVar.references
					.filter(isNotNormalMemberAccess)
					.forEach(report);
			}
		}

		return {
			"FunctionDeclaration:exit": checkForArguments,
			"FunctionExpression:exit": checkForArguments,
		};
	},
};
