lib/ree_lib/packages/ree_object/package/ree_object/functions/to_obj.rb



# frozen_string_literal: true


class ReeObject::ToObj
  include Ree::FnDSL

  fn :to_obj do
    link :except, from: :ree_hash
    link :slice, from: :ree_hash
    link :to_hash, import: -> { BASIC_TYPES }
    link 'ree_hash/contracts/hash_keys_contract', -> { HashKeysContract }
  end

  contract(
    Any,
    Ksplat[
      include?: HashKeysContract,
      exclude?: HashKeysContract,
      global_exclude?: ArrayOf[Symbol]
    ] => Or[Object, ArrayOf[Object], *BASIC_TYPES]
  ).throws(ArgumentError)
  def call(obj, **opts)
    dump = to_hash(obj)

    options = prepare_options(opts)

    if opts[:include]
      dump = slice(dump, options[:include])
    end

    if opts[:exclude]
      dump = except(dump, options[:exclude])
    end

    if opts[:global_exclude]
      dump = except(dump, global_except: options[:global_exclude])
    end

    ancestors = dump.class.ancestors
    return dump if ancestors.intersection(BASIC_TYPES).size > 0

    if dump.is_a?(Array)
      build_array(dump)
    else
      recursively_assign(Object.new, dump)
    end
  end

  private

  def build_array(array)
    array.map do |value|
      if value.is_a?(Array)
        build_array(value)
      elsif value.is_a?(Hash)
        recursively_assign(Object.new, value)
      else
        value
      end
    end
  end

  def recursively_assign(obj, hash)
    hash.each do |key, value|
      var = :"@#{key}"

      obj.define_singleton_method key do
        instance_variable_get(var)
      end

      if value.is_a?(Array)
        obj.instance_variable_set(var, build_array(value))
      elsif value.is_a?(Hash)
        obj.instance_variable_set(var, recursively_assign(Object.new, value))
      else
        obj.instance_variable_set(var, value)
      end
    end

    obj
  end

  def prepare_options(opts)
    if opts[:include] && (opts[:exclude] || opts[:global_exclude])
      intersection = opts_keys(opts[:include]).intersection(opts_keys(opts[:exclude] || opts[:global_exclude]))

      if intersection.length > 0
        raise ArgumentError, "Exclude and include have the same values: #{intersection}"
      end
    end

    opts
  end

  def opts_keys(arr)
    arr.reject { |e| e.is_a?(Hash) }
  end
end