# frozen_string_literal: true

module DiverDown
  module Helper
    CLASS_NAME_QUERY = Module.method(:name).unbind.freeze

    INSTANCE_CLASS_QUERY = Class.instance_method(:class).freeze
    private_constant :INSTANCE_CLASS_QUERY

    # @param obj [Object, String, Module, Class]
    # @return [String, nil] if obj is anonymous module, return nil
    def self.normalize_module_name(obj)
      if String === obj
        obj
      else
        mod = resolve_module(obj)

        # Do not call the original method as much as possible
        CLASS_NAME_QUERY.bind_call(mod) ||
          (mod.name if mod.respond_to?(:name))
      end
    end

    # @note The object passed as an argument may be a Proxied BasicObject.
    # For example, the DSL of FactoryBot's factory will add a new method in response to the invoked method name,
    # so passed as an argument methods are not called within this method.
    #
    # @return [Module, Class]
    def self.resolve_module(obj)
      if module?(obj) # Do not call method of this
        resolve_singleton_class(obj)
      else
        k = INSTANCE_CLASS_QUERY.bind_call(obj)
        resolve_singleton_class(k)
      end
    end

    # @param obj [Module, Class]
    # @return [Module, Class]
    def self.resolve_singleton_class(obj)
      if obj.singleton_class?
        obj.attached_object
      else
        obj
      end
    end

    # @param obj [Object]
    # @return [Boolean]
    def self.module?(obj)
      Module === obj
    end

    # @param obj [Object]
    # @return [Boolean]
    def self.class?(obj)
      Class === obj
    end

    # @param str [String]
    # @return [Module]
    def self.constantize(str)
      ::ActiveSupport::Inflector.constantize(str)
    end

    # @param hash [Hash]
    # @return [Hash]
    def self.deep_symbolize_keys(object)
      case object
      when Hash
        object.each_with_object({}) do |(key, value), memo|
          memo[key.to_sym] = deep_symbolize_keys(value)
        end
      when Array
        object.map { deep_symbolize_keys(_1) }
      else
        object
      end
    end
  end
end