package spec

import "fmt"

// Registry is a name-keyed collection of FunctionSpec entries. It is the
// single source of truth for the runtime function arity surface; readers look
// functions up here rather than indexing a raw map, so the catalog cannot drift
// into a second declaration.
type Registry struct {
	specs map[string]FunctionSpec
}

// NewRegistry returns an empty Registry ready for Register calls.
func NewRegistry() *Registry {
	return &Registry{specs: make(map[string]FunctionSpec)}
}

// Register adds s to the registry. It rejects an empty Name and a duplicate
// registration so the catalog stays a single, conflict-free source of truth.
func (r *Registry) Register(s FunctionSpec) error {
	if s.Name == "" {
		return fmt.Errorf("spec: cannot register a function with an empty name")
	}
	if _, exists := r.specs[s.Name]; exists {
		return fmt.Errorf("spec: duplicate registration for function %q", s.Name)
	}
	r.specs[s.Name] = cloneFunctionSpec(s)
	return nil
}

// Clone returns an independent copy of r. FunctionSpec values are copied with
// their mutable nested data detached; immutable predicate/function references
// are shared. Mutating the returned registry cannot add, remove, or reshape
// entries from the source registry.
func (r *Registry) Clone() *Registry {
	clone := NewRegistry()
	if r == nil {
		return clone
	}
	for name, s := range r.specs {
		clone.specs[name] = cloneFunctionSpec(s)
	}
	return clone
}

// Lookup returns the spec registered under name and whether it was found.
func (r *Registry) Lookup(name string) (FunctionSpec, bool) {
	if r == nil {
		return FunctionSpec{}, false
	}
	s, ok := r.specs[name]
	if !ok {
		return FunctionSpec{}, false
	}
	return cloneFunctionSpec(s), true
}

func cloneFunctionSpec(s FunctionSpec) FunctionSpec {
	if s.Args != nil {
		s.Args = append([]ArgSpec(nil), s.Args...)
	}
	if s.Return.MergeArgs != nil {
		s.Return.MergeArgs = append([]int(nil), s.Return.MergeArgs...)
	}
	if s.VarArg != nil {
		varArg := *s.VarArg
		s.VarArg = &varArg
	}
	return s
}

// CheckArity validates argc for name against this registry's declaration. It
// is the registry-scoped equivalent of the package-level CheckArity helper.
func (r *Registry) CheckArity(name string, argc int) error {
	// Arity validation is read-only and sits on every built-in formula call.
	// Lookup intentionally clones nested slices for callers that receive a
	// FunctionSpec value, but cloning here adds an allocation without protecting
	// any mutable state: Validate only reads MinArgs, MaxArgs, and Predicate.
	// Read the registry-owned value directly so the registry remains the SSoT
	// without charging each runtime invocation for a defensive copy.
	if r == nil {
		return fmt.Errorf("spec: runtime function %q is not registered in the arity catalog", name)
	}
	s, ok := r.specs[name]
	if !ok {
		return fmt.Errorf("spec: runtime function %q is not registered in the arity catalog", name)
	}
	return s.Validate(argc)
}

// Names returns the registered function names in unspecified order. Callers
// that need deterministic output must sort the result.
func (r *Registry) Names() []string {
	if r == nil {
		return nil
	}
	names := make([]string, 0, len(r.specs))
	for name := range r.specs {
		names = append(names, name)
	}
	return names
}

// Len reports how many specs are registered.
func (r *Registry) Len() int {
	if r == nil {
		return 0
	}
	return len(r.specs)
}
