"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.generatePrismaSchema = generatePrismaSchema;
const generator_1 = require("@postxl/generator");
const utils_1 = require("@postxl/utils");
const model_types_1 = require("../../backend-repositories/model.types");
const prisma_utils_1 = require("../prisma.utils");
/**
 * Checks if a model is configured as InMemory-only.
 * InMemory models should not be included in the Prisma schema as they don't persist to the database.
 */
function isInMemoryOnly(model) {
    if (!model.source) {
        return false;
    }
    try {
        const { repository: { type: kind }, } = model_types_1.zRepository.parse(model.source);
        return kind === 'InMemory';
    }
    catch {
        return false; // Default to including in schema if parse fails
    }
}
function generatePrismaSchema(context) {
    const schemas = new Map();
    for (const model of context.models.values()) {
        if (isInMemoryOnly(model)) {
            continue;
        }
        const schema = schemas.get(model.databaseSchema) || [];
        if (schema.length === 0) {
            schema.push(createSchemaHeader(model.databaseSchema));
        }
        schema.push(createPrismaModel({ model, models: context.models }));
        schemas.set(model.databaseSchema, schema);
    }
    for (const enumType of context.enums.values()) {
        const schema = schemas.get(enumType.databaseSchema) || [];
        if (schema.length === 0) {
            schema.push(createSchemaHeader(enumType.databaseSchema));
        }
        schema.push(createPrismaEnum(enumType));
        schemas.set(enumType.databaseSchema, schema);
    }
    const result = /**prisma */ `
    datasource db {
      provider = "postgres"
      schemas  = [${Array.from(schemas.keys())
        .map((schema) => `"${(0, utils_1.toSnakeCase)(schema)}"`)
        .join(', ')}]
    }

    generator client {
      provider        = "prisma-client"
      output   = "./prisma"
      engineType = "client"
    }

  ${Array.from(schemas.values())
        .map((schema) => schema.join('\n'))
        .join('\n')}`;
    // return result
    const formatted = (0, prisma_utils_1.formatPrismaSchema)(result);
    const linterResult = (0, prisma_utils_1.lintPrismaSchema)(formatted);
    if (linterResult.isErr()) {
        const error = linterResult.unwrapErr();
        console.error(error);
        if (!context.options.ignoreErrors) {
            throw new Error(`Prisma schema is invalid: ${error.join('\n')}`);
        }
    }
    return formatted;
}
function createSchemaHeader(schema) {
    return (0, generator_1.ts)(`
  // # --------------------------- #
  // # ${schema} Schema #
  // # --------------------------- #`);
}
function createPrismaModel({ model, models, }) {
    const fields = [];
    for (const field of model.fields.values()) {
        fields.push(createPrismaField({ field, models }));
    }
    const backReferences = createModelBackReferences({ model, models });
    return (0, generator_1.ts)(`
model ${model.name} {
${fields.join('\n')}

${backReferences}
  @@map("${model.databaseName}")
  @@schema("${(0, utils_1.toSnakeCase)(model.databaseSchema)}")
}`);
}
function createPrismaField({ field, models, }) {
    switch (field.kind) {
        case 'id':
            return `${field.name} ${field.databaseType} @id @default(cuid())${description(field)}${mapField(field)}\n`;
        case 'scalar':
            return createScalarField(field);
        case 'relation':
            return createRelationField(field);
        case 'enum':
            return createEnumField(field);
        case 'discriminatedUnion':
            return createDiscriminatedUnionField({ field, models });
        default:
            throw new utils_1.ExhaustiveSwitchCheck(field);
    }
}
function createScalarField(field) {
    return `${field.databaseName} ${field.databaseType}${fieldAttributes(field)}`;
}
function createRelationField(field) {
    return `
      ${field.databaseName} ${field.databaseType}${fieldAttributes(field)}
      ${removeId(field.databaseName)} ${field.referencedModelName}${isRequired(field)} @relation("${relationName(field)}", fields: [${field.databaseName}], references: [id])
    `;
}
function createDiscriminatedUnionField({ field, models, }) {
    const elements = [];
    // Discriminant field
    elements.push(`
    /// -- Discriminated union definition for ${field.name}

    /// Discriminant field
    /// Values can be: ${Array.from(field.members.keys()).join(', ')}
    ${field.name} ${field.databaseType}${fieldAttributes(field)}
    `);
    if (field.commonFields.size > 0) {
        elements.push(` /// -- Shared fields`);
    }
    for (const sharedField of field.commonFields.values()) {
        elements.push(createPrismaField({ field: sharedField, models }));
    }
    for (const member of field.members.values()) {
        elements.push(` /// -- Member ${member.type}`);
        for (const memberField of member.fields.values()) {
            elements.push(createPrismaField({
                field: {
                    ...memberField,
                    //even if the field is required, we need to make it optional in the database as it will be null in case the item is not of this type
                    isRequired: false,
                },
                models,
            }));
        }
    }
    elements.push(` /// -- Discriminated union ${field.name} end`);
    return elements.join('\n');
}
function fieldAttributes(field) {
    const attributes = [];
    const add = (condition, attribute) => {
        if (condition) {
            attributes.push(attribute);
        }
    };
    add(!field.isRequired, '?');
    add(field.isUnique, '@unique');
    add(field.name !== field.databaseName, `@map("${field.databaseName}")`);
    add(field.kind === 'scalar' && field.isCreatedAt, '@default(now())');
    add(field.kind === 'scalar' && field.isUpdatedAt, '@updatedAt');
    add(field.description !== undefined, description(field));
    return attributes.join(' ');
}
function createEnumField(field) {
    return `${field.databaseName} ${field.enumName}${fieldAttributes(field)}`;
}
function isRequired({ isRequired }) {
    return isRequired ? '' : '?'; // NOSONAR
}
function description({ description }) {
    return description
        ? description
            .split('\n')
            .map((d) => `  /// ${d}`)
            .join('\n')
        : '';
}
function mapField(field) {
    if (field.name === field.databaseName) {
        return '';
    }
    return `@map("${field.databaseName}")`;
}
function relationName(field) {
    return `${field.modelName}.${field.name}->${field.referencedModelName}`;
}
function createPrismaEnum(enum_) {
    const members = [];
    for (const member of enum_.members.values()) {
        members.push(`  ${member.value}`);
    }
    return (0, generator_1.ts)(`
enum ${enum_.name} {
${members.join('\n')}
  @@map("${enum_.databaseName}")
  @@schema("${(0, utils_1.toSnakeCase)(enum_.databaseSchema)}")
}`);
}
function createModelBackReferences({ model, models, }) {
    const backReferences = [];
    for (const targetModel of models.values()) {
        if (isInMemoryOnly(targetModel)) {
            continue;
        }
        backReferences.push(...createModelBackReferencesFromFields({
            model,
            targetModel,
            fields: Array.from(targetModel.fields.values()),
        }));
    }
    if (backReferences.length === 0) {
        return '';
    }
    return `/// Back references to ${model.name}
  ${backReferences.join('\n')}`;
}
function createModelBackReferencesFromFields({ model, targetModel, fields, }) {
    const backReferences = [];
    for (const field of fields.values()) {
        if (field.kind === 'relation' && field.referencedModelName === model.name) {
            backReferences.push(`${(0, utils_1.toCamelCase)(targetModel.name)}${(0, utils_1.toPascalCase)((0, utils_1.pluralize)(removeId(field.name)))} ${targetModel.name}[] @relation("${relationName(field)}")`);
        }
        if (field.kind === 'discriminatedUnion') {
            backReferences.push(...createModelBackReferencesFromFields({
                model,
                targetModel,
                fields: Array.from(field.commonFields.values()),
            }));
            for (const member of field.members.values()) {
                backReferences.push(...createModelBackReferencesFromFields({
                    model,
                    targetModel,
                    fields: Array.from(member.fields.values()),
                }));
            }
        }
    }
    return backReferences;
}
function removeId(name) {
    return name.replace(/_id$/, '');
}
//# sourceMappingURL=prisma-schema.generator.js.map