// registry.go — multi-index manager. Mirrors the catalogd Registry
// shape: a thread-safe map[name]*Index with Create / Get / Delete.
// Per-index operations (Add, Search) go through each Index's own
// RWMutex so registry-wide locking only fires on lifecycle events.
package vectord

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

// ErrIndexNotFound is returned by Get / Delete when the requested
// name has no registered index.
var ErrIndexNotFound = errors.New("vectord: index not found")

// ErrIndexAlreadyExists is returned by Create when the name is
// taken. Callers can treat this as a 409 Conflict — paralleling
// catalogd's ADR-020 idempotency contract, but stricter (no
// "same params reuses index" semantics yet).
var ErrIndexAlreadyExists = errors.New("vectord: index already exists")

// Registry holds the live indexes by name.
type Registry struct {
	mu      sync.RWMutex
	indexes map[string]*Index
}

func NewRegistry() *Registry {
	return &Registry{indexes: make(map[string]*Index)}
}

// Create builds a new Index from params and registers it under
// params.Name. Returns ErrIndexAlreadyExists if the name is taken.
func (r *Registry) Create(p IndexParams) (*Index, error) {
	idx, err := NewIndex(p)
	if err != nil {
		return nil, err
	}
	r.mu.Lock()
	defer r.mu.Unlock()
	if _, exists := r.indexes[p.Name]; exists {
		return nil, fmt.Errorf("%w: %q", ErrIndexAlreadyExists, p.Name)
	}
	r.indexes[p.Name] = idx
	return idx, nil
}

// RegisterPrebuilt installs an already-built Index under its
// params.Name. Used by the persistor's rehydrate path — the index
// has been Decoded from disk and shouldn't be re-built fresh by
// Create. Returns ErrIndexAlreadyExists if the name is taken.
func (r *Registry) RegisterPrebuilt(idx *Index) error {
	r.mu.Lock()
	defer r.mu.Unlock()
	name := idx.Params().Name
	if _, exists := r.indexes[name]; exists {
		return fmt.Errorf("%w: %q", ErrIndexAlreadyExists, name)
	}
	r.indexes[name] = idx
	return nil
}

// Get returns the index for name, or ErrIndexNotFound.
func (r *Registry) Get(name string) (*Index, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	idx, ok := r.indexes[name]
	if !ok {
		return nil, fmt.Errorf("%w: %q", ErrIndexNotFound, name)
	}
	return idx, nil
}

// Delete removes the index for name. Returns ErrIndexNotFound if
// not present (so callers see explicit no-op vs success on the
// idempotent path).
func (r *Registry) Delete(name string) error {
	r.mu.Lock()
	defer r.mu.Unlock()
	if _, ok := r.indexes[name]; !ok {
		return fmt.Errorf("%w: %q", ErrIndexNotFound, name)
	}
	delete(r.indexes, name)
	return nil
}

// Names returns the registered index names sorted ascending —
// stable enumeration for /v1/vectors GET listings.
func (r *Registry) Names() []string {
	r.mu.RLock()
	defer r.mu.RUnlock()
	out := make([]string, 0, len(r.indexes))
	for name := range r.indexes {
		out = append(out, name)
	}
	sort.Strings(out)
	return out
}
