'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

const graphql = require('graphql');
const utils = require('@graphql-tools/utils');
const schema = require('@graphql-tools/schema');
const delegate = require('@graphql-tools/delegate');
const wrap = require('@graphql-tools/wrap');
const merge = require('@graphql-tools/merge');
const batchDelegate = require('@graphql-tools/batch-delegate');

const backcompatOptions = { commentDescriptions: true };
function typeFromAST(node) {
    switch (node.kind) {
        case graphql.Kind.OBJECT_TYPE_DEFINITION:
            return makeObjectType(node);
        case graphql.Kind.INTERFACE_TYPE_DEFINITION:
            return makeInterfaceType(node);
        case graphql.Kind.ENUM_TYPE_DEFINITION:
            return makeEnumType(node);
        case graphql.Kind.UNION_TYPE_DEFINITION:
            return makeUnionType(node);
        case graphql.Kind.SCALAR_TYPE_DEFINITION:
            return makeScalarType(node);
        case graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION:
            return makeInputObjectType(node);
        case graphql.Kind.DIRECTIVE_DEFINITION:
            return makeDirective(node);
        default:
            return null;
    }
}
function makeObjectType(node) {
    const config = {
        name: node.name.value,
        description: getDescription(node, backcompatOptions),
        interfaces: () => node.interfaces.map(iface => utils.createNamedStub(iface.name.value, 'interface')),
        fields: () => makeFields(node.fields),
        astNode: node,
    };
    return new graphql.GraphQLObjectType(config);
}
function makeInterfaceType(node) {
    var _a;
    const config = {
        name: node.name.value,
        description: getDescription(node, backcompatOptions),
        interfaces: (_a = node.interfaces) === null || _a === void 0 ? void 0 : _a.map(iface => utils.createNamedStub(iface.name.value, 'interface')),
        fields: () => makeFields(node.fields),
        astNode: node,
    };
    return new graphql.GraphQLInterfaceType(config);
}
function makeEnumType(node) {
    const values = node.values.reduce((prev, value) => ({
        ...prev,
        [value.name.value]: {
            description: getDescription(value, backcompatOptions),
            deprecationReason: getDeprecationReason(value),
            astNode: value,
        },
    }), {});
    return new graphql.GraphQLEnumType({
        name: node.name.value,
        description: getDescription(node, backcompatOptions),
        values,
        astNode: node,
    });
}
function makeUnionType(node) {
    return new graphql.GraphQLUnionType({
        name: node.name.value,
        description: getDescription(node, backcompatOptions),
        types: () => node.types.map(type => utils.createNamedStub(type.name.value, 'object')),
        astNode: node,
    });
}
function makeScalarType(node) {
    return new graphql.GraphQLScalarType({
        name: node.name.value,
        description: getDescription(node, backcompatOptions),
        astNode: node,
        // TODO: serialize default property setting can be dropped once
        // upstream graphql-js TypeScript typings are updated, likely in v16
        serialize: value => value,
    });
}
function makeInputObjectType(node) {
    return new graphql.GraphQLInputObjectType({
        name: node.name.value,
        description: getDescription(node, backcompatOptions),
        fields: () => makeValues(node.fields),
        astNode: node,
    });
}
function makeFields(nodes) {
    return nodes.reduce((prev, node) => ({
        ...prev,
        [node.name.value]: {
            type: utils.createStub(node.type, 'output'),
            description: getDescription(node, backcompatOptions),
            args: makeValues(node.arguments),
            deprecationReason: getDeprecationReason(node),
            astNode: node,
        },
    }), {});
}
function makeValues(nodes) {
    return nodes.reduce((prev, node) => ({
        ...prev,
        [node.name.value]: {
            type: utils.createStub(node.type, 'input'),
            defaultValue: node.defaultValue !== undefined ? graphql.valueFromASTUntyped(node.defaultValue) : undefined,
            description: getDescription(node, backcompatOptions),
            astNode: node,
        },
    }), {});
}
function makeDirective(node) {
    const locations = [];
    node.locations.forEach(location => {
        if (location.value in graphql.DirectiveLocation) {
            locations.push(location.value);
        }
    });
    return new graphql.GraphQLDirective({
        name: node.name.value,
        description: node.description != null ? node.description.value : null,
        locations,
        isRepeatable: node.repeatable,
        args: makeValues(node.arguments),
        astNode: node,
    });
}
// graphql < v13 does not export getDescription
function getDescription(node, options) {
    if (node.description != null) {
        return node.description.value;
    }
    if (options.commentDescriptions) {
        const rawValue = getLeadingCommentBlock(node);
        if (rawValue !== undefined) {
            return dedentBlockStringValue(`\n${rawValue}`);
        }
    }
}
function getLeadingCommentBlock(node) {
    const loc = node.loc;
    if (!loc) {
        return;
    }
    const comments = [];
    let token = loc.startToken.prev;
    while (token != null &&
        token.kind === graphql.TokenKind.COMMENT &&
        token.next != null &&
        token.prev != null &&
        token.line + 1 === token.next.line &&
        token.line !== token.prev.line) {
        const value = String(token.value);
        comments.push(value);
        token = token.prev;
    }
    return comments.length > 0 ? comments.reverse().join('\n') : undefined;
}
function dedentBlockStringValue(rawString) {
    // Expand a block string's raw value into independent lines.
    const lines = rawString.split(/\r\n|[\n\r]/g);
    // Remove common indentation from all lines but first.
    const commonIndent = getBlockStringIndentation(lines);
    if (commonIndent !== 0) {
        for (let i = 1; i < lines.length; i++) {
            lines[i] = lines[i].slice(commonIndent);
        }
    }
    // Remove leading and trailing blank lines.
    while (lines.length > 0 && isBlank(lines[0])) {
        lines.shift();
    }
    while (lines.length > 0 && isBlank(lines[lines.length - 1])) {
        lines.pop();
    }
    // Return a string of the lines joined with U+000A.
    return lines.join('\n');
}
/**
 * @internal
 */
function getBlockStringIndentation(lines) {
    let commonIndent = null;
    for (let i = 1; i < lines.length; i++) {
        const line = lines[i];
        const indent = leadingWhitespace(line);
        if (indent === line.length) {
            continue; // skip empty lines
        }
        if (commonIndent === null || indent < commonIndent) {
            commonIndent = indent;
            if (commonIndent === 0) {
                break;
            }
        }
    }
    return commonIndent === null ? 0 : commonIndent;
}
function leadingWhitespace(str) {
    let i = 0;
    while (i < str.length && (str[i] === ' ' || str[i] === '\t')) {
        i++;
    }
    return i;
}
function isBlank(str) {
    return leadingWhitespace(str) === str.length;
}
function getDeprecationReason(node) {
    const deprecated = graphql.getDirectiveValues(graphql.GraphQLDeprecatedDirective, node);
    return deprecated === null || deprecated === void 0 ? void 0 : deprecated.reason;
}

(function (ValidationLevel) {
    ValidationLevel["Error"] = "error";
    ValidationLevel["Warn"] = "warn";
    ValidationLevel["Off"] = "off";
})(exports.ValidationLevel || (exports.ValidationLevel = {}));

function validateFieldConsistency(finalFieldConfig, candidates, typeMergingOptions) {
    const fieldNamespace = `${candidates[0].type.name}.${candidates[0].fieldName}`;
    const finalFieldNull = graphql.isNonNullType(finalFieldConfig.type);
    validateTypeConsistency(finalFieldConfig, candidates.map(c => c.fieldConfig), 'field', fieldNamespace, typeMergingOptions);
    if (getValidationSettings(fieldNamespace, typeMergingOptions).strictNullComparison &&
        candidates.some(c => finalFieldNull !== graphql.isNonNullType(c.fieldConfig.type))) {
        validationMessage(`Nullability of field "${fieldNamespace}" does not match across subschemas. Disable typeMergingOptions.validationSettings.strictNullComparison to permit safe divergences.`, fieldNamespace, typeMergingOptions);
    }
    else if (finalFieldNull && candidates.some(c => !graphql.isNonNullType(c.fieldConfig.type))) {
        validationMessage(`Canonical definition of field "${fieldNamespace}" is not-null while some subschemas permit null. This will be an automatic error in future versions.`, fieldNamespace, typeMergingOptions);
    }
    const argCandidatesMap = Object.create(null);
    candidates.forEach(({ fieldConfig }) => {
        Object.entries(fieldConfig.args).forEach(([argName, arg]) => {
            argCandidatesMap[argName] = argCandidatesMap[argName] || [];
            argCandidatesMap[argName].push(arg);
        });
    });
    if (Object.values(argCandidatesMap).some(argCandidates => candidates.length !== argCandidates.length)) {
        validationMessage(`Canonical definition of field "${fieldNamespace}" implements inconsistent argument names across subschemas. Input may be filtered from some requests.`, fieldNamespace, typeMergingOptions);
    }
    Object.entries(argCandidatesMap).forEach(([argName, argCandidates]) => {
        const argNamespace = `${fieldNamespace}.${argName}`;
        const finalArgConfig = finalFieldConfig.args[argName] || argCandidates[argCandidates.length - 1];
        const finalArgType = graphql.getNamedType(finalArgConfig.type);
        const finalArgNull = graphql.isNonNullType(finalArgConfig.type);
        validateTypeConsistency(finalArgConfig, argCandidates, 'argument', argNamespace, typeMergingOptions);
        if (getValidationSettings(argNamespace, typeMergingOptions).strictNullComparison &&
            argCandidates.some(c => finalArgNull !== graphql.isNonNullType(c.type))) {
            validationMessage(`Nullability of argument "${argNamespace}" does not match across subschemas. Disable typeMergingOptions.validationSettings.strictNullComparison to permit safe divergences.`, argNamespace, typeMergingOptions);
        }
        else if (!finalArgNull && argCandidates.some(c => graphql.isNonNullType(c.type))) {
            validationMessage(`Canonical definition of argument "${argNamespace}" permits null while some subschemas require not-null. This will be an automatic error in future versions.`, argNamespace, typeMergingOptions);
        }
        if (graphql.isEnumType(finalArgType)) {
            validateInputEnumConsistency(finalArgType, argCandidates, typeMergingOptions);
        }
    });
}
function validateInputObjectConsistency(fieldInclusionMap, candidates, typeMergingOptions) {
    Object.entries(fieldInclusionMap).forEach(([fieldName, count]) => {
        if (candidates.length !== count) {
            const namespace = `${candidates[0].type.name}.${fieldName}`;
            validationMessage(`Definition of input field "${namespace}" is not implemented by all subschemas. Input may be filtered from some requests.`, namespace, typeMergingOptions);
        }
    });
}
function validateInputFieldConsistency(finalInputFieldConfig, candidates, typeMergingOptions) {
    const inputFieldNamespace = `${candidates[0].type.name}.${candidates[0].fieldName}`;
    const inputFieldConfigs = candidates.map(c => c.inputFieldConfig);
    const finalInputFieldType = graphql.getNamedType(finalInputFieldConfig.type);
    const finalInputFieldNull = graphql.isNonNullType(finalInputFieldConfig.type);
    validateTypeConsistency(finalInputFieldConfig, inputFieldConfigs, 'input field', inputFieldNamespace, typeMergingOptions);
    if (getValidationSettings(inputFieldNamespace, typeMergingOptions).strictNullComparison &&
        candidates.some(c => finalInputFieldNull !== graphql.isNonNullType(c.inputFieldConfig.type))) {
        validationMessage(`Nullability of input field "${inputFieldNamespace}" does not match across subschemas. Disable typeMergingOptions.validationSettings.strictNullComparison to permit safe divergences.`, inputFieldNamespace, typeMergingOptions);
    }
    else if (!finalInputFieldNull && candidates.some(c => graphql.isNonNullType(c.inputFieldConfig.type))) {
        validationMessage(`Canonical definition of input field "${inputFieldNamespace}" permits null while some subschemas require not-null. This will be an automatic error in future versions.`, inputFieldNamespace, typeMergingOptions);
    }
    if (graphql.isEnumType(finalInputFieldType)) {
        validateInputEnumConsistency(finalInputFieldType, inputFieldConfigs, typeMergingOptions);
    }
}
function validateTypeConsistency(finalElementConfig, candidates, definitionType, settingNamespace, typeMergingOptions) {
    const finalNamedType = graphql.getNamedType(finalElementConfig.type);
    const finalIsScalar = graphql.isScalarType(finalNamedType);
    const finalIsList = hasListType(finalElementConfig.type);
    candidates.forEach(c => {
        var _a, _b, _c;
        if (finalIsList !== hasListType(c.type)) {
            throw new Error(`Definitions of ${definitionType} "${settingNamespace}" implement inconsistent list types across subschemas and cannot be merged.`);
        }
        const currentNamedType = graphql.getNamedType(c.type);
        if (finalNamedType.toString() !== currentNamedType.toString()) {
            const proxiableScalar = !!((_c = (_b = (_a = typeMergingOptions === null || typeMergingOptions === void 0 ? void 0 : typeMergingOptions.validationSettings) === null || _a === void 0 ? void 0 : _a.proxiableScalars) === null || _b === void 0 ? void 0 : _b[finalNamedType.toString()]) === null || _c === void 0 ? void 0 : _c.includes(currentNamedType.toString()));
            const bothScalars = finalIsScalar && graphql.isScalarType(currentNamedType);
            const permitScalar = proxiableScalar && bothScalars;
            if (proxiableScalar && !bothScalars) {
                throw new Error(`Types ${finalNamedType} and ${currentNamedType} are not proxiable scalars.`);
            }
            if (!permitScalar) {
                validationMessage(`Definitions of ${definitionType} "${settingNamespace}" implement inconsistent named types across subschemas. This will be an automatic error in future versions.`, settingNamespace, typeMergingOptions);
            }
        }
    });
}
function hasListType(type) {
    return graphql.isListType(graphql.getNullableType(type));
}
function validateInputEnumConsistency(inputEnumType, candidates, typeMergingOptions) {
    const enumValueInclusionMap = Object.create(null);
    candidates.forEach(candidate => {
        const enumType = graphql.getNamedType(candidate.type);
        if (graphql.isEnumType(enumType)) {
            enumType.getValues().forEach(({ value }) => {
                enumValueInclusionMap[value] = enumValueInclusionMap[value] || 0;
                enumValueInclusionMap[value] += 1;
            });
        }
    });
    if (Object.values(enumValueInclusionMap).some(count => candidates.length !== count)) {
        validationMessage(`Enum "${inputEnumType.name}" is used as an input with inconsistent values across subschemas. This will be an automatic error in future versions.`, inputEnumType.name, typeMergingOptions);
    }
}
function validationMessage(message, settingNamespace, typeMergingOptions) {
    var _a;
    const override = `typeMergingOptions.validationScopes['${settingNamespace}'].validationLevel`;
    const settings = getValidationSettings(settingNamespace, typeMergingOptions);
    switch ((_a = settings.validationLevel) !== null && _a !== void 0 ? _a : exports.ValidationLevel.Warn) {
        case exports.ValidationLevel.Off:
            return;
        case exports.ValidationLevel.Error:
            throw new Error(`${message} If this is intentional, you may disable this error by setting ${override} = "warn|off"`);
        default:
            console.warn(`${message} To disable this warning or elevate it to an error, set ${override} = "error|off"`);
    }
}
function getValidationSettings(settingNamespace, typeMergingOptions) {
    var _a, _b, _c;
    return {
        ...((_a = typeMergingOptions === null || typeMergingOptions === void 0 ? void 0 : typeMergingOptions.validationSettings) !== null && _a !== void 0 ? _a : {}),
        ...((_c = (_b = typeMergingOptions === null || typeMergingOptions === void 0 ? void 0 : typeMergingOptions.validationScopes) === null || _b === void 0 ? void 0 : _b[settingNamespace]) !== null && _c !== void 0 ? _c : {}),
    };
}

function mergeCandidates(typeName, candidates, typeMergingOptions) {
    const initialCandidateType = candidates[0].type;
    if (candidates.some(candidate => candidate.type.constructor !== initialCandidateType.constructor)) {
        throw new Error(`Cannot merge different type categories into common type ${typeName}.`);
    }
    if (graphql.isObjectType(initialCandidateType)) {
        return mergeObjectTypeCandidates(typeName, candidates, typeMergingOptions);
    }
    else if (graphql.isInputObjectType(initialCandidateType)) {
        return mergeInputObjectTypeCandidates(typeName, candidates, typeMergingOptions);
    }
    else if (graphql.isInterfaceType(initialCandidateType)) {
        return mergeInterfaceTypeCandidates(typeName, candidates, typeMergingOptions);
    }
    else if (graphql.isUnionType(initialCandidateType)) {
        return mergeUnionTypeCandidates(typeName, candidates, typeMergingOptions);
    }
    else if (graphql.isEnumType(initialCandidateType)) {
        return mergeEnumTypeCandidates(typeName, candidates, typeMergingOptions);
    }
    else if (graphql.isScalarType(initialCandidateType)) {
        return mergeScalarTypeCandidates(typeName, candidates, typeMergingOptions);
    }
    else {
        // not reachable.
        throw new Error(`Type ${typeName} has unknown GraphQL type.`);
    }
}
function mergeObjectTypeCandidates(typeName, candidates, typeMergingOptions) {
    candidates = orderedTypeCandidates(candidates, typeMergingOptions);
    const description = mergeTypeDescriptions(candidates, typeMergingOptions);
    const fields = fieldConfigMapFromTypeCandidates(candidates, typeMergingOptions);
    const typeConfigs = candidates.map(candidate => candidate.type.toConfig());
    const interfaceMap = typeConfigs
        .map(typeConfig => typeConfig.interfaces)
        .reduce((acc, interfaces) => {
        if (interfaces != null) {
            interfaces.forEach(iface => {
                acc[iface.name] = iface;
            });
        }
        return acc;
    }, Object.create(null));
    const interfaces = Object.keys(interfaceMap).map(interfaceName => interfaceMap[interfaceName]);
    const astNodes = pluck('astNode', candidates);
    const fieldAstNodes = canonicalFieldNamesForType(candidates)
        .map(fieldName => { var _a; return (_a = fields[fieldName]) === null || _a === void 0 ? void 0 : _a.astNode; })
        .filter(n => n != null);
    if (astNodes.length > 1 && fieldAstNodes.length) {
        astNodes.push({
            ...astNodes[astNodes.length - 1],
            fields: JSON.parse(JSON.stringify(fieldAstNodes)),
        });
    }
    const astNode = astNodes
        .slice(1)
        .reduce((acc, astNode) => merge.mergeType(astNode, acc, { ignoreFieldConflicts: true }), astNodes[0]);
    const extensionASTNodes = [].concat(pluck('extensionASTNodes', candidates));
    const extensions = Object.assign({}, ...pluck('extensions', candidates));
    const typeConfig = {
        name: typeName,
        description,
        fields,
        interfaces,
        astNode,
        extensionASTNodes,
        extensions,
    };
    return new graphql.GraphQLObjectType(typeConfig);
}
function mergeInputObjectTypeCandidates(typeName, candidates, typeMergingOptions) {
    candidates = orderedTypeCandidates(candidates, typeMergingOptions);
    const description = mergeTypeDescriptions(candidates, typeMergingOptions);
    const fields = inputFieldConfigMapFromTypeCandidates(candidates, typeMergingOptions);
    const astNodes = pluck('astNode', candidates);
    const fieldAstNodes = canonicalFieldNamesForType(candidates)
        .map(fieldName => { var _a; return (_a = fields[fieldName]) === null || _a === void 0 ? void 0 : _a.astNode; })
        .filter(n => n != null);
    if (astNodes.length > 1 && fieldAstNodes.length) {
        astNodes.push({
            ...astNodes[astNodes.length - 1],
            fields: JSON.parse(JSON.stringify(fieldAstNodes)),
        });
    }
    const astNode = astNodes.slice(1).reduce((acc, astNode) => merge.mergeInputType(astNode, acc, {
        ignoreFieldConflicts: true,
    }), astNodes[0]);
    const extensionASTNodes = [].concat(pluck('extensionASTNodes', candidates));
    const extensions = Object.assign({}, ...pluck('extensions', candidates));
    const typeConfig = {
        name: typeName,
        description,
        fields,
        astNode,
        extensionASTNodes,
        extensions,
    };
    return new graphql.GraphQLInputObjectType(typeConfig);
}
function pluck(typeProperty, candidates) {
    return candidates.map(candidate => candidate.type[typeProperty]).filter(value => value != null);
}
function mergeInterfaceTypeCandidates(typeName, candidates, typeMergingOptions) {
    candidates = orderedTypeCandidates(candidates, typeMergingOptions);
    const description = mergeTypeDescriptions(candidates, typeMergingOptions);
    const fields = fieldConfigMapFromTypeCandidates(candidates, typeMergingOptions);
    const typeConfigs = candidates.map(candidate => candidate.type.toConfig());
    const interfaceMap = typeConfigs
        .map(typeConfig => typeConfig.interfaces)
        .reduce((acc, interfaces) => {
        if (interfaces != null) {
            interfaces.forEach(iface => {
                acc[iface.name] = iface;
            });
        }
        return acc;
    }, Object.create(null));
    const interfaces = Object.keys(interfaceMap).map(interfaceName => interfaceMap[interfaceName]);
    const astNodes = pluck('astNode', candidates);
    const fieldAstNodes = canonicalFieldNamesForType(candidates)
        .map(fieldName => { var _a; return (_a = fields[fieldName]) === null || _a === void 0 ? void 0 : _a.astNode; })
        .filter(n => n != null);
    if (astNodes.length > 1 && fieldAstNodes.length) {
        astNodes.push({
            ...astNodes[astNodes.length - 1],
            fields: JSON.parse(JSON.stringify(fieldAstNodes)),
        });
    }
    const astNode = astNodes.slice(1).reduce((acc, astNode) => merge.mergeInterface(astNode, acc, {
        ignoreFieldConflicts: true,
    }), astNodes[0]);
    const extensionASTNodes = [].concat(pluck('extensionASTNodes', candidates));
    const extensions = Object.assign({}, ...pluck('extensions', candidates));
    const typeConfig = {
        name: typeName,
        description,
        fields,
        interfaces,
        astNode,
        extensionASTNodes,
        extensions,
    };
    return new graphql.GraphQLInterfaceType(typeConfig);
}
function mergeUnionTypeCandidates(typeName, candidates, typeMergingOptions) {
    candidates = orderedTypeCandidates(candidates, typeMergingOptions);
    const description = mergeTypeDescriptions(candidates, typeMergingOptions);
    const typeConfigs = candidates.map(candidate => candidate.type.toConfig());
    const typeMap = typeConfigs.reduce((acc, typeConfig) => {
        typeConfig.types.forEach(type => {
            acc[type.name] = type;
        });
        return acc;
    }, Object.create(null));
    const types = Object.keys(typeMap).map(typeName => typeMap[typeName]);
    const astNodes = pluck('astNode', candidates);
    const astNode = astNodes
        .slice(1)
        .reduce((acc, astNode) => merge.mergeUnion(astNode, acc), astNodes[0]);
    const extensionASTNodes = [].concat(pluck('extensionASTNodes', candidates));
    const extensions = Object.assign({}, ...pluck('extensions', candidates));
    const typeConfig = {
        name: typeName,
        description,
        types,
        astNode,
        extensionASTNodes,
        extensions,
    };
    return new graphql.GraphQLUnionType(typeConfig);
}
function mergeEnumTypeCandidates(typeName, candidates, typeMergingOptions) {
    candidates = orderedTypeCandidates(candidates, typeMergingOptions);
    const description = mergeTypeDescriptions(candidates, typeMergingOptions);
    const values = enumValueConfigMapFromTypeCandidates(candidates, typeMergingOptions);
    const astNodes = pluck('astNode', candidates);
    const astNode = astNodes
        .slice(1)
        .reduce((acc, astNode) => merge.mergeEnum(astNode, acc, { consistentEnumMerge: true }), astNodes[0]);
    const extensionASTNodes = [].concat(pluck('extensionASTNodes', candidates));
    const extensions = Object.assign({}, ...pluck('extensions', candidates));
    const typeConfig = {
        name: typeName,
        description,
        values,
        astNode,
        extensionASTNodes,
        extensions,
    };
    return new graphql.GraphQLEnumType(typeConfig);
}
function enumValueConfigMapFromTypeCandidates(candidates, typeMergingOptions) {
    const enumValueConfigCandidatesMap = Object.create(null);
    candidates.forEach(candidate => {
        const valueMap = candidate.type.toConfig().values;
        Object.keys(valueMap).forEach(enumValue => {
            const enumValueConfigCandidate = {
                enumValueConfig: valueMap[enumValue],
                enumValue,
                type: candidate.type,
                subschema: candidate.subschema,
                transformedSubschema: candidate.transformedSubschema,
            };
            if (enumValue in enumValueConfigCandidatesMap) {
                enumValueConfigCandidatesMap[enumValue].push(enumValueConfigCandidate);
            }
            else {
                enumValueConfigCandidatesMap[enumValue] = [enumValueConfigCandidate];
            }
        });
    });
    const enumValueConfigMap = Object.create(null);
    Object.keys(enumValueConfigCandidatesMap).forEach(enumValue => {
        var _a;
        const enumValueConfigMerger = (_a = typeMergingOptions === null || typeMergingOptions === void 0 ? void 0 : typeMergingOptions.enumValueConfigMerger) !== null && _a !== void 0 ? _a : defaultEnumValueConfigMerger;
        enumValueConfigMap[enumValue] = enumValueConfigMerger(enumValueConfigCandidatesMap[enumValue]);
    });
    return enumValueConfigMap;
}
function defaultEnumValueConfigMerger(candidates) {
    const preferred = candidates.find(({ type, transformedSubschema }) => { var _a, _b; return delegate.isSubschemaConfig(transformedSubschema) && ((_b = (_a = transformedSubschema.merge) === null || _a === void 0 ? void 0 : _a[type.name]) === null || _b === void 0 ? void 0 : _b.canonical); });
    return (preferred || candidates[candidates.length - 1]).enumValueConfig;
}
function mergeScalarTypeCandidates(typeName, candidates, typeMergingOptions) {
    candidates = orderedTypeCandidates(candidates, typeMergingOptions);
    const description = mergeTypeDescriptions(candidates, typeMergingOptions);
    const serializeFns = pluck('serialize', candidates);
    const serialize = serializeFns[serializeFns.length - 1];
    const parseValueFns = pluck('parseValue', candidates);
    const parseValue = parseValueFns[parseValueFns.length - 1];
    const parseLiteralFns = pluck('parseLiteral', candidates);
    const parseLiteral = parseLiteralFns[parseLiteralFns.length - 1];
    const astNodes = pluck('astNode', candidates);
    const astNode = astNodes
        .slice(1)
        .reduce((acc, astNode) => merge.mergeScalar(astNode, acc), astNodes[0]);
    const extensionASTNodes = [].concat(pluck('extensionASTNodes', candidates));
    const extensions = Object.assign({}, ...pluck('extensions', candidates));
    const typeConfig = {
        name: typeName,
        description,
        serialize,
        parseValue,
        parseLiteral,
        astNode,
        extensionASTNodes,
        extensions,
    };
    return new graphql.GraphQLScalarType(typeConfig);
}
function orderedTypeCandidates(candidates, typeMergingOptions) {
    var _a;
    const typeCandidateMerger = (_a = typeMergingOptions === null || typeMergingOptions === void 0 ? void 0 : typeMergingOptions.typeCandidateMerger) !== null && _a !== void 0 ? _a : defaultTypeCandidateMerger;
    const candidate = typeCandidateMerger(candidates);
    return candidates.filter(c => c !== candidate).concat([candidate]);
}
function defaultTypeCandidateMerger(candidates) {
    const canonical = candidates.filter(({ type, transformedSubschema }) => { var _a, _b; return delegate.isSubschemaConfig(transformedSubschema) ? (_b = (_a = transformedSubschema.merge) === null || _a === void 0 ? void 0 : _a[type.name]) === null || _b === void 0 ? void 0 : _b.canonical : false; });
    if (canonical.length > 1) {
        throw new Error(`Multiple canonical definitions for "${canonical[0].type.name}"`);
    }
    else if (canonical.length) {
        return canonical[0];
    }
    return candidates[candidates.length - 1];
}
function mergeTypeDescriptions(candidates, typeMergingOptions) {
    var _a;
    const typeDescriptionsMerger = (_a = typeMergingOptions === null || typeMergingOptions === void 0 ? void 0 : typeMergingOptions.typeDescriptionsMerger) !== null && _a !== void 0 ? _a : defaultTypeDescriptionMerger;
    return typeDescriptionsMerger(candidates);
}
function defaultTypeDescriptionMerger(candidates) {
    return candidates[candidates.length - 1].type.description;
}
function fieldConfigMapFromTypeCandidates(candidates, typeMergingOptions) {
    const fieldConfigCandidatesMap = Object.create(null);
    candidates.forEach(candidate => {
        const fieldMap = candidate.type.getFields();
        Object.keys(fieldMap).forEach(fieldName => {
            const fieldConfigCandidate = {
                fieldConfig: utils.fieldToFieldConfig(fieldMap[fieldName]),
                fieldName,
                type: candidate.type,
                subschema: candidate.subschema,
                transformedSubschema: candidate.transformedSubschema,
            };
            if (fieldName in fieldConfigCandidatesMap) {
                fieldConfigCandidatesMap[fieldName].push(fieldConfigCandidate);
            }
            else {
                fieldConfigCandidatesMap[fieldName] = [fieldConfigCandidate];
            }
        });
    });
    const fieldConfigMap = Object.create(null);
    Object.keys(fieldConfigCandidatesMap).forEach(fieldName => {
        fieldConfigMap[fieldName] = mergeFieldConfigs(fieldConfigCandidatesMap[fieldName], typeMergingOptions);
    });
    return fieldConfigMap;
}
function mergeFieldConfigs(candidates, typeMergingOptions) {
    var _a;
    const fieldConfigMerger = (_a = typeMergingOptions === null || typeMergingOptions === void 0 ? void 0 : typeMergingOptions.fieldConfigMerger) !== null && _a !== void 0 ? _a : defaultFieldConfigMerger;
    const finalFieldConfig = fieldConfigMerger(candidates);
    validateFieldConsistency(finalFieldConfig, candidates, typeMergingOptions);
    return finalFieldConfig;
}
function defaultFieldConfigMerger(candidates) {
    const canonicalByField = [];
    const canonicalByType = [];
    candidates.forEach(({ type, fieldName, fieldConfig, transformedSubschema }) => {
        var _a, _b, _c, _d, _e, _f;
        if (!delegate.isSubschemaConfig(transformedSubschema))
            return;
        if ((_d = (_c = (_b = (_a = transformedSubschema.merge) === null || _a === void 0 ? void 0 : _a[type.name]) === null || _b === void 0 ? void 0 : _b.fields) === null || _c === void 0 ? void 0 : _c[fieldName]) === null || _d === void 0 ? void 0 : _d.canonical) {
            canonicalByField.push(fieldConfig);
        }
        else if ((_f = (_e = transformedSubschema.merge) === null || _e === void 0 ? void 0 : _e[type.name]) === null || _f === void 0 ? void 0 : _f.canonical) {
            canonicalByType.push(fieldConfig);
        }
    });
    if (canonicalByField.length > 1) {
        throw new Error(`Multiple canonical definitions for "${candidates[0].type.name}.${candidates[0].fieldName}"`);
    }
    else if (canonicalByField.length) {
        return canonicalByField[0];
    }
    else if (canonicalByType.length) {
        return canonicalByType[0];
    }
    return candidates[candidates.length - 1].fieldConfig;
}
function inputFieldConfigMapFromTypeCandidates(candidates, typeMergingOptions) {
    const inputFieldConfigCandidatesMap = Object.create(null);
    const fieldInclusionMap = Object.create(null);
    candidates.forEach(candidate => {
        const inputFieldMap = candidate.type.getFields();
        Object.keys(inputFieldMap).forEach(fieldName => {
            fieldInclusionMap[fieldName] = fieldInclusionMap[fieldName] || 0;
            fieldInclusionMap[fieldName] += 1;
            const inputFieldConfigCandidate = {
                inputFieldConfig: utils.inputFieldToFieldConfig(inputFieldMap[fieldName]),
                fieldName,
                type: candidate.type,
                subschema: candidate.subschema,
                transformedSubschema: candidate.transformedSubschema,
            };
            if (fieldName in inputFieldConfigCandidatesMap) {
                inputFieldConfigCandidatesMap[fieldName].push(inputFieldConfigCandidate);
            }
            else {
                inputFieldConfigCandidatesMap[fieldName] = [inputFieldConfigCandidate];
            }
        });
    });
    validateInputObjectConsistency(fieldInclusionMap, candidates, typeMergingOptions);
    const inputFieldConfigMap = Object.create(null);
    Object.keys(inputFieldConfigCandidatesMap).forEach(fieldName => {
        var _a;
        const inputFieldConfigMerger = (_a = typeMergingOptions === null || typeMergingOptions === void 0 ? void 0 : typeMergingOptions.inputFieldConfigMerger) !== null && _a !== void 0 ? _a : defaultInputFieldConfigMerger;
        inputFieldConfigMap[fieldName] = inputFieldConfigMerger(inputFieldConfigCandidatesMap[fieldName]);
        validateInputFieldConsistency(inputFieldConfigMap[fieldName], inputFieldConfigCandidatesMap[fieldName], typeMergingOptions);
    });
    return inputFieldConfigMap;
}
function defaultInputFieldConfigMerger(candidates) {
    const canonicalByField = [];
    const canonicalByType = [];
    candidates.forEach(({ type, fieldName, inputFieldConfig, transformedSubschema }) => {
        var _a, _b, _c, _d, _e, _f;
        if (!delegate.isSubschemaConfig(transformedSubschema))
            return;
        if ((_d = (_c = (_b = (_a = transformedSubschema.merge) === null || _a === void 0 ? void 0 : _a[type.name]) === null || _b === void 0 ? void 0 : _b.fields) === null || _c === void 0 ? void 0 : _c[fieldName]) === null || _d === void 0 ? void 0 : _d.canonical) {
            canonicalByField.push(inputFieldConfig);
        }
        else if ((_f = (_e = transformedSubschema.merge) === null || _e === void 0 ? void 0 : _e[type.name]) === null || _f === void 0 ? void 0 : _f.canonical) {
            canonicalByType.push(inputFieldConfig);
        }
    });
    if (canonicalByField.length > 1) {
        throw new Error(`Multiple canonical definitions for "${candidates[0].type.name}.${candidates[0].fieldName}"`);
    }
    else if (canonicalByField.length) {
        return canonicalByField[0];
    }
    else if (canonicalByType.length) {
        return canonicalByType[0];
    }
    return candidates[candidates.length - 1].inputFieldConfig;
}
function canonicalFieldNamesForType(candidates) {
    const canonicalFieldNames = Object.create(null);
    candidates.forEach(({ type, transformedSubschema }) => {
        var _a, _b;
        if (delegate.isSubschemaConfig(transformedSubschema) &&
            ((_b = (_a = transformedSubschema.merge) === null || _a === void 0 ? void 0 : _a[type.name]) === null || _b === void 0 ? void 0 : _b.fields) &&
            !transformedSubschema.merge[type.name].canonical) {
            Object.entries(transformedSubschema.merge[type.name].fields).forEach(([fieldName, mergedFieldConfig]) => {
                if (mergedFieldConfig.canonical) {
                    canonicalFieldNames[fieldName] = true;
                }
            });
        }
    });
    return Object.keys(canonicalFieldNames);
}

function extractDefinitions(ast) {
    const typeDefinitions = [];
    const directiveDefs = [];
    const schemaDefs = [];
    const schemaExtensions = [];
    const extensionDefs = [];
    ast.definitions.forEach(def => {
        switch (def.kind) {
            case graphql.Kind.OBJECT_TYPE_DEFINITION:
            case graphql.Kind.INTERFACE_TYPE_DEFINITION:
            case graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION:
            case graphql.Kind.UNION_TYPE_DEFINITION:
            case graphql.Kind.ENUM_TYPE_DEFINITION:
            case graphql.Kind.SCALAR_TYPE_DEFINITION:
                typeDefinitions.push(def);
                break;
            case graphql.Kind.DIRECTIVE_DEFINITION:
                directiveDefs.push(def);
                break;
            case graphql.Kind.SCHEMA_DEFINITION:
                schemaDefs.push(def);
                break;
            case graphql.Kind.SCHEMA_EXTENSION:
                schemaExtensions.push(def);
                break;
            case graphql.Kind.OBJECT_TYPE_EXTENSION:
            case graphql.Kind.INTERFACE_TYPE_EXTENSION:
            case graphql.Kind.INPUT_OBJECT_TYPE_EXTENSION:
            case graphql.Kind.UNION_TYPE_EXTENSION:
            case graphql.Kind.ENUM_TYPE_EXTENSION:
            case graphql.Kind.SCALAR_TYPE_EXTENSION:
                extensionDefs.push(def);
                break;
        }
    });
    return {
        typeDefinitions,
        directiveDefs,
        schemaDefs,
        schemaExtensions,
        extensionDefs,
    };
}

function buildTypeCandidates({ subschemas, originalSubschemaMap, types, typeDefs, parseOptions, extensions, directiveMap, schemaDefs, operationTypeNames, mergeDirectives, }) {
    const typeCandidates = Object.create(null);
    let schemaDef;
    let schemaExtensions = [];
    let document;
    let extraction;
    if ((typeDefs && !Array.isArray(typeDefs)) || (Array.isArray(typeDefs) && typeDefs.length)) {
        document = schema.buildDocumentFromTypeDefinitions(typeDefs, parseOptions);
        extraction = extractDefinitions(document);
        schemaDef = extraction.schemaDefs[0];
        schemaExtensions = schemaExtensions.concat(extraction.schemaExtensions);
    }
    schemaDefs.schemaDef = schemaDef;
    schemaDefs.schemaExtensions = schemaExtensions;
    setOperationTypeNames(schemaDefs, operationTypeNames);
    subschemas.forEach(subschema => {
        const schema = wrap.wrapSchema(subschema);
        const operationTypes = {
            query: schema.getQueryType(),
            mutation: schema.getMutationType(),
            subscription: schema.getSubscriptionType(),
        };
        Object.keys(operationTypes).forEach(operationType => {
            if (operationTypes[operationType] != null) {
                addTypeCandidate(typeCandidates, operationTypeNames[operationType], {
                    type: operationTypes[operationType],
                    subschema: originalSubschemaMap.get(subschema),
                    transformedSubschema: subschema,
                });
            }
        });
        if (mergeDirectives) {
            schema.getDirectives().forEach(directive => {
                directiveMap[directive.name] = directive;
            });
        }
        const originalTypeMap = schema.getTypeMap();
        Object.keys(originalTypeMap).forEach(typeName => {
            const type = originalTypeMap[typeName];
            if (graphql.isNamedType(type) &&
                graphql.getNamedType(type).name.slice(0, 2) !== '__' &&
                type !== operationTypes.query &&
                type !== operationTypes.mutation &&
                type !== operationTypes.subscription) {
                addTypeCandidate(typeCandidates, type.name, {
                    type,
                    subschema: originalSubschemaMap.get(subschema),
                    transformedSubschema: subschema,
                });
            }
        });
    });
    if (document !== undefined) {
        extraction.typeDefinitions.forEach(def => {
            const type = typeFromAST(def);
            if (type != null) {
                addTypeCandidate(typeCandidates, type.name, { type });
            }
        });
        extraction.directiveDefs.forEach(def => {
            const directive = typeFromAST(def);
            directiveMap[directive.name] = directive;
        });
        if (extraction.extensionDefs.length > 0) {
            extensions.push({
                ...document,
                definitions: extraction.extensionDefs,
            });
        }
    }
    types.forEach(type => addTypeCandidate(typeCandidates, type.name, { type }));
    return typeCandidates;
}
function setOperationTypeNames({ schemaDef, schemaExtensions, }, operationTypeNames) {
    const allNodes = schemaExtensions.slice();
    if (schemaDef != null) {
        allNodes.unshift(schemaDef);
    }
    allNodes.forEach(node => {
        if (node.operationTypes != null) {
            node.operationTypes.forEach(operationType => {
                operationTypeNames[operationType.operation] = operationType.type.name.value;
            });
        }
    });
}
function addTypeCandidate(typeCandidates, name, typeCandidate) {
    if (!(name in typeCandidates)) {
        typeCandidates[name] = [];
    }
    typeCandidates[name].push(typeCandidate);
}
function buildTypes({ typeCandidates, directives, stitchingInfo, operationTypeNames, onTypeConflict, mergeTypes, typeMergingOptions, }) {
    const typeMap = Object.create(null);
    Object.keys(typeCandidates).forEach(typeName => {
        if (typeName === operationTypeNames.query ||
            typeName === operationTypeNames.mutation ||
            typeName === operationTypeNames.subscription ||
            (mergeTypes === true && !typeCandidates[typeName].some(candidate => graphql.isSpecifiedScalarType(candidate.type))) ||
            (typeof mergeTypes === 'function' && mergeTypes(typeCandidates[typeName], typeName)) ||
            (Array.isArray(mergeTypes) && mergeTypes.includes(typeName)) ||
            (stitchingInfo != null && typeName in stitchingInfo.mergedTypes)) {
            typeMap[typeName] = mergeCandidates(typeName, typeCandidates[typeName], typeMergingOptions);
        }
        else {
            const candidateSelector = onTypeConflict != null
                ? onTypeConflictToCandidateSelector(onTypeConflict)
                : (cands) => cands[cands.length - 1];
            typeMap[typeName] = candidateSelector(typeCandidates[typeName]).type;
        }
    });
    return utils.rewireTypes(typeMap, directives);
}
function onTypeConflictToCandidateSelector(onTypeConflict) {
    return cands => cands.reduce((prev, next) => {
        const type = onTypeConflict(prev.type, next.type, {
            left: {
                subschema: prev.subschema,
                transformedSubschema: prev.transformedSubschema,
            },
            right: {
                subschema: prev.subschema,
                transformedSubschema: prev.transformedSubschema,
            },
        });
        if (prev.type === type) {
            return prev;
        }
        else if (next.type === type) {
            return next;
        }
        return {
            schemaName: 'unknown',
            type,
        };
    });
}

function createMergedTypeResolver(mergedTypeResolverOptions) {
    const { fieldName, argsFromKeys, valuesFromResults, args } = mergedTypeResolverOptions;
    if (argsFromKeys != null) {
        return (originalResult, context, info, subschema, selectionSet, key) => {
            var _a;
            return batchDelegate.batchDelegateToSchema({
                schema: subschema,
                operation: 'query',
                fieldName,
                returnType: new graphql.GraphQLList(graphql.getNamedType((_a = info.schema.getType(originalResult.__typename)) !== null && _a !== void 0 ? _a : info.returnType)),
                key,
                argsFromKeys,
                valuesFromResults,
                selectionSet,
                context,
                info,
                skipTypeMerging: true,
            });
        };
    }
    if (args != null) {
        return (originalResult, context, info, subschema, selectionSet) => {
            var _a;
            return delegate.delegateToSchema({
                schema: subschema,
                operation: 'query',
                fieldName,
                returnType: graphql.getNamedType((_a = info.schema.getType(originalResult.__typename)) !== null && _a !== void 0 ? _a : info.returnType),
                args: args(originalResult),
                selectionSet,
                context,
                info,
                skipTypeMerging: true,
            });
        };
    }
    return undefined;
}

function createStitchingInfo(subschemaMap, typeCandidates, mergeTypes) {
    const mergedTypes = createMergedTypes(typeCandidates, mergeTypes);
    const selectionSetsByField = Object.create(null);
    Object.entries(mergedTypes).forEach(([typeName, mergedTypeInfo]) => {
        if (mergedTypeInfo.selectionSets == null && mergedTypeInfo.fieldSelectionSets == null) {
            return;
        }
        selectionSetsByField[typeName] = Object.create(null);
        mergedTypeInfo.selectionSets.forEach((selectionSet, subschemaConfig) => {
            const schema = subschemaConfig.transformedSchema;
            const type = schema.getType(typeName);
            const fields = type.getFields();
            Object.keys(fields).forEach(fieldName => {
                const field = fields[fieldName];
                const fieldType = graphql.getNamedType(field.type);
                if (selectionSet && graphql.isLeafType(fieldType) && selectionSetContainsTopLevelField(selectionSet, fieldName)) {
                    return;
                }
                if (selectionSetsByField[typeName][fieldName] == null) {
                    selectionSetsByField[typeName][fieldName] = {
                        kind: graphql.Kind.SELECTION_SET,
                        selections: [utils.parseSelectionSet('{ __typename }', { noLocation: true }).selections[0]],
                    };
                }
                selectionSetsByField[typeName][fieldName].selections = selectionSetsByField[typeName][fieldName].selections.concat(selectionSet.selections);
            });
        });
        mergedTypeInfo.fieldSelectionSets.forEach(selectionSetFieldMap => {
            Object.keys(selectionSetFieldMap).forEach(fieldName => {
                if (selectionSetsByField[typeName][fieldName] == null) {
                    selectionSetsByField[typeName][fieldName] = {
                        kind: graphql.Kind.SELECTION_SET,
                        selections: [utils.parseSelectionSet('{ __typename }', { noLocation: true }).selections[0]],
                    };
                }
                selectionSetsByField[typeName][fieldName].selections = selectionSetsByField[typeName][fieldName].selections.concat(selectionSetFieldMap[fieldName].selections);
            });
        });
    });
    return {
        subschemaMap,
        selectionSetsByType: undefined,
        selectionSetsByField,
        dynamicSelectionSetsByField: undefined,
        mergedTypes,
    };
}
function createMergedTypes(typeCandidates, mergeTypes) {
    const mergedTypes = Object.create(null);
    Object.keys(typeCandidates).forEach(typeName => {
        if (typeCandidates[typeName].length > 1 &&
            (graphql.isObjectType(typeCandidates[typeName][0].type) || graphql.isInterfaceType(typeCandidates[typeName][0].type))) {
            const typeCandidatesWithMergedTypeConfig = typeCandidates[typeName].filter(typeCandidate => typeCandidate.transformedSubschema != null &&
                typeCandidate.transformedSubschema.merge != null &&
                typeName in typeCandidate.transformedSubschema.merge);
            if (mergeTypes === true ||
                (typeof mergeTypes === 'function' && mergeTypes(typeCandidates[typeName], typeName)) ||
                (Array.isArray(mergeTypes) && mergeTypes.includes(typeName)) ||
                typeCandidatesWithMergedTypeConfig.length) {
                const targetSubschemas = [];
                const typeMaps = new Map();
                const supportedBySubschemas = Object.create({});
                const selectionSets = new Map();
                const fieldSelectionSets = new Map();
                const resolvers = new Map();
                typeCandidates[typeName].forEach(typeCandidate => {
                    var _a, _b;
                    const subschema = typeCandidate.transformedSubschema;
                    if (subschema == null) {
                        return;
                    }
                    typeMaps.set(subschema, subschema.transformedSchema.getTypeMap());
                    const mergedTypeConfig = (_a = subschema === null || subschema === void 0 ? void 0 : subschema.merge) === null || _a === void 0 ? void 0 : _a[typeName];
                    if (mergedTypeConfig == null) {
                        return;
                    }
                    if (mergedTypeConfig.selectionSet) {
                        const selectionSet = utils.parseSelectionSet(mergedTypeConfig.selectionSet, { noLocation: true });
                        selectionSets.set(subschema, selectionSet);
                    }
                    if (mergedTypeConfig.fields) {
                        const parsedFieldSelectionSets = Object.create(null);
                        Object.keys(mergedTypeConfig.fields).forEach(fieldName => {
                            if (mergedTypeConfig.fields[fieldName].selectionSet) {
                                const rawFieldSelectionSet = mergedTypeConfig.fields[fieldName].selectionSet;
                                parsedFieldSelectionSets[fieldName] = utils.parseSelectionSet(rawFieldSelectionSet, { noLocation: true });
                            }
                        });
                        fieldSelectionSets.set(subschema, parsedFieldSelectionSets);
                    }
                    const resolver = (_b = mergedTypeConfig.resolve) !== null && _b !== void 0 ? _b : createMergedTypeResolver(mergedTypeConfig);
                    if (resolver == null) {
                        return;
                    }
                    const keyFn = mergedTypeConfig.key;
                    resolvers.set(subschema, keyFn
                        ? (originalResult, context, info, subschema, selectionSet) => {
                            const key = keyFn(originalResult);
                            return resolver(originalResult, context, info, subschema, selectionSet, key);
                        }
                        : resolver);
                    targetSubschemas.push(subschema);
                    const type = subschema.transformedSchema.getType(typeName);
                    const fieldMap = type.getFields();
                    const selectionSet = selectionSets.get(subschema);
                    Object.keys(fieldMap).forEach(fieldName => {
                        const field = fieldMap[fieldName];
                        const fieldType = graphql.getNamedType(field.type);
                        if (selectionSet && graphql.isLeafType(fieldType) && selectionSetContainsTopLevelField(selectionSet, fieldName)) {
                            return;
                        }
                        if (!(fieldName in supportedBySubschemas)) {
                            supportedBySubschemas[fieldName] = [];
                        }
                        supportedBySubschemas[fieldName].push(subschema);
                    });
                });
                const sourceSubschemas = typeCandidates[typeName]
                    .filter(typeCandidate => typeCandidate.transformedSubschema != null)
                    .map(typeCandidate => typeCandidate.transformedSubschema);
                const targetSubschemasBySubschema = new Map();
                sourceSubschemas.forEach(subschema => {
                    const filteredSubschemas = targetSubschemas.filter(s => s !== subschema);
                    if (filteredSubschemas.length) {
                        targetSubschemasBySubschema.set(subschema, filteredSubschemas);
                    }
                });
                mergedTypes[typeName] = {
                    typeName,
                    targetSubschemas: targetSubschemasBySubschema,
                    typeMaps,
                    selectionSets,
                    fieldSelectionSets,
                    uniqueFields: Object.create({}),
                    nonUniqueFields: Object.create({}),
                    resolvers,
                };
                Object.keys(supportedBySubschemas).forEach(fieldName => {
                    if (supportedBySubschemas[fieldName].length === 1) {
                        mergedTypes[typeName].uniqueFields[fieldName] = supportedBySubschemas[fieldName][0];
                    }
                    else {
                        mergedTypes[typeName].nonUniqueFields[fieldName] = supportedBySubschemas[fieldName];
                    }
                });
            }
        }
    });
    return mergedTypes;
}
function completeStitchingInfo(stitchingInfo, resolvers, schema) {
    const selectionSetsByType = Object.create(null);
    [schema.getQueryType(), schema.getMutationType()].forEach(rootType => {
        if (rootType) {
            selectionSetsByType[rootType.name] = utils.parseSelectionSet('{ __typename }', { noLocation: true });
        }
    });
    const selectionSetsByField = stitchingInfo.selectionSetsByField;
    const dynamicSelectionSetsByField = Object.create(null);
    Object.keys(resolvers).forEach(typeName => {
        const type = resolvers[typeName];
        if (graphql.isScalarType(type)) {
            return;
        }
        Object.keys(type).forEach(fieldName => {
            const field = type[fieldName];
            if (field.selectionSet) {
                if (typeof field.selectionSet === 'function') {
                    if (!(typeName in dynamicSelectionSetsByField)) {
                        dynamicSelectionSetsByField[typeName] = Object.create(null);
                    }
                    if (!(fieldName in dynamicSelectionSetsByField[typeName])) {
                        dynamicSelectionSetsByField[typeName][fieldName] = [];
                    }
                    dynamicSelectionSetsByField[typeName][fieldName].push(field.selectionSet);
                }
                else {
                    const selectionSet = utils.parseSelectionSet(field.selectionSet, { noLocation: true });
                    if (!(typeName in selectionSetsByField)) {
                        selectionSetsByField[typeName] = Object.create(null);
                    }
                    if (!(fieldName in selectionSetsByField[typeName])) {
                        selectionSetsByField[typeName][fieldName] = {
                            kind: graphql.Kind.SELECTION_SET,
                            selections: [],
                        };
                    }
                    selectionSetsByField[typeName][fieldName].selections = selectionSetsByField[typeName][fieldName].selections.concat(selectionSet.selections);
                }
            }
        });
    });
    Object.keys(selectionSetsByField).forEach(typeName => {
        const typeSelectionSets = selectionSetsByField[typeName];
        Object.keys(typeSelectionSets).forEach(fieldName => {
            const consolidatedSelections = new Map();
            const selectionSet = typeSelectionSets[fieldName];
            selectionSet.selections.forEach(selection => {
                consolidatedSelections.set(graphql.print(selection), selection);
            });
            selectionSet.selections = Array.from(consolidatedSelections.values());
        });
    });
    stitchingInfo.selectionSetsByType = selectionSetsByType;
    stitchingInfo.selectionSetsByField = selectionSetsByField;
    stitchingInfo.dynamicSelectionSetsByField = dynamicSelectionSetsByField;
    return stitchingInfo;
}
function addStitchingInfo(stitchedSchema, stitchingInfo) {
    return new graphql.GraphQLSchema({
        ...stitchedSchema.toConfig(),
        extensions: {
            ...stitchedSchema.extensions,
            stitchingInfo,
        },
    });
}
function selectionSetContainsTopLevelField(selectionSet, fieldName) {
    return selectionSet.selections.some(selection => selection.kind === graphql.Kind.FIELD && selection.name.value === fieldName);
}

function computedDirectiveTransformer(computedDirectiveName) {
    return (subschemaConfig) => {
        const newSubschemaConfig = delegate.cloneSubschemaConfig(subschemaConfig);
        utils.mapSchema(subschemaConfig.schema, {
            [utils.MapperKind.OBJECT_FIELD]: (fieldConfig, fieldName, typeName, schema) => {
                var _a, _b, _c, _d;
                const mergeTypeConfig = (_a = newSubschemaConfig.merge) === null || _a === void 0 ? void 0 : _a[typeName];
                if (mergeTypeConfig == null) {
                    return undefined;
                }
                const computed = utils.getDirectives(schema, fieldConfig)[computedDirectiveName];
                if (computed == null) {
                    return undefined;
                }
                const selectionSet = computed.fields != null ? `{ ${computed.fields} }` : computed.selectionSet;
                if (selectionSet == null) {
                    return undefined;
                }
                mergeTypeConfig.fields = (_b = mergeTypeConfig.fields) !== null && _b !== void 0 ? _b : {};
                mergeTypeConfig.fields[fieldName] = (_c = mergeTypeConfig.fields[fieldName]) !== null && _c !== void 0 ? _c : {};
                const mergeFieldConfig = mergeTypeConfig.fields[fieldName];
                mergeFieldConfig.selectionSet = (_d = mergeFieldConfig.selectionSet) !== null && _d !== void 0 ? _d : selectionSet;
                mergeFieldConfig.computed = true;
                return undefined;
            },
        });
        return newSubschemaConfig;
    };
}

function isolateComputedFieldsTransformer(subschemaConfig) {
    if (subschemaConfig.merge == null) {
        return [subschemaConfig];
    }
    const baseSchemaTypes = Object.create(null);
    const isolatedSchemaTypes = Object.create(null);
    Object.entries(subschemaConfig.merge).forEach(([typeName, mergedTypeConfig]) => {
        var _a;
        baseSchemaTypes[typeName] = mergedTypeConfig;
        if (mergedTypeConfig.computedFields) {
            mergedTypeConfig.fields = (_a = mergedTypeConfig.fields) !== null && _a !== void 0 ? _a : Object.create(null);
            Object.entries(mergedTypeConfig.computedFields).forEach(([fieldName, mergedFieldConfig]) => {
                var _a;
                console.warn(`The "computedFields" setting is deprecated. Update your @graphql-tools/stitching-directives package, and/or update static merged type config to "${typeName}.fields.${fieldName} = { selectionSet: '${mergedFieldConfig.selectionSet}', computed: true }"`);
                mergedTypeConfig.fields[fieldName] = {
                    ...((_a = mergedTypeConfig.fields[fieldName]) !== null && _a !== void 0 ? _a : {}),
                    ...mergedFieldConfig,
                    computed: true,
                };
            });
            delete mergedTypeConfig.computedFields;
        }
        if (mergedTypeConfig.fields) {
            const baseFields = Object.create(null);
            const isolatedFields = Object.create(null);
            Object.entries(mergedTypeConfig.fields).forEach(([fieldName, mergedFieldConfig]) => {
                if (mergedFieldConfig.computed && mergedFieldConfig.selectionSet) {
                    isolatedFields[fieldName] = mergedFieldConfig;
                }
                else if (mergedFieldConfig.computed) {
                    throw new Error(`A selectionSet is required for computed field "${typeName}.${fieldName}"`);
                }
                else {
                    baseFields[fieldName] = mergedFieldConfig;
                }
            });
            const isolatedFieldCount = Object.keys(isolatedFields).length;
            const objectType = subschemaConfig.schema.getType(typeName);
            if (isolatedFieldCount && isolatedFieldCount !== Object.keys(objectType.getFields()).length) {
                baseSchemaTypes[typeName] = {
                    ...mergedTypeConfig,
                    fields: baseFields,
                };
                isolatedSchemaTypes[typeName] = {
                    ...mergedTypeConfig,
                    fields: isolatedFields,
                    canonical: undefined,
                };
            }
        }
    });
    if (Object.keys(isolatedSchemaTypes).length) {
        return [
            filterBaseSubschema({ ...subschemaConfig, merge: baseSchemaTypes }, isolatedSchemaTypes),
            filterIsolatedSubschema({ ...subschemaConfig, merge: isolatedSchemaTypes }),
        ];
    }
    return [subschemaConfig];
}
function filterBaseSubschema(subschemaConfig, isolatedSchemaTypes) {
    var _a;
    const schema = subschemaConfig.schema;
    const typesForInterface = {};
    const filteredSchema = utils.pruneSchema(utils.filterSchema({
        schema,
        objectFieldFilter: (typeName, fieldName) => { var _a; return !((_a = isolatedSchemaTypes[typeName]) === null || _a === void 0 ? void 0 : _a.fields[fieldName]); },
        interfaceFieldFilter: (typeName, fieldName) => {
            if (!typesForInterface[typeName]) {
                typesForInterface[typeName] = utils.getImplementingTypes(typeName, schema);
            }
            return !typesForInterface[typeName].some(implementingTypeName => { var _a; return (_a = isolatedSchemaTypes[implementingTypeName]) === null || _a === void 0 ? void 0 : _a.fields[fieldName]; });
        },
    }));
    const filteredFields = {};
    Object.keys(filteredSchema.getTypeMap()).forEach(typeName => {
        const type = filteredSchema.getType(typeName);
        if (graphql.isObjectType(type) || graphql.isInterfaceType(type)) {
            filteredFields[typeName] = { __typename: true };
            const fieldMap = type.getFields();
            Object.keys(fieldMap).forEach(fieldName => {
                filteredFields[typeName][fieldName] = true;
            });
        }
    });
    const filteredSubschema = {
        ...subschemaConfig,
        merge: subschemaConfig.merge
            ? {
                ...subschemaConfig.merge,
            }
            : undefined,
        transforms: ((_a = subschemaConfig.transforms) !== null && _a !== void 0 ? _a : []).concat([
            new wrap.TransformCompositeFields((typeName, fieldName) => { var _a; return (((_a = filteredFields[typeName]) === null || _a === void 0 ? void 0 : _a[fieldName]) ? undefined : null); }, (typeName, fieldName) => { var _a; return (((_a = filteredFields[typeName]) === null || _a === void 0 ? void 0 : _a[fieldName]) ? undefined : null); }),
        ]),
    };
    const remainingTypes = filteredSchema.getTypeMap();
    Object.keys(filteredSubschema.merge).forEach(mergeType => {
        if (!remainingTypes[mergeType]) {
            delete filteredSubschema.merge[mergeType];
        }
    });
    if (!Object.keys(filteredSubschema.merge).length) {
        delete filteredSubschema.merge;
    }
    return filteredSubschema;
}
function filterIsolatedSubschema(subschemaConfig) {
    var _a;
    const rootFields = {};
    Object.values(subschemaConfig.merge).forEach(mergedTypeConfig => {
        var _a;
        const entryPoints = (_a = mergedTypeConfig.entryPoints) !== null && _a !== void 0 ? _a : [mergedTypeConfig];
        entryPoints.forEach(entryPoint => {
            rootFields[entryPoint.fieldName] = true;
        });
    });
    const interfaceFields = {};
    Object.keys(subschemaConfig.merge).forEach(typeName => {
        subschemaConfig.schema.getType(typeName).getInterfaces().forEach(int => {
            Object.keys(subschemaConfig.schema.getType(int.name).getFields()).forEach(intFieldName => {
                if (subschemaConfig.merge[typeName].fields[intFieldName]) {
                    interfaceFields[int.name] = interfaceFields[int.name] || {};
                    interfaceFields[int.name][intFieldName] = true;
                }
            });
        });
    });
    const filteredSchema = utils.pruneSchema(utils.filterSchema({
        schema: subschemaConfig.schema,
        rootFieldFilter: (operation, fieldName) => operation === 'Query' && rootFields[fieldName] != null,
        objectFieldFilter: (typeName, fieldName) => { var _a; return ((_a = subschemaConfig.merge[typeName]) === null || _a === void 0 ? void 0 : _a.fields[fieldName]) != null; },
        interfaceFieldFilter: (typeName, fieldName) => { var _a; return ((_a = interfaceFields[typeName]) === null || _a === void 0 ? void 0 : _a[fieldName]) != null; },
    }));
    const filteredFields = {};
    Object.keys(filteredSchema.getTypeMap()).forEach(typeName => {
        const type = filteredSchema.getType(typeName);
        if (graphql.isObjectType(type) || graphql.isInterfaceType(type)) {
            filteredFields[typeName] = { __typename: true };
            const fieldMap = type.getFields();
            Object.keys(fieldMap).forEach(fieldName => {
                filteredFields[typeName][fieldName] = true;
            });
        }
    });
    return {
        ...subschemaConfig,
        transforms: ((_a = subschemaConfig.transforms) !== null && _a !== void 0 ? _a : []).concat([
            new wrap.TransformCompositeFields((typeName, fieldName) => { var _a; return (((_a = filteredFields[typeName]) === null || _a === void 0 ? void 0 : _a[fieldName]) ? undefined : null); }, (typeName, fieldName) => { var _a; return (((_a = filteredFields[typeName]) === null || _a === void 0 ? void 0 : _a[fieldName]) ? undefined : null); }),
        ]),
    };
}

function splitMergedTypeEntryPointsTransformer(subschemaConfig) {
    if (!subschemaConfig.merge)
        return [subschemaConfig];
    const maxEntryPoints = Object.values(subschemaConfig.merge).reduce((max, mergedTypeConfig) => {
        var _a, _b;
        return Math.max(max, (_b = (_a = mergedTypeConfig === null || mergedTypeConfig === void 0 ? void 0 : mergedTypeConfig.entryPoints) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0);
    }, 0);
    if (maxEntryPoints === 0)
        return [subschemaConfig];
    const subschemaPermutations = [];
    for (let i = 0; i < maxEntryPoints; i += 1) {
        const subschemaPermutation = delegate.cloneSubschemaConfig(subschemaConfig);
        const mergedTypesCopy = subschemaPermutation.merge;
        if (i > 0) {
            subschemaPermutation.merge = Object.create(null);
        }
        Object.keys(mergedTypesCopy).forEach(typeName => {
            var _a, _b, _c;
            const mergedTypeConfig = mergedTypesCopy[typeName];
            const mergedTypeEntryPoint = (_a = mergedTypeConfig === null || mergedTypeConfig === void 0 ? void 0 : mergedTypeConfig.entryPoints) === null || _a === void 0 ? void 0 : _a[i];
            if (mergedTypeEntryPoint) {
                if ((_c = (_b = mergedTypeConfig.selectionSet) !== null && _b !== void 0 ? _b : mergedTypeConfig.fieldName) !== null && _c !== void 0 ? _c : mergedTypeConfig.resolve) {
                    throw new Error(`Merged type ${typeName} may not define entryPoints in addition to selectionSet, fieldName, or resolve`);
                }
                Object.assign(mergedTypeConfig, mergedTypeEntryPoint);
                delete mergedTypeConfig.entryPoints;
                if (i > 0) {
                    delete mergedTypeConfig.canonical;
                    if (mergedTypeConfig.fields != null) {
                        Object.values(mergedTypeConfig.fields).forEach(mergedFieldConfig => {
                            delete mergedFieldConfig.canonical;
                        });
                    }
                }
                subschemaPermutation.merge[typeName] = mergedTypeConfig;
            }
        });
        subschemaPermutations.push(subschemaPermutation);
    }
    return subschemaPermutations;
}

const defaultSubschemaConfigTransforms = [computedDirectiveTransformer('computed')];

function stitchSchemas({ subschemas = [], types = [], typeDefs, onTypeConflict, mergeDirectives, mergeTypes = true, typeMergingOptions, subschemaConfigTransforms = defaultSubschemaConfigTransforms, resolvers = {}, schemaDirectives, inheritResolversFromInterfaces = false, logger, allowUndefinedInResolve = true, resolverValidationOptions = {}, directiveResolvers, schemaTransforms = [], parseOptions = {}, pruningOptions, updateResolversInPlace, }) {
    if (typeof resolverValidationOptions !== 'object') {
        throw new Error('Expected `resolverValidationOptions` to be an object');
    }
    let transformedSubschemas = [];
    const subschemaMap = new Map();
    const originalSubschemaMap = new Map();
    subschemas.forEach(subschemaOrSubschemaArray => {
        if (Array.isArray(subschemaOrSubschemaArray)) {
            subschemaOrSubschemaArray.forEach(s => {
                transformedSubschemas = transformedSubschemas.concat(applySubschemaConfigTransforms(subschemaConfigTransforms, s, subschemaMap, originalSubschemaMap));
            });
        }
        else {
            transformedSubschemas = transformedSubschemas.concat(applySubschemaConfigTransforms(subschemaConfigTransforms, subschemaOrSubschemaArray, subschemaMap, originalSubschemaMap));
        }
    });
    const extensions = [];
    const directives = [];
    const directiveMap = graphql.specifiedDirectives.reduce((acc, directive) => {
        acc[directive.name] = directive;
        return acc;
    }, Object.create(null));
    const schemaDefs = Object.create(null);
    const operationTypeNames = {
        query: 'Query',
        mutation: 'Mutation',
        subscription: 'Subscription',
    };
    const typeCandidates = buildTypeCandidates({
        subschemas: transformedSubschemas,
        originalSubschemaMap,
        types,
        typeDefs,
        parseOptions,
        extensions,
        directiveMap,
        schemaDefs,
        operationTypeNames,
        mergeDirectives,
    });
    Object.keys(directiveMap).forEach(directiveName => {
        directives.push(directiveMap[directiveName]);
    });
    let stitchingInfo = createStitchingInfo(subschemaMap, typeCandidates, mergeTypes);
    const { typeMap: newTypeMap, directives: newDirectives } = buildTypes({
        typeCandidates,
        directives,
        stitchingInfo,
        operationTypeNames,
        onTypeConflict,
        mergeTypes,
        typeMergingOptions,
    });
    let schema$1 = new graphql.GraphQLSchema({
        query: newTypeMap[operationTypeNames.query],
        mutation: newTypeMap[operationTypeNames.mutation],
        subscription: newTypeMap[operationTypeNames.subscription],
        types: Object.keys(newTypeMap).map(key => newTypeMap[key]),
        directives: newDirectives,
        astNode: schemaDefs.schemaDef,
        extensionASTNodes: schemaDefs.schemaExtensions,
        extensions: null,
    });
    extensions.forEach(extension => {
        schema$1 = graphql.extendSchema(schema$1, extension, {
            commentDescriptions: true,
        });
    });
    // We allow passing in an array of resolver maps, in which case we merge them
    const resolverMap = Array.isArray(resolvers) ? resolvers.reduce(utils.mergeDeep, {}) : resolvers;
    const finalResolvers = inheritResolversFromInterfaces
        ? schema.extendResolversFromInterfaces(schema$1, resolverMap)
        : resolverMap;
    stitchingInfo = completeStitchingInfo(stitchingInfo, finalResolvers, schema$1);
    schema$1 = schema.addResolversToSchema({
        schema: schema$1,
        defaultFieldResolver: delegate.defaultMergedResolver,
        resolvers: finalResolvers,
        resolverValidationOptions,
        inheritResolversFromInterfaces: false,
        updateResolversInPlace,
    });
    if (Object.keys(resolverValidationOptions).length > 0) {
        schema.assertResolversPresent(schema$1, resolverValidationOptions);
    }
    schema$1 = addStitchingInfo(schema$1, stitchingInfo);
    if (!allowUndefinedInResolve) {
        schema$1 = schema.addCatchUndefinedToSchema(schema$1);
    }
    if (logger != null) {
        schema$1 = schema.addErrorLoggingToSchema(schema$1, logger);
    }
    if (typeof finalResolvers['__schema'] === 'function') {
        // TODO a bit of a hack now, better rewrite generateSchema to attach it there.
        // not doing that now, because I'd have to rewrite a lot of tests.
        schema$1 = schema.addSchemaLevelResolver(schema$1, finalResolvers['__schema']);
    }
    schemaTransforms.forEach(schemaTransform => {
        schema$1 = schemaTransform(schema$1);
    });
    if (directiveResolvers != null) {
        schema$1 = schema.attachDirectiveResolvers(schema$1, directiveResolvers);
    }
    if (schemaDirectives != null) {
        utils.SchemaDirectiveVisitor.visitSchemaDirectives(schema$1, schemaDirectives);
    }
    if (pruningOptions) {
        schema$1 = utils.pruneSchema(schema$1, pruningOptions);
    }
    return schema$1;
}
const subschemaConfigTransformerPresets = [isolateComputedFieldsTransformer, splitMergedTypeEntryPointsTransformer];
function applySubschemaConfigTransforms(subschemaConfigTransforms, subschemaOrSubschemaConfig, subschemaMap, originalSubschemaMap) {
    const subschemaConfig = delegate.isSubschemaConfig(subschemaOrSubschemaConfig)
        ? subschemaOrSubschemaConfig
        : { schema: subschemaOrSubschemaConfig };
    let transformedSubschemaConfigs = [subschemaConfig];
    subschemaConfigTransforms.concat(subschemaConfigTransformerPresets).forEach(subschemaConfigTransform => {
        const mapped = transformedSubschemaConfigs.map(ssConfig => subschemaConfigTransform(ssConfig));
        transformedSubschemaConfigs = mapped.reduce((acc, configOrList) => {
            if (Array.isArray(configOrList)) {
                return acc.concat(configOrList);
            }
            acc.push(configOrList);
            return acc;
        }, []);
    });
    const transformedSubschemas = transformedSubschemaConfigs.map(ssConfig => new delegate.Subschema(ssConfig));
    const baseSubschema = transformedSubschemas[0];
    subschemaMap.set(subschemaOrSubschemaConfig, baseSubschema);
    transformedSubschemas.forEach(subschema => originalSubschemaMap.set(subschema, subschemaOrSubschemaConfig));
    return transformedSubschemas;
}

const forwardArgsToSelectionSet = (selectionSet, mapping) => {
    const selectionSetDef = utils.parseSelectionSet(selectionSet, { noLocation: true });
    return (field) => {
        const selections = selectionSetDef.selections.map((selectionNode) => {
            if (selectionNode.kind === graphql.Kind.FIELD) {
                if (!mapping) {
                    return { ...selectionNode, arguments: field.arguments.slice() };
                }
                else if (selectionNode.name.value in mapping) {
                    const selectionArgs = mapping[selectionNode.name.value];
                    return {
                        ...selectionNode,
                        arguments: field.arguments.filter((arg) => selectionArgs.includes(arg.name.value)),
                    };
                }
            }
            return selectionNode;
        });
        return { ...selectionSetDef, selections };
    };
};

exports.computedDirectiveTransformer = computedDirectiveTransformer;
exports.createMergedTypeResolver = createMergedTypeResolver;
exports.defaultSubschemaConfigTransforms = defaultSubschemaConfigTransforms;
exports.forwardArgsToSelectionSet = forwardArgsToSelectionSet;
exports.isolateComputedFieldsTransformer = isolateComputedFieldsTransformer;
exports.splitMergedTypeEntryPointsTransformer = splitMergedTypeEntryPointsTransformer;
exports.stitchSchemas = stitchSchemas;
//# sourceMappingURL=index.cjs.js.map
