import { mergeDeepWith } from 'immutable'
import type { ArgReturnType, ExtractData } from './types'

function mergeCallback<T>(_oldVal: unknown, newVal: T): T {
  return newVal
}

function evaluateInput<T>(input: T): ArgReturnType<T> {
  return typeof input === 'function' ? input() : input
}

export function mergeData<Model, Data extends ExtractData<Model> = ExtractData<Model>>(
  baseInput: Data,
  input: Partial<Data>,
) {
  const evaluatedBaseInput = { ...baseInput } // Create a copy
  const evaluatedInput = { ...input } // Create a copy

  const newKeys = new Set(Object.keys(input))
  const newKeysToEvalute = []

  for (const key in baseInput) {
    if (newKeys.has(key)) {
      newKeysToEvalute.push(key)
      continue
    }

    evaluatedBaseInput[key] = evaluateInput(evaluatedBaseInput[key])
  }

  for (const key of newKeysToEvalute) {
    evaluatedInput[key] = evaluateInput(evaluatedInput[key])
  }

  return mergeDeepWith(mergeCallback, evaluatedBaseInput, evaluatedInput)
}

export function buildIncludes<Model>(
  data: ExtractData<Model>,
): Record<string, boolean> | undefined {
  const include: Record<string, any> = {}

  for (const key in data) {
    const value = data[key]
    const isObject = typeof value === 'object' && value !== null
    const hasRelation =
      isObject &&
      (Object.prototype.hasOwnProperty.call(value, 'create') ||
        Object.prototype.hasOwnProperty.call(value, 'connect') ||
        Object.prototype.hasOwnProperty.call(value, 'connectOrCreate'))

    if (hasRelation) include[key] = true
  }

  const hasInclude = Object.keys(include).length
  return hasInclude ? include : undefined
}
