import { CompletionItemKind, } from 'vscode-languageserver-types';
import { isLabelLeaf } from '../types';
import { findParent } from '../helpers';
import { NodePatternContext, PatternElementContext, QuantifierContext, RelationshipPatternContext, } from '../generated-parser/CypherCmdParser';
import { backtickIfNeeded } from './autocompletionHelpers';
import { _internalFeatureFlags } from '../featureFlags';
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);
export 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 intersectChildren(connectedLabels, children) {
    let intersection = undefined;
    children.forEach((c) => {
        intersection = intersection
            ? (intersection = intersection.intersection(walkLabelTree(connectedLabels, c)))
            : walkLabelTree(connectedLabels, c);
    });
    return intersection ?? new Set();
}
function uniteChildren(connectedLabels, children) {
    let union = new Set();
    children.forEach((c) => (union = union.union(walkLabelTree(connectedLabels, c))));
    return union;
}
function walkLabelTree(connectedLabels, labelTree) {
    if (isLabelLeaf(labelTree)) {
        return connectedLabels.get(labelTree.value);
    }
    else if (labelTree.andOr == 'and') {
        return intersectChildren(connectedLabels, labelTree.children);
    }
    else {
        return uniteChildren(connectedLabels, labelTree.children);
    }
}
function getRelsFromNodesSet(dbSchema) {
    if (dbSchema.graphSchema) {
        const relsFromLabelsSet = new Map();
        dbSchema.graphSchema.forEach((rel) => {
            let currentFromLabelEntry = relsFromLabelsSet.get(rel.from);
            let currentToLabelEntry = relsFromLabelsSet.get(rel.to);
            if (!currentFromLabelEntry) {
                relsFromLabelsSet.set(rel.from, new Set());
                currentFromLabelEntry = relsFromLabelsSet.get(rel.from);
            }
            if (!currentToLabelEntry) {
                relsFromLabelsSet.set(rel.to, new Set());
                currentToLabelEntry = relsFromLabelsSet.get(rel.to);
            }
            currentToLabelEntry.add(rel.relType);
            currentFromLabelEntry.add(rel.relType);
        });
        return relsFromLabelsSet;
    }
    return undefined;
}
function getNodesFromRelsSet(dbSchema) {
    if (dbSchema.graphSchema) {
        const nodesFromRelsSet = new Map();
        dbSchema.graphSchema.forEach((rel) => {
            if (!nodesFromRelsSet.has(rel.relType)) {
                nodesFromRelsSet.set(rel.relType, new Set());
            }
            const currentRelEntry = nodesFromRelsSet.get(rel.relType);
            currentRelEntry.add(rel.to);
            currentRelEntry.add(rel.from);
        });
        return nodesFromRelsSet;
    }
    return undefined;
}
export function completeNodeLabel(dbSchema, parsingResult, symbolsInfo) {
    if (!_internalFeatureFlags.schemaBasedPatternCompletions ||
        dbSchema.graphSchema === undefined) {
        return allLabelCompletions(dbSchema);
    }
    const callContext = findParent(parsingResult.stopNode.parentCtx, (x) => x instanceof PatternElementContext);
    if (callContext instanceof PatternElementContext) {
        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) {
            // limitation: not checking anonymous variables
            const variable = lastValidElement.variable();
            if (variable === null) {
                return allLabelCompletions(dbSchema);
            }
            const foundVariable = symbolsInfo?.symbolTables
                ?.flat()
                .find((entry) => entry.references.includes(variable.start.start));
            if (foundVariable === undefined ||
                ('children' in foundVariable.labels &&
                    foundVariable.labels.children.length == 0)) {
                return allLabelCompletions(dbSchema);
            }
            // limitation: not direction-aware (ignores <- vs ->)
            // limitation: not checking node label repetition
            const nodesFromRelsSet = getNodesFromRelsSet(dbSchema);
            const rels = walkLabelTree(nodesFromRelsSet, foundVariable.labels);
            return labelsToCompletions(Array.from(rels));
        }
    }
    return allLabelCompletions(dbSchema);
}
export function completeRelationshipType(dbSchema, parsingResult, symbolsInfo) {
    if (!_internalFeatureFlags.schemaBasedPatternCompletions ||
        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) {
        const lastValidElement = patternContext.children
            .toReversed()
            .find((child) => {
            if (child instanceof NodePatternContext) {
                if (child.exception === null) {
                    return true;
                }
            }
        });
        // limitation: bailing out on quantifiers
        if (lastValidElement instanceof QuantifierContext) {
            return allReltypeCompletions(dbSchema);
        }
        if (lastValidElement instanceof NodePatternContext) {
            // limitation: not checking anonymous variables
            const variable = lastValidElement.variable();
            if (variable === null) {
                return allReltypeCompletions(dbSchema);
            }
            const foundVariable = symbolsInfo?.symbolTables
                ?.flat()
                .find((entry) => entry.references.includes(variable.start.start));
            if (foundVariable === undefined ||
                ('children' in foundVariable.labels &&
                    foundVariable.labels.children.length == 0)) {
                return allReltypeCompletions(dbSchema);
            }
            // limitation: not direction-aware (ignores <- vs ->)
            // limitation: not checking relationship type repetition
            const relsFromLabelsSet = getRelsFromNodesSet(dbSchema);
            const rels = walkLabelTree(relsFromLabelsSet, foundVariable.labels);
            return reltypesToCompletions(Array.from(rels));
        }
    }
    return allReltypeCompletions(dbSchema);
}
//# sourceMappingURL=schemaBasedCompletions.js.map