module JSONAPI
  class ResponseDocument
    def initialize(operation_results, options = {})
      @operation_results = operation_results
      @options = options

      @key_formatter = @options.fetch(:key_formatter, JSONAPI.configuration.key_formatter)
    end

    def contents
      hash = results_to_hash

      meta = top_level_meta
      hash.merge!(meta: meta) unless meta.empty?

      links = top_level_links
      hash.merge!(links: links) unless links.empty?

      hash
    end

    def status
      if @operation_results.has_errors?
        @operation_results.all_errors[0].status
      else
        @operation_results.results[0].code
      end
    end

    private

    def serializer
      @serializer ||= @options.fetch(:resource_serializer_klass, JSONAPI::ResourceSerializer).new(
        @options.fetch(:primary_resource_klass),
        include_directives: @options[:include_directives],
        fields: @options[:fields],
        base_url: @options.fetch(:base_url, ''),
        key_formatter: @key_formatter,
        route_formatter: @options.fetch(:route_formatter, JSONAPI.configuration.route_formatter),
        scope_id: @options[:scope_id]
      )
    end

    # Rolls up the top level meta data from the base_meta, the set of operations,
    # and the result of each operation. The keys are then formatted.
    def top_level_meta
      meta = @options.fetch(:base_meta, {})

      meta.merge!(@operation_results.meta)

      @operation_results.results.each do |result|
        meta.merge!(result.meta)

        if JSONAPI.configuration.top_level_meta_include_record_count && result.respond_to?(:record_count)
          meta[JSONAPI.configuration.top_level_meta_record_count_key] = result.record_count
        end
      end

      meta.deep_transform_keys { |key| @key_formatter.format(key) }
    end

    # Rolls up the top level links from the base_links, the set of operations,
    # and the result of each operation. The keys are then formatted.
    def top_level_links
      links = @options.fetch(:base_links, {})

      links.merge!(@operation_results.links)

      @operation_results.results.each do |result|
        links.merge!(result.links)

        # Build pagination links
        if result.is_a?(JSONAPI::ResourcesOperationResult)
          result.pagination_params.each_pair do |link_name, params|
            query_params = {}
            query_params[:page] = params

            request = @options[:request]
            query_params[:fields] = request.params[:fields] if request.params[:fields]
            query_params[:include] = request.params[:include] if request.params[:include]
            query_params[:sort] = request.params[:sort] if request.params[:sort]
            query_params[:filter] = request.params[:filter] if request.params[:filter]

            links[link_name] = serializer.find_link(query_params)
          end
        end
      end

      links.deep_transform_keys { |key| @key_formatter.format(key) }
    end

    def results_to_hash
      if @operation_results.has_errors?
        { errors: @operation_results.all_errors }
      else
        if @operation_results.results.length == 1
          result = @operation_results.results[0]

          case result
          when JSONAPI::ResourceOperationResult
            serializer.serialize_to_hash(result.resource)
          when JSONAPI::ResourcesOperationResult
            serializer.serialize_to_hash(result.resources)
          when JSONAPI::LinksObjectOperationResult
            serializer.serialize_to_links_hash(result.parent_resource,
                                               result.association)
          when JSONAPI::OperationResult
            {}
          end

        elsif @operation_results.results.length > 1
          resources = []
          @operation_results.results.each do |result|
            case result
            when JSONAPI::ResourceOperationResult
              resources.push(result.resource)
            when JSONAPI::ResourcesOperationResult
              resources.concat(result.resources)
            end
          end

          serializer.serialize_to_hash(resources)
        end
      end
    end
  end
end