"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateDetectDeltaAllModels = generateDetectDeltaAllModels;
const generator_1 = require("@postxl/generator");
function generateDetectDeltaAllModels(context) {
    const { decoders, import: { detectDelta }, } = context;
    const { allModels } = detectDelta;
    const imports = generator_1.ImportGenerator
        //
        .from(allModels.location)
        .add(context.repositories.dataService)
        .addType(decoders.dataModel.decodedType)
        .add(context.import.detectDelta.types.generic.modelType)
        .addType(context.import.detectDelta.types.generic.errors.type)
        .add(context.import.detectDelta.types.generic.errors.typeGuard)
        .add(context.import.detectDelta._utils.ensureItemsHaveIds)
        .addImport({
        from: (0, generator_1.toPackageName)('@postxl/utils'),
        items: [(0, generator_1.toFunctionName)('red'), (0, generator_1.toFunctionName)('format'), (0, generator_1.toFunctionName)('pluralize')],
    })
        .addImport({
        from: (0, generator_1.toPackageName)('node:crypto'),
        items: [(0, generator_1.toFunctionName)('randomUUID')],
    });
    const { resultAssignments, idMapTypes, keyMapTypes, deltaTypes, prepareImportMapsAssignments, prepareImportMapsIdMapResult, prepareImportMapsKeyMapResult, } = prepareModelDeltaTypes({
        context,
        imports,
    });
    return `
${imports.generate()}

/**
 * The delta of for all models.
 */
export type ${context.import.detectDelta.allModels.type.name} = {
  ${deltaTypes.join('\n')}
}

/**
 * Maps model IDs to their records (for records that have an id field in import data).
 */
export type ${context.import.detectDelta.allModels.idMapType.name} = {
  ${idMapTypes.join('\n')}
}

/**
 * Maps keyField values to their records (for resolving foreign keys by keyField).
 */
export type KeyMap = {
  ${keyMapTypes.join('\n')}
}

/**
 * Pass 1: Ensure all items have IDs assigned.
 * For items without an ID, check database by key or generate a new UUID.
 * This populates idMap and keyMap with items that have proper IDs.
 */
async function prepareImportMaps(
  dataService: ${context.repositories.dataService.name},
  data: ${decoders.dataModel.decodedType.name},
): Promise<{ idMap: ${context.import.detectDelta.allModels.idMapType.name}; keyMap: KeyMap }> {
  ${prepareImportMapsAssignments.join('\n')}

  return {
    idMap: {
      ${prepareImportMapsIdMapResult.join(',\n')}
    },
    keyMap: {
      ${prepareImportMapsKeyMapResult.join(',\n')}
    },
  }
}

export async function detectDelta(
  dataService: ${context.repositories.dataService.name},
  data: ${decoders.dataModel.decodedType.name},
  throwInCaseOfError = true,
): Promise<${allModels.type.name}> {
  // Pass 1: Ensure all items have IDs and build lookup maps
  // This must happen FIRST so that when resolving relation keys,
  // referenced items already have their IDs assigned.
  const { idMap, keyMap } = await prepareImportMaps(dataService, data)

  // Pass 2 & 3: Run delta detection (which includes relation key resolution)
  // The resolveRelationKeys callback in each model's detectDelta will use
  // idMap and keyMap to resolve relation field keys to proper IDs.
  const result: ${allModels.type.name} = {
    ${resultAssignments.join(',\n')}
  }

  const errors = ${allModels.extractErrors.name}(result)
  if (Object.keys(errors).length > 0 && throwInCaseOfError) {
    throw new Error(${allModels.summarizeErrors.name}(errors))
  }
  return result
}

export function ${allModels.extractErrors.name}(delta: ${allModels.type.name}): Record<string, ${detectDelta.types.generic.errors.type.name}<any, any, any>[]> {
  const result: Record<string, ${detectDelta.types.generic.errors.type.name}<any, any, any>[]> = {}
  for (const [key, value] of Object.entries(delta)) {
    const errors = value?.filter((e: Delta_Model<any, any, any>) => ${detectDelta.types.generic.errors.typeGuard.name}(e)) as ${detectDelta.types.generic.errors.type.name}<
      any,
      any,
      any
    >[]
    if (errors && errors.length > 0) {
      result[key] = errors
    }
  }
  return result
}

export function ${allModels.summarizeErrors.name}(errors: Record<string, ${detectDelta.types.generic.errors.type.name}<any, any, any>[]>): string {
  let modelsWithErrors = 0
  let totalErrors = 0
  const modelCounts: string[] = []

  for (const [key, value] of Object.entries(errors)) {
    modelsWithErrors++
    totalErrors += value.length
    modelCounts.push(\`\${key}: \${format(value.length)} \${pluralize('error', value.length)}\`)
  }
  return (
    red(
      \`\${format(totalErrors)} \${pluralize('error', totalErrors)} in \${modelsWithErrors} \${pluralize('model', modelsWithErrors)} detected in delta!\`,
    ) +
    \`
\${modelCounts.join('\\n')}
Details:
\${JSON.stringify(errors, null, 2)}
\${red('Please fix the errors and try importing again!')}\`
  )
}
`;
}
function prepareModelDeltaTypes({ context, imports }) {
    const deltaTypes = [];
    const resultAssignments = [];
    const idMapTypes = [];
    const keyMapTypes = [];
    const prepareImportMapsAssignments = [];
    const prepareImportMapsIdMapResult = [];
    const prepareImportMapsKeyMapResult = [];
    const ensureItemsHaveIdsFn = context.import.detectDelta._utils.ensureItemsHaveIds.name;
    for (const model of context.models.values()) {
        imports
            //
            .addType(model.types)
            .addType(model.types.id)
            .add(model.import.delta.errors)
            .add(model.import.delta.detect);
        const keyFieldName = model.keyField.name;
        const dataEntry = model.decoder.dataModelEntry;
        deltaTypes.push(`${dataEntry}?: ${context.import.detectDelta.types.generic.modelType.name}<
        ${model.types.name},
        ${model.types.id.name},
        ${model.import.delta.errors.name}
        >[]`);
        idMapTypes.push(`'${dataEntry}': Map<${model.types.id.name}, ${model.types.name}>`);
        // KeyMap uses the keyField for lookups (supports string or number keys)
        keyMapTypes.push(`'${dataEntry}': Map<string | number, ${model.types.name}>`);
        // Generate the prepareImportMaps call for this model
        // For models with NoRepository, we can't look up by key from database - just generate new IDs
        if (model.repository.kind === 'NoRepository') {
            prepareImportMapsAssignments.push(`const ${dataEntry}Result = await ${ensureItemsHaveIdsFn}({
    items: data.${dataEntry},
    keyFieldName: '${keyFieldName}',
    getByKey: async () => null, // NoRepository - can't look up from database
    generateId: () => randomUUID() as ${model.types.id.name},
  })`);
        }
        else {
            prepareImportMapsAssignments.push(`const ${dataEntry}Result = await ${ensureItemsHaveIdsFn}({
    items: data.${dataEntry},
    keyFieldName: '${keyFieldName}',
    getByKey: (key) => dataService.${model.repository.dataServiceVariable}.getByKey(key),
    generateId: () => randomUUID() as ${model.types.id.name},
  })`);
        }
        prepareImportMapsIdMapResult.push(`${dataEntry}: ${dataEntry}Result.idMap`);
        prepareImportMapsKeyMapResult.push(`${dataEntry}: ${dataEntry}Result.keyMap`);
        resultAssignments.push(`${dataEntry}: await ${model.import.delta.detect.name}(dataService, data.${dataEntry} ?? [], idMap, keyMap)`);
    }
    return {
        resultAssignments,
        deltaTypes,
        idMapTypes,
        keyMapTypes,
        prepareImportMapsAssignments,
        prepareImportMapsIdMapResult,
        prepareImportMapsKeyMapResult,
    };
}
//# sourceMappingURL=detect-delta-all-models.generator.js.map