import { CharStreams, CommonTokenStream } from 'antlr4';
import { default as CypherCmdLexer } from '../generated-parser/CypherCmdLexer';
import CypherCmdParser, { EscapedSymbolicNameStringContext, UnescapedSymbolicNameStringContext, } from '../generated-parser/CypherCmdParser';
import { lexerKeywords } from '../lexerSymbols';
import { findParent } from '../helpers';
export const INTERNAL_FORMAT_ERROR_MESSAGE = `
Internal formatting error: An unexpected issue occurred while formatting.
This is likely a bug in the formatter itself. If possible, please report the issue
along with your input on GitHub:
https://github.com/neo4j/cypher-language-support.`.trim();
/**
 * The (default) maximum column width for the formatter. Not a hard limit as overflow
 * is unavoidable in some cases, but we always prefer a solution that doesn't overflow.
 */
export const DEFAULT_MAX_COL = 80;
const traillingCharacters = [
    CypherCmdLexer.SEMICOLON,
    CypherCmdLexer.COMMA,
    CypherCmdLexer.COLON,
    CypherCmdLexer.RPAREN,
    CypherCmdLexer.RBRACKET,
];
export function wantsToBeUpperCase(node) {
    return isKeywordTerminal(node);
}
export function wantsToBeConcatenated(node) {
    return traillingCharacters.includes(node.symbol.type);
}
function isKeywordTerminal(node) {
    return lexerKeywords.includes(node.symbol.type) && !isSymbolicName(node);
}
export function isComment(token) {
    return (token.type === CypherCmdLexer.MULTI_LINE_COMMENT ||
        token.type === CypherCmdLexer.SINGLE_LINE_COMMENT);
}
export const isInlineComment = (chunk) => chunk.comment && chunk.comment.startsWith('/*');
// Variables, functions/procedures namespaces or property names that have the same name as a keyword should not be
// treated as keywords
function isSymbolicName(node) {
    const unescapedSymbolicNameStringParent = findParent(node, (x) => x instanceof UnescapedSymbolicNameStringContext);
    const escapedSymbolicNameStringParent = findParent(node, (x) => x instanceof EscapedSymbolicNameStringContext);
    return !(unescapedSymbolicNameStringParent == null &&
        escapedSymbolicNameStringParent == null);
}
export function getParseTreeAndTokens(query) {
    const inputStream = CharStreams.fromString(query);
    const lexer = new CypherCmdLexer(inputStream);
    const tokens = new CommonTokenStream(lexer);
    const parser = new CypherCmdParser(tokens);
    parser.removeErrorListeners();
    parser.buildParseTrees = true;
    const tree = parser.statementsOrCommands();
    let unParseable;
    let firstUnParseableToken;
    if (tree.exception) {
        const idx = tree.exception.offendingToken.tokenIndex;
        const errorTokens = tokens.tokens.slice(idx);
        const hiddenBefore = (tokens.getHiddenTokensToLeft(idx) || [])
            .map((t) => t.text)
            .join('');
        unParseable =
            hiddenBefore +
                errorTokens
                    .slice(0, -1)
                    .map((t) => t.text)
                    .join('');
        firstUnParseableToken = tree.exception.offendingToken;
    }
    return { tree, tokens, unParseable, firstUnParseableToken };
}
export function findTargetToken(tokens, cursorPosition) {
    let targetToken;
    for (const token of tokens) {
        if (token.channel === 0) {
            targetToken = token;
        }
        if (cursorPosition >= token.start && cursorPosition <= token.stop) {
            return targetToken;
        }
    }
    return false;
}
// These three are helpers for the fillInGroupSizes method to make it more manageable
export function fillInRegularChunkGroupSizes(chunk, activeGroups) {
    const groupsEnding = new Set(chunk.groupsEnding.map((g) => g.id));
    for (const group of activeGroups) {
        if (!chunk.text) {
            throw new Error(INTERNAL_FORMAT_ERROR_MESSAGE);
        }
        group.size += chunk.text.length;
        if (isInlineComment(chunk)) {
            const inlineComment = ' ' + chunk.comment + ' ';
            group.size += inlineComment.length;
            group.dbgText += inlineComment;
        }
        // PERF: Right now we include dbgText always, even though it's only used for debugging.
        // It does not seem to have any significant performance downsides, but only doing so
        // when e.g. a flag is set might be a more prudent choice.
        group.dbgText += chunk.text;
        if (!chunk.noSpace &&
            shouldAddSpace(chunk, chunk) &&
            !isInlineComment(chunk)) {
            group.size++;
            group.dbgText += ' ';
        }
        if (chunk.comment &&
            !groupsEnding.has(group.id) &&
            !isInlineComment(chunk)) {
            group.shouldBreak = true;
        }
    }
}
export function verifyGroupSizes(chunkList) {
    for (const chunk of chunkList) {
        for (const group of chunk.groupsStarting) {
            if (group.size !== group.dbgText.length) {
                throw new Error(INTERNAL_FORMAT_ERROR_MESSAGE);
            }
        }
    }
}
const openingCharacters = [CypherCmdLexer.LPAREN, CypherCmdLexer.LBRACKET];
export function shouldAddSpace(chunk, nextChunk) {
    if (chunk.type === 'SYNTAX_ERROR' || nextChunk?.type === 'SYNTAX_ERROR') {
        return false;
    }
    if (chunk.type === 'REGULAR') {
        if (chunk.noSpace) {
            return false;
        }
        if (chunk.node && openingCharacters.includes(chunk.node.symbol.type)) {
            return false;
        }
    }
    return true;
}
export function getActiveGroups(activeGroups, groupsEnding, chunk) {
    for (const group of chunk.groupsStarting) {
        activeGroups.push(group);
    }
    const newActiveGroups = [];
    for (const group of activeGroups) {
        if (!groupsEnding.has(group.id)) {
            newActiveGroups.push(group);
        }
        else {
            // Trim trailling spaces from groups that are ending
            if (group.dbgText.at(-1) === ' ') {
                group.size--;
                group.dbgText = group.dbgText.slice(0, -1);
            }
        }
    }
    return newActiveGroups;
}
//# sourceMappingURL=formattingHelpers.js.map