import {isNotSemicolonToken} from '@eslint-community/eslint-utils';
import {isParenthesized} from './utils/index.js';
import {needsSemicolon} from './utils/index.js';
import {removeSpacesAfter} from './fix/index.js';

const MESSAGE_ID = 'no-lonely-if';
const messages = {
	[MESSAGE_ID]: 'Unexpected `if` as the only statement in a `if` block without `else`.',
};

const isIfStatementWithoutAlternate = node => node.type === 'IfStatement' && !node.alternate;

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table
// Lower precedence than `&&`
const needParenthesis = node => (
	(node.type === 'LogicalExpression' && (node.operator === '||' || node.operator === '??'))
	|| node.type === 'ConditionalExpression'
	|| node.type === 'AssignmentExpression'
	|| node.type === 'YieldExpression'
	|| node.type === 'SequenceExpression'
);

function getIfStatementTokens(node, sourceCode) {
	const tokens = {
		ifToken: sourceCode.getFirstToken(node),
		openingParenthesisToken: sourceCode.getFirstToken(node, 1),
	};

	const {consequent} = node;
	tokens.closingParenthesisToken = sourceCode.getTokenBefore(consequent);

	if (consequent.type === 'BlockStatement') {
		tokens.openingBraceToken = sourceCode.getFirstToken(consequent);
		tokens.closingBraceToken = sourceCode.getLastToken(consequent);
	}

	return tokens;
}

function fix(innerIfStatement, context) {
	const {sourceCode} = context;

	return function * (fixer) {
		const outerIfStatement = (
			innerIfStatement.parent.type === 'BlockStatement'
				? innerIfStatement.parent
				: innerIfStatement
		).parent;
		const outer = {
			...outerIfStatement,
			...getIfStatementTokens(outerIfStatement, sourceCode),
		};
		const inner = {
			...innerIfStatement,
			...getIfStatementTokens(innerIfStatement, sourceCode),
		};

		// Remove inner `if` token
		yield fixer.remove(inner.ifToken);
		yield removeSpacesAfter(inner.ifToken, context, fixer);

		// Remove outer `{}`
		if (outer.openingBraceToken) {
			yield fixer.remove(outer.openingBraceToken);
			yield removeSpacesAfter(outer.openingBraceToken, context, fixer);
			yield fixer.remove(outer.closingBraceToken);

			const tokenBefore = sourceCode.getTokenBefore(outer.closingBraceToken, {includeComments: true});
			yield removeSpacesAfter(tokenBefore, context, fixer);
		}

		// Add new `()`
		yield fixer.insertTextBefore(outer.openingParenthesisToken, '(');
		yield fixer.insertTextAfter(
			inner.closingParenthesisToken,
			`)${inner.consequent.type === 'EmptyStatement' ? '' : ' '}`,
		);

		// Add ` && `
		yield fixer.insertTextAfter(outer.closingParenthesisToken, ' && ');

		// Remove `()` if `test` don't need it
		for (const {test, openingParenthesisToken, closingParenthesisToken} of [outer, inner]) {
			if (
				isParenthesized(test, context)
				|| !needParenthesis(test)
			) {
				yield fixer.remove(openingParenthesisToken);
				yield fixer.remove(closingParenthesisToken);
			}

			yield removeSpacesAfter(closingParenthesisToken, context, fixer);
		}

		// If the `if` statement has no block, and is not followed by a semicolon,
		// make sure that fixing the issue would not change semantics due to ASI.
		// Similar logic https://github.com/eslint/eslint/blob/2124e1b5dad30a905dc26bde9da472bf622d3f50/lib/rules/no-lonely-if.js#L61-L77
		if (inner.consequent.type !== 'BlockStatement') {
			const lastToken = sourceCode.getLastToken(inner.consequent);
			if (isNotSemicolonToken(lastToken)) {
				const nextToken = sourceCode.getTokenAfter(outer);
				if (nextToken && needsSemicolon(lastToken, context, nextToken.value)) {
					yield fixer.insertTextBefore(nextToken, ';');
				}
			}
		}
	};
}

/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
	context.on('IfStatement', ifStatement => {
		if (!(
			isIfStatementWithoutAlternate(ifStatement)
			&& (
				// `if (a) { if (b) {} }`
				(
					ifStatement.parent.type === 'BlockStatement'
					&& ifStatement.parent.body.length === 1
					&& ifStatement.parent.body[0] === ifStatement
					&& isIfStatementWithoutAlternate(ifStatement.parent.parent)
					&& ifStatement.parent.parent.consequent === ifStatement.parent
				)
				// `if (a) if (b) {}`
				|| (
					isIfStatementWithoutAlternate(ifStatement.parent)
					&& ifStatement.parent.consequent === ifStatement
				)
			)
		)) {
			return;
		}

		return {
			node: ifStatement,
			messageId: MESSAGE_ID,
			fix: fix(ifStatement, context),
		};
	});
};

/** @type {import('eslint').Rule.RuleModule} */
const config = {
	create,
	meta: {
		type: 'suggestion',
		docs: {
			description: 'Disallow `if` statements as the only statement in `if` blocks without `else`.',
			recommended: 'unopinionated',
		},
		fixable: 'code',
		messages,
	},
};

export default config;
