'use strict';
const {checkVueTemplate} = require('./utils/rule.js');
const {
	isBooleanNode,
	getParenthesizedRange,
	isNodeValueNotFunction,
} = require('./utils/index.js');
const {removeMemberExpressionProperty} = require('./fix/index.js');
const {isLiteral, isUndefined, isMethodCall, isMemberExpression} = require('./ast/index.js');

const ERROR_ID_ARRAY_SOME = 'some';
const SUGGESTION_ID_ARRAY_SOME = 'some-suggestion';
const ERROR_ID_ARRAY_FILTER = 'filter';
const messages = {
	[ERROR_ID_ARRAY_SOME]: 'Prefer `.some(…)` over `.{{method}}(…)`.',
	[SUGGESTION_ID_ARRAY_SOME]: 'Replace `.{{method}}(…)` with `.some(…)`.',
	[ERROR_ID_ARRAY_FILTER]: 'Prefer `.some(…)` over non-zero length check from `.filter(…)`.',
};

const isCheckingUndefined = node =>
	node.parent.type === 'BinaryExpression'
	// Not checking yoda expression `null != foo.find()` and `undefined !== foo.find()
	&& node.parent.left === node
	&& (
		(
			(
				node.parent.operator === '!='
				|| node.parent.operator === '=='
				|| node.parent.operator === '==='
				|| node.parent.operator === '!=='
			)
			&& isUndefined(node.parent.right)
		)
		|| (
			(
				node.parent.operator === '!='
				|| node.parent.operator === '=='
			)
			// eslint-disable-next-line unicorn/no-null
			&& isLiteral(node.parent.right, null)
		)
	);

/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
	CallExpression(callExpression) {
		if (!isMethodCall(callExpression, {
			methods: ['find', 'findLast'],
			minimumArguments: 1,
			maximumArguments: 2,
			optionalCall: false,
			optionalMember: false,
		})) {
			return;
		}

		const isCompare = isCheckingUndefined(callExpression);
		if (!isCompare && !isBooleanNode(callExpression)) {
			return;
		}

		const methodNode = callExpression.callee.property;
		return {
			node: methodNode,
			messageId: ERROR_ID_ARRAY_SOME,
			data: {method: methodNode.name},
			suggest: [
				{
					messageId: SUGGESTION_ID_ARRAY_SOME,
					* fix(fixer) {
						yield fixer.replaceText(methodNode, 'some');

						if (!isCompare) {
							return;
						}

						const parenthesizedRange = getParenthesizedRange(callExpression, context.sourceCode);
						yield fixer.replaceTextRange([parenthesizedRange[1], callExpression.parent.range[1]], '');

						if (callExpression.parent.operator === '!=' || callExpression.parent.operator === '!==') {
							return;
						}

						yield fixer.insertTextBeforeRange(parenthesizedRange, '!');
					},
				},
			],
		};
	},
	BinaryExpression(binaryExpression) {
		if (!(
			// We assume the user already follows `unicorn/explicit-length-check`. These are allowed in that rule.
			(binaryExpression.operator === '>' || binaryExpression.operator === '!==')
			&& binaryExpression.right.type === 'Literal'
			&& binaryExpression.right.raw === '0'
			&& isMemberExpression(binaryExpression.left, {property: 'length', optional: false})
			&& isMethodCall(binaryExpression.left.object, {
				method: 'filter',
				optionalCall: false,
				optionalMember: false,
			})
		)) {
			return;
		}

		const filterCall = binaryExpression.left.object;
		const [firstArgument] = filterCall.arguments;
		if (!firstArgument || isNodeValueNotFunction(firstArgument)) {
			return;
		}

		const filterProperty = filterCall.callee.property;
		return {
			node: filterProperty,
			messageId: ERROR_ID_ARRAY_FILTER,
			* fix(fixer) {
				// `.filter` to `.some`
				yield fixer.replaceText(filterProperty, 'some');

				const {sourceCode} = context;
				const lengthNode = binaryExpression.left;
				/*
					Remove `.length`
					`(( (( array.filter() )).length )) > (( 0 ))`
					------------------------^^^^^^^
				*/
				yield removeMemberExpressionProperty(fixer, lengthNode, sourceCode);

				/*
					Remove `> 0`
					`(( (( array.filter() )).length )) > (( 0 ))`
					----------------------------------^^^^^^^^^^
				*/
				yield fixer.removeRange([
					getParenthesizedRange(lengthNode, sourceCode)[1],
					binaryExpression.range[1],
				]);

				// The `BinaryExpression` always ends with a number or `)`, no need check for ASI
			},
		};
	},
});

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
	create: checkVueTemplate(create),
	meta: {
		type: 'suggestion',
		docs: {
			description: 'Prefer `.some(…)` over `.filter(…).length` check and `.{find,findLast}(…)`.',
		},
		fixable: 'code',
		messages,
		hasSuggestions: true,
	},
};
