import {findVariable} from '@eslint-community/eslint-utils';
import {getAncestor} from './utils/index.js';
import {isStaticRequire, isStringLiteral, isMemberExpression} from './ast/index.js';

const MESSAGE_ID = 'prefer-event-target';
const messages = {
	[MESSAGE_ID]: 'Prefer `EventTarget` over `EventEmitter`.',
};

const packagesShouldBeIgnored = new Set([
	'@angular/core',
	'eventemitter3',
]);

const isConstVariableDeclarationId = node =>
	node.parent.type === 'VariableDeclarator'
	&& node.parent.id === node
	&& node.parent.parent.type === 'VariableDeclaration'
	&& node.parent.parent.kind === 'const'
	&& node.parent.parent.declarations.includes(node.parent);

function isAwaitImportOrRequireFromIgnoredPackages(node) {
	if (!node) {
		return false;
	}

	let source;
	if (isStaticRequire(node)) {
		[source] = node.arguments;
	} else if (node.type === 'AwaitExpression' && node.argument.type === 'ImportExpression') {
		({source} = node.argument);
	}

	if (isStringLiteral(source) && packagesShouldBeIgnored.has(source.value)) {
		return true;
	}

	return false;
}

function isFromIgnoredPackage(node) {
	if (!node) {
		return false;
	}

	const importDeclaration = getAncestor(node, 'ImportDeclaration');
	if (packagesShouldBeIgnored.has(importDeclaration?.source.value)) {
		return true;
	}

	// `const {EventEmitter} = ...`
	if (
		node.parent.type === 'Property'
		&& node.parent.value === node
		&& node.parent.key.type === 'Identifier'
		&& node.parent.key.name === 'EventEmitter'
		&& node.parent.parent.type === 'ObjectPattern'
		&& node.parent.parent.properties.includes(node.parent)
		&& isConstVariableDeclarationId(node.parent.parent)
		&& isAwaitImportOrRequireFromIgnoredPackages(node.parent.parent.parent.init)
	) {
		return true;
	}

	// `const EventEmitter = (...).EventEmitter`
	if (
		isConstVariableDeclarationId(node)
		&& isMemberExpression(node.parent.init, {property: 'EventEmitter', optional: false, computed: false})
		&& isAwaitImportOrRequireFromIgnoredPackages(node.parent.init.object)
	) {
		return true;
	}

	return false;
}

/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
	context.on('Identifier', node => {
		if (!(
			node.name === 'EventEmitter'
			&& (
				(
					(node.parent.type === 'ClassDeclaration' || node.parent.type === 'ClassExpression')
					&& node.parent.superClass === node
				)
				|| (node.parent.type === 'NewExpression' && node.parent.callee === node)
			)
		)) {
			return;
		}

		const scope = context.sourceCode.getScope(node);
		const variableNode = findVariable(scope, node)?.defs[0]?.name;
		if (isFromIgnoredPackage(variableNode)) {
			return;
		}

		return {
			node,
			messageId: MESSAGE_ID,
		};
	});
};

/** @type {import('eslint').Rule.RuleModule} */
const config = {
	create,
	meta: {
		type: 'suggestion',
		docs: {
			description: 'Prefer `EventTarget` over `EventEmitter`.',
			recommended: 'unopinionated',
		},
		messages,
	},
};

export default config;
