/**
 * @license MIT
 * author: Cube Dev Team
 * @cube-dev/ui-kit v0.0.0-canary-dc621f3
 * Released under the MIT license.
 */

/**
 * CSS Materialization
 *
 * Converts condition trees into CSS selectors and at-rules.
 * This is the final stage that produces actual CSS output.
 */
import { Lru } from '../parser/lru';
// ============================================================================
// Caching
// ============================================================================
const conditionCache = new Lru(3000);
// ============================================================================
// Main Functions
// ============================================================================
/**
 * Convert a condition tree to CSS components
 */
export function conditionToCSS(node) {
    // Check cache
    const key = getConditionKey(node);
    const cached = conditionCache.get(key);
    if (cached) {
        return cached;
    }
    const result = conditionToCSSInner(node);
    // Cache result
    conditionCache.set(key, result);
    return result;
}
/**
 * Clear the condition cache (for testing)
 */
export function clearConditionCache() {
    conditionCache.clear();
}
// ============================================================================
// Inner Implementation
// ============================================================================
/**
 * Create an empty selector variant
 */
function emptyVariant() {
    return {
        modifierSelectors: [],
        ownSelectors: [],
        mediaConditions: [],
        containerConditions: [],
        startingStyle: false,
    };
}
function conditionToCSSInner(node) {
    // Base case: TRUE condition - single empty variant (matches everything)
    if (node.kind === 'true') {
        return {
            variants: [emptyVariant()],
            isImpossible: false,
        };
    }
    // Base case: FALSE condition - no variants (matches nothing)
    if (node.kind === 'false') {
        return {
            variants: [],
            isImpossible: true,
        };
    }
    // State condition
    if (node.kind === 'state') {
        return stateToCSS(node);
    }
    // Compound condition
    if (node.kind === 'compound') {
        if (node.operator === 'AND') {
            return andToCSS(node.children);
        }
        else {
            return orToCSS(node.children);
        }
    }
    // Fallback - single empty variant
    return {
        variants: [emptyVariant()],
        isImpossible: false,
    };
}
/**
 * Convert a state condition to CSS
 */
function stateToCSS(state) {
    switch (state.type) {
        case 'media': {
            // Media conditions can return multiple variants for negated ranges (OR branches)
            const mediaResults = mediaToParsed(state);
            const variants = mediaResults.map((mediaCond) => {
                const v = emptyVariant();
                v.mediaConditions.push(mediaCond);
                return v;
            });
            return { variants, isImpossible: false };
        }
        default: {
            const variant = emptyVariant();
            switch (state.type) {
                case 'modifier':
                    variant.modifierSelectors.push(modifierToCSS(state));
                    break;
                case 'pseudo':
                    variant.modifierSelectors.push(pseudoToCSS(state));
                    break;
                case 'container':
                    variant.containerConditions.push(containerToParsed(state));
                    break;
                case 'root':
                    variant.rootPrefix = rootToCSS(state);
                    break;
                case 'own':
                    variant.ownSelectors.push(...ownToCSS(state));
                    break;
                case 'starting':
                    variant.startingStyle = !state.negated;
                    break;
            }
            return {
                variants: [variant],
                isImpossible: false,
            };
        }
    }
}
/**
 * Convert modifier condition to CSS selector
 */
function modifierToCSS(state) {
    let selector;
    if (state.value !== undefined) {
        // Value attribute: [data-attr="value"]
        const op = state.operator || '=';
        selector = `[${state.attribute}${op}"${state.value}"]`;
    }
    else {
        // Boolean attribute: [data-attr]
        selector = `[${state.attribute}]`;
    }
    if (state.negated) {
        return `:not(${selector})`;
    }
    return selector;
}
/**
 * Convert pseudo condition to CSS selector
 */
function pseudoToCSS(state) {
    if (state.negated) {
        // Wrap in :not() if not already
        if (state.pseudo.startsWith(':not(')) {
            // Double negation - remove :not()
            return state.pseudo.slice(5, -1);
        }
        return `:not(${state.pseudo})`;
    }
    return state.pseudo;
}
/**
 * Convert media condition to parsed structure(s)
 * Returns an array because negated ranges produce OR branches (two separate conditions)
 */
function mediaToParsed(state) {
    if (state.subtype === 'type') {
        // @media:print → @media print (or @media not print)
        const mediaType = state.mediaType || 'all';
        return [
            {
                subtype: 'type',
                negated: state.negated ?? false,
                condition: mediaType,
                mediaType: state.mediaType,
            },
        ];
    }
    else if (state.subtype === 'feature') {
        // @media(prefers-contrast: high) → @media (prefers-contrast: high)
        let condition;
        if (state.featureValue) {
            condition = `(${state.feature}: ${state.featureValue})`;
        }
        else {
            condition = `(${state.feature})`;
        }
        return [
            {
                subtype: 'feature',
                negated: state.negated ?? false,
                condition,
                feature: state.feature,
                featureValue: state.featureValue,
            },
        ];
    }
    else {
        // Dimension query - negation is handled by inverting the condition
        // because "not (width < x)" doesn't work reliably in browsers
        return dimensionToMediaParsed(state.dimension || 'width', state.lowerBound, state.upperBound, state.negated ?? false);
    }
}
/**
 * Convert dimension bounds to parsed media condition(s)
 * Returns an array because negated ranges produce OR branches (two separate conditions)
 */
function dimensionToMediaParsed(dimension, lowerBound, upperBound, negated) {
    if (negated) {
        // Invert the condition manually since "not (width < x)" doesn't work in browsers
        // Single bounds: flip the operator → returns single condition
        // Range: NOT (a <= x < b) = (x < a) OR (x >= b) → returns two conditions (OR branches)
        if (lowerBound && upperBound) {
            // Range query: (lower <= dim < upper) negated is (dim < lower) OR (dim >= upper)
            // Return two separate conditions - these become separate variants
            const lowerOp = lowerBound.inclusive ? '<' : '<=';
            const upperOp = upperBound.inclusive ? '>' : '>=';
            return [
                {
                    subtype: 'dimension',
                    negated: false,
                    condition: `(${dimension} ${lowerOp} ${lowerBound.value})`,
                    dimension,
                    upperBound: {
                        value: lowerBound.value,
                        valueNumeric: lowerBound.valueNumeric,
                        inclusive: !lowerBound.inclusive,
                    },
                },
                {
                    subtype: 'dimension',
                    negated: false,
                    condition: `(${dimension} ${upperOp} ${upperBound.value})`,
                    dimension,
                    lowerBound: {
                        value: upperBound.value,
                        valueNumeric: upperBound.valueNumeric,
                        inclusive: !upperBound.inclusive,
                    },
                },
            ];
        }
        else if (upperBound) {
            // (dim < upper) negated is (dim >= upper)
            // (dim <= upper) negated is (dim > upper)
            const op = upperBound.inclusive ? '>' : '>=';
            return [
                {
                    subtype: 'dimension',
                    negated: false,
                    condition: `(${dimension} ${op} ${upperBound.value})`,
                    dimension,
                    lowerBound: {
                        value: upperBound.value,
                        valueNumeric: upperBound.valueNumeric,
                        inclusive: !upperBound.inclusive,
                    },
                },
            ];
        }
        else if (lowerBound) {
            // (dim >= lower) negated is (dim < lower)
            // (dim > lower) negated is (dim <= lower)
            const op = lowerBound.inclusive ? '<' : '<=';
            return [
                {
                    subtype: 'dimension',
                    negated: false,
                    condition: `(${dimension} ${op} ${lowerBound.value})`,
                    dimension,
                    upperBound: {
                        value: lowerBound.value,
                        valueNumeric: lowerBound.valueNumeric,
                        inclusive: !lowerBound.inclusive,
                    },
                },
            ];
        }
    }
    // Non-negated conditions
    let condition;
    if (lowerBound && upperBound) {
        const lowerOp = lowerBound.inclusive ? '<=' : '<';
        const upperOp = upperBound.inclusive ? '<=' : '<';
        condition = `(${lowerBound.value} ${lowerOp} ${dimension} ${upperOp} ${upperBound.value})`;
    }
    else if (upperBound) {
        const op = upperBound.inclusive ? '<=' : '<';
        condition = `(${dimension} ${op} ${upperBound.value})`;
    }
    else if (lowerBound) {
        const op = lowerBound.inclusive ? '>=' : '>';
        condition = `(${dimension} ${op} ${lowerBound.value})`;
    }
    else {
        condition = `(${dimension})`;
    }
    return [
        {
            subtype: 'dimension',
            negated: false,
            condition,
            dimension,
            lowerBound,
            upperBound,
        },
    ];
}
/**
 * Convert container condition to parsed structure
 * This enables structured analysis for contradiction detection and condition combining
 */
function containerToParsed(state) {
    let condition;
    if (state.subtype === 'style') {
        // Style query: style(--prop: value)
        if (state.propertyValue) {
            condition = `style(--${state.property}: ${state.propertyValue})`;
        }
        else {
            condition = `style(--${state.property})`;
        }
    }
    else {
        // Dimension query
        condition = dimensionToContainerCondition(state.dimension || 'width', state.lowerBound, state.upperBound);
    }
    return {
        name: state.containerName,
        condition,
        negated: state.negated ?? false,
        subtype: state.subtype,
        property: state.property,
        propertyValue: state.propertyValue,
    };
}
/**
 * Convert dimension bounds to container query condition (single string)
 * Container queries support "not (condition)", so no need to invert manually
 */
function dimensionToContainerCondition(dimension, lowerBound, upperBound) {
    if (lowerBound && upperBound) {
        const lowerOp = lowerBound.inclusive ? '<=' : '<';
        const upperOp = upperBound.inclusive ? '<=' : '<';
        return `(${lowerBound.value} ${lowerOp} ${dimension} ${upperOp} ${upperBound.value})`;
    }
    else if (upperBound) {
        const op = upperBound.inclusive ? '<=' : '<';
        return `(${dimension} ${op} ${upperBound.value})`;
    }
    else if (lowerBound) {
        const op = lowerBound.inclusive ? '>=' : '>';
        return `(${dimension} ${op} ${lowerBound.value})`;
    }
    return '(width)'; // Fallback
}
/**
 * Convert root condition to CSS selector prefix
 */
function rootToCSS(state) {
    if (state.negated) {
        return `:root:not(${state.selector})`;
    }
    return `:root${state.selector}`;
}
/**
 * Convert own condition to CSS selectors for sub-element
 */
function ownToCSS(state) {
    const innerCSS = conditionToCSS(state.innerCondition);
    if (innerCSS.isImpossible || innerCSS.variants.length === 0) {
        return [];
    }
    // Collect all modifier selectors from all variants
    // (For @own, we typically expect simple conditions, so usually just one variant)
    const allSelectors = [];
    for (const variant of innerCSS.variants) {
        allSelectors.push(...variant.modifierSelectors);
    }
    if (state.negated) {
        // Negate all selectors
        return allSelectors.map((s) => {
            if (s.startsWith(':not(')) {
                // Double negation - remove :not()
                return s.slice(5, -1);
            }
            return `:not(${s})`;
        });
    }
    return allSelectors;
}
/**
 * Deduplicate an array while preserving order
 */
function dedupeArray(arr) {
    return [...new Set(arr)];
}
/**
 * Merge two selector variants (AND operation)
 * Deduplicates selectors to avoid `.foo:not([x]):not([x])` repetition
 */
function mergeVariants(a, b) {
    // Merge media conditions and check for contradictions
    const mergedMedia = dedupeMediaConditions([
        ...a.mediaConditions,
        ...b.mediaConditions,
    ]);
    if (hasMediaContradiction(mergedMedia)) {
        return null; // Impossible variant
    }
    // Combine root prefixes and check for contradictions
    const combinedRoot = combineRootPrefixes(a.rootPrefix, b.rootPrefix);
    if (combinedRoot && hasRootPrefixContradiction(combinedRoot)) {
        return null; // Impossible variant
    }
    // Merge modifier selectors and check for contradictions
    const mergedModifiers = dedupeArray([
        ...a.modifierSelectors,
        ...b.modifierSelectors,
    ]);
    if (hasModifierContradiction(mergedModifiers)) {
        return null; // Impossible variant
    }
    // Merge container conditions and check for contradictions
    const mergedContainers = dedupeContainerConditions([
        ...a.containerConditions,
        ...b.containerConditions,
    ]);
    if (hasContainerStyleContradiction(mergedContainers)) {
        return null; // Impossible variant
    }
    return {
        modifierSelectors: mergedModifiers,
        ownSelectors: dedupeArray([...a.ownSelectors, ...b.ownSelectors]),
        mediaConditions: mergedMedia,
        containerConditions: mergedContainers,
        rootPrefix: combinedRoot,
        startingStyle: a.startingStyle || b.startingStyle,
    };
}
/**
 * Deduplicate media conditions by their key (subtype + condition + negated)
 */
function dedupeMediaConditions(conditions) {
    const seen = new Set();
    const result = [];
    for (const c of conditions) {
        const key = `${c.subtype}|${c.condition}|${c.negated}`;
        if (!seen.has(key)) {
            seen.add(key);
            result.push(c);
        }
    }
    return result;
}
/**
 * Deduplicate container conditions by their key (name + condition + negated)
 */
function dedupeContainerConditions(conditions) {
    const seen = new Set();
    const result = [];
    for (const c of conditions) {
        const key = `${c.name ?? ''}|${c.condition}|${c.negated}`;
        if (!seen.has(key)) {
            seen.add(key);
            result.push(c);
        }
    }
    return result;
}
/**
 * Check if a set of media conditions contains contradictions
 * e.g., (prefers-color-scheme: light) AND NOT (prefers-color-scheme: light)
 * or (width >= 900px) AND (width < 600px)
 *
 * Uses parsed media conditions for efficient analysis without regex parsing.
 */
function hasMediaContradiction(conditions) {
    // Track conditions by their key (condition string) to detect A and NOT A
    const featureConditions = new Map(); // key -> isPositive
    const typeConditions = new Map(); // mediaType -> isPositive
    // Track dimension conditions for range contradiction detection
    const dimensionsByDim = new Map();
    for (const cond of conditions) {
        if (cond.subtype === 'type') {
            // Type query: check for direct contradiction (print AND NOT print)
            const key = cond.mediaType || 'all';
            const existing = typeConditions.get(key);
            if (existing !== undefined && existing !== !cond.negated) {
                return true; // Contradiction: positive AND negated
            }
            typeConditions.set(key, !cond.negated);
        }
        else if (cond.subtype === 'feature') {
            // Feature query: check for direct contradiction
            const key = cond.condition;
            const existing = featureConditions.get(key);
            if (existing !== undefined && existing !== !cond.negated) {
                return true; // Contradiction: positive AND negated
            }
            featureConditions.set(key, !cond.negated);
        }
        else if (cond.subtype === 'dimension') {
            // Dimension query: check for range contradictions
            // Note: negated conditions are already inverted in dimensionToMediaParsed
            const dim = cond.dimension || 'width';
            let bounds = dimensionsByDim.get(dim);
            if (!bounds) {
                bounds = { lowerBound: null, upperBound: null };
                dimensionsByDim.set(dim, bounds);
            }
            // Track the effective bounds
            if (cond.lowerBound?.valueNumeric != null) {
                const value = cond.lowerBound.valueNumeric;
                if (bounds.lowerBound === null || value > bounds.lowerBound) {
                    bounds.lowerBound = value;
                }
            }
            if (cond.upperBound?.valueNumeric != null) {
                const value = cond.upperBound.valueNumeric;
                if (bounds.upperBound === null || value < bounds.upperBound) {
                    bounds.upperBound = value;
                }
            }
            // Check for impossible range
            if (bounds.lowerBound !== null &&
                bounds.upperBound !== null &&
                bounds.lowerBound >= bounds.upperBound) {
                return true;
            }
        }
    }
    return false;
}
/**
 * Check if a root prefix contains contradictions
 * e.g., ":root:not([data-x="y"])[data-x="y"]" is a contradiction
 */
function hasRootPrefixContradiction(rootPrefix) {
    return hasSelectorStringContradiction(rootPrefix);
}
/**
 * Check if modifier selectors contain contradictions
 * e.g., "[data-selected]" and ":not([data-selected])" together
 */
function hasModifierContradiction(modifiers) {
    const combined = modifiers.join('');
    return hasSelectorStringContradiction(combined);
}
/**
 * Check if a selector string contains contradictions
 * e.g., "[data-x]:not([data-x])" or "[data-x="y"]:not([data-x="y"])"
 */
function hasSelectorStringContradiction(selector) {
    const positiveAttrs = [];
    const negativeAttrs = [];
    // Match :not([attr]) or :not([attr="value"])
    const notPattern = /:not\(\[([^\]]+)\]\)/g;
    let match;
    while ((match = notPattern.exec(selector)) !== null) {
        negativeAttrs.push(match[1]);
    }
    // Match [attr] or [attr="value"] (not inside :not())
    // The lookbehind (?<!...) ensures we don't match attributes inside :not()
    const attrPattern = /(?<!:not\()\[([^\]]+)\]/g;
    while ((match = attrPattern.exec(selector)) !== null) {
        positiveAttrs.push(match[1]);
    }
    // Check for contradictions
    for (const neg of negativeAttrs) {
        if (positiveAttrs.includes(neg)) {
            return true;
        }
    }
    return false;
}
/**
 * Check if container conditions contain contradictions in style queries
 * e.g., style(--variant: danger) and style(--variant: success) together
 * Same property with different values = always false
 *
 * Uses parsed container conditions for efficient analysis without regex parsing.
 */
function hasContainerStyleContradiction(conditions) {
    // Track style queries by property name
    // key: property name, value: { hasExistence: boolean, values: Set<string>, hasNegatedExistence: boolean }
    const styleQueries = new Map();
    for (const cond of conditions) {
        // Only analyze style queries
        if (cond.subtype !== 'style' || !cond.property) {
            continue;
        }
        const property = cond.property;
        const value = cond.propertyValue;
        if (!styleQueries.has(property)) {
            styleQueries.set(property, {
                hasExistence: false,
                values: new Set(),
                hasNegatedExistence: false,
            });
        }
        const entry = styleQueries.get(property);
        if (cond.negated) {
            if (value === undefined) {
                // not style(--prop) - negated existence check
                entry.hasNegatedExistence = true;
            }
            // Negated value checks don't contradict positive value checks directly
            // They just mean "not this value"
        }
        else {
            if (value === undefined) {
                // style(--prop) - existence check
                entry.hasExistence = true;
            }
            else {
                // style(--prop: value) - value check
                entry.values.add(value);
            }
        }
    }
    // Check for contradictions
    for (const [, entry] of styleQueries) {
        // Contradiction: existence check + negated existence check
        if (entry.hasExistence && entry.hasNegatedExistence) {
            return true;
        }
        // Contradiction: multiple different values for same property
        // style(--variant: danger) AND style(--variant: success) is impossible
        if (entry.values.size > 1) {
            return true;
        }
        // Contradiction: negated existence + value check
        // not style(--variant) AND style(--variant: danger) is impossible
        if (entry.hasNegatedExistence && entry.values.size > 0) {
            return true;
        }
    }
    return false;
}
/**
 * Combine two root prefixes
 */
function combineRootPrefixes(a, b) {
    if (!a)
        return b;
    if (!b)
        return a;
    // Combine: :root[x] + :root[y] → :root[x][y]
    return `${a}${b.replace(':root', '')}`;
}
/**
 * Get a unique key for a variant (for deduplication)
 */
function getVariantKey(v) {
    const containerKey = v.containerConditions
        .map((c) => `${c.name ?? ''}:${c.negated ? '!' : ''}${c.condition}`)
        .sort()
        .join('|');
    const mediaKey = v.mediaConditions
        .map((c) => `${c.subtype}:${c.negated ? '!' : ''}${c.condition}`)
        .sort()
        .join('|');
    return [
        v.modifierSelectors.slice().sort().join('|'),
        v.ownSelectors.slice().sort().join('|'),
        mediaKey,
        containerKey,
        v.rootPrefix || '',
        v.startingStyle ? '1' : '0',
    ].join('###');
}
/**
 * Check if variant A is a superset of variant B (A is more restrictive)
 *
 * If A has all of B's conditions plus more, then A is redundant
 * because B already covers the same cases (and more).
 *
 * Example:
 *   A: :not([size=large]):not([size=medium]):not([size=small])
 *   B: :not([size=large])
 *   A is a superset of B, so A is redundant when B exists.
 */
function isVariantSuperset(a, b) {
    // Must have same context (root prefix, at-rules, etc.)
    if (a.rootPrefix !== b.rootPrefix)
        return false;
    if (a.startingStyle !== b.startingStyle)
        return false;
    // Check if a.mediaConditions is superset of b.mediaConditions
    if (!isMediaConditionsSuperset(a.mediaConditions, b.mediaConditions))
        return false;
    // Check if a.containerConditions is superset of b.containerConditions
    if (!isContainerConditionsSuperset(a.containerConditions, b.containerConditions))
        return false;
    // Check if a.modifierSelectors is superset of b.modifierSelectors
    if (!isArraySuperset(a.modifierSelectors, b.modifierSelectors))
        return false;
    // Check if a.ownSelectors is superset of b.ownSelectors
    if (!isArraySuperset(a.ownSelectors, b.ownSelectors))
        return false;
    // A is a superset if it has all of B's items (possibly more)
    // and at least one category has strictly more items
    const aTotal = a.mediaConditions.length +
        a.containerConditions.length +
        a.modifierSelectors.length +
        a.ownSelectors.length;
    const bTotal = b.mediaConditions.length +
        b.containerConditions.length +
        b.modifierSelectors.length +
        b.ownSelectors.length;
    return aTotal > bTotal;
}
/**
 * Check if media conditions A is a superset of B
 */
function isMediaConditionsSuperset(a, b) {
    const aKeys = new Set(a.map((c) => `${c.subtype}|${c.condition}|${c.negated}`));
    for (const c of b) {
        const key = `${c.subtype}|${c.condition}|${c.negated}`;
        if (!aKeys.has(key))
            return false;
    }
    return true;
}
/**
 * Check if container conditions A is a superset of B
 */
function isContainerConditionsSuperset(a, b) {
    const aKeys = new Set(a.map((c) => `${c.name ?? ''}|${c.condition}|${c.negated}`));
    for (const c of b) {
        const key = `${c.name ?? ''}|${c.condition}|${c.negated}`;
        if (!aKeys.has(key))
            return false;
    }
    return true;
}
/**
 * Check if array A contains all elements of array B (superset)
 */
function isArraySuperset(a, b) {
    const setA = new Set(a);
    for (const item of b) {
        if (!setA.has(item))
            return false;
    }
    return true;
}
/**
 * Deduplicate variants
 *
 * Removes:
 * 1. Exact duplicates (same key)
 * 2. Superset variants (more restrictive selectors that are redundant)
 */
function dedupeVariants(variants) {
    // First pass: exact deduplication
    const seen = new Set();
    let result = [];
    for (const v of variants) {
        const key = getVariantKey(v);
        if (!seen.has(key)) {
            seen.add(key);
            result.push(v);
        }
    }
    // Second pass: remove supersets (more restrictive variants)
    // Sort by total selector count (fewer selectors = less restrictive = keep)
    result.sort((a, b) => {
        const aCount = a.modifierSelectors.length +
            a.ownSelectors.length +
            a.mediaConditions.length +
            a.containerConditions.length;
        const bCount = b.modifierSelectors.length +
            b.ownSelectors.length +
            b.mediaConditions.length +
            b.containerConditions.length;
        return aCount - bCount;
    });
    // Remove variants that are supersets of earlier (less restrictive) variants
    const filtered = [];
    for (const candidate of result) {
        let isRedundant = false;
        for (const kept of filtered) {
            if (isVariantSuperset(candidate, kept)) {
                isRedundant = true;
                break;
            }
        }
        if (!isRedundant) {
            filtered.push(candidate);
        }
    }
    return filtered;
}
/**
 * Combine AND conditions into CSS
 *
 * AND of conditions means cartesian product of variants:
 * (A1 | A2) & (B1 | B2) = A1&B1 | A1&B2 | A2&B1 | A2&B2
 *
 * Variants that result in contradictions (e.g., conflicting media rules)
 * are filtered out.
 */
function andToCSS(children) {
    // Start with a single empty variant
    let currentVariants = [emptyVariant()];
    for (const child of children) {
        const childCSS = conditionToCSSInner(child);
        if (childCSS.isImpossible || childCSS.variants.length === 0) {
            return { variants: [], isImpossible: true };
        }
        // Cartesian product: each current variant × each child variant
        const newVariants = [];
        for (const current of currentVariants) {
            for (const childVariant of childCSS.variants) {
                const merged = mergeVariants(current, childVariant);
                // Skip impossible variants (contradictions detected during merge)
                if (merged !== null) {
                    newVariants.push(merged);
                }
            }
        }
        if (newVariants.length === 0) {
            return { variants: [], isImpossible: true };
        }
        // Deduplicate after each step to prevent exponential blowup
        currentVariants = dedupeVariants(newVariants);
    }
    return {
        variants: currentVariants,
        isImpossible: false,
    };
}
/**
 * Combine OR conditions into CSS
 *
 * OR in CSS means multiple selector variants (DNF).
 * Each variant becomes a separate selector in the comma-separated list,
 * or multiple CSS rules if they have different at-rules.
 *
 * Note: OR exclusivity is handled at the pipeline level (expandOrConditions),
 * so here we just collect all variants. Any remaining ORs in the condition
 * tree (e.g., from De Morgan expansion) are handled as simple alternatives.
 */
function orToCSS(children) {
    const allVariants = [];
    for (const child of children) {
        const childCSS = conditionToCSSInner(child);
        if (childCSS.isImpossible)
            continue;
        allVariants.push(...childCSS.variants);
    }
    if (allVariants.length === 0) {
        return { variants: [], isImpossible: true };
    }
    // Deduplicate variants
    return {
        variants: dedupeVariants(allVariants),
        isImpossible: false,
    };
}
// ============================================================================
// Utility Functions
// ============================================================================
/**
 * Get a cache key for a condition
 */
function getConditionKey(node) {
    if (node.kind === 'true')
        return 'TRUE';
    if (node.kind === 'false')
        return 'FALSE';
    if (node.kind === 'state')
        return node.uniqueId;
    if (node.kind === 'compound') {
        const childKeys = node.children.map(getConditionKey).sort();
        return `${node.operator}(${childKeys.join(',')})`;
    }
    return 'UNKNOWN';
}
/**
 * Build a complete CSS selector from a single variant
 */
export function buildCSSSelectorFromVariant(className, variant, selectorSuffix = '') {
    // Start with base class (doubled for specificity)
    let selector = `.${className}.${className}`;
    // Add modifier selectors
    selector += variant.modifierSelectors.join('');
    // Add selector suffix (e.g., ' [data-element="Label"]')
    selector += selectorSuffix;
    // Add own selectors (after sub-element)
    selector += variant.ownSelectors.join('');
    // Add root prefix if present
    if (variant.rootPrefix) {
        selector = `${variant.rootPrefix} ${selector}`;
    }
    return selector;
}
/**
 * Build at-rules array from a variant
 */
export function buildAtRulesFromVariant(variant) {
    const atRules = [];
    // Add media rules - combine all conditions with "and"
    if (variant.mediaConditions.length > 0) {
        const conditionParts = variant.mediaConditions.map((c) => {
            if (c.subtype === 'type') {
                // Media type: print, screen, etc.
                return c.negated ? `not ${c.condition}` : c.condition;
            }
            else {
                // Feature or dimension: (condition) or not (condition)
                return c.negated ? `not ${c.condition}` : c.condition;
            }
        });
        atRules.push(`@media ${conditionParts.join(' and ')}`);
    }
    // Add container rules - group by container name and combine with "and"
    if (variant.containerConditions.length > 0) {
        // Group conditions by container name (undefined = unnamed/nearest)
        const byName = new Map();
        for (const cond of variant.containerConditions) {
            const group = byName.get(cond.name) || [];
            group.push(cond);
            byName.set(cond.name, group);
        }
        // Build one @container rule per container name
        for (const [name, conditions] of byName) {
            // CSS Container Query syntax requires parentheses around negated conditions:
            // @container (not style(--x)) and style(--y) - NOT @container not style(--x) and style(--y)
            const conditionParts = conditions.map((c) => c.negated ? `(not ${c.condition})` : c.condition);
            const namePrefix = name ? `${name} ` : '';
            atRules.push(`@container ${namePrefix}${conditionParts.join(' and ')}`);
        }
    }
    // Add starting-style
    if (variant.startingStyle) {
        atRules.push('@starting-style');
    }
    return atRules;
}
/**
 * Get a string key for a variant's at-rules (for grouping)
 */
function getAtRulesKey(variant) {
    const atRules = buildAtRulesFromVariant(variant);
    return atRules.sort().join('|||');
}
/**
 * Materialize a computed rule into final CSS format
 *
 * Returns an array of CSSRules because OR conditions may require multiple rules
 * (when different branches have different at-rules)
 */
export function materializeRule(condition, declarations, selectorSuffix, className) {
    const components = conditionToCSS(condition);
    if (components.isImpossible || components.variants.length === 0) {
        return [];
    }
    const declarationsStr = Object.entries(declarations)
        .map(([prop, value]) => `${prop}: ${value};`)
        .join(' ');
    // Group variants by their at-rules (variants with same at-rules can be combined with commas)
    const byAtRules = new Map();
    for (const variant of components.variants) {
        const key = getAtRulesKey(variant);
        const group = byAtRules.get(key) || [];
        group.push(variant);
        byAtRules.set(key, group);
    }
    // Generate one CSSRule per at-rules group
    const rules = [];
    for (const [, variants] of byAtRules) {
        // All variants in this group have the same at-rules, so combine selectors with commas
        const selectors = variants.map((v) => buildCSSSelectorFromVariant(className, v, selectorSuffix));
        const selector = selectors.join(', ');
        const atRules = buildAtRulesFromVariant(variants[0]);
        const rule = {
            selector,
            declarations: declarationsStr,
        };
        if (atRules.length > 0) {
            rule.atRules = atRules;
        }
        if (variants[0].rootPrefix) {
            rule.rootPrefix = variants[0].rootPrefix;
        }
        rules.push(rule);
    }
    return rules;
}

        