"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GraphQLTransform = void 0;
const aws_appsync_1 = require("aws-cdk-lib/aws-appsync");
const aws_cdk_lib_1 = require("aws-cdk-lib");
const graphql_1 = require("graphql");
const lodash_1 = __importDefault(require("lodash"));
const errors_1 = require("../errors");
const graphql_api_1 = require("../graphql-api");
const transformer_context_1 = require("../transformer-context");
const authType_1 = require("../utils/authType");
const cdk_compat_1 = require("../cdk-compat");
const pre_process_context_1 = require("../transformer-context/pre-process-context");
const transform_parameters_1 = require("../transformer-context/transform-parameters");
const SyncUtils = __importStar(require("./sync-utils"));
const utils_1 = require("./utils");
const validation_1 = require("./validation");
const isFunction = (obj) => obj && typeof obj === 'function';
class GraphQLTransform {
    constructor(options) {
        var _a;
        this.options = options;
        this.seenTransformations = {};
        if (!options.transformers || options.transformers.length === 0) {
            throw new Error('Must provide at least one transformer.');
        }
        const sortedTransformers = (0, utils_1.sortTransformerPlugins)(options.transformers);
        this.transformers = sortedTransformers;
        this.authConfig = options.authConfig || {
            defaultAuthentication: {
                authenticationType: 'API_KEY',
                apiKeyConfig: {
                    apiKeyExpirationDays: 7,
                    description: 'Default API Key',
                },
            },
            additionalAuthenticationProviders: [],
        };
        (0, validation_1.validateAuthModes)(this.authConfig);
        this.stackMappingOverrides = options.stackMapping || {};
        this.userDefinedSlots = options.userDefinedSlots || {};
        this.resolverConfig = options.resolverConfig || {};
        this.transformParameters = {
            ...transform_parameters_1.defaultTransformParameters,
            ...((_a = options.transformParameters) !== null && _a !== void 0 ? _a : {}),
        };
        this.logs = [];
    }
    preProcessSchema(schema) {
        const context = new pre_process_context_1.TransformerPreProcessContext(schema, this.transformParameters);
        this.transformers
            .filter((transformer) => isFunction(transformer.preMutateSchema))
            .forEach((transformer) => transformer.preMutateSchema && transformer.preMutateSchema(context));
        return this.transformers
            .filter((transformer) => isFunction(transformer.mutateSchema))
            .reduce((mutateContext, transformer) => {
            const updatedInputDocument = transformer.mutateSchema ? transformer.mutateSchema(mutateContext) : mutateContext.inputDocument;
            return {
                ...mutateContext,
                inputDocument: updatedInputDocument,
            };
        }, context).inputDocument;
    }
    transform({ assetProvider, dataSourceStrategies, nestedStackProvider, parameterProvider, rdsLayerMapping, rdsSnsTopicMapping, schema, scope, sqlDirectiveDataSourceStrategies, synthParameters, }) {
        this.seenTransformations = {};
        const parsedDocument = (0, graphql_1.parse)(schema);
        const context = new transformer_context_1.TransformerContext({
            assetProvider,
            authConfig: this.authConfig,
            dataSourceStrategies: dataSourceStrategies,
            inputDocument: parsedDocument,
            nestedStackProvider,
            parameterProvider,
            rdsLayerMapping,
            rdsSnsTopicMapping,
            resolverConfig: this.resolverConfig,
            scope,
            sqlDirectiveDataSourceStrategies: sqlDirectiveDataSourceStrategies !== null && sqlDirectiveDataSourceStrategies !== void 0 ? sqlDirectiveDataSourceStrategies : [],
            stackMapping: this.stackMappingOverrides,
            synthParameters,
            transformParameters: this.transformParameters,
        });
        const validDirectiveNameMap = this.transformers.reduce((acc, t) => ({ ...acc, [t.directive.name.value]: true }), {
            aws_subscribe: true,
            aws_auth: true,
            aws_api_key: true,
            aws_iam: true,
            aws_oidc: true,
            aws_lambda: true,
            aws_cognito_user_pools: true,
            deprecated: true,
        });
        let allModelDefinitions = [...context.inputDocument.definitions];
        for (const transformer of this.transformers) {
            allModelDefinitions = allModelDefinitions.concat(...transformer.typeDefinitions, transformer.directive);
        }
        const errors = (0, validation_1.validateModelSchema)({
            kind: graphql_1.Kind.DOCUMENT,
            definitions: allModelDefinitions,
        });
        if (errors && errors.length) {
            throw new errors_1.SchemaValidationError(errors);
        }
        for (const transformer of this.transformers) {
            if (isFunction(transformer.before)) {
                transformer.before(context);
            }
        }
        for (const transformer of this.transformers) {
            for (const def of context.inputDocument.definitions) {
                switch (def.kind) {
                    case 'ObjectTypeDefinition':
                        this.transformObject(transformer, def, validDirectiveNameMap, context);
                        break;
                    case 'InterfaceTypeDefinition':
                        this.transformInterface(transformer, def, validDirectiveNameMap, context);
                        break;
                    case 'ScalarTypeDefinition':
                        this.transformScalar(transformer, def, validDirectiveNameMap, context);
                        break;
                    case 'UnionTypeDefinition':
                        this.transformUnion(transformer, def, validDirectiveNameMap, context);
                        break;
                    case 'EnumTypeDefinition':
                        this.transformEnum(transformer, def, validDirectiveNameMap, context);
                        break;
                    case 'InputObjectTypeDefinition':
                        this.transformInputObject(transformer, def, validDirectiveNameMap, context);
                        break;
                    default:
                        continue;
                }
            }
        }
        for (const transformer of this.transformers) {
            if (isFunction(transformer.validate)) {
                transformer.validate(context);
            }
        }
        for (const transformer of this.transformers) {
            if (isFunction(transformer.prepare)) {
                transformer.prepare(context);
            }
        }
        for (const transformer of this.transformers) {
            if (isFunction(transformer.transformSchema)) {
                transformer.transformSchema(context);
            }
        }
        const output = context.output;
        const api = this.generateGraphQlApi(context.stackManager, context.synthParameters, output, context.transformParameters);
        context.bind(api);
        if (!lodash_1.default.isEmpty(this.resolverConfig)) {
            SyncUtils.createSyncTable(context);
        }
        for (const transformer of this.transformers) {
            if (isFunction(transformer.generateResolvers)) {
                transformer.generateResolvers(context);
            }
        }
        let reverseThroughTransformers = this.transformers.length - 1;
        while (reverseThroughTransformers >= 0) {
            const transformer = this.transformers[reverseThroughTransformers];
            if (isFunction(transformer.after)) {
                transformer.after(context);
            }
            reverseThroughTransformers -= 1;
        }
        for (const transformer of this.transformers) {
            if (isFunction(transformer.getLogs)) {
                const logs = transformer.getLogs();
                this.logs.push(...logs);
            }
        }
        this.collectResolvers(context, context.api);
        this.ensureNoneDataSource(context.api);
    }
    generateGraphQlApi(stackManager, synthParameters, output, transformParameters) {
        var _a, _b;
        const { scope } = stackManager;
        const authorizationConfig = (0, authType_1.adoptAuthModes)(stackManager, synthParameters, this.authConfig);
        const apiName = synthParameters.apiName;
        const env = synthParameters.amplifyEnvironmentName;
        const name = env === 'NONE' ? apiName : `${apiName}-${env}`;
        const api = new graphql_api_1.GraphQLApi(scope, 'GraphQLAPI', {
            name,
            authorizationConfig,
            host: this.options.host,
            sandboxModeEnabled: this.transformParameters.sandboxModeEnabled,
            environmentName: env,
            disableResolverDeduping: this.transformParameters.disableResolverDeduping,
        });
        const authModes = [authorizationConfig.defaultAuthorization, ...(authorizationConfig.additionalAuthorizationModes || [])].map((mode) => mode === null || mode === void 0 ? void 0 : mode.authorizationType);
        if (authModes.includes(aws_appsync_1.AuthorizationType.API_KEY) && !this.transformParameters.suppressApiKeyGeneration) {
            const apiKeyConfig = [
                authorizationConfig.defaultAuthorization,
                ...(authorizationConfig.additionalAuthorizationModes || []),
            ].find((auth) => (auth === null || auth === void 0 ? void 0 : auth.authorizationType) === aws_appsync_1.AuthorizationType.API_KEY);
            const apiKeyDescription = (_a = apiKeyConfig.apiKeyConfig) === null || _a === void 0 ? void 0 : _a.description;
            const apiKeyExpirationDays = (_b = apiKeyConfig.apiKeyConfig) === null || _b === void 0 ? void 0 : _b.expires;
            const apiKey = api.createAPIKey({
                description: apiKeyDescription,
                expires: apiKeyExpirationDays,
            });
            if (transformParameters.enableTransformerCfnOutputs) {
                new aws_cdk_lib_1.CfnOutput(aws_cdk_lib_1.Stack.of(scope), 'GraphQLAPIKeyOutput', {
                    value: apiKey.attrApiKey,
                    description: 'Your GraphQL API ID.',
                    exportName: aws_cdk_lib_1.Fn.join(':', [aws_cdk_lib_1.Aws.STACK_NAME, 'GraphQLApiKey']),
                });
            }
        }
        if (transformParameters.enableTransformerCfnOutputs) {
            new aws_cdk_lib_1.CfnOutput(aws_cdk_lib_1.Stack.of(scope), 'GraphQLAPIIdOutput', {
                value: api.apiId,
                description: 'Your GraphQL API ID.',
                exportName: aws_cdk_lib_1.Fn.join(':', [aws_cdk_lib_1.Aws.STACK_NAME, 'GraphQLApiId']),
            });
            new aws_cdk_lib_1.CfnOutput(aws_cdk_lib_1.Stack.of(scope), 'GraphQLAPIEndpointOutput', {
                value: api.graphqlUrl,
                description: 'Your GraphQL API endpoint.',
                exportName: aws_cdk_lib_1.Fn.join(':', [aws_cdk_lib_1.Aws.STACK_NAME, 'GraphQLApiEndpoint']),
            });
        }
        api.addToSchema(output.buildSchema());
        return api;
    }
    collectResolvers(context, api) {
        const resolverEntries = context.resolvers.collectResolvers();
        for (const [resolverName, resolver] of resolverEntries) {
            const userSlots = this.userDefinedSlots[resolverName] || [];
            userSlots.forEach((slot) => {
                const requestTemplate = slot.requestResolver
                    ? cdk_compat_1.MappingTemplate.s3MappingTemplateFromString(slot.requestResolver.template, slot.requestResolver.fileName)
                    : undefined;
                const responseTemplate = slot.responseResolver
                    ? cdk_compat_1.MappingTemplate.s3MappingTemplateFromString(slot.responseResolver.template, slot.responseResolver.fileName)
                    : undefined;
                resolver.addToSlot(slot.slotName, requestTemplate, responseTemplate);
            });
            resolver.synthesize(context, api);
        }
    }
    transformObject(transformer, def, validDirectiveNameMap, context) {
        var _a, _b;
        let index = 0;
        for (const dir of (_a = def.directives) !== null && _a !== void 0 ? _a : []) {
            if (!validDirectiveNameMap[dir.name.value]) {
                throw new errors_1.UnknownDirectiveError(`Unknown directive '${dir.name.value}'. Either remove the directive from the schema or add a transformer to handle it.`);
            }
            if ((0, utils_1.matchDirective)(transformer.directive, dir, def)) {
                if (isFunction(transformer.object)) {
                    const transformKey = (0, utils_1.makeSeenTransformationKey)(dir, def, undefined, undefined, index);
                    if (!this.seenTransformations[transformKey]) {
                        transformer.object(def, dir, context);
                        this.seenTransformations[transformKey] = true;
                    }
                }
                else {
                    throw new errors_1.InvalidTransformerError(`The transformer '${transformer.name}' must implement the 'object()' method`);
                }
            }
            index++;
        }
        for (const field of (_b = def.fields) !== null && _b !== void 0 ? _b : []) {
            this.transformField(transformer, def, field, validDirectiveNameMap, context);
        }
    }
    transformField(transformer, parent, def, validDirectiveNameMap, context) {
        var _a, _b;
        let index = 0;
        for (const dir of (_a = def.directives) !== null && _a !== void 0 ? _a : []) {
            if (!validDirectiveNameMap[dir.name.value]) {
                throw new errors_1.UnknownDirectiveError(`Unknown directive '${dir.name.value}'. Either remove the directive from the schema or add a transformer to handle it.`);
            }
            if ((0, utils_1.matchFieldDirective)(transformer.directive, dir, def)) {
                if (isFunction(transformer.field)) {
                    const transformKey = (0, utils_1.makeSeenTransformationKey)(dir, parent, def, undefined, index);
                    if (!this.seenTransformations[transformKey]) {
                        transformer.field(parent, def, dir, context);
                        this.seenTransformations[transformKey] = true;
                    }
                }
                else {
                    throw new errors_1.InvalidTransformerError(`The transformer '${transformer.name}' must implement the 'field()' method`);
                }
            }
            index++;
        }
        for (const arg of (_b = def.arguments) !== null && _b !== void 0 ? _b : []) {
            this.transformArgument(transformer, parent, def, arg, validDirectiveNameMap, context);
        }
    }
    transformArgument(transformer, parent, field, arg, validDirectiveNameMap, context) {
        var _a;
        let index = 0;
        for (const dir of (_a = arg.directives) !== null && _a !== void 0 ? _a : []) {
            if (!validDirectiveNameMap[dir.name.value]) {
                throw new errors_1.UnknownDirectiveError(`Unknown directive '${dir.name.value}'. Either remove the directive from the schema or add a transformer to handle it.`);
            }
            if ((0, utils_1.matchArgumentDirective)(transformer.directive, dir, arg)) {
                if (isFunction(transformer.argument)) {
                    const transformKey = (0, utils_1.makeSeenTransformationKey)(dir, parent, field, arg, index);
                    if (!this.seenTransformations[transformKey]) {
                        transformer.argument(arg, dir, context);
                        this.seenTransformations[transformKey] = true;
                    }
                }
                else {
                    throw new errors_1.InvalidTransformerError(`The transformer '${transformer.name}' must implement the 'argument()' method`);
                }
            }
            index++;
        }
    }
    transformInterface(transformer, def, validDirectiveNameMap, context) {
        var _a, _b;
        let index = 0;
        for (const dir of (_a = def.directives) !== null && _a !== void 0 ? _a : []) {
            if (!validDirectiveNameMap[dir.name.value]) {
                throw new errors_1.UnknownDirectiveError(`Unknown directive '${dir.name.value}'. Either remove the directive from the schema or add a transformer to handle it.`);
            }
            if ((0, utils_1.matchDirective)(transformer.directive, dir, def)) {
                if (isFunction(transformer.interface)) {
                    const transformKey = (0, utils_1.makeSeenTransformationKey)(dir, def, undefined, undefined, index);
                    if (!this.seenTransformations[transformKey]) {
                        transformer.interface(def, dir, context);
                        this.seenTransformations[transformKey] = true;
                    }
                }
                else {
                    throw new errors_1.InvalidTransformerError(`The transformer '${transformer.name}' must implement the 'interface()' method`);
                }
            }
            index++;
        }
        for (const field of (_b = def.fields) !== null && _b !== void 0 ? _b : []) {
            this.transformField(transformer, def, field, validDirectiveNameMap, context);
        }
    }
    transformScalar(transformer, def, validDirectiveNameMap, context) {
        var _a;
        let index = 0;
        for (const dir of (_a = def.directives) !== null && _a !== void 0 ? _a : []) {
            if (!validDirectiveNameMap[dir.name.value]) {
                throw new errors_1.UnknownDirectiveError(`Unknown directive '${dir.name.value}'. Either remove the directive from the schema or add a transformer to handle it.`);
            }
            if ((0, utils_1.matchDirective)(transformer.directive, dir, def)) {
                if (isFunction(transformer.scalar)) {
                    const transformKey = (0, utils_1.makeSeenTransformationKey)(dir, def, undefined, undefined, index);
                    if (!this.seenTransformations[transformKey]) {
                        transformer.scalar(def, dir, context);
                        this.seenTransformations[transformKey] = true;
                    }
                }
                else {
                    throw new errors_1.InvalidTransformerError(`The transformer '${transformer.name}' must implement the 'scalar()' method`);
                }
            }
            index++;
        }
    }
    transformUnion(transformer, def, validDirectiveNameMap, context) {
        var _a;
        let index = 0;
        for (const dir of (_a = def.directives) !== null && _a !== void 0 ? _a : []) {
            if (!validDirectiveNameMap[dir.name.value]) {
                throw new errors_1.UnknownDirectiveError(`Unknown directive '${dir.name.value}'. Either remove the directive from the schema or add a transformer to handle it.`);
            }
            if ((0, utils_1.matchDirective)(transformer.directive, dir, def)) {
                if (isFunction(transformer.union)) {
                    const transformKey = (0, utils_1.makeSeenTransformationKey)(dir, def, undefined, undefined, index);
                    if (!this.seenTransformations[transformKey]) {
                        transformer.union(def, dir, context);
                        this.seenTransformations[transformKey] = true;
                    }
                }
                else {
                    throw new errors_1.InvalidTransformerError(`The transformer '${transformer.name}' must implement the 'union()' method`);
                }
            }
            index++;
        }
    }
    transformEnum(transformer, def, validDirectiveNameMap, context) {
        var _a, _b;
        let index = 0;
        for (const dir of (_a = def.directives) !== null && _a !== void 0 ? _a : []) {
            if (!validDirectiveNameMap[dir.name.value]) {
                throw new errors_1.UnknownDirectiveError(`Unknown directive '${dir.name.value}'. Either remove the directive from the schema or add a transformer to handle it.`);
            }
            if ((0, utils_1.matchDirective)(transformer.directive, dir, def)) {
                if (isFunction(transformer.enum)) {
                    const transformKey = (0, utils_1.makeSeenTransformationKey)(dir, def, undefined, undefined, index);
                    if (!this.seenTransformations[transformKey]) {
                        transformer.enum(def, dir, context);
                        this.seenTransformations[transformKey] = true;
                    }
                }
                else {
                    throw new errors_1.InvalidTransformerError(`The transformer '${transformer.name}' must implement the 'enum()' method`);
                }
            }
            index++;
        }
        for (const value of (_b = def.values) !== null && _b !== void 0 ? _b : []) {
            this.transformEnumValue(transformer, def, value, validDirectiveNameMap, context);
        }
    }
    transformEnumValue(transformer, enm, def, validDirectiveNameMap, context) {
        var _a;
        let index = 0;
        for (const dir of (_a = def.directives) !== null && _a !== void 0 ? _a : []) {
            if (!validDirectiveNameMap[dir.name.value]) {
                throw new errors_1.UnknownDirectiveError(`Unknown directive '${dir.name.value}'. Either remove the directive from the schema or add a transformer to handle it.`);
            }
            if ((0, utils_1.matchEnumValueDirective)(transformer.directive, dir, def)) {
                if (isFunction(transformer.enumValue)) {
                    const transformKey = (0, utils_1.makeSeenTransformationKey)(dir, enm, def, undefined, index);
                    if (!this.seenTransformations[transformKey]) {
                        transformer.enumValue(def, dir, context);
                        this.seenTransformations[transformKey] = true;
                    }
                }
                else {
                    throw new errors_1.InvalidTransformerError(`The transformer '${transformer.name}' must implement the 'enumValue()' method`);
                }
            }
            index++;
        }
    }
    transformInputObject(transformer, def, validDirectiveNameMap, context) {
        var _a, _b;
        let index = 0;
        for (const dir of (_a = def.directives) !== null && _a !== void 0 ? _a : []) {
            if (!validDirectiveNameMap[dir.name.value]) {
                throw new errors_1.UnknownDirectiveError(`Unknown directive '${dir.name.value}'. Either remove the directive from the schema or add a transformer to handle it.`);
            }
            if ((0, utils_1.matchDirective)(transformer.directive, dir, def)) {
                if (isFunction(transformer.input)) {
                    const transformKey = (0, utils_1.makeSeenTransformationKey)(dir, def, undefined, undefined, index);
                    if (!this.seenTransformations[transformKey]) {
                        transformer.input(def, dir, context);
                        this.seenTransformations[transformKey] = true;
                    }
                }
                else {
                    throw new errors_1.InvalidTransformerError(`The transformer '${transformer.name}' must implement the 'input()' method`);
                }
            }
            index++;
        }
        for (const field of (_b = def.fields) !== null && _b !== void 0 ? _b : []) {
            this.transformInputField(transformer, def, field, validDirectiveNameMap, context);
        }
    }
    transformInputField(transformer, input, def, validDirectiveNameMap, context) {
        var _a;
        let index = 0;
        for (const dir of (_a = def.directives) !== null && _a !== void 0 ? _a : []) {
            if (!validDirectiveNameMap[dir.name.value]) {
                throw new errors_1.UnknownDirectiveError(`Unknown directive '${dir.name.value}'. Either remove the directive from the schema or add a transformer to handle it.`);
            }
            if ((0, utils_1.matchInputFieldDirective)(transformer.directive, dir, def)) {
                if (isFunction(transformer.inputValue)) {
                    const transformKey = (0, utils_1.makeSeenTransformationKey)(dir, input, def, undefined, index);
                    if (!this.seenTransformations[transformKey]) {
                        transformer.inputValue(def, dir, context);
                        this.seenTransformations[transformKey] = true;
                    }
                }
                else {
                    throw new errors_1.InvalidTransformerError(`The transformer '${transformer.name}' must implement the 'inputValue()' method`);
                }
            }
            index++;
        }
    }
    getLogs() {
        return this.logs;
    }
    ensureNoneDataSource(api) {
        if (!api.host.hasDataSource(transformer_context_1.NONE_DATA_SOURCE_NAME)) {
            api.host.addNoneDataSource(transformer_context_1.NONE_DATA_SOURCE_NAME, {
                name: transformer_context_1.NONE_DATA_SOURCE_NAME,
                description: 'None Data Source for Pipeline functions',
            });
        }
    }
}
exports.GraphQLTransform = GraphQLTransform;
//# sourceMappingURL=transform.js.map