package webtransport

import (
	
	
	
	
	
	
	
	
	

	
	
	

	
)

var errNoWebTransport = errors.New("server didn't enable WebTransport")

type Dialer struct {
	// TLSClientConfig is the TLS client config used when dialing the QUIC connection.
	// It must set the h3 ALPN.
	TLSClientConfig *tls.Config

	// QUICConfig is the QUIC config used when dialing the QUIC connection.
	QUICConfig *quic.Config

	// ApplicationProtocols is a list of application protocols that can be negotiated,
	// see section 3.3 of https://www.ietf.org/archive/id/draft-ietf-webtrans-http3-14 for details.
	ApplicationProtocols []string

	// StreamReorderingTime is the time an incoming WebTransport stream that cannot be associated
	// with a session is buffered.
	// This can happen if the response to a CONNECT request (that creates a new session) is reordered,
	// and arrives after the first WebTransport stream(s) for that session.
	// Defaults to 5 seconds.
	StreamReorderingTimeout time.Duration

	// DialAddr is the function used to dial the underlying QUIC connection.
	// If unset, quic.DialAddrEarly will be used.
	DialAddr func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error)

	ctx       context.Context
	ctxCancel context.CancelFunc

	initOnce sync.Once
}

func ( *Dialer) () {
	.ctx, .ctxCancel = context.WithCancel(context.Background())
}

func ( *Dialer) ( context.Context,  string,  http.Header) (*http.Response, *Session, error) {
	.initOnce.Do(func() { .init() })

	 := .QUICConfig
	if  == nil {
		 = &quic.Config{
			EnableDatagrams:                  true,
			EnableStreamResetPartialDelivery: true,
		}
	} else {
		if !.QUICConfig.EnableDatagrams {
			return nil, nil, errors.New("webtransport: DATAGRAM support required, enable it via QUICConfig.EnableDatagrams")
		}
		if !.QUICConfig.EnableStreamResetPartialDelivery {
			return nil, nil, errors.New("webtransport: stream reset partial delivery required, enable it via QUICConfig.EnableStreamResetPartialDelivery")
		}
	}

	 := .TLSClientConfig
	if  == nil {
		 = &tls.Config{}
	} else {
		 = .Clone()
	}
	if len(.NextProtos) == 0 {
		.NextProtos = []string{http3.NextProtoH3}
	}

	,  := url.Parse()
	if  != nil {
		return nil, nil, 
	}
	if  == nil {
		 = http.Header{}
	}
	if len(.ApplicationProtocols) > 0 && .Get(wtAvailableProtocolsHeader) == "" {
		 := httpsfv.List{}
		for ,  := range .ApplicationProtocols {
			 = append(, httpsfv.NewItem())
		}
		,  := httpsfv.Marshal()
		if  != nil {
			return nil, nil, fmt.Errorf("failed to marshal application protocols: %w", )
		}
		.Set(wtAvailableProtocolsHeader, )
	}

	 := &http.Request{
		Method: http.MethodConnect,
		Header: ,
		Proto:  "webtransport",
		Host:   .Host,
		URL:    ,
	}
	 = .WithContext()

	 := .DialAddr
	if  == nil {
		 = quic.DialAddrEarly
	}
	,  := (, .Host, , )
	if  != nil {
		return nil, nil, 
	}

	 := &http3.Transport{EnableDatagrams: true}
	, ,  := .handleConn(, , , )
	if  != nil {
		// TODO: use a more specific error code
		// see https://github.com/ietf-wg-webtrans/draft-ietf-webtrans-http3/issues/245
		.CloseWithError(quic.ApplicationErrorCode(http3.ErrCodeNoError), "")
		.Close()
		return , nil, 
	}
	context.AfterFunc(.Context(), func() {
		.CloseWithError(quic.ApplicationErrorCode(http3.ErrCodeNoError), "")
		.Close()
	})
	return , , nil
}

func ( *Dialer) ( context.Context,  *http3.Transport,  *quic.Conn,  *http.Request) (*http.Response, *Session, error) {
	 := .StreamReorderingTimeout
	if  == 0 {
		 = 5 * time.Second
	}
	 := newSessionManager()
	context.AfterFunc(.Context(), .Close)

	 := .NewRawClientConn()

	go func() {
		for {
			,  := .AcceptStream(context.Background())
			if  != nil {
				return
			}

			go func() {
				,  := quicvarint.Peek()
				if  != nil {
					return
				}
				if  != webTransportFrameType {
					.HandleBidirectionalStream()
					return
				}
				// read the frame type (already peeked above)
				if ,  := quicvarint.Read(quicvarint.NewReader());  != nil {
					return
				}
				// read the session ID
				,  := quicvarint.Read(quicvarint.NewReader())
				if  != nil {
					return
				}
				.AddStream(, sessionID())
			}()
		}
	}()

	go func() {
		for {
			,  := .AcceptUniStream(context.Background())
			if  != nil {
				return
			}

			go func() {
				,  := quicvarint.Peek()
				if  != nil {
					return
				}
				if  != webTransportUniStreamType {
					.HandleUnidirectionalStream()
					return
				}
				// read the stream type (already peeked above)
				if ,  := quicvarint.Read(quicvarint.NewReader());  != nil {
					return
				}
				// read the session ID
				,  := quicvarint.Read(quicvarint.NewReader())
				if  != nil {
					.CancelRead(quic.StreamErrorCode(http3.ErrCodeGeneralProtocolError))
					return
				}
				.AddUniStream(, sessionID())
			}()
		}
	}()

	select {
	case <-.ReceivedSettings():
	case <-.Done():
		return nil, nil, fmt.Errorf("error waiting for HTTP/3 settings: %w", context.Cause())
	case <-.ctx.Done():
		return nil, nil, context.Cause(.ctx)
	}
	 := .Settings()
	if !.EnableExtendedConnect {
		return nil, nil, errors.New("server didn't enable Extended CONNECT")
	}
	if !.EnableDatagrams {
		return nil, nil, errors.New("server didn't enable HTTP/3 datagram support")
	}
	if .Other == nil {
		return nil, nil, errNoWebTransport
	}
	,  := .Other[settingsEnableWebtransport]
	if ! ||  != 1 {
		return nil, nil, errNoWebTransport
	}

	,  := .OpenRequestStream()
	if  != nil {
		return nil, nil, 
	}
	if  := .SendRequestHeader();  != nil {
		return nil, nil, 
	}
	// TODO(#136): create the session to allow optimistic opening of streams and sending of datagrams
	,  := .ReadResponse()
	if  != nil {
		return nil, nil, 
	}
	if .StatusCode < 200 || .StatusCode >= 300 {
		return , nil, fmt.Errorf("received status %d", .StatusCode)
	}
	var  string
	if ,  := .Header[http.CanonicalHeaderKey(wtProtocolHeader)];  {
		 = .negotiateProtocol()
	}
	 := sessionID(.StreamID())
	 := newSession(context.WithoutCancel(), , , , )
	.AddSession(, )
	return , , nil
}

func ( *Dialer) ( []string) string {
	,  := httpsfv.UnmarshalItem()
	if  != nil {
		return ""
	}
	,  := .Value.(string)
	if ! {
		return ""
	}
	if !slices.Contains(.ApplicationProtocols, ) {
		return ""
	}
	return 
}

func ( *Dialer) () error {
	.ctxCancel()
	return nil
}