import { failure } from "../error/failure"
import { getGlobalConfig } from "../globalConfig"
import { isPlainObject, isPrimitive } from "../plainTypes/checks"
import { getNodeTypeAndKey, NodeKeyValue } from "./nodeTypeKey/nodeType"

/**
 * Deeply substitutes node keys in a data structure with new keys generated by a provided function.
 *
 * This function recursively traverses the input data structure and replaces all node keys with new ones
 * according to the provided key generator function. The function creates a deep clone of the original
 * data structure, ensuring no references to the original remain.
 *
 * Node keys are unique identifiers that are used internally to track and identify nodes in the data tree.
 * Substituting these keys is useful when cloning nodes to ensure each node has a unique identity.
 *
 * The function handles:
 * - Primitives: returned as-is
 * - Arrays: recursively processes each element
 * - Plain objects: identifies node keys using `getNodeTypeAndKey` and substitutes them
 *
 * @template T - The type of the input value
 * @param value - The value to process (primitive, array, or plain object)
 * @param newNodeKeyGenerator - Function to generate new node keys from old ones.
 *                              Defaults to the global key generator from configuration.
 * @returns A deep clone of the original value with all node keys substituted
 *
 * @throws Will throw an error if encountering a value type that isn't a primitive, array, or plain object
 */
export function substituteNodeKeys<T>(
  value: T,
  newNodeKeyGenerator: (oldKey: NodeKeyValue) => NodeKeyValue = getGlobalConfig().keyGenerator
): T {
  if (isPrimitive(value)) {
    return value
  }

  if (Array.isArray(value)) {
    return value.map((v) => substituteNodeKeys(v, newNodeKeyGenerator)) as T
  }

  if (isPlainObject(value)) {
    const typeAndKey = getNodeTypeAndKey(value)
    const keyProp = typeAndKey.type && "key" in typeAndKey.type ? typeAndKey.type.key : undefined

    const newValue: any = {}
    for (const key in value) {
      if (key === keyProp) {
        newValue[key] = newNodeKeyGenerator(value[key])
      } else {
        newValue[key] = substituteNodeKeys(value[key], newNodeKeyGenerator)
      }
    }
    return newValue
  }

  throw failure("unsupported value type")
}
