import { CompletionItemKind, InsertTextFormat, } from 'vscode-languageserver-types';
import { isLabelLeaf } from '../types.js';
import { findParent } from '../helpers.js';
import { NodePatternContext, PatternElementContext, QuantifierContext, RelationshipPatternContext, } from '../generated-parser/CypherCmdParser.js';
import { backtickIfNeeded } from './autocompletionHelpers.js';
import { convertToCNF, isAnyNode, isNotAnyNode, removeInnerAnys, } from '../labelTreeRewriting.js';
import { getNodesFromRelsSet, getRelsFromNodesSets, walkCNFTree, } from '../labelTreeWalking.js';
export function getShortPathCompletions(lastNode, symbolsInfo, dbSchema) {
    const snippetCompletions = [];
    const lastVariable = findLastVariable(lastNode, symbolsInfo);
    if (!lastVariable ||
        !dbSchema.graphSchema ||
        isLabelLeaf(lastVariable.labels) ||
        lastVariable.labels.children.length === 0) {
        return [];
    }
    const { toRels: nodesToRelsSet, fromRels: nodesFromRelsSet } = getNodesFromRelsSet(dbSchema);
    const assumedDirection = lastNode.leftArrow() ? 'outgoing' : 'incoming';
    let cnfTree;
    try {
        const treeWithRewrittenAnys = removeInnerAnys(lastVariable.labels);
        if (isAnyNode(treeWithRewrittenAnys) ||
            isNotAnyNode(treeWithRewrittenAnys)) {
            return [];
        }
        cnfTree = convertToCNF(treeWithRewrittenAnys);
    }
    catch {
        return [];
    }
    const { inLabels, outLabels } = walkCNFTree(nodesToRelsSet, nodesFromRelsSet, cnfTree);
    if (assumedDirection === 'outgoing') {
        for (const outLabel of outLabels) {
            snippetCompletions.push({
                label: '-(:' + outLabel + ')',
                kind: CompletionItemKind.Snippet,
                insertTextFormat: InsertTextFormat.Snippet,
                insertText: '-(${1: }:' + outLabel + ')${2:}',
                detail: 'path template',
            });
        }
    }
    else {
        for (const inLabel of inLabels) {
            snippetCompletions.push({
                label: '->(:' + inLabel + ')',
                kind: CompletionItemKind.Snippet,
                insertTextFormat: InsertTextFormat.Snippet,
                insertText: '->(${1: }:' + inLabel + ')${2:}',
                detail: 'path template',
            });
        }
    }
    return snippetCompletions;
}
export function getPathCompletions(lastNode, symbolsInfo, dbSchema) {
    const snippetCompletions = [];
    const lastVariable = findLastVariable(lastNode, symbolsInfo);
    if (!lastVariable ||
        !dbSchema.graphSchema ||
        isLabelLeaf(lastVariable.labels) ||
        lastVariable.labels.children.length === 0) {
        return [];
    }
    const { toNodes: relsToNodesSet, fromNodes: relsFromNodesSet } = getRelsFromNodesSets(dbSchema);
    let cnfTree;
    try {
        const treeWithRewrittenAnys = removeInnerAnys(lastVariable.labels);
        if (isAnyNode(treeWithRewrittenAnys) ||
            isNotAnyNode(treeWithRewrittenAnys)) {
            return [];
        }
        cnfTree = convertToCNF(treeWithRewrittenAnys);
    }
    catch {
        return [];
    }
    const { inLabels: inRelTypes, outLabels: outRelTypes } = walkCNFTree(relsToNodesSet, relsFromNodesSet, cnfTree);
    const { toRels: nodesToRelsSet, fromRels: nodesFromRelsSet } = getNodesFromRelsSet(dbSchema);
    for (const inRelType of inRelTypes) {
        const { inLabels } = walkCNFTree(nodesToRelsSet, nodesFromRelsSet, {
            condition: 'and',
            children: [{ value: inRelType }],
        });
        for (const inLabel of inLabels) {
            snippetCompletions.push({
                label: '-[:' + inRelType + ']->(:' + inLabel + ')',
                kind: CompletionItemKind.Snippet,
                insertTextFormat: InsertTextFormat.Snippet,
                insertText: '-[${1: }:' + inRelType + ']->(${2: }:' + inLabel + ')${3:}',
                detail: 'path template',
            });
        }
    }
    for (const outRelType of outRelTypes) {
        const { outLabels } = walkCNFTree(nodesToRelsSet, nodesFromRelsSet, {
            condition: 'and',
            children: [{ value: outRelType }],
        });
        for (const outLabel of outLabels) {
            snippetCompletions.push({
                label: '<-[:' + outRelType + ']-(:' + outLabel + ')',
                kind: CompletionItemKind.Snippet,
                insertTextFormat: InsertTextFormat.Snippet,
                insertText: '<-[${1: }:' + outRelType + ']-(${2: }:' + outLabel + ')${3:}',
                detail: 'path template',
            });
        }
    }
    return snippetCompletions;
}
export const labelsToCompletions = (labelNames = []) => labelNames.map((labelName) => {
    const backtickedName = backtickIfNeeded(labelName, 'label');
    const maybeInsertText = backtickedName
        ? { insertText: backtickedName }
        : {};
    const result = {
        label: labelName,
        kind: CompletionItemKind.TypeParameter,
        ...maybeInsertText,
    };
    return result;
});
export const allLabelCompletions = (dbSchema) => labelsToCompletions(dbSchema.labels);
const reltypesToCompletions = (reltypes = []) => reltypes.map((relType) => {
    const backtickedName = backtickIfNeeded(relType, 'relType');
    const maybeInsertText = backtickedName
        ? { insertText: backtickedName }
        : {};
    const result = {
        label: relType,
        kind: CompletionItemKind.TypeParameter,
        ...maybeInsertText,
    };
    return result;
});
export const allReltypeCompletions = (dbSchema) => reltypesToCompletions(dbSchema.relationshipTypes);
function findLastVariable(lastValidElement, symbolsInfo) {
    const variable = lastValidElement.variable();
    const isAnonVar = variable === null;
    const foundVariable = isAnonVar
        ? symbolsInfo?.symbolTables?.flat().find((entry) => 
        // Because the anonymous variable created in the AST is "made up", it doesnt have a position of its own in the query.
        // It therefor inherits the parent-nodes (The NodePattern/RelationshipPattern = lastValidElement) position
        entry.definitionPosition >= lastValidElement.start.start &&
            entry.definitionPosition <= (lastValidElement.stop?.stop ?? -1))
        : symbolsInfo?.symbolTables
            ?.flat()
            .find((entry) => entry.references.includes(variable.start.start));
    return foundVariable;
}
export function completeNodeLabel(dbSchema, parsingResult, symbolsInfo) {
    if (dbSchema.graphSchema === undefined) {
        return allLabelCompletions(dbSchema);
    }
    const callContext = findParent(parsingResult.stopNode.parentCtx, (x) => x instanceof PatternElementContext);
    if (callContext instanceof PatternElementContext && callContext.children) {
        const lastValidElement = callContext.children.toReversed().find((child) => {
            if (child instanceof RelationshipPatternContext) {
                if (child.exception === null) {
                    return true;
                }
            }
        });
        // limitation: bailing out on quantifiers
        if (lastValidElement instanceof QuantifierContext) {
            return allLabelCompletions(dbSchema);
        }
        if (lastValidElement instanceof RelationshipPatternContext) {
            const foundVariable = findLastVariable(lastValidElement, symbolsInfo);
            if (foundVariable === undefined ||
                isLabelLeaf(foundVariable.labels) ||
                foundVariable.labels.condition !== 'and' ||
                foundVariable.labels.children.length === 0) {
                return allLabelCompletions(dbSchema);
            }
            const direction = lastValidElement.leftArrow() && !lastValidElement.rightArrow()
                ? 'outgoing'
                : !lastValidElement.leftArrow() && lastValidElement.rightArrow()
                    ? 'incoming'
                    : 'bidirectional';
            // limitation: not checking node label repetition
            const { toRels: nodesToRelsSet, fromRels: nodesFromRelsSet } = getNodesFromRelsSet(dbSchema);
            let cnfTree;
            try {
                const treeWithRewrittenAnys = removeInnerAnys(foundVariable.labels);
                if (isAnyNode(treeWithRewrittenAnys)) {
                    return allLabelCompletions(dbSchema);
                }
                else if (isNotAnyNode(treeWithRewrittenAnys)) {
                    return [];
                }
                cnfTree = convertToCNF(treeWithRewrittenAnys);
            }
            catch {
                return allLabelCompletions(dbSchema);
            }
            const { inLabels, outLabels } = walkCNFTree(nodesToRelsSet, nodesFromRelsSet, cnfTree);
            const allNodes = direction === 'outgoing'
                ? outLabels
                : direction === 'incoming'
                    ? inLabels
                    : inLabels.union(outLabels);
            return labelsToCompletions(Array.from(allNodes));
        }
    }
    return allLabelCompletions(dbSchema);
}
export function completeRelationshipType(dbSchema, parsingResult, symbolsInfo) {
    if (dbSchema.graphSchema === undefined) {
        return allReltypeCompletions(dbSchema);
    }
    // limitation: not checking PathPatternNonEmptyContext
    // limitation: not handling parenthesized paths
    const patternContext = findParent(parsingResult.stopNode.parentCtx, (x) => x instanceof PatternElementContext);
    if (patternContext instanceof PatternElementContext &&
        patternContext.children) {
        const lastValidElement = patternContext.children
            .toReversed()
            .find((child) => {
            if (child instanceof NodePatternContext) {
                if (child.exception === null) {
                    return true;
                }
            }
        });
        const thisCtx = findParent(parsingResult.stopNode, (x) => x instanceof RelationshipPatternContext);
        let direction = 'bidirectional';
        if (thisCtx instanceof RelationshipPatternContext) {
            direction =
                thisCtx.leftArrow() && !thisCtx.rightArrow()
                    ? 'outgoing'
                    : !thisCtx.leftArrow() && thisCtx.rightArrow()
                        ? 'incoming'
                        : 'bidirectional';
        }
        // limitation: bailing out on quantifiers
        if (lastValidElement instanceof QuantifierContext) {
            return allReltypeCompletions(dbSchema);
        }
        if (lastValidElement instanceof NodePatternContext) {
            const foundVariable = findLastVariable(lastValidElement, symbolsInfo);
            if (foundVariable === undefined ||
                ('children' in foundVariable.labels &&
                    foundVariable.labels.children.length == 0)) {
                return allReltypeCompletions(dbSchema);
            }
            // limitation: not checking relationship type repetition
            const { toNodes: relsToNodesSet, fromNodes: relsFromNodesSet } = getRelsFromNodesSets(dbSchema);
            let cnfTree;
            try {
                const treeWithRewrittenAnys = removeInnerAnys(foundVariable.labels);
                if (isAnyNode(treeWithRewrittenAnys)) {
                    return allReltypeCompletions(dbSchema);
                }
                else if (isNotAnyNode(treeWithRewrittenAnys)) {
                    return [];
                }
                cnfTree = convertToCNF(treeWithRewrittenAnys);
            }
            catch {
                return [];
            }
            const { inLabels, outLabels } = walkCNFTree(relsToNodesSet, relsFromNodesSet, cnfTree);
            const allRels = direction === 'outgoing'
                ? outLabels
                : direction === 'incoming'
                    ? inLabels
                    : inLabels.union(outLabels);
            return reltypesToCompletions(Array.from(allRels));
        }
    }
    return allReltypeCompletions(dbSchema);
}
//# sourceMappingURL=schemaBasedCompletions.js.map