import { ParseTreeWalker } from 'antlr4';
import { SemanticTokenTypes, } from 'vscode-languageserver-types';
import CypherLexer from '../generated-parser/CypherCmdLexer';
import CypherParserListener from '../generated-parser/CypherCmdParserListener';
import { CypherTokenType } from '../lexerSymbols';
import { parserWrapper } from '../parserWrapper';
import { BracketType, computeTokenKey, getCypherTokenType, getTokenPosition, removeOverlappingTokens, shouldAssignTokenType, sortTokens, toParsedTokens, } from './syntaxColouringHelpers';
export const syntaxColouringLegend = {
    tokenModifiers: [],
    tokenTypes: Object.keys(SemanticTokenTypes),
};
const semanticTokenTypesNumber = new Map(syntaxColouringLegend.tokenTypes.map((tokenType, i) => [tokenType, i]));
export function mapCypherToSemanticTokenIndex(cypherTokenType) {
    const tokenMappings = {
        [CypherTokenType.comment]: SemanticTokenTypes.comment,
        [CypherTokenType.predicateFunction]: SemanticTokenTypes.function,
        [CypherTokenType.keyword]: SemanticTokenTypes.keyword,
        [CypherTokenType.keywordLiteral]: SemanticTokenTypes.string,
        [CypherTokenType.stringLiteral]: SemanticTokenTypes.string,
        [CypherTokenType.numberLiteral]: SemanticTokenTypes.number,
        [CypherTokenType.booleanLiteral]: SemanticTokenTypes.number,
        [CypherTokenType.operator]: SemanticTokenTypes.operator,
        [CypherTokenType.separator]: SemanticTokenTypes.operator,
        [CypherTokenType.punctuation]: SemanticTokenTypes.operator,
        [CypherTokenType.paramDollar]: SemanticTokenTypes.namespace,
        [CypherTokenType.paramValue]: SemanticTokenTypes.parameter,
        [CypherTokenType.property]: SemanticTokenTypes.property,
        [CypherTokenType.setting]: SemanticTokenTypes.enum,
        [CypherTokenType.settingValue]: SemanticTokenTypes.enumMember,
        [CypherTokenType.label]: SemanticTokenTypes.type,
        [CypherTokenType.variable]: SemanticTokenTypes.variable,
        [CypherTokenType.symbolicName]: SemanticTokenTypes.variable,
        [CypherTokenType.consoleCommand]: SemanticTokenTypes.macro,
    };
    const semanticTokenType = tokenMappings[cypherTokenType];
    if (semanticTokenType) {
        return semanticTokenTypesNumber.get(semanticTokenType);
    }
    return undefined;
}
class SyntaxHighlighter extends CypherParserListener {
    colouredTokens = new Map();
    constructor(colouredTokens) {
        super();
        this.colouredTokens = colouredTokens;
    }
    addToken(token, tokenType, tokenStr) {
        if (token.start >= 0) {
            const tokenPosition = getTokenPosition(token);
            toParsedTokens(tokenPosition, tokenType, tokenStr, token).forEach((token) => {
                const tokenKey = computeTokenKey(token.position, token.token.length);
                this.colouredTokens.set(tokenKey, token);
            });
        }
    }
    exitCypherOptionValue = (ctx) => {
        this.addToken(ctx.start, CypherTokenType.settingValue, ctx.getText());
    };
    exitCypherOptionName = (ctx) => {
        this.addToken(ctx.start, CypherTokenType.setting, ctx.getText());
    };
    exitLabelName = (ctx) => {
        this.addToken(ctx.start, CypherTokenType.label, ctx.getText());
    };
    exitLabelType = (ctx) => {
        const labelName = ctx.symbolicNameString()?.start;
        if (labelName) {
            this.addToken(labelName, CypherTokenType.label, labelName.text);
        }
    };
    exitLabelOrRelType = (ctx) => {
        // Error recovery can insert a LabelOrRelType node with no text
        // See for example CREATE CONSTRAINT FOR (node)
        const labelName = ctx.symbolicNameString()?.start;
        if (labelName) {
            this.addToken(labelName, CypherTokenType.label, labelName.text);
        }
    };
    exitLeftArrow = (ctx) => {
        this.addToken(ctx.start, CypherTokenType.separator, ctx.getText());
    };
    exitArrowLine = (ctx) => {
        this.addToken(ctx.start, CypherTokenType.separator, ctx.getText());
    };
    exitRightArrow = (ctx) => {
        this.addToken(ctx.start, CypherTokenType.separator, ctx.getText());
    };
    exitFunctionName = (ctx) => {
        this.colourMethodName(ctx, CypherTokenType.function);
    };
    exitProcedureName = (ctx) => {
        this.colourMethodName(ctx, CypherTokenType.procedure);
    };
    colourMethodName(ctx, tokenType) {
        const namespace = ctx.namespace();
        namespace.symbolicNameString_list().forEach((namespaceName) => {
            this.addToken(namespaceName.start, tokenType, namespaceName.getText());
        });
        const nameOfMethod = ctx.symbolicNameString();
        this.addToken(nameOfMethod.start, tokenType, nameOfMethod.getText());
    }
    colourPredicateFunction = (ctx) => {
        this.addToken(ctx.symbol, CypherTokenType.predicateFunction, ctx.getText());
    };
    exitVariable = (ctx) => {
        this.addToken(ctx.start, CypherTokenType.variable, ctx.getText());
    };
    exitParameterName = (ctx) => {
        this.addToken(ctx.start, CypherTokenType.variable, ctx.getText());
    };
    exitProcedureResultItem = (ctx) => {
        this.addToken(ctx.start, CypherTokenType.variable, ctx.getText());
    };
    exitPropertyKeyName = (ctx) => {
        // FIXME Is this correct in this case for all cases, not just simple properties?
        this.addToken(ctx.start, CypherTokenType.property, ctx.getText());
    };
    // TODO Do we need this one and the one below?
    exitStringLiteral = (ctx) => {
        this.addToken(ctx.start, CypherTokenType.stringLiteral, ctx.getText());
    };
    exitStringsLiteral = (ctx) => {
        this.addToken(ctx.start, CypherTokenType.stringLiteral, ctx.getText());
    };
    exitBooleanLiteral = (ctx) => {
        // Normally booleans are coloured as numbers in other languages
        this.addToken(ctx.start, CypherTokenType.booleanLiteral, ctx.getText());
    };
    exitNumberLiteral = (ctx) => {
        this.addToken(ctx.start, CypherTokenType.numberLiteral, ctx.getText());
    };
    exitKeywordLiteral = (ctx) => {
        this.addToken(ctx.start, CypherTokenType.keywordLiteral, ctx.getText());
    };
    exitParameter = (ctx) => {
        const dollar = ctx.DOLLAR();
        const parameterName = ctx.parameterName();
        this.addToken(dollar.symbol, CypherTokenType.paramDollar, dollar.getText());
        this.addToken(parameterName.start, CypherTokenType.paramValue, parameterName.getText());
    };
    exitListItemsPredicate = (ctx) => {
        if (ctx.ANY())
            this.colourPredicateFunction(ctx.ANY());
        if (ctx.ALL())
            this.colourPredicateFunction(ctx.ALL());
        if (ctx.NONE())
            this.colourPredicateFunction(ctx.NONE());
        if (ctx.SINGLE())
            this.colourPredicateFunction(ctx.SINGLE());
    };
    exitReduceExpression = (ctx) => {
        this.colourPredicateFunction(ctx.REDUCE());
    };
    exitSymbolicNameString = (ctx) => {
        this.addToken(ctx.start, CypherTokenType.symbolicName, ctx.getText());
    };
    // Fix coloring of colon in console commands (operator -> consoleCommand)
    exitConsoleCommand = (ctx) => {
        const colon = ctx.COLON();
        this.addToken(colon.symbol, CypherTokenType.consoleCommand, colon.getText());
    };
    // console commands that clash with cypher keywords
    exitUseCompletionRule = (ctx) => {
        const use = ctx.USE();
        this.addToken(use.symbol, CypherTokenType.consoleCommand, use.getText());
    };
    exitServerCompletionRule = (ctx) => {
        const server = ctx.SERVER();
        this.addToken(server.symbol, CypherTokenType.consoleCommand, server.getText());
    };
    exitParamsArgs = (ctx) => {
        const clear = ctx.CLEAR();
        if (clear) {
            this.addToken(clear.symbol, CypherTokenType.consoleCommand, clear.getText());
        }
        const list = ctx.listCompletionRule()?.LIST();
        if (list) {
            this.addToken(list.symbol, CypherTokenType.consoleCommand, list.getText());
        }
    };
    exitAccessModeArgs = (ctx) => {
        const read = ctx.readCompletionRule()?.READ();
        if (read) {
            this.addToken(read.symbol, CypherTokenType.consoleCommand, read.getText());
        }
        const write = ctx.writeCompletionRule()?.WRITE();
        if (write) {
            this.addToken(write.symbol, CypherTokenType.consoleCommand, write.getText());
        }
    };
}
function colourLexerTokens(tokens) {
    const result = new Map();
    const bracketsLevel = new Map([
        [BracketType.curly, -1],
        [BracketType.bracket, -1],
        [BracketType.parenthesis, -1],
    ]);
    let i = 0;
    let moreToProcess = true;
    while (i < tokens.length && moreToProcess) {
        const token = tokens.at(i);
        const nextToken = tokens.at(i + 1);
        if (shouldAssignTokenType(token)) {
            const { tokenType, finished: tokenFinished } = getCypherTokenType(token, nextToken);
            const tokenPosition = getTokenPosition(token);
            let tokenStr = token.text ?? '';
            if (!tokenFinished) {
                tokens.slice(i + 1, tokens.length).forEach((nextToken) => {
                    if (nextToken.type !== CypherLexer.EOF) {
                        tokenStr += nextToken.text;
                    }
                });
                moreToProcess = false;
            }
            toParsedTokens(tokenPosition, tokenType, tokenStr, token, bracketsLevel).forEach((t) => {
                const tokenKey = computeTokenKey(t.position, t.length);
                result.set(tokenKey, t);
            });
        }
        ++i;
    }
    return result;
}
export function applySyntaxColouring(wholeFileText) {
    const parsingResult = parserWrapper.parse(wholeFileText);
    const statements = parsingResult.statementsParsing;
    /* Get a second pass at the colouring correcting the colours
       using structural information from the parsing tree
    
       This allows to correctly colour things that are
       recognized as keywords by the lexer in positions
       where they are not keywords (e.g. MATCH (MATCH: MATCH))
    */
    const allColouredTokens = new Map();
    statements.forEach((statement) => {
        const tokens = statement.tokens;
        // Get a first pass at the colouring using only the lexer
        const lexerTokens = colourLexerTokens(tokens);
        const treeSyntaxHighlighter = new SyntaxHighlighter(lexerTokens);
        ParseTreeWalker.DEFAULT.walk(treeSyntaxHighlighter, statement.ctx);
        treeSyntaxHighlighter.colouredTokens.forEach((value, key) => allColouredTokens.set(key, value));
    });
    // When we push to the builder, tokens need to be sorted in ascending
    // starting position i.e. as we find them when we read them from left
    // to right, and from top to bottom in the file
    //
    // After that we should remove overlapping tokens. We need this because
    // certain tokens are double coloured by the lexer and the parsing tree, but
    // their positions in the map of Position -> ParsedToken are different
    //
    // Example: -1 would be split as [-, UNSIGNED_DECIMAL_INTEGER] by the lexer
    // (2 tokens), but as a unique numeric literal by the parsing tree
    const result = removeOverlappingTokens(sortTokens(Array.from(allColouredTokens.values())));
    return result;
}
//# sourceMappingURL=syntaxColouring.js.map