package iroh

import (
	
	
	
	
	
	
	
)

// ProtocolHandler handles connections accepted for a single ALPN
// (Application-Layer Protocol Negotiation) value. A [Router] dispatches each
// incoming connection to the handler registered for its negotiated ALPN.
//
// Accept is called in its own goroutine for every accepted connection; it should
// run for the lifetime of the connection and return when done. A returned error
// is logged. A handler must not panic; a panic is recovered and logged, closing
// only that connection while the router continues accepting. It is the Go
// analog of the Rust ProtocolHandler trait (iroh/src/protocol.rs:228).
type ProtocolHandler interface {
	// Accept handles an accepted connection. ctx is cancelled when the router
	// shuts down.
	Accept(ctx context.Context, conn *Conn) error
}

// ProtocolHandlerFunc adapts a function to [ProtocolHandler].
type ProtocolHandlerFunc func(ctx context.Context, conn *Conn) error

// Accept calls f(ctx, conn).
func ( ProtocolHandlerFunc) ( context.Context,  *Conn) error {
	return (, )
}

// AcceptingHandler is an optional interface a [ProtocolHandler] may implement
// to intercept an incoming connection before it is converted to a verified
// [Conn]. The default behavior is [Accepting.Connection].
type AcceptingHandler interface {
	OnAccepting(ctx context.Context, accepting *Accepting) (*Conn, error)
}

// ShutdownHandler is an optional interface a [ProtocolHandler] may implement to
// run cleanup when its [Router] shuts down. The router calls Shutdown on every
// registered handler that implements it before closing the endpoint, giving
// handlers a chance to close connections gracefully. It mirrors the Rust
// ProtocolHandler::shutdown hook (iroh/src/protocol.rs:284).
type ShutdownHandler interface {
	// Shutdown is called once when the router is shutting down.
	Shutdown(ctx context.Context)
}

// IncomingFilterOutcome is the decision an [IncomingFilter] returns for an
// incoming connection. It mirrors the Rust IncomingFilterOutcome
// (iroh/src/protocol.rs).
type IncomingFilterOutcome int

const (
	// FilterAccept accepts the connection and dispatches it to a handler.
	FilterAccept IncomingFilterOutcome = iota
	// FilterRetry asks the peer to retry. Router evaluates this outcome before
	// qng constructs a connection, so it emits a real QUIC Retry packet.
	FilterRetry
	// FilterReject refuses the connection.
	FilterReject
	// FilterIgnore closes the incoming connection without dispatching it.
	FilterIgnore
)

// IncomingFilter decides whether to accept each incoming connection. Router
// evaluates FilterRetry at QUIC Initial admission time, before ALPN negotiation;
// other outcomes are evaluated in the accept loop after qng has an early
// connection. It mirrors the Rust IncomingFilter (iroh/src/protocol.rs).
type IncomingFilter func(*Incoming) IncomingFilterOutcome

// RouterConfig configures a [Router].
type RouterConfig struct {
	// IncomingFilter is consulted for each incoming connection.
	IncomingFilter IncomingFilter
	// Logger records handler errors and recovered panics. If nil,
	// [slog.Default] is used.
	Logger *slog.Logger
}

// NewRouter registers every handler ALPN on ep, starts the accept loop, and
// returns the running router. The endpoint must not already be listening (do not
// pass [WithALPNs] to [Bind] when using a Router).
//
// The handlers map is keyed by exact ALPN string. ALPN values are opaque byte
// strings represented as Go strings; printable ASCII protocol names are
// conventional, but binary values compare byte-for-byte. NewRouter copies the
// map before returning.
func ( *Endpoint,  map[string]ProtocolHandler,  *RouterConfig) (*Router, error) {
	if  := .acquireAcceptOwner(acceptOwnerRouter);  != nil {
		return nil, 
	}
	 := true
	defer func() {
		if  {
			.releaseAcceptOwner(acceptOwnerRouter)
		}
	}()

	 = maps.Clone()
	 := slog.Default()
	 := IncomingFilter(nil)
	if  != nil {
		 = .IncomingFilter
		 = .Logger
	}
	if  == nil {
		 = slog.Default()
	}

	 := make([]string, 0, len())
	for  := range  {
		 = append(, )
	}
	 := .sourceAddressValidation()
	if  != nil {
		.setSourceAddressValidation(func( net.Addr) bool {
			if  != nil && () {
				return true
			}
			return (&Incoming{ep: , remote: }) == FilterRetry
		})
	}
	if  := .setALPNs(, acceptOwnerRouter);  != nil {
		.setSourceAddressValidation()
		return nil, fmt.Errorf("iroh: new router: %w", )
	}
	for ,  := range  {
		if ,  := .(streamListenerHandler);  {
			.l.addr = net.UDPAddrFromAddrPort(.LocalAddr())
		}
	}

	,  := context.WithCancel(context.Background())
	 := &Router{
		ep:       ,
		handlers: ,
		filter:   ,
		restoreSourceValidation: func() {
			.setSourceAddressValidation()
		},
		logger: ,
		cancel: ,
		ctx:    ,
	}
	.wg.Add(1)
	go .acceptLoop()
	 = false
	return , nil
}

// Router accepts incoming connections on an [Endpoint] and dispatches each to
// the [ProtocolHandler] registered for its negotiated ALPN. Start one with
// [NewRouter]; stop it with [Router.Shutdown]. It is the Go analog of the Rust
// Router (iroh/src/protocol.rs:97).
//
// Dispatch is by exact ALPN string. One goroutine runs the accept loop; each
// accepted connection is handled in a child goroutine with a context derived
// from the router's. A panic in a handler goroutine is recovered, logged, and
// stops the accept loop.
type Router struct {
	ep                      *Endpoint
	handlers                map[string]ProtocolHandler
	filter                  IncomingFilter
	restoreSourceValidation func()
	logger                  *slog.Logger

	ctx    context.Context
	cancel context.CancelFunc
	wg     sync.WaitGroup

	mu       sync.Mutex
	shutdown bool
}

// Endpoint returns the endpoint the router accepts on.
func ( *Router) () *Endpoint { return .ep }

// IsShutdown reports whether the router has been shut down.
func ( *Router) () bool {
	.mu.Lock()
	defer .mu.Unlock()
	return .shutdown
}

// acceptLoop accepts connections until ctx is cancelled or the endpoint closes.
// Each connection is dispatched in a child goroutine.
func ( *Router) ( context.Context) {
	defer .wg.Done()

	for {
		select {
		case <-.Done():
			return
		default:
		}

		,  := .ep.acceptIncoming()
		if  != nil {
			// A cancelled context or a closed endpoint ends the loop cleanly.
			if .Err() != nil || errors.Is(, ErrEndpointClosed) {
				return
			}
			// A failed accept (e.g. a peer aborting the handshake) is logged and
			// the loop continues. The endpoint surfaces a hard close via the
			// checks above.
			.logger.Warn("router: accept failed", "err", )
			continue
		}

		if .filter != nil {
			switch .filter() {
			case FilterAccept:
			case FilterRetry:
				.Ignore()
				continue
			case FilterReject:
				.Refuse()
				continue
			case FilterIgnore:
				.Ignore()
				continue
			}
		}

		,  := .Accept()
		if  != nil {
			.logger.Warn("router: incoming accept failed", "err", )
			continue
		}

		.wg.Add(1)
		go func( *Accepting) {
			defer .wg.Done()
			var  string
			defer func() {
				if  := recover();  != nil {
					.qc.CloseWithError(0, "handler panic")
					.logger.Error("router: handler panicked", "alpn", , "panic", )
				}
			}()

			var  error
			,  = .ALPN()
			if  != nil {
				if !handlerShutdownErr(, ) {
					.logger.Warn("router: accepting ALPN failed", "err", )
				}
				return
			}
			,  := .handlers[]
			if ! {
				.logger.Warn("router: no handler for ALPN", "alpn", )
				.qc.CloseWithError(0, "unsupported ALPN")
				return
			}
			var  *Conn
			if ,  := .(AcceptingHandler);  {
				,  = .OnAccepting(, )
			} else {
				,  = .Connection()
			}
			if  != nil {
				.logger.Warn("router: on accepting failed", "alpn", , "err", )
				return
			}
			if  := .Accept(, );  != nil && !handlerShutdownErr(, ) {
				.logger.Warn("router: handler returned error", "alpn", .ALPN(), "err", )
			}
		}()
	}
}

func handlerShutdownErr( context.Context,  error) bool {
	if .Err() != nil && errors.Is(, .Err()) {
		return true
	}
	return errors.Is(, net.ErrClosed)
}

// Shutdown stops the router: it cancels the accept loop and all handler
// contexts, calls Shutdown on every registered handler that implements
// [ShutdownHandler], closes the endpoint, and waits for the accept loop and
// handler goroutines to finish or ctx to be done. It is idempotent and the Go
// analog of the Rust Router::shutdown (iroh/src/protocol.rs:429).
func ( *Router) ( context.Context) error {
	.mu.Lock()
	if .shutdown {
		.mu.Unlock()
		return nil
	}
	.shutdown = true
	.mu.Unlock()
	defer .ep.releaseAcceptOwner(acceptOwnerRouter)

	if .restoreSourceValidation != nil {
		.restoreSourceValidation()
	}

	// Stop accepting and cancel handler contexts.
	.cancel()

	// Give handlers a chance to close connections gracefully before the endpoint
	// force-closes them. Rust awaits all protocol shutdown futures
	// concurrently; do the same so one slow protocol does not block the rest.
	var  sync.WaitGroup
	for ,  := range .handlers {
		if ,  := .(ShutdownHandler);  {
			.Add(1)
			go func( ShutdownHandler) {
				defer .Done()
				.Shutdown()
			}()
		}
	}
	 := make(chan struct{})
	go func() {
		.Wait()
		close()
	}()
	select {
	case <-:
	case <-.Done():
		return .Err()
	}

	 := .ep.Shutdown()

	// Wait for the accept loop and handler goroutines, bounded by ctx.
	 := make(chan struct{})
	go func() {
		.wg.Wait()
		close()
	}()
	select {
	case <-:
	case <-.Done():
		return .Err()
	}
	return 
}