package quic

import (
	
	
	

	
	
	
	
)

type connRunnerCallbacks struct {
	AddConnectionID    func(protocol.ConnectionID)
	RemoveConnectionID func(protocol.ConnectionID)
	ReplaceWithClosed  func([]protocol.ConnectionID, []byte, time.Duration)
}

// The memory address of the Transport is used as the key.
type connRunners map[connRunner]connRunnerCallbacks

func ( connRunners) ( protocol.ConnectionID) {
	for ,  := range  {
		.AddConnectionID()
	}
}

func ( connRunners) ( protocol.ConnectionID) {
	for ,  := range  {
		.RemoveConnectionID()
	}
}

func ( connRunners) ( []protocol.ConnectionID,  []byte,  time.Duration) {
	for ,  := range  {
		.ReplaceWithClosed(, , )
	}
}

type connIDToRetire struct {
	t      monotime.Time
	connID protocol.ConnectionID
}

type connIDGenerator struct {
	generator   ConnectionIDGenerator
	highestSeq  uint64
	connRunners connRunners

	activeSrcConnIDs        map[uint64]protocol.ConnectionID
	connIDsToRetire         []connIDToRetire       // sorted by t
	initialClientDestConnID *protocol.ConnectionID // nil for the client

	// pathSrcConnIDs / pathHighestSeq hold the connection IDs we (the issuer)
	// emit for QUIC multipath (draft-ietf-quic-multipath) paths via
	// PATH_NEW_CONNECTION_ID (0x3e78). They are keyed by protocol.PathID and are
	// STRICTLY SEPARATE from activeSrcConnIDs/highestSeq, which serve the single
	// connection-level CID sequence space (PathID 0). Each multipath PathID owns
	// an independent CID sequence number space (frame.rs:2005-2012 IssuedCid is
	// scoped to its path_id). Lazily initialized: no entry exists, and no frame
	// is emitted, unless multipath is negotiated and a non-zero path is opened.
	pathSrcConnIDs map[protocol.PathID]map[uint64]protocol.ConnectionID
	pathHighestSeq map[protocol.PathID]uint64

	statelessResetter *statelessResetter

	queueControlFrame func(wire.Frame)
}

func newConnIDGenerator(
	 connRunner,
	 protocol.ConnectionID,
	 *protocol.ConnectionID, // nil for the client
	 *statelessResetter,
	 connRunnerCallbacks,
	 func(wire.Frame),
	 ConnectionIDGenerator,
) *connIDGenerator {
	 := &connIDGenerator{
		generator:         ,
		activeSrcConnIDs:  make(map[uint64]protocol.ConnectionID),
		statelessResetter: ,
		connRunners:       map[connRunner]connRunnerCallbacks{: },
		queueControlFrame: ,
	}
	.activeSrcConnIDs[0] = 
	.initialClientDestConnID = 
	return 
}

func ( *connIDGenerator) ( uint64) error {
	if .generator.ConnectionIDLen() == 0 {
		return nil
	}
	// The active_connection_id_limit transport parameter is the number of
	// connection IDs the peer will store. This limit includes the connection ID
	// used during the handshake, and the one sent in the preferred_address
	// transport parameter.
	// We currently don't send the preferred_address transport parameter,
	// so we can issue (limit - 1) connection IDs.
	for  := uint64(len(.activeSrcConnIDs));  < min(, protocol.MaxIssuedConnectionIDs); ++ {
		if  := .issueNewConnID();  != nil {
			return 
		}
	}
	return nil
}

func ( *connIDGenerator) ( uint64,  protocol.ConnectionID,  monotime.Time) error {
	if  > .highestSeq {
		return &qerr.TransportError{
			ErrorCode:    qerr.ProtocolViolation,
			ErrorMessage: fmt.Sprintf("retired connection ID %d (highest issued: %d)", , .highestSeq),
		}
	}
	,  := .activeSrcConnIDs[]
	// We might already have deleted this connection ID, if this is a duplicate frame.
	if ! {
		return nil
	}
	if  ==  {
		return &qerr.TransportError{
			ErrorCode:    qerr.ProtocolViolation,
			ErrorMessage: fmt.Sprintf("retired connection ID %d (%s), which was used as the Destination Connection ID on this packet", , ),
		}
	}
	.queueConnIDForRetiring(, )

	delete(.activeSrcConnIDs, )
	// Don't issue a replacement for the initial connection ID.
	if  == 0 {
		return nil
	}
	return .issueNewConnID()
}

func ( *connIDGenerator) ( protocol.PathID,  uint64,  protocol.ConnectionID,  monotime.Time) error {
	if  == protocol.PathIDZero {
		return .Retire(, , )
	}
	,  := .pathHighestSeq[]
	if ! ||  >=  {
		 := uint64(0)
		if  &&  > 0 {
			 =  - 1
		}
		return &qerr.TransportError{
			ErrorCode:    qerr.ProtocolViolation,
			ErrorMessage: fmt.Sprintf("retired connection ID %d on path %d (highest issued: %d)", , , ),
		}
	}
	 := .pathSrcConnIDs[]
	,  := []
	if ! {
		return nil
	}
	if  ==  {
		return &qerr.TransportError{
			ErrorCode:    qerr.ProtocolViolation,
			ErrorMessage: fmt.Sprintf("retired connection ID %d on path %d (%s), which was used as the Destination Connection ID on this packet", , , ),
		}
	}
	.queueConnIDForRetiring(, )
	delete(, )
	if len() == 0 {
		delete(.pathSrcConnIDs, )
	}
	,  := .issuePathConnID()
	return 
}

func ( *connIDGenerator) ( protocol.ConnectionID,  monotime.Time) {
	 := slices.IndexFunc(.connIDsToRetire, func( connIDToRetire) bool {
		return .t.After()
	})
	if  == -1 {
		 = len(.connIDsToRetire)
	}
	.connIDsToRetire = slices.Insert(.connIDsToRetire, , connIDToRetire{t: , connID: })
}

func ( *connIDGenerator) () error {
	,  := .generator.GenerateConnectionID()
	if  != nil {
		return 
	}
	.activeSrcConnIDs[.highestSeq+1] = 
	.connRunners.AddConnectionID()
	.queueControlFrame(&wire.NewConnectionIDFrame{
		SequenceNumber:      .highestSeq + 1,
		ConnectionID:        ,
		StatelessResetToken: .statelessResetter.GetStatelessResetToken(),
	})
	.highestSeq++
	return nil
}

// issuePathConnID issues a connection ID for the QUIC multipath path pid and
// emits a PATH_NEW_CONNECTION_ID frame (0x3e78) advertising it. It mirrors
// issueNewConnID, but the frame carries pid (NewConnectionIDFrame.PathID) and
// the sequence number is drawn from pid's own per-path sequence space, not the
// connection-level highestSeq. This is the draft-multipath CID-issuance side; it
// must never be called for PathIDZero, whose CIDs flow through issueNewConnID.
//
// Reference: frame.rs:2015-2026 (NewConnectionId::encode writes path_id then
// sequence) and frame.rs:2005-2012 (IssuedCid scoped to path_id). The wire codec
// is new_connection_id_frame.go:84-90.
func ( *connIDGenerator) ( protocol.PathID) (protocol.ConnectionID, error) {
	if  == protocol.PathIDZero {
		return protocol.ConnectionID{}, fmt.Errorf("issuePathConnID called with PathIDZero")
	}
	if .generator.ConnectionIDLen() == 0 {
		// Zero-length connection IDs: nothing to issue, and the peer addresses
		// us by 4-tuple. Return the zero-length CID without emitting a frame.
		return protocol.ConnectionID{}, nil
	}
	,  := .generator.GenerateConnectionID()
	if  != nil {
		return protocol.ConnectionID{}, 
	}
	if .pathSrcConnIDs == nil {
		.pathSrcConnIDs = make(map[protocol.PathID]map[uint64]protocol.ConnectionID)
		.pathHighestSeq = make(map[protocol.PathID]uint64)
	}
	,  := .pathSrcConnIDs[]
	if ! {
		 = make(map[uint64]protocol.ConnectionID)
		.pathSrcConnIDs[] = 
	}
	 := .pathHighestSeq[]
	[] = 
	.pathHighestSeq[] =  + 1
	.connRunners.AddConnectionID()
	 := 
	.queueControlFrame(&wire.NewConnectionIDFrame{
		PathID:              &,
		SequenceNumber:      ,
		ConnectionID:        ,
		StatelessResetToken: .statelessResetter.GetStatelessResetToken(),
	})
	return , nil
}

// pathForLocalConnID reports which multipath PathID one of our issued source
// connection IDs belongs to. The PathIDZero (connection-level) CIDs live in
// activeSrcConnIDs; non-zero path CIDs live in pathSrcConnIDs (issuePathConnID).
// The receive side uses this to attribute an inbound 1-RTT packet (addressed to
// one of our CIDs) to the path it belongs to, so the packet is acked as a
// PATH_ACK{pid}. ok is false for a CID we never issued.
func ( *connIDGenerator) ( protocol.ConnectionID) (protocol.PathID, bool) {
	for ,  := range .activeSrcConnIDs {
		if  ==  {
			return protocol.PathIDZero, true
		}
	}
	if .initialClientDestConnID != nil && *.initialClientDestConnID ==  {
		return protocol.PathIDZero, true
	}
	for ,  := range .pathSrcConnIDs {
		for ,  := range  {
			if  ==  {
				return , true
			}
		}
	}
	return protocol.PathIDZero, false
}

func ( *connIDGenerator) ( monotime.Time) {
	if .initialClientDestConnID != nil {
		.queueConnIDForRetiring(*.initialClientDestConnID, )
		.initialClientDestConnID = nil
	}
}

func ( *connIDGenerator) ( monotime.Time) {
	if len(.connIDsToRetire) == 0 {
		return
	}
	for ,  := range .connIDsToRetire {
		if .t.After() {
			break
		}
		.connRunners.RemoveConnectionID(.connID)
		.connIDsToRetire = .connIDsToRetire[1:]
	}
}

func ( *connIDGenerator) () {
	if .initialClientDestConnID != nil {
		.connRunners.RemoveConnectionID(*.initialClientDestConnID)
	}
	for ,  := range .activeSrcConnIDs {
		.connRunners.RemoveConnectionID()
	}
	for ,  := range .pathSrcConnIDs {
		for ,  := range  {
			.connRunners.RemoveConnectionID()
		}
	}
	for ,  := range .connIDsToRetire {
		.connRunners.RemoveConnectionID(.connID)
	}
}

func ( *connIDGenerator) ( []byte,  time.Duration) {
	 := make([]protocol.ConnectionID, 0, len(.activeSrcConnIDs)+len(.connIDsToRetire)+1)
	if .initialClientDestConnID != nil {
		 = append(, *.initialClientDestConnID)
	}
	for ,  := range .activeSrcConnIDs {
		 = append(, )
	}
	for ,  := range .pathSrcConnIDs {
		for ,  := range  {
			 = append(, )
		}
	}
	for ,  := range .connIDsToRetire {
		 = append(, .connID)
	}
	.connRunners.ReplaceWithClosed(, , )
}

func ( *connIDGenerator) ( connRunner,  connRunnerCallbacks) {
	// The transport might have already been added earlier.
	// This happens if the application migrates back to and old path.
	if ,  := .connRunners[];  {
		return
	}
	.connRunners[] = 
	if .initialClientDestConnID != nil {
		.AddConnectionID(*.initialClientDestConnID)
	}
	for ,  := range .activeSrcConnIDs {
		.AddConnectionID()
	}
	for ,  := range .pathSrcConnIDs {
		for ,  := range  {
			.AddConnectionID()
		}
	}
}