# frozen_string_literal: true

module Aws
  module Record
    class ItemCollection
      include Enumerable

      def initialize(search_method, search_params, model, client)
        @search_method = search_method
        @search_params = search_params
        @model_filter = @search_params.delete(:model_filter)
        @model = model
        @client = client
      end

      # Provides an enumeration of the results of a query or scan operation on
      # your table, automatically converted into item classes.
      #
      # WARNING: This will enumerate over your entire partition in the case of
      # query, and over your entire table in the case of scan, save for key and
      # filter expressions used. This means that enumerable operations that
      # iterate over the full result set could make many network calls, or use a
      # lot of memory to build response objects. Use with caution.
      #
      # @return [Enumerable<Aws::Record>] an enumeration over the results of
      #   your query or scan request. These results are automatically converted
      #   into items on your behalf.
      def each(&block)
        return enum_for(:each) unless block_given?

        items.each_page do |page|
          @last_evaluated_key = page.last_evaluated_key
          items_array = _build_items_from_response(page.items, @model)
          items_array.each(&block)
        end
      end

      # Provides the first "page" of responses from your query operation. This
      # will only make a single client call, and will provide the items, if any
      # exist, from that response. It will not attempt to follow up on
      # pagination tokens, so this is not guaranteed to include all items that
      # match your search.
      #
      # @return [Array<Aws::Record>] an array of the record items found in the
      #   first page of responses from the query or scan call.
      def page
        search_response = items
        @last_evaluated_key = search_response.last_evaluated_key
        _build_items_from_response(search_response.items, @model)
      end

      # Provides the pagination key most recently used by the underlying client.
      # This can be useful in the case where you're exposing pagination to an
      # outside caller, and want to be able to "resume" your scan in a new call
      # without starting over.
      #
      # @return [Hash] a hash representing an attribute key/value pair, suitable
      #   for use as the +exclusive_start_key+ in another query or scan
      #   operation. If there are no more pages in the result, will be nil.
      def last_evaluated_key
        @last_evaluated_key
      end

      # Checks if the query/scan result is completely blank.
      #
      # WARNING: This can and will query your entire partition, or scan your
      # entire table, if no results are found. Especially if your table is
      # large, use this with extreme caution.
      #
      # @return [Boolean] true if the query/scan result is empty, false
      #   otherwise.
      def empty?
        items.each_page do |page|
          return false unless page.items.empty?
        end
        true
      end

      private

      def _build_items_from_response(items, model)
        ret = []
        items.each do |item|
          model_class = @model_filter ? @model_filter.call(item) : model
          next unless model_class

          record = model_class.new
          data = record.instance_variable_get('@data')
          model_class.attributes.attributes.each do |name, attr|
            data.set_attribute(name, attr.extract(item))
          end
          data.clean!
          data.new_record = false
          ret << record
        end
        ret
      end

      def items
        @items ||= @client.send(@search_method, @search_params)
      end
    end
  end
end
