
import { CharCode } from '../../../base/common/charCode.js';
import { illegalState } from '../../../base/common/errors.js';
import { localize } from '../../../nls.js';

var TokenType;
(function (TokenType) {
    TokenType[TokenType["LParen"] = 0] = "LParen";
    TokenType[TokenType["RParen"] = 1] = "RParen";
    TokenType[TokenType["Neg"] = 2] = "Neg";
    TokenType[TokenType["Eq"] = 3] = "Eq";
    TokenType[TokenType["NotEq"] = 4] = "NotEq";
    TokenType[TokenType["Lt"] = 5] = "Lt";
    TokenType[TokenType["LtEq"] = 6] = "LtEq";
    TokenType[TokenType["Gt"] = 7] = "Gt";
    TokenType[TokenType["GtEq"] = 8] = "GtEq";
    TokenType[TokenType["RegexOp"] = 9] = "RegexOp";
    TokenType[TokenType["RegexStr"] = 10] = "RegexStr";
    TokenType[TokenType["True"] = 11] = "True";
    TokenType[TokenType["False"] = 12] = "False";
    TokenType[TokenType["In"] = 13] = "In";
    TokenType[TokenType["Not"] = 14] = "Not";
    TokenType[TokenType["And"] = 15] = "And";
    TokenType[TokenType["Or"] = 16] = "Or";
    TokenType[TokenType["Str"] = 17] = "Str";
    TokenType[TokenType["QuotedStr"] = 18] = "QuotedStr";
    TokenType[TokenType["Error"] = 19] = "Error";
    TokenType[TokenType["EOF"] = 20] = "EOF";
})(TokenType || (TokenType = {}));
function hintDidYouMean(...meant) {
    switch (meant.length) {
        case 1:
            return localize(1689, "Did you mean {0}?", meant[0]);
        case 2:
            return localize(1690, "Did you mean {0} or {1}?", meant[0], meant[1]);
        case 3:
            return localize(1691, "Did you mean {0}, {1} or {2}?", meant[0], meant[1], meant[2]);
        default:
            return undefined;
    }
}
const hintDidYouForgetToOpenOrCloseQuote = ( localize(1692, "Did you forget to open or close the quote?"));
const hintDidYouForgetToEscapeSlash = ( localize(
    1693,
    "Did you forget to escape the '/' (slash) character? Put two backslashes before it to escape, e.g., '\\\\/\'."
));
class Scanner {
    constructor() {
        this._input = '';
        this._start = 0;
        this._current = 0;
        this._tokens = [];
        this._errors = [];
        this.stringRe = /[a-zA-Z0-9_<>\-\./\\:\*\?\+\[\]\^,#@;"%\$\p{L}-]+/uy;
    }
    static getLexeme(token) {
        switch (token.type) {
            case TokenType.LParen:
                return '(';
            case TokenType.RParen:
                return ')';
            case TokenType.Neg:
                return '!';
            case TokenType.Eq:
                return token.isTripleEq ? '===' : '==';
            case TokenType.NotEq:
                return token.isTripleEq ? '!==' : '!=';
            case TokenType.Lt:
                return '<';
            case TokenType.LtEq:
                return '<=';
            case TokenType.Gt:
                return '>=';
            case TokenType.GtEq:
                return '>=';
            case TokenType.RegexOp:
                return '=~';
            case TokenType.RegexStr:
                return token.lexeme;
            case TokenType.True:
                return 'true';
            case TokenType.False:
                return 'false';
            case TokenType.In:
                return 'in';
            case TokenType.Not:
                return 'not';
            case TokenType.And:
                return '&&';
            case TokenType.Or:
                return '||';
            case TokenType.Str:
                return token.lexeme;
            case TokenType.QuotedStr:
                return token.lexeme;
            case TokenType.Error:
                return token.lexeme;
            case TokenType.EOF:
                return 'EOF';
            default:
                throw illegalState(`unhandled token type: ${JSON.stringify(token)}; have you forgotten to add a case?`);
        }
    }
    static { this._regexFlags = ( new Set(( ['i', 'g', 's', 'm', 'y', 'u'].map(ch => ch.charCodeAt(0))))); }
    static { this._keywords = ( new Map([
        ['not', TokenType.Not],
        ['in', TokenType.In],
        ['false', TokenType.False],
        ['true', TokenType.True],
    ])); }
    get errors() {
        return this._errors;
    }
    reset(value) {
        this._input = value;
        this._start = 0;
        this._current = 0;
        this._tokens = [];
        this._errors = [];
        return this;
    }
    scan() {
        while (!this._isAtEnd()) {
            this._start = this._current;
            const ch = this._advance();
            switch (ch) {
                case CharCode.OpenParen:
                    this._addToken(TokenType.LParen);
                    break;
                case CharCode.CloseParen:
                    this._addToken(TokenType.RParen);
                    break;
                case CharCode.ExclamationMark:
                    if (this._match(CharCode.Equals)) {
                        const isTripleEq = this._match(CharCode.Equals);
                        this._tokens.push({ type: TokenType.NotEq, offset: this._start, isTripleEq });
                    }
                    else {
                        this._addToken(TokenType.Neg);
                    }
                    break;
                case CharCode.SingleQuote:
                    this._quotedString();
                    break;
                case CharCode.Slash:
                    this._regex();
                    break;
                case CharCode.Equals:
                    if (this._match(CharCode.Equals)) {
                        const isTripleEq = this._match(CharCode.Equals);
                        this._tokens.push({ type: TokenType.Eq, offset: this._start, isTripleEq });
                    }
                    else if (this._match(CharCode.Tilde)) {
                        this._addToken(TokenType.RegexOp);
                    }
                    else {
                        this._error(hintDidYouMean('==', '=~'));
                    }
                    break;
                case CharCode.LessThan:
                    this._addToken(this._match(CharCode.Equals) ? TokenType.LtEq : TokenType.Lt);
                    break;
                case CharCode.GreaterThan:
                    this._addToken(this._match(CharCode.Equals) ? TokenType.GtEq : TokenType.Gt);
                    break;
                case CharCode.Ampersand:
                    if (this._match(CharCode.Ampersand)) {
                        this._addToken(TokenType.And);
                    }
                    else {
                        this._error(hintDidYouMean('&&'));
                    }
                    break;
                case CharCode.Pipe:
                    if (this._match(CharCode.Pipe)) {
                        this._addToken(TokenType.Or);
                    }
                    else {
                        this._error(hintDidYouMean('||'));
                    }
                    break;
                case CharCode.Space:
                case CharCode.CarriageReturn:
                case CharCode.Tab:
                case CharCode.LineFeed:
                case CharCode.NoBreakSpace:
                    break;
                default:
                    this._string();
            }
        }
        this._start = this._current;
        this._addToken(TokenType.EOF);
        return Array.from(this._tokens);
    }
    _match(expected) {
        if (this._isAtEnd()) {
            return false;
        }
        if (this._input.charCodeAt(this._current) !== expected) {
            return false;
        }
        this._current++;
        return true;
    }
    _advance() {
        return this._input.charCodeAt(this._current++);
    }
    _peek() {
        return this._isAtEnd() ? CharCode.Null : this._input.charCodeAt(this._current);
    }
    _addToken(type) {
        this._tokens.push({ type, offset: this._start });
    }
    _error(additional) {
        const offset = this._start;
        const lexeme = this._input.substring(this._start, this._current);
        const errToken = { type: TokenType.Error, offset: this._start, lexeme };
        this._errors.push({ offset, lexeme, additionalInfo: additional });
        this._tokens.push(errToken);
    }
    _string() {
        this.stringRe.lastIndex = this._start;
        const match = this.stringRe.exec(this._input);
        if (match) {
            this._current = this._start + match[0].length;
            const lexeme = this._input.substring(this._start, this._current);
            const keyword = Scanner._keywords.get(lexeme);
            if (keyword) {
                this._addToken(keyword);
            }
            else {
                this._tokens.push({ type: TokenType.Str, lexeme, offset: this._start });
            }
        }
    }
    _quotedString() {
        while (this._peek() !== CharCode.SingleQuote && !this._isAtEnd()) {
            this._advance();
        }
        if (this._isAtEnd()) {
            this._error(hintDidYouForgetToOpenOrCloseQuote);
            return;
        }
        this._advance();
        this._tokens.push({ type: TokenType.QuotedStr, lexeme: this._input.substring(this._start + 1, this._current - 1), offset: this._start + 1 });
    }
    _regex() {
        let p = this._current;
        let inEscape = false;
        let inCharacterClass = false;
        while (true) {
            if (p >= this._input.length) {
                this._current = p;
                this._error(hintDidYouForgetToEscapeSlash);
                return;
            }
            const ch = this._input.charCodeAt(p);
            if (inEscape) {
                inEscape = false;
            }
            else if (ch === CharCode.Slash && !inCharacterClass) {
                p++;
                break;
            }
            else if (ch === CharCode.OpenSquareBracket) {
                inCharacterClass = true;
            }
            else if (ch === CharCode.Backslash) {
                inEscape = true;
            }
            else if (ch === CharCode.CloseSquareBracket) {
                inCharacterClass = false;
            }
            p++;
        }
        while (p < this._input.length && ( Scanner._regexFlags.has(this._input.charCodeAt(p)))) {
            p++;
        }
        this._current = p;
        const lexeme = this._input.substring(this._start, this._current);
        this._tokens.push({ type: TokenType.RegexStr, lexeme, offset: this._start });
    }
    _isAtEnd() {
        return this._current >= this._input.length;
    }
}

export { Scanner, TokenType };
