/**
 * Pratt parser for Architect expressions.
 *
 * Consumes a TokenSpan (the .value / .condition / etc. field of a statement node)
 * and returns a recursive ExprNode tree. Called on demand — not part of the base
 * document parse.
 *
 * Operator precedence (binding powers):
 *   OR (10) < AND (20) < NOT prefix (30) < comparisons (40) <
 *   additive (50) < multiplicative (60) < ** (70) < unary - (80) < atoms
 */

import { TokenType } from "../lexer/tokenTypes.js";
import { AcurityTypes } from "../types/acurityTypes.js";
import {
    ExprNodeType,
    makeLiteral,
    makeDateNode,
    makeTimeNode,
    makeIdentifier,
    makeCall,
    makeUnary,
    makeBinary,
    makeParen,
} from "./expressionNodes.js";

// ---------------------------------------------------------------------------
// Binding powers
// ---------------------------------------------------------------------------

const BP_OR       = 10;
const BP_AND      = 20;
const BP_NOT      = 30;   // prefix
const BP_COMPARE  = 40;
const BP_ADDITIVE = 50;
const BP_MULT     = 60;
const BP_POWER    = 70;
const BP_UNARY    = 80;   // unary minus prefix

/**
 * Left binding power for infix use of a token.
 * Returns 0 for tokens that are not infix operators.
 */
function lbp(token) {
    const { type, value } = token;

    if (type === TokenType.KEYWORD) {
        switch (value.toUpperCase()) {
            case "OR":  return BP_OR;
            case "AND": return BP_AND;
            case "MOD": return BP_MULT;
        }
        return 0;
    }

    switch (type) {
        case TokenType.EQUALS:
        case TokenType.NOT_EQ:
        case TokenType.LT:
        case TokenType.GT:
        case TokenType.LT_EQ:
        case TokenType.GT_EQ:
            return BP_COMPARE;
        case TokenType.PLUS:
        case TokenType.MINUS:
            return BP_ADDITIVE;
        case TokenType.STAR:
        case TokenType.SLASH:
            return BP_MULT;
        case TokenType.DOUBLE_STAR:
            return BP_POWER;
        default:
            return 0;
    }
}

// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------

/**
 * Parse a TokenSpan into an expression node tree.
 *
 * @param {object|null} span - TokenSpan from the statement-level parser
 * @returns {object|null} - ExprNode, or null if the span is empty
 */
function parseExpression(span) {
    if (!span || !span.tokens || span.tokens.length === 0) return null;

    const tokens = span.tokens;
    let pos = 0;

    function peek() {
        if (pos < tokens.length) return tokens[pos];
        return { type: TokenType.EOF, value: "", line: 0, char: 0 };
    }

    function advance() {
        const tok = tokens[pos] || { type: TokenType.EOF, value: "", line: 0, char: 0 };
        pos++;
        return tok;
    }

    function expression(minBP) {
        const tok = advance();
        let left = nud(tok);
        if (left === null) return null;

        while (lbp(peek()) > minBP) {
            const op = advance();
            left = led(op, left);
            if (left === null) break;
        }

        return left;
    }

    function nud(tok) {
        const { type, value } = tok;
        const upper = value.toUpperCase();

        // String literal
        if (type === TokenType.STRING_LITERAL) {
            return makeLiteral(tok, AcurityTypes.STRING);
        }

        // Number literal
        if (type === TokenType.NUMBER_LITERAL) {
            return makeLiteral(tok, inferNumericType(value));
        }

        // Date literal
        if (type === TokenType.DATE_LITERAL) {
            return makeDateNode(tok);
        }

        // Time literal
        if (type === TokenType.TIME_LITERAL) {
            return makeTimeNode(tok);
        }

        // Boolean literals
        if (type === TokenType.KEYWORD && (upper === "TRUE" || upper === "FALSE")) {
            return makeLiteral(tok, AcurityTypes.BOOLEAN);
        }

        // Unary minus
        if (type === TokenType.MINUS) {
            const operand = expression(BP_UNARY);
            return makeUnary("-", operand, tok);
        }

        // Unary NOT
        if (type === TokenType.KEYWORD && upper === "NOT") {
            const operand = expression(BP_NOT);
            return makeUnary("NOT", operand, tok);
        }

        // Parenthesised expression
        if (type === TokenType.LPAREN) {
            const inner = expression(0);
            if (peek().type === TokenType.RPAREN) advance();
            return makeParen(inner, tok);
        }

        // Identifier or function call
        if (type === TokenType.IDENTIFIER) {
            const callee = makeIdentifier(tok);
            if (peek().type === TokenType.LPAREN) {
                return parseCall(callee, tok);
            }
            return callee;
        }

        // TYPE_KEYWORD used as identifier (unusual but possible)
        if (type === TokenType.TYPE_KEYWORD) {
            const callee = makeIdentifier(tok);
            if (peek().type === TokenType.LPAREN) {
                return parseCall(callee, tok);
            }
            return callee;
        }

        // Unrecognised token in prefix position — return identifier as fallback
        return makeIdentifier(tok);
    }

    function parseCall(callee, startTok) {
        const lparen = advance(); // consume '('
        const args = [];

        if (peek().type !== TokenType.RPAREN && peek().type !== TokenType.EOF) {
            args.push(expression(0));
            while (peek().type === TokenType.COMMA) {
                advance(); // consume ','
                if (peek().type === TokenType.RPAREN || peek().type === TokenType.EOF) break;
                args.push(expression(0));
            }
        }

        const endTok = peek().type === TokenType.RPAREN ? advance() : startTok;
        return makeCall(callee, args, lparen, endTok);
    }

    function led(tok, left) {
        const bp = lbp(tok);
        const right = expression(bp); // left-associative: recurse at same level
        const op = tok.value.toUpperCase();
        return makeBinary(op, left, right, tok);
    }

    return expression(0);
}

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

/**
 * Infer the numeric type of a non-negative number literal string.
 * Negative literals are handled by the UNARY(-) node wrapping them.
 */
function inferNumericType(value) {
    if (value.includes(".")) return AcurityTypes.DOUBLE;
    const n = parseInt(value, 10);
    if (n <= 32767)       return AcurityTypes.SHORT;
    if (n <= 2147483647)  return AcurityTypes.LONG;
    return AcurityTypes.BIGINT;
}

export { parseExpression };
