package wal

import (
	"sync/atomic"

	"github.com/dailz/go-kv/errkit"
)

// SequenceManager manages monotonic sequence number allocation for the WAL.
// Invariant: durableSequence <= publishedSequence <= nextSequence
type SequenceManager struct {
	nextSequence      atomic.Uint64
	publishedSequence atomic.Uint64
	durableSequence   atomic.Uint64
	exhausted         atomic.Bool
}

// NewSequenceManager creates a SequenceManager initialised from a recovered
// sequence number. All three watermarks start at recoveredSequence.
func NewSequenceManager(recoveredSequence uint64) *SequenceManager {
	sm := &SequenceManager{}
	sm.nextSequence.Store(recoveredSequence)
	sm.publishedSequence.Store(recoveredSequence)
	sm.durableSequence.Store(recoveredSequence)
	return sm
}

// AllocateBatch atomically reserves [base, base+count-1] sequence numbers.
// Returns ErrSequenceExhausted if count is 0 or the allocation would overflow uint64.
func (sm *SequenceManager) AllocateBatch(count uint32) (baseSequence uint64, err error) {
	if count == 0 {
		return 0, errkit.ErrSequenceExhausted
	}
	for {
		if sm.exhausted.Load() {
			return 0, errkit.ErrSequenceExhausted
		}
		base := sm.nextSequence.Load()
		last := base + uint64(count) - 1
		if last < base {
			return 0, errkit.ErrSequenceExhausted
		}
		newNext := last + 1
		if !sm.nextSequence.CompareAndSwap(base, newNext) {
			continue
		}
		if newNext == 0 {
			sm.exhausted.Store(true)
		}
		return base, nil
	}
}

// Publish advances the publishedSequence watermark to seq (only forward).
func (sm *SequenceManager) Publish(seq uint64) {
	for {
		current := sm.publishedSequence.Load()
		if seq <= current {
			return
		}
		if sm.publishedSequence.CompareAndSwap(current, seq) {
			return
		}
	}
}

// MarkDurable advances the durableSequence watermark to seq (only forward).
func (sm *SequenceManager) MarkDurable(seq uint64) {
	for {
		current := sm.durableSequence.Load()
		if seq <= current {
			return
		}
		if sm.durableSequence.CompareAndSwap(current, seq) {
			return
		}
	}
}

// Published returns the current publishedSequence watermark.
func (sm *SequenceManager) Published() uint64 { return sm.publishedSequence.Load() }

// Durable returns the current durableSequence watermark.
func (sm *SequenceManager) Durable() uint64 { return sm.durableSequence.Load() }

// NextSequence returns the next sequence number to be allocated.
func (sm *SequenceManager) NextSequence() uint64 { return sm.nextSequence.Load() }
