"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.generateDiscriminatedUnionDecoder = generateDiscriminatedUnionDecoder;
const Generator = __importStar(require("@postxl/generator"));
const FlattenedFieldHelper = __importStar(require("./excel-field-decoder.helper"));
const FieldDecoder = __importStar(require("./field-decoder.helper"));
/**
 * Checks if a discriminated union has any relation fields (in common fields or any member).
 */
function hasRelationFields(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 generateDiscriminatedUnionDecoder({ context, model, discriminatedUnion, }) {
    const imports = Generator.ImportGenerator.from(model.decoder.decoder.zodDecoder.location);
    const params = { context, model, discriminatedUnion, field: discriminatedUnion.field, imports };
    // We cannot inline below definitions, as we need to add the imports before
    const decoder = getDecoder(params);
    const flattenedParams = {
        ...params,
        toName: (f) => f.databaseName,
        codecDefinition: discriminatedUnion.flattened,
    };
    const flattenedType = getFlattenedType(flattenedParams);
    const flattenedEncoder = getFlattenedEncoder(flattenedParams);
    const flattenedDecoder = getFlattenedDecoder(flattenedParams);
    const excelParams = {
        ...params,
        toName: (f) => f.excelName,
        codecDefinition: discriminatedUnion.excel,
        isExcel: true,
    };
    const excelType = getFlattenedType(excelParams);
    const excelEncoder = getFlattenedEncoder(excelParams);
    const excelDecoder = getFlattenedDecoder(excelParams);
    return /* ts */ `
import * as z from 'zod'
${imports.generate()}

${decoder}

${flattenedType}

${flattenedEncoder}

${flattenedDecoder}

${excelType}

${excelEncoder}

${excelDecoder}`;
}
function getDecoder({ context, model, discriminatedUnion, field, imports }) {
    imports.addTypeImport({ from: context.types.location, items: [field.type] });
    const sharedFields = [];
    for (const f of field.commonFields.values()) {
        sharedFields.push(FieldDecoder.getFieldDecoder({ context, model, field: f, imports }));
    }
    const members = [];
    for (const member of field.members.values()) {
        const memberFields = [`type: z.literal('${member.type}')`, ...sharedFields];
        for (const f of member.fields.values()) {
            memberFields.push(FieldDecoder.getFieldDecoder({ context, model, field: f, imports }));
        }
        members.push(`z.object({ ${memberFields.join(', ')}})`);
    }
    return `
export const ${discriminatedUnion.decoder.zodDecoder.name} = z.discriminatedUnion('type', [
  ${members.join(',\n')}
])

/**
 * Decoder to ensure that the input is a valid ${field.type}.
 */
export function ${discriminatedUnion.decoder.parseFunction.name}(item: ${field.type}): ${field.type} {
 return ${discriminatedUnion.decoder.zodDecoder.name}.parse(item)
}
`;
}
function getFlattenedType({ codecDefinition, field }) {
    return `/**
 * The ${field.type} discriminated union type flattened to a column structure.
 */
export type ${codecDefinition.decoder.rawInputType.name} = z.infer<typeof ${codecDefinition.decoder.rawInputValidation.name}>
`;
}
function getFlattenedEncoder({ context, field, imports, toName, codecDefinition }) {
    const hasRelations = hasRelationFields(field);
    // If there are relation fields, we need the lookup maps type
    if (hasRelations) {
        imports.addTypeImport({
            from: Generator.toBackendModuleLocation('@decoders/data-model.decoder'),
            items: [Generator.toTypeName('IdToKeyLookupMaps')],
        });
    }
    const encoderFields = getEncoderFields({ field, context, hasRelations, toName });
    if (hasRelations) {
        return `
/**
 * Encodes a ${field.type} discriminated union to a flattened object.
 * When lookupMaps is provided, relation IDs are resolved to human-readable keyField values.
 * When lookupMaps is not provided, IDs are output directly.
 */
export function ${codecDefinition.encoder.name}(item: ${field.type}, lookupMaps?: IdToKeyLookupMaps) {
  return {
    ${encoderFields.join(',\n')}
  }
}
`;
    }
    return `
/**
 * Encodes a ${field.type} discriminated union 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: ${field.type}) {
  return {
    ${encoderFields.join(',\n')}
  }
}
`;
}
function getEncoderFields({ field, context, hasRelations, toName, }) {
    const result = [];
    result.push(`"${toName(field)}": item.type`);
    for (const f of field.commonFields.values()) {
        result.push(getEncoderFieldValue({ f, context, hasRelations, memberType: null, toName }));
    }
    for (const member of field.members.values()) {
        for (const f of member.fields.values()) {
            result.push(getEncoderFieldValue({ f, context, hasRelations, memberType: member.type, toName }));
        }
    }
    return result;
}
function getEncoderFieldValue({ f, context, hasRelations, memberType, toName, }) {
    const key = `"${toName(f)}"`;
    const valueExpr = memberType ? `item.type === '${memberType}' ? item.${f.name} : null` : `item.${f.name}`;
    if (f.kind === 'relation' && hasRelations) {
        const referencedModel = context.models.get(f.referencedModelName);
        const lookupExpr = `lookupMaps?.${referencedModel.decoder.dataModelEntry}.get(item.${f.name})`;
        if (memberType) {
            // Member-specific field: check type first, then resolve ID to key
            if (f.isRequired) {
                return `${key}: item.type === '${memberType}' ? (${lookupExpr} ?? item.${f.name}) : null`;
            }
            return `${key}: item.type === '${memberType}' ? (item.${f.name} === null ? null : (${lookupExpr} ?? item.${f.name})) : null`;
        }
        else {
            // Common field: always present
            if (f.isRequired) {
                return `${key}: ${lookupExpr} ?? item.${f.name}`;
            }
            return `${key}: item.${f.name} === null ? null : (${lookupExpr} ?? item.${f.name})`;
        }
    }
    return `${key}: ${valueExpr}`;
}
function getFlattenedDecoder({ context, model, field, imports, toName, codecDefinition, }) {
    imports.addTypeImport({ from: context.types.location, items: [field.type] });
    const discriminatorKey = toName(field);
    const enumDecoder = `z.enum([${Array.from(field.members.values())
        .map((f) => `'${f.type}'`)
        .join(',')}]).transform((v) => v as string).nullable()`;
    const fieldRawDecoders = [
        `"${discriminatorKey}": z.preprocess((x) => (typeof x === 'string' && x === '') ? null : x, ${enumDecoder})`,
    ];
    const commonFieldRequiredChecks = [`nullCheck('${discriminatorKey}')`];
    const commonFieldTransformers = [];
    const transformerSwitchCases = [];
    for (const f of field.commonFields.values()) {
        const { rawDecoder, transformer = FieldDecoder.defaultTransformer } = FlattenedFieldHelper.getFieldElements({
            field: f,
            context,
            model,
            imports,
            // All fields are mode nullable even if required within a discriminated union, as the DU itself might be nullable!
            enforceNullable: true,
        });
        const key = toName(f);
        fieldRawDecoders.push(`"${key}": ${rawDecoder}`);
        if (field.isRequired) {
            commonFieldRequiredChecks.push(`if (item["${key}"] === null) {
        ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Field ${key} is required but null!', path: ['${key}'] })
        return z.NEVER
      }`);
        }
        commonFieldTransformers.push(`${f.name}: ${transformer('item["' + key + '"]' + (f.isRequired ? '!' : ''))}`);
    }
    for (const member of field.members.values()) {
        const memberTransformers = [`type: '${member.type}'`, ...commonFieldTransformers];
        const memberFieldRequiredChecks = [];
        for (const f of member.fields.values()) {
            const { rawDecoder, transformer = FieldDecoder.defaultTransformer } = FlattenedFieldHelper.getFieldElements({
                field: f,
                context,
                model,
                imports,
                enforceNullable: true,
            });
            const key = toName(f);
            fieldRawDecoders.push(`"${key}": ${rawDecoder}`);
            if (f.isRequired) {
                memberFieldRequiredChecks.push(`nullCheck('${key}')`);
            }
            memberTransformers.push(`${f.name}: ${transformer('item["' + key + '"]' + (f.isRequired ? '!' : ''))}`);
        }
        transformerSwitchCases.push(`case '${member.type}': {
        ${memberFieldRequiredChecks.join('\n')}
        return {
        ${memberTransformers.join(',\n')}
      }
    }`);
    }
    return `
export const ${codecDefinition.decoder.rawInputValidation.name} = z.object({
    ${fieldRawDecoders.join(',\n')}
  })

export function ${codecDefinition.decoder.transformer.name}(item: ${codecDefinition.decoder.rawInputType.name}, ctx: z.RefinementCtx): ${field.type} {
  try {
    const nullCheck = (fieldName: keyof ${codecDefinition.decoder.rawInputType.name}) => {
      if (item[fieldName] === null) {
        const message = \`Field \${fieldName} is required but null!\`
        ctx.addIssue({ code: z.ZodIssueCode.custom, message, path: [fieldName] })
        throw new Error(message)
      }
    }

    ${commonFieldRequiredChecks.join('\n')}
    switch (item["${discriminatorKey}"]) {
      ${transformerSwitchCases.join('\n')}
      default: {
        const message = \`Field ${field.name} is not a valid enum value!\`
        ctx.addIssue({ code: z.ZodIssueCode.custom, message, path: ['${field.name}'] })
        throw new Error(message)
      }
    }
  } catch {
    return z.NEVER
  }
}

/**
 * Decoder that parses the flattened object and transforms it to the ${field.type} discriminated union.
 */
export const ${codecDefinition.decoder.zodDecoder.name} = ${codecDefinition.decoder.rawInputValidation.name}.transform(${codecDefinition.decoder.transformer.name})
`;
}
//# sourceMappingURL=discriminated-union.decoder.generator.js.map