package config

import (
	"context"
	"encoding/json"
	"strings"
	"testing"

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

func TestEffectiveConfigRejectsEmptyAndUnknownPipelineIDs(t *testing.T) {
	cfg := configForEffectiveTests(t, effectiveProfile())
	for _, pipelineID := range []string{"", "missing"} {
		name := pipelineID
		if name == "" {
			name = "empty"
		}
		t.Run(name, func(t *testing.T) {
			_, err := cfg.Resolve(ResolveInput{PipelineID: pipelineID, Catalog: effectiveCatalog(t)})
			if err == nil || !strings.Contains(err.Error(), "pipeline") {
				t.Fatalf("Resolve(%q) error = %v, want pipeline context", pipelineID, err)
			}
		})
	}
}

func TestEffectiveConfigResolvesTrimmedPipelineMapKeys(t *testing.T) {
	profile := effectiveProfile()
	profile.ID = " main "
	cfg := Default()
	cfg.Pipelines = map[string]pipeline.PipelineProfile{" main ": profile}
	effective, err := cfg.Resolve(ResolveInput{PipelineID: "main", Catalog: effectiveCatalog(t)})
	if err != nil {
		t.Fatalf("Resolve() error = %v", err)
	}
	if effective.PipelineID != "main" || effective.ResolvedPipeline.ID != "main" {
		t.Fatalf("resolved IDs = %q, %q", effective.PipelineID, effective.ResolvedPipeline.ID)
	}
}

func TestEffectiveConfigOnlySelectsRequestedLanesWithoutMutatingSource(t *testing.T) {
	profile := effectiveProfile()
	profile.Artifacts["other"] = pipeline.ArtifactLaneProfile{Extract: pipeline.Binding("extract")}
	cfg := configForEffectiveTests(t, profile)
	effective, err := cfg.Resolve(ResolveInput{
		PipelineID: "main",
		Only:       []string{"other"},
		Catalog:    effectiveCatalog(t),
	})
	if err != nil {
		t.Fatalf("Resolve() error = %v", err)
	}
	if len(effective.ResolvedPipeline.Steps[0].ArtifactLanes) != 1 || effective.ResolvedPipeline.Steps[0].ArtifactLanes[0].ID != "other" {
		t.Fatalf("resolved lanes = %#v", effective.ResolvedPipeline.Steps[0].ArtifactLanes)
	}
	if len(cfg.Pipelines["main"].Artifacts) != 2 {
		t.Fatalf("source lanes were mutated: %#v", cfg.Pipelines["main"].Artifacts)
	}

	_, err = cfg.Resolve(ResolveInput{
		PipelineID: "main",
		Only:       []string{"missing"},
		Catalog:    effectiveCatalog(t),
	})
	if err == nil || !strings.Contains(err.Error(), "lane \"missing\"") {
		t.Fatalf("unknown lane error = %v", err)
	}
}

func TestEffectiveConfigMaterializesDefaultBindingsThroughCatalog(t *testing.T) {
	effective, err := resolveEffectiveProfile(t, effectiveProfile(), ResolveInput{})
	if err != nil {
		t.Fatalf("Resolve() error = %v", err)
	}
	resolved := effective.ResolvedPipeline
	if resolved.Chunk.Module != pipeline.DefaultChunkModule || resolved.Output.Module != pipeline.DefaultOutputModule {
		t.Fatalf("default pipeline bindings = %#v, %#v", resolved.Chunk, resolved.Output)
	}
	if len(resolved.Steps[0].ArtifactLanes) != 1 || resolved.Steps[0].ArtifactLanes[0].Merge.Module != pipeline.DefaultMergeModule || resolved.Steps[0].ArtifactLanes[0].Normalize.Module != pipeline.DefaultNormalizeModule {
		t.Fatalf("default lane bindings = %#v", resolved.Steps[0].ArtifactLanes)
	}
}

func TestEffectiveConfigPreservesPromptKitProfileSource(t *testing.T) {
	tests := []struct {
		name          string
		profileSource PromptKitConfig
	}{
		{name: "profile directory", profileSource: PromptKitConfig{ProfileDir: "./profiles"}},
		{name: "profile file", profileSource: PromptKitConfig{ProfileFile: "./profiles.yml"}},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			cfg := configForEffectiveTests(t, effectiveProfile())
			cfg.PromptKit = tt.profileSource
			effective, err := cfg.Resolve(ResolveInput{PipelineID: "main", Catalog: effectiveCatalog(t)})
			if err != nil {
				t.Fatalf("Resolve() error = %v", err)
			}
			if effective.Config.PromptKit != cfg.PromptKit {
				t.Fatalf("effective PromptKit config = %#v, want %#v", effective.Config.PromptKit, cfg.PromptKit)
			}
		})
	}
}

func TestEffectiveConfigOwnsPromptKitLocalBackend(t *testing.T) {
	cfg := configForEffectiveTests(t, effectiveProfile())
	cfg.PromptKit.LocalBackend = &PromptKitLocalBackendConfig{
		Endpoint:         "http://localhost:8000/v1",
		ConcurrencyLimit: 2,
	}
	effective, err := cfg.Resolve(ResolveInput{PipelineID: "main", Catalog: effectiveCatalog(t)})
	if err != nil {
		t.Fatalf("Resolve() error = %v", err)
	}
	if effective.Config.PromptKit.LocalBackend == nil {
		t.Fatal("effective local backend = nil")
	}
	if effective.Config.PromptKit.LocalBackend == cfg.PromptKit.LocalBackend {
		t.Fatal("effective local backend aliases input config")
	}

	cfg.PromptKit.LocalBackend.Endpoint = "http://changed-input.example/v1"
	if effective.Config.PromptKit.LocalBackend.Endpoint != "http://localhost:8000/v1" {
		t.Fatalf("input mutation changed effective config: %#v", effective.Config.PromptKit.LocalBackend)
	}
	effective.Config.PromptKit.LocalBackend.ConcurrencyLimit = 9
	if cfg.PromptKit.LocalBackend.ConcurrencyLimit != 2 {
		t.Fatalf("effective mutation changed input config: %#v", cfg.PromptKit.LocalBackend)
	}
}

func TestEffectiveConfigResolutionFailuresRetainContext(t *testing.T) {
	tests := []struct {
		name   string
		mutate func(*pipeline.PipelineProfile)
		want   []string
	}{
		{
			name: "unknown module",
			mutate: func(profile *pipeline.PipelineProfile) {
				profile.Input.Module = "missing-input"
			},
			want: []string{"pipeline \"main\"", "input"},
		},
		{
			name: "missing capability",
			mutate: func(profile *pipeline.PipelineProfile) {
				profile.Chunk.Module = "needs-capability"
			},
			want: []string{"pipeline \"main\"", "chunk"},
		},
		{
			name: "missing artifact variant",
			mutate: func(profile *pipeline.PipelineProfile) {
				profile.Artifacts["lane"] = pipeline.ArtifactLaneProfile{
					Extract: pipeline.Binding("extract"),
					Merge:   pipeline.Binding("other-merge"),
				}
			},
			want: []string{"pipeline \"main\"", "lane \"lane\"", "merge"},
		},
		{
			name: "invalid module options",
			mutate: func(profile *pipeline.PipelineProfile) {
				profile.Chunk = pipeline.ModuleBinding{Module: "generic", Options: map[string]any{"unknown": true}}
			},
			want: []string{"pipeline \"main\"", "chunk", "generic", "options"},
		},
		{
			name: "invalid validator options",
			mutate: func(profile *pipeline.PipelineProfile) {
				lane := profile.Artifacts["lane"]
				lane.Extract.Validators = pipeline.ValidatorOverride{
					Set: true,
					Validators: []pipeline.ModuleBinding{{
						Module:  "option-validator",
						Options: map[string]any{"invalid": true},
					}},
				}
				profile.Artifacts["lane"] = lane
			},
			want: []string{"pipeline \"main\"", "lane \"lane\"", "extract", "option-validator", "options"},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			profile := effectiveProfile()
			tt.mutate(&profile)
			_, err := resolveEffectiveProfile(t, profile, ResolveInput{})
			if err == nil {
				t.Fatal("Resolve() error = nil, want failure")
			}
			for _, fragment := range tt.want {
				if !strings.Contains(err.Error(), fragment) {
					t.Fatalf("Resolve() error = %v, want context %q", err, fragment)
				}
			}
		})
	}
}

func TestEffectiveConfigLLMProfileOverrideChangesDigestAndOverridesValidators(t *testing.T) {
	profile := effectiveProfile()
	profile.Chunk.LLMProfile = "chunk-profile"
	lane := profile.Artifacts["lane"]
	lane.Extract.LLMProfile = "extract-profile"
	lane.Merge.LLMProfile = "merge-profile"
	lane.Normalize.LLMProfile = "normalize-profile"
	lane.Extract.Validators = pipeline.ValidatorOverride{
		Set: true,
		Validators: []pipeline.ModuleBinding{{
			Module:     "llm-validator",
			LLMProfile: "validator-profile",
		}},
	}
	profile.Artifacts["lane"] = lane

	base, err := resolveEffectiveProfile(t, profile, ResolveInput{})
	if err != nil {
		t.Fatalf("base Resolve() error = %v", err)
	}
	overridden, err := resolveEffectiveProfile(t, profile, ResolveInput{LLMProfileOverride: "override-profile"})
	if err != nil {
		t.Fatalf("overridden Resolve() error = %v", err)
	}
	if base.ResolvedPipeline.Digest == overridden.ResolvedPipeline.Digest {
		t.Fatal("LLM profile override did not change the pipeline digest")
	}
	resolved := overridden.ResolvedPipeline
	if resolved.Chunk.LLMProfile != "override-profile" || resolved.Steps[0].ArtifactLanes[0].Extract.LLMProfile != "override-profile" ||
		resolved.Steps[0].ArtifactLanes[0].Merge.LLMProfile != "override-profile" || resolved.Steps[0].ArtifactLanes[0].Normalize.LLMProfile != "override-profile" {
		t.Fatalf("pipeline profile override was not applied: %#v", resolved)
	}
	validators := findEffectiveValidatorChain(resolved, pipeline.StageExtract, "lane")
	if len(validators.Validators) != 1 || validators.Validators[0].Binding.LLMProfile != "override-profile" {
		t.Fatalf("validator profile = %#v, want runtime override", validators)
	}
}

func TestEffectiveConfigPipelineLLMProfileIsInheritedWithoutMutatingConfig(t *testing.T) {
	profile := effectiveProfile()
	profile.LLMProfile = " configured-profile "
	effective, err := resolveEffectiveProfile(t, profile, ResolveInput{})
	if err != nil {
		t.Fatalf("Resolve() error = %v", err)
	}
	if got := effective.Config.Pipelines["main"].LLMProfile; got != " configured-profile " {
		t.Fatalf("effective config pipeline llm profile = %q, want preserved programmatic value", got)
	}
	resolved := effective.ResolvedPipeline
	if got := resolved.Chunk.LLMProfile; got != "configured-profile" {
		t.Fatalf("resolved chunk profile = %q, want inherited profile", got)
	}
	if got := resolved.Steps[0].ArtifactLanes[0].Extract.LLMProfile; got != "configured-profile" {
		t.Fatalf("resolved extract profile = %q, want inherited profile", got)
	}
}

func TestEffectiveConfigValidatorOverridesRemainDistinctAndOrdered(t *testing.T) {
	tests := []struct {
		name  string
		value pipeline.ValidatorOverride
		want  []string
	}{
		{
			name: "omitted uses default",
			want: []string{"default-validator"},
		},
		{
			name:  "explicit empty",
			value: pipeline.ValidatorOverride{Set: true},
			want:  nil,
		},
		{
			name: "configured order",
			value: pipeline.ValidatorOverride{
				Set: true,
				Validators: []pipeline.ModuleBinding{
					pipeline.Binding("configured-a"),
					pipeline.Binding("configured-b"),
				},
			},
			want: []string{"configured-a", "configured-b"},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			profile := effectiveProfile()
			lane := profile.Artifacts["lane"]
			lane.Extract.Validators = tt.value
			profile.Artifacts["lane"] = lane
			effective, err := resolveEffectiveProfile(t, profile, ResolveInput{})
			if err != nil {
				t.Fatalf("Resolve() error = %v", err)
			}
			chain := findEffectiveValidatorChain(effective.ResolvedPipeline, pipeline.StageExtract, "lane")
			got := make([]string, len(chain.Validators))
			for i, validator := range chain.Validators {
				got[i] = validator.Binding.Module
			}
			if len(got) != len(tt.want) {
				t.Fatalf("validator chain = %#v, want %v", got, tt.want)
			}
			for i := range got {
				if got[i] != tt.want[i] {
					t.Fatalf("validator chain = %#v, want %v", got, tt.want)
				}
			}
		})
	}
}

func TestEffectiveConfigAndResolutionInputsDoNotAliasSource(t *testing.T) {
	profile := effectiveProfile()
	profile.Chunk.Options = map[string]any{"nested": map[string]any{"safe": "source"}}
	profile.Chunk.References = pipeline.ExternalReferenceMap(map[string]string{"chunk-ref": "chunk.txt"})
	lane := profile.Artifacts["lane"]
	lane.Extract.Validators = pipeline.ValidatorOverride{
		Set: true,
		Validators: []pipeline.ModuleBinding{{
			Module:  "configured-a",
			Options: map[string]any{"nested": map[string]any{"safe": "validator-source"}},
		}},
	}
	profile.Artifacts["lane"] = lane
	cfg := configForEffectiveTests(t, profile)
	only := []string{"lane"}
	overrides := []pipeline.ReferenceBinding{{Stage: pipeline.StageChunk, SlotName: "chunk-ref", Source: "source.txt"}}
	effective, err := cfg.Resolve(ResolveInput{
		PipelineID:         "main",
		Only:               only,
		ReferenceOverrides: overrides,
		Catalog:            effectiveCatalog(t),
	})
	if err != nil {
		t.Fatalf("Resolve() error = %v", err)
	}

	effective.Config.Pipelines["main"].Chunk.Options["nested"].(map[string]any)["safe"] = "effective-config"
	effective.ResolvedPipeline.Chunk.Options["nested"].(map[string]any)["safe"] = "resolved-pipeline"
	effective.ResolvedPipeline.ChunkReferences.Bindings[0].Source = "resolved-reference"
	effective.ResolvedPipeline.ValidatorChains[1].Validators[0].Binding.Options["nested"].(map[string]any)["safe"] = "resolved-validator"
	effective.Only[0] = "mutated-only"
	effective.ReferenceOverrides[0].Source = "mutated-override"

	if got := cfg.Pipelines["main"].Chunk.Options["nested"].(map[string]any)["safe"]; got != "source" {
		t.Fatalf("source config option was aliased: %v", got)
	}
	if got := cfg.Pipelines["main"].Chunk.References["chunk-ref"].Path; got != "chunk.txt" {
		t.Fatalf("source config references were aliased: %v", got)
	}
	if only[0] != "lane" || overrides[0].Source != "source.txt" {
		t.Fatal("resolution inputs were aliased")
	}
}

func effectiveProfile() pipeline.PipelineProfile {
	return pipeline.PipelineProfile{
		ID:    "main",
		Input: pipeline.Binding("input"),
		Artifacts: map[string]pipeline.ArtifactLaneProfile{
			"lane": {Extract: pipeline.Binding("extract")},
		},
	}
}

func configForEffectiveTests(t *testing.T, profile pipeline.PipelineProfile) Config {
	t.Helper()
	cfg := Default()
	cfg.Pipelines = map[string]pipeline.PipelineProfile{"main": profile}
	return cfg
}

func resolveEffectiveProfile(t *testing.T, profile pipeline.PipelineProfile, input ResolveInput) (EffectiveConfig, error) {
	t.Helper()
	cfg := configForEffectiveTests(t, profile)
	if input.PipelineID == "" {
		input.PipelineID = "main"
	}
	if input.Catalog.Inputs == nil {
		input.Catalog = effectiveCatalog(t)
	}
	return cfg.Resolve(input)
}

func findEffectiveValidatorChain(resolved pipeline.ResolvedPipeline, stage pipeline.ModuleStage, laneID string) pipeline.ResolvedValidatorChain {
	for _, chain := range resolved.ValidatorChains {
		if chain.Stage == stage && chain.LaneID == laneID {
			return chain
		}
	}
	return pipeline.ResolvedValidatorChain{}
}

type effectiveArtifact struct {
	Value string `json:"value"`
}

const effectiveArtifactKind contracts.ArtifactKind = "test/effective"

type effectiveCodec struct{}

func (effectiveCodec) Kind() contracts.ArtifactKind { return effectiveArtifactKind }
func (effectiveCodec) Schema() contracts.ArtifactSchema {
	return contracts.ArtifactSchema{
		ID:         "effective-schema",
		Name:       "Effective artifact",
		Version:    "1",
		JSONSchema: []byte(`{"type":"object"}`),
	}
}
func (effectiveCodec) MediaType() string { return "application/json" }
func (effectiveCodec) EncodeCandidate(value effectiveArtifact) ([]byte, error) {
	return json.Marshal(value)
}
func (effectiveCodec) Encode(value effectiveArtifact) ([]byte, error) {
	return json.Marshal(value)
}
func (effectiveCodec) Decode(content []byte) (effectiveArtifact, error) {
	var value effectiveArtifact
	err := json.Unmarshal(content, &value)
	return value, err
}

func (codec effectiveCodec) DecodeCandidate(content []byte) (effectiveArtifact, error) {
	return codec.Decode(content)
}

type effectiveInput struct{ key string }

func (m effectiveInput) Key() string { return m.key }
func (m effectiveInput) Parse(context.Context, contracts.ParseRequest) (*source.SourceDocument, error) {
	return &source.SourceDocument{}, nil
}

type effectiveChunker struct{ key string }

func (m effectiveChunker) Key() string { return m.key }
func (m effectiveChunker) ReferenceSlots() []contracts.ReferenceSlot {
	if m.key == pipeline.DefaultChunkModule {
		return []contracts.ReferenceSlot{{Name: "chunk-ref"}}
	}
	return nil
}
func (m effectiveChunker) Plan(context.Context, contracts.ChunkRequest) (contracts.ChunkPlanResult, error) {
	return contracts.ChunkPlanResult{}, nil
}

type effectiveExtractor struct{ key string }

func (m effectiveExtractor) Key() string                               { return m.key }
func (m effectiveExtractor) ReferenceSlots() []contracts.ReferenceSlot { return nil }
func (m effectiveExtractor) Extract(context.Context, contracts.TypedExtractionRequest) (contracts.TypedExtractionResult[effectiveArtifact], error) {
	return contracts.TypedExtractionResult[effectiveArtifact]{}, nil
}

type effectiveMerger struct{ key string }

func (m effectiveMerger) Key() string { return m.key }
func (m effectiveMerger) Merge(context.Context, contracts.TypedMergeRequest[effectiveArtifact]) (contracts.TypedMergeResult[effectiveArtifact], error) {
	return contracts.TypedMergeResult[effectiveArtifact]{}, nil
}

type effectiveNormalizer struct{ key string }

func (m effectiveNormalizer) Key() string                               { return m.key }
func (m effectiveNormalizer) ReferenceSlots() []contracts.ReferenceSlot { return nil }
func (m effectiveNormalizer) Normalize(context.Context, contracts.TypedNormalizeRequest[effectiveArtifact]) (contracts.TypedNormalizeResult[effectiveArtifact], error) {
	return contracts.TypedNormalizeResult[effectiveArtifact]{}, nil
}

type effectiveOutput struct{ key string }

func (m effectiveOutput) Key() string { return m.key }
func (m effectiveOutput) Encode(context.Context, contracts.OutputRequest) (contracts.OutputResult, error) {
	return contracts.OutputResult{}, nil
}

type effectiveValidator struct {
	name  string
	class contracts.ExecutionClass
}

func (v effectiveValidator) Name() string                             { return v.name }
func (v effectiveValidator) ExecutionClass() contracts.ExecutionClass { return v.class }
func (v effectiveValidator) Validate(context.Context, contracts.TypedValidationRequest[effectiveArtifact]) (contracts.ValidationResult, error) {
	return contracts.ValidationResult{Approved: true}, nil
}

func effectiveCatalog(t *testing.T) pipeline.ModuleCatalog {
	t.Helper()
	catalog := pipeline.ModuleCatalog{
		Inputs:          pipeline.NewInputAdapterRegistry(),
		Chunkers:        pipeline.NewChunkerRegistry(),
		ArtifactCodecs:  pipeline.NewArtifactCodecRegistry(),
		Extractors:      pipeline.NewExtractorRegistry(),
		Mergers:         pipeline.NewMergerRegistry(),
		Normalizers:     pipeline.NewNormalizerRegistry(),
		Validators:      pipeline.NewValidatorRegistry(),
		ValidatorChains: pipeline.NewValidatorChainRegistry(),
		Outputs:         pipeline.NewOutputEncoderRegistry(),
	}
	if err := pipeline.RegisterArtifactCodec(catalog.ArtifactCodecs, effectiveCodec{}); err != nil {
		t.Fatal(err)
	}
	if err := catalog.Inputs.RegisterWithSpec(pipeline.ModuleSpec{Key: "input", Stage: pipeline.StageInput, ExecutionClass: contracts.ExecutionClassDeterministic, Provides: []string{"source"}}, func() (contracts.InputAdapter, error) {
		return effectiveInput{key: "input"}, nil
	}); err != nil {
		t.Fatal(err)
	}
	chunkSpec := pipeline.ModuleSpec{
		Key:            pipeline.DefaultChunkModule,
		Stage:          pipeline.StageChunk,
		ExecutionClass: contracts.ExecutionClassLLMBacked,
		Requires:       []string{"source"},
		Provides:       []string{"chunk"},
		ReferenceSlots: []contracts.ReferenceSlot{{Name: "chunk-ref"}},
	}
	chunkOptions := func(options map[string]any) error { return pipeline.RejectUnknownOptions(options, "size", "nested") }
	if err := catalog.Chunkers.RegisterBuilderWithSpec(chunkSpec, chunkOptions, func(pipeline.BuildRequest) (contracts.Chunker, error) {
		return effectiveChunker{key: pipeline.DefaultChunkModule}, nil
	}); err != nil {
		t.Fatal(err)
	}
	if err := catalog.Chunkers.RegisterBuilderWithSpec(pipeline.ModuleSpec{Key: "needs-capability", Stage: pipeline.StageChunk, ExecutionClass: contracts.ExecutionClassDeterministic, Requires: []string{"missing"}}, chunkOptions, func(pipeline.BuildRequest) (contracts.Chunker, error) {
		return effectiveChunker{key: "needs-capability"}, nil
	}); err != nil {
		t.Fatal(err)
	}
	if err := pipeline.RegisterExtractor(catalog.Extractors, pipeline.ModuleSpec{Key: "extract", Stage: pipeline.StageExtract, ExecutionClass: contracts.ExecutionClassLLMBacked, ArtifactKind: effectiveArtifactKind, Requires: []string{"chunk"}, Provides: []string{"candidate"}}, func() (contracts.Extractor[effectiveArtifact], error) {
		return effectiveExtractor{key: "extract"}, nil
	}); err != nil {
		t.Fatal(err)
	}
	if err := pipeline.RegisterMerger(catalog.Mergers, pipeline.ModuleSpec{Key: pipeline.DefaultMergeModule, Stage: pipeline.StageMerge, ExecutionClass: contracts.ExecutionClassLLMBacked, ArtifactKind: effectiveArtifactKind, Requires: []string{"candidate"}, Provides: []string{"merged"}}, func() (contracts.Merger[effectiveArtifact], error) {
		return effectiveMerger{key: pipeline.DefaultMergeModule}, nil
	}); err != nil {
		t.Fatal(err)
	}
	if err := pipeline.RegisterMerger(catalog.Mergers, pipeline.ModuleSpec{Key: "other-merge", Stage: pipeline.StageMerge, ExecutionClass: contracts.ExecutionClassDeterministic, ArtifactKind: "other-kind", Requires: []string{"candidate"}, Provides: []string{"merged"}}, func() (contracts.Merger[effectiveArtifact], error) {
		return effectiveMerger{key: "other-merge"}, nil
	}); err != nil {
		t.Fatal(err)
	}
	if err := pipeline.RegisterNormalizer(catalog.Normalizers, pipeline.ModuleSpec{Key: pipeline.DefaultNormalizeModule, Stage: pipeline.StageNormalize, ExecutionClass: contracts.ExecutionClassLLMBacked, ArtifactKind: effectiveArtifactKind, Requires: []string{"merged"}, Provides: []string{"normalized"}}, func() (contracts.Normalizer[effectiveArtifact], error) {
		return effectiveNormalizer{key: pipeline.DefaultNormalizeModule}, nil
	}); err != nil {
		t.Fatal(err)
	}
	if err := catalog.Outputs.RegisterWithSpec(pipeline.ModuleSpec{Key: pipeline.DefaultOutputModule, Stage: pipeline.StageOutput, ExecutionClass: contracts.ExecutionClassDeterministic, Requires: []string{"normalized"}}, func() (contracts.OutputEncoder, error) {
		return effectiveOutput{key: pipeline.DefaultOutputModule}, nil
	}); err != nil {
		t.Fatal(err)
	}
	for _, validator := range []struct {
		key   string
		class contracts.ExecutionClass
	}{
		{key: "default-validator", class: contracts.ExecutionClassDeterministic},
		{key: "configured-a", class: contracts.ExecutionClassDeterministic},
		{key: "configured-b", class: contracts.ExecutionClassDeterministic},
		{key: "llm-validator", class: contracts.ExecutionClassLLMBacked},
	} {
		if err := pipeline.RegisterTypedValidatorBuilder(catalog.Validators, effectiveArtifactKind, pipeline.ValidatorSpec{Key: validator.key, ExecutionClass: validator.class}, func(options map[string]any) error {
			return pipeline.RejectUnknownOptions(options, "nested")
		}, func(pipeline.BuildRequest) (contracts.TypedValidator[effectiveArtifact], error) {
			return effectiveValidator{name: validator.key, class: validator.class}, nil
		}); err != nil {
			t.Fatal(err)
		}
	}
	if err := pipeline.RegisterTypedValidatorBuilder(catalog.Validators, effectiveArtifactKind, pipeline.ValidatorSpec{Key: "option-validator", ExecutionClass: contracts.ExecutionClassDeterministic}, func(options map[string]any) error {
		return pipeline.RejectUnknownOptions(options, "allowed")
	}, func(pipeline.BuildRequest) (contracts.TypedValidator[effectiveArtifact], error) {
		return effectiveValidator{name: "option-validator", class: contracts.ExecutionClassDeterministic}, nil
	}); err != nil {
		t.Fatal(err)
	}
	if err := catalog.ValidatorChains.Register(pipeline.ValidatorChainMapping{Stage: pipeline.StageExtract, Module: "extract", Validators: []pipeline.ModuleBinding{pipeline.Binding("default-validator")}}); err != nil {
		t.Fatal(err)
	}
	return catalog
}
