# frozen_string_literal: true

module Grape
  module Formatter
    module FastJsonapi
      class << self
        def call(object, env)
          return object if object.is_a?(String)
          return ::Grape::Json.dump(serialize(object, env)) if serializable?(object)
          return object.to_json if object.respond_to?(:to_json)
          ::Grape::Json.dump(object)
        end

        private

        def serializable?(object)
          return false if object.nil?

          object.respond_to?(:serializable_hash) || object.respond_to?(:to_a) && object.all? { |o| o.respond_to? :serializable_hash } || object.is_a?(Hash)
        end

        def serialize(object, env)
          if object.respond_to? :serializable_hash
            serializable_object(object, fast_jsonapi_options(env)).serializable_hash
          elsif object.respond_to?(:to_a) && object.all? { |o| o.respond_to? :serializable_hash }
            serializable_collection(object, fast_jsonapi_options(env))
          elsif object.is_a?(Hash)
            serialize_each_pair(object, env)
          else
            object
          end
        end

        def serializable_object(object, options)
          fast_jsonapi_serializable(object, options) || object
        end

        def fast_jsonapi_serializable(object, options)
          serializable_class(object, options)&.new(object, options)
        end

        def serializable_collection(collection, options)
         if heterogeneous_collection?(collection)
            collection.map do |o|
              fast_jsonapi_serializable(o, options).serializable_hash || o.map(&:serializable_hash)
            end
          else
            fast_jsonapi_serializable(collection, options)&.serializable_hash || collection.map(&:serializable_hash)
          end
        end

        def heterogeneous_collection?(collection)
          collection.map { |item| item.class.name }.uniq.size > 1
        end

        def serializable_class(object, options)
          klass_name = options['serializer'] || options[:serializer]
          klass_name ||= begin
            object = object.first if object.is_a?(Array)

            (object.try(:model_name) || object.class).name + 'Serializer'
          end

          klass_name&.safe_constantize
        end

        def serialize_each_pair(object, env)
          h = {}
          object.each_pair { |k, v| h[k] = serialize(v, env) }
          h
        end

        def fast_jsonapi_options(env)
          env['fast_jsonapi_options'] || {}
        end
      end
    end
  end
end
