# frozen_string_literal: true

# Including the `UnnestedInFilters::Dsl` module to an ActiveRecord
# model extends the interface of the following class instances to be
# able to use the `use_unnested_filters` method;
#
#   - Model relation;
#     `Model.where(...).use_unnested_filters`
#   - All the association proxies
#     `project.model_association.use_unnested_filters`
#   - All the relation instances of the association
#     `project.model_association.where(...).use_unnested_filters
#
# Note: The interface of the model itself won't be extended as we don't
# have a use-case for now(`Model.use_unnested_filters` won't work).
#
# Example usage of the API;
#
#   relation = Vulnerabilities::Read.where(state: [1, 4])
#                                   .use_unnested_filters
#                                   .order(severity: :desc, vulnerability_id: :desc)
#
#   relation.to_a # => Will load records by using the optimized query
#
# See `UnnestedInFilters::Rewriter` for the details about the optimizations applied.
#
# rubocop:disable Gitlab/ModuleWithInstanceVariables
module UnnestedInFilters
  module Dsl
    extend ActiveSupport::Concern

    MODULES_TO_EXTEND = [
      ActiveRecord::Relation,
      ActiveRecord::Associations::CollectionProxy,
      ActiveRecord::AssociationRelation
    ].freeze

    included do
      MODULES_TO_EXTEND.each do |mod|
        delegate_mod = relation_delegate_class(mod)
        delegate_mod.prepend(UnnestedInFilters::Dsl::Relation)
      end
    end

    module Relation
      def use_unnested_filters
        spawn.use_unnested_filters!
      end

      def use_unnested_filters!
        if Gitlab.next_rails?
          assert_modifiable!
        else
          assert_mutability!
        end

        @values[:unnested_filters] = true

        self
      end

      def use_unnested_filters?
        @values.fetch(:unnested_filters, false)
      end

      def load(*)
        return super if loaded? || !rewrite_query?

        @records = unnested_filter_rewriter.rewrite.to_a
        @loaded = true

        self
      end

      def exists?(*)
        return super unless rewrite_query?

        unnested_filter_rewriter.rewrite.exists?
      end

      private

      def rewrite_query?
        use_unnested_filters? && unnested_filter_rewriter.rewrite?
      end

      def unnested_filter_rewriter
        @unnested_filter_rewriter ||= UnnestedInFilters::Rewriter.new(self)
      end
    end
  end
end
