module ActiveRecord
  module Associations
    class Preloader
      class CountLoader < SingularAssociation
        def association_key_name
          reflection.foreign_key
        end

        def owner_key_name
          reflection.active_record_primary_key
        end

        private

        def key_conversion_required?
          # Are you sure this is always false? But this method is required to map result for polymorphic association.
          false
        end

        def preload(preloader)
          associated_records_by_owner(preloader).each do |owner, associated_records|
            owner.association(reflection.name).target = associated_records.first.to_i
          end
        end

        def records_for(ids)
          key = model.reflections[reflection.name.to_s.sub(/_count\z/, '')].foreign_key
          scope.reorder(nil).where(key => ids).group(key).count(key)
        end

        def build_scope
          super.tap do |scope|
            has_many_reflection = model.reflections[reflection.name.to_s.sub(/_count\z/, '')]
            if has_many_reflection.options[:as]
              scope.where!(klass.table_name => { has_many_reflection.type => model.base_class.sti_name })
            end
          end
        end

        if ActiveRecord.version.segments.first >= 5
          def load_records
            return {} if owner_keys.empty?

            slices  = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
            @preloaded_records = slices.flat_map { |slice|
              records_for(slice)
            }

            Hash[@preloaded_records.first.map { |key, count| [key, [count]] }]
          end
        else
          def load_slices(slices)
            @preloaded_records = slices.flat_map { |slice|
              records_for(slice)
            }

            @preloaded_records.first.map { |key, count|
              [count, key]
            }
          end
        end
      end
    end
  end

  module Precount
    module PreloaderExtension
      def preloader_for(reflection, owners, rhs_klass)
        preloader = super
        return preloader if preloader

        case reflection.macro
        when :count_loader
          Associations::Preloader::CountLoader
        end
      end
    end
  end

  Associations::Preloader.prepend(Precount::PreloaderExtension)
end
