"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.generateDetectDeltaFunctions = generateDetectDeltaFunctions;
const Generator = __importStar(require("@postxl/generator"));
function generateDetectDeltaFunctions(context) {
    const { import: { detectDelta }, } = context;
    const dto = context.types.dto;
    const generic = detectDelta.types.generic;
    const { create, update, errors, unchanged, delta_Fields, modelType } = generic;
    const imports = Generator.ImportGenerator
        //
        .from(detectDelta._utils.getDelta.location)
        .addType(dto.genericModel)
        .addType(dto.idType)
        .addType(dto.update)
        .addType(delta_Fields)
        .addType(modelType)
        .addType(errors.invalidReference.type)
        .addType(errors.invalidType.type)
        .addType(errors.missingField.type)
        .addType(errors.nonUnique.type)
        .addType(errors.isRequiredDependency.type);
    return /* ts */ `
import { ExhaustiveSwitchCheck } from '@postxl/utils'
${imports.generate()}

type Delta_Result<Model extends GenericModel<ID>, ID extends IdType> =
  | Delta_Result_Create<Model, ID>
  | Delta_Result_Update<Model, ID>
  | Delta_Result_Delete<Model, ID>
  | { type: '${unchanged.discriminant}' }

type Delta_Result_Create<Model extends GenericModel<ID>, ID extends IdType> = { type: '${create.discriminant}'; newItem: Model }
type Delta_Result_Update<Model extends GenericModel<ID>, ID extends IdType> = {
  type: '${update.discriminant}'
  delta: ${delta_Fields.name}<Model, ID>
  existingItem: Model
  updatedItem: Model
}
type Delta_Result_Delete<Model extends GenericModel<ID>, ID extends IdType> = { type: '${generic.delete.discriminant}'; existingItem: Model }

export type ${detectDelta._utils.validation.params.name}<Model extends ${dto.genericModel.name}<ID>, ID extends IdType, ModelErrors> = {
  create: (params: Omit<Delta_Result_Create<Model, ID>, 'type'>) => Promise<ModelErrors[]>
  update: (params: Omit<Delta_Result_Update<Model, ID>, 'type' | 'delta'>) => Promise<ModelErrors[]>
  delete: (params: Omit<Delta_Result_Delete<Model, ID>, 'type'>) => Promise<ModelErrors[]>
}

/**
 * Detects changes for a model and returns the result as a Delta_Model object.
 * When both id and keyField are provided, id takes precedence for looking up existing records.
 * Falls back to keyField when id is not provided.
 *
 * IMPORTANT: For correct delta detection, relation field keys must be resolved to IDs
 * BEFORE calling this function. Use the resolveRelationKeys callback to do this.
 * Otherwise, the delta calculation will incorrectly show changes for relation fields
 * when comparing keys against IDs.
 */
export async function ${detectDelta._utils.detectModelDelta.name}<Model extends ${dto.genericModel.name}<ID>, ID extends ${dto.idType.name}, ModelErrors, KeyFieldName extends keyof Model>({
  items,
  keyFieldName,
  getById,
  getByKey,
  getDelta,
  resolveRelationKeys,
  validate
}: {
  items?: Model[]
  /** The name of the field used as the key to identify records */
  keyFieldName: KeyFieldName
  /** Function to look up an existing record by its id value (takes precedence over keyField) */
  getById: (id: ID) => Promise<Model | null>
  /** Function to look up an existing record by its key value (fallback when id is not provided) */
  getByKey: (key: Model[KeyFieldName]) => Promise<Model | null>
  getDelta: (args: { item: Model; existingItem: Model }) => ${delta_Fields.name}<Model, ID>
  /**
   * Function to resolve relation field keys to IDs in the item.
   * This MUST be called before delta calculation to ensure correct comparison.
   * Returns the resolved item and any errors encountered during resolution.
   */
  resolveRelationKeys: (item: Model) => Promise<{ item: Model; errors: ModelErrors[] }>
  validate: ${detectDelta._utils.validation.params.name}<Model, ID, ModelErrors>
}): Promise<${modelType.name}<Model, ID, ModelErrors>[]> {
  const upsertResult: ${modelType.name}<Model, ID, ModelErrors>[] = []

  if (items === undefined || items.length === 0) {
    return upsertResult
  }

  for (const item of items) {
    // IMPORTANT: Resolve relation field keys to IDs BEFORE delta calculation.
    // This ensures that when we compare the item against existing records,
    // relation fields contain actual IDs (not keys), enabling correct delta detection.
    const { item: resolvedItem, errors: resolutionErrors } = await resolveRelationKeys(item)

    // If key resolution failed, report errors and skip this item
    if (resolutionErrors.length > 0) {
      upsertResult.push({ type: 'errors', input: item, errors: resolutionErrors })
      continue
    }

    const deltaResult = await determineDeltaResult({ item: resolvedItem, keyFieldName, getById, getByKey, getDelta })
    const errors = deltaResult.type === 'unchanged' ? [] : await validate[deltaResult.type](deltaResult as any)

    if (errors.length !== 0) {
      upsertResult.push({ type: 'errors', input: item, errors })
      continue
    }
    switch (deltaResult.type) {
      case '${create.discriminant}':
        upsertResult.push({ type: '${create.discriminant}', input: item, createDto: resolvedItem })
        break

      case '${update.discriminant}':
        upsertResult.push({
          type: '${update.discriminant}',
          input: item,
          delta: deltaResult.delta,
          updateDto: deltaToUpdateDtoFunction({ id: deltaResult.existingItem.id, delta: deltaResult.delta }),
        })
        break

      case '${generic.delete.discriminant}':
        upsertResult.push({ type: 'errors', input: item, errors })
        break

      case '${unchanged.discriminant}': {
        upsertResult.push({ type: '${unchanged.discriminant}', input: item })
        break
      }

      default:
        throw new ExhaustiveSwitchCheck(deltaResult)
    }
  }

  return upsertResult
}

/**
 * Generic function to determine what action (create/update/delete/unchanged) to take with an item.
 * For models with a non-id keyField (e.g. slug), keyField lookup is preferred and item.id is
 * synchronized to the matched existing entity.
 * For id-keyed models, id lookup is used as primary strategy.
 *
 * In case the type is update it also includes the delta for a given item. In case of delete, it also includes
 * the existing item.
 *
 * **Important**: The function does not implement any logic to automatically determine if an item should be deleted.
 * Implement this logic based on the business requirements!
 */
async function determineDeltaResult<Model extends ${dto.genericModel.name}<ID>, ID extends ${dto.idType.name}, KeyFieldName extends keyof Model>({ // NOSONAR
  item,
  keyFieldName,
  getById,
  getByKey,
  getDelta,
}: {
  item: Model
  keyFieldName: KeyFieldName
  getById: (id: ID) => Promise<Model | null>
  getByKey: (key: Model[KeyFieldName]) => Promise<Model | null>
  getDelta: ({ item, existingItem }: { item: Model; existingItem: Model }) => ${delta_Fields.name}<Model, ID>
}): Promise<Delta_Result<Model, ID>> {
  const keyValue = normalizeKeyValue(item[keyFieldName])
  const isIdKeyField = keyFieldName === ('id' as KeyFieldName)

  // For non-id key fields (e.g. slug), prefer key lookup to avoid stale/generated IDs
  // forcing false create behavior.
  if (!isIdKeyField && keyValue !== undefined && keyValue !== null && keyValue !== '') {
    const existingItemByKey = await findExistingByKeyValue({ keyValue, getByKey })
    if (existingItemByKey) {
      item.id = existingItemByKey.id
      const delta = getDelta({ item, existingItem: existingItemByKey })
      if (Object.keys(delta).length > 0) {
        return { type: '${update.discriminant}', delta, existingItem: existingItemByKey, updatedItem: item }
      }
      return { type: '${unchanged.discriminant}' }
    }
  }

  // Check if id is provided - this is the primary lookup for id-keyed models
  // and the fallback for non-id key models.
  const idValue = item.id
  if (idValue !== undefined && idValue !== null && idValue !== '') {
    const existingItemById = await getById(idValue)
    if (existingItemById) {
      const delta = getDelta({ item, existingItem: existingItemById })
      if (Object.keys(delta).length > 0) {
        return { type: '${update.discriminant}', delta, existingItem: existingItemById, updatedItem: item }
      }
      return { type: '${unchanged.discriminant}' }
    }
    // If id is provided but not found, fall back to keyField lookup.
    // This prevents false creates when imported files contain stale/generated IDs
    // but a stable keyField (e.g. slug) still matches an existing record.
    if (keyValue !== undefined && keyValue !== null && keyValue !== '') {
      const existingItemByKey = await findExistingByKeyValue({ keyValue, getByKey })
      if (existingItemByKey) {
        item.id = existingItemByKey.id
        const delta = getDelta({ item, existingItem: existingItemByKey })
        if (Object.keys(delta).length > 0) {
          return { type: '${update.discriminant}', delta, existingItem: existingItemByKey, updatedItem: item }
        }
        return { type: '${unchanged.discriminant}' }
      }
    }
    // No item found by id or keyField: treat as create (id might be pre-assigned).
    return { type: '${create.discriminant}', newItem: item }
  }

  // Fall back to keyField lookup when id is not provided

  // If the key field value is also missing or empty, treat as a new record to create
  if (keyValue === undefined || keyValue === null || keyValue === '') {
    return { type: '${create.discriminant}', newItem: item }
  }

  // Look up existing record by key field value
  const existingItem = await findExistingByKeyValue({ keyValue, getByKey })

  if (!existingItem) {
    return { type: '${create.discriminant}', newItem: item }
  }
  item.id = existingItem.id

  const delta = getDelta({ item, existingItem })

  if (Object.keys(delta).length > 0) {
    return { type: '${update.discriminant}', delta, existingItem, updatedItem: item }
  }

  // We do not have a default logic to identify deletions. Implement it here, e.g. using a
  // custom "Action" field in the Excel table. If it is set to "Delete", we can handle the delete here.
  // return {type: '${generic.delete.discriminant}', existingItem}

  // If we reach this point, we assume the item has not changed
  return { type: '${unchanged.discriminant}' }
}

export function ${detectDelta._utils.getDelta.name}<Model extends ${dto.genericModel.name}<ID>, ID extends ${dto.idType.name}>({
  item,
  existingItem,
  properties,
}: {
  item: Model
  existingItem: Model
  properties: (keyof Omit<Model, 'id'>)[]
}): ${delta_Fields.name}<Model, ID> {
  const delta: ${delta_Fields.name}<Model, ID> = {}

  for (const property of properties) {
    addDelta({ delta, existingItem, item, property })
  }

  return delta
}

/**
 * Adds a delta entry for a property to the given delta object if the property
 * of the item is different from the existing item
 */
function addDelta<Model extends ${dto.genericModel.name}<ID>, ID extends ${dto.idType.name}>({
  delta,
  existingItem,
  item,
  property,
}: {
  delta: ${delta_Fields.name}<Model, ID>
  existingItem: Model
  item: Model
  property: keyof Omit<Model, 'id'>
}): void {
  // createdAt is system-managed and should not trigger user-facing update deltas during import preview.
  if (property === 'createdAt') {
    return
  }
  if (areValuesEqual(existingItem[property], item[property])) {
    return
  }
  delta[property] = { old: existingItem[property], new: item[property] }
}

function areValuesEqual(left: unknown, right: unknown): boolean {
  if (left === right) {
    return true
  }

  if (left === null || left === undefined || right === null || right === undefined) {
    return false
  }

  if (left instanceof Date && right instanceof Date) {
    return left.getTime() === right.getTime()
  }

  if (Array.isArray(left) || Array.isArray(right)) {
    if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
      return false
    }
    return left.every((item, index) => areValuesEqual(item, right[index]))
  }

  if (isPlainObject(left) || isPlainObject(right)) {
    if (!isPlainObject(left) || !isPlainObject(right)) {
      return false
    }
    const leftKeys = Object.keys(left)
    const rightKeys = Object.keys(right)
    if (leftKeys.length !== rightKeys.length) {
      return false
    }
    return leftKeys.every((key) => areValuesEqual(left[key], right[key]))
  }

  return false
}

function isPlainObject(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null && !Array.isArray(value)
}

/**
 * Converts a Delta to an UpdateDto.
 */
function deltaToUpdateDtoFunction<Model extends ${dto.genericModel.name}<ID>, ID extends ${dto.idType.name}>({
  id,
  delta,
}: {
  id: ID
  delta: ${delta_Fields.name}<Model, ID>
}): ${dto.update.name}<Model, ID> {
  const updateDto = {
    id: id,
  } as UpdateDto<Model, ID>

  for (const [key, value] of Object.entries(delta)) {
    const updatedValue = value.new as Model[keyof Omit<Model, 'id'>]
    updateDto[key as keyof Omit<Model, 'id'>] = updatedValue
  }
  return updateDto
}

/**
 * Helper function that removes undefined values from an array.
 */
export function ${detectDelta._utils.keepErrors.name}<O, I extends O | undefined>(errors: I[]): O[] {
  return errors.filter((error) => error !== undefined) as O[]
}

/**
 * Checks if the item needs an ID assigned
 */
function needsIdAssignment<ID extends ${dto.idType.name}>(id: ID | undefined): boolean {
  return !id || id === ('' as unknown as ID)
}

/**
 * Extracts and validates the key value from an item
 */
function getValidKeyValue<Model, KeyFieldName extends keyof Model>(
  item: Model,
  keyFieldName: KeyFieldName,
): string | number | null {
  const keyValue = normalizeKeyValue(item[keyFieldName]) as string | number | undefined | null

  if (keyValue === undefined || keyValue === null || keyValue === '') {
    return null
  }

  return keyValue
}

function normalizeKeyValue<Key>(value: Key): Key {
  if (typeof value === 'string') {
    return value.trim() as Key
  }
  return value
}

/**
 * Assigns an ID to an item by either finding existing entity or generating new ID
 */
async function assignIdToItem<Model extends ${dto.genericModel.name}<ID>, ID extends ${dto.idType.name}>({
  item,
  keyValue,
  getByKey,
  generateId,
}: {
  item: Model
  keyValue: string | number | null
  getByKey: (key: string) => Promise<Model | null>
  generateId: () => ID
}): Promise<void> {
  // If no valid key, generate new ID
  if (keyValue === null) {
    item.id = generateId()
    return
  }

  // Try to find existing entity by key
  const existingItem = await findByKeyWithNormalization({ keyValue, getByKey })
  item.id = existingItem ? existingItem.id : generateId()
}

async function findByKeyWithNormalization<Model>({
  keyValue,
  getByKey,
}: {
  keyValue: string | number
  getByKey: (key: string) => Promise<Model | null>
}): Promise<Model | null> {
  for (const key of buildKeyCandidates(keyValue)) {
    const existingItem = await getByKey(key)
    if (existingItem) {
      return existingItem
    }
  }
  return null
}

async function findExistingByKeyValue<Model, Key>({
  keyValue,
  getByKey,
}: {
  keyValue: Key
  getByKey: (key: Key) => Promise<Model | null>
}): Promise<Model | null> {
  if (typeof keyValue === 'string') {
    for (const key of buildKeyCandidates(keyValue)) {
      const existingItem = await getByKey(key as Key)
      if (existingItem) {
        return existingItem
      }
    }
    return null
  }

  return getByKey(keyValue)
}

function buildKeyCandidates(keyValue: string | number): string[] {
  const value = String(keyValue)
  const candidates = [
    value,
    value.trim(),
    value.trim().normalize('NFKC'),
    value.trim().normalize('NFKC').replaceAll(/[\\u200B-\\u200D\\uFEFF]/g, ''),
    value.trim().normalize('NFKC').replaceAll(/[\\u200B-\\u200D\\uFEFF]/g, '').toLowerCase(),
  ]
  return [...new Set(candidates.filter((candidate) => candidate !== ''))]
}

/**
 * Adds an item to the ID and key lookup maps
 */
function addItemToMaps<
  Model extends ${dto.genericModel.name}<ID>,
  ID extends ${dto.idType.name},
  KeyFieldName extends keyof Model,
>({
  item,
  keyFieldName,
  idMap,
  keyMap,
}: {
  item: Model
  keyFieldName: KeyFieldName
  idMap: Map<ID, Model>
  keyMap: Map<string | number, Model>
}): void {
  idMap.set(item.id, item)

  const keyValue = getValidKeyValue(item, keyFieldName)
  if (keyValue !== null) {
    keyMap.set(keyValue, item)
  }
}

/**
 * Ensures all items have IDs assigned.
 * For items without an ID (undefined, null, or empty string):
 * 1. If keyField value is provided, check database for existing entity with that key -> use its ID
 * 2. Otherwise, generate a new UUID
 *
 * This must be called BEFORE building idMap/keyMap to ensure all items have proper IDs
 * that can be referenced by other models during relation key resolution.
 *
 * @returns Object containing:
 *   - items: The items with IDs assigned
 *   - idMap: Map of ID -> item (for items with IDs)
 *   - keyMap: Map of keyField -> item (for all items with keyField values)
 */
export async function ${detectDelta._utils.ensureItemsHaveIds.name}<
  Model extends ${dto.genericModel.name}<ID>,
  ID extends ${dto.idType.name},
  KeyFieldName extends keyof Model,
>({
  items,
  keyFieldName,
  getByKey,
  generateId,
}: {
  items?: Model[]
  /** The name of the keyField for this model */
  keyFieldName: KeyFieldName
  /** Function to look up an existing entity by keyField value from the database */
  getByKey: (key: string) => Promise<Model | null>
  /** Function to generate a new ID (e.g., crypto.randomUUID() with brand) */
  generateId: () => ID
}): Promise<{
  items: Model[]
  idMap: Map<ID, Model>
  keyMap: Map<string | number, Model>
}> {
  const idMap = new Map<ID, Model>()
  const keyMap = new Map<string | number, Model>()

  if (!items || items.length === 0) {
    return { items: [], idMap, keyMap }
  }

  for (const item of items) {
    const keyValue = getValidKeyValue(item, keyFieldName)
    const isIdKeyField = keyFieldName === ('id' as KeyFieldName)

    // For non-id key fields (e.g. slug), always reconcile ID from key lookup.
    // This prevents stale/random IDs from forcing incorrect create behavior.
    if ((!isIdKeyField && keyValue !== null) || needsIdAssignment(item.id)) {
      // For non-id key fields always reconcile from key; otherwise only when ID is missing.
      await assignIdToItem({ item, keyValue, getByKey, generateId })
    }

    // Add item to lookup maps
    addItemToMaps({ item, keyFieldName, idMap, keyMap })
  }

  return { items, idMap, keyMap }
}

/**
 * Validates that the model has all required fields.
 */
export function ${detectDelta._utils.validation.requiredFields.name}<
  Model extends ${dto.genericModel.name}<ID>,
  ID extends ${dto.idType.name},
  FieldName extends Extract<keyof Omit<Model, 'id'>, string>
>({
  item,
  fieldNames,
}: {
  item: Model
  fieldNames: FieldName[]
}): ${errors.missingField.type.name}<FieldName>[] {
  const errors: ${errors.missingField.type.name}<FieldName>[] = []
  for (const fieldName of fieldNames) {
    if (item[fieldName] === undefined) {
      errors.push({ error: '${errors.missingField.discriminant}', fieldName })
    }
  }
  if (errors.length > 0) {
    return errors
  } else {
    return []
  }
}

/**
 * Resolves a relation field value (which could be an ID or a key) to the actual ID.
 * This is used before delta calculation to ensure relation fields contain IDs, not keys.
 *
 * Resolution order:
 * 1. Check idMap (if value is already an ID in the current import batch)
 * 2. Check keyMap (if value is a keyField value from imported data)
 * 3. Database lookup by ID
 * 4. Database lookup by keyField
 *
 * @returns The resolved ID if found, or an error if the reference is invalid
 */
export async function ${detectDelta._utils.resolveRelationKey.name}<
  ID extends ${dto.idType.name},
  RelatedModel extends ${dto.genericModel.name}<ID>,
  FieldName extends string,
  RelatedModelName extends string,
  KeyType extends string | number = string | number,
>({
  value,
  fieldName,
  relatedModel,
  idMapEntry,
  keyMapEntry,
  getById,
  getByKey,
}: {
  /** The value to resolve - could be an ID or a keyField value */
  value: ID
  /** The name of the field being resolved (for error reporting) */
  fieldName: FieldName
  /** The name of the related model (for error reporting) */
  relatedModel: RelatedModelName
  /** Map of IDs to items from the current import batch */
  idMapEntry: Map<ID, RelatedModel>
  /** Map of keyField values to items from the current import batch */
  keyMapEntry: Map<KeyType, RelatedModel>
  /** Function to look up an item by ID from the database */
  getById: (id: ID) => Promise<RelatedModel | null>
  /** Function to look up an item by keyField from the database */
  getByKey: (key: string) => Promise<RelatedModel | null>
}): Promise<{ id: ID } | { error: ${errors.invalidReference.type.name}<RelatedModelName, FieldName, ID> }> {
  // 1. Try idMap (if value is an id from the current import batch)
  const byId = idMapEntry.get(value)
  if (byId) {
    return { id: byId.id }
  }

  // 2. Try keyMap (if value is a keyField value from imported data)
  const byKey = keyMapEntry.get(value as unknown as KeyType)
  if (byKey) {
    return { id: byKey.id }
  }

  // 3. Try database lookup by ID
  const fromDbById = await getById(value)
  if (fromDbById) {
    return { id: fromDbById.id }
  }

  // 4. Try database lookup by keyField
  const fromDbByKey = await getByKey(String(value))
  if (fromDbByKey) {
    return { id: fromDbByKey.id }
  }

  // Not found - return error
  return { error: { error: '${errors.invalidReference.discriminant}', relatedModel, fieldName, value } }
}

/**
 * Validates that the field is a reference to an existing item.
 * If the value is a key (not an ID), it resolves the key to the actual ID and updates the item.
 * This allows importing Excel data where relation fields contain human-readable keys instead of UUIDs.
 */
export async function ${detectDelta._utils.validation.referenceField.name}<
  Model extends ${dto.genericModel.name}<ID>,
  ID extends ${dto.idType.name},
  RelatedModel extends ${dto.genericModel.name}<RelatedID>,
  RelatedID extends ${dto.idType.name},
  FieldName extends keyof Omit<Model, 'id'> & string,
  Value extends RelatedID,
  RelatedModelName extends string,
>({
  item,
  fieldName,
  relatedModel,
  getExistingItem,
}: {
  item: Partial<Model>
  fieldName: FieldName
  relatedModel: RelatedModelName
  /**
   * Function to get an existing item by ID or key.
   * Should try ID lookup first, then key lookup.
   * Returns the item if found, null otherwise.
   */
  getExistingItem: (idOrKey: RelatedID) => Promise<RelatedModel | null>
}): Promise<${errors.invalidReference.type.name}<RelatedModelName, FieldName, Value> | undefined> {
  const value: Value | undefined | null = item[fieldName] as Value | undefined | null
  if (value === undefined || value === null) {
    return undefined
  }
  const existingItem = await getExistingItem(value)
  if (!existingItem) {
    return { error: '${errors.invalidReference.discriminant}', relatedModel, fieldName, value }
  }
  // If the existing item was found and its ID differs from the provided value,
  // it means the value was a key (not an ID). Replace the key with the actual ID.
  // This ensures the item has the correct ID for database operations.
  if (existingItem.id !== value) {
    (item as Record<string, unknown>)[fieldName] = existingItem.id
  }
  return undefined
}

/**
 * Validates that the field value is unique.
 */
export async function ${detectDelta._utils.validation.uniqueField.name}<
  Model extends ${dto.genericModel.name}<ID>,
  ID extends ${dto.idType.name},
  FieldName extends keyof Omit<Model, 'id'> & string,
  Value extends Model[FieldName],
>({
  item,
  fieldName,
  getExistingItem,
}: {
  item: Partial<Model>
  fieldName: FieldName
  getExistingItem: (fieldValue: Value) => Promise<Model | undefined>
}): Promise<${errors.nonUnique.type.name}<FieldName, Value> | undefined> {
  const value: Value | undefined = item[fieldName] as Value | undefined
  if (value === undefined) {
    return undefined
  }
  const existingItem = await getExistingItem(value)
  if (existingItem && existingItem.id !== item.id) {
    return { error: '${errors.nonUnique.discriminant}', fieldName, value }
  }
  return undefined
}

/**
 * Validates that the field value is an integer.
 */
export function ${detectDelta._utils.validation.int.name}<
  Model extends ${dto.genericModel.name}<ID>,
  ID extends ${dto.idType.name},
  FieldName extends keyof Omit<Model, 'id'> & string,
>({
  item,
  fieldName,
}: {
  item: Model
  fieldName: FieldName
}): ${errors.invalidType.type.name}<FieldName, 'Int', number> | undefined {
  const value: number | undefined | null = item[fieldName] as number | undefined | null
  //we handle undefined and null in other value checks so we can ignore them here
  if (value === undefined || value === null) {
    return undefined
  }

  if (value !== Math.floor(value)) {
    return { error: '${errors.invalidType.discriminant}', fieldName, typeExpected: 'Int', value }
  }
  return undefined
}

/**
 * Validates that the field is a reference to an existing item.
 */
export async function ${detectDelta._utils.validation.requiredDependency.name}<
  Model extends ${dto.genericModel.name}<ID>,
  ID extends ${dto.idType.name},
  RelatedModelName extends string,
  RelatedField extends string,
  RelatedModel extends ${dto.genericModel.name}<RelatedID>,
  RelatedID extends ${dto.idType.name},
>({
  item,
  relatedModel,
  relatedField,
  getReferencedItems,
}: {
  item: Model
  relatedModel: RelatedModelName
  relatedField: RelatedField
  getReferencedItems: (id: ID) => Promise<RelatedModel[]>
}): Promise<
  ${errors.isRequiredDependency.type.name}<RelatedModelName, RelatedField, RelatedModel, RelatedID> | undefined
> {
  const relatedItems = await getReferencedItems(item.id)
  if (relatedItems.length > 0) {
    return undefined
  }
  return { error: '${errors.isRequiredDependency.discriminant}', relatedModel, relatedField, relatedItems }
}`;
}
//# sourceMappingURL=detect-delta-functions.generator.js.map