UNPKG

@apollo/query-planner

Version:
2,960 lines 144 kB
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.QueryPlanner = exports.compareOptionsComplexityOutOfContext = void 0;
const federation_internals_1 = require("@apollo/federation-internals");
const query_graphs_1 = require("@apollo/query-graphs");
const graphql_1 = require("graphql");
const conditions_1 = require("./conditions");
const config_1 = require("./config");
const generateAllPlans_1 = require("./generateAllPlans");
const QueryPlan_1 = require("./QueryPlan");
const recursiveSelectionsLimit_1 = require("./recursiveSelectionsLimit");
const debug = (0, federation_internals_1.newDebugLogger)('plan');
const SIBLING_TYPENAME_KEY = 'sibling_typename';
const fetchCost = 1000;
const pipeliningCost = 100;
const defaultCostFunction = {
    onFetchGroup: (group) => (fetchCost + group.cost()),
    onConditions: (_, value) => value,
    reduceParallel: (values) => parallelCost(values),
    reduceSequence: (values) => sequenceCost(values),
    reduceDeferred(_, value) {
        return value;
    },
    reduceDefer(nonDeferred, _, deferredValues) {
        return sequenceCost([nonDeferred, parallelCost(deferredValues)]);
    },
};
function parallelCost(values) {
    return sum(values);
}
function sequenceCost(stages) {
    return stages.reduceRight((acc, stage, idx) => (acc + (Math.max(1, idx * pipeliningCost) * stage)), 0);
}
function closedPathToString(p) {
    const pathStr = (0, query_graphs_1.simultaneousPathsToString)(p.paths);
    return p.selection ? `${pathStr} -> ${p.selection}` : pathStr;
}
function flattenClosedPath(p) {
    return p.paths.map((path) => ({ path, selection: p.selection }));
}
function allTailVertices(options) {
    const vertices = new Set();
    for (const option of options) {
        for (const path of option.paths) {
            vertices.add(path.tail);
        }
    }
    return vertices;
}
function selectionIsFullyLocalFromAllVertices(selection, vertices, inconsistentAbstractTypesRuntimes) {
    let _useInconsistentAbstractTypes = undefined;
    const useInconsistentAbstractTypes = () => {
        if (_useInconsistentAbstractTypes === undefined) {
            _useInconsistentAbstractTypes = selection.some((elt) => elt.kind === 'FragmentElement' && !!elt.typeCondition && inconsistentAbstractTypesRuntimes.has(elt.typeCondition.name));
        }
        return _useInconsistentAbstractTypes;
    };
    for (const vertex of vertices) {
        if (vertex.hasReachableCrossSubgraphEdges
            || !(0, federation_internals_1.isCompositeType)(vertex.type)
            || !selection.canRebaseOn(vertex.type)
            || useInconsistentAbstractTypes()) {
            return false;
        }
    }
    return true;
}
function compareOptionsComplexityOutOfContext(opt1, opt2) {
    if (opt1.length === 1) {
        if (opt2.length === 1) {
            return compareSinglePathOptionsComplexityOutOfContext(opt1[0], opt2[0]);
        }
        else {
            return compareSingleVsMultiPathOptionsComplexityOutOfContext(opt1[0], opt2);
        }
    }
    else if (opt2.length === 1) {
        return -compareSingleVsMultiPathOptionsComplexityOutOfContext(opt2[0], opt1);
    }
    return 0;
}
exports.compareOptionsComplexityOutOfContext = compareOptionsComplexityOutOfContext;
function compareSinglePathOptionsComplexityOutOfContext(p1, p2) {
    if (p1.tail.source !== p2.tail.source) {
        const { thisJumps: p1Jumps, thatJumps: p2Jumps } = p1.countSubgraphJumpsAfterLastCommonVertex(p2);
        if (p1Jumps === 0 && p2Jumps > 0) {
            return -1;
        }
        else if (p1Jumps > 0 && p2Jumps === 0) {
            return 1;
        }
        else {
            return 0;
        }
    }
    return 0;
}
function compareSingleVsMultiPathOptionsComplexityOutOfContext(p1, p2s) {
    for (const p2 of p2s) {
        if (compareSinglePathOptionsComplexityOutOfContext(p1, p2) >= 0) {
            return 0;
        }
    }
    return -1;
}
class QueryPlanningTraversal {
    constructor(parameters, selectionSet, startFetchIdGen, hasDefers, rootKind, costFunction, initialContext, typeConditionedFetching, nonLocalSelectionsState, excludedDestinations = [], excludedConditions = []) {
        var _a;
        this.parameters = parameters;
        this.startFetchIdGen = startFetchIdGen;
        this.hasDefers = hasDefers;
        this.rootKind = rootKind;
        this.costFunction = costFunction;
        this.closedBranches = [];
        const { root, federatedQueryGraph } = parameters;
        this.typeConditionedFetching = typeConditionedFetching || false;
        this.isTopLevel = (0, query_graphs_1.isRootVertex)(root);
        this.optionsLimit = (_a = parameters.config.debug) === null || _a === void 0 ? void 0 : _a.pathsLimit;
        this.conditionResolver = (0, query_graphs_1.cachingConditionResolver)((edge, context, excludedEdges, excludedConditions, extras) => this.resolveConditionPlan(edge, context, excludedEdges, excludedConditions, extras));
        const initialPath = query_graphs_1.GraphPath.create(federatedQueryGraph, root);
        const initialOptions = (0, query_graphs_1.createInitialOptions)(initialPath, initialContext, this.conditionResolver, excludedDestinations, excludedConditions, parameters.overrideConditions);
        this.stack = mapOptionsToSelections(selectionSet, initialOptions);
        if (this.parameters.federatedQueryGraph.nonLocalSelectionsMetadata
            && nonLocalSelectionsState) {
            if (this.parameters.federatedQueryGraph.nonLocalSelectionsMetadata
                .checkNonLocalSelectionsLimitExceededAtRoot(this.stack, nonLocalSelectionsState, this.parameters.supergraphSchema, this.parameters.inconsistentAbstractTypesRuntimes, this.parameters.overrideConditions)) {
                throw Error(`Number of non-local selections exceeds limit of ${query_graphs_1.NonLocalSelectionsMetadata.MAX_NON_LOCAL_SELECTIONS}`);
            }
        }
    }
    debugStack() {
        if (this.isTopLevel && debug.enabled) {
            debug.group('Query planning open branches:');
            for (const [selection, options] of this.stack) {
                debug.groupedValues(options, opt => `${(0, query_graphs_1.simultaneousPathsToString)(opt)}`, `${selection}:`);
            }
            debug.groupEnd();
        }
    }
    findBestPlan() {
        while (this.stack.length > 0) {
            this.debugStack();
            const [selection, options] = this.stack.pop();
            this.handleOpenBranch(selection, options);
        }
        this.computeBestPlanFromClosedBranches();
        return this.bestPlan;
    }
    recordClosedBranch(closed) {
        const maybeTrimmed = this.maybeEliminateStrictlyMoreCostlyPaths(closed);
        debug.log(() => `Closed branch has ${maybeTrimmed.length} options (eliminated ${closed.length - maybeTrimmed.length} that could be proved as unecessary)`);
        this.closedBranches.push(maybeTrimmed);
    }
    handleOpenBranch(selection, options) {
        const operation = selection.element;
        debug.group(() => `Handling open branch: ${operation}`);
        let newOptions = [];
        for (const option of options) {
            const followupForOption = (0, query_graphs_1.advanceSimultaneousPathsWithOperation)(this.parameters.supergraphSchema, option, operation, this.parameters.overrideConditions);
            if (!followupForOption) {
                continue;
            }
            if (followupForOption.length === 0) {
                if (operation.kind === 'FragmentElement') {
                    this.recordClosedBranch(options.map((o) => ({
                        paths: o.paths.map(p => (0, query_graphs_1.terminateWithNonRequestedTypenameField)(p, this.parameters.overrideConditions))
                    })));
                }
                debug.groupEnd(() => `Terminating branch with no possible results`);
                return;
            }
            newOptions = newOptions.concat(followupForOption);
            if (this.optionsLimit && newOptions.length > this.optionsLimit) {
                throw new Error(`Too many options generated for ${selection}, reached the limit of ${this.optionsLimit}`);
            }
        }
        if (newOptions.length === 0) {
            if (this.isTopLevel) {
                debug.groupEnd(() => `No valid options to advance ${selection} from ${(0, query_graphs_1.advanceOptionsToString)(options)}`);
                throw new Error(`Was not able to find any options for ${selection}: This shouldn't have happened.`);
            }
            else {
                this.stack.splice(0, this.stack.length);
                this.closedBranches.splice(0, this.closedBranches.length);
                debug.groupEnd(() => `No possible plan for ${selection} from ${(0, query_graphs_1.advanceOptionsToString)(options)}; terminating condition`);
                return;
            }
        }
        if (selection.selectionSet) {
            const allTails = allTailVertices(newOptions);
            if (selectionIsFullyLocalFromAllVertices(selection.selectionSet, allTails, this.parameters.inconsistentAbstractTypesRuntimes)
                && !selection.hasDefer()) {
                const selectionSet = addTypenameFieldForAbstractTypes(addBackTypenameInAttachments(selection.selectionSet));
                this.recordClosedBranch(newOptions.map((opt) => ({ paths: opt.paths, selection: selectionSet })));
            }
            else {
                for (const branch of mapOptionsToSelections(selection.selectionSet, newOptions)) {
                    this.stack.push(branch);
                }
            }
            debug.groupEnd();
        }
        else {
            this.recordClosedBranch(newOptions.map((opt) => ({ paths: opt.paths })));
            debug.groupEnd(() => `Branch finished`);
        }
    }
    maybeEliminateStrictlyMoreCostlyPaths(branch) {
        if (branch.length <= 1) {
            return branch;
        }
        const toHandle = branch.concat();
        const keptOptions = [];
        while (toHandle.length >= 2) {
            const first = toHandle[0];
            let shouldKeepFirst = true;
            for (let i = toHandle.length - 1; i >= 1; i--) {
                const other = toHandle[i];
                const cmp = compareOptionsComplexityOutOfContext(first.paths, other.paths);
                if (cmp < 0) {
                    toHandle.splice(i, 1);
                }
                else if (cmp > 0) {
                    toHandle.splice(0, 1);
                    shouldKeepFirst = false;
                    break;
                }
            }
            if (shouldKeepFirst) {
                keptOptions.push(first);
                toHandle.splice(0, 1);
            }
        }
        if (toHandle.length > 0) {
            keptOptions.push(toHandle[0]);
        }
        return keptOptions;
    }
    newDependencyGraph() {
        const { supergraphSchema, federatedQueryGraph } = this.parameters;
        const rootType = this.isTopLevel && this.hasDefers ? supergraphSchema.schemaDefinition.rootType(this.rootKind) : undefined;
        return FetchDependencyGraph.create(supergraphSchema, federatedQueryGraph, this.startFetchIdGen, rootType, this.parameters.config.generateQueryFragments);
    }
    reorderFirstBranch() {
        const firstBranch = this.closedBranches[0];
        let i = 1;
        while (i < this.closedBranches.length && this.closedBranches[i].length > firstBranch.length) {
            i++;
        }
        this.closedBranches[0] = this.closedBranches[i - 1];
        this.closedBranches[i - 1] = firstBranch;
    }
    sortOptionsInClosedBranches() {
        this.closedBranches.forEach((branch) => branch.sort((p1, p2) => {
            const p1Jumps = Math.max(...p1.paths.map((p) => p.subgraphJumps()));
            const p2Jumps = Math.max(...p2.paths.map((p) => p.subgraphJumps()));
            return p1Jumps - p2Jumps;
        }));
    }
    computeBestPlanFromClosedBranches() {
        if (this.closedBranches.length === 0) {
            return;
        }
        this.sortOptionsInClosedBranches();
        this.closedBranches.sort((b1, b2) => b1.length > b2.length ? -1 : (b1.length < b2.length ? 1 : 0));
        let planCount = possiblePlans(this.closedBranches);
        debug.log(() => `Query has ${planCount} possible plans`);
        let firstBranch = this.closedBranches[0];
        const maxPlansToCompute = this.parameters.config.debug.maxEvaluatedPlans;
        while (planCount > maxPlansToCompute && firstBranch.length > 1) {
            const prevSize = BigInt(firstBranch.length);
            firstBranch.pop();
            planCount -= planCount / prevSize;
            this.reorderFirstBranch();
            firstBranch = this.closedBranches[0];
            debug.log(() => `Reduced plans to consider to ${planCount} plans`);
        }
        if (this.parameters.statistics && this.isTopLevel) {
            this.parameters.statistics.evaluatedPlanCount += Number(planCount);
        }
        debug.log(() => `All branches:${this.closedBranches.map((opts, i) => `\n${i}:${opts.map((opt => `\n - ${closedPathToString(opt)}`))}`)}`);
        let idxFirstOfLengthOne = 0;
        while (idxFirstOfLengthOne < this.closedBranches.length && this.closedBranches[idxFirstOfLengthOne].length > 1) {
            idxFirstOfLengthOne++;
        }
        let initialTree;
        let initialDependencyGraph;
        const { federatedQueryGraph, root } = this.parameters;
        if (idxFirstOfLengthOne === this.closedBranches.length) {
            initialTree = query_graphs_1.PathTree.createOp(federatedQueryGraph, root);
            initialDependencyGraph = this.newDependencyGraph();
        }
        else {
            const singleChoiceBranches = this
                .closedBranches
                .slice(idxFirstOfLengthOne)
                .flat()
                .map((cp) => flattenClosedPath(cp))
                .flat();
            initialTree = query_graphs_1.PathTree.createFromOpPaths(federatedQueryGraph, root, singleChoiceBranches);
            initialDependencyGraph = this.updatedDependencyGraph(this.newDependencyGraph(), initialTree);
            if (idxFirstOfLengthOne === 0) {
                this.bestPlan = [initialDependencyGraph, initialTree, this.cost(initialDependencyGraph)];
                return;
            }
        }
        const otherTrees = this
            .closedBranches
            .slice(0, idxFirstOfLengthOne)
            .map(b => b.map(opt => query_graphs_1.PathTree.createFromOpPaths(federatedQueryGraph, root, flattenClosedPath(opt))));
        const { best, cost } = (0, generateAllPlans_1.generateAllPlansAndFindBest)({
            initial: { graph: initialDependencyGraph, tree: initialTree },
            toAdd: otherTrees,
            addFct: (p, t) => {
                const updatedDependencyGraph = p.graph.clone();
                this.updatedDependencyGraph(updatedDependencyGraph, t);
                const updatedTree = p.tree.merge(t);
                return { graph: updatedDependencyGraph, tree: updatedTree };
            },
            costFct: (p) => this.cost(p.graph),
            onPlan: (p, cost, prevCost) => {
                debug.log(() => {
                    if (!prevCost) {
                        return `Computed plan with cost ${cost}: ${p.tree}`;
                    }
                    else if (cost > prevCost) {
                        return `Ignoring plan with cost ${cost} (a better plan with cost ${prevCost} exists): ${p.tree}`;
                    }
                    else {
                        return `Found better with cost ${cost} (previous had cost ${prevCost}: ${p.tree}`;
                    }
                });
            },
        });
        this.bestPlan = [best.graph, best.tree, cost];
    }
    cost(dependencyGraph) {
        const { main, deferred } = dependencyGraph.process(this.costFunction, this.rootKind);
        return deferred.length === 0
            ? main
            : this.costFunction.reduceDefer(main, dependencyGraph.deferTracking.primarySelection.get(), deferred);
    }
    updatedDependencyGraph(dependencyGraph, tree) {
        return (0, query_graphs_1.isRootPathTree)(tree)
            ? computeRootFetchGroups(dependencyGraph, tree, this.rootKind, this.typeConditionedFetching)
            : computeNonRootFetchGroups(dependencyGraph, tree, this.rootKind, this.typeConditionedFetching);
    }
    resolveConditionPlan(edge, context, excludedDestinations, excludedConditions, extraConditions) {
        const bestPlan = new QueryPlanningTraversal({
            ...this.parameters,
            root: edge.head,
        }, extraConditions !== null && extraConditions !== void 0 ? extraConditions : edge.conditions, 0, false, 'query', this.costFunction, context, this.typeConditionedFetching, null, excludedDestinations, (0, query_graphs_1.addConditionExclusion)(excludedConditions, edge.conditions)).findBestPlan();
        return bestPlan ? { satisfied: true, cost: bestPlan[2], pathTree: bestPlan[1] } : query_graphs_1.unsatisfiedConditionsResolution;
    }
}
const conditionsMemoizer = (selectionSet) => ({ conditions: (0, conditions_1.conditionsOfSelectionSet)(selectionSet) });
class GroupInputs {
    constructor(supergraphSchema) {
        this.supergraphSchema = supergraphSchema;
        this.usedContexts = new Map;
        this.perType = new Map();
        this.onUpdateCallback = undefined;
    }
    add(selection) {
        var _a;
        (0, federation_internals_1.assert)(selection.parentType.schema() === this.supergraphSchema, 'Inputs selections must be based on the supergraph schema');
        const typeName = selection.parentType.name;
        let typeSelection = this.perType.get(typeName);
        if (!typeSelection) {
            typeSelection = federation_internals_1.MutableSelectionSet.empty(selection.parentType);
            this.perType.set(typeName, typeSelection);
        }
        typeSelection.updates().add(selection);
        (_a = this.onUpdateCallback) === null || _a === void 0 ? void 0 : _a.call(this);
    }
    addContext(context, type) {
        this.usedContexts.set(context, type);
    }
    addAll(other) {
        for (const otherSelection of other.perType.values()) {
            this.add(otherSelection.get());
        }
        for (const [context, type] of other.usedContexts) {
            this.addContext(context, type);
        }
    }
    selectionSets() {
        return (0, federation_internals_1.mapValues)(this.perType).map((s) => s.get());
    }
    toSelectionSetNode(variablesDefinitions, handledConditions) {
        const selectionSets = (0, federation_internals_1.mapValues)(this.perType).map((s) => (0, conditions_1.removeConditionsFromSelectionSet)(s.get(), handledConditions));
        selectionSets.forEach((s) => s.validate(variablesDefinitions));
        const selections = selectionSets.flatMap((sSet) => sSet.selections().map((s) => s.toSelectionNode()));
        return {
            kind: graphql_1.Kind.SELECTION_SET,
            selections,
        };
    }
    contains(other) {
        for (const [type, otherSelection] of other.perType) {
            const thisSelection = this.perType.get(type);
            if (!thisSelection || !thisSelection.get().contains(otherSelection.get())) {
                return false;
            }
        }
        if (this.usedContexts.size < other.usedContexts.size) {
            return false;
        }
        for (const [c, _] of other.usedContexts) {
            if (!this.usedContexts.has(c)) {
                return false;
            }
        }
        return true;
    }
    equals(other) {
        if (this.perType.size !== other.perType.size) {
            return false;
        }
        for (const [type, thisSelection] of this.perType) {
            const otherSelection = other.perType.get(type);
            if (!otherSelection || !thisSelection.get().equals(otherSelection.get())) {
                return false;
            }
        }
        if (this.usedContexts.size !== other.usedContexts.size) {
            return false;
        }
        for (const [c, _] of other.usedContexts) {
            if (!this.usedContexts.has(c)) {
                return false;
            }
        }
        return true;
    }
    clone() {
        const cloned = new GroupInputs(this.supergraphSchema);
        for (const [type, selection] of this.perType.entries()) {
            cloned.perType.set(type, selection.clone());
        }
        for (const [c, v] of this.usedContexts) {
            cloned.usedContexts.set(c, v);
        }
        return cloned;
    }
    toString() {
        const inputs = (0, federation_internals_1.mapValues)(this.perType);
        if (inputs.length === 0) {
            return '{}';
        }
        if (inputs.length === 1) {
            return inputs[0].toString();
        }
        return '[' + inputs.join(',') + ']';
    }
}
class FetchGroup {
    constructor(dependencyGraph, index, subgraphName, rootKind, parentType, isEntityFetch, _selection, _inputs, _contextInputs, mergeAt, deferRef, subgraphAndMergeAtKey, cachedCost, generateQueryFragments = false, isKnownUseful = false, inputRewrites = []) {
        this.dependencyGraph = dependencyGraph;
        this.index = index;
        this.subgraphName = subgraphName;
        this.rootKind = rootKind;
        this.parentType = parentType;
        this.isEntityFetch = isEntityFetch;
        this._selection = _selection;
        this._inputs = _inputs;
        this._contextInputs = _contextInputs;
        this.mergeAt = mergeAt;
        this.deferRef = deferRef;
        this.subgraphAndMergeAtKey = subgraphAndMergeAtKey;
        this.cachedCost = cachedCost;
        this.generateQueryFragments = generateQueryFragments;
        this.isKnownUseful = isKnownUseful;
        this.inputRewrites = inputRewrites;
        this._parents = [];
        this._children = [];
        this.mustPreserveSelection = false;
        if (this._inputs) {
            this._inputs.onUpdateCallback = () => {
                this.isKnownUseful = false;
            };
        }
    }
    static create({ dependencyGraph, index, subgraphName, rootKind, parentType, hasInputs, mergeAt, deferRef, generateQueryFragments, }) {
        var _a;
        (0, federation_internals_1.assert)(parentType.schema() === dependencyGraph.subgraphSchemas.get(subgraphName), `Expected parent type ${parentType} to belong to ${subgraphName}`);
        return new FetchGroup(dependencyGraph, index, subgraphName, rootKind, parentType, hasInputs, federation_internals_1.MutableSelectionSet.emptyWithMemoized(parentType, conditionsMemoizer), hasInputs ? new GroupInputs(dependencyGraph.supergraphSchema) : undefined, undefined, mergeAt, deferRef, hasInputs ? `${toValidGraphQLName(subgraphName)}-${(_a = mergeAt === null || mergeAt === void 0 ? void 0 : mergeAt.join('::')) !== null && _a !== void 0 ? _a : ''}` : undefined, undefined, generateQueryFragments);
    }
    cloneShallow(newDependencyGraph) {
        var _a;
        return new FetchGroup(newDependencyGraph, this.index, this.subgraphName, this.rootKind, this.parentType, this.isEntityFetch, this._selection.clone(), (_a = this._inputs) === null || _a === void 0 ? void 0 : _a.clone(), this._contextInputs ? this._contextInputs.map((c) => ({ ...c })) : undefined, this.mergeAt, this.deferRef, this.subgraphAndMergeAtKey, this.cachedCost, this.generateQueryFragments, this.isKnownUseful, [...this.inputRewrites]);
    }
    cost() {
        if (!this.cachedCost) {
            this.cachedCost = selectionCost(this.selection);
        }
        return this.cachedCost;
    }
    set id(id) {
        (0, federation_internals_1.assert)(!this._id, () => `The id for fetch group ${this} is already set`);
        this._id = id;
    }
    get id() {
        return this._id;
    }
    get isTopLevel() {
        return !this.mergeAt;
    }
    get selection() {
        return this._selection.get();
    }
    selectionUpdates() {
        this.cachedCost = undefined;
        return this._selection.updates();
    }
    get inputs() {
        return this._inputs;
    }
    addParents(parents) {
        for (const parent of parents) {
            this.addParent(parent);
        }
    }
    addParent(parent) {
        if (this.isChildOf(parent.group)) {
            return;
        }
        (0, federation_internals_1.assert)(!parent.group.isParentOf(this), () => `Group ${parent.group} is a parent of ${this}, but the child relationship is broken`);
        (0, federation_internals_1.assert)(!parent.group.isChildOf(this), () => `Group ${parent.group} is a child of ${this}: adding it as parent would create a cycle`);
        this.dependencyGraph.onModification();
        this._parents.push(parent);
        parent.group._children.push(this);
    }
    removeChild(child) {
        if (!this.isParentOf(child)) {
            return;
        }
        this.dependencyGraph.onModification();
        findAndRemoveInPlace((g) => g === child, this._children);
        findAndRemoveInPlace((p) => p.group === this, child._parents);
    }
    isParentOf(maybeChild) {
        return this._children.includes(maybeChild);
    }
    isChildOf(maybeParent) {
        return !!this.parentRelation(maybeParent);
    }
    isDescendantOf(maybeAncestor) {
        const children = Array.from(maybeAncestor.children());
        while (children.length > 0) {
            const child = children.pop();
            if (child === this) {
                return true;
            }
            child.children().forEach((c) => children.push(c));
        }
        return false;
    }
    isChildOfWithArtificialDependency(maybeParent) {
        const relation = this.parentRelation(maybeParent);
        if (!relation || !relation.path) {
            return false;
        }
        if (!this.inputs) {
            return true;
        }
        if (relation.path.some((elt) => elt.kind === 'Field')) {
            return false;
        }
        return !!maybeParent.inputs && maybeParent.inputs.contains(this.inputs);
    }
    parentRelation(maybeParent) {
        return this._parents.find(({ group }) => maybeParent === group);
    }
    parents() {
        return this._parents;
    }
    parentGroups() {
        return this.parents().map((p) => p.group);
    }
    children() {
        return this._children;
    }
    addInputs(selection, rewrites) {
        (0, federation_internals_1.assert)(this._inputs, "Shouldn't try to add inputs to a root fetch group");
        this._inputs.add(selection);
        if (rewrites) {
            rewrites.forEach((r) => this.inputRewrites.push(r));
        }
    }
    addInputContext(context, type) {
        (0, federation_internals_1.assert)(this._inputs, "Shouldn't try to add inputs to a root fetch group");
        this._inputs.addContext(context, type);
    }
    copyInputsOf(other) {
        var _a;
        if (other.inputs) {
            (_a = this.inputs) === null || _a === void 0 ? void 0 : _a.addAll(other.inputs);
            if (other.inputRewrites) {
                other.inputRewrites.forEach((r) => {
                    if (!this.inputRewrites.some((r2) => r2 === r)) {
                        this.inputRewrites.push(r);
                    }
                });
            }
            if (other._contextInputs) {
                if (!this._contextInputs) {
                    this._contextInputs = [];
                }
                other._contextInputs.forEach((r) => {
                    if (!this._contextInputs.some((r2) => sameKeyRenamer(r, r2))) {
                        this._contextInputs.push(r);
                    }
                });
            }
        }
    }
    addAtPath(path, selection) {
        this.selectionUpdates().addAtPath(path, selection);
    }
    addSelections(selection) {
        this.selectionUpdates().add(selection);
    }
    canMergeChildIn(child) {
        var _a;
        return this.deferRef === child.deferRef && !!((_a = child.parentRelation(this)) === null || _a === void 0 ? void 0 : _a.path);
    }
    removeInputsFromSelection() {
        const inputs = this.inputs;
        if (inputs) {
            this.cachedCost = undefined;
            const updated = inputs.selectionSets().reduce((prev, value) => prev.minus(value), this.selection);
            this._selection = federation_internals_1.MutableSelectionSet.ofWithMemoized(updated, conditionsMemoizer);
        }
    }
    isUseless() {
        if (this.isKnownUseful || !this.inputs || this.mustPreserveSelection) {
            return false;
        }
        const conditionInSupergraphIfInterfaceObject = (selection) => {
            if (selection.kind === 'FragmentSelection') {
                const condition = selection.element.typeCondition;
                if (condition && (0, federation_internals_1.isObjectType)(condition)) {
                    const conditionInSupergraph = this.dependencyGraph.supergraphSchema.type(condition.name);
                    (0, federation_internals_1.assert)(conditionInSupergraph, () => `Type ${condition.name} should exists in the supergraph`);
                    if ((0, federation_internals_1.isInterfaceType)(conditionInSupergraph)) {
                        return conditionInSupergraph;
                    }
                }
            }
            return undefined;
        };
        const isInterfaceTypeConditionOnInterfaceObject = (selection) => {
            if (selection.kind === "FragmentSelection") {
                const parentType = selection.element.typeCondition;
                if (parentType && (0, federation_internals_1.isInterfaceType)(parentType)) {
                    return this.parents().some((p) => {
                        var _a;
                        const typeInParent = (_a = this.dependencyGraph.subgraphSchemas
                            .get(p.group.subgraphName)) === null || _a === void 0 ? void 0 : _a.type(parentType.name);
                        return typeInParent && (0, federation_internals_1.isInterfaceObjectType)(typeInParent);
                    });
                }
            }
            return false;
        };
        const inputSelections = this.inputs.selectionSets().flatMap((s) => s.selections());
        const isUseless = this.selection.selections().every((selection) => {
            if (isInterfaceTypeConditionOnInterfaceObject(selection)) {
                return false;
            }
            const conditionInSupergraph = conditionInSupergraphIfInterfaceObject(selection);
            if (!conditionInSupergraph) {
                return inputSelections.some((input) => input.contains(selection));
            }
            const implemTypeNames = conditionInSupergraph.possibleRuntimeTypes().map((t) => t.name);
            const interfaceInputSelections = [];
            const implementationInputSelections = [];
            for (const inputSelection of inputSelections) {
                (0, federation_internals_1.assert)(inputSelection.kind === 'FragmentSelection', () => `Unexpecting input selection ${inputSelection} on ${this}`);
                const inputCondition = inputSelection.element.typeCondition;
                (0, federation_internals_1.assert)(inputCondition, () => `Unexpecting input selection ${inputSelection} on ${this} (missing condition)`);
                if (inputCondition.name == conditionInSupergraph.name) {
                    interfaceInputSelections.push(inputSelection);
                }
                else if (implemTypeNames.includes(inputCondition.name)) {
                    implementationInputSelections.push(inputSelection);
                }
            }
            const subSelectionSet = selection.selectionSet;
            (0, federation_internals_1.assert)(subSelectionSet, () => `Should not be here for ${selection}`);
            if (interfaceInputSelections.length > 0) {
                return interfaceInputSelections.some((input) => input.selectionSet.contains(subSelectionSet));
            }
            return implementationInputSelections.length > 0
                && implementationInputSelections.every((input) => input.selectionSet.contains(subSelectionSet));
        });
        this.isKnownUseful = !isUseless;
        return isUseless;
    }
    mergeChildIn(child) {
        const relationToChild = child.parentRelation(this);
        (0, federation_internals_1.assert)(relationToChild, () => `Cannot merge ${child} into ${this}: the former is not a child of the latter`);
        const childPathInThis = relationToChild.path;
        (0, federation_internals_1.assert)(childPathInThis, () => `Cannot merge ${child} into ${this}: the path of the former into the later is unknown`);
        this.mergeInInternal(child, childPathInThis);
    }
    canMergeSiblingIn(sibling) {
        const ownParents = this.parents();
        const siblingParents = sibling.parents();
        return this.deferRef === sibling.deferRef
            && this.subgraphName === sibling.subgraphName
            && sameMergeAt(this.mergeAt, sibling.mergeAt)
            && ownParents.length === 1
            && siblingParents.length === 1
            && ownParents[0].group === siblingParents[0].group;
    }
    mergeSiblingIn(sibling) {
        this.copyInputsOf(sibling);
        this.mergeInInternal(sibling, []);
    }
    canMergeGrandChildIn(grandChild) {
        var _a;
        const gcParents = grandChild.parents();
        if (gcParents.length !== 1) {
            return false;
        }
        return this.deferRef === grandChild.deferRef && !!gcParents[0].path && !!((_a = gcParents[0].group.parentRelation(this)) === null || _a === void 0 ? void 0 : _a.path);
    }
    mergeGrandChildIn(grandChild) {
        const gcParents = grandChild.parents();
        (0, federation_internals_1.assert)(gcParents.length === 1, () => `Cannot merge ${grandChild} as it has multiple parents ([${gcParents}])`);
        const gcParent = gcParents[0];
        const gcGrandParent = gcParent.group.parentRelation(this);
        (0, federation_internals_1.assert)(gcGrandParent, () => `Cannot merge ${grandChild} into ${this}: the former parent (${gcParent.group}) is not a child of the latter`);
        (0, federation_internals_1.assert)(gcParent.path && gcGrandParent.path, () => `Cannot merge ${grandChild} into ${this}: some paths in parents are unknown`);
        this.mergeInInternal(grandChild, (0, federation_internals_1.concatOperationPaths)(gcGrandParent.path, gcParent.path));
    }
    mergeInWithAllDependencies(other) {
        (0, federation_internals_1.assert)(this.deferRef === other.deferRef, () => `Can only merge unrelated groups within the same @defer block: cannot merge ${this} and ${other}`);
        (0, federation_internals_1.assert)(this.subgraphName === other.subgraphName, () => `Can only merge unrelated groups to the same subraphs: cannot merge ${this} and ${other}`);
        (0, federation_internals_1.assert)(sameMergeAt(this.mergeAt, other.mergeAt), () => `Can only merge unrelated groups at the same "mergeAt": ${this} has mergeAt=${this.mergeAt}, but ${other} has mergeAt=${other.mergeAt}`);
        this.copyInputsOf(other);
        this.mergeInInternal(other, [], true);
    }
    mergeInInternal(merged, path, mergeParentDependencies = false) {
        (0, federation_internals_1.assert)(!merged.isTopLevel, "Shouldn't remove top level groups");
        if (path.length === 0) {
            this.addSelections(merged.selection);
        }
        else {
            const mergePathConditionalDirectives = (0, federation_internals_1.conditionalDirectivesInOperationPath)(path);
            this.addAtPath(path, removeUnneededTopLevelFragmentDirectives(merged.selection, mergePathConditionalDirectives));
        }
        this.dependencyGraph.onModification();
        this.relocateChildrenOnMergedIn(merged, path);
        if (mergeParentDependencies) {
            this.relocateParentsOnMergedIn(merged);
        }
        if (merged.mustPreserveSelection) {
            this.mustPreserveSelection = true;
        }
        this.dependencyGraph.remove(merged);
    }
    removeUselessChild(child) {
        const relationToChild = child.parentRelation(this);
        (0, federation_internals_1.assert)(relationToChild, () => `Cannot remove useless ${child} of ${this}: the former is not a child of the latter`);
        const childPathInThis = relationToChild.path;
        (0, federation_internals_1.assert)(childPathInThis, () => `Cannot remove useless ${child} of ${this}: the path of the former into the later is unknown`);
        this.dependencyGraph.onModification();
        this.relocateChildrenOnMergedIn(child, childPathInThis);
        this.dependencyGraph.remove(child);
    }
    relocateChildrenOnMergedIn(merged, pathInThis) {
        var _a;
        for (const child of merged.children()) {
            if (this.isParentOf(child)) {
                continue;
            }
            const pathInMerged = (_a = child.parentRelation(merged)) === null || _a === void 0 ? void 0 : _a.path;
            child.addParent({ group: this, path: concatPathsInParents(pathInThis, pathInMerged) });
        }
    }
    relocateParentsOnMergedIn(merged) {
        for (const parent of merged.parents()) {
            if (parent.group.isParentOf(this)) {
                continue;
            }
            if (parent.group.isDescendantOf(this)) {
                continue;
            }
            this.addParent(parent);
        }
    }
    finalizeSelection(variableDefinitions, handledConditions) {
        const selectionWithoutConditions = (0, conditions_1.removeConditionsFromSelectionSet)(this.selection, handledConditions);
        const selectionWithTypenames = addTypenameFieldForAbstractTypes(selectionWithoutConditions);
        const { updated: selection, outputRewrites } = addAliasesForNonMergingFields(selectionWithTypenames);
        selection.validate(variableDefinitions, true);
        return { selection, outputRewrites };
    }
    conditions() {
        return this._selection.memoized().conditions;
    }
    toPlanNode(queryPlannerConfig, handledConditions, variableDefinitions, fragments, operationName, directives) {
        var _a, _b;
        if (this.selection.isEmpty()) {
            return undefined;
        }
        for (const [context, type] of (_b = (_a = this.inputs) === null || _a === void 0 ? void 0 : _a.usedContexts) !== null && _b !== void 0 ? _b : []) {
            (0, federation_internals_1.assert)((0, federation_internals_1.isInputType)(type), () => `Expected ${type} to be a input type`);
            variableDefinitions.add(new federation_internals_1.VariableDefinition(type.schema(), new federation_internals_1.Variable(context), type));
        }
        const { selection, outputRewrites } = this.finalizeSelection(variableDefinitions, handledConditions);
        const inputNodes = this._inputs ? this._inputs.toSelectionSetNode(variableDefinitions, handledConditions) : undefined;
        const subgraphSchema = this.dependencyGraph.subgraphSchemas.get(this.subgraphName);
        let operation = this.isEntityFetch
            ? operationForEntitiesFetch(subgraphSchema, selection, variableDefinitions, operationName, directives)
            : operationForQueryFetch(subgraphSchema, this.rootKind, selection, variableDefinitions, operationName, directives);
        if (this.generateQueryFragments) {
            operation = operation.generateQueryFragments();
        }
        else {
            operation = operation.optimize(fragments === null || fragments === void 0 ? void 0 : fragments.forSubgraph(this.subgraphName, subgraphSchema), federation_internals_1.DEFAULT_MIN_USAGES_TO_OPTIMIZE, variableDefinitions);
        }
        const collector = new federation_internals_1.VariableCollector();
        selection.collectVariables(collector);
        operation.collectVariablesInAppliedDirectives(collector);
        if (operation.fragments) {
            for (const namedFragment of operation.fragments.definitions()) {
                namedFragment.collectVariables(collector);
            }
        }
        const usedVariables = collector.variables();
        const operationDocument = (0, federation_internals_1.operationToDocument)(operation);
        const fetchNode = {
            kind: 'Fetch',
            id: this.id,
            serviceName: this.subgraphName,
            requires: inputNodes ? (0, QueryPlan_1.trimSelectionNodes)(inputNodes.selections) : undefined,
            variableUsages: usedVariables.map((v) => v.name),
            operation: (0, graphql_1.stripIgnoredCharacters)((0, graphql_1.print)(operationDocument)),
            operationKind: schemaRootKindToOperationKind(operation.rootKind),
            operationName: operation.name,
            operationDocumentNode: queryPlannerConfig.exposeDocumentNodeInFetchNode ? operationDocument : undefined,
            inputRewrites: this.inputRewrites.length === 0 ? undefined : this.inputRewrites,
            outputRewrites: outputRewrites.length === 0 ? undefined : outputRewrites,
            contextRewrites: this._contextInputs,
        };
        return this.isTopLevel
            ? fetchNode
            : {
                kind: 'Flatten',
                path: this.mergeAt,
                node: fetchNode,
            };
    }
    addContextRenamer(renamer) {
        if (!this._contextInputs) {
            this._contextInputs = [];
        }
        if (!this._contextInputs.some((c) => sameKeyRenamer(c, renamer))) {
            this._contextInputs.push(renamer);
        }
    }
    toString() {
        const base = `[${this.index}]${this.deferRef ? '(deferred)' : ''}${this._id ? `{id: ${this._id}}` : ''} ${this.subgraphName}`;
        return this.isTopLevel
            ? `${base}[${this._selection}]`
            : `${base}@(${this.mergeAt})[${this._inputs} => ${this._selection}]`;
    }
}
class RebasedFragments {
    constructor(queryFragments) {
        this.queryFragments = queryFragments;
        this.bySubgraph = new Map();
    }
    forSubgraph(name, schema) {
        var _a;
        let frags = this.bySubgraph.get(name);
        if (frags === undefined) {
            frags = (_a = this.queryFragments.rebaseOn(schema)) !== null && _a !== void 0 ? _a : null;
            this.bySubgraph.set(name, frags);
        }
        return frags !== null && frags !== void 0 ? frags : undefined;
    }
}
function genAliasName(baseName, unavailableNames) {
    let counter = 0;
    let candidate = `${baseName}__alias_${counter}`;
    while (unavailableNames.has(candidate)) {
        candidate = `${baseName}__alias_${++counter}`;
    }
    return candidate;
}
function selectionSetAsKeyRenamers(selectionSet, relPath, alias) {
    if (!selectionSet || selectionSet.isEmpty()) {
        return [
            {
                kind: 'KeyRenamer',
                path: relPath,
                renameKeyTo: alias,
            }
        ];
    }
    return selectionSet.selections().map((selection) => {
        if (selection.kind === 'FieldSelection') {
            if (relPath[relPath.length - 1] === '..' && selectionSet.parentType.name !== 'Query') {
                return (0, federation_internals_1.possibleRuntimeTypes)(selectionSet.parentType).map((t) => selectionSetAsKeyRenamers(selectionSet, [...relPath, `... on ${t.name}`], alias)).flat();
            }
            else {
                return selectionSetAsKeyRenamers(selection.selectionSet, [...relPath, selection.element.name], alias);
            }
        }
        else if (selection.kind === 'FragmentSelection') {
            const element = selection.element;
            if (element.typeCondition) {
                return selectionSetAsKeyRenamers(selection.selectionSet, [...relPath, `... on ${element.typeCondition.name}`], alias);
            }
        }
        return undefined;
    }).filter(federation_internals_1.isDefined)
        .reduce((acc, val) => acc.concat(val), []);
}
function computeAliasesForNonMergingFields(selections, aliasCollector) {
    const seenResponseNames = new Map();
    const rebasedFieldsInSet = (s) => (s.selections.fieldsInSet().map(({ path, field }) => ({ fieldPath: s.path.concat(path), field })));
    for (const { fieldPath, field } of selections.map((s) => rebasedFieldsInSet(s)).flat()) {
        const fieldName = field.element.name;
        const responseName = field.element.responseName();
        const fieldType = field.element.definition.type;
        const previous = seenResponseNames.get(responseName);
        if (previous) {
            if (previous.fieldName === fieldName && (0, federation_internals_1.typesCanBeMerged)(previous.fieldType, fieldType)) {
                if ((0, federation_internals_1.isCompositeType)((0, federation_internals_1.baseType)(fieldType))) {
                    (0, federation_internals_1.assert)(previous.selections, () => `Should have added selections for ${previous.fieldType}`);
                    const selections = previous.selections.concat({ path: fieldPath.concat(responseName), selections: field.selectionSet });
                    seenResponseNames.set(responseName, { ...previous, selections });
                }
            }
            else {
                const alias = genAliasName(responseName, seenResponseNames);
                const selections = field.selectionSet ? [{ path: fieldPath.concat(alias), selections: field.selectionSet }] : undefined;
                seenResponseNames.set(alias, { fieldName, fieldType, selections });
                aliasCollector.push({
                    path: fieldPath,
                    responseName,
                    alias
                });
            }
        }
        else {
            const selections = field.selectionSet ? [{ path: fieldPath.concat(responseName), selections: field.selectionSet }] : undefined;
            seenResponseNames.set(responseName, { fieldName, fieldType, selections });
        }
    }
    for (const selections of seenResponseNames.values()) {
        if (!selections.selections) {
            continue;
        }
        computeAliasesForNonMergingFields(selections.selections, aliasCollector);
    }
}
function addAliasesForNonMergingFields(selectionSet) {
    const aliases = [];
    computeAliasesForNonMergingFields([{ path: [], selections: selectionSet }], aliases);
    const updated = withFieldAliased(selectionSet, aliases);
    const outputRewrites = aliases.map(({ path, responseName, alias }) => ({
        kind: 'KeyRenamer',
        path: path.concat(alias),
        renameKeyTo: responseName,
    }));
    return { updated, outputRewrites };
}
function withFieldAliased(selectionSet, aliases) {
    if (aliases.length === 0) {
        return selectionSet;
    }
    const atCurrentLevel = new Map();
    const remaining = new Array();
    for (const alias of aliases) {
        if (alias.path.length > 0) {
            remaining.push(alias);
        }
        else {
            atCurrentLevel.set(alias.responseName, alias);
        }
    }
    return selectionSet.lazyMap((selection) => {
        const pathElement = selection.element.asPathElement();
        const subselectionAliases = remaining.map((alias) => {
            if (alias.path[0] === pathElement) {
                return {
                    ...alias,
                    path: alias.path.slice(1),
                };
            }
            else {
                return undefined;
            }
        }).filter(federation_internals_1.isDefined);
        const updatedSelectionSet = selection.selectionSet
            ? withFieldAliased(selection.selectionSet, subselectionAliases)
            : undefined;
        if (selection.kind === 'FieldSelection') {
            const field = selection.element;
            const alias = pathElement && atCurrentLevel.get(pathElement);
            return !alias && selection.selectionSet === updatedSelectionSet
                ? selection
                : selection.withUpdatedComponents(alias ? field.withUpdatedAlias(alias.alias) : field, updatedSelectionSet);
        }
        else {
            return selection.selectionSet === updatedSelectionSet
                ? selection
                : selection.withUpdatedSelectionSet(updatedSelectionSet);
        }
    });
}
class DeferredInfo {
    constructor(label, path, subselection, deferred = new Set(), dependencies = new Set()) {
        this.label = label;
        this.path = path;
        this.subselection = subselection;
        this.deferred = deferred;
        this.dependencies = dependencies;
    }
    static empty(label, path, parentType) {
        return new DeferredInfo(label, path, federation_internals_1.MutableSelectionSet.empty(parentType));
    }
    clone() {
        return new DeferredInfo(this.label, this.path, this.subselection.clone(), new Set(this.deferred), new Set(this.dependencies));
    }
}
const emptyDeferContext = {
    currentDeferRef: undefined,
    pathToDeferParent: [],
    activeDeferRef: undefined,
    isPartOfQuery: true,
};
function deferContextForConditions(baseContext) {
    return {
        ...baseContext,
        isPartOfQuery: false,
        currentDeferRef: baseContext.activeDeferRef,
    };
}
function deferContextAfterSubgraphJump(baseContext) {
    return baseContext.currentDeferRef === baseContext.activeDeferRef
        ? baseContext
        : {
            ...baseContext,
            activeDeferRef: baseContext.currentDeferRef,
        };
}
function filterOperationPath(path, schema) {
    return path.map((elt) => {
        if (elt.kind === 'FragmentElement' && elt.typeCondition && !schema.type(elt.typeCondition.name)) {
            return elt.appliedDirectives.length > 0 ? elt.withUpdatedCondition(undefined) : undefined;
        }
        return elt;
    }).filter(federation_internals_1.isDefined);
}
class GroupPath {
    constructor(fullPath, pathInGroup, responsePath, typeConditionedFetching, possibleTypes, possibleTypesAfterLastField) {
        this.fullPath = fullPath;
        this.pathInGroup = pathInGroup;
        this.responsePath = responsePath;
        this.typeConditionedFetching = typeConditionedFetching;
        this.possibleTypes = possibleTypes;
        this.possibleTypesAfterLastField = possibleTypesAfterLastField;
    }
    static empty(typeConditionedFetching, rootType) {
        const rootPossibleRuntimeTypes = typeConditionedFetching ? Array.from((0, federation_internals_1.possibleRuntimeTypes)(rootType)) : [];
        rootPossibleRuntimeTypes.sort();
        return new GroupPath([], [], [], typeConditionedFetching, rootPossibleRuntimeTypes, rootPossibleRuntimeTypes);
    }
    inGroup() {
        return this.pathInGroup;
    }
    full() {
        return this.fullPath;
    }
    inResponse() {
        return this.responsePath;
    }
    forNewKeyFetch(newGroupContext) {
        return new GroupPath(this.fullPath, newGroupContext, this.responsePath, this.typeConditionedFetching, this.possibleTypes, this.possibleTypesAfterLastField);
    }
    forParentOfGroup(pathOfGroupInParent, parentSchema) {
        return new GroupPath(this.fullPath, (0, federation_internals_1.concatOperationPaths)(pathOfGroupInParent, filterOperationPath(this.pathInGroup, parentSchema)), this.responsePath, this.typeConditionedFetching, this.possibleTypes, this.possibleTypesAfterLastField);
    }
    updatedResponsePath(element) {
        switch (element.kind) {
            case 'FragmentElement':
                return this.responsePath;
            case 'Field':
                let newPath = this.responsePath;
                if (this.possibleTypesAfterLastField.length !== this.possibleTypes.length) {
                    const conditions = `|[${this.possibleTypes.join(',')}]`;
                    const previousLastElement = newPath[newPath.length - 1] || '';
                    if (previousLastElement.startsWith('|[')) {
                        newPath = [...newPath.slice(0, -1), conditions];
                    }
                    else {
                        newPath = [...newPath.slice(0, -1), `${previousLastElement}${conditions}`];
                    }
                }
                let type = element.definition.type;
                if (newPath.length === 0 && this.typeConditionedFetching) {
                    newPath = newPath.concat('');
                }
                newPath = newPath.concat(`${element.responseName()}`);
                while (!(0, federation_internals_1.isNamedType)(type)) {
                    if ((0, federation_internals_1.isListType)(type)) {
                        newPath.push('@');
                    }
                    type = type.ofType;
                }
                return newPath;
        }
    }
    add(element) {
        const responsePath = this.updatedResponsePath(element);
        const newPossibleTypes = this.computeNewPossibleTypes(element);
        return new GroupPath(this.fullPath.concat(element), this.pathInGroup.concat(element), responsePath, this.typeConditionedFetching, newPossibleTypes, element.kind === 'Field' ? newPossibleTypes : this.possibleTypesAfterLastField);
    }
    toString() {
        return this.inResponse().join('.');
    }
    computeNewPossibleTypes(element) {
        if (!this.typeConditionedFetching) {
            return [];
        }
        switch (element.kind) {
            case 'FragmentElement':
                if (!element.typeCondition) {
                    return this.possibleTypes;
                }
                const elementPossibleTypes = (0, federation_internals_1.possibleRuntimeTypes)(element.typeCondition);
                return this.possibleTypes.filter((pt) => elementPossibleTypes.some((ept) => ept.name === pt.name));
            case 'Field':
                return this.advanceFieldType(element);
        }
    }
    advanceFieldType(element) {
        if (!(0, federation_internals_1.isCompositeType)(element.baseType())) {
            return [];
        }
        const res = Array.from(new Set(this.possibleTypes.map((pt) => (0, federation_internals_1.possibleRuntimeTypes)((0, federation_internals_1.baseType)(pt.field(element.name).type))).flat()));
        res.sort();
        return res;
    }
}
class DeferTracking {
    constructor(primarySelection) {
        this.primarySelection = primarySelection;
        this.topLevelDeferred = new Set();
        this.deferred = new federation_internals_1.MapWithCachedArrays();
    }
    static empty(rootType) {
        return new DeferTracking(rootType ? federation_internals_1.MutableSelectionSet.empty(rootType) : undefined);
    }
    clone() {
        var _a;
        const cloned = new DeferTracking((_a = this.primarySelection) === null || _a === void 0 ? void 0 : _a.clone());
        this.topLevelDeferred.forEach((label) => cloned.topLevelDeferred.add(label));
        for (const deferredBlock of this.deferred.values()) {
            cloned.deferred.set(deferredBlock.label, deferredBlock.clone());
        }
        return cloned;
    }
    registerDefer({ deferContext, deferArgs, path, parentType, }) {
        if (!this.primarySelection) {
            return;
        }
        (0, federation_internals_1.assert)(deferArgs.label, 'All @defer should have be labelled at this point');
        let deferredBlock = this.deferred.get(deferArgs.label);
        if (!deferredBlock) {
            deferredBlock = DeferredInfo.empty(deferArgs.label, path, parentType);
            this.deferred.set(deferArgs.label, deferredBlock);
        }
        const parentRef = deferContext.currentDeferRef;
        if (!parentRef) {
            this.topLevelDeferred.add(deferArgs.label);
            this.primarySelection.updates().addAtPath(deferContext.pathToDeferParent);
        }
        else {
            const parentInfo = this.deferred.get(parentRef);
            (0, federation_internals_1.assert)(parentInfo, `Cannot find info for parent ${parentRef} or ${deferArgs.label}`);
            parentInfo.deferred.add(deferArgs.label);
            parentInfo.subselection.updates().addAtPath(deferContext.pathToDeferParent);
        }
    }
    updateSubselection(deferContext, selection) {
        if (!this.primarySelection || !deferContext.isPartOfQuery) {
            return;
        }
        const parentRef = deferContext.currentDeferRef;
        let updates;
        if (parentRef) {
            const info = this.deferred.get(parentRef);
            (0, federation_internals_1.assert)(info, () => `Cannot find info for label ${parentRef}`);
            updates = info.subselection.updates();
        }
        else {
            updates = this.primarySelection.updates();
        }
        updates.addAtPath(deferContext.pathToDeferParent, selection);
    }
    getBlock(label) {
        return this.deferred.get(label);
    }
    addDependency(label, idDependency) {
        const info = this.deferred.get(label);
        (0, federation_internals_1.assert)(info, () => `Cannot find info for label ${label}`);
        info.dependencies.add(idDependency);
    }
    defersInParent(parentRef) {
        var _a;
        const labels = parentRef ? (_a = this.deferred.get(parentRef)) === null || _a === void 0 ? void 0 : _a.deferred : this.topLevelDeferred;
        return labels
            ? (0, federation_internals_1.setValues)(labels).map((label) => {
                const info = this.deferred.get(label);
                (0, federation_internals_1.assert)(info, () => `Should not have referenced ${label} without an existing info`);
                return info;
            })
            : [];
    }
}
function printUnhandled(u) {
    return '[' + u.map(([g, relations]) => `${g.index} (missing: [${relations.map((r) => r.group.index).join(', ')}])`).join(', ') + ']';
}
class ProcessingState {
    constructor(next, unhandled) {
        this.next = next;
        this.unhandled = unhandled;
    }
    static empty() {
        return new ProcessingState([], []);
    }
    static forChildrenOfProcessedGroup(processed, children) {
        const ready = [];
        const unhandled = [];
        for (const c of children) {
            const parents = c.parents();
            if (parents.length === 1) {
                ready.push(c);
            }
            else {
                unhandled.push([c, parents.filter((p) => p.group !== processed)]);
            }
        }
        return new ProcessingState(ready, unhandled);
    }
    static ofReadyGroups(groups) {
        return new ProcessingState(groups, []);
    }
    withOnlyUnhandled() {
        return new ProcessingState([], this.unhandled);
    }
    mergeWith(that) {
        const next = this.next.concat(that.next.filter((g) => !this.next.includes(g)));
        const unhandled = [];
        const thatUnhandled = that.unhandled.concat();
        for (const [g, edges] of this.unhandled) {
            const newEdges = this.mergeRemaingsAndRemoveIfFound(g, edges, thatUnhandled);
            if (newEdges.length == 0) {
                if (!next.includes(g)) {
                    next.push(g);
                }
            }
            else {
                unhandled.push([g, newEdges]);
            }
        }
        unhandled.push(...thatUnhandled);
        return new ProcessingState(next, unhandled);
    }
    mergeRemaingsAndRemoveIfFound(group, inEdges, otherGroups) {
        const idx = otherGroups.findIndex(g => g[0] === group);
        if (idx < 0) {
            return inEdges;
        }
        else {
            const otherEdges = otherGroups[idx][1];
            otherGroups.splice(idx, 1);
            return inEdges.filter(e => otherEdges.includes(e));
        }
    }
    updateForProcessedGroups(processed) {
        const next = this.next.concat();
        const unhandled = [];
        for (const [g, edges] of this.unhandled) {
            const newEdges = edges.filter((edge) => !processed.includes(edge.group));
            if (newEdges.length === 0) {
                if (!next.includes(g)) {
                    next.push(g);
                }
            }
            else {
                unhandled.push([g, newEdges]);
            }
        }
        return new ProcessingState(next, unhandled);
    }
}
class FetchDependencyGraph {
    constructor(supergraphSchema, subgraphSchemas, federatedQueryGraph, startingIdGen, rootGroups, groups, deferTracking, generateQueryFragments) {
        this.supergraphSchema = supergraphSchema;
        this.subgraphSchemas = subgraphSchemas;
        this.federatedQueryGraph = federatedQueryGraph;
        this.startingIdGen = startingIdGen;
        this.rootGroups = rootGroups;
        this.groups = groups;
        this.deferTracking = deferTracking;
        this.generateQueryFragments = generateQueryFragments;
        this.isReduced = false;
        this.isOptimized = false;
        this.fetchIdGen = startingIdGen;
    }
    static create(supergraphSchema, federatedQueryGraph, startingIdGen, rootTypeForDefer, generateQueryFragments) {
        return new FetchDependencyGraph(supergraphSchema, federatedQueryGraph.sources, federatedQueryGraph, startingIdGen, new federation_internals_1.MapWithCachedArrays(), [], DeferTracking.empty(rootTypeForDefer), generateQueryFragments);
    }
    federationMetadata(subgraphName) {
        const schema = this.subgraphSchemas.get(subgraphName);
        (0, federation_internals_1.assert)(schema, () => `Unknown schema ${subgraphName}`);
        const metadata = (0, federation_internals_1.federationMetadata)(schema);
        (0, federation_internals_1.assert)(metadata, () => `Schema ${subgraphName} should be a federation subgraph`);
        return metadata;
    }
    nextFetchId() {
        return this.fetchIdGen;
    }
    clone() {
        const cloned = new FetchDependencyGraph(this.supergraphSchema, this.subgraphSchemas, this.federatedQueryGraph, this.startingIdGen, new federation_internals_1.MapWithCachedArrays(), new Array(this.groups.length), this.deferTracking.clone(), this.generateQueryFragments);
        for (const group of this.groups) {
            cloned.groups[group.index] = group.cloneShallow(cloned);
        }
        for (const root of this.rootGroups.values()) {
            cloned.rootGroups.set(root.subgraphName, cloned.groups[root.index]);
        }
        for (const group of this.groups) {
            const clonedGroup = cloned.groups[group.index];
            for (const parent of group.parents()) {
                clonedGroup.addParent({
                    group: cloned.groups[parent.group.index],
                    path: parent.path
                });
            }
        }
        return cloned;
    }
    supergraphSchemaType(typeName) {
        return this.supergraphSchema.type(typeName);
    }
    getOrCreateRootFetchGroup({ subgraphName, rootKind, parentType, }) {
        let group = this.rootGroups.get(subgraphName);
        if (!group) {
            group = this.createRootFetchGroup({ subgraphName, rootKind, parentType });
            this.rootGroups.set(subgraphName, group);
        }
        return group;
    }
    rootSubgraphs() {
        return this.rootGroups.keys();
    }
    isRootGroup(group) {
        return group === this.rootGroups.get(group.subgraphName);
    }
    createRootFetchGroup({ subgraphName, rootKind, parentType, }) {
        const group = this.newFetchGroup({ subgraphName, parentType, rootKind, hasInputs: false });
        this.rootGroups.set(subgraphName, group);
        return group;
    }
    newFetchGroup({ subgraphName, parentType, hasInputs, rootKind, mergeAt, deferRef, }) {
        this.onModification();
        const newGroup = FetchGroup.create({
            dependencyGraph: this,
            index: this.groups.length,
            subgraphName,
            rootKind,
            parentType,
            hasInputs,
            mergeAt,
            deferRef,
            generateQueryFragments: this.generateQueryFragments,
        });
        this.groups.push(newGroup);
        return newGroup;
    }
    getOrCreateKeyFetchGroup({ subgraphName, mergeAt, type, parent, conditionsGroups, deferRef, }) {
        var _a;
        for (const existing of parent.group.children()) {
            if (existing.subgraphName === subgraphName
                && existing.mergeAt
                && sameMergeAt(existing.mergeAt, mergeAt)
                && existing.selection.selections().every((s) => s.kind === 'FragmentSelection' && s.element.castedType() === type)
                && !this.isInGroupsOrTheirAncestors(existing, conditionsGroups)
                && existing.deferRef === deferRef
                && samePathsInParents((_a = existing.parentRelation(parent.group)) === null || _a === void 0 ? void 0 : _a.path, parent.path)) {
                return existing;
            }
        }
        const newGroup = this.newKeyFetchGroup({
            subgraphName,
            mergeAt,
            deferRef
        });
        newGroup.addParent(parent);
        return newGroup;
    }
    newRootTypeFetchGroup({ subgraphName, rootKind, parentType, mergeAt, deferRef, }) {
        return this.newFetchGroup({
            subgraphName,
            parentType,
            rootKind,
            hasInputs: false,
            mergeAt,
            deferRef
        });
    }
    isInGroupsOrTheirAncestors(toCheck, conditions) {
        const stack = conditions.concat();
        while (stack.length > 0) {
            const group = stack.pop();
            if (toCheck === group) {
                return true;
            }
            stack.push(...group.parentGroups());
        }
        return false;
    }
    typeForFetchInputs(name) {
        const type = this.supergraphSchema.type(name);
        (0, federation_internals_1.assert)(type, `Type ${name} should exist in the supergraph`);
        (0, federation_internals_1.assert)((0, federation_internals_1.isCompositeType)(type), `Type ${type} should be a composite, but got ${type.kind}`);
        return type;
    }
    newKeyFetchGroup({ subgraphName, mergeAt, deferRef, }) {
        const parentType = this.federationMetadata(subgraphName).entityType();
        (0, federation_internals_1.assert)(parentType, () => `Subgraph ${subgraphName} has no entities defined`);
        return this.newFetchGroup({
            subgraphName,
            parentType,
            hasInputs: true,
            rootKind: 'query',
            mergeAt,
            deferRef
        });
    }
    remove(toRemove) {
        this.onModification();
        const children = toRemove.children().concat();
        const parents = toRemove.parents().concat();
        for (const child of children) {
            (0, federation_internals_1.assert)(child.parents().length > 1, () => `Cannot remove ${toRemove} as it is the *only* parent of ${child}`);
            toRemove.removeChild(child);
        }
        for (const parent of parents) {
            parent.group.removeChild(toRemove);
        }
        this.groups.splice(toRemove.index, 1);
        for (let i = toRemove.index; i < this.groups.length; i++) {
            --this.groups[i].index;
        }
    }
    onModification() {
        this.isReduced = false;
        this.isOptimized = false;
    }
    reduce() {
        if (this.isReduced) {
            return;
        }
        for (const group of this.groups) {
            this.dfsRemoveRedundantEdges(group);
        }
        this.isReduced = true;
    }
    reduceAndOptimize() {
        if (this.isOptimized) {
            return;
        }
        this.reduce();
        for (const group of this.rootGroups.values()) {
            this.removeEmptyGroups(group);
        }
        for (const group of this.rootGroups.values()) {
            this.removeUselessGroups(group);
        }
        for (const group of this.rootGroups.values()) {
            this.mergeChildFetchesForSameSubgraphAndPath(group);
        }
        this.mergeFetchesToSameSubgraphAndSameInputs();
        this.isOptimized = true;
    }
    removeEmptyGroups(group) {
        const children = group.children().concat();
        if (group.selection.isEmpty() && !this.isRootGroup(group)) {
            this.remove(group);
        }
        for (const g of children) {
            this.removeEmptyGroups(g);
        }
    }
    removeUselessGroups(group) {
        const children = group.children().concat();
        for (const child of children) {
            this.removeUselessGroups(child);
        }
        if (group.isUseless()) {
            if (group.children().length === 0) {
                this.remove(group);
            }
            else {
                const parents = group.parents();
                const parent = parents[0];
                if (parents.length === 1 && parent.path) {
                    parent.group.removeUselessChild(group);
                }
            }
        }
    }
    mergeChildFetchesForSameSubgraphAndPath(group) {
        const children = group.children();
        if (children.length > 1) {
            for (let i = 0; i < children.length; i++) {
                const gi = children[i];
                let j = i + 1;
                while (j < children.length) {
                    const gj = children[j];
                    if (gi.canMergeSiblingIn(gj)) {
                        gi.mergeSiblingIn(gj);
                        this.dfsRemoveRedundantEdges(gi);
                    }
                    else {
                        ++j;
                    }
                }
            }
        }
        for (const g of children) {
            this.mergeChildFetchesForSameSubgraphAndPath(g);
        }
    }
    mergeFetchesToSameSubgraphAndSameInputs() {
        const bySubgraphs = new federation_internals_1.MultiMap();
        for (const group of this.groups) {
            if (group.inputs) {
                bySubgraphs.add(group.subgraphAndMergeAtKey, group);
            }
        }
        for (const groups of bySubgraphs.values()) {
            if (groups.length <= 1) {
                continue;
            }
            const toMergeBuckets = [];
            while (groups.length > 1) {
                const group = groups.pop();
                const bucket = [group];
                let i = 0;
                while (i < groups.length) {
                    const current = groups[i];
                    if (group.deferRef === current.deferRef && group.inputs.equals(current.inputs)) {
                        bucket.push(current);
                        groups.splice(i, 1);
                    }
                    else {
                        ++i;
                    }
                }
                if (bucket.length > 1) {
                    toMergeBuckets.push(bucket);
                }
            }
            for (const bucket of toMergeBuckets) {
                const group = bucket.pop();
                for (const other of bucket) {
                    group.mergeInWithAllDependencies(other);
                }
            }
        }
        this.reduce();
    }
    dfsRemoveRedundantEdges(from) {
        for (const startVertex of from.children()) {
            const stack = startVertex.children().concat();
            while (stack.length > 0) {
                const v = stack.pop();
                from.removeChild(v);
                stack.push(...v.children());
            }
        }
    }
    extractChildrenAndDeferredDependencies(group) {
        const children = [];
        const deferredGroups = new federation_internals_1.SetMultiMap();
        for (const child of group.children()) {
            if (group.deferRef === child.deferRef) {
                children.push(child);
            }
            else {
                (0, federation_internals_1.assert)(child.deferRef, () => `${group} has deferRef "${group.deferRef}", so its child ${child} cannot have a top-level deferRef`);
                if (!group.selection.isEmpty()) {
                    if (!group.id) {
                        group.id = String(this.fetchIdGen++);
                    }
                    this.deferTracking.addDependency(child.deferRef, group.id);
                }
                deferredGroups.add(child.deferRef, child);
            }
        }
        return { children, deferredGroups };
    }
    processGroup(processor, group, handledConditions) {
        const conditions = (0, conditions_1.updatedConditions)(group.conditions(), handledConditions);
        const newHandledConditions = (0, conditions_1.mergeConditions)(conditions, handledConditions);
        const { children, deferredGroups } = this.extractChildrenAndDeferredDependencies(group);
        const processed = processor.onFetchGroup(group, newHandledConditions);
        if (children.length == 0) {
            return { main: processor.onConditions(conditions, processed), state: ProcessingState.empty(), deferredGroups };
        }
        const state = ProcessingState.forChildrenOfProcessedGroup(group, children);
        if (state.next.length > 0) {
            const { mainSequence, newState, deferredGroups: allDeferredGroups, } = this.processRootMainGroups({
                processor,
                state,
                rootsAreParallel: true,
                initialDeferredGroups: deferredGroups,
                handledConditions: newHandledConditions,
            });
            return {
                main: processor.onConditions(conditions, processor.reduceSequence([processed].concat(mainSequence))),
                state: newState,
                deferredGroups: allDeferredGroups,
            };
        }
        else {
            return {
                main: processor.onConditions(conditions, processed),
                state,
                deferredGroups,
            };
        }
    }
    processGroups(processor, state, processInParallel, handledConditions) {
        const processedNodes = [];
        const allDeferredGroups = new federation_internals_1.SetMultiMap();
        let newState = state.withOnlyUnhandled();
        for (const group of state.next) {
            const { main, deferredGroups, state: stateAfterGroup } = this.processGroup(processor, group, handledConditions);
            processedNodes.push(main);
            allDeferredGroups.addAll(deferredGroups);
            newState = newState.mergeWith(stateAfterGroup);
        }
        return {
            processed: processInParallel ? processor.reduceParallel(processedNodes) : processor.reduceSequence(processedNodes),
            newState: newState.updateForProcessedGroups(state.next),
            deferredGroups: allDeferredGroups,
        };
    }
    processRootMainGroups({ processor, state, rootsAreParallel, initialDeferredGroups, handledConditions, }) {
        const mainSequence = [];
        const allDeferredGroups = initialDeferredGroups
            ? new federation_internals_1.SetMultiMap(initialDeferredGroups)
            : new federation_internals_1.SetMultiMap();
        let processInParallel = rootsAreParallel;
        while (state.next.length > 0) {
            const { processed, newState, deferredGroups } = this.processGroups(processor, state, processInParallel, handledConditions);
            processInParallel = true;
            mainSequence.push(processed);
            state = newState;
            allDeferredGroups.addAll(deferredGroups);
        }
        return {
            mainSequence,
            newState: state,
            deferredGroups: allDeferredGroups,
        };
    }
    processRootGroups({ processor, rootGroups, rootsAreParallel = true, currentDeferRef, otherDeferGroups = undefined, handledConditions, }) {
        var _a;
        const { mainSequence, newState, deferredGroups, } = this.processRootMainGroups({ processor, rootsAreParallel, state: ProcessingState.ofReadyGroups(rootGroups), handledConditions });
        (0, federation_internals_1.assert)(newState.next.length === 0, () => `Should not have left some ready groups, but got ${newState.next}`);
        (0, federation_internals_1.assert)(newState.unhandled.length == 0, () => `Root groups:\n${rootGroups.map((g) => ` - ${g}`).join('\n')}\nshould have no remaining groups unhandled, but got: ${printUnhandled(newState.unhandled)}`);
        const allDeferredGroups = new federation_internals_1.SetMultiMap();
        if (otherDeferGroups) {
            allDeferredGroups.addAll(otherDeferGroups);
        }
        allDeferredGroups.addAll(deferredGroups);
        const defersInCurrent = this.deferTracking.defersInParent(currentDeferRef);
        const handledDefersInCurrent = new Set(defersInCurrent.map((d) => d.label));
        const unhandledDefersInCurrent = (0, federation_internals_1.mapKeys)(allDeferredGroups).filter((label) => !handledDefersInCurrent.has(label));
        let unhandledDeferGroups = undefined;
        if (unhandledDefersInCurrent.length > 0) {
            unhandledDeferGroups = new federation_internals_1.SetMultiMap();
            for (const label of unhandledDefersInCurrent) {
                unhandledDeferGroups.set(label, allDeferredGroups.get(label));
            }
        }
        const allDeferred = [];
        for (const defer of defersInCurrent) {
            const groups = (_a = allDeferredGroups.get(defer.label)) !== null && _a !== void 0 ? _a : [];
            const { mainSequence: mainSequenceOfDefer, deferred: deferredOfDefer } = this.processRootGroups({
                processor,
                rootGroups: Array.from(groups),
                rootsAreParallel: true,
                currentDeferRef: defer.label,
                otherDeferGroups: unhandledDeferGroups,
                handledConditions,
            });
            const mainReduced = processor.reduceSequence(mainSequenceOfDefer);
            const processed = deferredOfDefer.length === 0
                ? mainReduced
                : processor.reduceDefer(mainReduced, defer.subselection.get(), deferredOfDefer);
            allDeferred.push(processor.reduceDeferred(defer, processed));
        }
        return { mainSequence, deferred: allDeferred };
    }
    process(processor, rootKind) {
        this.reduceAndOptimize();
        const { mainSequence, deferred } = this.processRootGroups({
            processor,
            rootGroups: this.rootGroups.values(),
            rootsAreParallel: rootKind === 'query',
            handledConditions: true,
        });
        return {
            main: processor.reduceSequence(mainSequence),
            deferred,
        };
    }
    dumpOnConsole(msg) {
        if (msg) {
            console.log(msg);
        }
        console.log('Groups:');
        for (const group of this.groups) {
            console.log(`  ${group}`);
        }
        console.log('Children relationships:');
        for (const group of this.groups) {
            const children = group.children();
            if (children.length === 1) {
                console.log(`  [${group.index}] => [ ${children[0]} ]`);
            }
            else if (children.length !== 0) {
                console.log(`  [${group.index}] => [\n    ${children.join('\n    ')}\n  ]`);
            }
        }
        console.log('Parent relationships:');
        const printParentRelation = (rel) => (rel.path ? `${rel.group} (path: [${rel.path.join(', ')}])` : rel.group.toString());
        for (const group of this.groups) {
            const parents = group.parents();
            if (parents.length === 1) {
                console.log(`  [${group.index}] => [ ${printParentRelation(parents[0])} ]`);
            }
            else if (parents.length !== 0) {
                console.log(`  [${group.index}] => [\n    ${parents.map(printParentRelation).join('\n    ')}\n  ]`);
            }
        }
        console.log('--------');
    }
    toString() {
        return this.rootGroups.values().map(g => this.toStringInternal(g, "")).join('\n');
    }
    toStringInternal(group, indent) {
        const children = group.children();
        return [indent + group.subgraphName + ' <- ' + children.map((child) => child.subgraphName).join(', ')]
            .concat(children
            .flatMap(g => g.children().length == 0
            ? []
            : this.toStringInternal(g, indent + "  ")))
            .join('\n');
    }
}
class QueryPlanner {
    constructor(supergraph, config) {
        this.supergraph = supergraph;
        this._defaultOverrideConditions = new Map();
        this.interfaceTypesWithInterfaceObjects = new Set();
        this.inconsistentAbstractTypesRuntimes = new Set();
        this.config = (0, config_1.enforceQueryPlannerConfigDefaults)(config);
        (0, config_1.validateQueryPlannerConfig)(this.config);
        this.federatedQueryGraph = (0, query_graphs_1.buildFederatedQueryGraph)(supergraph, true);
        this.collectInterfaceTypesWithInterfaceObjects();
        this.collectInconsistentAbstractTypesRuntimes();
        this.collectAllOverrideLabels();
        if (this.config.debug.bypassPlannerForSingleSubgraph && this.config.incrementalDelivery.enableDefer) {
            throw new Error(`Cannot use the "debug.bypassPlannerForSingleSubgraph" query planner option when @defer support is enabled`);
        }
    }
    collectInterfaceTypesWithInterfaceObjects() {
        const isInterfaceObject = (name, schema) => {
            const typeInSchema = schema.type(name);
            return !!typeInSchema && (0, federation_internals_1.isInterfaceObjectType)(typeInSchema);
        };
        for (const itfType of this.supergraph.schema.interfaceTypes()) {
            if ((0, federation_internals_1.mapValues)(this.federatedQueryGraph.sources).some((s) => isInterfaceObject(itfType.name, s))) {
                this.interfaceTypesWithInterfaceObjects.add(itfType.name);
            }
        }
    }
    collectInconsistentAbstractTypesRuntimes() {
        const subgraphs = (0, federation_internals_1.mapValues)(this.federatedQueryGraph.sources);
        const isInconsistent = (name) => {
            let expectedRuntimes = undefined;
            for (const subgraph of subgraphs) {
                const typeInSubgraph = subgraph.type(name);
                if (!typeInSubgraph || (0, federation_internals_1.isObjectType)(typeInSubgraph)) {
                    continue;
                }
                (0, federation_internals_1.assert)((0, federation_internals_1.isAbstractType)(typeInSubgraph), () => `Expected type ${typeInSubgraph} to be abstract but is ${typeInSubgraph.kind}`);
                const runtimes = (0, federation_internals_1.possibleRuntimeTypes)(typeInSubgraph);
                if (!expectedRuntimes) {
                    expectedRuntimes = new Set(runtimes.map((t) => t.name));
                }
                else if (runtimes.length !== expectedRuntimes.size || runtimes.some((t) => !(expectedRuntimes === null || expectedRuntimes === void 0 ? void 0 : expectedRuntimes.has(t.name)))) {
                    return true;
                }
            }
            return false;
        };
        for (const type of this.supergraph.schema.types()) {
            if (!(0, federation_internals_1.isAbstractType)(type)) {
                continue;
            }
            if ((0, federation_internals_1.isAbstractType)(type) && isInconsistent(type.name)) {
                this.inconsistentAbstractTypesRuntimes.add(type.name);
            }
        }
    }
    collectAllOverrideLabels() {
        var _a, _b;
        const applications = (_b = (_a = this.supergraph.schema.directives()
            .find((d) => d.name === 'join__field')) === null || _a === void 0 ? void 0 : _a.applications()) !== null && _b !== void 0 ? _b : new Set();
        this._defaultOverrideConditions = new Map(Array.from(applications)
            .map((application) => application.arguments().overrideLabel)
            .filter(Boolean)
            .map(label => [label, false]));
    }
    buildQueryPlan(operation, options) {
        var _a;
        if (operation.selectionSet.isEmpty()) {
            return { kind: 'QueryPlan' };
        }
        if (!(options === null || options === void 0 ? void 0 : options.recursiveSelectionsLimitDisabled)) {
            (0, recursiveSelectionsLimit_1.validateRecursiveSelections)(operation);
        }
        const isSubscription = operation.rootKind === 'subscription';
        const statistics = {
            evaluatedPlanCount: 0,
        };
        this._lastGeneratedPlanStatistics = statistics;
        if (this.config.debug.bypassPlannerForSingleSubgraph) {
            const subgraphs = (0, federation_internals_1.mapKeys)(this.federatedQueryGraph.sources).filter((name) => name !== query_graphs_1.FEDERATED_GRAPH_ROOT_SOURCE);
            if (subgraphs.length === 1) {
                const operationDocument = (0, federation_internals_1.operationToDocument)(operation);
                const node = {
                    kind: 'Fetch',
                    serviceName: subgraphs[0],
                    variableUsages: operation.variableDefinitions.definitions().map(v => v.variable.name),
                    operation: (0, graphql_1.stripIgnoredCharacters)((0, graphql_1.print)(operationDocument)),
                    operationKind: schemaRootKindToOperationKind(operation.rootKind),
                    operationName: operation.name,
                    operationDocumentNode: this.config.exposeDocumentNodeInFetchNode ? operationDocument : undefined,
                };
                return { kind: 'QueryPlan', node };
            }
        }
        const reuseQueryFragments = (_a = this.config.reuseQueryFragments) !== null && _a !== void 0 ? _a : true;
        let fragments = operation.fragments;
        if (fragments && !fragments.isEmpty() && reuseQueryFragments) {
            fragments = addTypenameFieldForAbstractTypesInNamedFragments(fragments);
        }
        else {
            fragments = undefined;
        }
        operation = operation.expandAllFragments();
        operation = withoutIntrospection(operation);
        operation = this.withSiblingTypenameOptimizedAway(operation);
        let assignedDeferLabels = undefined;
        let hasDefers = false;
        let deferConditions = undefined;
        if (this.config.incrementalDelivery.enableDefer) {
            ({ operation, hasDefers, assignedDeferLabels, deferConditions } = operation.withNormalizedDefer());
            if (isSubscription && hasDefers) {
                throw new Error(`@defer is not supported on subscriptions`);
            }
        }
        else {
            operation = operation.withoutDefer();
        }
        debug.group(() => `Computing plan for\n${operation}`);
        if (operation.selectionSet.isEmpty()) {
            debug.groupEnd('Empty plan');
            return { kind: 'QueryPlan' };
        }
        const root = this.federatedQueryGraph.root(operation.rootKind);
        (0, federation_internals_1.assert)(root, () => `Shouldn't have a ${operation.rootKind} operation if the subgraphs don't have a ${operation.rootKind} root`);
        const processor = fetchGroupToPlanProcessor({
            config: this.config,
            variableDefinitions: operation.variableDefinitions,
            fragments: fragments ? new RebasedFragments(fragments) : undefined,
            operationName: operation.name,
            directives: operation.appliedDirectives,
            assignedDeferLabels,
        });
        const overrideConditions = new Map(this._defaultOverrideConditions);
        if (options === null || options === void 0 ? void 0 : options.overrideConditions) {
            for (const [label, value] of options.overrideConditions) {
                overrideConditions.set(label, value);
            }
        }
        const parameters = {
            supergraphSchema: this.supergraph.schema,
            federatedQueryGraph: this.federatedQueryGraph,
            operation,
            processor,
            root,
            statistics,
            inconsistentAbstractTypesRuntimes: this.inconsistentAbstractTypesRuntimes,
            config: this.config,
            overrideConditions,
        };
        let rootNode;
        let nonLocalSelectionsState = (options === null || options === void 0 ? void 0 : options.nonLocalSelectionsLimitDisabled)
            ? null
            : new query_graphs_1.NonLocalSelectionsState();
        if (deferConditions && deferConditions.size > 0) {
            (0, federation_internals_1.assert)(hasDefers, 'Should not have defer conditions without @defer');
            rootNode = computePlanForDeferConditionals({
                parameters,
                deferConditions,
                nonLocalSelectionsState,
            });
        }
        else {
            rootNode = computePlanInternal({
                parameters,
                hasDefers,
                nonLocalSelectionsState,
            });
        }
        if (rootNode && isSubscription) {
            switch (rootNode.kind) {
                case 'Fetch':
                    {
                        rootNode = {
                            kind: 'Subscription',
                            primary: rootNode,
                        };
                    }
                    break;
                case 'Sequence':
                    {
                        const [primary, ...rest] = rootNode.nodes;
                        (0, federation_internals_1.assert)(primary.kind === 'Fetch', 'Primary node of a subscription is not a Fetch');
                        rootNode = {
                            kind: 'Subscription',
                            primary,
                            rest: {
                                kind: 'Sequence',
                                nodes: rest,
                            },
                        };
                    }
                    break;
                default:
                    throw new Error(`Unexpected top level PlanNode kind: '${rootNode.kind}' when processing subscription`);
            }
        }
        debug.groupEnd('Query plan computed');
        return { kind: 'QueryPlan', node: rootNode };
    }
    optimizeSiblingTypenames(selectionSet) {
        const selections = selectionSet.selections();
        const parentType = selectionSet.parentType;
        const parentMaybeInterfaceObject = this.interfaceTypesWithInterfaceObjects.has(parentType.name);
        let updatedSelections = undefined;
        let typenameSelection = undefined;
        let firstFieldSelection = undefined;
        let firstFieldIndex = -1;
        for (let i = 0; i < selections.length; i++) {
            const selection = selections[i];
            let updated;
            if (!typenameSelection
                && selection.kind === 'FieldSelection'
                && selection.isPlainTypenameField()
                && !parentMaybeInterfaceObject) {
                updated = undefined;
                typenameSelection = selection;
            }
            else {
                const updatedSubSelection = selection.selectionSet ? this.optimizeSiblingTypenames(selection.selectionSet) : undefined;
                if (updatedSubSelection === selection.selectionSet) {
                    updated = selection;
                }
                else {
                    updated = selection.withUpdatedSelectionSet(updatedSubSelection);
                }
                if (!firstFieldSelection && updated.kind === 'FieldSelection') {
                    firstFieldSelection = updated;
                    firstFieldIndex = updatedSelections
                        ? updatedSelections.length
                        : i;
                }
            }
            if (updated !== selection && !updatedSelections) {
                updatedSelections = [];
                for (let j = 0; j < i; j++) {
                    updatedSelections.push(selections[j]);
                }
            }
            if (updatedSelections && !!updated) {
                updatedSelections.push(updated);
            }
        }
        if (!updatedSelections || updatedSelections.length === 0) {
            return selectionSet;
        }
        if (typenameSelection) {
            if (firstFieldSelection) {
                updatedSelections[firstFieldIndex] = firstFieldSelection.withAttachment(SIBLING_TYPENAME_KEY, typenameSelection.element.alias ? typenameSelection.element.alias : '');
            }
            else {
                updatedSelections = [typenameSelection].concat(updatedSelections);
            }
        }
        return new federation_internals_1.SelectionSetUpdates().add(updatedSelections).toSelectionSet(selectionSet.parentType);
    }
    withSiblingTypenameOptimizedAway(operation) {
        const updatedSelectionSet = this.optimizeSiblingTypenames(operation.selectionSet);
        if (updatedSelectionSet === operation.selectionSet) {
            return operation;
        }
        return new federation_internals_1.Operation(operation.schema(), operation.rootKind, updatedSelectionSet, operation.variableDefinitions, operation.fragments, operation.name, operation.appliedDirectives);
    }
    lastGeneratedPlanStatistics() {
        return this._lastGeneratedPlanStatistics;
    }
}
exports.QueryPlanner = QueryPlanner;
function computePlanInternal({ parameters, hasDefers, nonLocalSelectionsState, }) {
    let main = undefined;
    let primarySelection = undefined;
    let deferred = [];
    const { operation, processor } = parameters;
    if (operation.rootKind === 'mutation') {
        const dependencyGraphs = computeRootSerialDependencyGraph(parameters, hasDefers, nonLocalSelectionsState);
        for (const dependencyGraph of dependencyGraphs) {
            const { main: localMain, deferred: localDeferred } = dependencyGraph.process(processor, operation.rootKind);
            main = main ? processor.reduceSequence([main, localMain]) : localMain;
            deferred = deferred.concat(localDeferred);
            const newSelection = dependencyGraph.deferTracking.primarySelection;
            if (newSelection) {
                if (primarySelection) {
                    primarySelection.updates().add(newSelection.get());
                }
                else {
                    primarySelection = newSelection.clone();
                }
            }
        }
    }
    else {
        const dependencyGraph = computeRootParallelDependencyGraph(parameters, 0, hasDefers, nonLocalSelectionsState);
        ({ main, deferred } = dependencyGraph.process(processor, operation.rootKind));
        primarySelection = dependencyGraph.deferTracking.primarySelection;
    }
    if (deferred.length > 0) {
        (0, federation_internals_1.assert)(primarySelection, 'Should have had a primary selection created');
        return processor.reduceDefer(main, primarySelection.get(), deferred);
    }
    return main;
}
function computePlanForDeferConditionals({ parameters, deferConditions, nonLocalSelectionsState, }) {
    return generateConditionNodes(parameters.operation, Array.from(deferConditions.entries()), 0, (op) => computePlanInternal({
        parameters: {
            ...parameters,
            operation: op,
        },
        hasDefers: true,
        nonLocalSelectionsState,
    }));
}
function generateConditionNodes(operation, conditions, idx, onFinalOperation) {
    if (idx >= conditions.length) {
        return onFinalOperation(operation);
    }
    const [variable, labels] = conditions[idx];
    const ifOperation = operation;
    const elseOperation = operation.withoutDefer(labels);
    return {
        kind: 'Condition',
        condition: variable,
        ifClause: generateConditionNodes(ifOperation, conditions, idx + 1, onFinalOperation),
        elseClause: generateConditionNodes(elseOperation, conditions, idx + 1, onFinalOperation),
    };
}
function isIntrospectionSelection(selection) {
    return selection.kind == 'FieldSelection' && selection.element.definition.isIntrospectionField();
}
function mapOptionsToSelections(selectionSet, options) {
    return selectionSet.selectionsInReverseOrder().map(node => [node, options]);
}
function possiblePlans(closedBranches) {
    let totalCombinations = BigInt(1);
    for (let i = 0; i < closedBranches.length; ++i) {
        const eltSize = BigInt(closedBranches[i].length);
        if (eltSize === BigInt(0)) {
            return BigInt(0);
        }
        totalCombinations *= eltSize;
    }
    return totalCombinations;
}
function sum(arr) {
    return arr.reduce((a, b) => a + b, 0);
}
function selectionCost(selection, depth = 1) {
    return selection ? selection.selections().reduce((prev, curr) => prev + depth + selectionCost(curr.selectionSet, depth + 1), 0) : 0;
}
function withoutIntrospection(operation) {
    if (!operation.selectionSet.selections().some(isIntrospectionSelection)) {
        return operation;
    }
    return new federation_internals_1.Operation(operation.schema(), operation.rootKind, operation.selectionSet.lazyMap((s) => isIntrospectionSelection(s) ? undefined : s), operation.variableDefinitions, operation.fragments, operation.name, operation.appliedDirectives);
}
function computeRootParallelDependencyGraph(parameters, startFetchIdGen, hasDefer, nonLocalSelectionsState) {
    return computeRootParallelBestPlan(parameters, parameters.operation.selectionSet, startFetchIdGen, hasDefer, nonLocalSelectionsState)[0];
}
function computeRootParallelBestPlan(parameters, selection, startFetchIdGen, hasDefers, nonLocalSelectionsState) {
    const planningTraversal = new QueryPlanningTraversal(parameters, selection, startFetchIdGen, hasDefers, parameters.root.rootKind, defaultCostFunction, query_graphs_1.emptyContext, parameters.config.typeConditionedFetching, nonLocalSelectionsState);
    const plan = planningTraversal.findBestPlan();
    return plan !== null && plan !== void 0 ? plan : createEmptyPlan(parameters);
}
function createEmptyPlan(parameters) {
    const { supergraphSchema, federatedQueryGraph, root, config } = parameters;
    return [
        FetchDependencyGraph.create(supergraphSchema, federatedQueryGraph, 0, undefined, config.generateQueryFragments),
        query_graphs_1.PathTree.createOp(federatedQueryGraph, root),
        0
    ];
}
function onlyRootSubgraph(graph) {
    const subgraphs = graph.rootSubgraphs();
    (0, federation_internals_1.assert)(subgraphs.length === 1, () => `${graph} should have only one root, but has [${graph.rootSubgraphs()}]`);
    return subgraphs[0];
}
function computeRootSerialDependencyGraph(parameters, hasDefers, nonLocalSelectionsState) {
    const { supergraphSchema, federatedQueryGraph, operation, root } = parameters;
    const rootType = hasDefers ? supergraphSchema.schemaDefinition.rootType(root.rootKind) : undefined;
    const splittedRoots = splitTopLevelFields(operation.selectionSet);
    const graphs = [];
    let startingFetchId = 0;
    let [prevDepGraph, prevPaths] = computeRootParallelBestPlan(parameters, splittedRoots[0], startingFetchId, hasDefers, nonLocalSelectionsState);
    let prevSubgraph = onlyRootSubgraph(prevDepGraph);
    for (let i = 1; i < splittedRoots.length; i++) {
        const [newDepGraph, newPaths] = computeRootParallelBestPlan(parameters, splittedRoots[i], prevDepGraph.nextFetchId(), hasDefers, nonLocalSelectionsState);
        const newSubgraph = onlyRootSubgraph(newDepGraph);
        if (prevSubgraph === newSubgraph) {
            prevPaths = prevPaths.concat(newPaths);
            prevDepGraph = computeRootFetchGroups(FetchDependencyGraph.create(supergraphSchema, federatedQueryGraph, startingFetchId, rootType, parameters.config.generateQueryFragments), prevPaths, root.rootKind, parameters.config.typeConditionedFetching);
        }
        else {
            startingFetchId = prevDepGraph.nextFetchId();
            graphs.push(prevDepGraph);
            [prevDepGraph, prevPaths, prevSubgraph] = [newDepGraph, newPaths, newSubgraph];
        }
    }
    graphs.push(prevDepGraph);
    return graphs;
}
function splitTopLevelFields(selectionSet) {
    return selectionSet.selections().flatMap(selection => {
        if (selection.kind === 'FieldSelection') {
            return [(0, federation_internals_1.selectionSetOf)(selectionSet.parentType, selection)];
        }
        else {
            return splitTopLevelFields(selection.selectionSet).map(s => (0, federation_internals_1.selectionSetOfElement)(selection.element, s));
        }
    });
}
function toValidGraphQLName(subgraphName) {
    const sanitized = subgraphName
        .replace(/-/ig, '_')
        .replace(/[^_0-9A-Za-z]/ig, '');
    return sanitized.match(/^[0-9].*/i) ? '_' + sanitized : sanitized;
}
function sanitizeAndPrintSubselection(subSelection) {
    var _a;
    return (_a = subSelection.withoutEmptyBranches()) === null || _a === void 0 ? void 0 : _a.toString();
}
function fetchGroupToPlanProcessor({ config, variableDefinitions, fragments, operationName, directives, assignedDeferLabels, }) {
    let counter = 0;
    return {
        onFetchGroup: (group, handledConditions) => {
            const opName = operationName ? `${operationName}__${toValidGraphQLName(group.subgraphName)}__${counter++}` : undefined;
            return group.toPlanNode(config, handledConditions, variableDefinitions, fragments, opName, directives);
        },
        onConditions: (conditions, value) => {
            if (!value) {
                return undefined;
            }
            if ((0, conditions_1.isConstantCondition)(conditions)) {
                return conditions ? value : undefined;
            }
            else {
                return conditions.reduce((node, condition) => ({
                    kind: 'Condition',
                    condition: condition.variable.name,
                    ifClause: condition.negated ? undefined : node,
                    elseClause: condition.negated ? node : undefined,
                }), value);
            }
        },
        reduceParallel: (values) => flatWrapNodes('Parallel', values),
        reduceSequence: (values) => flatWrapNodes('Sequence', values),
        reduceDeferred: (deferInfo, value) => ({
            depends: [...deferInfo.dependencies].map((id) => ({ id })),
            label: (assignedDeferLabels === null || assignedDeferLabels === void 0 ? void 0 : assignedDeferLabels.has(deferInfo.label)) ? undefined : deferInfo.label,
            queryPath: (0, federation_internals_1.operationPathToStringPath)(deferInfo.path.full()),
            subselection: deferInfo.deferred.size === 0 ? sanitizeAndPrintSubselection(deferInfo.subselection.get()) : undefined,
            node: value,
        }),
        reduceDefer: (main, subselection, deferredBlocks) => ({
            kind: 'Defer',
            primary: {
                subselection: sanitizeAndPrintSubselection(subselection),
                node: main,
            },
            deferred: deferredBlocks,
        }),
    };
}
function flatWrapNodes(kind, nodes) {
    const filteredNodes = nodes.filter((n) => !!n);
    if (filteredNodes.length === 0) {
        return undefined;
    }
    if (filteredNodes.length === 1) {
        return filteredNodes[0];
    }
    return {
        kind,
        nodes: filteredNodes.flatMap((n) => n.kind === kind ? n.nodes : [n]),
    };
}
function addTypenameFieldForAbstractTypesInNamedFragments(fragments) {
    (0, federation_internals_1.assert)(!fragments.isEmpty(), 'Should not pass empty fragments to this method');
    const updated = fragments.mapToExpandedSelectionSets(addTypenameFieldForAbstractTypes);
    (0, federation_internals_1.assert)(updated, 'No fragments should have been removed');
    return updated;
}
function removeUnneededTopLevelFragmentDirectives(selectionSet, unneededDirectives) {
    return selectionSet.lazyMap((selection) => {
        if (selection.kind !== 'FragmentSelection') {
            return selection;
        }
        const fragment = selection.element;
        const fragmentType = fragment.typeCondition;
        if (!fragmentType) {
            return selection;
        }
        let neededDirectives = [];
        if (fragment.appliedDirectives.length > 0) {
            neededDirectives = (0, federation_internals_1.directiveApplicationsSubstraction)(fragment.appliedDirectives, unneededDirectives);
        }
        const updated = removeUnneededTopLevelFragmentDirectives(selection.selectionSet, unneededDirectives);
        if (neededDirectives.length === fragment.appliedDirectives.length) {
            return selection.selectionSet === updated ? selection : selection.withUpdatedSelectionSet(updated);
        }
        return selection.withUpdatedComponents(fragment.withUpdatedDirectives(neededDirectives), updated);
    });
}
function schemaRootKindToOperationKind(operation) {
    switch (operation) {
        case "query": return graphql_1.OperationTypeNode.QUERY;
        case "mutation": return graphql_1.OperationTypeNode.MUTATION;
        case "subscription": return graphql_1.OperationTypeNode.SUBSCRIPTION;
    }
}
function findAndRemoveInPlace(predicate, array) {
    const idx = array.findIndex((v) => predicate(v));
    if (idx >= 0) {
        array.splice(idx, 1);
    }
    return idx;
}
function sameMergeAt(m1, m2) {
    if (!m1) {
        return !m2;
    }
    if (!m2) {
        return false;
    }
    return (0, federation_internals_1.arrayEquals)(m1, m2);
}
function concatPathsInParents(first, second) {
    return first && second ? (0, federation_internals_1.concatOperationPaths)(first, second) : undefined;
}
function samePathsInParents(first, second) {
    if (!first) {
        return !second;
    }
    return !!second && (0, federation_internals_1.sameOperationPaths)(first, second);
}
function computeRootFetchGroups(dependencyGraph, pathTree, rootKind, typeConditionedFetching) {
    for (const [edge, _trigger, _conditions, child] of pathTree.childElements()) {
        (0, federation_internals_1.assert)(edge !== null, `The root edge should not be null`);
        const subgraphName = edge.tail.source;
        const rootType = edge.tail.type;
        const group = dependencyGraph.getOrCreateRootFetchGroup({ subgraphName, rootKind, parentType: rootType });
        const rootTypeInSupergraph = dependencyGraph.supergraphSchemaType(rootType.name);
        computeGroupsForTree({
            dependencyGraph,
            pathTree: child,
            startGroup: group,
            initialGroupPath: GroupPath.empty(typeConditionedFetching, rootTypeInSupergraph),
            initialDeferContext: emptyDeferContext,
        });
    }
    return dependencyGraph;
}
function computeNonRootFetchGroups(dependencyGraph, pathTree, rootKind, typeConditionedFetching) {
    const subgraphName = pathTree.vertex.source;
    const rootType = pathTree.vertex.type;
    (0, federation_internals_1.assert)((0, federation_internals_1.isCompositeType)(rootType), () => `Should not have condition on non-selectable type ${rootType}`);
    const group = dependencyGraph.getOrCreateRootFetchGroup({ subgraphName, rootKind, parentType: rootType });
    const rootTypeInSupergraph = dependencyGraph.supergraphSchemaType(rootType.name);
    computeGroupsForTree({
        dependencyGraph,
        pathTree,
        startGroup: group,
        initialGroupPath: GroupPath.empty(typeConditionedFetching, rootTypeInSupergraph),
        initialDeferContext: emptyDeferContext,
    });
    return dependencyGraph;
}
function wrapInputsSelections(wrappingType, selections, context) {
    return wrapSelectionWithTypeAndConditions(wrappingType, selections, (fragment, currentSeletions) => (0, federation_internals_1.selectionSetOf)(fragment.parentType, (0, federation_internals_1.selectionOfElement)(fragment, currentSeletions)), context);
}
function createFetchInitialPath(supergraphSchema, wrappingType, context) {
    const rebasedType = supergraphSchema.type(wrappingType.name);
    (0, federation_internals_1.assert)(rebasedType && (0, federation_internals_1.isCompositeType)(rebasedType), () => `${wrappingType} should be composite in the supergraph but got ${rebasedType === null || rebasedType === void 0 ? void 0 : rebasedType.kind}`);
    return wrapSelectionWithTypeAndConditions(rebasedType, [], (fragment, path) => [fragment].concat(path), context);
}
function wrapSelectionWithTypeAndConditions(wrappingType, initialSelection, wrapInFragment, context) {
    if (context.conditionals.length === 0) {
        return wrapInFragment(new federation_internals_1.FragmentElement(wrappingType, wrappingType.name), initialSelection);
    }
    const { kind: name0, value: ifs0 } = context.conditionals[0];
    let updatedSelection = wrapInFragment(new federation_internals_1.FragmentElement(wrappingType, wrappingType.name, [new federation_internals_1.Directive(name0, { 'if': ifs0 })]), initialSelection);
    for (let i = 1; i < context.conditionals.length; i++) {
        const { kind: name, value: ifs } = context.conditionals[i];
        updatedSelection = wrapInFragment(new federation_internals_1.FragmentElement(wrappingType, wrappingType.name, [new federation_internals_1.Directive(name, { 'if': ifs })]), updatedSelection);
    }
    return updatedSelection;
}
function maybeSubstratPathPrefix(basePath, maybePrefix) {
    if (maybePrefix.length <= basePath.length && (0, federation_internals_1.sameOperationPaths)(maybePrefix, basePath.slice(0, maybePrefix.length))) {
        return basePath.slice(maybePrefix.length);
    }
    return undefined;
}
function updateCreatedGroups(createdGroups, ...newCreatedGroups) {
    for (const newGroup of newCreatedGroups) {
        if (!createdGroups.includes(newGroup)) {
            createdGroups.push(newGroup);
        }
    }
}
function computeGroupsForTree({ dependencyGraph, pathTree, startGroup, initialGroupPath, initialDeferContext, initialContext = query_graphs_1.emptyContext, initialContextsToConditionsGroups = new Map(), }) {
    const stack = [{
            tree: pathTree,
            group: startGroup,
            path: initialGroupPath,
            context: initialContext,
            deferContext: initialDeferContext,
            contextToConditionsGroups: initialContextsToConditionsGroups,
        }];
    const createdGroups = [];
    while (stack.length > 0) {
        const { tree, group, path, context, deferContext, contextToConditionsGroups } = stack.pop();
        if (tree.localSelections) {
            for (const selection of tree.localSelections) {
                group.addAtPath(path.inGroup(), selection);
                dependencyGraph.deferTracking.updateSubselection(deferContext, selection);
            }
        }
        if (tree.isLeaf()) {
            group.addAtPath(path.inGroup());
            dependencyGraph.deferTracking.updateSubselection(deferContext);
        }
        else {
            for (const [edge, operation, conditions, child, contextToSelection, parameterToContext] of tree.childElements(true)) {
                if ((0, query_graphs_1.isPathContext)(operation)) {
                    const newContext = operation;
                    (0, federation_internals_1.assert)(edge !== null, () => `Unexpected 'null' edge with no trigger at ${path}`);
                    if (edge.transition.kind === 'KeyResolution') {
                        (0, federation_internals_1.assert)(conditions, () => `Key edge ${edge} should have some conditions paths`);
                        const conditionsGroups = computeGroupsForTree({
                            dependencyGraph,
                            pathTree: conditions,
                            startGroup: group,
                            initialGroupPath: path,
                            initialDeferContext: deferContextForConditions(deferContext),
                        });
                        updateCreatedGroups(createdGroups, ...conditionsGroups);
                        const sourceType = edge.head.type;
                        const destType = edge.tail.type;
                        const pathInParent = path.inGroup();
                        const updatedDeferContext = deferContextAfterSubgraphJump(deferContext);
                        const newGroup = dependencyGraph.getOrCreateKeyFetchGroup({
                            subgraphName: edge.tail.source,
                            mergeAt: path.inResponse(),
                            type: destType,
                            parent: { group, path: pathInParent },
                            conditionsGroups,
                            deferRef: updatedDeferContext.activeDeferRef,
                        });
                        updateCreatedGroups(createdGroups, newGroup);
                        newGroup.addParents(conditionsGroups.map((conditionGroup) => {
                            const conditionGroupParents = conditionGroup.parents();
                            let path = undefined;
                            if (conditionGroupParents.length === 1 && conditionGroupParents[0].group === group && conditionGroupParents[0].path) {
                                path = maybeSubstratPathPrefix(conditionGroupParents[0].path, pathInParent);
                            }
                            return { group: conditionGroup, path };
                        }));
                        const inputType = dependencyGraph.typeForFetchInputs(sourceType.name);
                        const inputSelections = newCompositeTypeSelectionSet(inputType);
                        inputSelections.updates().add(edge.conditions);
                        newGroup.addInputs(wrapInputsSelections(inputType, inputSelections.get(), newContext), computeInputRewritesOnKeyFetch(inputType.name, destType));
                        group.addAtPath(path.inGroup().concat(new federation_internals_1.Field(sourceType.typenameField())));
                        stack.push({
                            tree: child,
                            group: newGroup,
                            path: path.forNewKeyFetch(createFetchInitialPath(dependencyGraph.supergraphSchema, edge.tail.type, newContext)),
                            context: newContext,
                            deferContext: updatedDeferContext,
                            contextToConditionsGroups,
                        });
                    }
                    else {
                        (0, federation_internals_1.assert)(edge.transition.kind === 'RootTypeResolution', () => `Unexpected non-collecting edge ${edge}`);
                        const rootKind = edge.transition.rootKind;
                        (0, federation_internals_1.assert)(!conditions, () => `Root type resolution edge ${edge} should not have conditions`);
                        (0, federation_internals_1.assert)((0, federation_internals_1.isObjectType)(edge.head.type) && (0, federation_internals_1.isObjectType)(edge.tail.type), () => `Expected an objects for the vertices of ${edge}`);
                        const type = edge.tail.type;
                        (0, federation_internals_1.assert)(type === type.schema().schemaDefinition.rootType(rootKind), () => `Expected ${type} to be the root ${rootKind} type, but that is ${type.schema().schemaDefinition.rootType(rootKind)}`);
                        if (path.inGroup().length > 0) {
                            group.addAtPath(path.inGroup().concat(new federation_internals_1.Field(edge.head.type.typenameField())));
                        }
                        const updatedDeferContext = deferContextAfterSubgraphJump(deferContext);
                        const newGroup = dependencyGraph.newRootTypeFetchGroup({
                            subgraphName: edge.tail.source,
                            rootKind,
                            parentType: type,
                            mergeAt: path.inResponse(),
                            deferRef: updatedDeferContext.activeDeferRef,
                        });
                        newGroup.addParent({ group, path: path.inGroup() });
                        stack.push({
                            tree: child,
                            group: newGroup,
                            path: path.forNewKeyFetch(createFetchInitialPath(dependencyGraph.supergraphSchema, type, newContext)),
                            context: newContext,
                            deferContext: updatedDeferContext,
                            contextToConditionsGroups,
                        });
                    }
                }
                else if (edge === null) {
                    const { updatedOperation, updatedDeferContext } = extractDeferFromOperation({
                        dependencyGraph,
                        operation,
                        deferContext,
                        path,
                    });
                    let newPath = path;
                    if (updatedOperation && updatedOperation.appliedDirectives.length > 0) {
                        newPath = path.add(updatedOperation);
                    }
                    stack.push({
                        tree: child,
                        group,
                        path: newPath,
                        context,
                        deferContext: updatedDeferContext,
                        contextToConditionsGroups,
                    });
                }
                else {
                    (0, federation_internals_1.assert)(edge.head.source === edge.tail.source, () => `Collecting edge ${edge} for ${operation} should not change the underlying subgraph`);
                    const typenameAttachment = operation.getAttachment(SIBLING_TYPENAME_KEY);
                    if (typenameAttachment !== undefined) {
                        const alias = typenameAttachment === '' ? undefined : typenameAttachment;
                        const typenameField = new federation_internals_1.Field(operation.parentType.typenameField(), undefined, undefined, alias);
                        group.addAtPath(path.inGroup().concat(typenameField));
                        dependencyGraph.deferTracking.updateSubselection({
                            ...deferContext,
                            pathToDeferParent: deferContext.pathToDeferParent.concat(typenameField),
                        });
                    }
                    const { updatedOperation, updatedDeferContext } = extractDeferFromOperation({
                        dependencyGraph,
                        operation,
                        deferContext,
                        path,
                    });
                    (0, federation_internals_1.assert)(updatedOperation, () => `Extracting @defer from ${operation} should not have resulted in no operation`);
                    const updated = {
                        tree: child,
                        group,
                        path,
                        context,
                        deferContext: updatedDeferContext,
                        contextToConditionsGroups,
                    };
                    if (conditions) {
                        let addTypenameAtPath;
                        if (contextToSelection) {
                            (0, federation_internals_1.assert)((0, federation_internals_1.isCompositeType)(edge.head.type), () => `Expected a composite type for ${edge.head.type}`);
                            addTypenameAtPath = {
                                pathType: edge.head.type,
                            };
                        }
                        const handleRequiresResult = handleRequiresTree(dependencyGraph, conditions, group, path, deferContext, addTypenameAtPath);
                        updateCreatedGroups(createdGroups, ...handleRequiresResult.createdGroups);
                        if (contextToSelection) {
                            const newContextToConditionsGroups = new Map([...contextToConditionsGroups]);
                            for (const context of contextToSelection) {
                                newContextToConditionsGroups.set(context, [handleRequiresResult.conditionsMergeGroup, ...handleRequiresResult.createdGroups]);
                            }
                            updated.contextToConditionsGroups = newContextToConditionsGroups;
                        }
                        if (edge.conditions) {
                            const createPostRequiresResult = createPostRequiresGroup(dependencyGraph, edge, group, path, context, handleRequiresResult);
                            updated.group = createPostRequiresResult.group;
                            updated.path = createPostRequiresResult.path;
                            updateCreatedGroups(createdGroups, ...createPostRequiresResult.createdGroups);
                        }
                    }
                    if (parameterToContext && Array.from(parameterToContext.values()).some(({ contextId }) => { var _a; return ((_a = updated.contextToConditionsGroups.get(contextId)) === null || _a === void 0 ? void 0 : _a[0]) === updated.group; })) {
                        (0, federation_internals_1.assert)(group === updated.group, "Group created by @requires handling shouldn't have set context already");
                        const conditionGroups = new Set();
                        for (const { contextId } of parameterToContext.values()) {
                            const groups = updated.contextToConditionsGroups.get(contextId);
                            (0, federation_internals_1.assert)(groups, () => `Could not find groups for context ${contextId}`);
                            for (const conditionGroup of groups) {
                                conditionGroups.add(conditionGroup);
                            }
                        }
                        (0, federation_internals_1.assert)((0, federation_internals_1.isCompositeType)(edge.head.type), () => `Expected a composite type for ${edge.head.type}`);
                        updated.group = dependencyGraph.getOrCreateKeyFetchGroup({
                            subgraphName: edge.head.source,
                            mergeAt: updated.path.inResponse(),
                            type: edge.head.type,
                            parent: { group: group, path: path.inGroup() },
                            conditionsGroups: [...conditionGroups],
                        });
                        updateCreatedGroups(createdGroups, updated.group);
                        updated.path = path.forNewKeyFetch(createFetchInitialPath(dependencyGraph.supergraphSchema, edge.head.type, context));
                        const keyCondition = (0, query_graphs_1.getLocallySatisfiableKey)(dependencyGraph.federatedQueryGraph, edge.head);
                        (0, federation_internals_1.assert)(keyCondition, () => `canSatisfyConditions() validation should have required a key to be present for ${edge}`);
                        const keyInputs = newCompositeTypeSelectionSet(edge.head.type);
                        keyInputs.updates().add(keyCondition);
                        group.addAtPath(path.inGroup(), keyInputs.get());
                        const inputType = dependencyGraph.typeForFetchInputs(edge.head.type.name);
                        const inputSelectionSet = newCompositeTypeSelectionSet(inputType);
                        inputSelectionSet.updates().add(keyCondition);
                        const inputs = wrapInputsSelections(inputType, inputSelectionSet.get(), context);
                        updated.group.addInputs(inputs, computeInputRewritesOnKeyFetch(edge.head.type.name, edge.head.type));
                        for (const parentGroup of conditionGroups) {
                            updated.group.addParent({ group: parentGroup });
                        }
                        for (const [_, { contextId, selectionSet, relativePath, subgraphArgType }] of parameterToContext) {
                            updated.group.addInputContext(contextId, subgraphArgType);
                            const keyRenamers = selectionSetAsKeyRenamers(selectionSet, relativePath, contextId);
                            for (const keyRenamer of keyRenamers) {
                                updated.group.addContextRenamer(keyRenamer);
                            }
                        }
                    }
                    else {
                        if (parameterToContext) {
                            const numFields = updated.path.inGroup().filter((e) => e.kind === 'Field').length;
                            for (const [_, { selectionSet, relativePath, contextId, subgraphArgType }] of parameterToContext) {
                                const newRelativePath = relativePath.slice(0, relativePath.length - numFields);
                                updated.group.addInputContext(contextId, subgraphArgType);
                                const keyRenamers = selectionSetAsKeyRenamers(selectionSet, newRelativePath, contextId);
                                for (const keyRenamer of keyRenamers) {
                                    updated.group.addContextRenamer(keyRenamer);
                                }
                            }
                        }
                    }
                    if (updatedOperation.kind === 'Field' && updatedOperation.name === federation_internals_1.typenameFieldName) {
                        updated.group.mustPreserveSelection = true;
                    }
                    if (edge.transition.kind === 'InterfaceObjectFakeDownCast') {
                        (0, federation_internals_1.assert)(updatedOperation.kind === 'FragmentElement', () => `Unexpected operation ${updatedOperation} for edge ${edge}`);
                        if (updatedOperation.appliedDirectives.length > 0) {
                            updated.path = updated.path.add(updatedOperation.withUpdatedCondition(undefined));
                        }
                    }
                    else {
                        updated.path = updated.path.add(updatedOperation);
                    }
                    stack.push(updated);
                }
            }
        }
    }
    return createdGroups;
}
function computeInputRewritesOnKeyFetch(inputTypeName, destType) {
    if ((0, federation_internals_1.isInterfaceObjectType)(destType) || (0, federation_internals_1.isInterfaceType)(destType)) {
        return [{
                kind: 'ValueSetter',
                path: [`... on ${inputTypeName}`, federation_internals_1.typenameFieldName],
                setValueTo: destType.name,
            }];
    }
    return undefined;
}
function extractDeferFromOperation({ dependencyGraph, operation, deferContext, path, }) {
    const deferArgs = operation.deferDirectiveArgs();
    if (!deferArgs) {
        return {
            updatedOperation: operation,
            updatedDeferContext: {
                ...deferContext,
                pathToDeferParent: deferContext.pathToDeferParent.concat(operation),
            }
        };
    }
    (0, federation_internals_1.assert)(deferArgs.label, 'All defers should have a lalel at this point');
    const updatedDeferRef = deferArgs.label;
    const updatedOperation = operation.withoutDefer();
    const updatedPathToDeferParent = updatedOperation ? [updatedOperation] : [];
    dependencyGraph.deferTracking.registerDefer({
        deferContext,
        deferArgs,
        path,
        parentType: operation.parentType,
    });
    return {
        updatedOperation,
        updatedDeferContext: {
            ...deferContext,
            currentDeferRef: updatedDeferRef,
            pathToDeferParent: updatedPathToDeferParent,
        },
    };
}
function subselectionTypeIfAbstract(selection) {
    if (selection.kind === 'FieldSelection') {
        const fieldBaseType = (0, federation_internals_1.baseType)(selection.element.definition.type);
        return (0, federation_internals_1.isAbstractType)(fieldBaseType) ? fieldBaseType : undefined;
    }
    else {
        const conditionType = selection.element.typeCondition;
        return conditionType && (0, federation_internals_1.isAbstractType)(conditionType) ? conditionType : undefined;
    }
}
function addTypenameFieldForAbstractTypes(selectionSet, parentTypeIfAbstract) {
    const handleSelection = (selection) => {
        if (!selection.selectionSet) {
            return selection;
        }
        const typeIfAbstract = subselectionTypeIfAbstract(selection);
        const updatedSelectionSet = addTypenameFieldForAbstractTypes(selection.selectionSet, typeIfAbstract);
        if (updatedSelectionSet === selection.selectionSet) {
            return selection;
        }
        else {
            return selection.withUpdatedSelectionSet(updatedSelectionSet);
        }
    };
    if (!parentTypeIfAbstract || selectionSet.hasTopLevelTypenameField()) {
        return selectionSet.lazyMap((selection) => handleSelection(selection));
    }
    const updates = new federation_internals_1.SelectionSetUpdates();
    updates.add(new federation_internals_1.FieldSelection(new federation_internals_1.Field(parentTypeIfAbstract.typenameField())));
    selectionSet.selections().forEach((selection) => updates.add(handleSelection(selection)));
    return updates.toSelectionSet(selectionSet.parentType);
}
function addBackTypenameInAttachments(selectionSet) {
    return selectionSet.lazyMap((s) => {
        const updated = s.mapToSelectionSet((ss) => addBackTypenameInAttachments(ss));
        const typenameAttachment = s.element.getAttachment(SIBLING_TYPENAME_KEY);
        if (typenameAttachment === undefined) {
            return updated;
        }
        else {
            const alias = typenameAttachment === '' ? undefined : typenameAttachment;
            const typenameField = new federation_internals_1.Field(s.element.parentType.typenameField(), undefined, undefined, alias);
            return [
                (0, federation_internals_1.selectionOfElement)(typenameField),
                updated,
            ];
        }
    });
}
function pathHasOnlyFragments(path) {
    return path.every((element) => element.kind === 'FragmentElement');
}
function typeAtPath(parentType, path) {
    let type = parentType;
    for (const element of path) {
        if (element.kind === 'Field') {
            const fieldType = (0, federation_internals_1.baseType)(type.field(element.name).type);
            (0, federation_internals_1.assert)((0, federation_internals_1.isCompositeType)(fieldType), () => `Invalid call fro ${path} starting at ${parentType}: ${element.definition.coordinate} is not composite`);
            type = fieldType;
        }
        else if (element.typeCondition) {
            const rebasedType = parentType.schema().type(element.typeCondition.name);
            (0, federation_internals_1.assert)(rebasedType && (0, federation_internals_1.isCompositeType)(rebasedType), () => `Type condition of ${element} should be composite`);
            type = rebasedType;
        }
    }
    return type;
}
function createPostRequiresGroup(dependencyGraph, edge, group, path, context, result) {
    const { fullyLocalRequires, createdGroups } = result;
    const entityType = edge.head.type;
    if (fullyLocalRequires) {
        return { group, path, ...result };
    }
    const parents = group.parents();
    const triedToMerge = parents.length === 1 && pathHasOnlyFragments(path.inGroup());
    if (triedToMerge) {
        const parent = parents[0];
        if (createdGroups.length === 0) {
            group.addInputs(inputsForRequire(dependencyGraph, entityType, edge, context, false).inputs);
            return { group, path, createdGroups: [] };
        }
        const postRequireGroup = dependencyGraph.newKeyFetchGroup({
            subgraphName: group.subgraphName,
            mergeAt: group.mergeAt,
            deferRef: group.deferRef
        });
        postRequireGroup.addParents(createdGroups.map((group) => ({ group })));
        if (result.requiresParent) {
            postRequireGroup.addParent(result.requiresParent);
        }
        (0, federation_internals_1.assert)(parent.path, `Missing path-in-parent for @requires on ${edge} with group ${group} and parent ${parent}`);
        addPostRequireInputs(dependencyGraph, path.forParentOfGroup(parent.path, parent.group.parentType.schema()), entityType, edge, context, parent.group, postRequireGroup);
        return {
            group: postRequireGroup,
            path: path.forNewKeyFetch(createFetchInitialPath(dependencyGraph.supergraphSchema, entityType, context)),
            createdGroups: [postRequireGroup],
        };
    }
    else {
        const postRequireGroup = dependencyGraph.newKeyFetchGroup({
            subgraphName: group.subgraphName,
            mergeAt: path.inResponse(),
        });
        postRequireGroup.addParents(createdGroups.map((group) => ({
            group,
            path: sameMergeAt(group.mergeAt, postRequireGroup.mergeAt)
                && (0, federation_internals_1.sameType)(group.parentType, postRequireGroup.parentType)
                ? []
                : undefined,
        })));
        addPostRequireInputs(dependencyGraph, path, entityType, edge, context, group, postRequireGroup);
        return {
            group: postRequireGroup,
            path: path.forNewKeyFetch(createFetchInitialPath(dependencyGraph.supergraphSchema, entityType, context)),
            createdGroups: [postRequireGroup],
        };
    }
}
function handleRequiresTree(dependencyGraph, requiresConditions, group, path, deferContext, addTypenameAtPath) {
    dependencyGraph.reduce();
    const parents = group.parents();
    let groupCopy;
    if (parents.length === 1 && pathHasOnlyFragments(path.inGroup())) {
        const parent = parents[0];
        groupCopy = dependencyGraph.newKeyFetchGroup({
            subgraphName: group.subgraphName,
            mergeAt: group.mergeAt,
            deferRef: group.deferRef
        });
        groupCopy.addParent(parent);
        groupCopy.copyInputsOf(group);
        if (addTypenameAtPath) {
            groupCopy.addAtPath(path.inGroup().concat(new federation_internals_1.Field(addTypenameAtPath.pathType.typenameField())));
        }
    }
    else {
        if (addTypenameAtPath) {
            group.addAtPath(path.inGroup().concat(new federation_internals_1.Field(addTypenameAtPath.pathType.typenameField())));
        }
    }
    const createdGroups = computeGroupsForTree({
        dependencyGraph,
        pathTree: requiresConditions,
        startGroup: groupCopy !== null && groupCopy !== void 0 ? groupCopy : group,
        initialGroupPath: path,
        initialDeferContext: deferContextForConditions(deferContext)
    });
    if (createdGroups.length == 0) {
        if (groupCopy) {
            (0, federation_internals_1.assert)(group.canMergeSiblingIn(groupCopy), () => `We should be able to merge ${groupCopy} into ${group} by construction`);
            group.mergeSiblingIn(groupCopy);
        }
        return { fullyLocalRequires: true, createdGroups: [], requiresParent: undefined, conditionsMergeGroup: group };
    }
    if (groupCopy) {
        const parent = groupCopy.parents()[0];
        groupCopy.removeInputsFromSelection();
        const newGroupIsUnneeded = parent.path && groupCopy.selection.canRebaseOn(typeAtPath(parent.group.selection.parentType, parent.path));
        const unmergedGroups = [];
        if (newGroupIsUnneeded) {
            parent.group.mergeChildIn(groupCopy);
            for (const created of createdGroups) {
                if (created.subgraphName === parent.group.subgraphName && parent.group.canMergeChildIn(created)) {
                    parent.group.mergeChildIn(created);
                }
                else {
                    unmergedGroups.push(created);
                    let currentParent = parent;
                    while (currentParent
                        && !currentParent.group.isTopLevel
                        && created.isChildOfWithArtificialDependency(currentParent.group)) {
                        currentParent.group.removeChild(created);
                        const grandParents = currentParent.group.parents();
                        (0, federation_internals_1.assert)(grandParents.length > 0, `${currentParent.group} is not top-level, so it should have parents`);
                        for (const grandParent of grandParents) {
                            created.addParent({
                                group: grandParent.group,
                                path: concatPathsInParents(grandParent.path, currentParent.path),
                            });
                        }
                        currentParent = grandParents.length === 1 ? grandParents[0] : undefined;
                    }
                }
            }
        }
        else {
            (0, federation_internals_1.assert)(group.canMergeSiblingIn(groupCopy), () => `We should be able to merge ${groupCopy} into ${group} by construction`);
            group.mergeSiblingIn(groupCopy);
            if (parent.path) {
                for (const created of createdGroups) {
                    if (created.subgraphName === parent.group.subgraphName
                        && parent.group.canMergeGrandChildIn(created)
                        && sameMergeAt(created.mergeAt, group.mergeAt)
                        && group.inputs.contains(created.inputs)) {
                        parent.group.mergeGrandChildIn(created);
                    }
                    else {
                        unmergedGroups.push(created);
                    }
                }
            }
        }
        return {
            fullyLocalRequires: false,
            createdGroups: unmergedGroups,
            requiresParent: newGroupIsUnneeded ? parent : { group, path: [] },
            conditionsMergeGroup: newGroupIsUnneeded ? parent.group : group,
        };
    }
    else {
        return {
            fullyLocalRequires: false,
            createdGroups,
            requiresParent: undefined,
            conditionsMergeGroup: group,
        };
    }
}
function addPostRequireInputs(dependencyGraph, requirePath, entityType, edge, context, preRequireGroup, postRequireGroup) {
    const { inputs, keyInputs } = inputsForRequire(dependencyGraph, entityType, edge, context);
    postRequireGroup.addInputs(inputs, computeInputRewritesOnKeyFetch(entityType.name, entityType));
    if (keyInputs) {
        preRequireGroup.addAtPath(requirePath.inGroup(), keyInputs.selections());
    }
}
function newCompositeTypeSelectionSet(type) {
    const selectionSet = federation_internals_1.MutableSelectionSet.empty(type);
    selectionSet.updates().add(new federation_internals_1.FieldSelection(new federation_internals_1.Field(type.typenameField())));
    return selectionSet;
}
function inputsForRequire(dependencyGraph, entityType, edge, context, includeKeyInputs = true) {
    const isInterfaceObjectDownCast = edge.transition.kind === 'InterfaceObjectFakeDownCast';
    const inputTypeName = isInterfaceObjectDownCast ? edge.transition.castedTypeName : entityType.name;
    const inputType = dependencyGraph.supergraphSchema.type(inputTypeName);
    (0, federation_internals_1.assert)(inputType && (0, federation_internals_1.isCompositeType)(inputType), () => `Type ${inputTypeName} should exist in the supergraph and be a composite type`);
    const fullSelectionSet = newCompositeTypeSelectionSet(inputType);
    if (edge.conditions) {
        fullSelectionSet.updates().add(edge.conditions);
    }
    let keyInputs = undefined;
    if (includeKeyInputs) {
        const keyCondition = (0, query_graphs_1.getLocallySatisfiableKey)(dependencyGraph.federatedQueryGraph, edge.head);
        (0, federation_internals_1.assert)(keyCondition, () => `Due to @require, validation should have required a key to be present for ${edge}`);
        let keyConditionAsInput = keyCondition;
        if (isInterfaceObjectDownCast) {
            const supergraphItfType = dependencyGraph.supergraphSchema.type(entityType.name);
            (0, federation_internals_1.assert)(supergraphItfType && (0, federation_internals_1.isInterfaceType)(supergraphItfType), () => `Type ${entityType} should be an interface in the supergraph`);
            keyConditionAsInput = keyConditionAsInput.rebaseOn({ parentType: supergraphItfType, fragments: undefined, errorIfCannotRebase: true });
        }
        fullSelectionSet.updates().add(keyConditionAsInput);
        keyInputs = newCompositeTypeSelectionSet(entityType);
        keyInputs.updates().add(keyCondition);
    }
    return {
        inputs: wrapInputsSelections(inputType, fullSelectionSet.get(), context),
        keyInputs: keyInputs === null || keyInputs === void 0 ? void 0 : keyInputs.get(),
    };
}
const representationsVariable = new federation_internals_1.Variable('representations');
function representationsVariableDefinition(schema) {
    const metadata = (0, federation_internals_1.federationMetadata)(schema);
    (0, federation_internals_1.assert)(metadata, 'Expected schema to be a federation subgraph');
    const representationsType = new federation_internals_1.NonNullType(new federation_internals_1.ListType(new federation_internals_1.NonNullType(metadata.anyType())));
    return new federation_internals_1.VariableDefinition(schema, representationsVariable, representationsType);
}
function collectUsedVariables(selectionSet, operationDirectives) {
    const collector = new federation_internals_1.VariableCollector();
    selectionSet.collectVariables(collector);
    if (operationDirectives) {
        for (const applied of operationDirectives) {
            collector.collectInArguments(applied.arguments());
        }
    }
    return collector.variables();
}
function operationForEntitiesFetch(subgraphSchema, selectionSet, allVariableDefinitions, operationName, directives) {
    const variableDefinitions = new federation_internals_1.VariableDefinitions();
    variableDefinitions.add(representationsVariableDefinition(subgraphSchema));
    variableDefinitions.addAll(allVariableDefinitions.filter(collectUsedVariables(selectionSet, directives)));
    const queryType = subgraphSchema.schemaDefinition.rootType('query');
    (0, federation_internals_1.assert)(queryType, `Subgraphs should always have a query root (they should at least provides _entities)`);
    const entities = queryType.field(federation_internals_1.entitiesFieldName);
    (0, federation_internals_1.assert)(entities, `Subgraphs should always have the _entities field`);
    const entitiesCall = (0, federation_internals_1.selectionSetOfElement)(new federation_internals_1.Field(entities, { representations: representationsVariable }), selectionSet);
    return new federation_internals_1.Operation(subgraphSchema, 'query', entitiesCall, variableDefinitions, undefined, operationName, directives);
}
function operationForQueryFetch(subgraphSchema, rootKind, selectionSet, allVariableDefinitions, operationName, directives) {
    return new federation_internals_1.Operation(subgraphSchema, rootKind, selectionSet, allVariableDefinitions.filter(collectUsedVariables(selectionSet, directives)), undefined, operationName, directives);
}
const sameKeyRenamer = (k1, k2) => {
    if (k1.renameKeyTo !== k2.renameKeyTo || k1.path.length !== k2.path.length) {
        return false;
    }
    for (let i = 0; i < k1.path.length; i++) {
        if (k1.path[i] !== k2.path[i]) {
            return false;
        }
    }
    return true;
};
//# sourceMappingURL=buildPlan.js.map