"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BaseResolversVisitor = void 0;
const tslib_1 = require("tslib");
const plugin_helpers_1 = require("@graphql-codegen/plugin-helpers");
const utils_1 = require("@graphql-tools/utils");
const auto_bind_1 = tslib_1.__importDefault(require("auto-bind"));
const graphql_1 = require("graphql");
const base_visitor_js_1 = require("./base-visitor.js");
const enum_values_js_1 = require("./enum-values.js");
const mappers_js_1 = require("./mappers.js");
const scalars_js_1 = require("./scalars.js");
const utils_js_1 = require("./utils.js");
const variables_to_object_js_1 = require("./variables-to-object.js");
class BaseResolversVisitor extends base_visitor_js_1.BaseVisitor {
    constructor(rawConfig, additionalConfig, _schema, defaultScalars = scalars_js_1.DEFAULT_SCALARS) {
        super(rawConfig, {
            immutableTypes: (0, utils_js_1.getConfigValue)(rawConfig.immutableTypes, false),
            optionalResolveType: (0, utils_js_1.getConfigValue)(rawConfig.optionalResolveType, false),
            enumPrefix: (0, utils_js_1.getConfigValue)(rawConfig.enumPrefix, true),
            enumSuffix: (0, utils_js_1.getConfigValue)(rawConfig.enumSuffix, true),
            federation: (0, utils_js_1.getConfigValue)(rawConfig.federation, false),
            resolverTypeWrapperSignature: (0, utils_js_1.getConfigValue)(rawConfig.resolverTypeWrapperSignature, 'Promise<T> | T'),
            enumValues: (0, enum_values_js_1.parseEnumValues)({
                schema: _schema,
                mapOrStr: rawConfig.enumValues,
            }),
            addUnderscoreToArgsType: (0, utils_js_1.getConfigValue)(rawConfig.addUnderscoreToArgsType, false),
            onlyResolveTypeForInterfaces: (0, utils_js_1.getConfigValue)(rawConfig.onlyResolveTypeForInterfaces, false),
            contextType: (0, mappers_js_1.parseMapper)(rawConfig.contextType || 'any', 'ContextType'),
            fieldContextTypes: (0, utils_js_1.getConfigValue)(rawConfig.fieldContextTypes, []),
            directiveContextTypes: (0, utils_js_1.getConfigValue)(rawConfig.directiveContextTypes, []),
            resolverTypeSuffix: (0, utils_js_1.getConfigValue)(rawConfig.resolverTypeSuffix, 'Resolvers'),
            allResolversTypeName: (0, utils_js_1.getConfigValue)(rawConfig.allResolversTypeName, 'Resolvers'),
            rootValueType: (0, mappers_js_1.parseMapper)(rawConfig.rootValueType || '{}', 'RootValueType'),
            namespacedImportName: (0, utils_js_1.getConfigValue)(rawConfig.namespacedImportName, ''),
            avoidOptionals: (0, utils_js_1.getConfigValue)(rawConfig.avoidOptionals, false),
            defaultMapper: rawConfig.defaultMapper
                ? (0, mappers_js_1.parseMapper)(rawConfig.defaultMapper || 'any', 'DefaultMapperType')
                : null,
            mappers: (0, mappers_js_1.transformMappers)(rawConfig.mappers || {}, rawConfig.mapperTypeSuffix),
            scalars: (0, utils_js_1.buildScalarsFromConfig)(_schema, rawConfig, defaultScalars),
            internalResolversPrefix: (0, utils_js_1.getConfigValue)(rawConfig.internalResolversPrefix, '__'),
            resolversNonOptionalTypename: normalizeResolversNonOptionalTypename((0, utils_js_1.getConfigValue)(rawConfig.resolversNonOptionalTypename, false)),
            ...additionalConfig,
        });
        this._schema = _schema;
        this._declarationBlockConfig = {};
        this._collectedResolvers = {};
        this._collectedDirectiveResolvers = {};
        this._usedMappers = {};
        this._resolversTypes = {};
        this._resolversParentTypes = {};
        this._hasReferencedResolversUnionTypes = false;
        this._hasReferencedResolversInterfaceTypes = false;
        this._resolversUnionTypes = {};
        this._resolversUnionParentTypes = {};
        this._resolversInterfaceTypes = {};
        this._rootTypeNames = new Set();
        this._globalDeclarations = new Set();
        this._hasScalars = false;
        this._hasFederation = false;
        this._shouldMapType = {};
        (0, auto_bind_1.default)(this);
        this._federation = new plugin_helpers_1.ApolloFederation({ enabled: this.config.federation, schema: this.schema });
        this._rootTypeNames = (0, utils_1.getRootTypeNames)(_schema);
        this._variablesTransformer = new variables_to_object_js_1.OperationVariablesToObject(this.scalars, this.convertName, this.config.namespacedImportName);
        this._resolversTypes = this.createResolversFields({
            applyWrapper: type => this.applyResolverTypeWrapper(type),
            clearWrapper: type => this.clearResolverTypeWrapper(type),
            getTypeToUse: name => this.getTypeToUse(name),
            currentType: 'ResolversTypes',
        });
        this._resolversParentTypes = this.createResolversFields({
            applyWrapper: type => type,
            clearWrapper: type => type,
            getTypeToUse: name => this.getParentTypeToUse(name),
            currentType: 'ResolversParentTypes',
            shouldInclude: namedType => !(0, graphql_1.isEnumType)(namedType),
        });
        this._resolversUnionTypes = this.createResolversUnionTypes();
        this._resolversInterfaceTypes = this.createResolversInterfaceTypes();
        this._fieldContextTypeMap = this.createFieldContextTypeMap();
        this._directiveContextTypesMap = this.createDirectivedContextType();
        this._directiveResolverMappings = rawConfig.directiveResolverMappings ?? {};
    }
    getResolverTypeWrapperSignature() {
        return `export type ResolverTypeWrapper<T> = ${this.config.resolverTypeWrapperSignature};`;
    }
    shouldMapType(type, duringCheck = []) {
        if (type.name.startsWith('__') || this.config.scalars[type.name]) {
            return false;
        }
        if (this.config.mappers[type.name]) {
            return true;
        }
        if ((0, graphql_1.isObjectType)(type) || (0, graphql_1.isInterfaceType)(type)) {
            const fields = type.getFields();
            return Object.keys(fields)
                .filter(fieldName => {
                const field = fields[fieldName];
                const fieldType = (0, plugin_helpers_1.getBaseType)(field.type);
                return !duringCheck.includes(fieldType.name);
            })
                .some(fieldName => {
                const field = fields[fieldName];
                const fieldType = (0, plugin_helpers_1.getBaseType)(field.type);
                if (this._shouldMapType[fieldType.name] !== undefined) {
                    return this._shouldMapType[fieldType.name];
                }
                if (this.config.mappers[type.name]) {
                    return true;
                }
                duringCheck.push(type.name);
                const innerResult = this.shouldMapType(fieldType, duringCheck);
                return innerResult;
            });
        }
        return false;
    }
    convertName(node, options, applyNamespacedImport = false) {
        const sourceType = super.convertName(node, options);
        return `${applyNamespacedImport && this.config.namespacedImportName ? this.config.namespacedImportName + '.' : ''}${sourceType}`;
    }
    // Kamil: this one is heeeeavvyyyy
    createResolversFields({ applyWrapper, clearWrapper, getTypeToUse, currentType, shouldInclude, }) {
        const allSchemaTypes = this._schema.getTypeMap();
        const typeNames = this._federation.filterTypeNames(Object.keys(allSchemaTypes));
        // avoid checking all types recursively if we have no `mappers` defined
        if (Object.keys(this.config.mappers).length > 0) {
            for (const typeName of typeNames) {
                if (this._shouldMapType[typeName] === undefined) {
                    const schemaType = allSchemaTypes[typeName];
                    this._shouldMapType[typeName] = this.shouldMapType(schemaType);
                }
            }
        }
        return typeNames.reduce((prev, typeName) => {
            const schemaType = allSchemaTypes[typeName];
            if (typeName.startsWith('__') || (shouldInclude && !shouldInclude(schemaType))) {
                return prev;
            }
            const isRootType = this._rootTypeNames.has(typeName);
            const isMapped = this.config.mappers[typeName];
            const isScalar = this.config.scalars[typeName];
            const hasDefaultMapper = !!this.config.defaultMapper?.type;
            if (isRootType) {
                prev[typeName] = applyWrapper(this.config.rootValueType.type);
                return prev;
            }
            if (isMapped && this.config.mappers[typeName].type && !hasPlaceholder(this.config.mappers[typeName].type)) {
                this.markMapperAsUsed(typeName);
                prev[typeName] = applyWrapper(this.config.mappers[typeName].type);
            }
            else if ((0, graphql_1.isEnumType)(schemaType) && this.config.enumValues[typeName]) {
                prev[typeName] =
                    this.config.enumValues[typeName].sourceIdentifier ||
                        this.convertName(this.config.enumValues[typeName].typeIdentifier);
            }
            else if (hasDefaultMapper && !hasPlaceholder(this.config.defaultMapper.type)) {
                prev[typeName] = applyWrapper(this.config.defaultMapper.type);
            }
            else if (isScalar) {
                prev[typeName] = applyWrapper(this._getScalar(typeName));
            }
            else if ((0, graphql_1.isInterfaceType)(schemaType)) {
                this._hasReferencedResolversInterfaceTypes = true;
                const type = this.convertName('ResolversInterfaceTypes');
                const generic = this.convertName(currentType);
                prev[typeName] = applyWrapper(`${type}<${generic}>['${typeName}']`);
                return prev;
            }
            else if ((0, graphql_1.isUnionType)(schemaType)) {
                this._hasReferencedResolversUnionTypes = true;
                const type = this.convertName('ResolversUnionTypes');
                const generic = this.convertName(currentType);
                prev[typeName] = applyWrapper(`${type}<${generic}>['${typeName}']`);
            }
            else if ((0, graphql_1.isEnumType)(schemaType)) {
                prev[typeName] = this.convertName(typeName, {
                    useTypesPrefix: this.config.enumPrefix,
                    useTypesSuffix: this.config.enumSuffix,
                }, true);
            }
            else {
                prev[typeName] = this.convertName(typeName, {}, true);
                if (prev[typeName] !== 'any' && (0, graphql_1.isObjectType)(schemaType)) {
                    const relevantFields = this.getRelevantFieldsToOmit({
                        schemaType,
                        getTypeToUse,
                        shouldInclude,
                    });
                    // If relevantFields, puts ResolverTypeWrapper on top of an entire type
                    let internalType = relevantFields.length > 0 ? this.replaceFieldsInType(prev[typeName], relevantFields) : prev[typeName];
                    if (isMapped) {
                        // replace the placeholder with the actual type
                        if (hasPlaceholder(internalType)) {
                            internalType = replacePlaceholder(internalType, typeName);
                        }
                        if (this.config.mappers[typeName].type && hasPlaceholder(this.config.mappers[typeName].type)) {
                            internalType = replacePlaceholder(this.config.mappers[typeName].type, internalType);
                        }
                    }
                    prev[typeName] = applyWrapper(internalType);
                }
            }
            if (!isMapped && hasDefaultMapper && hasPlaceholder(this.config.defaultMapper.type)) {
                const originalTypeName = isScalar ? this._getScalar(typeName) : prev[typeName];
                if ((0, graphql_1.isUnionType)(schemaType)) {
                    // Don't clear ResolverTypeWrapper from Unions
                    prev[typeName] = replacePlaceholder(this.config.defaultMapper.type, originalTypeName);
                }
                else {
                    const name = clearWrapper(originalTypeName);
                    const replaced = replacePlaceholder(this.config.defaultMapper.type, name);
                    prev[typeName] = applyWrapper(replaced);
                }
            }
            return prev;
        }, {});
    }
    replaceFieldsInType(typeName, relevantFields) {
        this._globalDeclarations.add(utils_js_1.OMIT_TYPE);
        return `Omit<${typeName}, ${relevantFields.map(f => `'${f.fieldName}'`).join(' | ')}> & { ${relevantFields
            .map(f => `${f.fieldName}${f.addOptionalSign ? '?' : ''}: ${f.replaceWithType}`)
            .join(', ')} }`;
    }
    applyMaybe(str) {
        const namespacedImportPrefix = this.config.namespacedImportName ? this.config.namespacedImportName + '.' : '';
        return `${namespacedImportPrefix}Maybe<${str}>`;
    }
    applyResolverTypeWrapper(str) {
        return `ResolverTypeWrapper<${this.clearResolverTypeWrapper(str)}>`;
    }
    clearMaybe(str) {
        const namespacedImportPrefix = this.config.namespacedImportName ? this.config.namespacedImportName + '.' : '';
        if (str.startsWith(`${namespacedImportPrefix}Maybe<`)) {
            const maybeRe = new RegExp(`${namespacedImportPrefix.replace('.', '\\.')}Maybe<(.*?)>$`);
            return str.replace(maybeRe, '$1');
        }
        return str;
    }
    clearResolverTypeWrapper(str) {
        if (str.startsWith('ResolverTypeWrapper<')) {
            return str.replace(/ResolverTypeWrapper<(.*?)>$/, '$1');
        }
        return str;
    }
    wrapWithArray(t) {
        if (this.config.immutableTypes) {
            return `ReadonlyArray<${t}>`;
        }
        return `Array<${t}>`;
    }
    createResolversUnionTypes() {
        if (!this._hasReferencedResolversUnionTypes) {
            return {};
        }
        const allSchemaTypes = this._schema.getTypeMap();
        const typeNames = this._federation.filterTypeNames(Object.keys(allSchemaTypes));
        const unionTypes = typeNames.reduce((res, typeName) => {
            const schemaType = allSchemaTypes[typeName];
            if ((0, graphql_1.isUnionType)(schemaType)) {
                const { unionMember, excludeTypes } = this.config.resolversNonOptionalTypename;
                res[typeName] = this.getAbstractMembersType({
                    typeName,
                    memberTypes: schemaType.getTypes(),
                    isTypenameNonOptional: unionMember && !excludeTypes?.includes(typeName),
                });
            }
            return res;
        }, {});
        return unionTypes;
    }
    createResolversInterfaceTypes() {
        if (!this._hasReferencedResolversInterfaceTypes) {
            return {};
        }
        const allSchemaTypes = this._schema.getTypeMap();
        const typeNames = this._federation.filterTypeNames(Object.keys(allSchemaTypes));
        const interfaceTypes = typeNames.reduce((res, typeName) => {
            const schemaType = allSchemaTypes[typeName];
            if ((0, graphql_1.isInterfaceType)(schemaType)) {
                const allTypesMap = this._schema.getTypeMap();
                const implementingTypes = [];
                for (const graphqlType of Object.values(allTypesMap)) {
                    if (graphqlType instanceof graphql_1.GraphQLObjectType) {
                        const allInterfaces = graphqlType.getInterfaces();
                        if (allInterfaces.some(int => int.name === schemaType.name)) {
                            implementingTypes.push(graphqlType);
                        }
                    }
                }
                const { interfaceImplementingType, excludeTypes } = this.config.resolversNonOptionalTypename;
                res[typeName] = this.getAbstractMembersType({
                    typeName,
                    memberTypes: implementingTypes,
                    isTypenameNonOptional: interfaceImplementingType && !excludeTypes?.includes(typeName),
                });
            }
            return res;
        }, {});
        return interfaceTypes;
    }
    /**
     * Function to generate the types of Abstract Type Members i.e. Union Members or Interface Implementing Types
     */
    getAbstractMembersType({ typeName, memberTypes, isTypenameNonOptional, }) {
        const result = memberTypes
            .map(type => {
            const isTypeMapped = this.config.mappers[type.name];
            // 1. If mapped without placehoder, just use it without doing extra checks
            if (isTypeMapped && !hasPlaceholder(isTypeMapped.type)) {
                return { typename: type.name, typeValue: isTypeMapped.type };
            }
            // 2. Work out value for type
            // 2a. By default, use the typescript type
            let typeValue = this.convertName(type.name, {}, true);
            // 2b. Find fields to Omit if needed.
            //  - If no field to Omit, "type with maybe Omit" is typescript type i.e. no Omit
            //  - If there are fields to Omit, keep track of these "type with maybe Omit" to replace in original unionMemberValue
            const fieldsToOmit = this.getRelevantFieldsToOmit({
                schemaType: type,
                getTypeToUse: baseType => `RefType['${baseType}']`,
            });
            if (fieldsToOmit.length > 0) {
                typeValue = this.replaceFieldsInType(typeValue, fieldsToOmit);
            }
            // 2c. If type is mapped with placeholder, use the "type with maybe Omit" as {T}
            if (isTypeMapped && hasPlaceholder(isTypeMapped.type)) {
                return { typename: type.name, typeValue: replacePlaceholder(isTypeMapped.type, typeValue) };
            }
            // 2d. If has default mapper with placeholder, use the "type with maybe Omit" as {T}
            const hasDefaultMapper = !!this.config.defaultMapper?.type;
            const isScalar = this.config.scalars[typeName];
            if (hasDefaultMapper && hasPlaceholder(this.config.defaultMapper.type)) {
                const finalTypename = isScalar ? this._getScalar(typeName) : typeValue;
                return {
                    typename: type.name,
                    typeValue: replacePlaceholder(this.config.defaultMapper.type, finalTypename),
                };
            }
            return { typename: type.name, typeValue };
        })
            .map(({ typename, typeValue }) => {
            const nonOptionalTypenameModifier = isTypenameNonOptional ? ` & { __typename: '${typename}' }` : '';
            return `( ${typeValue}${nonOptionalTypenameModifier} )`; // Must wrap every type in explicit "( )" to separate them
        })
            .join(' | ') || 'never';
        return result;
    }
    createFieldContextTypeMap() {
        return this.config.fieldContextTypes.reduce((prev, fieldContextType) => {
            const isScoped = fieldContextType.includes('\\#');
            if (fieldContextType.includes('\\#')) {
                fieldContextType = fieldContextType.replace('\\#', '');
            }
            const items = fieldContextType.split('#');
            if (items.length === 3) {
                const [path, source, contextTypeName] = items;
                const sourceStr = isScoped ? `\\#${source}` : source;
                return { ...prev, [path]: (0, mappers_js_1.parseMapper)(`${sourceStr}#${contextTypeName}`) };
            }
            const [path, contextType] = items;
            return { ...prev, [path]: (0, mappers_js_1.parseMapper)(contextType) };
        }, {});
    }
    createDirectivedContextType() {
        return this.config.directiveContextTypes.reduce((prev, fieldContextType) => {
            const isScoped = fieldContextType.includes('\\#');
            if (fieldContextType.includes('\\#')) {
                fieldContextType = fieldContextType.replace('\\#', '');
            }
            const items = fieldContextType.split('#');
            if (items.length === 3) {
                const [path, source, contextTypeName] = items;
                const sourceStr = isScoped ? `\\#${source}` : source;
                return { ...prev, [path]: (0, mappers_js_1.parseMapper)(`${sourceStr}#${contextTypeName}`) };
            }
            const [path, contextType] = items;
            return { ...prev, [path]: (0, mappers_js_1.parseMapper)(contextType) };
        }, {});
    }
    buildResolversTypes() {
        const declarationKind = 'type';
        return new utils_js_1.DeclarationBlock(this._declarationBlockConfig)
            .export()
            .asKind(declarationKind)
            .withName(this.convertName('ResolversTypes'))
            .withComment('Mapping between all available schema types and the resolvers types')
            .withBlock(Object.keys(this._resolversTypes)
            .map(typeName => (0, utils_js_1.indent)(`${typeName}: ${this._resolversTypes[typeName]}${this.getPunctuation(declarationKind)}`))
            .join('\n')).string;
    }
    buildResolversParentTypes() {
        const declarationKind = 'type';
        return new utils_js_1.DeclarationBlock(this._declarationBlockConfig)
            .export()
            .asKind(declarationKind)
            .withName(this.convertName('ResolversParentTypes'))
            .withComment('Mapping between all available schema types and the resolvers parents')
            .withBlock(Object.keys(this._resolversParentTypes)
            .map(typeName => (0, utils_js_1.indent)(`${typeName}: ${this._resolversParentTypes[typeName]}${this.getPunctuation(declarationKind)}`))
            .join('\n')).string;
    }
    buildResolversUnionTypes() {
        if (Object.keys(this._resolversUnionTypes).length === 0) {
            return '';
        }
        const declarationKind = 'type';
        return new utils_js_1.DeclarationBlock(this._declarationBlockConfig)
            .export()
            .asKind(declarationKind)
            .withName(this.convertName('ResolversUnionTypes'), `<RefType extends Record<string, unknown>>`)
            .withComment('Mapping of union types')
            .withBlock(Object.entries(this._resolversUnionTypes)
            .map(([typeName, value]) => (0, utils_js_1.indent)(`${typeName}: ${value}${this.getPunctuation(declarationKind)}`))
            .join('\n')).string;
    }
    buildResolversInterfaceTypes() {
        if (Object.keys(this._resolversInterfaceTypes).length === 0) {
            return '';
        }
        const declarationKind = 'type';
        return new utils_js_1.DeclarationBlock(this._declarationBlockConfig)
            .export()
            .asKind(declarationKind)
            .withName(this.convertName('ResolversInterfaceTypes'), `<RefType extends Record<string, unknown>>`)
            .withComment('Mapping of interface types')
            .withBlock(Object.entries(this._resolversInterfaceTypes)
            .map(([typeName, value]) => (0, utils_js_1.indent)(`${typeName}: ${value}${this.getPunctuation(declarationKind)}`))
            .join('\n')).string;
    }
    get schema() {
        return this._schema;
    }
    get defaultMapperType() {
        return this.config.defaultMapper.type;
    }
    get unusedMappers() {
        return Object.keys(this.config.mappers).filter(name => !this._usedMappers[name]);
    }
    get globalDeclarations() {
        return Array.from(this._globalDeclarations);
    }
    isMapperImported(groupedMappers, identifier, source) {
        const exists = groupedMappers[source] ? !!groupedMappers[source].find(m => m.identifier === identifier) : false;
        const existsFromEnums = !!Object.keys(this.config.enumValues)
            .map(key => this.config.enumValues[key])
            .find(o => o.sourceFile === source && o.typeIdentifier === identifier);
        return exists || existsFromEnums;
    }
    get mappersImports() {
        const groupedMappers = {};
        const addMapper = (source, identifier, asDefault) => {
            if (!this.isMapperImported(groupedMappers, identifier, source)) {
                groupedMappers[source] ||= [];
                groupedMappers[source].push({ identifier, asDefault });
            }
        };
        for (const { mapper } of Object.keys(this.config.mappers)
            .map(gqlTypeName => ({ gqlType: gqlTypeName, mapper: this.config.mappers[gqlTypeName] }))
            .filter(({ mapper }) => mapper.isExternal)) {
            const externalMapper = mapper;
            const identifier = (0, utils_js_1.stripMapperTypeInterpolation)(externalMapper.import);
            addMapper(externalMapper.source, identifier, externalMapper.default);
        }
        if (this.config.contextType.isExternal) {
            addMapper(this.config.contextType.source, this.config.contextType.import, this.config.contextType.default);
        }
        if (this.config.rootValueType.isExternal) {
            addMapper(this.config.rootValueType.source, this.config.rootValueType.import, this.config.rootValueType.default);
        }
        if (this.config.defaultMapper?.isExternal) {
            const identifier = (0, utils_js_1.stripMapperTypeInterpolation)(this.config.defaultMapper.import);
            addMapper(this.config.defaultMapper.source, identifier, this.config.defaultMapper.default);
        }
        for (const parsedMapper of Object.values(this._fieldContextTypeMap)) {
            if (parsedMapper.isExternal) {
                addMapper(parsedMapper.source, parsedMapper.import, parsedMapper.default);
            }
        }
        for (const parsedMapper of Object.values(this._directiveContextTypesMap)) {
            if (parsedMapper.isExternal) {
                addMapper(parsedMapper.source, parsedMapper.import, parsedMapper.default);
            }
        }
        return Object.keys(groupedMappers)
            .map(source => (0, mappers_js_1.buildMapperImport)(source, groupedMappers[source], this.config.useTypeImports))
            .filter(Boolean);
    }
    setDeclarationBlockConfig(config) {
        this._declarationBlockConfig = config;
    }
    setVariablesTransformer(variablesTransfomer) {
        this._variablesTransformer = variablesTransfomer;
    }
    hasScalars() {
        return this._hasScalars;
    }
    hasFederation() {
        return this._hasFederation;
    }
    getRootResolver() {
        const name = this.convertName(this.config.allResolversTypeName);
        const declarationKind = 'type';
        const contextType = `<ContextType = ${this.config.contextType.type}>`;
        return [
            new utils_js_1.DeclarationBlock(this._declarationBlockConfig)
                .export()
                .asKind(declarationKind)
                .withName(name, contextType)
                .withBlock(Object.keys(this._collectedResolvers)
                .map(schemaTypeName => {
                const resolverType = this._collectedResolvers[schemaTypeName];
                return (0, utils_js_1.indent)(this.formatRootResolver(schemaTypeName, resolverType, declarationKind));
            })
                .join('\n')).string,
        ].join('\n');
    }
    formatRootResolver(schemaTypeName, resolverType, declarationKind) {
        return `${schemaTypeName}${this.config.avoidOptionals ? '' : '?'}: ${resolverType}${this.getPunctuation(declarationKind)}`;
    }
    getAllDirectiveResolvers() {
        if (Object.keys(this._collectedDirectiveResolvers).length) {
            const declarationKind = 'type';
            const name = this.convertName('DirectiveResolvers');
            const contextType = `<ContextType = ${this.config.contextType.type}>`;
            return [
                new utils_js_1.DeclarationBlock(this._declarationBlockConfig)
                    .export()
                    .asKind(declarationKind)
                    .withName(name, contextType)
                    .withBlock(Object.keys(this._collectedDirectiveResolvers)
                    .map(schemaTypeName => {
                    const resolverType = this._collectedDirectiveResolvers[schemaTypeName];
                    return (0, utils_js_1.indent)(this.formatRootResolver(schemaTypeName, resolverType, declarationKind));
                })
                    .join('\n')).string,
            ].join('\n');
        }
        return '';
    }
    Name(node) {
        return node.value;
    }
    ListType(node) {
        const asString = node.type;
        return this.wrapWithArray(asString);
    }
    _getScalar(name) {
        return `${this.config.namespacedImportName ? this.config.namespacedImportName + '.' : ''}Scalars['${name}']['output']`;
    }
    NamedType(node) {
        const nameStr = node.name;
        if (this.config.scalars[nameStr]) {
            return this._getScalar(nameStr);
        }
        return this.convertName(node, null, true);
    }
    NonNullType(node) {
        const asString = node.type;
        return asString;
    }
    markMapperAsUsed(name) {
        this._usedMappers[name] = true;
    }
    getTypeToUse(name) {
        const resolversType = this.convertName('ResolversTypes');
        return `${resolversType}['${name}']`;
    }
    getParentTypeToUse(name) {
        const resolversType = this.convertName('ResolversParentTypes');
        return `${resolversType}['${name}']`;
    }
    getParentTypeForSignature(_node) {
        return 'ParentType';
    }
    transformParentGenericType(parentType) {
        return `ParentType extends ${parentType} = ${parentType}`;
    }
    FieldDefinition(node, key, parent) {
        const hasArguments = node.arguments && node.arguments.length > 0;
        const declarationKind = 'type';
        return (parentName) => {
            const original = parent[key];
            const baseType = (0, utils_js_1.getBaseTypeNode)(original.type);
            const realType = baseType.name.value;
            const parentType = this.schema.getType(parentName);
            if (this._federation.skipField({ fieldNode: original, parentType })) {
                return null;
            }
            const contextType = this.getContextType(parentName, node);
            const typeToUse = this.getTypeToUse(realType);
            const mappedType = this._variablesTransformer.wrapAstTypeWithModifiers(typeToUse, original.type);
            const subscriptionType = this._schema.getSubscriptionType();
            const isSubscriptionType = subscriptionType && subscriptionType.name === parentName;
            let argsType = hasArguments
                ? this.convertName(parentName +
                    (this.config.addUnderscoreToArgsType ? '_' : '') +
                    this.convertName(node.name, {
                        useTypesPrefix: false,
                        useTypesSuffix: false,
                    }) +
                    'Args', {
                    useTypesPrefix: true,
                }, true)
                : null;
            const avoidInputsOptionals = typeof this.config.avoidOptionals === 'object'
                ? this.config.avoidOptionals?.inputValue
                : this.config.avoidOptionals === true;
            if (argsType !== null) {
                const argsToForceRequire = original.arguments.filter(arg => !!arg.defaultValue || arg.type.kind === 'NonNullType');
                if (argsToForceRequire.length > 0) {
                    argsType = this.applyRequireFields(argsType, argsToForceRequire);
                }
                else if (original.arguments.length > 0 && avoidInputsOptionals !== true) {
                    argsType = this.applyOptionalFields(argsType, original.arguments);
                }
            }
            const parentTypeSignature = this._federation.transformParentType({
                fieldNode: original,
                parentType,
                parentTypeSignature: this.getParentTypeForSignature(node),
            });
            const mappedTypeKey = isSubscriptionType ? `${mappedType}, "${node.name}"` : mappedType;
            const directiveMappings = node.directives
                ?.map(directive => this._directiveResolverMappings[directive.name])
                .filter(Boolean)
                .reverse() ?? [];
            const resolverType = isSubscriptionType ? 'SubscriptionResolver' : directiveMappings[0] ?? 'Resolver';
            const avoidResolverOptionals = typeof this.config.avoidOptionals === 'object'
                ? this.config.avoidOptionals?.resolvers
                : this.config.avoidOptionals === true;
            const signature = {
                name: node.name,
                modifier: avoidResolverOptionals ? '' : '?',
                type: resolverType,
                genericTypes: [mappedTypeKey, parentTypeSignature, contextType, argsType].filter(f => f),
            };
            if (this._federation.isResolveReferenceField(node)) {
                this._hasFederation = true;
                signature.type = 'ReferenceResolver';
                if (signature.genericTypes.length >= 3) {
                    signature.genericTypes = signature.genericTypes.slice(0, 3);
                }
            }
            return (0, utils_js_1.indent)(`${signature.name}${signature.modifier}: ${signature.type}<${signature.genericTypes.join(', ')}>${this.getPunctuation(declarationKind)}`);
        };
    }
    getFieldContextType(parentName, node) {
        if (this._fieldContextTypeMap[`${parentName}.${node.name}`]) {
            return this._fieldContextTypeMap[`${parentName}.${node.name}`].type;
        }
        return 'ContextType';
    }
    getContextType(parentName, node) {
        let contextType = this.getFieldContextType(parentName, node);
        for (const directive of node.directives) {
            const name = directive.name;
            const directiveMap = this._directiveContextTypesMap[name];
            if (directiveMap) {
                contextType = `${directiveMap.type}<${contextType}>`;
            }
        }
        return contextType;
    }
    applyRequireFields(argsType, fields) {
        this._globalDeclarations.add(utils_js_1.REQUIRE_FIELDS_TYPE);
        return `RequireFields<${argsType}, ${fields.map(f => `'${f.name.value}'`).join(' | ')}>`;
    }
    applyOptionalFields(argsType, _fields) {
        return `Partial<${argsType}>`;
    }
    ObjectTypeDefinition(node) {
        const declarationKind = 'type';
        const name = this.convertName(node, {
            suffix: this.config.resolverTypeSuffix,
        });
        const typeName = node.name;
        const parentType = this.getParentTypeToUse(typeName);
        const isRootType = [
            this.schema.getQueryType()?.name,
            this.schema.getMutationType()?.name,
            this.schema.getSubscriptionType()?.name,
        ].includes(typeName);
        const fieldsContent = node.fields.map((f) => f(node.name));
        if (!isRootType) {
            fieldsContent.push((0, utils_js_1.indent)(`${this.config.internalResolversPrefix}isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>${this.getPunctuation(declarationKind)}`));
        }
        const block = new utils_js_1.DeclarationBlock(this._declarationBlockConfig)
            .export()
            .asKind(declarationKind)
            .withName(name, `<ContextType = ${this.config.contextType.type}, ${this.transformParentGenericType(parentType)}>`)
            .withBlock(fieldsContent.join('\n'));
        this._collectedResolvers[node.name] = name + '<ContextType>';
        return block.string;
    }
    UnionTypeDefinition(node, key, parent) {
        const declarationKind = 'type';
        const name = this.convertName(node, {
            suffix: this.config.resolverTypeSuffix,
        });
        const originalNode = parent[key];
        const possibleTypes = originalNode.types
            .map(node => node.name.value)
            .map(f => `'${f}'`)
            .join(' | ');
        this._collectedResolvers[node.name] = name + '<ContextType>';
        const parentType = this.getParentTypeToUse(node.name);
        return new utils_js_1.DeclarationBlock(this._declarationBlockConfig)
            .export()
            .asKind(declarationKind)
            .withName(name, `<ContextType = ${this.config.contextType.type}, ${this.transformParentGenericType(parentType)}>`)
            .withBlock((0, utils_js_1.indent)(`${this.config.internalResolversPrefix}resolveType${this.config.optionalResolveType ? '?' : ''}: TypeResolveFn<${possibleTypes}, ParentType, ContextType>${this.getPunctuation(declarationKind)}`)).string;
    }
    ScalarTypeDefinition(node) {
        const nameAsString = node.name;
        const baseName = this.getTypeToUse(nameAsString);
        if (this._federation.skipScalar(nameAsString)) {
            return null;
        }
        this._hasScalars = true;
        this._collectedResolvers[node.name] = 'GraphQLScalarType';
        return new utils_js_1.DeclarationBlock({
            ...this._declarationBlockConfig,
            blockTransformer(block) {
                return block;
            },
        })
            .export()
            .asKind('interface')
            .withName(this.convertName(node, {
            suffix: 'ScalarConfig',
        }), ` extends GraphQLScalarTypeConfig<${baseName}, any>`)
            .withBlock((0, utils_js_1.indent)(`name: '${node.name}'${this.getPunctuation('interface')}`)).string;
    }
    DirectiveDefinition(node, key, parent) {
        if (this._federation.skipDirective(node.name)) {
            return null;
        }
        const directiveName = this.convertName(node, {
            suffix: 'DirectiveResolver',
        });
        const sourceNode = parent[key];
        const hasArguments = sourceNode.arguments && sourceNode.arguments.length > 0;
        this._collectedDirectiveResolvers[node.name] = directiveName + '<any, any, ContextType>';
        const directiveArgsTypeName = this.convertName(node, {
            suffix: 'DirectiveArgs',
        });
        return [
            new utils_js_1.DeclarationBlock({
                ...this._declarationBlockConfig,
                blockTransformer(block) {
                    return block;
                },
            })
                .export()
                .asKind('type')
                .withName(directiveArgsTypeName)
                .withContent(hasArguments
                ? `{\n${this._variablesTransformer.transform(sourceNode.arguments)}\n}`
                : '{ }').string,
            new utils_js_1.DeclarationBlock({
                ...this._declarationBlockConfig,
                blockTransformer(block) {
                    return block;
                },
            })
                .export()
                .asKind('type')
                .withName(directiveName, `<Result, Parent, ContextType = ${this.config.contextType.type}, Args = ${directiveArgsTypeName}>`)
                .withContent(`DirectiveResolverFn<Result, Parent, ContextType, Args>`).string,
        ].join('\n');
    }
    buildEnumResolverContentBlock(_node, _mappedEnumType) {
        throw new Error(`buildEnumResolverContentBlock is not implemented!`);
    }
    buildEnumResolversExplicitMappedValues(_node, _valuesMapping) {
        throw new Error(`buildEnumResolversExplicitMappedValues is not implemented!`);
    }
    EnumTypeDefinition(node) {
        const rawTypeName = node.name;
        // If we have enumValues set, and it's point to an external enum - we need to allow internal values resolvers
        // In case we have enumValues set but as explicit values, no need to to do mapping since it's already
        // have type validation (the original enum has been modified by base types plugin).
        // If we have mapper for that type - we can skip
        if (!this.config.mappers[rawTypeName] && !this.config.enumValues[rawTypeName]) {
            return null;
        }
        const name = this.convertName(node, { suffix: this.config.resolverTypeSuffix });
        this._collectedResolvers[rawTypeName] = name;
        const hasExplicitValues = this.config.enumValues[rawTypeName]?.mappedValues;
        return new utils_js_1.DeclarationBlock(this._declarationBlockConfig)
            .export()
            .asKind('type')
            .withName(name)
            .withContent(hasExplicitValues
            ? this.buildEnumResolversExplicitMappedValues(node, this.config.enumValues[rawTypeName].mappedValues)
            : this.buildEnumResolverContentBlock(node, this.getTypeToUse(rawTypeName))).string;
    }
    InterfaceTypeDefinition(node) {
        const name = this.convertName(node, {
            suffix: this.config.resolverTypeSuffix,
        });
        const declarationKind = 'type';
        const allTypesMap = this._schema.getTypeMap();
        const implementingTypes = [];
        this._collectedResolvers[node.name] = name + '<ContextType>';
        for (const graphqlType of Object.values(allTypesMap)) {
            if (graphqlType instanceof graphql_1.GraphQLObjectType) {
                const allInterfaces = graphqlType.getInterfaces();
                if (allInterfaces.find(int => int.name === node.name)) {
                    implementingTypes.push(graphqlType.name);
                }
            }
        }
        const parentType = this.getParentTypeToUse(node.name);
        const possibleTypes = implementingTypes.map(name => `'${name}'`).join(' | ') || 'null';
        const fields = this.config.onlyResolveTypeForInterfaces ? [] : node.fields || [];
        return new utils_js_1.DeclarationBlock(this._declarationBlockConfig)
            .export()
            .asKind(declarationKind)
            .withName(name, `<ContextType = ${this.config.contextType.type}, ${this.transformParentGenericType(parentType)}>`)
            .withBlock([
            (0, utils_js_1.indent)(`${this.config.internalResolversPrefix}resolveType${this.config.optionalResolveType ? '?' : ''}: TypeResolveFn<${possibleTypes}, ParentType, ContextType>${this.getPunctuation(declarationKind)}`),
            ...fields.map((f) => f(node.name)),
        ].join('\n')).string;
    }
    SchemaDefinition() {
        return null;
    }
    getRelevantFieldsToOmit({ schemaType, shouldInclude, getTypeToUse, }) {
        const fields = schemaType.getFields();
        return this._federation
            .filterFieldNames(Object.keys(fields))
            .filter(fieldName => {
            const field = fields[fieldName];
            const baseType = (0, plugin_helpers_1.getBaseType)(field.type);
            // Filter out fields of types that are not included
            if (shouldInclude && !shouldInclude(baseType)) {
                return false;
            }
            return true;
        })
            .map(fieldName => {
            const field = fields[fieldName];
            const baseType = (0, plugin_helpers_1.getBaseType)(field.type);
            const isUnion = (0, graphql_1.isUnionType)(baseType);
            if (!this.config.mappers[baseType.name] && !isUnion && !this._shouldMapType[baseType.name]) {
                return null;
            }
            const addOptionalSign = !this.config.avoidOptionals && !(0, graphql_1.isNonNullType)(field.type);
            return {
                addOptionalSign,
                fieldName,
                replaceWithType: (0, utils_js_1.wrapTypeWithModifiers)(getTypeToUse(baseType.name), field.type, {
                    wrapOptional: this.applyMaybe,
                    wrapArray: this.wrapWithArray,
                }),
            };
        })
            .filter(a => a);
    }
}
exports.BaseResolversVisitor = BaseResolversVisitor;
function replacePlaceholder(pattern, typename) {
    return pattern.replace(/\{T\}/g, typename);
}
function hasPlaceholder(pattern) {
    return pattern.includes('{T}');
}
function normalizeResolversNonOptionalTypename(input) {
    const defaultConfig = {
        unionMember: false,
    };
    if (typeof input === 'boolean') {
        return {
            unionMember: input,
            interfaceImplementingType: input,
        };
    }
    return {
        ...defaultConfig,
        ...input,
    };
}
