import { resolveNestedSelector } from '@csstools/selector-resolve-nested';

import { isAtRule, isRule } from './typeGuards.mjs';
import { atRuleRegexes } from './regexes.mjs';
import getRuleSelector from './getRuleSelector.mjs';
import isStandardSyntaxRule from './isStandardSyntaxRule.mjs';
import parseSelector from './parseSelector.mjs';
import selectorParser from 'postcss-selector-parser';

/** @import { Selector, Root as SelectorRoot } from 'postcss-selector-parser' */
/** @import { Rule, Node } from 'postcss' */
/** @import { PostcssResult } from 'stylelint' */

/** @type {WeakMap<Rule, Map<PostcssResult, Array<ResolvedSelector>>>} */
const cache = new WeakMap();

/**
 * Resolve nested selectors following the CSS nesting specification
 *
 * @typedef {{selector: Selector, resolvedSelectors: SelectorRoot, nested: boolean}} ResolvedSelector
 *
 * @param {Rule} rule
 * @param {PostcssResult} result
 * @returns {Array<ResolvedSelector>}
 */
export default function resolveNestedSelectorsForRule(rule, result) {
	let ruleCache = cache.get(rule);

	if (ruleCache) {
		const cachedResolved = ruleCache.get(result);

		if (cachedResolved) return cachedResolved;
	} else {
		ruleCache = new Map();
		cache.set(rule, ruleCache);
	}

	const ownAST = parseSelector(getRuleSelector(rule), result, rule);

	if (!ownAST) return [];

	/** @type {SelectorRoot | undefined} */
	let ast = undefined;

	/** @type {Array<Node>} */
	const ancestors = [];

	{
		/** @type {Node['parent']} */
		let parent = rule.parent;

		while (parent) {
			ancestors.push(parent);
			parent = parent.parent;
		}
	}

	ancestors.reverse();

	for (const child of ancestors) {
		if (isAtRule(child) && atRuleRegexes.scopeName.test(child.name)) {
			ast = parseSelector(':where(:scope)', result, child);
			continue;
		}

		if (!isRule(child)) continue;

		if (!isStandardSyntaxRule(child)) return [];

		const childAST = parseSelector(getRuleSelector(child), result, child);

		if (!childAST) return [];

		if (ast) {
			ast = resolveNestedSelector(childAST, ast);
		} else {
			ast = childAST;
		}
	}

	const resolved = ownAST.map((selector) => {
		if (!ast) {
			return {
				selector,
				resolvedSelectors: selectorParser.root({
					value: '',
					nodes: [selector],
				}),
				nested: false,
			};
		}

		return {
			selector,
			resolvedSelectors: resolveNestedSelector(
				selectorParser.root({
					nodes: [selector],
					value: '',
				}),
				ast,
			),
			nested: true,
		};
	});

	ruleCache.set(result, resolved);

	return resolved;
}
