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

/**
 * State Key Parser
 *
 * Parses state notation strings (like 'hovered & !disabled', '@media(w < 768px)')
 * into ConditionNode trees for processing in the pipeline.
 */
import { Lru } from '../parser/lru.js';
import { expandDimensionShorthands, expandTastyUnits, resolvePredefinedState, } from '../states/index.js';
import { camelToKebab } from '../utils/case-converter.js';
import { and, createContainerDimensionCondition, createContainerStyleCondition, createMediaDimensionCondition, createMediaFeatureCondition, createMediaTypeCondition, createModifierCondition, createOwnCondition, createPseudoCondition, createRootCondition, createStartingCondition, createSupportsCondition, not, or, trueCondition, } from './conditions.js';
// ============================================================================
// Caching
// ============================================================================
// Cache for parsed state keys (key -> ConditionNode)
const parseCache = new Lru(5000);
// ============================================================================
// Tokenizer Patterns
// ============================================================================
/**
 * Pattern for tokenizing state notation.
 * Matches: operators, parentheses, @-prefixed states, value mods, boolean mods,
 * pseudo-classes, class selectors, and attribute selectors.
 *
 * Note: For @supports we need to handle nested parentheses like @supports($, :has(*))
 * We use a pattern that allows one level of nesting: [^()]*(?:\([^)]*\))?[^)]*
 */
const STATE_TOKEN_PATTERN = /([&|!^])|([()])|(@media:[a-z]+)|(@media\([^)]+\))|(@supports\([^()]*(?:\([^)]*\))?[^)]*\))|(@root\([^)]+\))|(@own\([^)]+\))|(@\([^)]+\))|(@starting)|(@[A-Za-z][A-Za-z0-9-]*)|([a-z][a-z0-9-]*(?:\^=|\$=|\*=|=)(?:"[^"]*"|'[^']*'|[^\s&|!^()]+))|([a-z][a-z0-9-]+)|(:[a-z][a-z0-9-]*(?:\([^)]+\))?)|(\.[a-z][a-z0-9-]+)|(\[[^\]]+\])/gi;
// ============================================================================
// Tokenizer
// ============================================================================
/**
 * Tokenize a state notation string
 */
function tokenize(stateKey) {
    const tokens = [];
    let match;
    // Replace commas with | outside of parentheses (for compatibility)
    const normalized = replaceCommasOutsideParens(stateKey);
    STATE_TOKEN_PATTERN.lastIndex = 0;
    while ((match = STATE_TOKEN_PATTERN.exec(normalized)) !== null) {
        const fullMatch = match[0];
        if (match[1]) {
            // Operator: &, |, !, ^
            switch (fullMatch) {
                case '&':
                    tokens.push({ type: 'AND', value: '&', raw: fullMatch });
                    break;
                case '|':
                    tokens.push({ type: 'OR', value: '|', raw: fullMatch });
                    break;
                case '!':
                    tokens.push({ type: 'NOT', value: '!', raw: fullMatch });
                    break;
                case '^':
                    tokens.push({ type: 'XOR', value: '^', raw: fullMatch });
                    break;
            }
        }
        else if (match[2]) {
            // Parenthesis
            if (fullMatch === '(') {
                tokens.push({ type: 'LPAREN', value: '(', raw: fullMatch });
            }
            else {
                tokens.push({ type: 'RPAREN', value: ')', raw: fullMatch });
            }
        }
        else {
            // State token (all other capture groups)
            tokens.push({ type: 'STATE', value: fullMatch, raw: fullMatch });
        }
    }
    return tokens;
}
/**
 * Replace commas with | only outside of parentheses
 */
function replaceCommasOutsideParens(str) {
    let result = '';
    let depth = 0;
    for (let i = 0; i < str.length; i++) {
        const char = str[i];
        if (char === '(') {
            depth++;
            result += char;
        }
        else if (char === ')') {
            depth--;
            result += char;
        }
        else if (char === ',' && depth === 0) {
            result += '|';
        }
        else {
            result += char;
        }
    }
    return result;
}
// ============================================================================
// Recursive Descent Parser
// ============================================================================
/**
 * Parser state
 */
class Parser {
    tokens;
    pos = 0;
    options;
    constructor(tokens, options) {
        this.tokens = tokens;
        this.options = options;
    }
    parse() {
        if (this.tokens.length === 0) {
            return trueCondition();
        }
        const result = this.parseExpression();
        return result;
    }
    current() {
        return this.tokens[this.pos];
    }
    advance() {
        return this.tokens[this.pos++];
    }
    match(type) {
        if (this.current()?.type === type) {
            this.advance();
            return true;
        }
        return false;
    }
    /**
     * Parse expression with operator precedence:
     * ! (NOT) > ^ (XOR) > | (OR) > & (AND)
     */
    parseExpression() {
        return this.parseAnd();
    }
    parseAnd() {
        let left = this.parseOr();
        while (this.current()?.type === 'AND') {
            this.advance();
            const right = this.parseOr();
            left = and(left, right);
        }
        return left;
    }
    parseOr() {
        let left = this.parseXor();
        while (this.current()?.type === 'OR') {
            this.advance();
            const right = this.parseXor();
            left = or(left, right);
        }
        return left;
    }
    parseXor() {
        let left = this.parseUnary();
        while (this.current()?.type === 'XOR') {
            this.advance();
            const right = this.parseUnary();
            // XOR: (A & !B) | (!A & B)
            left = or(and(left, not(right)), and(not(left), right));
        }
        return left;
    }
    parseUnary() {
        if (this.match('NOT')) {
            const operand = this.parseUnary();
            return not(operand);
        }
        return this.parsePrimary();
    }
    parsePrimary() {
        // Handle parentheses
        if (this.match('LPAREN')) {
            const expr = this.parseExpression();
            this.match('RPAREN'); // Consume closing paren (lenient if missing)
            return expr;
        }
        // Handle state tokens
        const token = this.current();
        if (token?.type === 'STATE') {
            this.advance();
            return this.parseStateToken(token.value);
        }
        // Fallback for empty/invalid - return TRUE
        return trueCondition();
    }
    /**
     * Parse a state token into a ConditionNode
     */
    parseStateToken(value) {
        // @starting
        if (value === '@starting') {
            return createStartingCondition(false, value);
        }
        // @media:type (e.g., @media:print)
        if (value.startsWith('@media:')) {
            const mediaType = value.slice(7);
            return createMediaTypeCondition(mediaType, false, value);
        }
        // @media(...) - media query
        if (value.startsWith('@media(')) {
            return this.parseMediaQuery(value);
        }
        // @supports(...) - feature/selector support query
        if (value.startsWith('@supports(')) {
            return this.parseSupportsQuery(value);
        }
        // @root(...) - root state
        if (value.startsWith('@root(')) {
            return this.parseRootState(value);
        }
        // @own(...) - own state (sub-element)
        if (value.startsWith('@own(')) {
            return this.parseOwnState(value);
        }
        // @(...) - container query
        if (value.startsWith('@(')) {
            return this.parseContainerQuery(value);
        }
        // @name - predefined state
        if (value.startsWith('@') && /^@[A-Za-z][A-Za-z0-9-]*$/.test(value)) {
            return this.parsePredefinedState(value);
        }
        // Pseudo-class (e.g., :hover, :focus-visible, :nth-child(2n))
        if (value.startsWith(':')) {
            return createPseudoCondition(value, false, value);
        }
        // Class selector (e.g., .active)
        if (value.startsWith('.')) {
            return createPseudoCondition(value, false, value);
        }
        // Attribute selector (e.g., [disabled], [data-state="active"])
        if (value.startsWith('[')) {
            return createPseudoCondition(value, false, value);
        }
        // Value modifier (e.g., theme=danger, size=large)
        if (value.includes('=')) {
            return this.parseValueModifier(value);
        }
        // Boolean modifier (e.g., hovered, disabled)
        return this.parseBooleanModifier(value);
    }
    /**
     * Parse @media(...) query
     */
    parseMediaQuery(raw) {
        const content = raw.slice(7, -1); // Remove '@media(' and ')'
        if (!content.trim()) {
            return trueCondition();
        }
        // Expand shorthands and units
        let condition = expandDimensionShorthands(content);
        condition = expandTastyUnits(condition);
        // Check for feature queries (contains ':' but not dimension comparison)
        if (condition.includes(':') &&
            !condition.includes('<') &&
            !condition.includes('>') &&
            !condition.includes('=')) {
            // Feature query: @media(prefers-contrast: high)
            const colonIdx = condition.indexOf(':');
            const feature = condition.slice(0, colonIdx).trim();
            const featureValue = condition.slice(colonIdx + 1).trim();
            return createMediaFeatureCondition(feature, featureValue, false, raw);
        }
        // Boolean feature query: @media(prefers-reduced-motion)
        if (!condition.includes('<') &&
            !condition.includes('>') &&
            !condition.includes('=')) {
            return createMediaFeatureCondition(condition.trim(), undefined, false, raw);
        }
        // Dimension query - parse bounds
        const { dimension, lowerBound, upperBound } = this.parseDimensionCondition(condition);
        if (!dimension) {
            // Fallback for unparseable - treat as pseudo
            return createPseudoCondition(raw, false, raw);
        }
        return createMediaDimensionCondition(dimension, lowerBound, upperBound, false, raw);
    }
    /**
     * Parse dimension condition string (e.g., "width < 768px", "600px <= width < 1200px")
     */
    parseDimensionCondition(condition) {
        // Range syntax: "600px <= width < 1200px"
        const rangeMatch = condition.match(/^(.+?)\s*(<=|<)\s*(width|height|inline-size|block-size)\s*(<=|<)\s*(.+)$/);
        if (rangeMatch) {
            const [, lowerValue, lowerOp, dimension, upperOp, upperValue] = rangeMatch;
            return {
                dimension,
                lowerBound: {
                    value: lowerValue.trim(),
                    valueNumeric: parseNumericValue(lowerValue.trim()),
                    inclusive: lowerOp === '<=',
                },
                upperBound: {
                    value: upperValue.trim(),
                    valueNumeric: parseNumericValue(upperValue.trim()),
                    inclusive: upperOp === '<=',
                },
            };
        }
        // Simple comparison: "width < 768px"
        const simpleMatch = condition.match(/^(width|height|inline-size|block-size)\s*(<=|>=|<|>|=)\s*(.+)$/);
        if (simpleMatch) {
            const [, dimension, operator, value] = simpleMatch;
            const numeric = parseNumericValue(value.trim());
            if (operator === '<' || operator === '<=') {
                return {
                    dimension,
                    upperBound: {
                        value: value.trim(),
                        valueNumeric: numeric,
                        inclusive: operator === '<=',
                    },
                };
            }
            else if (operator === '>' || operator === '>=') {
                return {
                    dimension,
                    lowerBound: {
                        value: value.trim(),
                        valueNumeric: numeric,
                        inclusive: operator === '>=',
                    },
                };
            }
            else if (operator === '=') {
                // Exact match: both bounds are the same and inclusive
                return {
                    dimension,
                    lowerBound: {
                        value: value.trim(),
                        valueNumeric: numeric,
                        inclusive: true,
                    },
                    upperBound: {
                        value: value.trim(),
                        valueNumeric: numeric,
                        inclusive: true,
                    },
                };
            }
        }
        // Reversed: "768px > width"
        const reversedMatch = condition.match(/^(.+?)\s*(<=|>=|<|>|=)\s*(width|height|inline-size|block-size)$/);
        if (reversedMatch) {
            const [, value, operator, dimension] = reversedMatch;
            const numeric = parseNumericValue(value.trim());
            // Reverse the operator
            if (operator === '<' || operator === '<=') {
                return {
                    dimension,
                    lowerBound: {
                        value: value.trim(),
                        valueNumeric: numeric,
                        inclusive: operator === '<=',
                    },
                };
            }
            else if (operator === '>' || operator === '>=') {
                return {
                    dimension,
                    upperBound: {
                        value: value.trim(),
                        valueNumeric: numeric,
                        inclusive: operator === '>=',
                    },
                };
            }
        }
        return {};
    }
    /**
     * Parse @root(...) state
     */
    parseRootState(raw) {
        const content = raw.slice(6, -1); // Remove '@root(' and ')'
        if (!content.trim()) {
            return trueCondition();
        }
        // Build selector from condition
        const selector = buildRootSelector(content);
        return createRootCondition(selector, false, raw);
    }
    /**
     * Parse @supports(...) query
     *
     * Syntax:
     *   @supports(display: grid)     → @supports (display: grid)
     *   @supports($, :has(*))        → @supports selector(:has(*))
     */
    parseSupportsQuery(raw) {
        const content = raw.slice(10, -1); // Remove '@supports(' and ')'
        if (!content.trim()) {
            return trueCondition();
        }
        // Check for selector syntax: @supports($, :has(*))
        if (content.startsWith('$,')) {
            const selector = content.slice(2).trim(); // Remove '$,' prefix
            return createSupportsCondition('selector', selector, false, raw);
        }
        // Feature syntax: @supports(display: grid)
        return createSupportsCondition('feature', content, false, raw);
    }
    /**
     * Parse @own(...) state
     */
    parseOwnState(raw) {
        const content = raw.slice(5, -1); // Remove '@own(' and ')'
        if (!content.trim()) {
            return trueCondition();
        }
        // Parse the inner condition recursively
        const innerCondition = parseStateKey(content, this.options);
        return createOwnCondition(innerCondition, false, raw);
    }
    /**
     * Parse @(...) container query
     */
    parseContainerQuery(raw) {
        const content = raw.slice(2, -1); // Remove '@(' and ')'
        if (!content.trim()) {
            return trueCondition();
        }
        // Check for named container: @(layout, w < 600px)
        const commaIdx = content.indexOf(',');
        let containerName;
        let condition;
        if (commaIdx !== -1) {
            containerName = content.slice(0, commaIdx).trim();
            condition = content.slice(commaIdx + 1).trim();
        }
        else {
            condition = content.trim();
        }
        // Check for style query: @($variant=primary)
        if (condition.startsWith('$')) {
            const styleQuery = condition.slice(1); // Remove '$'
            const eqIdx = styleQuery.indexOf('=');
            if (eqIdx === -1) {
                // Existence check: @($variant)
                return createContainerStyleCondition(styleQuery, undefined, containerName, false, raw);
            }
            const property = styleQuery.slice(0, eqIdx).trim();
            let propertyValue = styleQuery.slice(eqIdx + 1).trim();
            // Remove quotes if present
            if ((propertyValue.startsWith('"') && propertyValue.endsWith('"')) ||
                (propertyValue.startsWith("'") && propertyValue.endsWith("'"))) {
                propertyValue = propertyValue.slice(1, -1);
            }
            return createContainerStyleCondition(property, propertyValue, containerName, false, raw);
        }
        // Dimension query
        let expandedCondition = expandDimensionShorthands(condition);
        expandedCondition = expandTastyUnits(expandedCondition);
        const { dimension, lowerBound, upperBound } = this.parseDimensionCondition(expandedCondition);
        if (!dimension) {
            // Fallback
            return createPseudoCondition(raw, false, raw);
        }
        return createContainerDimensionCondition(dimension, lowerBound, upperBound, containerName, false, raw);
    }
    /**
     * Parse predefined state (@mobile, @dark, etc.)
     */
    parsePredefinedState(raw) {
        const ctx = this.options.context;
        if (!ctx) {
            // No context - can't resolve predefined states
            return createPseudoCondition(raw, false, raw);
        }
        const resolved = resolvePredefinedState(raw, ctx);
        if (!resolved) {
            // Undefined predefined state - treat as modifier
            return createModifierCondition(`data-${camelToKebab(raw.slice(1))}`, undefined, '=', false, raw);
        }
        // Parse the resolved value recursively
        return parseStateKey(resolved, this.options);
    }
    /**
     * Parse value modifier (e.g., theme=danger, size^=sm)
     */
    parseValueModifier(raw) {
        // Match operators: =, ^=, $=, *=
        const opMatch = raw.match(/^([a-z][a-z0-9-]*)(\^=|\$=|\*=|=)(.+)$/i);
        if (!opMatch) {
            return createModifierCondition(`data-${camelToKebab(raw)}`, undefined, '=', false, raw);
        }
        const [, key, operator, value] = opMatch;
        let cleanValue = value;
        // Remove quotes if present
        if ((cleanValue.startsWith('"') && cleanValue.endsWith('"')) ||
            (cleanValue.startsWith("'") && cleanValue.endsWith("'"))) {
            cleanValue = cleanValue.slice(1, -1);
        }
        return createModifierCondition(`data-${camelToKebab(key)}`, cleanValue, operator, false, raw);
    }
    /**
     * Parse boolean modifier (e.g., hovered, disabled)
     */
    parseBooleanModifier(raw) {
        return createModifierCondition(`data-${camelToKebab(raw)}`, undefined, '=', false, raw);
    }
}
// ============================================================================
// Helper Functions
// ============================================================================
/**
 * Parse a numeric value from a CSS value string
 */
function parseNumericValue(value) {
    const match = value.match(/^(\d+(?:\.\d+)?)(px|em|rem|vh|vw|%)?$/);
    if (match) {
        return parseFloat(match[1]);
    }
    return null;
}
/**
 * Build a root state selector from a condition string
 */
function buildRootSelector(condition) {
    // Handle class selector: .className
    if (condition.startsWith('.')) {
        return condition;
    }
    // Handle attribute selector: [attr]
    if (condition.startsWith('[')) {
        return condition;
    }
    // Handle value mod: theme=dark
    if (condition.includes('=')) {
        const [key, value] = condition.split('=');
        return `[data-${camelToKebab(key.trim())}="${value.trim()}"]`;
    }
    // Boolean mod: camelCase -> [data-camel-case]
    return `[data-${camelToKebab(condition)}]`;
}
// ============================================================================
// Main Export
// ============================================================================
/**
 * Parse a state key string into a ConditionNode
 */
export function parseStateKey(stateKey, options = {}) {
    // Handle empty/default state
    if (!stateKey || !stateKey.trim()) {
        return trueCondition();
    }
    const trimmed = stateKey.trim();
    // Build cache key including local predefined states (they affect parsing)
    // Global predefined states are set once at initialization and don't change
    const ctx = options.context;
    const localStatesKey = ctx && Object.keys(ctx.localPredefinedStates).length > 0
        ? JSON.stringify(ctx.localPredefinedStates)
        : '';
    const cacheKey = JSON.stringify([
        trimmed,
        options.isSubElement,
        localStatesKey,
    ]);
    // Check cache
    const cached = parseCache.get(cacheKey);
    if (cached) {
        return cached;
    }
    // Tokenize and parse
    const tokens = tokenize(trimmed);
    const parser = new Parser(tokens, options);
    const result = parser.parse();
    // Cache result
    parseCache.set(cacheKey, result);
    return result;
}
/**
 * Clear the parse cache (for testing)
 */
export function clearParseCache() {
    parseCache.clear();
}

        