/*! © 2026 Yahoo, Inc. UDS v0.0.0-development */
import { camelCase, isEmpty } from "lodash-es";
import { Node, SyntaxKind } from "ts-morph";
//#region ../tailwind-v3/dist/utils/tsMorph.mjs
/*! © 2026 Yahoo, Inc. UDS Tailwind and Purger v0.0.0-development */
/** Determines if an expression is using React.forwardRef. */
function isForwardRefExpression(node) {
	if (Node.isCallExpression(node)) {
		const expression = node.getExpression();
		/**
		* forwardRef(() => <Component />)
		*/
		if (Node.isIdentifier(expression) && expression.getText() === "forwardRef") return true;
		/**
		* React.forwardRef(() => <Component />)
		*/
		if (Node.isPropertyAccessExpression(expression) && expression.getText() === "React.forwardRef") return true;
	}
	return false;
}
/** Determines if a node is a JSX component. */
function isJsxComponent(node) {
	let name;
	if (Node.isVariableDeclaration(node) || Node.isFunctionDeclaration(node) || Node.isClassDeclaration(node)) name = node.getName();
	else if (Node.isFunctionExpression(node) || Node.isArrowFunction(node)) name = node.getFirstAncestorByKind(SyntaxKind.VariableDeclaration)?.getName();
	return name ? /[A-Z]/.test(name.charAt(0)) : false;
}
/** Returns a functional component declaration, unwrapping forwardRef if needed. */
function getReactFunctionDeclaration(declaration) {
	if (isJsxComponent(declaration)) {
		if (Node.isFunctionDeclaration(declaration)) return declaration;
		if (Node.isVariableDeclaration(declaration)) {
			const initializer = declaration.getInitializer();
			if (initializer) {
				if (isForwardRefExpression(initializer)) {
					const [initializerDeclaration] = initializer.getArguments();
					if (Node.isFunctionDeclaration(initializerDeclaration) || Node.isFunctionExpression(initializerDeclaration) || Node.isArrowFunction(initializerDeclaration)) return initializerDeclaration;
				} else if (Node.isFunctionDeclaration(initializer) || Node.isFunctionExpression(initializer) || Node.isArrowFunction(initializer)) return initializer;
			}
		}
	}
	return null;
}
/**
* Traces component references.
*
* This is similar to `findReferencesAsNodes` but returns JsxSelfClosingElement and JsxElement nodes.
* Note, this currently does not account for cases where the component is used as a prop or is renamed.
*/
function findReferencesAsJsxElements(identifer) {
	const jsxElements = [];
	for (const reference of identifer.findReferencesAsNodes()) {
		const node = reference.getFirstAncestor((node) => {
			return Node.isJsxOpeningElement(node) || Node.isJsxSelfClosingElement(node);
		});
		if (node) jsxElements.push(node);
	}
	return jsxElements;
}
/**
* Given a Jsx Reference, this function finds the parent of that reference,
* looks at the props, and returns the name used either for spread operator rest or
* the prop name if there's no spread operator
*/
const getParentSpreadOrParamIdentifier = (reference) => {
	const blockParent = reference.getParentWhile((parent) => {
		return !getReactFunctionDeclaration(parent);
	}).getParent();
	if (!blockParent) return;
	const reactComponent = getReactFunctionDeclaration(blockParent);
	if (reactComponent) {
		const parameters = reactComponent.getParameters();
		for (let i = 0; i < parameters.length; i++) {
			const parameter = parameters[i];
			const objectBindingPattern = parameter.getChildrenOfKind(SyntaxKind.ObjectBindingPattern)[0];
			const identifier = parameter.getChildrenOfKind(SyntaxKind.Identifier)[0];
			if (objectBindingPattern) {
				const dotDotToken = objectBindingPattern.getFirstDescendantByKind(SyntaxKind.DotDotDotToken);
				if (dotDotToken !== void 0) return dotDotToken?.getParent()?.getFirstChildByKindOrThrow(SyntaxKind.Identifier).getText();
			} else if (identifier) return identifier.getText();
		}
	}
};
/**
* Get used props on a reference.
*/
const getUsedPropsInReference = (reference, variants) => {
	const attributes = reference.getAttributes();
	const res = [];
	attributes.forEach((attribute) => {
		if (Node.isJsxAttribute(attribute)) {
			const attributeName = attribute.getNameNode().getText();
			const stringLiterals = attribute.getDescendantsOfKind(SyntaxKind.StringLiteral);
			const values = [];
			if (stringLiterals.length !== 0) stringLiterals.forEach((stringLiteral) => values.push(stringLiteral.getLiteralText()));
			const typeName = attribute.getFirstChildByKindOrThrow(SyntaxKind.Identifier).getDefinitions().map((d) => d.getDeclarationNode()?.getChildrenOfKind(SyntaxKind.TypeReference).pop()?.getText()).pop();
			if (!typeName && !isEmpty(attributeName)) res.push([attributeName, values]);
			else if (variants[camelCase(typeName)] && !isEmpty(typeName)) res.push([camelCase(typeName), values]);
			else if (!isEmpty(attributeName)) res.push([attributeName, values]);
		}
		if (Node.isJsxSpreadAttribute(attribute)) {
			const componentPropsSpreadIdentifier = getParentSpreadOrParamIdentifier(reference);
			const children = attribute.getExpression().getChildren();
			const lastIdentifier = children.length === 0 ? attribute.getExpression() : children.pop();
			if (!lastIdentifier || lastIdentifier?.getText() === componentPropsSpreadIdentifier) return [];
			lastIdentifier.getType().getProperties().forEach((property) => {
				const propertyNameFromType = property.getDeclarations().map((declaration) => {
					return camelCase(declaration.getChildrenOfKind(SyntaxKind.TypeReference)[0]?.getText() ?? "");
				})[0];
				if (!isEmpty(property.getName())) res.push([property.getName(), []]);
				if (!isEmpty(propertyNameFromType)) res.push([propertyNameFromType, []]);
			});
		}
		return [];
	});
	return res;
};
//#endregion
export { findReferencesAsJsxElements, getUsedPropsInReference };
