package service

// queueReservation is the dispatch goroutine's handle on one admitted lease.
// Admission reserves a slot on the selected node/provider — and a long-context
// slot when the request is long and the provider declares a long-context limit —
// and mints the lease that owns it before any request is built or sent. Every
// dispatch path that fails after admission hands the lease back through release.
//
// The exactly-once guarantee lives in the manager, not here: release resolves the
// lease id under the manager lock, so a send failure racing an early terminal
// event or a node disconnect frees the slot once regardless of which one wins.
// This handle only tracks whether *this* goroutine still owns the lease, and is
// touched from the single dispatch goroutine.
//
// A dispatch that succeeds calls handOff: the run terminal path (normalized) or
// the tunnel terminal frame / close (tunnel) owns the release from that point.
type queueReservation struct {
	queue   *modelQueueManager
	leaseID uint64
	// done marks that this handle has given up ownership, either by releasing
	// the lease or by handing it to the run/tunnel lifecycle.
	done bool
}

// newQueueReservation takes ownership of the lease that admission minted for the
// selected candidate.
func newQueueReservation(queue *modelQueueManager, selected *candidateNode) *queueReservation {
	return &queueReservation{queue: queue, leaseID: selected.leaseID}
}

// track binds the run id to the lease so terminal events and node disconnects
// can find it. It runs before the send so a terminal event arriving early still
// resolves the lease to release.
func (res *queueReservation) track(runID string) {
	if res == nil || res.queue == nil {
		return
	}
	res.queue.trackLease(res.leaseID, runID)
}

// release frees the lease. Repeated calls, and calls after handOff, are no-ops
// on this handle; a release that does reach the manager is itself idempotent by
// lease identity.
func (res *queueReservation) release(reason string) {
	if res == nil || res.queue == nil || res.done {
		return
	}
	res.done = true
	res.queue.releaseLease(res.leaseID, reason)
}

// handOff marks the lease as owned by the dispatched run or tunnel lifecycle, so
// no later release on this handle can free a slot that now belongs to a live
// request.
func (res *queueReservation) handOff() {
	if res == nil {
		return
	}
	res.done = true
}
