"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Tokenizer = void 0;
const scanner_1 = require("./scanner");
const detector_1 = require("./detector");
const exceptions_1 = require("./exceptions");
const types_1 = require("./types");
class Tokenizer {
    template;
    tagsDef;
    options;
    tokens = [];
    tagStatement = null;
    mustacheStatement = null;
    line = 0;
    isLastLineATag = false;
    dropNewLine = false;
    openedTags = [];
    constructor(template, tagsDef, options) {
        this.template = template;
        this.tagsDef = tagsDef;
        this.options = options;
    }
    getRawNode(text) {
        return {
            type: 'raw',
            value: text,
            filename: this.options.filename,
            line: this.line,
        };
    }
    getNewLineNode(line) {
        return {
            type: 'newline',
            filename: this.options.filename,
            line: (line || this.line) - 1,
        };
    }
    getTagNode(tag, jsArg, closingLoc) {
        return {
            type: tag.escaped ? types_1.TagTypes.ETAG : types_1.TagTypes.TAG,
            filename: tag.filename,
            properties: {
                name: tag.name,
                jsArg: jsArg,
                selfclosed: tag.selfclosed,
            },
            loc: {
                start: {
                    line: tag.line,
                    col: tag.col,
                },
                end: closingLoc,
            },
            children: [],
        };
    }
    consumeTag(tag, jsArg, loc) {
        if (tag.block && !tag.selfclosed) {
            this.openedTags.push(this.getTagNode(tag, jsArg, loc));
        }
        else {
            this.consumeNode(this.getTagNode(tag, jsArg, loc));
        }
    }
    handleTagOpening(line, tag) {
        if (tag.seekable && !tag.hasBrace) {
            throw (0, exceptions_1.unopenedParen)({ line: tag.line, col: tag.col }, tag.filename);
        }
        if (!tag.seekable) {
            this.consumeTag(tag, '', { line: tag.line, col: tag.col });
            if (tag.noNewLine || line.endsWith('~')) {
                this.dropNewLine = true;
            }
            return;
        }
        tag.col += 1;
        this.tagStatement = {
            tag: tag,
            scanner: new scanner_1.Scanner(')', ['(', ')'], this.line, tag.col),
        };
        this.feedCharsToCurrentTag(line.slice(tag.col));
    }
    feedCharsToCurrentTag(content) {
        const { tag, scanner } = this.tagStatement;
        scanner.scan(content);
        if (!scanner.closed) {
            return;
        }
        this.consumeTag(tag, scanner.match, scanner.loc);
        if (scanner.leftOver.trim() === '~') {
            this.tagStatement = null;
            this.dropNewLine = true;
            return;
        }
        if (scanner.leftOver.trim()) {
            throw (0, exceptions_1.cannotSeekStatement)(scanner.leftOver, scanner.loc, tag.filename);
        }
        if (tag.noNewLine) {
            this.dropNewLine = true;
        }
        this.tagStatement = null;
    }
    getMustacheType(mustache) {
        if (mustache.safe) {
            return mustache.escaped ? types_1.MustacheTypes.ESMUSTACHE : types_1.MustacheTypes.SMUSTACHE;
        }
        return mustache.escaped ? types_1.MustacheTypes.EMUSTACHE : types_1.MustacheTypes.MUSTACHE;
    }
    getMustacheNode(mustache, jsArg, closingLoc) {
        return {
            type: this.getMustacheType(mustache),
            filename: mustache.filename,
            properties: {
                jsArg: jsArg,
            },
            loc: {
                start: {
                    line: mustache.line,
                    col: mustache.col,
                },
                end: closingLoc,
            },
        };
    }
    getCommentNode(comment, value, closingLoc) {
        return {
            type: 'comment',
            filename: comment.filename,
            value: value,
            loc: {
                start: {
                    line: comment.line,
                    col: comment.col,
                },
                end: closingLoc,
            },
        };
    }
    handleMustacheOpening(line, mustache) {
        const pattern = mustache.isComment ? '--}}' : mustache.safe ? '}}}' : '}}';
        const textLeftIndex = mustache.isComment || !mustache.escaped ? mustache.realCol : mustache.realCol - 1;
        if (textLeftIndex > 0) {
            this.consumeNode(this.getRawNode(line.slice(0, textLeftIndex)));
        }
        mustache.col += pattern.length;
        mustache.realCol += pattern.length;
        this.mustacheStatement = {
            mustache,
            scanner: new scanner_1.Scanner(pattern, ['{', '}'], mustache.line, mustache.col),
        };
        this.feedCharsToCurrentMustache(line.slice(mustache.realCol));
    }
    feedCharsToCurrentMustache(content) {
        const { mustache, scanner } = this.mustacheStatement;
        scanner.scan(content);
        if (!scanner.closed) {
            return;
        }
        if (mustache.isComment) {
            this.consumeNode(this.getCommentNode(mustache, scanner.match, scanner.loc));
        }
        else {
            this.consumeNode(this.getMustacheNode(mustache, scanner.match, scanner.loc));
        }
        if (scanner.leftOver.trim()) {
            const anotherMustache = (0, detector_1.getMustache)(scanner.leftOver, this.options.filename, scanner.loc.line, scanner.loc.col);
            if (anotherMustache) {
                this.handleMustacheOpening(scanner.leftOver, anotherMustache);
                return;
            }
            this.consumeNode(this.getRawNode(scanner.leftOver));
        }
        this.mustacheStatement = null;
    }
    isClosingTag(line) {
        if (!this.openedTags.length) {
            return false;
        }
        line = line.trim();
        const recentTag = this.openedTags[this.openedTags.length - 1];
        const endStatement = `@end${recentTag.properties.name}`;
        return (line === endStatement || line === `${endStatement}~` || line === '@end' || line === '@end~');
    }
    consumeNode(tag) {
        if (this.openedTags.length) {
            this.openedTags[this.openedTags.length - 1].children.push(tag);
            return;
        }
        this.tokens.push(tag);
    }
    pushNewLine(line) {
        if ((line || this.line) === 1) {
            return;
        }
        if (this.dropNewLine) {
            this.dropNewLine = false;
            return;
        }
        this.consumeNode(this.getNewLineNode(line));
    }
    processText(line) {
        if (typeof this.options.onLine === 'function') {
            line = this.options.onLine(line);
        }
        if (this.tagStatement) {
            this.feedCharsToCurrentTag('\n');
            this.feedCharsToCurrentTag(line);
            return;
        }
        if (this.mustacheStatement) {
            this.feedCharsToCurrentMustache('\n');
            this.feedCharsToCurrentMustache(line);
            return;
        }
        if (this.isClosingTag(line)) {
            this.consumeNode(this.openedTags.pop());
            if (line.endsWith('~')) {
                this.dropNewLine = true;
            }
            return;
        }
        const tag = (0, detector_1.getTag)(line, this.options.filename, this.line, 0, this.tagsDef, this.options.claimTag);
        if (tag) {
            if (this.isLastLineATag) {
                this.pushNewLine();
            }
            this.isLastLineATag = true;
            this.handleTagOpening(line, tag);
            return;
        }
        this.isLastLineATag = false;
        const mustache = (0, detector_1.getMustache)(line, this.options.filename, this.line, 0);
        if (mustache) {
            this.pushNewLine();
            this.handleMustacheOpening(line, mustache);
            return;
        }
        this.pushNewLine();
        this.consumeNode(this.getRawNode(line));
    }
    checkForErrors() {
        if (this.tagStatement) {
            const { tag } = this.tagStatement;
            throw (0, exceptions_1.unclosedParen)({ line: tag.line, col: tag.col }, tag.filename);
        }
        if (this.mustacheStatement) {
            const { mustache } = this.mustacheStatement;
            throw (0, exceptions_1.unclosedCurlyBrace)({ line: mustache.line, col: mustache.col }, mustache.filename);
        }
        if (this.openedTags.length) {
            const openedTag = this.openedTags[this.openedTags.length - 1];
            throw (0, exceptions_1.unclosedTag)(openedTag.properties.name, openedTag.loc.start, openedTag.filename);
        }
    }
    parse() {
        const lines = this.template.split(/\r\n|\r|\n/g);
        const linesLength = lines.length;
        while (this.line < linesLength) {
            const line = lines[this.line];
            this.line++;
            this.processText(line);
        }
        this.checkForErrors();
    }
}
exports.Tokenizer = Tokenizer;
