// SPDX-License-Identifier: Apache-2.0
// Copyright 2024-2026 Vorion LLC
/**
 * Error thrown during parsing
 */
export class ParserError extends Error {
    token;
    expected;
    constructor(message, token, expected) {
        const posInfo = `at position ${token.position}`;
        const expectedInfo = expected ? ` (expected ${expected})` : '';
        super(`Parser error ${posInfo}: ${message}${expectedInfo}`);
        this.token = token;
        this.expected = expected;
        this.name = 'ParserError';
    }
}
/**
 * Get current token without advancing
 */
function peek(state) {
    return state.tokens[state.position];
}
/**
 * Advance to next token and return current
 */
function advance(state) {
    const token = state.tokens[state.position];
    if (token.type !== 'EOF') {
        state.position++;
    }
    return token;
}
/**
 * Check if current token matches expected type
 */
function check(state, type) {
    return peek(state).type === type;
}
/**
 * Consume token if it matches, otherwise throw error
 */
function expect(state, type, message) {
    const token = peek(state);
    if (token.type !== type) {
        throw new ParserError(message || `Unexpected token '${token.value}'`, token, type);
    }
    return advance(state);
}
/**
 * Parse an expression (entry point)
 */
function parseExpression(state) {
    return parseOrExpression(state);
}
/**
 * Parse OR expression (lowest precedence)
 */
function parseOrExpression(state) {
    let left = parseAndExpression(state);
    while (check(state, 'OR')) {
        advance(state); // consume OR
        const right = parseAndExpression(state);
        left = {
            type: 'BinaryExpression',
            operator: 'OR',
            left,
            right,
        };
    }
    return left;
}
/**
 * Parse AND expression
 */
function parseAndExpression(state) {
    let left = parseNotExpression(state);
    while (check(state, 'AND')) {
        advance(state); // consume AND
        const right = parseNotExpression(state);
        left = {
            type: 'BinaryExpression',
            operator: 'AND',
            left,
            right,
        };
    }
    return left;
}
/**
 * Parse NOT expression (unary)
 */
function parseNotExpression(state) {
    if (check(state, 'NOT')) {
        advance(state); // consume NOT
        const operand = parseNotExpression(state);
        return {
            type: 'UnaryExpression',
            operator: 'NOT',
            operand,
        };
    }
    return parseComparison(state);
}
/**
 * Parse comparison expression
 */
function parseComparison(state) {
    const left = parsePrimary(state);
    // Check for comparison operator
    if (check(state, 'OPERATOR')) {
        const operatorToken = advance(state);
        const operator = operatorToken.value;
        const right = parsePrimary(state);
        return {
            type: 'ComparisonExpression',
            operator,
            left,
            right,
        };
    }
    // Check for IN
    if (check(state, 'IN')) {
        advance(state); // consume IN
        const array = parseArray(state);
        return {
            type: 'InExpression',
            value: left,
            array,
        };
    }
    // Check for LIKE
    if (check(state, 'LIKE')) {
        advance(state); // consume LIKE
        const pattern = parsePrimary(state);
        return {
            type: 'LikeExpression',
            value: left,
            pattern,
        };
    }
    return left;
}
/**
 * Parse primary expression
 */
function parsePrimary(state) {
    const token = peek(state);
    switch (token.type) {
        case 'IDENTIFIER': {
            advance(state);
            return {
                type: 'Identifier',
                name: token.value,
            };
        }
        case 'STRING': {
            advance(state);
            return {
                type: 'Literal',
                value: token.value,
                raw: `"${token.value}"`,
            };
        }
        case 'NUMBER': {
            advance(state);
            const numValue = parseFloat(token.value);
            return {
                type: 'Literal',
                value: numValue,
                raw: token.value,
            };
        }
        case 'TRUE': {
            advance(state);
            return {
                type: 'Literal',
                value: true,
                raw: 'true',
            };
        }
        case 'FALSE': {
            advance(state);
            return {
                type: 'Literal',
                value: false,
                raw: 'false',
            };
        }
        case 'NULL': {
            advance(state);
            return {
                type: 'Literal',
                value: null,
                raw: 'null',
            };
        }
        case 'LPAREN': {
            advance(state); // consume (
            const expr = parseExpression(state);
            expect(state, 'RPAREN', 'closing parenthesis');
            return expr;
        }
        case 'LBRACKET': {
            return parseArray(state);
        }
        case 'EOF': {
            throw new ParserError('Unexpected end of expression', token, 'value');
        }
        default: {
            throw new ParserError(`Unexpected token '${token.value}'`, token, 'value');
        }
    }
}
/**
 * Parse array expression
 */
function parseArray(state) {
    expect(state, 'LBRACKET', 'opening bracket');
    const elements = [];
    // Handle empty array
    if (!check(state, 'RBRACKET')) {
        elements.push(parsePrimary(state));
        while (check(state, 'COMMA')) {
            advance(state); // consume comma
            elements.push(parsePrimary(state));
        }
    }
    expect(state, 'RBRACKET', 'closing bracket');
    return {
        type: 'ArrayExpression',
        elements,
    };
}
/**
 * Parse tokens into an AST
 *
 * @param tokens - Array of tokens from the lexer
 * @returns Root AST node
 * @throws ParserError if syntax is invalid
 *
 * @example
 * ```ts
 * const tokens = tokenize("intent.action == 'read' AND risk > 50");
 * const ast = parse(tokens);
 * // Returns BinaryExpression with AND operator
 * ```
 */
export function parse(tokens) {
    if (tokens.length === 0) {
        throw new ParserError('Empty token stream', { type: 'EOF', value: '', position: 0 }, 'expression');
    }
    // Handle EOF-only token stream (empty expression)
    if (tokens.length === 1 && tokens[0].type === 'EOF') {
        throw new ParserError('Empty expression', tokens[0], 'expression');
    }
    const state = {
        tokens,
        position: 0,
    };
    const ast = parseExpression(state);
    // Ensure we consumed all tokens (except EOF)
    if (!check(state, 'EOF')) {
        const token = peek(state);
        throw new ParserError(`Unexpected token '${token.value}' after expression`, token, 'end of expression');
    }
    return ast;
}
//# sourceMappingURL=parser.js.map