# frozen_string_literal: true
module ActiveAdmin
  module DisplayHelper
    DISPLAY_NAME_FALLBACK = -> {
      klass = self.class
      name = if klass.respond_to?(:model_name)
               if klass.respond_to?(:primary_key)
                 "#{klass.model_name.human} ##{send(klass.primary_key)}"
               else
                 klass.model_name.human
               end
             elsif klass.respond_to?(:primary_key)
               " ##{send(klass.primary_key)}"
             end
      name.present? ? name : to_s
    }

    def DISPLAY_NAME_FALLBACK.inspect
      "DISPLAY_NAME_FALLBACK"
    end

    # Attempts to call any known display name methods on the resource.
    # See the setting in `application.rb` for the list of methods and their priority.
    def display_name(resource)
      unless resource.nil?
        result = render_in_context(resource, display_name_method_for(resource))
        if result.to_s.strip.present?
          ERB::Util.html_escape(result)
        else
          ERB::Util.html_escape(render_in_context(resource, DISPLAY_NAME_FALLBACK))
        end
      end
    end

    def format_attribute(resource, attr)
      value = find_value resource, attr

      if value.is_a?(Arbre::Element)
        value
      elsif boolean_attr?(resource, attr, value)
        Arbre::Context.new { status_tag value }
      else
        pretty_format value
      end
    end

    # Attempts to create a human-readable string for any object
    def pretty_format(object)
      case object
      when String, Numeric, Symbol, Arbre::Element
        object.to_s
      when Date, Time
        I18n.localize object, format: active_admin_application.localize_format
      when Array
        format_collection(object)
      else
        if defined?(::ActiveRecord) && object.is_a?(ActiveRecord::Base) ||
            defined?(::Mongoid) && object.class.include?(Mongoid::Document)
          auto_link object
        elsif defined?(::ActiveRecord) && object.is_a?(ActiveRecord::Relation)
          format_collection(object)
        else
          display_name object
        end
      end
    end

    private

    # Looks up and caches the first available display name method.
    # To prevent conflicts, we exclude any methods that happen to be associations.
    # If no methods are available and we're about to use the Kernel's `to_s`, provide our own.
    def display_name_method_for(resource)
      @@display_name_methods_cache ||= {}
      @@display_name_methods_cache[resource.class] ||= begin
        methods = active_admin_application.display_name_methods - association_methods_for(resource)
        method = methods.detect { |method| resource.respond_to? method }

        if method != :to_s || resource.method(method).source_location
          method
        else
          DISPLAY_NAME_FALLBACK
        end
      end
    end

    def association_methods_for(resource)
      return [] unless resource.class.respond_to? :reflect_on_all_associations
      resource.class.reflect_on_all_associations.map(&:name)
    end

    def find_value(resource, attr)
      if attr.is_a? Proc
        attr.call resource
      elsif resource.respond_to? attr
        resource.public_send attr
      elsif resource.respond_to? :[]
        resource[attr]
      end
    end

    def format_collection(collection)
      safe_join(collection.map { |item| pretty_format(item) }, ", ")
    end

    def boolean_attr?(resource, attr, value)
      case value
      when TrueClass, FalseClass
        true
      else
        if resource.class.respond_to? :attribute_types
          resource.class.attribute_types[attr.to_s].is_a?(ActiveModel::Type::Boolean)
        end
      end
    end
  end
end
