package webtransport

import (
	
	
	
	

	
)

type unestablishedSession struct {
	Streams    []*quic.Stream
	UniStreams []*quic.ReceiveStream

	Timer *time.Timer
}

type sessionEntry struct {
	// at any point in time, only one of these will be non-nil
	Unestablished *unestablishedSession
	Session       *Session
}

const maxRecentlyClosedSessions = 16

type sessionManager struct {
	timeout time.Duration

	mx                     sync.Mutex
	sessions               map[sessionID]sessionEntry
	recentlyClosedSessions []sessionID
}

func newSessionManager( time.Duration) *sessionManager {
	return &sessionManager{
		timeout:  ,
		sessions: make(map[sessionID]sessionEntry),
	}
}

// AddStream adds a new bidirectional stream to a WebTransport session.
// If the WebTransport session has not yet been established,
// the stream is buffered until the session is established.
// If that takes longer than timeout, the stream is reset.
func ( *sessionManager) ( *quic.Stream,  sessionID) {
	.mx.Lock()
	defer .mx.Unlock()

	,  := .sessions[]
	if ! {
		// Receiving a stream for an unknown session is expected to be rare,
		// so the performance impact of searching through the slice is negligible.
		if slices.Contains(.recentlyClosedSessions, ) {
			.CancelRead(WTBufferedStreamRejectedErrorCode)
			.CancelWrite(WTBufferedStreamRejectedErrorCode)
			return
		}
		 = sessionEntry{Unestablished: &unestablishedSession{}}
		.sessions[] = 
	}
	if .Session != nil {
		.Session.addIncomingStream()
		return
	}

	.Unestablished.Streams = append(.Unestablished.Streams, )
	.resetTimer()
}

// AddUniStream adds a new unidirectional stream to a WebTransport session.
// If the WebTransport session has not yet been established,
// the stream is buffered until the session is established.
// If that takes longer than timeout, the stream is reset.
func ( *sessionManager) ( *quic.ReceiveStream,  sessionID) {
	.mx.Lock()
	defer .mx.Unlock()

	,  := .sessions[]
	if ! {
		// Receiving a stream for an unknown session is expected to be rare,
		// so the performance impact of searching through the slice is negligible.
		if slices.Contains(.recentlyClosedSessions, ) {
			.CancelRead(WTBufferedStreamRejectedErrorCode)
			return
		}
		 = sessionEntry{Unestablished: &unestablishedSession{}}
		.sessions[] = 
	}
	if .Session != nil {
		.Session.addIncomingUniStream()
		return
	}

	.Unestablished.UniStreams = append(.Unestablished.UniStreams, )
	.resetTimer()
}

func ( *sessionManager) ( sessionID) {
	 := .sessions[]
	if .Unestablished.Timer != nil {
		.Unestablished.Timer.Reset(.timeout)
		return
	}
	.Unestablished.Timer = time.AfterFunc(.timeout, func() { .onTimer() })
}

func ( *sessionManager) ( sessionID) {
	.mx.Lock()
	defer .mx.Unlock()

	,  := .sessions[]
	if ! { // session already closed
		return
	}
	if .Session != nil { // session already established
		return
	}
	for ,  := range .Unestablished.Streams {
		.CancelRead(WTBufferedStreamRejectedErrorCode)
		.CancelWrite(WTBufferedStreamRejectedErrorCode)
	}
	for ,  := range .Unestablished.UniStreams {
		.CancelRead(WTBufferedStreamRejectedErrorCode)
	}
	delete(.sessions, )
}

// AddSession adds a new WebTransport session.
func ( *sessionManager) ( sessionID,  *Session) {
	.mx.Lock()
	defer .mx.Unlock()

	,  := .sessions[]

	if  && .Unestablished != nil {
		// We might already have an entry of this session.
		// This can happen when we receive streams for this WebTransport session before we complete
		// the Extended CONNECT request.
		for ,  := range .Unestablished.Streams {
			.addIncomingStream()
		}
		for ,  := range .Unestablished.UniStreams {
			.addIncomingUniStream()
		}
		if .Unestablished.Timer != nil {
			.Unestablished.Timer.Stop()
		}
		.Unestablished = nil
	}
	.sessions[] = sessionEntry{Session: }

	context.AfterFunc(.Context(), func() {
		.deleteSession()
	})
}

func ( *sessionManager) ( sessionID) {
	.mx.Lock()
	defer .mx.Unlock()

	delete(.sessions, )
	.recentlyClosedSessions = append(.recentlyClosedSessions, )
	if len(.recentlyClosedSessions) > maxRecentlyClosedSessions {
		.recentlyClosedSessions = .recentlyClosedSessions[1:]
	}
}

func ( *sessionManager) () {
	.mx.Lock()
	defer .mx.Unlock()

	for ,  := range .sessions {
		if .Unestablished != nil && .Unestablished.Timer != nil {
			.Unestablished.Timer.Stop()
		}
	}
	clear(.sessions)
}