package plan

import (
	"io/fs"
	"reflect"
	"sort"
	"strings"
	"testing"

	"github.com/tidbcloud/AgentTeam/presets"
)

func TestOfficialWorkflowPlanAuthorReferenceCompiles(t *testing.T) {
	for _, locale := range []string{
		presets.LocaleEnglish,
		presets.LocaleSimplifiedChinese,
		presets.LocaleJapanese,
	} {
		t.Run(locale, func(t *testing.T) {
			skillFS, err := presets.OfficialSkillLibraryFS("agentteam-workflow-plan-author-skills", locale)
			if err != nil {
				t.Fatalf("OfficialSkillLibraryFS(%q): %v", locale, err)
			}
			source, err := fs.ReadFile(skillFS, "skills/workflow-plan-author/references/complete-plan.yaml")
			if err != nil {
				t.Fatalf("read complete Plan reference for %q: %v", locale, err)
			}
			artifact, diagnostics, err := Compile(source)
			if err != nil {
				t.Fatalf("Compile(%q) error = %v", locale, err)
			}
			if len(diagnostics) != 0 {
				t.Fatalf("Compile(%q) diagnostics = %#v", locale, diagnostics)
			}
			if artifact == nil {
				t.Fatalf("Compile(%q) artifact = nil", locale)
			}
			if err := ValidateEnvelope(artifact); err != nil {
				t.Fatalf("ValidateEnvelope(%q): %v", locale, err)
			}
			document, parseDiagnostics := Parse(source)
			if len(parseDiagnostics) != 0 {
				t.Fatalf("Parse(%q) diagnostics = %#v", locale, parseDiagnostics)
			}
			assertOfficialReferenceDynamicInstructionResultIsScalar(t, document)
			assertOfficialReferenceCoversAuthoringCapabilities(t, document, artifact)
		})
	}
}

func assertOfficialReferenceDynamicInstructionResultIsScalar(t *testing.T, document *Document) {
	t.Helper()
	if document == nil {
		t.Fatal("official reference document is nil")
	}
	var execute, join *Node
	for index := range document.Graph.Nodes {
		node := &document.Graph.Nodes[index]
		switch node.ID {
		case "execute_dynamic_item":
			execute = node
		case "dynamic_join":
			join = node
		}
	}
	if execute == nil || execute.SendMessage == nil {
		t.Fatal("official reference is missing execute_dynamic_item send_message")
	}
	if got := execute.SendMessage.CallbackOutput["result"].Type; got != TypeNumber {
		t.Fatalf("execute_dynamic_item callback result type = %q, want %q so $dependency.result is materializable", got, TypeNumber)
	}
	if join == nil {
		t.Fatal("official reference is missing dynamic_join")
	}
	if got := join.Input["result"].Type; got != TypeNumber {
		t.Fatalf("dynamic_join input result type = %q, want %q", got, TypeNumber)
	}
}

func TestCompilerAuthoringCapabilityCatalogCoversAllPublicNamedStringTypes(t *testing.T) {
	actual := publicAuthoringNamedStringTypes(reflect.TypeOf(Document{}))
	if _, ok := actual["Predicate"]; !ok {
		t.Fatal("public authoring type graph no longer contains Predicate")
	}
	// Predicate carries the open expression grammar rather than a finite set of
	// string values, so it is intentionally not a closed authoring union.
	delete(actual, "Predicate")

	expected := map[string]struct{}{
		"ValueType":         {},
		"NodeType":          {},
		"CompletionMode":    {},
		"SwitchMode":        {},
		"JoinMode":          {},
		"FanOutFailureMode": {},
		"LoopMode":          {},
		"EndOutcome":        {},
		"SelectOneStrategy": {},
	}
	assertExactStringSet(t, "public authoring named-string closed unions", expected, actual)
}

func TestCompilerAuthoringCapabilityCatalogMatchesPublicUnionFields(t *testing.T) {
	t.Run("node configs", func(t *testing.T) {
		nodeType := reflect.TypeOf(Node{})
		actualFields := make(map[string]struct{})
		for fieldIndex := 0; fieldIndex < nodeType.NumField(); fieldIndex++ {
			field := nodeType.Field(fieldIndex)
			yamlName := strings.Split(field.Tag.Get("yaml"), ",")[0]
			switch yamlName {
			case "id", "type", "description", "input":
				continue
			}
			if yamlName == "" || yamlName == "-" {
				t.Fatalf("Node.%s is missing an authoring yaml field", field.Name)
			}
			if field.Type.Kind() != reflect.Pointer {
				t.Fatalf("Node.%s (%q) is not a pointer config field", field.Name, yamlName)
			}
			actualFields[yamlName] = struct{}{}
		}

		catalogFields := make(map[string]struct{}, len(compilerAuthoringCapabilities.Nodes))
		nodeTypes := make(map[NodeType]struct{}, len(compilerAuthoringCapabilities.Nodes))
		for _, capability := range compilerAuthoringCapabilities.Nodes {
			if capability.ConfigPresent == nil {
				t.Fatalf("node capability %q has no presence accessor", capability.Type)
			}
			if _, duplicate := catalogFields[capability.ConfigField]; duplicate {
				t.Fatalf("duplicate node config capability %q", capability.ConfigField)
			}
			if _, duplicate := nodeTypes[capability.Type]; duplicate {
				t.Fatalf("duplicate node type capability %q", capability.Type)
			}
			catalogFields[capability.ConfigField] = struct{}{}
			nodeTypes[capability.Type] = struct{}{}

			node := Node{Type: capability.Type}
			field := nodeFieldByYAMLName(t, reflect.ValueOf(&node).Elem(), capability.ConfigField)
			field.Set(reflect.New(field.Type().Elem()))
			for _, candidate := range compilerAuthoringCapabilities.Nodes {
				want := candidate.ConfigField == capability.ConfigField
				if got := candidate.ConfigPresent(&node); got != want {
					t.Fatalf("%q presence for populated %q = %v, want %v", candidate.Type, capability.ConfigField, got, want)
				}
			}
		}
		assertExactStringSet(t, "node config fields", catalogFields, actualFields)
	})

	t.Run("value expressions", func(t *testing.T) {
		expressionType := reflect.TypeOf(ValueExpr{})
		actualFields := make(map[string]struct{}, expressionType.NumField())
		for fieldIndex := 0; fieldIndex < expressionType.NumField(); fieldIndex++ {
			field := expressionType.Field(fieldIndex)
			yamlName := strings.Split(field.Tag.Get("yaml"), ",")[0]
			if yamlName == "" || yamlName == "-" {
				t.Fatalf("ValueExpr.%s is missing an authoring yaml field", field.Name)
			}
			actualFields[yamlName] = struct{}{}
		}

		catalogFields := make(map[string]struct{}, len(compilerAuthoringCapabilities.ValueExpressions))
		for _, capability := range compilerAuthoringCapabilities.ValueExpressions {
			if capability.Present == nil {
				t.Fatalf("value expression capability %q has no presence accessor", capability.Kind)
			}
			name := string(capability.Kind)
			if _, duplicate := catalogFields[name]; duplicate {
				t.Fatalf("duplicate value expression capability %q", name)
			}
			catalogFields[name] = struct{}{}

			expression := ValueExpr{}
			field := nodeFieldByYAMLName(t, reflect.ValueOf(&expression).Elem(), name)
			switch field.Kind() {
			case reflect.String:
				field.SetString("present")
			case reflect.Pointer:
				value := any("present")
				field.Set(reflect.ValueOf(&value))
			default:
				t.Fatalf("ValueExpr field %q has unsupported test kind %s", name, field.Kind())
			}
			kinds := compilerAuthoringCapabilities.presentValueExpressionKinds(expression)
			if len(kinds) != 1 || kinds[0] != capability.Kind {
				t.Fatalf("present kinds for %q = %#v, want [%q]", name, kinds, capability.Kind)
			}
		}
		assertExactStringSet(t, "value expression fields", catalogFields, actualFields)
	})
}

func TestCompilerAuthoringCapabilityCatalogIsSelfConsistent(t *testing.T) {
	assertUnique := func(label string, values []string) {
		t.Helper()
		seen := make(map[string]struct{}, len(values))
		for _, value := range values {
			if value == "" {
				t.Fatalf("%s contains an empty value", label)
			}
			if _, duplicate := seen[value]; duplicate {
				t.Fatalf("%s contains duplicate %q", label, value)
			}
			seen[value] = struct{}{}
		}
	}

	valueTypes := make([]string, 0, len(compilerAuthoringCapabilities.ValueTypes))
	for _, valueType := range compilerAuthoringCapabilities.ValueTypes {
		valueTypes = append(valueTypes, string(valueType))
	}
	assertUnique("value types", valueTypes)
	legacyValueTypes := make([]string, 0, len(compilerAuthoringCapabilities.LegacyValueTypes))
	supportedValueTypes := make(map[ValueType]bool, len(compilerAuthoringCapabilities.ValueTypes))
	for _, valueType := range compilerAuthoringCapabilities.ValueTypes {
		supportedValueTypes[valueType] = true
	}
	for _, valueType := range compilerAuthoringCapabilities.LegacyValueTypes {
		legacyValueTypes = append(legacyValueTypes, string(valueType))
		if !supportedValueTypes[valueType] {
			t.Fatalf("legacy value type %q is not supported", valueType)
		}
	}
	assertUnique("legacy value types", legacyValueTypes)

	var nodeTypes, nodeConfigs []string
	semanticOpcodes := make(map[ArtifactOpcode]string)
	addSemanticOpcode := func(owner string, opcode ArtifactOpcode) {
		t.Helper()
		if opcode == "" {
			t.Fatalf("%s has an empty opcode", owner)
		}
		if previous, duplicate := semanticOpcodes[opcode]; duplicate {
			t.Fatalf("%s and %s share semantic opcode %q", previous, owner, opcode)
		}
		semanticOpcodes[opcode] = owner
	}
	for _, node := range compilerAuthoringCapabilities.Nodes {
		nodeTypes = append(nodeTypes, string(node.Type))
		nodeConfigs = append(nodeConfigs, node.ConfigField)
		if node.ConfigPresent == nil {
			t.Fatalf("node %q has no config presence accessor", node.Type)
		}
		if node.DynamicSourcePorts && len(node.SourcePorts) != 0 {
			t.Fatalf("dynamic source node %q also declares fixed source ports", node.Type)
		}
		if node.DynamicTargetPorts && len(node.TargetPorts) != 0 {
			t.Fatalf("dynamic target node %q also declares fixed target ports", node.Type)
		}
		assertUnique(string(node.Type)+" source ports", node.SourcePorts)
		assertUnique(string(node.Type)+" target ports", node.TargetPorts)
		if node.Opcode != "" {
			addSemanticOpcode("node "+string(node.Type), node.Opcode)
		}
	}
	assertUnique("node types", nodeTypes)
	assertUnique("node config fields", nodeConfigs)

	var valueLiterals []string
	for _, literal := range compilerAuthoringCapabilities.ValueLiterals {
		valueLiterals = append(valueLiterals, string(literal))
	}
	assertUnique("value literals", valueLiterals)

	for _, modes := range []struct {
		label   string
		opcodes map[string]ArtifactOpcode
	}{
		{"completion mode", stringOpcodeMap(compilerAuthoringCapabilities.CompletionModes)},
		{"switch mode", stringOpcodeMap(compilerAuthoringCapabilities.SwitchModes)},
		{"join mode", stringOpcodeMap(compilerAuthoringCapabilities.JoinModes)},
		{"loop mode", stringOpcodeMap(compilerAuthoringCapabilities.LoopModes)},
		{"end outcome", stringOpcodeMap(compilerAuthoringCapabilities.EndOutcomes)},
	} {
		for mode, opcode := range modes.opcodes {
			if mode == "" {
				t.Fatalf("%s contains an empty value", modes.label)
			}
			addSemanticOpcode(modes.label+" "+mode, opcode)
		}
	}
	for mode := range compilerAuthoringCapabilities.FanOutFailureModes {
		if mode == "" {
			t.Fatal("fan_out failure modes contain an empty value")
		}
	}

	var expressionLiterals []string
	for _, literal := range compilerAuthoringCapabilities.ExpressionLiterals {
		expressionLiterals = append(expressionLiterals, string(literal))
	}
	assertUnique("expression literals", expressionLiterals)

	expressionOpcodes := make(map[ArtifactExpressionOp]string)
	addExpressionOpcode := func(owner string, opcode ArtifactExpressionOp) {
		t.Helper()
		if opcode == "" {
			t.Fatalf("%s has an empty expression opcode", owner)
		}
		if previous, duplicate := expressionOpcodes[opcode]; duplicate {
			t.Fatalf("%s and %s share expression opcode %q", previous, owner, opcode)
		}
		expressionOpcodes[opcode] = owner
	}
	for operator, opcode := range compilerAuthoringCapabilities.ExpressionOperators {
		if operator == "" {
			t.Fatal("expression operators contain an empty symbol")
		}
		addExpressionOpcode("operator "+operator, opcode)
	}
	for name, function := range compilerAuthoringCapabilities.ExpressionFunctions {
		if name == "" || function.Arity < 1 || function.ResultType == "" {
			t.Fatalf("invalid expression function capability %q: %#v", name, function)
		}
		addExpressionOpcode("function "+name, function.Opcode)
	}
	addExpressionOpcode("dynamic array literal", compilerAuthoringCapabilities.ArrayExpressionOp)

	var references []string
	loadSpaces := make(map[ArtifactLoadSpace]string)
	for _, reference := range compilerAuthoringCapabilities.ReferenceSpaces {
		references = append(references, string(reference.Reference))
		if reference.LoadSpace == "" {
			t.Fatalf("reference %q has an empty load space", reference.Reference)
		}
		if previous, duplicate := loadSpaces[reference.LoadSpace]; duplicate {
			t.Fatalf("references %s and %s share load space %q", previous, reference.Reference, reference.LoadSpace)
		}
		loadSpaces[reference.LoadSpace] = string(reference.Reference)
	}
	assertUnique("reference spaces", references)
}

func TestCompilerAuthoringCapabilityCatalogRejectsUnknownVariantsWithoutFallback(t *testing.T) {
	tests := []Node{
		{Type: NodeSendMessage, SendMessage: &SendMessageConfig{Completion: CompletionMode("future")}},
		{Type: NodeSwitch, Switch: &SwitchConfig{Mode: SwitchMode("future")}},
		{Type: NodeJoin, Join: &JoinConfig{Mode: JoinMode("future")}},
		{Type: NodeLoop, Loop: &AgentTeamConfig{Mode: LoopMode("future")}},
		{Type: NodeEnd, End: &EndConfig{Outcome: EndOutcome("future")}},
	}
	for _, node := range tests {
		t.Run(string(node.Type), func(t *testing.T) {
			if opcode, err := compilerAuthoringCapabilities.opcode(&node); err == nil {
				t.Fatalf("unknown %q variant silently lowered to %q", node.Type, opcode)
			}
		})
	}
}

func TestCompilerAuthoringCapabilityCatalogOpcodeMappings(t *testing.T) {
	tests := []struct {
		name    string
		variant string
		node    Node
		opcode  ArtifactOpcode
	}{
		{"send sent", "completion/sent", Node{Type: NodeSendMessage, SendMessage: &SendMessageConfig{Completion: CompletionSent}}, OpcodeSendMessageSent},
		{"send callback", "completion/callback", Node{Type: NodeSendMessage, SendMessage: &SendMessageConfig{Completion: CompletionCallback}}, OpcodeSendMessageCallback},
		{"set variable", "node/set_variable", Node{Type: NodeSetVariable, SetVariable: &SetVariableConfig{}}, OpcodeSetVariable},
		{"wait", "node/wait", Node{Type: NodeWait, Wait: &WaitConfig{}}, OpcodeWait},
		{"request approval", "node/request_approval", Node{Type: NodeRequestApproval, RequestApproval: &RequestApprovalConfig{}}, OpcodeRequestApproval},
		{"switch first", "switch/first_match", Node{Type: NodeSwitch, Switch: &SwitchConfig{Mode: SwitchFirstMatch}}, OpcodeSwitchFirstMatch},
		{"switch all", "switch/all_match", Node{Type: NodeSwitch, Switch: &SwitchConfig{Mode: SwitchAllMatch}}, OpcodeSwitchAllMatch},
		{"fork", "node/fork", Node{Type: NodeFork, Fork: &ForkConfig{}}, OpcodeFork},
		{"join all", "join/all", Node{Type: NodeJoin, Join: &JoinConfig{Mode: JoinAll}}, OpcodeJoinAll},
		{"join any", "join/any", Node{Type: NodeJoin, Join: &JoinConfig{Mode: JoinAny}}, OpcodeJoinAny},
		{"fan out", "node/fan_out", Node{Type: NodeFanOut, FanOut: &FanOutConfig{}}, OpcodeFanOut},
		{"loop counted", "loop/counted", Node{Type: NodeLoop, Loop: &AgentTeamConfig{Mode: LoopCounted}}, OpcodeLoop},
		{"end succeeded", "end/succeeded", Node{Type: NodeEnd, End: &EndConfig{Outcome: EndSucceeded}}, OpcodeEndSucceeded},
		{"end failed", "end/failed", Node{Type: NodeEnd, End: &EndConfig{Outcome: EndFailed}}, OpcodeEndFailed},
	}
	actualVariants := make(map[string]struct{}, len(tests))
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			if _, duplicate := actualVariants[test.variant]; duplicate {
				t.Fatalf("duplicate explicit mapping case %q", test.variant)
			}
			actualVariants[test.variant] = struct{}{}
			opcode, err := compilerAuthoringCapabilities.opcode(&test.node)
			if err != nil {
				t.Fatalf("opcode: %v", err)
			}
			if opcode != test.opcode {
				t.Fatalf("opcode = %q, want %q", opcode, test.opcode)
			}
		})
	}
	expectedVariants := make(map[string]struct{})
	for _, node := range compilerAuthoringCapabilities.Nodes {
		if node.Opcode != "" {
			expectedVariants["node/"+string(node.Type)] = struct{}{}
		}
	}
	for mode := range compilerAuthoringCapabilities.CompletionModes {
		expectedVariants["completion/"+string(mode)] = struct{}{}
	}
	for mode := range compilerAuthoringCapabilities.SwitchModes {
		expectedVariants["switch/"+string(mode)] = struct{}{}
	}
	for mode := range compilerAuthoringCapabilities.JoinModes {
		expectedVariants["join/"+string(mode)] = struct{}{}
	}
	for mode := range compilerAuthoringCapabilities.LoopModes {
		expectedVariants["loop/"+string(mode)] = struct{}{}
	}
	for outcome := range compilerAuthoringCapabilities.EndOutcomes {
		expectedVariants["end/"+string(outcome)] = struct{}{}
	}
	assertExactStringSet(t, "explicit node/mode opcode cases", expectedVariants, actualVariants)
}

func TestCompilerAuthoringCapabilityCatalogExpressionAndReferenceMappings(t *testing.T) {
	operatorCases := map[string]ArtifactExpressionOp{
		"!":  ArtifactOpNot,
		"&&": ArtifactOpAnd,
		"||": ArtifactOpOr,
		"==": ArtifactOpEqual,
		"!=": ArtifactOpNotEqual,
		"<":  ArtifactOpLess,
		"<=": ArtifactOpLessEqual,
		">":  ArtifactOpGreater,
		">=": ArtifactOpGreaterEq,
		"in": ArtifactOpIn,
	}
	assertExactStringSet(t, "explicit expression operator cases", stringKeySet(operatorCases), stringKeySet(compilerAuthoringCapabilities.ExpressionOperators))
	for operator, opcode := range operatorCases {
		if got := compilerAuthoringCapabilities.ExpressionOperators[operator]; got != opcode {
			t.Fatalf("operator %q opcode = %q, want %q", operator, got, opcode)
		}
	}
	if compilerAuthoringCapabilities.ArrayExpressionOp != ArtifactOpArray {
		t.Fatalf("array expression opcode = %q, want %q", compilerAuthoringCapabilities.ArrayExpressionOp, ArtifactOpArray)
	}

	functionCases := map[string]expressionFunctionCapability{
		"contains":   {Opcode: ArtifactOpContains, ResultType: TypeBoolean, Arity: 2},
		"startsWith": {Opcode: ArtifactOpStartsWith, ResultType: TypeBoolean, Arity: 2},
		"endsWith":   {Opcode: ArtifactOpEndsWith, ResultType: TypeBoolean, Arity: 2},
		"length":     {Opcode: ArtifactOpLength, ResultType: TypeNumber, Arity: 1},
	}
	assertExactStringSet(t, "explicit expression function cases", stringKeySet(functionCases), stringKeySet(compilerAuthoringCapabilities.ExpressionFunctions))
	for name, function := range functionCases {
		if got := compilerAuthoringCapabilities.ExpressionFunctions[name]; got != function {
			t.Fatalf("function %q capability = %#v, want %#v", name, got, function)
		}
	}

	referenceCases := map[referenceSpace]ArtifactLoadSpace{
		referencePlanInput:        ArtifactLoadPlanInput,
		referenceActivationInput:  ArtifactLoadActivationInput,
		referenceActivationOutput: ArtifactLoadActivationOutput,
		referenceRunState:         ArtifactLoadRunState,
		referenceLoopState:        ArtifactLoadLoopState,
		referenceLoopIteration:    ArtifactLoadLoopIteration,
		referenceLoopIsLast:       ArtifactLoadLoopIsLast,
		referenceJoinBranch:       ArtifactLoadJoinBranch,
		referenceJoinWinnerBranch: ArtifactLoadJoinWinnerBranch,
		referenceJoinWinnerValue:  ArtifactLoadJoinWinnerValue,
		referenceFanOutItem:       ArtifactLoadFanOutItem,
		referenceJoinItems:        ArtifactLoadJoinItems,
	}
	actualReferences := make(map[string]struct{}, len(compilerAuthoringCapabilities.ReferenceSpaces))
	for _, capability := range compilerAuthoringCapabilities.ReferenceSpaces {
		actualReferences[string(capability.Reference)] = struct{}{}
		if got := referenceCases[capability.Reference]; got != capability.LoadSpace {
			t.Fatalf("reference %q load space = %q, want %q", capability.Reference, capability.LoadSpace, got)
		}
	}
	expectedReferences := make(map[string]struct{}, len(referenceCases))
	for reference := range referenceCases {
		expectedReferences[string(reference)] = struct{}{}
	}
	assertExactStringSet(t, "explicit reference-space cases", expectedReferences, actualReferences)
}

func stringKeySet[V any](values map[string]V) map[string]struct{} {
	result := make(map[string]struct{}, len(values))
	for key := range values {
		result[key] = struct{}{}
	}
	return result
}

type authoringCapabilityCoverage struct {
	valueTypes          map[ValueType]struct{}
	nodeTypes           map[NodeType]struct{}
	completionModes     map[CompletionMode]struct{}
	switchModes         map[SwitchMode]struct{}
	joinModes           map[JoinMode]struct{}
	fanOutFailureModes  map[FanOutFailureMode]struct{}
	loopModes           map[LoopMode]struct{}
	endOutcomes         map[EndOutcome]struct{}
	valueExprKinds      map[valueExpressionKind]struct{}
	valueLiteralKinds   map[valueLiteralKind]struct{}
	expressionOperators map[string]struct{}
	expressionFunctions map[string]struct{}
	expressionLiterals  map[expressionLiteralKind]struct{}
	expressionOpcodes   map[ArtifactExpressionOp]struct{}
	referenceLoadSpaces map[ArtifactLoadSpace]struct{}
	fixedSourcePorts    map[string]struct{}
	fixedTargetPorts    map[string]struct{}
}

func newAuthoringCapabilityCoverage() authoringCapabilityCoverage {
	return authoringCapabilityCoverage{
		valueTypes:          make(map[ValueType]struct{}),
		nodeTypes:           make(map[NodeType]struct{}),
		completionModes:     make(map[CompletionMode]struct{}),
		switchModes:         make(map[SwitchMode]struct{}),
		joinModes:           make(map[JoinMode]struct{}),
		fanOutFailureModes:  make(map[FanOutFailureMode]struct{}),
		loopModes:           make(map[LoopMode]struct{}),
		endOutcomes:         make(map[EndOutcome]struct{}),
		valueExprKinds:      make(map[valueExpressionKind]struct{}),
		valueLiteralKinds:   make(map[valueLiteralKind]struct{}),
		expressionOperators: make(map[string]struct{}),
		expressionFunctions: make(map[string]struct{}),
		expressionLiterals:  make(map[expressionLiteralKind]struct{}),
		expressionOpcodes:   make(map[ArtifactExpressionOp]struct{}),
		referenceLoadSpaces: make(map[ArtifactLoadSpace]struct{}),
		fixedSourcePorts:    make(map[string]struct{}),
		fixedTargetPorts:    make(map[string]struct{}),
	}
}

func assertOfficialReferenceCoversAuthoringCapabilities(t *testing.T, document *Document, artifact *Artifact) {
	t.Helper()
	actual := newAuthoringCapabilityCoverage()
	collectAuthoringCapabilityCoverage(reflect.ValueOf(document), &actual)
	collectAuthoringExpressionSyntax(t, reflect.ValueOf(document), &actual)
	collectArtifactAuthoringCoverage(reflect.ValueOf(artifact), &actual)
	collectFixedPortCoverage(document, &actual)

	expected := newAuthoringCapabilityCoverage()
	legacyValueTypes := make(map[ValueType]bool, len(compilerAuthoringCapabilities.LegacyValueTypes))
	for _, valueType := range compilerAuthoringCapabilities.LegacyValueTypes {
		legacyValueTypes[valueType] = true
	}
	for _, valueType := range compilerAuthoringCapabilities.ValueTypes {
		if !legacyValueTypes[valueType] {
			expected.valueTypes[valueType] = struct{}{}
		}
	}
	for _, node := range compilerAuthoringCapabilities.Nodes {
		expected.nodeTypes[node.Type] = struct{}{}
	}
	for mode := range compilerAuthoringCapabilities.CompletionModes {
		expected.completionModes[mode] = struct{}{}
	}
	for mode := range compilerAuthoringCapabilities.SwitchModes {
		expected.switchModes[mode] = struct{}{}
	}
	for mode := range compilerAuthoringCapabilities.JoinModes {
		expected.joinModes[mode] = struct{}{}
	}
	for mode := range compilerAuthoringCapabilities.FanOutFailureModes {
		expected.fanOutFailureModes[mode] = struct{}{}
	}
	for mode := range compilerAuthoringCapabilities.LoopModes {
		expected.loopModes[mode] = struct{}{}
	}
	for outcome := range compilerAuthoringCapabilities.EndOutcomes {
		expected.endOutcomes[outcome] = struct{}{}
	}
	for _, expression := range compilerAuthoringCapabilities.ValueExpressions {
		expected.valueExprKinds[expression.Kind] = struct{}{}
	}
	for _, literal := range compilerAuthoringCapabilities.ValueLiterals {
		expected.valueLiteralKinds[literal] = struct{}{}
	}
	for operator, opcode := range compilerAuthoringCapabilities.ExpressionOperators {
		expected.expressionOperators[operator] = struct{}{}
		expected.expressionOpcodes[opcode] = struct{}{}
	}
	for function, capability := range compilerAuthoringCapabilities.ExpressionFunctions {
		expected.expressionFunctions[function] = struct{}{}
		expected.expressionOpcodes[capability.Opcode] = struct{}{}
	}
	for _, literal := range compilerAuthoringCapabilities.ExpressionLiterals {
		expected.expressionLiterals[literal] = struct{}{}
	}
	expected.expressionOpcodes[ArtifactOpConst] = struct{}{}
	expected.expressionOpcodes[ArtifactOpLoad] = struct{}{}
	expected.expressionOpcodes[compilerAuthoringCapabilities.ArrayExpressionOp] = struct{}{}
	for _, reference := range compilerAuthoringCapabilities.ReferenceSpaces {
		expected.referenceLoadSpaces[reference.LoadSpace] = struct{}{}
	}
	for _, node := range compilerAuthoringCapabilities.Nodes {
		if !node.DynamicSourcePorts {
			for _, port := range node.SourcePorts {
				if compiledIRTypePortDropped(node.Type, port) {
					continue
				}
				expected.fixedSourcePorts[authoringPortKey(node.Type, port)] = struct{}{}
			}
		}
		if !node.DynamicTargetPorts {
			for _, port := range node.TargetPorts {
				expected.fixedTargetPorts[authoringPortKey(node.Type, port)] = struct{}{}
			}
		}
	}

	assertExactNamedStringSet(t, "value types", expected.valueTypes, actual.valueTypes)
	assertExactNamedStringSet(t, "node types", expected.nodeTypes, actual.nodeTypes)
	assertExactNamedStringSet(t, "send-message completion modes", expected.completionModes, actual.completionModes)
	assertExactNamedStringSet(t, "switch modes", expected.switchModes, actual.switchModes)
	assertExactNamedStringSet(t, "join modes", expected.joinModes, actual.joinModes)
	assertExactNamedStringSet(t, "fan_out failure modes", expected.fanOutFailureModes, actual.fanOutFailureModes)
	assertExactNamedStringSet(t, "loop modes", expected.loopModes, actual.loopModes)
	assertExactNamedStringSet(t, "end outcomes", expected.endOutcomes, actual.endOutcomes)
	assertExactNamedStringSet(t, "value expression kinds", expected.valueExprKinds, actual.valueExprKinds)
	assertExactNamedStringSet(t, "direct value literal kinds", expected.valueLiteralKinds, actual.valueLiteralKinds)
	assertExactStringSet(t, "expression operators", expected.expressionOperators, actual.expressionOperators)
	assertExactStringSet(t, "expression functions", expected.expressionFunctions, actual.expressionFunctions)
	assertExactNamedStringSet(t, "expression literal kinds", expected.expressionLiterals, actual.expressionLiterals)
	assertExactNamedStringSet(t, "compiled expression opcodes", expected.expressionOpcodes, actual.expressionOpcodes)
	assertExactNamedStringSet(t, "reference load spaces", expected.referenceLoadSpaces, actual.referenceLoadSpaces)
	assertExactStringSet(t, "fixed source ports", expected.fixedSourcePorts, actual.fixedSourcePorts)
	assertExactStringSet(t, "fixed target ports", expected.fixedTargetPorts, actual.fixedTargetPorts)
	assertExactStringSet(
		t,
		"public authoring YAML fields",
		publicAuthoringYAMLFields(reflect.TypeOf(Document{})),
		usedAuthoringYAMLFields(reflect.ValueOf(document)),
	)

	expectedOpcodes := make(map[ArtifactOpcode]struct{})
	for _, node := range compilerAuthoringCapabilities.Nodes {
		if node.Opcode != "" {
			expectedOpcodes[node.Opcode] = struct{}{}
		}
	}
	for _, variants := range []map[string]ArtifactOpcode{
		stringOpcodeMap(compilerAuthoringCapabilities.CompletionModes),
		stringOpcodeMap(compilerAuthoringCapabilities.SwitchModes),
		stringOpcodeMap(compilerAuthoringCapabilities.JoinModes),
		stringOpcodeMap(compilerAuthoringCapabilities.LoopModes),
		stringOpcodeMap(compilerAuthoringCapabilities.EndOutcomes),
	} {
		for _, opcode := range variants {
			expectedOpcodes[opcode] = struct{}{}
		}
	}
	actualOpcodes := make(map[ArtifactOpcode]struct{})
	for _, node := range artifact.Nodes {
		actualOpcodes[node.Opcode] = struct{}{}
	}
	// fail_plan.implicit is a compiler-owned sink rather than an authorable
	// node opcode. Dynamic fan-out deliberately leaves recoverable item
	// failures implicit so runtime can settle the item at the scope boundary.
	delete(actualOpcodes, OpcodeFailPlan)
	assertExactNamedStringSet(t, "author-facing artifact opcodes", expectedOpcodes, actualOpcodes)
}

func stringOpcodeMap[K ~string](source map[K]ArtifactOpcode) map[string]ArtifactOpcode {
	result := make(map[string]ArtifactOpcode, len(source))
	for key, value := range source {
		result[string(key)] = value
	}
	return result
}

func collectAuthoringCapabilityCoverage(value reflect.Value, coverage *authoringCapabilityCoverage) {
	if !value.IsValid() {
		return
	}
	if value.Kind() == reflect.Interface || value.Kind() == reflect.Pointer {
		if value.IsNil() {
			return
		}
		collectAuthoringCapabilityCoverage(value.Elem(), coverage)
		return
	}

	switch typed := value.Interface().(type) {
	case ValueType:
		if typed != "" {
			coverage.valueTypes[typed] = struct{}{}
		}
		return
	case NodeType:
		if typed != "" {
			coverage.nodeTypes[typed] = struct{}{}
		}
		return
	case CompletionMode:
		if typed != "" {
			coverage.completionModes[typed] = struct{}{}
		}
		return
	case SwitchMode:
		if typed != "" {
			coverage.switchModes[typed] = struct{}{}
		}
		return
	case JoinMode:
		if typed != "" {
			coverage.joinModes[typed] = struct{}{}
		}
		return
	case FanOutFailureMode:
		if typed != "" {
			coverage.fanOutFailureModes[typed] = struct{}{}
		}
		return
	case LoopMode:
		if typed != "" {
			coverage.loopModes[typed] = struct{}{}
		}
		return
	case EndOutcome:
		if typed != "" {
			coverage.endOutcomes[typed] = struct{}{}
		}
		return
	case ValueExpr:
		if strings.TrimSpace(typed.Ref) != "" {
			coverage.valueExprKinds[valueExpressionRef] = struct{}{}
		}
		if typed.Value != nil {
			coverage.valueExprKinds[valueExpressionLiteral] = struct{}{}
			switch (*typed.Value).(type) {
			case string:
				coverage.valueLiteralKinds[valueLiteralString] = struct{}{}
			case int, int8, int16, int32, int64,
				uint, uint8, uint16, uint32, uint64,
				float32, float64:
				coverage.valueLiteralKinds[valueLiteralNumber] = struct{}{}
			case bool:
				coverage.valueLiteralKinds[valueLiteralBoolean] = struct{}{}
			case map[string]any:
				coverage.valueLiteralKinds[valueLiteralObject] = struct{}{}
			case []any:
				coverage.valueLiteralKinds[valueLiteralArray] = struct{}{}
			}
		}
		if strings.TrimSpace(typed.Expr) != "" {
			coverage.valueExprKinds[valueExpressionExpr] = struct{}{}
		}
	}

	switch value.Kind() {
	case reflect.Struct:
		for fieldIndex := 0; fieldIndex < value.NumField(); fieldIndex++ {
			if !value.Type().Field(fieldIndex).IsExported() {
				continue
			}
			collectAuthoringCapabilityCoverage(value.Field(fieldIndex), coverage)
		}
	case reflect.Map:
		iter := value.MapRange()
		for iter.Next() {
			collectAuthoringCapabilityCoverage(iter.Value(), coverage)
		}
	case reflect.Slice, reflect.Array:
		for index := 0; index < value.Len(); index++ {
			collectAuthoringCapabilityCoverage(value.Index(index), coverage)
		}
	}
}

func collectAuthoringExpressionSyntax(t *testing.T, value reflect.Value, coverage *authoringCapabilityCoverage) {
	t.Helper()
	if !value.IsValid() {
		return
	}
	if value.Kind() == reflect.Interface || value.Kind() == reflect.Pointer {
		if value.IsNil() {
			return
		}
		collectAuthoringExpressionSyntax(t, value.Elem(), coverage)
		return
	}
	if value.CanInterface() {
		switch typed := value.Interface().(type) {
		case ValueExpr:
			if source := strings.TrimSpace(typed.Expr); source != "" {
				collectExpressionSourceSyntax(t, source, coverage)
			}
			return
		case Predicate:
			if source := strings.TrimSpace(string(typed)); source != "" {
				collectExpressionSourceSyntax(t, source, coverage)
			}
			return
		}
	}

	switch value.Kind() {
	case reflect.String:
		for _, match := range templatePattern.FindAllStringSubmatch(value.String(), -1) {
			if len(match) == 2 {
				collectExpressionSourceSyntax(t, strings.TrimSpace(match[1]), coverage)
			}
		}
	case reflect.Struct:
		for fieldIndex := 0; fieldIndex < value.NumField(); fieldIndex++ {
			collectAuthoringExpressionSyntax(t, value.Field(fieldIndex), coverage)
		}
	case reflect.Map:
		iter := value.MapRange()
		for iter.Next() {
			collectAuthoringExpressionSyntax(t, iter.Value(), coverage)
		}
	case reflect.Slice, reflect.Array:
		for index := 0; index < value.Len(); index++ {
			collectAuthoringExpressionSyntax(t, value.Index(index), coverage)
		}
	}
}

func collectExpressionSourceSyntax(t *testing.T, source string, coverage *authoringCapabilityCoverage) {
	t.Helper()
	tokens, ok := tokenizeTypedExpression(source)
	if !ok {
		t.Fatalf("tokenize compiler-validated reference expression %q", source)
	}
	for index, token := range tokens {
		switch token.kind {
		case planTokenString:
			coverage.expressionLiterals[expressionLiteralString] = struct{}{}
		case planTokenNumber:
			coverage.expressionLiterals[expressionLiteralNumber] = struct{}{}
		case planTokenLeftBracket:
			coverage.expressionLiterals[expressionLiteralArray] = struct{}{}
		case planTokenNot:
			coverage.expressionOperators["!"] = struct{}{}
		case planTokenAnd:
			coverage.expressionOperators["&&"] = struct{}{}
		case planTokenOr:
			coverage.expressionOperators["||"] = struct{}{}
		case planTokenOperator:
			coverage.expressionOperators[token.text] = struct{}{}
		case planTokenIdent:
			switch token.text {
			case "true":
				coverage.expressionLiterals[expressionLiteralTrue] = struct{}{}
			case "false":
				coverage.expressionLiterals[expressionLiteralFalse] = struct{}{}
			case "null":
				coverage.expressionLiterals[expressionLiteralNull] = struct{}{}
			case "in":
				coverage.expressionOperators["in"] = struct{}{}
			default:
				if index+1 < len(tokens) && tokens[index+1].kind == planTokenLeftParen {
					coverage.expressionFunctions[token.text] = struct{}{}
				}
			}
		}
	}
}

func collectArtifactAuthoringCoverage(value reflect.Value, coverage *authoringCapabilityCoverage) {
	if !value.IsValid() {
		return
	}
	if value.Kind() == reflect.Interface || value.Kind() == reflect.Pointer {
		if value.IsNil() {
			return
		}
		collectArtifactAuthoringCoverage(value.Elem(), coverage)
		return
	}
	if value.CanInterface() {
		if expression, ok := value.Interface().(ArtifactExpression); ok {
			if expression.Op != "" {
				coverage.expressionOpcodes[expression.Op] = struct{}{}
			}
			if expression.Load != nil && expression.Load.Space != "" {
				coverage.referenceLoadSpaces[expression.Load.Space] = struct{}{}
			}
			for index := range expression.Args {
				collectArtifactAuthoringCoverage(reflect.ValueOf(expression.Args[index]), coverage)
			}
			return
		}
	}

	switch value.Kind() {
	case reflect.Struct:
		for fieldIndex := 0; fieldIndex < value.NumField(); fieldIndex++ {
			collectArtifactAuthoringCoverage(value.Field(fieldIndex), coverage)
		}
	case reflect.Map:
		iter := value.MapRange()
		for iter.Next() {
			collectArtifactAuthoringCoverage(iter.Value(), coverage)
		}
	case reflect.Slice, reflect.Array:
		for index := 0; index < value.Len(); index++ {
			collectArtifactAuthoringCoverage(value.Index(index), coverage)
		}
	}
}

func collectFixedPortCoverage(document *Document, coverage *authoringCapabilityCoverage) {
	if document == nil {
		return
	}
	nodes := make(map[string]*Node, len(document.Graph.Nodes))
	for index := range document.Graph.Nodes {
		node := &document.Graph.Nodes[index]
		nodes[node.ID] = node
	}
	recordSource := func(endpoint Endpoint) {
		node := nodes[endpoint.NodeID]
		if node == nil {
			return
		}
		capability, ok := compilerAuthoringCapabilities.nodeCapability(node.Type)
		if ok && !capability.DynamicSourcePorts {
			coverage.fixedSourcePorts[authoringPortKey(node.Type, endpoint.Port)] = struct{}{}
		}
	}
	recordTarget := func(endpoint Endpoint) {
		node := nodes[endpoint.NodeID]
		if node == nil {
			return
		}
		capability, ok := compilerAuthoringCapabilities.nodeCapability(node.Type)
		if ok && !capability.DynamicTargetPorts {
			coverage.fixedTargetPorts[authoringPortKey(node.Type, endpoint.Port)] = struct{}{}
		}
	}
	recordTarget(Endpoint{NodeID: document.Graph.Entry.NodeID, Port: document.Graph.Entry.Port})
	for _, edge := range document.Graph.Edges {
		recordSource(edge.Source)
		recordTarget(edge.Target)
	}
}

func authoringPortKey(nodeType NodeType, port string) string {
	return string(nodeType) + "." + port
}

func publicAuthoringNamedStringTypes(root reflect.Type) map[string]struct{} {
	result := make(map[string]struct{})
	seen := make(map[reflect.Type]struct{})
	planPackage := reflect.TypeOf(Document{}).PkgPath()
	var visit func(reflect.Type)
	visit = func(valueType reflect.Type) {
		if valueType == nil {
			return
		}
		if _, duplicate := seen[valueType]; duplicate {
			return
		}
		seen[valueType] = struct{}{}

		switch valueType.Kind() {
		case reflect.Pointer, reflect.Slice, reflect.Array:
			visit(valueType.Elem())
		case reflect.Map:
			visit(valueType.Key())
			visit(valueType.Elem())
		case reflect.String:
			// Built-in string has no name. Only named strings declared by the
			// public Plan authoring package are closed-union candidates.
			if valueType.Name() != "" && valueType.PkgPath() == planPackage {
				result[valueType.Name()] = struct{}{}
			}
		case reflect.Struct:
			if valueType.PkgPath() != planPackage {
				return
			}
			for fieldIndex := 0; fieldIndex < valueType.NumField(); fieldIndex++ {
				field := valueType.Field(fieldIndex)
				if field.IsExported() {
					visit(field.Type)
				}
			}
		}
	}
	visit(root)
	return result
}

func publicAuthoringYAMLFields(root reflect.Type) map[string]struct{} {
	result := make(map[string]struct{})
	seen := make(map[reflect.Type]struct{})
	planPackage := reflect.TypeOf(Document{}).PkgPath()
	var visit func(reflect.Type)
	visit = func(valueType reflect.Type) {
		for valueType.Kind() == reflect.Pointer || valueType.Kind() == reflect.Slice ||
			valueType.Kind() == reflect.Array || valueType.Kind() == reflect.Map {
			valueType = valueType.Elem()
		}
		if valueType.Kind() != reflect.Struct || valueType.PkgPath() != planPackage {
			return
		}
		if _, duplicate := seen[valueType]; duplicate {
			return
		}
		seen[valueType] = struct{}{}
		for fieldIndex := 0; fieldIndex < valueType.NumField(); fieldIndex++ {
			field := valueType.Field(fieldIndex)
			if !field.IsExported() {
				continue
			}
			yamlName := strings.Split(field.Tag.Get("yaml"), ",")[0]
			if yamlName != "" && yamlName != "-" {
				result[valueType.Name()+"."+yamlName] = struct{}{}
			}
			visit(field.Type)
		}
	}
	visit(root)
	return result
}

func usedAuthoringYAMLFields(root reflect.Value) map[string]struct{} {
	result := make(map[string]struct{})
	planPackage := reflect.TypeOf(Document{}).PkgPath()
	var visit func(reflect.Value)
	visit = func(value reflect.Value) {
		if !value.IsValid() {
			return
		}
		if value.Kind() == reflect.Interface || value.Kind() == reflect.Pointer {
			if value.IsNil() {
				return
			}
			visit(value.Elem())
			return
		}
		switch value.Kind() {
		case reflect.Struct:
			valueType := value.Type()
			for fieldIndex := 0; fieldIndex < value.NumField(); fieldIndex++ {
				fieldType := valueType.Field(fieldIndex)
				fieldValue := value.Field(fieldIndex)
				if fieldType.IsExported() && valueType.PkgPath() == planPackage {
					yamlName := strings.Split(fieldType.Tag.Get("yaml"), ",")[0]
					if yamlName != "" && yamlName != "-" && authoringYAMLFieldPresent(fieldValue) {
						result[valueType.Name()+"."+yamlName] = struct{}{}
					}
				}
				visit(fieldValue)
			}
		case reflect.Map:
			iter := value.MapRange()
			for iter.Next() {
				visit(iter.Value())
			}
		case reflect.Slice, reflect.Array:
			for index := 0; index < value.Len(); index++ {
				visit(value.Index(index))
			}
		}
	}
	visit(root)
	return result
}

func authoringYAMLFieldPresent(value reflect.Value) bool {
	if !value.IsValid() {
		return false
	}
	switch value.Kind() {
	case reflect.Interface, reflect.Pointer:
		return !value.IsNil()
	case reflect.Map, reflect.Slice:
		return value.Len() != 0
	case reflect.Array, reflect.Struct:
		return !value.IsZero()
	case reflect.Bool:
		return value.Bool()
	case reflect.String:
		return value.String() != ""
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return value.Int() != 0
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
		return value.Uint() != 0
	case reflect.Float32, reflect.Float64:
		return value.Float() != 0
	default:
		return !value.IsZero()
	}
}

func nodeFieldByYAMLName(t *testing.T, value reflect.Value, name string) reflect.Value {
	t.Helper()
	valueType := value.Type()
	for fieldIndex := 0; fieldIndex < valueType.NumField(); fieldIndex++ {
		field := valueType.Field(fieldIndex)
		if strings.Split(field.Tag.Get("yaml"), ",")[0] == name {
			return value.Field(fieldIndex)
		}
	}
	t.Fatalf("%s has no yaml field %q", valueType, name)
	return reflect.Value{}
}

func assertExactNamedStringSet[T ~string](t *testing.T, label string, expected, actual map[T]struct{}) {
	t.Helper()
	expectedStrings := make(map[string]struct{}, len(expected))
	for value := range expected {
		expectedStrings[string(value)] = struct{}{}
	}
	actualStrings := make(map[string]struct{}, len(actual))
	for value := range actual {
		actualStrings[string(value)] = struct{}{}
	}
	assertExactStringSet(t, label, expectedStrings, actualStrings)
}

func assertExactStringSet(t *testing.T, label string, expected, actual map[string]struct{}) {
	t.Helper()
	var missing, unexpected []string
	for value := range expected {
		if _, ok := actual[value]; !ok {
			missing = append(missing, value)
		}
	}
	for value := range actual {
		if _, ok := expected[value]; !ok {
			unexpected = append(unexpected, value)
		}
	}
	sort.Strings(missing)
	sort.Strings(unexpected)
	if len(missing) != 0 || len(unexpected) != 0 {
		t.Fatalf("%s coverage mismatch: missing=%v unexpected=%v", label, missing, unexpected)
	}
}
