/**
 * EFTL Token Types
 */
export enum TokenType {
    // Block delimiters
    EFTL_OPEN = 'EFTL_OPEN',           // [EFTL]
    EFTL_CLOSE = 'EFTL_CLOSE',         // [/EFTL]

    // Variable
    VAR_OPEN = 'VAR_OPEN',             // [VAR ...]
    VAR_CLOSE = 'VAR_CLOSE',           // [/VAR]

    // Control structures
    IF_OPEN = 'IF_OPEN',               // [IF]
    IF_CLOSE = 'IF_CLOSE',             // [/IF]
    CONDITION_OPEN = 'CONDITION_OPEN', // [CONDITION]
    CONDITION_CLOSE = 'CONDITION_CLOSE', // [/CONDITION]
    THEN_OPEN = 'THEN_OPEN',           // [THEN]
    THEN_CLOSE = 'THEN_CLOSE',         // [/THEN]
    ELSE_OPEN = 'ELSE_OPEN',           // [ELSE]
    ELSE_CLOSE = 'ELSE_CLOSE',         // [/ELSE]
    ELSE_IF_OPEN = 'ELSE_IF_OPEN',     // [ELSE IF]
    ELSE_IF_CLOSE = 'ELSE_IF_CLOSE',   // [/ELSE IF]
    WHILE_OPEN = 'WHILE_OPEN',         // [WHILE]
    WHILE_CLOSE = 'WHILE_CLOSE',       // [/WHILE]
    DO_OPEN = 'DO_OPEN',               // [DO]
    DO_CLOSE = 'DO_CLOSE',             // [/DO]

    // Functions
    TAG_OPEN = 'TAG_OPEN',             // [TAG]
    TAG_CLOSE = 'TAG_CLOSE',           // [/TAG]
    SPLIT_OPEN = 'SPLIT_OPEN',         // [SPLIT ...]
    SPLIT_CLOSE = 'SPLIT_CLOSE',       // [/SPLIT]
    TRIM_OPEN = 'TRIM_OPEN',           // [TRIM]
    TRIM_CLOSE = 'TRIM_CLOSE',         // [/TRIM]
    CONTAINS_OPEN = 'CONTAINS_OPEN',   // [CONTAINS ...]
    CONTAINS_CLOSE = 'CONTAINS_CLOSE', // [/CONTAINS]
    FORMAT_OPEN = 'FORMAT_OPEN',       // [FORMAT ...]
    FORMAT_CLOSE = 'FORMAT_CLOSE',     // [/FORMAT]
    VALUE_OF = 'VALUE_OF',             // [VALUE_OF ... /]
    SIZE_OF = 'SIZE_OF',               // [SIZE_OF ... /]
    HEADER = 'HEADER',                 // [HEADER ... /]

    // Expressions
    EXPR_OPEN = 'EXPR_OPEN',           // [%
    EXPR_CLOSE = 'EXPR_CLOSE',         // %]
    EXPR_OUTPUT_OPEN = 'EXPR_OUTPUT_OPEN', // [%=

    // Comments
    COMMENT_OPEN = 'COMMENT_OPEN',     // [!--
    COMMENT_CLOSE = 'COMMENT_CLOSE',   // --]

    // Content
    TEXT = 'TEXT',                     // Plain text
    IDENTIFIER = 'IDENTIFIER',         // Variable names
    STRING = 'STRING',                 // "..."
    NUMBER = 'NUMBER',                 // 123, 45.67

    // Other
    EOF = 'EOF',
    ERROR = 'ERROR'
}

export interface Token {
    type: TokenType;
    value: string;
    line: number;
    column: number;
    length: number;
}

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

/**
 * EFTL Tokenizer - converts source text into tokens
 */
export class EftlTokenizer {
    private source: string;
    private pos: number = 0;
    private line: number = 1;
    private column: number = 1;
    private tokens: Token[] = [];
    private errors: TokenizerError[] = [];

    constructor(source: string) {
        this.source = source;
    }

    tokenize(): { tokens: Token[]; errors: TokenizerError[] } {
        this.tokens = [];
        this.errors = [];
        this.pos = 0;
        this.line = 1;
        this.column = 1;

        while (this.pos < this.source.length) {
            this.scanToken();
        }

        this.tokens.push({
            type: TokenType.EOF,
            value: '',
            line: this.line,
            column: this.column,
            length: 0
        });

        return { tokens: this.tokens, errors: this.errors };
    }

    private scanToken(): void {
        const startLine = this.line;
        const startColumn = this.column;

        // Check for EFTL constructs starting with [
        if (this.peek() === '[') {
            if (this.match('[EFTL]')) {
                this.addToken(TokenType.EFTL_OPEN, '[EFTL]', startLine, startColumn);
                return;
            }
            if (this.match('[/EFTL]')) {
                this.addToken(TokenType.EFTL_CLOSE, '[/EFTL]', startLine, startColumn);
                return;
            }
            if (this.match('[!--')) {
                this.addToken(TokenType.COMMENT_OPEN, '[!--', startLine, startColumn);
                this.scanComment();
                return;
            }
            if (this.match('[%=')) {
                this.addToken(TokenType.EXPR_OUTPUT_OPEN, '[%=', startLine, startColumn);
                return;
            }
            if (this.match('[%')) {
                this.addToken(TokenType.EXPR_OPEN, '[%', startLine, startColumn);
                return;
            }
            if (this.match('[VAR')) {
                this.scanVarOpen(startLine, startColumn);
                return;
            }
            if (this.match('[/VAR]')) {
                this.addToken(TokenType.VAR_CLOSE, '[/VAR]', startLine, startColumn);
                return;
            }
            if (this.match('[IF]')) {
                this.addToken(TokenType.IF_OPEN, '[IF]', startLine, startColumn);
                return;
            }
            if (this.match('[/IF]')) {
                this.addToken(TokenType.IF_CLOSE, '[/IF]', startLine, startColumn);
                return;
            }
            if (this.match('[CONDITION]')) {
                this.addToken(TokenType.CONDITION_OPEN, '[CONDITION]', startLine, startColumn);
                return;
            }
            if (this.match('[/CONDITION]')) {
                this.addToken(TokenType.CONDITION_CLOSE, '[/CONDITION]', startLine, startColumn);
                return;
            }
            if (this.match('[THEN]')) {
                this.addToken(TokenType.THEN_OPEN, '[THEN]', startLine, startColumn);
                return;
            }
            if (this.match('[/THEN]')) {
                this.addToken(TokenType.THEN_CLOSE, '[/THEN]', startLine, startColumn);
                return;
            }
            if (this.match('[ELSE IF]')) {
                this.addToken(TokenType.ELSE_IF_OPEN, '[ELSE IF]', startLine, startColumn);
                return;
            }
            if (this.match('[/ELSE IF]')) {
                this.addToken(TokenType.ELSE_IF_CLOSE, '[/ELSE IF]', startLine, startColumn);
                return;
            }
            if (this.match('[ELSE]')) {
                this.addToken(TokenType.ELSE_OPEN, '[ELSE]', startLine, startColumn);
                return;
            }
            if (this.match('[/ELSE]')) {
                this.addToken(TokenType.ELSE_CLOSE, '[/ELSE]', startLine, startColumn);
                return;
            }
            if (this.match('[WHILE]')) {
                this.addToken(TokenType.WHILE_OPEN, '[WHILE]', startLine, startColumn);
                return;
            }
            if (this.match('[/WHILE]')) {
                this.addToken(TokenType.WHILE_CLOSE, '[/WHILE]', startLine, startColumn);
                return;
            }
            if (this.match('[DO]')) {
                this.addToken(TokenType.DO_OPEN, '[DO]', startLine, startColumn);
                return;
            }
            if (this.match('[/DO]')) {
                this.addToken(TokenType.DO_CLOSE, '[/DO]', startLine, startColumn);
                return;
            }
            if (this.match('[TAG]')) {
                this.addToken(TokenType.TAG_OPEN, '[TAG]', startLine, startColumn);
                return;
            }
            if (this.match('[/TAG]')) {
                this.addToken(TokenType.TAG_CLOSE, '[/TAG]', startLine, startColumn);
                return;
            }
            if (this.match('[SPLIT')) {
                this.scanTagWithAttributes(TokenType.SPLIT_OPEN, 'SPLIT', startLine, startColumn);
                return;
            }
            if (this.match('[/SPLIT]')) {
                this.addToken(TokenType.SPLIT_CLOSE, '[/SPLIT]', startLine, startColumn);
                return;
            }
            if (this.match('[TRIM]')) {
                this.addToken(TokenType.TRIM_OPEN, '[TRIM]', startLine, startColumn);
                return;
            }
            if (this.match('[/TRIM]')) {
                this.addToken(TokenType.TRIM_CLOSE, '[/TRIM]', startLine, startColumn);
                return;
            }
            if (this.match('[CONTAINS')) {
                this.scanTagWithAttributes(TokenType.CONTAINS_OPEN, 'CONTAINS', startLine, startColumn);
                return;
            }
            if (this.match('[/CONTAINS]')) {
                this.addToken(TokenType.CONTAINS_CLOSE, '[/CONTAINS]', startLine, startColumn);
                return;
            }
            if (this.match('[FORMAT')) {
                this.scanTagWithAttributes(TokenType.FORMAT_OPEN, 'FORMAT', startLine, startColumn);
                return;
            }
            if (this.match('[/FORMAT]')) {
                this.addToken(TokenType.FORMAT_CLOSE, '[/FORMAT]', startLine, startColumn);
                return;
            }
            if (this.match('[VALUE_OF')) {
                this.scanSelfClosingTag(TokenType.VALUE_OF, 'VALUE_OF', startLine, startColumn);
                return;
            }
            if (this.match('[SIZE_OF')) {
                this.scanSelfClosingTag(TokenType.SIZE_OF, 'SIZE_OF', startLine, startColumn);
                return;
            }
            if (this.match('[HEADER')) {
                this.scanSelfClosingTag(TokenType.HEADER, 'HEADER', startLine, startColumn);
                return;
            }
        }

        // Check for expression close
        if (this.match('%]')) {
            this.addToken(TokenType.EXPR_CLOSE, '%]', startLine, startColumn);
            return;
        }

        // Check for comment close
        if (this.match('--]')) {
            this.addToken(TokenType.COMMENT_CLOSE, '--]', startLine, startColumn);
            return;
        }

        // Plain text - consume until we hit a special character
        this.scanText(startLine, startColumn);
    }

    private scanComment(): void {
        const startLine = this.line;
        const startColumn = this.column;
        let content = '';

        while (this.pos < this.source.length) {
            if (this.match('--]')) {
                this.addToken(TokenType.COMMENT_CLOSE, '--]', this.line, this.column - 3);
                return;
            }
            content += this.advance();
        }

        // Unclosed comment
        this.errors.push({
            message: 'Unclosed comment: expected "--]"',
            line: startLine,
            column: startColumn,
            length: content.length
        });
    }

    private scanVarOpen(startLine: number, startColumn: number): void {
        let value = '[VAR';
        // Scan until we find ]
        while (this.pos < this.source.length && this.peek() !== ']') {
            value += this.advance();
        }
        if (this.peek() === ']') {
            value += this.advance();
        }
        this.addToken(TokenType.VAR_OPEN, value, startLine, startColumn);
    }

    private scanTagWithAttributes(type: TokenType, tagName: string, startLine: number, startColumn: number): void {
        let value = '[' + tagName;
        while (this.pos < this.source.length && this.peek() !== ']') {
            value += this.advance();
        }
        if (this.peek() === ']') {
            value += this.advance();
        }
        this.addToken(type, value, startLine, startColumn);
    }

    private scanSelfClosingTag(type: TokenType, tagName: string, startLine: number, startColumn: number): void {
        let value = '[' + tagName;
        while (this.pos < this.source.length) {
            const ch = this.advance();
            value += ch;
            if (value.endsWith('/]')) {
                break;
            }
            if (ch === ']') {
                // Could be block start, let parser handle it if it's supposed to be self-closing
                break;
            }
        }
        this.addToken(type, value, startLine, startColumn);
    }

    private scanText(startLine: number, startColumn: number): void {
        let text = '';
        while (this.pos < this.source.length) {
            const ch = this.peek();
            // Stop at special characters
            if (ch === '[' || (ch === '%' && this.peekNext() === ']') || (ch === '-' && this.peekNext() === '-' && this.peekAt(2) === ']')) {
                break;
            }
            text += this.advance();
        }
        if (text.length > 0) {
            this.addToken(TokenType.TEXT, text, startLine, startColumn);
        }
    }

    private peek(): string {
        return this.source[this.pos] || '\0';
    }

    private peekNext(): string {
        return this.source[this.pos + 1] || '\0';
    }

    private peekAt(offset: number): string {
        return this.source[this.pos + offset] || '\0';
    }

    private advance(): string {
        const ch = this.source[this.pos++];
        if (ch === '\n') {
            this.line++;
            this.column = 1;
        } else {
            this.column++;
        }
        return ch;
    }

    private match(expected: string): boolean {
        if (this.source.substring(this.pos, this.pos + expected.length) === expected) {
            for (let i = 0; i < expected.length; i++) {
                this.advance();
            }
            return true;
        }
        return false;
    }

    private addToken(type: TokenType, value: string, line: number, column: number): void {
        this.tokens.push({
            type,
            value,
            line,
            column,
            length: value.length
        });
    }
}
