package bbr

// LOCAL addition (see PROVENANCE.md): a read-only view of the sender's
// model for the bridge's per-session log line. quic-go drives the sender
// from a single goroutine; the bridge reads from another, so the view is
// published atomically at the end of every congestion event instead of
// letting readers touch the live fields.

import (
	"time"

	"github.com/apernet/quic-go/congestion"
	"github.com/apernet/quic-go/monotime"
)

// Snapshot is the sender's model at the last congestion event.
type Snapshot struct {
	Mode           string
	InRecovery     bool
	AtFullBW       bool
	Bandwidth      Bandwidth
	PacingRate     Bandwidth
	Cwnd           congestion.ByteCount
	RecoveryWindow congestion.ByteCount
	BytesInFlight  congestion.ByteCount
	MinRTT         time.Duration
	AppLimited     bool
	// Lifetime byte totals from the bandwidth sampler. Their deltas are
	// what the path actually carried over an interval - the honest
	// counterpart of Bandwidth, which is a max filter and therefore
	// optimistic on a path that delivers in flushes.
	BytesSent  congestion.ByteCount
	BytesAcked congestion.ByteCount
	BytesLost  congestion.ByteCount
}

func (m bbrMode) String() string {
	switch m {
	case bbrModeStartup:
		return "startup"
	case bbrModeDrain:
		return "drain"
	case bbrModeProbeBw:
		return "probe_bw"
	case bbrModeProbeRtt:
		return "probe_rtt"
	default:
		return "unknown"
	}
}

func (b *bbrSender) publishSnapshot() {
	b.snapshot.Store(&Snapshot{
		Mode:           b.mode.String(),
		InRecovery:     b.InRecovery(),
		AtFullBW:       b.isAtFullBandwidth,
		Bandwidth:      b.bandwidthEstimate(),
		PacingRate:     b.PacingRate(),
		Cwnd:           b.GetCongestionWindow(),
		RecoveryWindow: b.recoveryWindow,
		BytesInFlight:  b.bytesInFlight,
		MinRTT:         b.getMinRtt(),
		AppLimited:     b.lastSampleIsAppLimited,
		BytesSent:      b.sampler.TotalBytesSent(),
		BytesAcked:     b.sampler.TotalBytesAcked(),
		BytesLost:      b.sampler.TotalBytesLost(),
	})
}

// Ack-stall tracker. A link HOLD (a Wi-Fi client off-channel for a scan, a
// cellular handover) stops every ack for hundreds of milliseconds while
// data sits in flight, then flushes; congestion never does that - acks
// keep arriving, late. The bridge uses the longest such silence per
// second to keep hold seconds out of its pressure reports.
//
// The silence is measured IN EXCESS OF the smoothed round trip. The raw
// gap is not a signal: an app-limited sender that went idle between
// bursts waits one full RTT for its first ack, and on a path with a
// standing queue that RTT is 300-500 ms - the raw gap then flagged every
// second of an overload as a hold, so the governor never cut (2026-08-22:
// 40 fps into a ~20 Mbps path, srtt 500 ms, 10% loss). An ack that
// arrives within a round trip of the send is the link working; only
// silence beyond that is the link gone. The measurement runs from the
// first send after quiescence or from the last ack, whichever is later,
// so an idle sender records nothing.

// armAckGap starts a measurement at the first send with nothing armed.
func (b *bbrSender) armAckGap(now monotime.Time) {
	if !b.ackGapArmed {
		b.ackGapArmed = true
		b.ackGapSince = now
	}
}

// noteAck closes the current measurement; OnCongestionEventEx re-arms it
// afterwards if data remains in flight.
func (b *bbrSender) noteAck(now monotime.Time) {
	if b.ackGapArmed {
		stall := now.Sub(b.ackGapSince)
		if b.rttStats != nil {
			stall -= b.rttStats.SmoothedRTT()
		}
		if stall > 0 {
			for {
				current := b.maxAckStallNs.Load()
				if int64(stall) <= current ||
					b.maxAckStallNs.CompareAndSwap(current, int64(stall)) {
					break
				}
			}
		}
	}
	b.ackGapSince = now
}

// TakeMaxAckStall returns the longest ack silence beyond one smoothed
// round trip, with data in flight, since the previous call, and resets
// it. Safe from any goroutine.
func (b *bbrSender) TakeMaxAckStall() time.Duration {
	return time.Duration(b.maxAckStallNs.Swap(0))
}

// Snapshot returns the model at the last congestion event, or the zero
// value before the first one.
func (b *bbrSender) Snapshot() Snapshot {
	if s := b.snapshot.Load(); s != nil {
		return *s
	}
	return Snapshot{}
}
