package shell import ( "errors" "fmt" "math/rand/v2" "sync" ) type registryEntry struct { shell Shell weight int } // Registry holds shells with associated weights for random selection. type Registry struct { mu sync.RWMutex entries []registryEntry } // NewRegistry returns an empty Registry. func NewRegistry() *Registry { return &Registry{} } // Register adds a shell with the given weight. Weight must be >= 1 and // no duplicate names are allowed. func (r *Registry) Register(shell Shell, weight int) error { if weight < 1 { return fmt.Errorf("weight must be >= 1, got %d", weight) } r.mu.Lock() defer r.mu.Unlock() for _, e := range r.entries { if e.shell.Name() == shell.Name() { return fmt.Errorf("shell %q already registered", shell.Name()) } } r.entries = append(r.entries, registryEntry{shell: shell, weight: weight}) return nil } // Select picks a shell using weighted random selection. func (r *Registry) Select() (Shell, error) { r.mu.RLock() defer r.mu.RUnlock() if len(r.entries) == 0 { return nil, errors.New("no shells registered") } total := 0 for _, e := range r.entries { total += e.weight } pick := rand.IntN(total) cumulative := 0 for _, e := range r.entries { cumulative += e.weight if pick < cumulative { return e.shell, nil } } // Should never reach here, but return last entry as fallback. return r.entries[len(r.entries)-1].shell, nil } // Get returns a shell by name. func (r *Registry) Get(name string) (Shell, bool) { r.mu.RLock() defer r.mu.RUnlock() for _, e := range r.entries { if e.shell.Name() == name { return e.shell, true } } return nil, false }