import { Token, TokenType, TokenizerError } from './tokenizer';

/**
 * AST Node types for EFTL
 */
export interface AstNode {
    type: string;
    line: number;
    column: number;
    children?: AstNode[];
}

export interface ParserError {
    message: string;
    line: number;
    column: number;
    length: number;
}

/**
 * EFTL Parser - validates structure and reports errors
 */
export class EftlParser {
    private tokens: Token[];
    private pos: number = 0;
    private errors: ParserError[] = [];
    private blockStack: { type: TokenType; token: Token }[] = [];

    constructor(tokens: Token[]) {
        this.tokens = tokens;
    }

    parse(): { errors: ParserError[] } {
        this.pos = 0;
        this.errors = [];
        this.blockStack = [];

        while (!this.isAtEnd()) {
            this.parseTopLevel();
        }

        // Check for unclosed blocks
        for (const block of this.blockStack) {
            this.errors.push({
                message: `Unclosed block: ${block.token.value}`,
                line: block.token.line,
                column: block.token.column,
                length: block.token.length
            });
        }

        return { errors: this.errors };
    }

    private parseTopLevel(): void {
        const token = this.current();

        switch (token.type) {
            case TokenType.EFTL_OPEN:
                this.pushBlock(token);
                this.advance();
                break;

            case TokenType.EFTL_CLOSE:
                this.popBlock(TokenType.EFTL_OPEN, token);
                this.advance();
                break;

            case TokenType.VAR_OPEN:
                this.pushBlock(token);
                this.advance();
                break;

            case TokenType.VAR_CLOSE:
                this.popBlock(TokenType.VAR_OPEN, token);
                this.advance();
                break;

            case TokenType.IF_OPEN:
                this.pushBlock(token);
                this.advance();
                break;

            case TokenType.IF_CLOSE:
                this.popBlock(TokenType.IF_OPEN, token);
                this.advance();
                break;

            case TokenType.CONDITION_OPEN:
                this.pushBlock(token);
                this.advance();
                break;

            case TokenType.CONDITION_CLOSE:
                this.popBlock(TokenType.CONDITION_OPEN, token);
                this.advance();
                break;

            case TokenType.THEN_OPEN:
                this.pushBlock(token);
                this.advance();
                break;

            case TokenType.THEN_CLOSE:
                this.popBlock(TokenType.THEN_OPEN, token);
                this.advance();
                break;

            case TokenType.ELSE_OPEN:
                this.pushBlock(token);
                this.advance();
                break;

            case TokenType.ELSE_CLOSE:
                this.popBlock(TokenType.ELSE_OPEN, token);
                this.advance();
                break;

            case TokenType.ELSE_IF_OPEN:
                this.pushBlock(token);
                this.advance();
                break;

            case TokenType.ELSE_IF_CLOSE:
                this.popBlock(TokenType.ELSE_IF_OPEN, token);
                this.advance();
                break;

            case TokenType.WHILE_OPEN:
                this.pushBlock(token);
                this.advance();
                break;

            case TokenType.WHILE_CLOSE:
                this.popBlock(TokenType.WHILE_OPEN, token);
                this.advance();
                break;

            case TokenType.DO_OPEN:
                this.pushBlock(token);
                this.advance();
                break;

            case TokenType.DO_CLOSE:
                this.popBlock(TokenType.DO_OPEN, token);
                this.advance();
                break;

            case TokenType.TAG_OPEN:
                this.pushBlock(token);
                this.advance();
                break;

            case TokenType.TAG_CLOSE:
                this.popBlock(TokenType.TAG_OPEN, token);
                this.advance();
                break;

            case TokenType.SPLIT_OPEN:
                this.pushBlock(token);
                this.advance();
                break;

            case TokenType.SPLIT_CLOSE:
                this.popBlock(TokenType.SPLIT_OPEN, token);
                this.advance();
                break;

            case TokenType.TRIM_OPEN:
                this.pushBlock(token);
                this.advance();
                break;

            case TokenType.TRIM_CLOSE:
                this.popBlock(TokenType.TRIM_OPEN, token);
                this.advance();
                break;

            case TokenType.CONTAINS_OPEN:
                this.pushBlock(token);
                this.advance();
                break;

            case TokenType.CONTAINS_CLOSE:
                this.popBlock(TokenType.CONTAINS_OPEN, token);
                this.advance();
                break;

            case TokenType.FORMAT_OPEN:
                this.pushBlock(token);
                this.advance();
                break;

            case TokenType.FORMAT_CLOSE:
                this.popBlock(TokenType.FORMAT_OPEN, token);
                this.advance();
                break;

            case TokenType.EXPR_OPEN:
            case TokenType.EXPR_OUTPUT_OPEN:
                this.pushBlock(token);
                this.advance();
                break;

            case TokenType.EXPR_CLOSE:
                // Can close either EXPR_OPEN or EXPR_OUTPUT_OPEN
                const exprBlock = this.blockStack.pop();
                if (!exprBlock || (exprBlock.type !== TokenType.EXPR_OPEN && exprBlock.type !== TokenType.EXPR_OUTPUT_OPEN)) {
                    this.errors.push({
                        message: `Unexpected closing: ${token.value}`,
                        line: token.line,
                        column: token.column,
                        length: token.length
                    });
                    if (exprBlock) {
                        this.blockStack.push(exprBlock);
                    }
                }
                this.advance();
                break;

            case TokenType.COMMENT_OPEN:
                this.pushBlock(token);
                this.advance();
                break;

            case TokenType.COMMENT_CLOSE:
                this.popBlock(TokenType.COMMENT_OPEN, token);
                this.advance();
                break;

            // Self-closing tags - no block needed
            case TokenType.VALUE_OF:
            case TokenType.SIZE_OF:
            case TokenType.HEADER:
                if (!token.value.endsWith('/]')) {
                    this.pushBlock(token);
                }
                this.advance();
                break;

            case TokenType.TEXT:
            case TokenType.EOF:
                this.advance();
                break;

            default:
                // Unknown token, skip
                this.advance();
                break;
        }
    }

    private pushBlock(token: Token): void {
        if (token.value.endsWith('/]')) {
            return; // It's self-closing, don't push to stack
        }
        this.blockStack.push({ type: token.type, token });
    }

    private popBlock(expectedOpenType: TokenType, closeToken: Token): void {
        if (this.blockStack.length === 0) {
            this.errors.push({
                message: `Unexpected closing tag: ${closeToken.value} (no matching opening tag)`,
                line: closeToken.line,
                column: closeToken.column,
                length: closeToken.length
            });
            return;
        }

        const top = this.blockStack[this.blockStack.length - 1];
        if (top.type !== expectedOpenType) {
            // Find the expected closing tag name
            const expectedClose = this.getMatchingClose(top.type);
            this.errors.push({
                message: `Mismatched closing tag: expected ${expectedClose}, got ${closeToken.value}`,
                line: closeToken.line,
                column: closeToken.column,
                length: closeToken.length
            });
            return;
        }

        this.blockStack.pop();
    }

    private getMatchingClose(openType: TokenType): string {
        const closeMap: { [key: string]: string } = {
            [TokenType.EFTL_OPEN]: '[/EFTL]',
            [TokenType.VAR_OPEN]: '[/VAR]',
            [TokenType.IF_OPEN]: '[/IF]',
            [TokenType.CONDITION_OPEN]: '[/CONDITION]',
            [TokenType.THEN_OPEN]: '[/THEN]',
            [TokenType.ELSE_OPEN]: '[/ELSE]',
            [TokenType.ELSE_IF_OPEN]: '[/ELSE IF]',
            [TokenType.WHILE_OPEN]: '[/WHILE]',
            [TokenType.DO_OPEN]: '[/DO]',
            [TokenType.TAG_OPEN]: '[/TAG]',
            [TokenType.SPLIT_OPEN]: '[/SPLIT]',
            [TokenType.TRIM_OPEN]: '[/TRIM]',
            [TokenType.CONTAINS_OPEN]: '[/CONTAINS]',
            [TokenType.FORMAT_OPEN]: '[/FORMAT]',
            [TokenType.VALUE_OF]: '[/VALUE_OF]',
            [TokenType.SIZE_OF]: '[/SIZE_OF]',
            [TokenType.HEADER]: '[/HEADER]',
            [TokenType.EXPR_OPEN]: '%]',
            [TokenType.EXPR_OUTPUT_OPEN]: '%]',
            [TokenType.COMMENT_OPEN]: '--]'
        };
        return closeMap[openType] || 'unknown';
    }

    private current(): Token {
        return this.tokens[this.pos] || { type: TokenType.EOF, value: '', line: 0, column: 0, length: 0 };
    }

    private advance(): Token {
        if (!this.isAtEnd()) {
            this.pos++;
        }
        return this.tokens[this.pos - 1];
    }

    private isAtEnd(): boolean {
        return this.current().type === TokenType.EOF;
    }
}
