"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getObjectPrimaryKey = exports.registerManyToManyForeignKeyMappings = exports.registerHasManyForeignKeyMappings = exports.registerHasOneForeignKeyMappings = exports.validateDisallowedDataStoreRelationships = exports.getBackendConnectionAttributeName = exports.getSortKeyConnectionAttributeName = exports.getManyToManyConnectionAttributeName = exports.getConnectionAttributeName = exports.validateRelatedModelDirective = exports.getBelongsToReferencesNodes = exports.getReferencesNodes = exports.getFieldsNodes = exports.getRelatedType = exports.validateModelDirective = exports.getModelDirective = exports.ensureReferencesBidirectionality = exports.ensureReferencesArray = exports.ensureFieldsArray = exports.getRelatedTypeIndex = exports.validateChildReferencesFields = exports.validateParentReferencesFields = void 0;
const graphql_transformer_core_1 = require("@aws-amplify/graphql-transformer-core");
const graphql_1 = require("graphql");
const graphql_transformer_common_1 = require("graphql-transformer-common");
const validateParentReferencesFields = (config, ctx) => {
    const { directiveName, object, references, relatedType } = config;
    const enums = ctx.output.getTypeDefinitionsOfKind(graphql_1.Kind.ENUM_TYPE_DEFINITION);
    const primaryKeys = (0, graphql_transformer_core_1.getPrimaryKeyFields)(object);
    if (primaryKeys.length !== references.length) {
        throw new graphql_transformer_core_1.InvalidDirectiveError(`The number of references provided to @${directiveName} must match the number of primary keys on ${object.name.value}.`);
    }
    for (const reference of references) {
        const fieldNode = relatedType.fields.find((field) => field.name.value === reference);
        if (!fieldNode) {
            throw new graphql_transformer_core_1.InvalidDirectiveError(`${reference} is not a field in ${relatedType.name.value}`);
        }
        if (!(0, graphql_transformer_common_1.isScalarOrEnum)(fieldNode.type, enums)) {
            throw new graphql_transformer_core_1.InvalidDirectiveError(`All reference fields provided to @${directiveName} must be scalar or enum fields.`);
        }
    }
};
exports.validateParentReferencesFields = validateParentReferencesFields;
const validateChildReferencesFields = (config, ctx) => {
    const { directiveName, object, references, relatedType } = config;
    const enums = ctx.output.getTypeDefinitionsOfKind(graphql_1.Kind.ENUM_TYPE_DEFINITION);
    const primaryKeys = (0, graphql_transformer_core_1.getPrimaryKeyFields)(relatedType);
    if (primaryKeys.length !== references.length) {
        throw new graphql_transformer_core_1.InvalidDirectiveError(`The number of references provided to @${directiveName} must match the number of primary keys on ${relatedType.name.value}.`);
    }
    for (const reference of references) {
        const fieldNode = object.fields.find((field) => field.name.value === reference);
        if (!fieldNode) {
            throw new graphql_transformer_core_1.InvalidDirectiveError(`${reference} is not a field in ${object.name.value}`);
        }
        if (!(0, graphql_transformer_common_1.isScalarOrEnum)(fieldNode.type, enums)) {
            throw new graphql_transformer_core_1.InvalidDirectiveError(`All reference fields provided to @${directiveName} must be scalar or enum fields.`);
        }
    }
};
exports.validateChildReferencesFields = validateChildReferencesFields;
const getRelatedTypeIndex = (config, ctx, indexName) => {
    const { directiveName, field, fieldNodes } = config;
    const relatedType = ctx.output.getType(config.relatedType.name.value);
    const fieldMap = new Map();
    let partitionFieldName;
    const sortFieldNames = [];
    const sortFields = [];
    for (const relatedTypeField of relatedType.fields) {
        fieldMap.set(relatedTypeField.name.value, relatedTypeField);
        for (const directive of relatedTypeField.directives) {
            const relatedDirectiveName = directive.name.value;
            const name = getIndexName(directive);
            if ((!indexName && relatedDirectiveName === 'primaryKey') || (indexName && indexName === name && relatedDirectiveName === 'index')) {
                partitionFieldName = relatedTypeField.name.value;
                for (const argument of directive.arguments) {
                    if (argument.name.value === 'sortKeyFields') {
                        if (argument.value.kind === graphql_1.Kind.STRING) {
                            sortFieldNames.push(argument.value.value);
                        }
                        else if (argument.value.kind === graphql_1.Kind.LIST) {
                            for (const keyField of argument.value.values) {
                                sortFieldNames.push(keyField.value);
                            }
                        }
                    }
                }
                break;
            }
        }
    }
    if (partitionFieldName === undefined) {
        if (indexName) {
            throw new graphql_transformer_core_1.InvalidDirectiveError(`Index ${indexName} does not exist for model ${relatedType.name.value}`);
        }
        partitionFieldName = 'id';
    }
    const partitionField = fieldMap.get(partitionFieldName);
    if (!partitionField) {
        throw new Error(`Expected partition field ${partitionFieldName} to be found in map.`);
    }
    for (const sortFieldName of sortFieldNames) {
        const sortField = fieldMap.get(sortFieldName);
        if (!sortField) {
            throw new Error(`Expected sort field ${sortFieldName} to be found in map.`);
        }
        sortFields.push(sortField);
    }
    if (fieldNodes.length > 0) {
        if ((0, graphql_transformer_common_1.getBaseType)(fieldNodes[0].type) !== (0, graphql_transformer_common_1.getBaseType)(partitionField.type)) {
            throw new graphql_transformer_core_1.InvalidDirectiveError(`${fieldNodes[0].name.value} field is not of type ${(0, graphql_transformer_common_1.getBaseType)(partitionField.type)}`);
        }
        if (fieldNodes.length > 1) {
            if (sortFields.length !== fieldNodes.length - 1) {
                throw new graphql_transformer_core_1.InvalidDirectiveError(`Invalid @${directiveName} directive on ${field.name.value}. Partial sort keys are not accepted.`);
            }
            for (let i = 0; i < sortFields.length; i++) {
                const sortField = sortFields[i];
                const fieldNode = fieldNodes[i + 1];
                if ((0, graphql_transformer_common_1.getBaseType)(fieldNode.type) !== (0, graphql_transformer_common_1.getBaseType)(sortField.type)) {
                    throw new graphql_transformer_core_1.InvalidDirectiveError(`${fieldNode.name.value} field is not of type ${(0, graphql_transformer_common_1.getBaseType)(sortField.type)}`);
                }
            }
        }
    }
    return [partitionField, ...sortFields];
};
exports.getRelatedTypeIndex = getRelatedTypeIndex;
const ensureFieldsArray = (config) => {
    if (config.references) {
        throw new graphql_transformer_core_1.InvalidDirectiveError(`DynamoDB models do not support 'references' on @${config.directiveName} directive.`);
    }
    if (!config.fields) {
        config.fields = [];
    }
    else if (!Array.isArray(config.fields)) {
        config.fields = [config.fields];
    }
    else if (config.fields.length === 0) {
        throw new graphql_transformer_core_1.InvalidDirectiveError(`No fields passed to @${config.directiveName} directive.`);
    }
};
exports.ensureFieldsArray = ensureFieldsArray;
const ensureReferencesArray = (config) => {
    if (config.fields) {
        throw new graphql_transformer_core_1.InvalidDirectiveError(`Relational database models do not support 'fields' on @${config.directiveName} directive.`);
    }
    if (!config.references) {
        throw new graphql_transformer_core_1.InvalidDirectiveError(`Reference fields must be passed to @${config.directiveName} directive for SQL models.`);
    }
    else if (!Array.isArray(config.references)) {
        config.references = [config.references];
    }
    else if (config.references.length === 0) {
        throw new graphql_transformer_core_1.InvalidDirectiveError(`No reference fields passed to @${config.directiveName} directive.`);
    }
};
exports.ensureReferencesArray = ensureReferencesArray;
const ensureReferencesBidirectionality = (config) => {
    if (config.fields) {
        throw new graphql_transformer_core_1.InvalidDirectiveError('fields and references cannot be used together.');
    }
};
exports.ensureReferencesBidirectionality = ensureReferencesBidirectionality;
const getModelDirective = (objectType) => {
    return objectType.directives.find((directive) => directive.name.value === 'model');
};
exports.getModelDirective = getModelDirective;
const validateModelDirective = (config) => {
    if (!(0, exports.getModelDirective)(config.object)) {
        throw new graphql_transformer_core_1.InvalidDirectiveError(`@${config.directiveName} must be on an @model object type field.`);
    }
};
exports.validateModelDirective = validateModelDirective;
const getRelatedType = (config, ctx) => {
    const { field } = config;
    const relatedTypeName = (0, graphql_transformer_common_1.getBaseType)(field.type);
    const relatedType = ctx.inputDocument.definitions.find((d) => d.kind === graphql_1.Kind.OBJECT_TYPE_DEFINITION && d.name.value === relatedTypeName);
    if (!relatedType) {
        throw new Error(`Could not find related type with name ${relatedTypeName} while processing relationships.`);
    }
    return relatedType;
};
exports.getRelatedType = getRelatedType;
const getFieldsNodes = (config, ctx) => {
    const { directiveName, fields, object } = config;
    const enums = ctx.output.getTypeDefinitionsOfKind(graphql_1.Kind.ENUM_TYPE_DEFINITION);
    return fields.map((fieldName) => {
        const fieldNode = object.fields.find((field) => field.name.value === fieldName);
        if (!fieldNode) {
            throw new graphql_transformer_core_1.InvalidDirectiveError(`${fieldName} is not a field in ${object.name.value}`);
        }
        if (!(0, graphql_transformer_common_1.isScalarOrEnum)(fieldNode.type, enums)) {
            throw new graphql_transformer_core_1.InvalidDirectiveError(`All fields provided to @${directiveName} must be scalar or enum fields.`);
        }
        return fieldNode;
    });
};
exports.getFieldsNodes = getFieldsNodes;
const getReferencesNodes = (config, ctx) => {
    const { directiveName, references, relatedType } = config;
    const enums = ctx.output.getTypeDefinitionsOfKind(graphql_1.Kind.ENUM_TYPE_DEFINITION);
    return references.map((fieldName) => {
        const fieldNode = relatedType.fields.find((field) => field.name.value === fieldName);
        if (!fieldNode) {
            throw new graphql_transformer_core_1.InvalidDirectiveError(`${fieldName} is not a field in ${relatedType.name.value}`);
        }
        if (!(0, graphql_transformer_common_1.isScalarOrEnum)(fieldNode.type, enums)) {
            throw new graphql_transformer_core_1.InvalidDirectiveError(`All references provided to @${directiveName} must be scalar or enum fields.`);
        }
        return fieldNode;
    });
};
exports.getReferencesNodes = getReferencesNodes;
const getBelongsToReferencesNodes = (config, ctx) => {
    const { directiveName, references, object } = config;
    const enums = ctx.output.getTypeDefinitionsOfKind(graphql_1.Kind.ENUM_TYPE_DEFINITION);
    return references.map((fieldName) => {
        const fieldNode = object.fields.find((field) => field.name.value === fieldName);
        if (!fieldNode) {
            throw new graphql_transformer_core_1.InvalidDirectiveError(`${fieldName} is not a field in ${object.name.value}`);
        }
        if (!(0, graphql_transformer_common_1.isScalarOrEnum)(fieldNode.type, enums)) {
            throw new graphql_transformer_core_1.InvalidDirectiveError(`All references provided to @${directiveName} must be scalar or enum fields.`);
        }
        return fieldNode;
    });
};
exports.getBelongsToReferencesNodes = getBelongsToReferencesNodes;
const validateRelatedModelDirective = (config) => {
    if (!(0, exports.getModelDirective)(config.relatedType)) {
        throw new graphql_transformer_core_1.InvalidDirectiveError(`Object type ${config.relatedType.name.value} must be annotated with @model.`);
    }
};
exports.validateRelatedModelDirective = validateRelatedModelDirective;
const getIndexName = (directive) => {
    for (const argument of directive.arguments) {
        if (argument.name.value === 'name') {
            return argument.value.value;
        }
    }
    return undefined;
};
const getConnectionAttributeName = (transformParameters, type, field, relatedTypeField) => {
    const nameSuffix = transformParameters.respectPrimaryKeyAttributesOnConnectionField ? relatedTypeField : 'id';
    return (0, graphql_transformer_common_1.toCamelCase)([type, field, nameSuffix]);
};
exports.getConnectionAttributeName = getConnectionAttributeName;
const getManyToManyConnectionAttributeName = (transformParameters, field, relatedTypeField) => {
    const nameSuffix = transformParameters.respectPrimaryKeyAttributesOnConnectionField ? (0, graphql_transformer_common_1.toPascalCase)([relatedTypeField]) : 'ID';
    return `${(0, graphql_transformer_common_1.toCamelCase)([field])}${nameSuffix}`;
};
exports.getManyToManyConnectionAttributeName = getManyToManyConnectionAttributeName;
const getSortKeyConnectionAttributeName = (type, field, relatedTypeField) => {
    return (0, graphql_transformer_common_1.toCamelCase)([type, field, relatedTypeField]);
};
exports.getSortKeyConnectionAttributeName = getSortKeyConnectionAttributeName;
const getBackendConnectionAttributeName = (transformParameters, resourceHelper, type, field, relatedTypeField) => {
    return (0, exports.getConnectionAttributeName)(transformParameters, resourceHelper.getModelNameMapping(type), field, relatedTypeField);
};
exports.getBackendConnectionAttributeName = getBackendConnectionAttributeName;
const validateDisallowedDataStoreRelationships = (config, ctx) => {
    if (!ctx.isProjectUsingDataStore()) {
        return;
    }
    const modelType = config.object.name.value;
    const relatedType = ctx.output.getType(config.relatedType.name.value);
    if (!relatedType) {
        throw new Error(`Expected related type ${config.relatedType.name.value} to be found in output, but did not.`);
    }
    if (modelType === relatedType.name.value) {
        return;
    }
    const hasUnsupportedConnectionFields = relatedType.fields.some((field) => (0, graphql_transformer_common_1.getBaseType)(field.type) === modelType &&
        field.directives.some((directive) => directive.name.value === 'hasOne' || directive.name.value === 'hasMany'));
    if (hasUnsupportedConnectionFields) {
        throw new graphql_transformer_core_1.InvalidDirectiveError(`${modelType} and ${relatedType.name.value} cannot refer to each other via @hasOne or @hasMany when DataStore is in use. Use @belongsTo instead. See https://docs.amplify.aws/cli/graphql/data-modeling/#belongs-to-relationship`);
    }
};
exports.validateDisallowedDataStoreRelationships = validateDisallowedDataStoreRelationships;
const registerHasOneForeignKeyMappings = ({ transformParameters, resourceHelper, thisTypeName, thisFieldName, relatedType, }) => {
    if (resourceHelper.isModelRenamed(thisTypeName)) {
        const currAttrName = (0, exports.getConnectionAttributeName)(transformParameters, thisTypeName, thisFieldName, (0, exports.getObjectPrimaryKey)(relatedType).name.value);
        const origAttrName = (0, exports.getBackendConnectionAttributeName)(transformParameters, resourceHelper, thisTypeName, thisFieldName, (0, exports.getObjectPrimaryKey)(relatedType).name.value);
        const modelFieldMap = resourceHelper.getModelFieldMap(thisTypeName);
        modelFieldMap.addMappedField({ currentFieldName: currAttrName, originalFieldName: origAttrName });
        ['create', 'update', 'delete', 'get', 'list', 'sync'].forEach((op) => {
            const opFieldName = (0, graphql_transformer_core_1.getFieldNameFor)(op, thisTypeName);
            const opTypeName = op === 'create' || op === 'update' || op === 'delete' ? 'Mutation' : 'Query';
            const opIsList = op === 'list' || op === 'sync';
            modelFieldMap.addResolverReference({ typeName: opTypeName, fieldName: opFieldName, isList: opIsList });
        });
    }
    resourceHelper
        .getModelFieldMap(relatedType.name.value)
        .addResolverReference({ typeName: thisTypeName, fieldName: thisFieldName, isList: false });
};
exports.registerHasOneForeignKeyMappings = registerHasOneForeignKeyMappings;
const registerHasManyForeignKeyMappings = ({ transformParameters, resourceHelper, thisTypeName, thisFieldName, relatedType, }) => {
    if (!resourceHelper.isModelRenamed(thisTypeName)) {
        return;
    }
    const currAttrName = (0, exports.getConnectionAttributeName)(transformParameters, thisTypeName, thisFieldName, (0, exports.getObjectPrimaryKey)(relatedType).name.value);
    const origAttrName = (0, exports.getBackendConnectionAttributeName)(transformParameters, resourceHelper, thisTypeName, thisFieldName, (0, exports.getObjectPrimaryKey)(relatedType).name.value);
    const modelFieldMap = resourceHelper.getModelFieldMap(relatedType.name.value);
    modelFieldMap
        .addMappedField({ currentFieldName: currAttrName, originalFieldName: origAttrName })
        .addResolverReference({ typeName: thisTypeName, fieldName: thisFieldName, isList: true });
    ['create', 'update', 'delete', 'get', 'list', 'sync'].forEach((op) => {
        const opFieldName = (0, graphql_transformer_core_1.getFieldNameFor)(op, relatedType.name.value);
        const opTypeName = op === 'create' || op === 'update' || op === 'delete' ? 'Mutation' : 'Query';
        const opIsList = op === 'list' || op === 'sync';
        modelFieldMap.addResolverReference({ typeName: opTypeName, fieldName: opFieldName, isList: opIsList });
    });
};
exports.registerHasManyForeignKeyMappings = registerHasManyForeignKeyMappings;
const registerManyToManyForeignKeyMappings = ({ resourceHelper, typeName, referencedBy, fieldMap, }) => {
    const modelFieldMap = resourceHelper.getModelFieldMap(typeName);
    fieldMap.forEach(modelFieldMap.addMappedField);
    referencedBy.forEach(modelFieldMap.addResolverReference);
    ['create', 'update', 'delete', 'get', 'list', 'sync'].forEach((op) => {
        const opFieldName = (0, graphql_transformer_core_1.getFieldNameFor)(op, typeName);
        const opTypeName = op === 'create' || op === 'update' || op === 'delete' ? 'Mutation' : 'Query';
        const opIsList = op === 'list' || op === 'sync';
        modelFieldMap.addResolverReference({ typeName: opTypeName, fieldName: opFieldName, isList: opIsList });
    });
};
exports.registerManyToManyForeignKeyMappings = registerManyToManyForeignKeyMappings;
const getObjectPrimaryKey = (object) => {
    let primaryKey = (0, graphql_transformer_common_1.makeField)('id', [], (0, graphql_transformer_common_1.makeNonNullType)((0, graphql_transformer_common_1.makeNamedType)('ID')));
    object.fields.forEach((objectField) => {
        objectField.directives.forEach((directive) => {
            if (directive.name.value === 'primaryKey') {
                primaryKey = objectField;
            }
        });
    });
    return primaryKey;
};
exports.getObjectPrimaryKey = getObjectPrimaryKey;
//# sourceMappingURL=utils.js.map