class ReeObject::DeepDup

def call(obj, **opts)

def call(obj, **opts)
  recursively_dup(obj, opts, {})
end

def dup_array(array, opts, cache)

def dup_array(array, opts, cache)
  dup = array.map { recursively_dup(_1, opts, cache) }
  dup_singleton_methods(array, dup)
  opts[:freeze] ? dup.freeze : dup
end

def dup_hash(hash, opts, cache)

def dup_hash(hash, opts, cache)
  dup = {}
  dup.default = recursively_dup(hash.default, opts, cache)
  dup.default_proc = hash.default_proc if hash.default_proc
  hash.each do |k, v|
    dup[recursively_dup(k, opts, cache)] = recursively_dup(v, opts, cache)
  end
  dup_singleton_methods(hash, dup)
  
  opts[:freeze] ? dup.freeze : dup
end

def dup_object(obj, opts, cache)

def dup_object(obj, opts, cache)
  return obj if obj.is_a?(Class) || obj.is_a?(Module)
  return cache[obj.object_id] if cache.key?(obj.object_id)
  dup = obj.dup
  cache[obj.object_id] = dup
  dup.instance_variables.each do |var|
    dup.instance_variable_set(
      var, recursively_dup(
        dup.instance_variable_get(var), opts, cache
      )
    )
  end
  dup_singleton_methods(obj, dup)
  opts[:freeze] ? dup.freeze : dup
end

def dup_singleton_methods(source, target)

def dup_singleton_methods(source, target)
  return if source.singleton_methods.empty?
  source.singleton_methods.each do |method_name|
    target.define_singleton_method(
      method_name, &source.method(method_name)
    )
  end
end

def recursively_dup(obj, opts, cache)

def recursively_dup(obj, opts, cache)
  if obj.is_a?(Array)
    dup_array(obj, opts, cache)
  elsif obj.is_a?(Hash)
    dup_hash(obj, opts, cache)
  else
    dup_object(obj, opts, cache)
  end
end