package selector

import (
	"math/rand"
	"time"
)

type randomWeightedItem[T any] struct {
	item   T
	weight int
}

// RandomWeighted is a weighted random selector.
// Items are selected randomly with probability proportional to their weight.
type RandomWeighted[T any] struct {
	items []*randomWeightedItem[T]
	sum   int
	r     *rand.Rand
}

// NewRandomWeighted creates a new RandomWeighted selector.
func NewRandomWeighted[T any]() *RandomWeighted[T] {
	return &RandomWeighted[T]{
		r: rand.New(rand.NewSource(time.Now().UnixNano())),
	}
}

// Add adds an item with the given weight to the selector.
func (rw *RandomWeighted[T]) Add(item T, weight int) {
	ri := &randomWeightedItem[T]{item: item, weight: weight}
	rw.items = append(rw.items, ri)
	rw.sum += weight
}

// Next selects an item randomly based on weights.
// Returns the zero value if no items are added.
func (rw *RandomWeighted[T]) Next() (v T) {
	if len(rw.items) == 0 {
		return
	}
	if rw.sum <= 0 {
		return
	}
	weight := rw.r.Intn(rw.sum) + 1
	for _, item := range rw.items {
		weight -= item.weight
		if weight <= 0 {
			return item.item
		}
	}

	return rw.items[len(rw.items)-1].item
}

// Reset clears all items and weights.
func (rw *RandomWeighted[T]) Reset() {
	rw.items = nil
	rw.sum = 0
}
