package socket

import (
	
	
	
	

	
	
)

// RemoteMap is the registry of per-remote [RemoteStateActor]s, keyed by
// [key.EndpointID]. It spawns an actor on first reference to a remote and
// removes it when the actor idles out. It is the Go analog of the Rust RemoteMap
// (iroh/src/socket/remote_map.rs).
//
// Actor insertion and idle-teardown deregistration are serialized by a single
// mutex, and an actor only removes itself if it is still the actor registered
// under its id. So an AddConnection arriving exactly as the 60s idle timeout
// fires yields exactly one actor: the teardown either runs first (the next
// reference spawns a fresh actor) or the AddConnection runs first (which resets
// the actor's idle timer, so it does not tear down). See [RemoteMap.ResolveRemote]
// / [RemoteMap.AddConnection] and the onExit closure in [RemoteMap.actor].
//
// RemoteMap is safe for concurrent use. Create one with [NewRemoteMap].
type RemoteMap struct {
	ctx      context.Context
	selector PathSelector
	resolve  ResolveFunc
	idle     time.Duration // actor idle timeout; ActorMaxIdleTimeout unless overridden for tests
	metrics  *Metrics

	mu          sync.Mutex
	actors      map[key.EndpointID]*RemoteStateActor
	onEvict     func(id key.EndpointID, addrs []Addr)
	noHolepunch bool
}

// NewRemoteMap returns a RemoteMap whose actors live until ctx is cancelled or
// they idle out. selector is the path selector shared by all actors (nil uses
// [BiasedRttPathSelector]); resolve is the address-lookup hook (nil disables
// lookup-driven resolution).
func ( context.Context,  PathSelector,  ResolveFunc) *RemoteMap {
	return newRemoteMap(, , , ActorMaxIdleTimeout, nil)
}

// NewRemoteMapWithMetrics is like [NewRemoteMap], but records actor path
// lifecycle counters in metrics.
func ( context.Context,  PathSelector,  ResolveFunc,  *Metrics) *RemoteMap {
	return newRemoteMap(, , , ActorMaxIdleTimeout, )
}

// newRemoteMap is the constructor with a configurable actor idle timeout, so
// tests can drive the idle-teardown race without waiting the full minute.
func newRemoteMap( context.Context,  PathSelector,  ResolveFunc,  time.Duration,  *Metrics) *RemoteMap {
	if  == nil {
		 = BiasedRttPathSelector{}
	}
	return &RemoteMap{
		ctx:      ,
		selector: ,
		resolve:  ,
		idle:     ,
		metrics:  ,
		actors:   make(map[key.EndpointID]*RemoteStateActor),
	}
}

// actor returns the running actor for id, spawning one if none is registered.
// The caller must hold m.mu. The spawned actor's onExit removes it from the map
// under m.mu, but only if it is still the actor registered under id, so a
// concurrently-spawned successor is never reaped.
func ( *RemoteMap) ( key.EndpointID) *RemoteStateActor {
	if ,  := .actors[];  {
		return 
	}
	var  *RemoteStateActor
	 := func() {
		.mu.Lock()
		defer .mu.Unlock()
		// The captured a is assigned by the spawner under m.mu, and onExit
		// runs deferred in the actor goroutine, so under m.mu both the
		// variable and the actor-owned path state are race-free to read.
		 := .paths.Addrs()
		 := .actors[]
		if  ==  {
			delete(.actors, )
			 = nil
		}
		// Evict the remote's mapped addresses unless a successor actor has
		// already been spawned for the same id (it shares them). Calling the
		// hook under m.mu keeps eviction ordered before any later spawn, so a
		// fresh actor always regenerates fresh mappings.
		if  == nil && .onEvict != nil {
			.onEvict(, )
		}
	}
	 = newRemoteStateActor(.ctx, , .selector, .resolve, .idle, .metrics, )
	.noHolepunch.Store(.noHolepunch)
	.actors[] = 
	return 
}

// DisableHolepunch stops actors from initiating NAT traversal or direct-path
// validation on their upgrade tick. Endpoints without IP transports set it:
// there is no direct path to punch toward, and a traversal round initiated on
// a relay-only connection stalls its in-flight relay streams. Set it before
// the first remote is referenced.
func ( *RemoteMap) () {
	.mu.Lock()
	defer .mu.Unlock()
	.noHolepunch = true
}

// SetOnEvict sets f to be called when a remote's actor is reaped with no
// successor, passing the remote's known path addresses. The endpoint uses it to
// release the remote's mapped addresses (see [Socket.EvictRemote]), so the
// mapped-address tables do not grow without bound under peer churn. f is called
// with the map's internal mutex held and must not call back into the RemoteMap.
// Set it before the first remote is referenced.
func ( *RemoteMap) ( func( key.EndpointID,  []Addr)) {
	.mu.Lock()
	defer .mu.Unlock()
	.onEvict = 
}

// Actor returns the running actor for id, spawning one if none exists.
func ( *RemoteMap) ( key.EndpointID) *RemoteStateActor {
	.mu.Lock()
	defer .mu.Unlock()
	return .actor()
}

// AddNATTraversalAddresses reconciles local QNT candidates on currently-active
// remote actors. It does not spawn actors and ignores per-actor errors, because
// candidate updates must not make an established endpoint fail.
func ( *RemoteMap) ( []netip.AddrPort) {
	.mu.Lock()
	 := make([]*RemoteStateActor, 0, len(.actors))
	for ,  := range .actors {
		 = append(, )
	}
	.mu.Unlock()
	for ,  := range  {
		_ = .AddNATTraversalAddresses()
	}
}

// Len returns the number of registered actors. Intended for tests and metrics.
func ( *RemoteMap) () int {
	.mu.Lock()
	defer .mu.Unlock()
	return len(.actors)
}

// RemoteInfo returns a snapshot for id if a running actor exists. It does not
// spawn a new actor.
func ( *RemoteMap) ( key.EndpointID) (RemoteInfo, bool) {
	.mu.Lock()
	,  := .actors[]
	.mu.Unlock()
	if ! {
		return RemoteInfo{}, false
	}
	select {
	case <-.donec():
		.dropIfStopped(, )
		return RemoteInfo{}, false
	default:
	}
	return .RemoteInfo(), true
}

// AddConnection registers conn with the actor for remote, spawning the actor if
// needed, and returns the connection's path-event channel. Registering a
// connection resets the actor's idle timer, so this can race the idle teardown
// safely (O12): if the actor is mid-teardown the send observes its done channel
// and a fresh actor is spawned on the retry.
func ( *RemoteMap) ( key.EndpointID,  Connection) <-chan PathEvent {
	,  := .AddConnectionActor(, )
	return 
}

// AddConnectionActor is like [RemoteMap.AddConnection], but also returns the
// actor that accepted conn.
func ( *RemoteMap) ( key.EndpointID,  Connection) (<-chan PathEvent, *RemoteStateActor) {
	for {
		.mu.Lock()
		 := .actor()
		.mu.Unlock()

		// If the actor stopped between selection and use, spawn a fresh one.
		select {
		case <-.donec():
			.dropIfStopped(, )
			continue
		default:
		}
		,  := .AddConnection()
		// If the actor stopped before it could register the connection, drop it
		// and retry with a fresh actor.
		if ! {
			.dropIfStopped(, )
			continue
		}
		return , 
	}
}

// ResolveRemote asks the actor for addr.ID to resolve and register more
// candidate paths, spawning the actor if needed. It returns the lookup error if
// any. It races idle teardown the same way as [RemoteMap.AddConnection].
func ( *RemoteMap) ( netaddr.EndpointAddr) error {
	for {
		.mu.Lock()
		 := .actor(.ID)
		.mu.Unlock()

		select {
		case <-.donec():
			.dropIfStopped(.ID, )
			continue
		default:
		}
		 := .ResolveRemote()
		// A canceled result with a stopped actor means we raced teardown; retry.
		if  == context.Canceled {
			select {
			case <-.donec():
				.dropIfStopped(.ID, )
				continue
			default:
			}
		}
		return 
	}
}

// dropIfStopped removes a from the map if it is the stopped actor still
// registered under id, so the next reference spawns a fresh actor. The actor's
// own onExit already does this; dropIfStopped covers the window before onExit
// has run.
func ( *RemoteMap) ( key.EndpointID,  *RemoteStateActor) {
	.mu.Lock()
	defer .mu.Unlock()
	if .actors[] ==  {
		delete(.actors, )
	}
}