package module

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

// Registry is a concurrency-safe in-memory index of loaded manifests.
// It's held as a shared component by both Controller and Target.
type Registry struct {
	mu      sync.RWMutex
	modules map[string]*Manifest
}

// NewRegistry returns an empty registry.
func NewRegistry() *Registry {
	return &Registry{modules: make(map[string]*Manifest)}
}

// LoadDir scans dir for modules and inserts them. Duplicate IDs are rejected.
func (r *Registry) LoadDir(dir string) error {
	ms, err := LoadDir(dir)
	if err != nil {
		return err
	}
	r.mu.Lock()
	defer r.mu.Unlock()
	for _, m := range ms {
		if _, exists := r.modules[m.ID]; exists {
			return fmt.Errorf("duplicate module id %q in %s", m.ID, dir)
		}
		r.modules[m.ID] = m
	}
	return nil
}

// Put inserts or replaces a manifest.
func (r *Registry) Put(m *Manifest) {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.modules[m.ID] = m
}

// Get returns the manifest for id, or (nil, false) if unknown.
func (r *Registry) Get(id string) (*Manifest, bool) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	m, ok := r.modules[id]
	return m, ok
}

// List returns all manifests sorted by id.
func (r *Registry) List() []*Manifest {
	r.mu.RLock()
	defer r.mu.RUnlock()
	out := make([]*Manifest, 0, len(r.modules))
	for _, m := range r.modules {
		out = append(out, m)
	}
	sort.Slice(out, func(i, j int) bool { return out[i].ID < out[j].ID })
	return out
}

// Len returns the number of modules currently registered.
func (r *Registry) Len() int {
	r.mu.RLock()
	defer r.mu.RUnlock()
	return len(r.modules)
}
