/**
 * Buddy Script Parser
 *
 * Parses tokens into an Abstract Syntax Tree (AST)
 */
import { TokenType } from './types.js';
import { createLoopGuard, LoopTimeoutError } from '../utils/errors.js';
/** Maximum recursion depth for recursive descent parsing */
const MAX_RECURSION_DEPTH = 500;
export class Parser {
    tokens;
    current = 0;
    recursionDepth = 0;
    constructor(tokens) {
        this.tokens = tokens;
    }
    /**
     * Track recursion depth to prevent stack overflow on deeply nested input
     */
    enterRecursion(context) {
        this.recursionDepth++;
        if (this.recursionDepth > MAX_RECURSION_DEPTH) {
            throw new LoopTimeoutError(`Maximum recursion depth exceeded in ${context}. Input may be too deeply nested or malformed.`, MAX_RECURSION_DEPTH, context);
        }
    }
    exitRecursion() {
        this.recursionDepth--;
    }
    parse() {
        const body = [];
        const guard = createLoopGuard({
            maxIterations: 100000,
            context: 'BuddyScript program parsing',
        });
        while (!this.isAtEnd()) {
            guard();
            const stmt = this.declaration();
            if (stmt) {
                body.push(stmt);
            }
        }
        return { type: 'Program', body };
    }
    declaration() {
        this.enterRecursion('declaration');
        try {
            if (this.match(TokenType.LET, TokenType.CONST)) {
                return this.variableDeclaration();
            }
            if (this.check(TokenType.ASYNC) && this.checkNext(TokenType.FUNCTION)) {
                this.advance(); // async
                return this.functionDeclaration(true);
            }
            if (this.match(TokenType.FUNCTION)) {
                return this.functionDeclaration(false);
            }
            if (this.match(TokenType.IMPORT)) {
                return this.importStatement();
            }
            return this.statement();
        }
        catch (error) {
            // Re-throw recursion/loop errors without synchronizing
            if (error instanceof LoopTimeoutError) {
                throw error;
            }
            this.synchronize();
            return null;
        }
        finally {
            this.exitRecursion();
        }
    }
    variableDeclaration() {
        const kind = this.previous().value === 'const' ? 'const' : 'let';
        const name = this.consume(TokenType.IDENTIFIER, 'Expected variable name').value;
        let init = null;
        if (this.match(TokenType.ASSIGN)) {
            init = this.expression();
        }
        this.consumeOptional(TokenType.SEMICOLON);
        return {
            type: 'VariableDeclaration',
            kind,
            name,
            init,
        };
    }
    functionDeclaration(isAsync) {
        const name = this.consume(TokenType.IDENTIFIER, 'Expected function name').value;
        this.consume(TokenType.LPAREN, "Expected '(' after function name");
        const params = [];
        if (!this.check(TokenType.RPAREN)) {
            do {
                const paramName = this.consume(TokenType.IDENTIFIER, 'Expected parameter name').value;
                let defaultValue;
                if (this.match(TokenType.ASSIGN)) {
                    defaultValue = this.expression();
                }
                params.push({ name: paramName, defaultValue });
            } while (this.match(TokenType.COMMA));
        }
        this.consume(TokenType.RPAREN, "Expected ')' after parameters");
        this.consume(TokenType.LBRACE, "Expected '{' before function body");
        const body = this.block();
        return {
            type: 'FunctionDeclaration',
            name,
            params,
            body,
            async: isAsync,
        };
    }
    importStatement() {
        const module = this.consume(TokenType.IDENTIFIER, 'Expected module name').value;
        this.consumeOptional(TokenType.SEMICOLON);
        return {
            type: 'ImportStatement',
            module,
        };
    }
    statement() {
        if (this.match(TokenType.IF))
            return this.ifStatement();
        if (this.match(TokenType.WHILE))
            return this.whileStatement();
        if (this.match(TokenType.FOR))
            return this.forStatement();
        if (this.match(TokenType.RETURN))
            return this.returnStatement();
        if (this.match(TokenType.TRY))
            return this.tryStatement();
        if (this.match(TokenType.THROW))
            return this.throwStatement();
        if (this.match(TokenType.BREAK))
            return this.breakStatement();
        if (this.match(TokenType.CONTINUE))
            return this.continueStatement();
        if (this.match(TokenType.LBRACE))
            return this.block();
        return this.expressionStatement();
    }
    ifStatement() {
        this.consume(TokenType.LPAREN, "Expected '(' after 'if'");
        const condition = this.expression();
        this.consume(TokenType.RPAREN, "Expected ')' after condition");
        let consequent;
        if (this.match(TokenType.LBRACE)) {
            consequent = this.block();
        }
        else {
            consequent = {
                type: 'BlockStatement',
                body: [this.statement()],
            };
        }
        let alternate = null;
        if (this.match(TokenType.ELSE)) {
            if (this.match(TokenType.IF)) {
                alternate = this.ifStatement();
            }
            else if (this.match(TokenType.LBRACE)) {
                alternate = this.block();
            }
            else {
                alternate = {
                    type: 'BlockStatement',
                    body: [this.statement()],
                };
            }
        }
        return {
            type: 'IfStatement',
            condition,
            consequent,
            alternate,
        };
    }
    whileStatement() {
        this.consume(TokenType.LPAREN, "Expected '(' after 'while'");
        const condition = this.expression();
        this.consume(TokenType.RPAREN, "Expected ')' after condition");
        this.consume(TokenType.LBRACE, "Expected '{' before while body");
        const body = this.block();
        return {
            type: 'WhileStatement',
            condition,
            body,
        };
    }
    forStatement() {
        // Check if it's a C-style for loop: for (init; test; update) { }
        if (this.match(TokenType.LPAREN)) {
            return this.forCStyleStatement();
        }
        // Otherwise, for-in loop: for x in iterable { }
        const variable = this.consume(TokenType.IDENTIFIER, 'Expected variable name').value;
        this.consume(TokenType.IN, "Expected 'in' in for loop");
        const iterable = this.expression();
        this.consume(TokenType.LBRACE, "Expected '{' before for body");
        const body = this.block();
        return {
            type: 'ForInStatement',
            variable,
            iterable,
            body,
        };
    }
    forCStyleStatement() {
        // Parse C-style for: for (init; test; update) { }
        // Parse init
        let init = null;
        if (!this.check(TokenType.SEMICOLON)) {
            if (this.match(TokenType.LET) || this.match(TokenType.CONST)) {
                const kind = this.previous().type === TokenType.LET ? 'let' : 'const';
                const name = this.consume(TokenType.IDENTIFIER, 'Expected variable name').value;
                let initValue = null;
                if (this.match(TokenType.ASSIGN)) {
                    initValue = this.expression();
                }
                init = {
                    type: 'VariableDeclaration',
                    kind,
                    name,
                    init: initValue,
                };
            }
            else {
                init = this.expression();
            }
        }
        this.consume(TokenType.SEMICOLON, "Expected ';' after for init");
        // Parse test
        let test = null;
        if (!this.check(TokenType.SEMICOLON)) {
            test = this.expression();
        }
        this.consume(TokenType.SEMICOLON, "Expected ';' after for condition");
        // Parse update
        let update = null;
        if (!this.check(TokenType.RPAREN)) {
            update = this.expression();
        }
        this.consume(TokenType.RPAREN, "Expected ')' after for clauses");
        // Parse body
        this.consume(TokenType.LBRACE, "Expected '{' before for body");
        const body = this.block();
        return {
            type: 'ForStatement',
            init,
            test,
            update,
            body,
        };
    }
    returnStatement() {
        let argument = null;
        if (!this.check(TokenType.SEMICOLON) && !this.check(TokenType.RBRACE) && !this.check(TokenType.EOF)) {
            argument = this.expression();
        }
        this.consumeOptional(TokenType.SEMICOLON);
        return {
            type: 'ReturnStatement',
            argument,
        };
    }
    tryStatement() {
        this.consume(TokenType.LBRACE, "Expected '{' after 'try'");
        const block = this.block();
        let handler = null;
        if (this.match(TokenType.CATCH)) {
            this.consume(TokenType.LPAREN, "Expected '(' after 'catch'");
            const param = this.consume(TokenType.IDENTIFIER, 'Expected error parameter').value;
            this.consume(TokenType.RPAREN, "Expected ')' after catch parameter");
            this.consume(TokenType.LBRACE, "Expected '{' before catch body");
            const catchBody = this.block();
            handler = { param, body: catchBody };
        }
        return {
            type: 'TryStatement',
            block,
            handler,
        };
    }
    throwStatement() {
        const argument = this.expression();
        this.consumeOptional(TokenType.SEMICOLON);
        return {
            type: 'ThrowStatement',
            argument,
        };
    }
    breakStatement() {
        this.consumeOptional(TokenType.SEMICOLON);
        return { type: 'BreakStatement' };
    }
    continueStatement() {
        this.consumeOptional(TokenType.SEMICOLON);
        return { type: 'ContinueStatement' };
    }
    block() {
        const statements = [];
        const guard = createLoopGuard({
            maxIterations: 100000,
            context: 'BuddyScript block parsing',
        });
        while (!this.check(TokenType.RBRACE) && !this.isAtEnd()) {
            guard();
            const decl = this.declaration();
            if (decl)
                statements.push(decl);
        }
        this.consume(TokenType.RBRACE, "Expected '}' after block");
        return {
            type: 'BlockStatement',
            body: statements,
        };
    }
    expressionStatement() {
        const expression = this.expression();
        this.consumeOptional(TokenType.SEMICOLON);
        return {
            type: 'ExpressionStatement',
            expression,
        };
    }
    // ============================================
    // Expression Parsing (Precedence Climbing)
    // ============================================
    expression() {
        return this.assignment();
    }
    assignment() {
        const expr = this.ternary();
        if (this.match(TokenType.ASSIGN, TokenType.PLUS_ASSIGN, TokenType.MINUS_ASSIGN)) {
            const operator = this.previous().value;
            const value = this.assignment();
            if (expr.type === 'Identifier' || expr.type === 'MemberExpression') {
                return {
                    type: 'AssignmentExpression',
                    operator,
                    left: expr,
                    right: value,
                };
            }
            throw new Error('Invalid assignment target');
        }
        return expr;
    }
    ternary() {
        let expr = this.or();
        if (this.match(TokenType.QUESTION)) {
            const consequent = this.expression();
            this.consume(TokenType.COLON, "Expected ':' in ternary expression");
            const alternate = this.ternary();
            return {
                type: 'ConditionalExpression',
                test: expr,
                consequent,
                alternate,
            };
        }
        return expr;
    }
    or() {
        let expr = this.and();
        const guard = createLoopGuard({
            maxIterations: 10000,
            context: 'BuddyScript OR expression parsing',
        });
        while (this.match(TokenType.OR)) {
            guard();
            const right = this.and();
            expr = {
                type: 'LogicalExpression',
                operator: '||',
                left: expr,
                right,
            };
        }
        return expr;
    }
    and() {
        let expr = this.equality();
        const guard = createLoopGuard({
            maxIterations: 10000,
            context: 'BuddyScript AND expression parsing',
        });
        while (this.match(TokenType.AND)) {
            guard();
            const right = this.equality();
            expr = {
                type: 'LogicalExpression',
                operator: '&&',
                left: expr,
                right,
            };
        }
        return expr;
    }
    equality() {
        let expr = this.comparison();
        const guard = createLoopGuard({
            maxIterations: 10000,
            context: 'BuddyScript equality expression parsing',
        });
        while (this.match(TokenType.EQUALS, TokenType.NOT_EQUALS)) {
            guard();
            const operator = this.previous().value;
            const right = this.comparison();
            expr = {
                type: 'BinaryExpression',
                operator,
                left: expr,
                right,
            };
        }
        return expr;
    }
    comparison() {
        let expr = this.term();
        const guard = createLoopGuard({
            maxIterations: 10000,
            context: 'BuddyScript comparison expression parsing',
        });
        while (this.match(TokenType.LESS_THAN, TokenType.LESS_EQUAL, TokenType.GREATER_THAN, TokenType.GREATER_EQUAL)) {
            guard();
            const operator = this.previous().value;
            const right = this.term();
            expr = {
                type: 'BinaryExpression',
                operator,
                left: expr,
                right,
            };
        }
        return expr;
    }
    term() {
        let expr = this.factor();
        const guard = createLoopGuard({
            maxIterations: 10000,
            context: 'BuddyScript term expression parsing',
        });
        while (this.match(TokenType.PLUS, TokenType.MINUS)) {
            guard();
            const operator = this.previous().value;
            const right = this.factor();
            expr = {
                type: 'BinaryExpression',
                operator,
                left: expr,
                right,
            };
        }
        return expr;
    }
    factor() {
        let expr = this.power();
        const guard = createLoopGuard({
            maxIterations: 10000,
            context: 'BuddyScript factor expression parsing',
        });
        while (this.match(TokenType.MULTIPLY, TokenType.DIVIDE, TokenType.MODULO)) {
            guard();
            const operator = this.previous().value;
            const right = this.power();
            expr = {
                type: 'BinaryExpression',
                operator,
                left: expr,
                right,
            };
        }
        return expr;
    }
    power() {
        let expr = this.unary();
        if (this.match(TokenType.POWER)) {
            const right = this.power(); // Right associative
            expr = {
                type: 'BinaryExpression',
                operator: '**',
                left: expr,
                right,
            };
        }
        return expr;
    }
    unary() {
        if (this.match(TokenType.NOT, TokenType.MINUS)) {
            const operator = this.previous().value;
            const right = this.unary();
            return {
                type: 'UnaryExpression',
                operator,
                argument: right,
                prefix: true,
            };
        }
        if (this.match(TokenType.AWAIT)) {
            const argument = this.unary();
            return {
                type: 'AwaitExpression',
                argument,
            };
        }
        return this.call();
    }
    call() {
        let expr = this.primary();
        // Guard against infinite loops in call/member parsing (deeply nested chains)
        const guard = createLoopGuard({
            maxIterations: 10000,
            context: 'BuddyScript call expression parsing',
        });
        while (true) {
            guard();
            if (this.match(TokenType.LPAREN)) {
                expr = this.finishCall(expr);
            }
            else if (this.match(TokenType.DOT)) {
                const name = this.consume(TokenType.IDENTIFIER, 'Expected property name').value;
                expr = {
                    type: 'MemberExpression',
                    object: expr,
                    property: { type: 'Identifier', name },
                    computed: false,
                };
            }
            else if (this.match(TokenType.LBRACKET)) {
                const property = this.expression();
                this.consume(TokenType.RBRACKET, "Expected ']' after index");
                expr = {
                    type: 'MemberExpression',
                    object: expr,
                    property,
                    computed: true,
                };
            }
            else {
                break;
            }
        }
        return expr;
    }
    finishCall(callee) {
        const args = [];
        if (!this.check(TokenType.RPAREN)) {
            do {
                // Handle named arguments: name: value
                if (this.check(TokenType.IDENTIFIER) && this.checkNext(TokenType.COLON)) {
                    const name = this.advance().value;
                    this.advance(); // :
                    const value = this.expression();
                    args.push({
                        type: 'ObjectExpression',
                        properties: [{ key: name, value, computed: false }],
                    });
                }
                else {
                    args.push(this.expression());
                }
            } while (this.match(TokenType.COMMA));
        }
        this.consume(TokenType.RPAREN, "Expected ')' after arguments");
        return {
            type: 'CallExpression',
            callee,
            arguments: args,
        };
    }
    primary() {
        if (this.match(TokenType.NUMBER, TokenType.STRING, TokenType.BOOLEAN, TokenType.NULL)) {
            return {
                type: 'Literal',
                value: this.previous().value,
            };
        }
        if (this.match(TokenType.IDENTIFIER)) {
            return {
                type: 'Identifier',
                name: this.previous().value,
            };
        }
        if (this.match(TokenType.LBRACKET)) {
            return this.arrayExpression();
        }
        if (this.match(TokenType.LBRACE)) {
            return this.objectExpression();
        }
        if (this.match(TokenType.LPAREN)) {
            // Could be grouping or arrow function
            const expr = this.expression();
            this.consume(TokenType.RPAREN, "Expected ')' after expression");
            if (this.match(TokenType.ARROW)) {
                return this.arrowFunction([expr]);
            }
            return expr;
        }
        throw new Error(`Unexpected token: ${this.peek().type} at line ${this.peek().line}`);
    }
    arrayExpression() {
        const elements = [];
        if (!this.check(TokenType.RBRACKET)) {
            do {
                elements.push(this.expression());
            } while (this.match(TokenType.COMMA));
        }
        this.consume(TokenType.RBRACKET, "Expected ']' after array");
        return {
            type: 'ArrayExpression',
            elements,
        };
    }
    objectExpression() {
        const properties = [];
        if (!this.check(TokenType.RBRACE)) {
            do {
                let key;
                let computed = false;
                if (this.match(TokenType.LBRACKET)) {
                    key = this.expression();
                    this.consume(TokenType.RBRACKET, "Expected ']' after computed property");
                    computed = true;
                }
                else if (this.check(TokenType.STRING)) {
                    key = this.advance().value;
                }
                else {
                    key = this.consume(TokenType.IDENTIFIER, 'Expected property name').value;
                }
                this.consume(TokenType.COLON, "Expected ':' after property name");
                const value = this.expression();
                properties.push({ key, value, computed });
            } while (this.match(TokenType.COMMA));
        }
        this.consume(TokenType.RBRACE, "Expected '}' after object");
        return {
            type: 'ObjectExpression',
            properties,
        };
    }
    arrowFunction(params) {
        const paramNodes = params.map(p => {
            if (p.type !== 'Identifier') {
                throw new Error('Arrow function parameters must be identifiers');
            }
            return { name: p.name };
        });
        let body;
        if (this.match(TokenType.LBRACE)) {
            body = this.block();
        }
        else {
            body = this.expression();
        }
        return {
            type: 'ArrowFunction',
            params: paramNodes,
            body,
            async: false,
        };
    }
    // ============================================
    // Utility Methods
    // ============================================
    match(...types) {
        for (const type of types) {
            if (this.check(type)) {
                this.advance();
                return true;
            }
        }
        return false;
    }
    check(type) {
        if (this.isAtEnd())
            return false;
        return this.peek().type === type;
    }
    checkNext(type) {
        if (this.current + 1 >= this.tokens.length)
            return false;
        return this.tokens[this.current + 1].type === type;
    }
    advance() {
        if (!this.isAtEnd())
            this.current++;
        return this.previous();
    }
    isAtEnd() {
        return this.peek().type === TokenType.EOF;
    }
    peek() {
        return this.tokens[this.current];
    }
    previous() {
        return this.tokens[this.current - 1];
    }
    consume(type, message) {
        if (this.check(type))
            return this.advance();
        throw new Error(`${message}. Got ${this.peek().type} at line ${this.peek().line}`);
    }
    consumeOptional(type) {
        if (this.check(type)) {
            this.advance();
            return true;
        }
        return false;
    }
    synchronize() {
        this.advance();
        const guard = createLoopGuard({
            maxIterations: 100000,
            context: 'BuddyScript error synchronization',
        });
        while (!this.isAtEnd()) {
            guard();
            if (this.previous().type === TokenType.SEMICOLON)
                return;
            switch (this.peek().type) {
                case TokenType.FUNCTION:
                case TokenType.LET:
                case TokenType.CONST:
                case TokenType.IF:
                case TokenType.WHILE:
                case TokenType.FOR:
                case TokenType.RETURN:
                case TokenType.TRY:
                    return;
            }
            this.advance();
        }
    }
}
export function parse(tokens) {
    return new Parser(tokens).parse();
}
//# sourceMappingURL=parser.js.map