package socket

import (
	
)

// PathBroadcastCapacity is the per-subscriber buffer capacity for path events. A
// subscriber that falls more than this many events behind is told how many it
// missed via a [PathEventLagged] event rather than silently dropping. It matches
// the Rust BROADCAST_CAPACITY
// (iroh/src/socket/remote_map/remote_state/path_watcher.rs:50).
const PathBroadcastCapacity = 8

// PathEventKind tags the variant of a [PathEvent].
type PathEventKind int

const (
	// PathEventOpened reports a newly-opened network path.
	PathEventOpened PathEventKind = iota
	// PathEventClosed reports a closed network path.
	PathEventClosed
	// PathEventSelected reports that a path was selected for transmission.
	PathEventSelected
	// PathEventLagged reports that events were dropped before a subscriber read
	// them; Missed carries the count.
	PathEventLagged
)

func ( PathEventKind) () string {
	switch  {
	case PathEventOpened:
		return "opened"
	case PathEventClosed:
		return "closed"
	case PathEventSelected:
		return "selected"
	case PathEventLagged:
		return "lagged"
	default:
		return "invalid"
	}
}

// PathEvent is a lifecycle notification for a network path of a connection. It
// is the Go analog of the Rust PathEvent enum (path_watcher.rs:55).
//
// For Opened, Closed, and Selected, Addr identifies the path. For Lagged, Missed
// is the number of events the subscriber missed and Addr is the zero value.
type PathEvent struct {
	// Kind is which kind of event this is.
	Kind PathEventKind
	// Addr is the path's transport address (zero for Lagged).
	Addr Addr
	// Missed is the number of dropped events (only for Lagged).
	Missed uint64
}

// PathWatcher is a drop-oldest broadcast of [PathEvent]s to any number of
// subscribers. Each subscriber has its own ring buffer of [PathBroadcastCapacity]
// events; when a subscriber falls behind, the oldest buffered event is dropped
// and the next event the subscriber receives is a [PathEventLagged] with the
// running missed count, mirroring tokio::broadcast's lagged-receiver behavior
// (path_watcher.rs).
//
// PathWatcher is safe for concurrent use. The writer calls [PathWatcher.Send];
// readers call [PathWatcher.Subscribe] and consume the returned channel. Each
// subscriber is served by a dedicated delivery goroutine that stops when the
// subscriber is cancelled or the watcher is closed.
type PathWatcher struct {
	mu     sync.Mutex
	subs   map[*pathSub]struct{}
	closed bool
}

// pathSub is a single subscriber. The ring buffer and lag bookkeeping are
// guarded by mu; cond wakes the delivery goroutine when an event is enqueued or
// the subscriber is closed.
type pathSub struct {
	mu     sync.Mutex
	cond   *sync.Cond
	ring   []PathEvent // pending events, oldest first; len <= PathBroadcastCapacity
	missed uint64      // events dropped since the last delivered Lagged
	lagged bool        // a Lagged event is pending delivery
	closed bool

	ch   chan PathEvent
	done chan struct{}
	once sync.Once
}

// NewPathWatcher returns an empty broadcast with no subscribers.
func () *PathWatcher {
	return &PathWatcher{subs: make(map[*pathSub]struct{})}
}

// Subscribe registers a new subscriber and returns the channel its events are
// delivered on plus a function to unsubscribe and stop delivery. Events sent
// before Subscribe are not replayed. The channel is closed when the subscriber
// is cancelled or the watcher is closed.
//
// The cancel function should be called when the subscriber is done, like
// [time.Ticker.Stop]: each subscription runs a delivery goroutine, which
// cancel stops without waiting for the subscriber to drain. [PathWatcher.Close]
// stops the goroutine of every remaining subscriber, so an abandoned
// subscription outlives at most the watcher itself. Events already buffered on
// the channel stay readable after it is closed; events still pending delivery
// when a subscriber stops reading are dropped.
func ( *PathWatcher) () (<-chan PathEvent, func()) {
	.mu.Lock()
	defer .mu.Unlock()
	 := &pathSub{
		ch:   make(chan PathEvent, PathBroadcastCapacity+1),
		done: make(chan struct{}),
	}
	.cond = sync.NewCond(&.mu)
	if .closed {
		close(.ch)
		.closed = true
		return .ch, func() {}
	}
	.subs[] = struct{}{}
	go .deliver()
	return .ch, func() { .unsubscribe() }
}

func ( *PathWatcher) ( *pathSub) {
	.mu.Lock()
	if ,  := .subs[]; ! {
		.mu.Unlock()
		return
	}
	delete(.subs, )
	.mu.Unlock()
	.close()
}

// Send broadcasts ev to every subscriber. A subscriber whose ring buffer is full
// has its oldest pending event dropped and its missed counter incremented; the
// next event that subscriber receives is a [PathEventLagged] carrying the missed
// count. Send never blocks.
func ( *PathWatcher) ( PathEvent) {
	.mu.Lock()
	if .closed {
		.mu.Unlock()
		return
	}
	 := make([]*pathSub, 0, len(.subs))
	for  := range .subs {
		 = append(, )
	}
	.mu.Unlock()
	for ,  := range  {
		.enqueue()
	}
}

// Close stops every subscriber's delivery goroutine, closing its channel, and
// rejects future sends and subscriptions. It is idempotent. It is the analog of
// dropping the Rust broadcast sender, which ends every outstanding receiver.
func ( *PathWatcher) () {
	.mu.Lock()
	if .closed {
		.mu.Unlock()
		return
	}
	.closed = true
	 := make([]*pathSub, 0, len(.subs))
	for  := range .subs {
		 = append(, )
	}
	.subs = make(map[*pathSub]struct{})
	.mu.Unlock()
	for ,  := range  {
		.close()
	}
}

// enqueue appends ev to the subscriber's ring buffer, dropping the oldest
// pending event and accruing a missed count if the buffer is full, then wakes
// the delivery goroutine. Never blocks.
func ( *pathSub) ( PathEvent) {
	.mu.Lock()
	if .closed {
		.mu.Unlock()
		return
	}
	if len(.ring) >= PathBroadcastCapacity {
		.ring = .ring[1:] // drop oldest
		.missed++
		.lagged = true
	}
	.ring = append(.ring, )
	.cond.Signal()
	.mu.Unlock()
}

// close marks the subscriber closed, wakes its delivery goroutine, and
// interrupts an in-flight send so the goroutine cannot outlive the
// subscription. Idempotent.
func ( *pathSub) () {
	.mu.Lock()
	if .closed {
		.mu.Unlock()
		return
	}
	.closed = true
	.cond.Signal()
	.mu.Unlock()
	.once.Do(func() { close(.done) })
}

// deliver is the per-subscriber delivery goroutine. It pops events from the ring
// buffer and sends them on the channel, prepending a single Lagged event
// whenever one is pending. It exits and closes the channel when the subscriber
// is closed and its ring is drained. If the subscriber stops reading, close
// interrupts an in-flight send instead of waiting forever for the drain.
func ( *pathSub) () {
	for {
		.mu.Lock()
		for !.closed && !.lagged && len(.ring) == 0 {
			.cond.Wait()
		}
		if .closed && !.lagged && len(.ring) == 0 {
			.mu.Unlock()
			close(.ch)
			return
		}
		var  PathEvent
		if .lagged {
			 = PathEvent{Kind: PathEventLagged, Missed: .missed}
			.lagged = false
			.missed = 0
		} else {
			 = .ring[0]
			.ring = .ring[1:]
		}
		.mu.Unlock()
		select {
		case .ch <- :
			continue
		default:
		}
		select {
		case .ch <- :
		case <-.done:
			close(.ch)
			return
		}
	}
}