// Package registrar provides name registration. It reserves a name to a given key.
package registrar

import (
	"errors"
	"maps"
	"sync"
)

var (
	// ErrNameReserved is an error which is returned when a name is requested to be reserved that already is reserved.
	ErrNameReserved = errors.New("name is reserved")
	// ErrNameNotReserved is an error which is returned when trying to find a name that is not reserved.
	ErrNameNotReserved = errors.New("name is not reserved")
	// ErrNoSuchKey is returned when trying to find the names for a key which is not known.
	ErrNoSuchKey = errors.New("provided key does not exist")
)

// Registrar stores indexes a list of keys and their registered names as well as indexes names and the key that they are registered to
// Names must be unique.
// Registrar is safe for concurrent access.
type Registrar struct {
	idx   map[string][]string
	names map[string]string
	mu    sync.Mutex
}

// NewRegistrar creates a new Registrar with the an empty index.
func NewRegistrar() *Registrar {
	return &Registrar{
		idx:   make(map[string][]string),
		names: make(map[string]string),
	}
}

// Reserve registers a key to a name
// Reserve is idempotent
// Attempting to reserve a key to a name that already exists results in an `ErrNameReserved`
// A name reservation is globally unique.
func (r *Registrar) Reserve(name, key string) error {
	r.mu.Lock()
	defer r.mu.Unlock()

	if k, exists := r.names[name]; exists {
		if k != key {
			return ErrNameReserved
		}

		return nil
	}

	r.idx[key] = append(r.idx[key], name)
	r.names[name] = key

	return nil
}

// Release releases the reserved name
// Once released, a name can be reserved again.
func (r *Registrar) Release(name string) {
	r.mu.Lock()
	defer r.mu.Unlock()

	key, exists := r.names[name]
	if !exists {
		return
	}

	for i, n := range r.idx[key] {
		if n != name {
			continue
		}

		r.idx[key] = append(r.idx[key][:i], r.idx[key][i+1:]...)

		break
	}

	delete(r.names, name)

	if len(r.idx[key]) == 0 {
		delete(r.idx, key)
	}
}

// Delete removes all reservations for the passed in key.
// All names reserved to this key are released.
func (r *Registrar) Delete(key string) {
	r.mu.Lock()

	for _, name := range r.idx[key] {
		delete(r.names, name)
	}

	delete(r.idx, key)
	r.mu.Unlock()
}

// GetNames lists all the reserved names for the given key.
func (r *Registrar) GetNames(key string) ([]string, error) {
	r.mu.Lock()
	defer r.mu.Unlock()

	names, exists := r.idx[key]
	if !exists {
		return nil, ErrNoSuchKey
	}

	return names, nil
}

// Get returns the key that the passed in name is reserved to.
func (r *Registrar) Get(name string) (string, error) {
	r.mu.Lock()
	key, exists := r.names[name]
	r.mu.Unlock()

	if !exists {
		return "", ErrNameNotReserved
	}

	return key, nil
}

// GetAll returns all registered names.
func (r *Registrar) GetAll() map[string][]string {
	out := make(map[string][]string)

	r.mu.Lock()
	// copy index into out
	maps.Copy(out, r.idx)

	r.mu.Unlock()

	return out
}
