// BucketRegistry resolves logical bucket names → *Bucket. G0 holds
// exactly one bucket ("primary"); G2 (multi-bucket federation, per
// Rust ADR-017) extends this to many. Keeping the registry as a
// distinct type now means cmd/storaged never has to be refactored
// when G2 lands — every handler already routes through Resolve.
package storaged

import (
	"errors"
	"fmt"
	"sync"
)

var ErrBucketUnknown = errors.New("storaged: unknown bucket")

// BucketRegistry is a thread-safe map of logical name → *Bucket.
// In G0 only "primary" is registered; the registry exists so that
// when G2 multi-bucket lands we add buckets in registry.go and the
// HTTP layer is unchanged.
type BucketRegistry struct {
	mu      sync.RWMutex
	buckets map[string]*Bucket
}

func NewRegistry() *BucketRegistry {
	return &BucketRegistry{buckets: make(map[string]*Bucket)}
}

// Register adds a bucket under its logical name. Re-registering the
// same name is rejected — G0 services build the registry once at
// startup and a duplicate is almost always a config bug.
func (r *BucketRegistry) Register(b *Bucket) error {
	r.mu.Lock()
	defer r.mu.Unlock()
	if _, exists := r.buckets[b.name]; exists {
		return fmt.Errorf("bucket %q already registered", b.name)
	}
	r.buckets[b.name] = b
	return nil
}

// Resolve returns the bucket for a logical name. G0 callers pass
// "primary" since that's the only one registered.
func (r *BucketRegistry) Resolve(name string) (*Bucket, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	b, ok := r.buckets[name]
	if !ok {
		return nil, fmt.Errorf("%w: %q", ErrBucketUnknown, name)
	}
	return b, nil
}

// Names returns the registered logical names (sorted-order not
// guaranteed). Useful for /health surface or debug dumps.
func (r *BucketRegistry) Names() []string {
	r.mu.RLock()
	defer r.mu.RUnlock()
	out := make([]string, 0, len(r.buckets))
	for name := range r.buckets {
		out = append(out, name)
	}
	return out
}
