"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const graphql_1 = require("graphql");
const utils_1 = require("@graphql-tools/utils");
class TransformCompositeFields {
    constructor(fieldTransformer, fieldNodeTransformer, dataTransformer, errorsTransformer) {
        this.fieldTransformer = fieldTransformer;
        this.fieldNodeTransformer = fieldNodeTransformer;
        this.dataTransformer = dataTransformer;
        this.errorsTransformer = errorsTransformer;
        this.mapping = {};
    }
    _getTypeInfo() {
        const typeInfo = this.typeInfo;
        if (typeInfo === undefined) {
            throw new Error(`The TransformCompositeFields transform's  "transformRequest" and "transformResult" methods cannot be used without first calling "transformSchema".`);
        }
        return typeInfo;
    }
    transformSchema(originalWrappingSchema, _subschemaConfig) {
        this.transformedSchema = (0, utils_1.mapSchema)(originalWrappingSchema, {
            [utils_1.MapperKind.COMPOSITE_FIELD]: (fieldConfig, fieldName, typeName) => {
                const transformedField = this.fieldTransformer(typeName, fieldName, fieldConfig);
                if (Array.isArray(transformedField)) {
                    const newFieldName = transformedField[0];
                    if (newFieldName !== fieldName) {
                        if (!(typeName in this.mapping)) {
                            this.mapping[typeName] = {};
                        }
                        this.mapping[typeName][newFieldName] = fieldName;
                    }
                }
                return transformedField;
            },
        });
        this.typeInfo = new graphql_1.TypeInfo(this.transformedSchema);
        this.subscriptionTypeName = originalWrappingSchema.getSubscriptionType()?.name;
        return this.transformedSchema;
    }
    transformRequest(originalRequest, _delegationContext, transformationContext) {
        const document = originalRequest.document;
        return {
            ...originalRequest,
            document: this.transformDocument(document, transformationContext),
        };
    }
    transformResult(result, _delegationContext, transformationContext) {
        const dataTransformer = this.dataTransformer;
        if (dataTransformer != null) {
            result.data = (0, utils_1.visitData)(result.data, value => dataTransformer(value, transformationContext));
        }
        if (this.errorsTransformer != null && Array.isArray(result.errors)) {
            result.errors = this.errorsTransformer(result.errors, transformationContext);
        }
        return result;
    }
    transformDocument(document, transformationContext) {
        const fragments = Object.create(null);
        for (const def of document.definitions) {
            if (def.kind === graphql_1.Kind.FRAGMENT_DEFINITION) {
                fragments[def.name.value] = def;
            }
        }
        return (0, graphql_1.visit)(document, (0, graphql_1.visitWithTypeInfo)(this._getTypeInfo(), {
            [graphql_1.Kind.SELECTION_SET]: {
                leave: node => this.transformSelectionSet(node, this._getTypeInfo(), fragments, transformationContext),
            },
        }));
    }
    transformSelectionSet(node, typeInfo, fragments, transformationContext) {
        const parentType = typeInfo.getParentType();
        if (parentType == null) {
            return undefined;
        }
        const parentTypeName = parentType.name;
        let newSelections = [];
        let isTypenameSelected = false;
        for (const selection of node.selections) {
            if (selection.kind !== graphql_1.Kind.FIELD) {
                newSelections.push(selection);
                continue;
            }
            // The `__typename` selection should not be aliased
            // to be accessible with this name
            if (selection.name.value === '__typename' &&
                (!selection.alias || selection.alias.value === '__typename')) {
                isTypenameSelected = true;
            }
            const newName = selection.name.value;
            let transformedSelection;
            if (this.fieldNodeTransformer == null) {
                transformedSelection = selection;
            }
            else {
                transformedSelection = this.fieldNodeTransformer(parentTypeName, newName, selection, fragments, transformationContext);
                transformedSelection =
                    transformedSelection === undefined ? selection : transformedSelection;
            }
            if (transformedSelection == null) {
                continue;
            }
            else if (Array.isArray(transformedSelection)) {
                newSelections = newSelections.concat(transformedSelection);
                continue;
            }
            else if (transformedSelection.kind !== graphql_1.Kind.FIELD) {
                newSelections.push(transformedSelection);
                continue;
            }
            const typeMapping = this.mapping[parentTypeName];
            if (typeMapping == null) {
                newSelections.push(transformedSelection);
                continue;
            }
            const oldName = this.mapping[parentTypeName][newName];
            if (oldName == null) {
                newSelections.push(transformedSelection);
                continue;
            }
            newSelections.push({
                ...transformedSelection,
                name: {
                    kind: graphql_1.Kind.NAME,
                    value: oldName,
                },
                alias: {
                    kind: graphql_1.Kind.NAME,
                    value: transformedSelection.alias?.value ?? newName,
                },
            });
        }
        // See https://github.com/ardatan/graphql-tools/issues/2282
        if (!isTypenameSelected &&
            (this.dataTransformer != null || this.errorsTransformer != null) &&
            (this.subscriptionTypeName == null || parentTypeName !== this.subscriptionTypeName)) {
            newSelections.push({
                kind: graphql_1.Kind.FIELD,
                name: {
                    kind: graphql_1.Kind.NAME,
                    value: '__typename',
                },
            });
        }
        return {
            ...node,
            selections: newSelections,
        };
    }
}
exports.default = TransformCompositeFields;
