"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 () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.getFilterFieldName = void 0;
exports.generateModel = generateModel;
exports.getFieldDefinitions = getFieldDefinitions;
exports.getFilterValueType = getFilterValueType;
const Generator = __importStar(require("@postxl/generator"));
const utils_1 = require("@postxl/utils");
function generateModel({ model, context }) {
    const imports = Generator.ImportGenerator.from(model.types._filePath);
    imports.addImport({
        items: [Generator.toTypeName('CreateDto'), Generator.toTypeName('UpdateDto'), Generator.toTypeName('UpsertDto')],
        from: Generator.toFilePath('./dto.types'),
    });
    const fieldDefinitions = getFieldDefinitions({
        fields: Array.from(model.fields.values()),
        model,
        imports,
        context,
    });
    const types = model.types;
    const filterTypesCode = generateFilterTypes(model, imports, context);
    return `
import { z } from 'zod'
${imports.generate()}

  ${Generator.toJsDocComment(model.description)}
export type ${types.name} = {
  ${fieldDefinitions}
}

${generateBrandedIdType(model)}

/**
 * Data transfer object for creating a new ${model.userFriendlyName} instance.
 */
export type ${types.dto.create.name} = CreateDto<${types.name}, ${types.id.name}>

/**
 * Data transfer object for updating an existing ${model.userFriendlyName} instance.
 */
export type ${types.dto.update.name} = UpdateDto<${types.name}, ${types.id.name}>

/**
 * Data transfer object for upserting a new or existing ${model.userFriendlyName} instance.
 */
export type ${types.dto.upsert.name} = UpsertDto<${types.name}, ${types.id.name}>

/**
 * Data transfer object for cloning a new ${model.userFriendlyName} instance.
 */
export type ${types.dto.clone.name} = { 
  ${Array.from(model.fields.values())
        .map((f) => {
        // Id field is always required to resolve the source.
        if (f.kind === 'id') {
            return `${f.name}: ${f.type}`;
        }
        if (f.isUnique) {
            //`unique` fields require a new value.
            if (f.isRequired) {
                return `${f.name}: ${f.type}`;
            }
            return `${f.name}: ${f.type} | null`;
        }
        // Non-unique fields can be copied from the source.
        if (f.isRequired) {
            return `${f.name}?: ${f.type}`;
        }
        return `${f.name}?: ${f.type} | null`;
    })
        .join(', ')} 
}

${filterTypesCode}
`;
}
function generateFilterTypes(model, imports, context) {
    const { PascalCase, snake_case } = model._conjugated;
    const upperCase = snake_case.toUpperCase();
    const filterName = Generator.toTypeName(`${PascalCase}Filter`);
    const filterFieldName = Generator.toTypeName(`${PascalCase}FilterField`);
    const filterConfigName = Generator.toConstantName(`${upperCase}_FILTER_CONFIG`);
    const filterDecoderName = Generator.toFunctionName(`z${PascalCase}Filter`);
    const filterFields = [];
    for (const field of model.fields.values()) {
        const infos = getFilterFieldInfos({ field, model, context, imports });
        filterFields.push(...infos);
    }
    if (filterFields.length === 0) {
        return '';
    }
    const filterImports = [Generator.toTypeName('FilterConfig')];
    if (filterFields.some((f) => f.type === 'NumberFilter')) {
        filterImports.push(Generator.toTypeName('NumberFilter'), Generator.toFunctionName('zNumberFilter'));
    }
    if (filterFields.some((f) => f.type === 'DateFilter')) {
        filterImports.push(Generator.toTypeName('DateFilter'), Generator.toFunctionName('zDateFilter'));
    }
    imports.addImport({
        items: filterImports,
        from: Generator.toFilePath('./filter.types'),
    });
    return `
export type ${filterName} = {
  ${filterFields.map((f) => `${(0, exports.getFilterFieldName)(f.name, f.duParentFieldName)}?: ${f.type}`).join('\n  ')}
}

export const ${filterFieldName} = [
  ${filterFields.map((f) => `'${(0, exports.getFilterFieldName)(f.name, f.duParentFieldName)}'`).join(',\n  ')}
] as const

export type ${filterFieldName} = (typeof ${filterFieldName})[number]

export const ${filterConfigName}: FilterConfig<${filterFieldName}, ${filterName}> = {
  ${filterFields
        .map((f) => `${(0, exports.getFilterFieldName)(f.name, f.duParentFieldName)}: {
    filterKey: '${(0, exports.getFilterFieldName)(f.name, f.duParentFieldName)}',
    valueType: '${f.valueType}',
    kind: '${f.kind}',
    ${generateExtraFilterConfigFields(f)}
  }`)
        .join(',\n  ')}
}

export const ${filterDecoderName} = z.object({
  ${filterFields.map((f) => `${(0, exports.getFilterFieldName)(f.name, f.duParentFieldName)}: ${f.decoder}`).join(',\n  ')}
})
`;
}
const getFilterFieldName = (name, duParentFieldName) => {
    return duParentFieldName ? `${duParentFieldName}${name.charAt(0).toUpperCase()}${name.slice(1)}` : name;
};
exports.getFilterFieldName = getFilterFieldName;
/**
 * Returns all filter field infos for a field, including nested DU fields.
 */
function getFilterFieldInfos({ field, model, context, imports, duParentFieldName, duMemberType, }) {
    const result = [];
    // Handle discriminated union: add the discriminant field plus all nested fields
    if (field.kind === 'discriminatedUnion') {
        // Add the discriminant field itself
        const info = getFilterFieldInfo({ field, model, context, imports });
        if (info) {
            info.duParentFieldName = duParentFieldName;
            info.duMemberType = duMemberType;
            result.push(info);
        }
        // Add common fields
        for (const commonField of field.commonFields.values()) {
            const commonInfos = getFilterFieldInfos({
                field: commonField,
                model,
                context,
                imports,
                duParentFieldName: field.name,
            });
            result.push(...commonInfos);
        }
        // Add member-specific fields
        for (const member of field.members.values()) {
            for (const memberField of member.fields.values()) {
                const memberInfos = getFilterFieldInfos({
                    field: memberField,
                    model,
                    context,
                    imports,
                    duParentFieldName: field.name,
                    duMemberType: member.type,
                });
                result.push(...memberInfos);
            }
        }
    }
    else {
        // Regular field
        const info = getFilterFieldInfo({ field, model, context, imports });
        if (info) {
            info.duParentFieldName = duParentFieldName;
            info.duMemberType = duMemberType;
            result.push(info);
        }
    }
    return result;
}
function getFilterFieldInfo({ field, model, context, imports, }) {
    const { PascalCase } = model._conjugated;
    const valueType = getFilterValueType(field);
    if (field.kind === 'id') {
        const decoder = Generator.toFunctionName(`z${PascalCase}Id`);
        const typeName = Generator.toTypeName(`${PascalCase}Id`);
        return {
            name: field.name,
            type: `${typeName}[]`,
            valueType,
            decoder: `z.array(z.lazy(() => ${decoder})).optional()`,
            kind: 'id',
        };
    }
    else if (field.kind === 'scalar') {
        let type = 'string[]';
        let decoder = 'z.array(z.string()).optional()';
        if (field.type === 'number') {
            type = 'NumberFilter';
            decoder = 'zNumberFilter.optional()';
        }
        else if (field.type === 'boolean') {
            type = 'boolean[]';
            decoder = 'z.array(z.boolean()).optional()';
        }
        else if (field.type === 'Date') {
            type = 'DateFilter';
            decoder = 'zDateFilter.optional()';
        }
        return {
            name: field.name,
            type,
            valueType,
            decoder,
            kind: 'scalar',
        };
    }
    else if (field.kind === 'relation') {
        const referencedModel = context.models.get(field.referencedModelName);
        if (referencedModel) {
            const decoder = Generator.toFunctionName(`z${referencedModel._conjugated.PascalCase}Id`);
            const typeName = referencedModel.types.id.name;
            imports.addImport({ items: [decoder, typeName], from: referencedModel.types._filePath });
            return {
                name: field.name,
                type: `${typeName}[]`,
                valueType,
                decoder: `z.array(z.lazy(() => ${decoder})).optional()`,
                kind: 'relation',
                referencedModelViewServiceVariableName: Generator.toVariableName(referencedModel._conjugated.camelCasePlural),
                referencedModelLabelField: referencedModel.labelField.name,
                referencedModelIdField: referencedModel.idField.name,
            };
        }
    }
    else if (field.kind === 'enum') {
        const referencedEnum = context.enums.get(field.enumName);
        if (referencedEnum) {
            const typeName = referencedEnum.types.name;
            imports.addImport({ items: [typeName], from: referencedEnum.types._filePath });
            return {
                name: field.name,
                type: `${typeName}[]`,
                valueType,
                decoder: `z.array(z.lazy(() => z.nativeEnum(${typeName}))).optional()`,
                kind: 'enum',
                enumName: typeName,
                enumValues: Array.from(referencedEnum.members.values()).map((v) => v.value),
            };
        }
    }
    else if (field.kind === 'discriminatedUnion') {
        const duMemberTypes = Array.from(field.members.values()).map((m) => ({
            type: m.type,
            label: m.label,
        }));
        return {
            name: field.name,
            type: 'string[]',
            valueType,
            decoder: 'z.array(z.string()).optional()',
            kind: 'discriminatedUnion',
            duMemberTypes,
        };
    }
    return undefined;
}
function generateExtraFilterConfigFields(f) {
    switch (f.kind) {
        case 'relation':
            return `referencedModelViewServiceVariableName: '${f.referencedModelViewServiceVariableName}',
    referencedModelLabelField: '${f.referencedModelLabelField}',
    referencedModelIdField: '${f.referencedModelIdField}',`;
        case 'enum':
            return `enumName: '${f.enumName}',
    enumValues: ${JSON.stringify(f.enumValues)},`;
        case 'discriminatedUnion':
            return `duMemberTypes: ${JSON.stringify(f.duMemberTypes)},`;
        case 'scalar':
        case 'id':
            return '';
        default:
            throw new utils_1.ExhaustiveSwitchCheck(f);
    }
}
function getFieldDefinitions({ fields, model, imports, context, }) {
    const fieldDefinitions = [];
    for (const field of fields) {
        const fieldDefinition = getFieldDefinition({ field, model, imports, context });
        fieldDefinitions.push(Generator.addJsDocComment(`${field.name}: ${fieldDefinition}`, field.description));
    }
    return fieldDefinitions.join('\n\n');
}
function getFieldDefinition({ field, model, imports, context, }) {
    switch (field.kind) {
        case 'id':
            return field.type;
        case 'scalar':
            return addNullable(field.type, field.isRequired);
        case 'enum': {
            const referencedEnum = context.enums.get(field.enumName);
            if (!referencedEnum) {
                throw new Error(`Enum ${field.enumName} not found`);
            }
            imports.addImport({ items: [referencedEnum.types.name], from: referencedEnum.types._filePath });
            return addNullable(referencedEnum.name, field.isRequired);
        }
        case 'relation': {
            const referencedModel = context.models.get(field.referencedModelName);
            if (!referencedModel) {
                throw new Error(`Model ${field.referencedModelName} not found`);
            }
            imports.addImport({ items: [referencedModel.types.id.name], from: referencedModel.types._filePath });
            return addNullable(referencedModel.types.id.name, field.isRequired);
        }
        case 'discriminatedUnion': {
            const du = model.types.discriminatedUnions.get(field.name);
            imports.add(du.type);
            return addNullable(field.type, field.isRequired);
        }
        default:
            throw new utils_1.ExhaustiveSwitchCheck(field);
    }
}
function addNullable(type, isRequired) {
    return isRequired ? type : `${type} | null`; //NOSONAR
}
function getFilterValueType(field) {
    switch (field.kind) {
        case 'id':
        case 'relation':
        case 'enum':
        case 'discriminatedUnion':
            return 'string';
        case 'scalar':
            switch (field.type) {
                case 'number':
                    return 'number';
                case 'Date':
                    return 'date';
                case 'boolean':
                    return 'boolean';
                case 'string':
                default:
                    return 'string';
            }
        default:
            throw new utils_1.ExhaustiveSwitchCheck(field);
    }
}
function generateBrandedIdType(model) {
    const { PascalCase } = model._conjugated;
    const decoder = Generator.toFunctionName(`z${PascalCase}Id`);
    const brandedType = Generator.toTypeName(`${PascalCase}Id`);
    const toBrandedFunction = Generator.toFunctionName(`to${PascalCase}Id`);
    return `  
/**
 * Zod decoder for validating a ${model.userFriendlyName} id.
 */
export const ${decoder} = z
  .string()
  .brand('PXL.${brandedType}')
  
/**
 * Branded Id type used to identify an instance of a ${model.userFriendlyName} model.
 */
export type ${brandedType} = z.infer<typeof ${decoder}>

/**
 * Converts a given scalar to a branded id type.
 */
export const ${toBrandedFunction} = (input: string) => ${decoder}.parse(input)`;
}
//# sourceMappingURL=model-type.generator.js.map