package pipeline

import (
	"context"
	"fmt"
	"reflect"
	"strings"

	"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
)

type ExtractorRegistry struct {
	typedEntries map[string]typedExtractorEntry
	specs        map[string]ModuleSpec
}

type typedExtractorEntry struct {
	spec            ModuleSpec
	valueType       reflect.Type
	validateOptions OptionValidator
	builder         func(BuildRequest) (any, error)
	extract         typedExtractOperation
}

func NewExtractorRegistry() *ExtractorRegistry {
	return &ExtractorRegistry{typedEntries: map[string]typedExtractorEntry{}, specs: map[string]ModuleSpec{}}
}

func RegisterExtractor[T any](registry *ExtractorRegistry, spec ModuleSpec, constructor func() (contracts.Extractor[T], error)) error {
	if constructor == nil {
		return fmt.Errorf("extractor constructor for %q must not be nil", strings.TrimSpace(spec.Key))
	}
	return RegisterExtractorBuilder(registry, spec, rejectUnconfiguredOptions, func(BuildRequest) (contracts.Extractor[T], error) { return constructor() })
}

func RegisterExtractorBuilder[T any](registry *ExtractorRegistry, spec ModuleSpec, validateOptions OptionValidator, builder func(BuildRequest) (contracts.Extractor[T], error)) error {
	if registry == nil {
		return fmt.Errorf("extractor registry must not be nil")
	}
	normalized := normalizeModuleSpec(spec)
	if err := validateModuleSpec("extractor", StageExtract, normalized); err != nil {
		return err
	}
	if normalized.ArtifactKind == "" {
		return fmt.Errorf("typed extractor %q artifact kind must not be empty", normalized.Key)
	}
	if validateOptions == nil {
		return fmt.Errorf("extractor option validator for %q must not be nil", normalized.Key)
	}
	if builder == nil {
		return fmt.Errorf("extractor builder for %q must not be nil", normalized.Key)
	}
	if _, ok := registry.specs[normalized.Key]; ok {
		return fmt.Errorf("extractor %q is already registered", normalized.Key)
	}
	entry := typedExtractorEntry{spec: cloneModuleSpec(normalized), valueType: reflect.TypeFor[T](), validateOptions: validateOptions, builder: func(request BuildRequest) (any, error) { return builder(cloneBuildRequest(request)) }, extract: func(ctx context.Context, implementation any, request contracts.TypedExtractionRequest) (erasedTypedResult, error) {
		extractor, ok := implementation.(contracts.Extractor[T])
		if !ok {
			return erasedTypedResult{}, fmt.Errorf("extractor %q has incompatible implementation %T", normalized.Key, implementation)
		}
		correction, err := contracts.CloneSemanticCorrection(request.Correction)
		if err != nil {
			return erasedTypedResult{}, fmt.Errorf("clone extraction correction: %w", err)
		}
		request.Correction = correction
		result, err := extractor.Extract(ctx, request)
		if err != nil {
			return erasedTypedResult{}, err
		}
		candidate, err := contracts.CloneModelCandidate(result.ModelCandidate)
		if err != nil {
			return erasedTypedResult{}, fmt.Errorf("clone extraction model candidate: %w", err)
		}
		return erasedTypedResult{Value: result.Value, Diagnostics: contracts.CloneProducerDiagnostics(result.Diagnostics), ModelCandidate: candidate}, nil
	}}
	if registry.typedEntries == nil {
		registry.typedEntries = map[string]typedExtractorEntry{}
	}
	if registry.specs == nil {
		registry.specs = map[string]ModuleSpec{}
	}
	registry.typedEntries[normalized.Key] = entry
	registry.specs[normalized.Key] = cloneModuleSpec(normalized)
	return nil
}

func (r *ExtractorRegistry) validateOptions(key string, options map[string]any) error {
	if r == nil {
		return fmt.Errorf("extractor registry must not be nil")
	}
	normalized := strings.TrimSpace(key)
	entry, ok := r.typedEntries[normalized]
	if !ok {
		return fmt.Errorf("extractor %q is not registered", normalized)
	}
	return validateRegisteredOptions(entry.validateOptions, options)
}
func (r *ExtractorRegistry) Spec(key string) (ModuleSpec, bool) {
	if r == nil {
		return ModuleSpec{}, false
	}
	spec, ok := r.specs[strings.TrimSpace(key)]
	return cloneModuleSpec(spec), ok
}
func (r *ExtractorRegistry) typedEntry(key string) (typedExtractorEntry, bool) {
	if r == nil {
		return typedExtractorEntry{}, false
	}
	entry, ok := r.typedEntries[strings.TrimSpace(key)]
	return entry, ok
}
func (r *ExtractorRegistry) RegisteredKeys() []string {
	if r == nil {
		return nil
	}
	return sortedRegistryKeys(r.specs)
}
