"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.generateModelDecoder = generateModelDecoder;
const Generator = __importStar(require("@postxl/generator"));
const FlattenedFieldHelper = __importStar(require("./excel-field-decoder.helper"));
const FieldHelper = __importStar(require("./field-decoder.helper"));
/**
 * Checks if a discriminated union has any relation fields (in common fields or any member).
 */
function discriminatedUnionHasRelationFields(field) {
    // Check common fields
    for (const f of field.commonFields.values()) {
        if (f.kind === 'relation') {
            return true;
        }
    }
    // Check member fields
    for (const member of field.members.values()) {
        for (const f of member.fields.values()) {
            if (f.kind === 'relation') {
                return true;
            }
        }
    }
    return false;
}
function generateModelDecoder({ model, context }) {
    const imports = Generator.ImportGenerator.from(model.decoder.decoder.zodDecoder.location).add(model.types);
    const sharedParams = { model, context, imports };
    // We cannot inline below definitions, as we need to add the imports before
    const decoder = getDecoder(sharedParams);
    const flattenedParams = {
        ...sharedParams,
        toName: (field) => field.databaseName,
        codecDefinition: model.decoder.flattened,
    };
    const flattenedType = getFlattenedType(flattenedParams);
    const flattenedEncoder = getFlattenedEncoder(flattenedParams);
    const flattenedDecoder = getFlattenedDecoder(flattenedParams);
    const excelFlattenedParams = {
        ...sharedParams,
        toName: (field) => field.excelName,
        codecDefinition: model.decoder.excel,
        isExcel: true,
    };
    const excelFlattenedType = getFlattenedType(excelFlattenedParams);
    const excelFlattenedEncoder = getFlattenedEncoder(excelFlattenedParams);
    const excelFlattenedDecoder = getFlattenedDecoder(excelFlattenedParams);
    return /* ts */ `
 import * as z from 'zod'
 ${imports.generate()}
 
 ${decoder}
 
 ${flattenedType}
 
 ${flattenedEncoder}
 
 ${flattenedDecoder}
 
 ${excelFlattenedType}
 
 ${excelFlattenedEncoder}
 
 ${excelFlattenedDecoder}`;
}
function getDecoder(params) {
    const { model } = params;
    const decoder = model.decoder.decoder;
    const fieldDecoders = [];
    for (const field of model.fields.values()) {
        fieldDecoders.push(FieldHelper.getFieldDecoder({ field, ...params }));
    }
    return `
    export const ${decoder.zodDecoder.name} = z.object({
      ${fieldDecoders.join(',\n')}
    })

    /**
     * Decoder to ensure that the input is a valid ${model.types.name} object.
     */
    export function ${decoder.parseFunction.name}(item: ${model.types.name}): ${model.types.name} {
    return ${decoder.zodDecoder.name}.parse(item)
    }`;
}
function getFlattenedType({ model, codecDefinition }) {
    return `/**
    * The ${model.types.name} flattened to a column structure.
    */
    export type ${codecDefinition.decoder.rawInputType.name} = z.infer<typeof ${codecDefinition.decoder.rawInputValidation.name}>`;
}
/**
 * Checks if the model needs lookup maps for Excel export (has relation fields or discriminated unions with relations).
 */
function checkNeedsLookupMaps(model, isExcel) {
    if (!isExcel) {
        return false;
    }
    for (const field of model.fields.values()) {
        if (field.kind === 'relation') {
            return true;
        }
        if (field.kind === 'discriminatedUnion' && discriminatedUnionHasRelationFields(field)) {
            return true;
        }
    }
    return false;
}
/**
 * Generates field encoding for a discriminated union.
 */
function encodeDiscriminatedUnionField(params) {
    const { field, model, imports, isExcel } = params;
    const du = model.decoder.discriminatedUnions.get(field.name);
    imports.add(du.flattened.encoder);
    const hasRelations = isExcel && discriminatedUnionHasRelationFields(field);
    const encoderCall = hasRelations
        ? `${du.flattened.encoder.name}(item.${field.name}, lookupMaps)`
        : `${du.flattened.encoder.name}(item.${field.name})`;
    return `...${encoderCall}`;
}
/**
 * Generates field encoding for a relation field.
 */
function encodeRelationField(params) {
    const { field, toName, context, isExcel } = params;
    if (!isExcel) {
        return `"${toName(field)}": item.${field.name}`;
    }
    // For Excel export, resolve relation IDs to keyField values using lookup maps
    const referencedModel = context.models.get(field.referencedModelName);
    const lookupExpr = `lookupMaps?.${referencedModel.decoder.dataModelEntry}.get(item.${field.name}) ?? item.${field.name}`;
    if (field.isRequired) {
        return `"${toName(field)}": ${lookupExpr}`;
    }
    return `"${toName(field)}": item.${field.name} === null ? null : (${lookupExpr})`;
}
/**
 * Generates field encoding for a single field based on its kind.
 */
function encodeField(field, params) {
    const { toName, model, imports, context, isExcel } = params;
    switch (field.kind) {
        case 'discriminatedUnion':
            return encodeDiscriminatedUnionField({ field, model, imports, isExcel });
        case 'relation':
            return encodeRelationField({ field, toName, context, isExcel });
        case 'enum':
        default:
            return `"${toName(field)}": item.${field.name}`;
    }
}
/**
 * Generates the function signature and JSDoc based on whether lookup maps are needed.
 */
function generateEncoderFunction(params) {
    const { needsLookupMaps, model, codecDefinition, inputType, fields } = params;
    if (needsLookupMaps) {
        return `
    /**
     * Encodes a ${model.types.name} object to a flattened Excel object.
     * When lookupMaps is provided, relation IDs are resolved to human-readable keyField values.
     * When lookupMaps is not provided (e.g., single-model export), IDs are output directly.
     * This ensures exported Excel files match the expected import format when used with flattenExcelDataModel.
     */
    export function ${codecDefinition.encoder.name}(item: ${inputType}, lookupMaps?: IdToKeyLookupMaps) {
      return {
        ${fields.join(',\n')}
      }
    }`;
    }
    return `
    /**
     * Encodes a ${model.types.name} object to a flattened object, where each
     * nested field is mapped to a top-level field. This allows serialization to a column structure.
     */
    export function ${codecDefinition.encoder.name}(item: ${inputType}) {
      return {
        ${fields.join(',\n')}
      }
    }`;
}
function getFlattenedEncoder(params) {
    const { model, imports, codecDefinition, isExcel } = params;
    const needsLookupMaps = checkNeedsLookupMaps(model, isExcel);
    if (needsLookupMaps) {
        imports.addTypeImport({
            from: Generator.toBackendModuleLocation('@decoders/data-model.decoder'),
            items: [Generator.toTypeName('IdToKeyLookupMaps')],
        });
    }
    const fields = [];
    for (const field of model.fields.values()) {
        fields.push(encodeField(field, params));
    }
    const inputType = getFlattenedEncoderInputType(params);
    return generateEncoderFunction({
        needsLookupMaps,
        model,
        codecDefinition,
        inputType,
        fields,
    });
}
function getFlattenedEncoderInputType({ model, imports }) {
    const omits = [];
    for (const field of model.fields.values()) {
        if (field.kind !== 'scalar') {
            continue;
        }
        if (field.isCreatedAt || field.isUpdatedAt) {
            omits.push(`'${field.name}'`);
        }
    }
    if (omits.length === 0) {
        return model.types.name;
    }
    imports.addTypeImport({
        from: Generator.toPackageName('@postxl/utils'),
        items: [Generator.toTypeName('Optional')],
    });
    return `Optional<${model.types.name}, ${omits.join(' | ')}>`;
}
function getFlattenedDecoder(params) {
    const { model, codecDefinition, toName } = params;
    const decoder = codecDefinition.decoder;
    const fieldDecoders = [];
    const duDecoders = [];
    const fieldTransformers = [];
    let hasDiscriminatedUnion = false;
    for (const field of model.fields.values()) {
        const { rawDecoder, transformer = FieldHelper.defaultTransformer } = FlattenedFieldHelper.getFieldElements({
            field,
            ...params,
            enforceNullable: false,
        });
        if (field.kind === 'discriminatedUnion') {
            hasDiscriminatedUnion = true;
            duDecoders.push(rawDecoder);
            fieldTransformers.push(`${field.name}: ${transformer('item, ctx')}`);
        }
        else {
            fieldDecoders.push(`"${toName(field)}": ${rawDecoder}`);
            fieldTransformers.push(`${field.name}: ${transformer('item["' + toName(field) + '"]')}`);
        }
    }
    return `
    export const ${decoder.rawInputValidation.name} = z.object({
        ${fieldDecoders.join(',\n')}
      })${duDecoders.map((du) => `.merge(${du})`).join('\n')}

    export function ${decoder.transformer.name}(item: ${decoder.rawInputType.name}${hasDiscriminatedUnion ? ', ctx: z.RefinementCtx' : ''}): ${model.types.name} {
      return {
        ${fieldTransformers.join(',\n')}
      }
    }

    /**
     * Decoder that parses the flattened object and transforms it to the ${model.types.name} type.
     */
    export const ${decoder.zodDecoder.name} = ${decoder.rawInputValidation.name}.transform(${decoder.transformer.name})

    export function ${decoder.parseFunction.name}(input: ${decoder.rawInputType.name}): ${model.types.name} {
      return ${decoder.zodDecoder.name}.parse(input)
    }`;
}
//# sourceMappingURL=model-decoder.generator.js.map