# frozen_string_literal: true

module HTTPX
  module Plugins
    #
    # This plugin adds support for HTTP/2 Push responses.
    #
    # In order to benefit from this, requests are sent one at a time, so that
    # no push responses are received after corresponding request has been sent.
    #
    # https://gitlab.com/os85/httpx/wikis/Server-Push
    #
    module PushPromise
      def self.extra_options(options)
        options.merge(http2_settings: { settings_enable_push: 1 },
                      max_concurrent_requests: 1)
      end

      module ResponseMethods
        def pushed?
          @__pushed
        end

        def mark_as_pushed!
          @__pushed = true
        end
      end

      module InstanceMethods
        private

        def promise_headers
          @promise_headers ||= {}
        end

        def on_promise(parser, stream)
          stream.on(:promise_headers) do |h|
            __on_promise_request(parser, stream, h)
          end
          stream.on(:headers) do |h|
            __on_promise_response(parser, stream, h)
          end
        end

        def __on_promise_request(parser, stream, h)
          log(level: 1, color: :yellow) do
            # :nocov:
            h.map { |k, v| "#{stream.id}: -> PROMISE HEADER: #{k}: #{v}" }.join("\n")
            # :nocov:
          end
          headers = @options.headers_class.new(h)
          path = headers[":path"]
          authority = headers[":authority"]

          request = parser.pending.find { |r| r.authority == authority && r.path == path }
          if request
            request.merge_headers(headers)
            promise_headers[stream] = request
            parser.pending.delete(request)
            parser.streams[request] = stream
            request.transition(:done)
          else
            stream.refuse
          end
        end

        def __on_promise_response(parser, stream, h)
          request = promise_headers.delete(stream)
          return unless request

          parser.__send__(:on_stream_headers, stream, request, h)
          response = request.response
          response.mark_as_pushed!
          stream.on(:data, &parser.method(:on_stream_data).curry(3)[stream, request])
          stream.on(:close, &parser.method(:on_stream_close).curry(3)[stream, request])
        end
      end
    end
    register_plugin(:push_promise, PushPromise)
  end
end
