package socket

import (
	
	
	
	

	
	
)

// Socket holds the magic socket's mapped-address tables: the bidirectional maps
// between transport addresses and the synthetic IPv6 ULAs that quic-go uses to
// address paths. It is the Go analog of the Rust Socket's mapped_addrs
// (iroh/src/socket.rs:332).
//
// A Socket is created by [NewSocket] and shared by a [MagicConn] and its
// [Transports]. It is safe for concurrent use. The zero Socket is not usable;
// use [NewSocket].
type Socket struct {
	// endpointAddrs maps endpoint ids to endpoint-id mapped addresses used for
	// initial packets before a concrete path is selected.
	endpointAddrs *AddrMap[key.EndpointID, EndpointIDMappedAddr]

	// relayAddrs maps (relay url, endpoint id) pairs to relay mapped addresses.
	// The map key is the relay key's string form, because netaddr.RelayURL wraps a
	// pointer and is not reliably comparable across separately-parsed URLs.
	relayAddrs *AddrMap[string, RelayMappedAddr]
	// relayByKey recovers the original RelayKey from its string form.
	relayMu    sync.Mutex
	relayByKey map[string]RelayKey

	// customAddrs maps a custom address (by its string key) to a custom mapped
	// address.
	customAddrs *AddrMap[string, CustomMappedAddr]
	// customByKey recovers the original netaddr.CustomAddr from its string key.
	customMu    sync.Mutex
	customByKey map[string]netaddr.CustomAddr

	closed atomic.Bool
}

// relayKeyString renders a relay key as a stable map key. netaddr.RelayURL
// normalizes its string form, so equivalent URLs collapse to one key.
func relayKeyString( netaddr.RelayURL,  key.EndpointID) string {
	return .String() + "|" + .String()
}

// RelayKey identifies a relay path: a relay URL together with the remote
// endpoint reached through it. It is the key type of the relay mapped-address
// table.
type RelayKey struct {
	URL netaddr.RelayURL
	EID key.EndpointID
}

// NewSocket returns a ready Socket with empty mapped-address tables.
func () *Socket {
	return &Socket{
		endpointAddrs: NewAddrMap[key.EndpointID, EndpointIDMappedAddr](
			NewEndpointIDMappedAddr,
			func( EndpointIDMappedAddr) netip.Addr { return .Addr() },
		),
		relayAddrs: NewAddrMap[string, RelayMappedAddr](
			NewRelayMappedAddr,
			func( RelayMappedAddr) netip.Addr { return .Addr() },
		),
		relayByKey: make(map[string]RelayKey),
		customAddrs: NewAddrMap[string, CustomMappedAddr](
			NewCustomMappedAddr,
			func( CustomMappedAddr) netip.Addr { return .Addr() },
		),
		customByKey: make(map[string]netaddr.CustomAddr),
	}
}

// Close marks the socket closed. Subsequent sends are dropped (blackholed) so
// quic-go's loss recovery handles in-flight datagrams rather than seeing a hard
// error. It is idempotent.
func ( *Socket) () { .closed.Store(true) }

// IsClosed reports whether the socket has been closed.
func ( *Socket) () bool { return .closed.Load() }

// EndpointIDMappedAddrFor returns the endpoint-id mapped address for id,
// allocating one on first use.
func ( *Socket) ( key.EndpointID) EndpointIDMappedAddr {
	return .endpointAddrs.Get()
}

// LookupEndpointID returns the endpoint id for an endpoint-id mapped address, if
// known.
func ( *Socket) ( EndpointIDMappedAddr) (key.EndpointID, bool) {
	return .endpointAddrs.Lookup(.Addr())
}

// RelayMappedAddrFor returns the relay mapped address for the (url, eid) pair,
// allocating one on first use.
func ( *Socket) ( netaddr.RelayURL,  key.EndpointID) RelayMappedAddr {
	 := relayKeyString(, )
	.relayMu.Lock()
	.relayByKey[] = RelayKey{URL: , EID: }
	.relayMu.Unlock()
	return .relayAddrs.Get()
}

// LookupRelay returns the (url, eid) pair for a relay mapped address, if known.
func ( *Socket) ( RelayMappedAddr) (RelayKey, bool) {
	,  := .relayAddrs.Lookup(.Addr())
	if ! {
		return RelayKey{}, false
	}
	.relayMu.Lock()
	,  := .relayByKey[]
	.relayMu.Unlock()
	return , 
}

// CustomMappedAddrFor returns the custom mapped address for c, allocating one on
// first use and recording the reverse mapping back to c.
func ( *Socket) ( netaddr.CustomAddr) CustomMappedAddr {
	 := .String()
	.customMu.Lock()
	.customByKey[] = 
	.customMu.Unlock()
	return .customAddrs.Get()
}

// PathAddr classifies a QUIC connection's remote net.Addr into the magic
// socket's transport [Addr]: a real IP becomes an IP path; a relay or custom
// mapped ULA is reverse-looked-up through the mapped-address tables. An unknown
// mapped address (or one whose mapping has been forgotten) falls back to an IP
// path so the per-remote actor still tracks a stable address. remoteID is used
// for relay paths, which are keyed by (relay url, endpoint id).
func ( *Socket) ( key.EndpointID,  net.Addr) Addr {
	,  := addrPort()
	if ! {
		return Addr{}
	}
	switch Classify(.Addr()) {
	case KindRelay:
		if ,  := .LookupRelay(RelayMappedAddrFromAddr(.Addr()));  {
			return RelayAddr(.URL, .EID)
		}
		return IPAddr()
	case KindCustom:
		if ,  := .LookupCustom(CustomMappedAddr{a: .Addr()});  {
			return CustomAddr()
		}
		return IPAddr()
	default:
		return IPAddr()
	}
}

// EvictRemote drops the mapped addresses recorded for a reaped remote: the
// endpoint-id mapping for id, every relay mapping whose remote endpoint is id,
// and the custom mappings among addrs (the remote's known transport
// addresses). Without eviction the tables grow without bound under peer churn
// (the upstream Rust implementation has the same leak, iroh issue #4293). A
// mapping is regenerated on the next use of the same key, so evicting a remote
// that immediately returns only costs a fresh mapped address.
func ( *Socket) ( key.EndpointID,  []Addr) {
	.endpointAddrs.Remove()

	.relayMu.Lock()
	var  []string
	for ,  := range .relayByKey {
		if .EID ==  {
			 = append(, )
			delete(.relayByKey, )
		}
	}
	.relayMu.Unlock()
	for ,  := range  {
		.relayAddrs.Remove()
	}

	for ,  := range  {
		,  := .Custom()
		if ! {
			continue
		}
		 := .String()
		.customMu.Lock()
		delete(.customByKey, )
		.customMu.Unlock()
		.customAddrs.Remove()
	}
}

// LookupCustom returns the custom address for a custom mapped address, if known.
func ( *Socket) ( CustomMappedAddr) (netaddr.CustomAddr, bool) {
	,  := .customAddrs.Lookup(.Addr())
	if ! {
		return netaddr.CustomAddr{}, false
	}
	.customMu.Lock()
	,  := .customByKey[]
	.customMu.Unlock()
	return , 
}