package layoutauthoringv1

import (
	"context"
	"encoding/json"
	"reflect"
	"strings"
	"testing"
	"time"
)

func TestEveryOperationValidAndInvalid(t *testing.T) {
	name := "Renamed"
	changes := preserveChanges()
	changes.Name = Change[string]{Mode: ChangeSet, Value: &name}
	valid := map[string]CommandOperation{
		"create": validCreateOperation(),
		"update": {Update: &UpdateLayout{
			LayoutID: "layout-main", ExpectedAggregateVersion: "aggregate-v1", Changes: changes,
			ListFields: FieldIDListChange{Mode: ChangePreserve},
		}},
		"clone": {Clone: &CloneLayout{
			SourceLayoutID: "layout-main", SourceResourceVersion: "resource-v1", TargetLayoutID: "layout-copy",
			TargetName: "Copy", ExpectedWorkspaceVersion: "workspace-v1",
		}},
		"set default": {SetDefault: &SetDefaultLayout{ObjectID: "Account", LayoutID: "layout-main", ExpectedWorkspaceVersion: "workspace-v1"}},
		"delete":      {Delete: &DeleteLayout{LayoutID: "layout-main", ExpectedResourceVersion: "resource-v1", ExpectedWorkspaceVersion: "workspace-v1"}},
		"restore":     {Restore: &RestoreLayout{LayoutID: "layout-main", HistoryVersion: 2, ExpectedResourceVersion: "resource-v1"}},
		"assignment upsert": {AssignmentUpsert: &UpsertLayoutAssignment{
			ObjectID: "Account", LayoutID: "layout-main", Selectors: AssignmentSelectors{ProfileID: pointer("profile-a")},
			ExpectedWorkspaceVersion: "workspace-v1",
		}},
		"assignment delete": {AssignmentDelete: &DeleteLayoutAssignment{
			AssignmentID: "assignment-a", ExpectedResourceVersion: "resource-v1", ExpectedWorkspaceVersion: "workspace-v1",
		}},
	}
	for name, operation := range valid {
		t.Run(name+" valid", func(t *testing.T) {
			if err := ValidateSubmitRequest(deadlineContext(t), validSubmit(operation)); err != nil {
				t.Fatalf("ValidateSubmitRequest() error = %v", err)
			}
		})
	}

	invalidCases := map[string]CommandOperation{
		"update lacks aggregate version": func() CommandOperation {
			operation := valid["update"]
			operation.Update.ExpectedAggregateVersion = ""
			return operation
		}(),
		"clone aliases source": func() CommandOperation {
			operation := valid["clone"]
			operation.Clone.TargetLayoutID = operation.Clone.SourceLayoutID
			return operation
		}(),
		"set default lacks workspace version": func() CommandOperation {
			operation := valid["set default"]
			operation.SetDefault.ExpectedWorkspaceVersion = ""
			return operation
		}(),
		"delete lacks resource version": func() CommandOperation {
			operation := valid["delete"]
			operation.Delete.ExpectedResourceVersion = ""
			return operation
		}(),
		"restore has zero history version": func() CommandOperation {
			operation := valid["restore"]
			operation.Restore.HistoryVersion = 0
			return operation
		}(),
		"assignment upsert has no selector": func() CommandOperation {
			operation := valid["assignment upsert"]
			operation.AssignmentUpsert.Selectors = AssignmentSelectors{}
			return operation
		}(),
		"assignment delete lacks workspace version": func() CommandOperation {
			operation := valid["assignment delete"]
			operation.AssignmentDelete.ExpectedWorkspaceVersion = ""
			return operation
		}(),
	}
	for name, operation := range invalidCases {
		t.Run(name, func(t *testing.T) {
			if err := ValidateSubmitRequest(deadlineContext(t), validSubmit(operation)); err == nil {
				t.Fatal("ValidateSubmitRequest() unexpectedly succeeded")
			}
		})
	}
}

func TestCreateListFieldsWorkspaceVersionSemantics(t *testing.T) {
	fields := []string{"Name"}
	nilFields := []string(nil)
	workspaceVersion := "workspace-v1"

	valid := map[string]CreateLayout{
		"preserve without workspace version": {
			Document: validDocument(), ListFields: FieldIDListChange{Mode: ChangePreserve},
		},
		"set fields": {
			Document: validDocument(), ListFields: FieldIDListChange{Mode: ChangeSet, Value: &fields},
			ExpectedWorkspaceVersion: &workspaceVersion,
		},
		"set authored empty": {
			Document: validDocument(), ListFields: FieldIDListChange{Mode: ChangeSet, Value: pointer([]string{})},
			ExpectedWorkspaceVersion: &workspaceVersion,
		},
		"clear": {
			Document: validDocument(), ListFields: FieldIDListChange{Mode: ChangeClear},
			ExpectedWorkspaceVersion: &workspaceVersion,
		},
	}
	for name, create := range valid {
		t.Run(name, func(t *testing.T) {
			if err := ValidateSubmitRequest(deadlineContext(t), validSubmit(CommandOperation{Create: &create})); err != nil {
				t.Fatalf("ValidateSubmitRequest() error = %v", err)
			}
		})
	}

	invalid := map[string]CreateLayout{
		"preserve with workspace version": {
			Document: validDocument(), ListFields: FieldIDListChange{Mode: ChangePreserve},
			ExpectedWorkspaceVersion: &workspaceVersion,
		},
		"preserve with value": {
			Document: validDocument(), ListFields: FieldIDListChange{Mode: ChangePreserve, Value: &fields},
		},
		"set without workspace version": {
			Document: validDocument(), ListFields: FieldIDListChange{Mode: ChangeSet, Value: &fields},
		},
		"set nil collection": {
			Document: validDocument(), ListFields: FieldIDListChange{Mode: ChangeSet, Value: &nilFields},
			ExpectedWorkspaceVersion: &workspaceVersion,
		},
		"clear without workspace version": {
			Document: validDocument(), ListFields: FieldIDListChange{Mode: ChangeClear},
		},
		"clear with value": {
			Document: validDocument(), ListFields: FieldIDListChange{Mode: ChangeClear, Value: &fields},
			ExpectedWorkspaceVersion: &workspaceVersion,
		},
		"malformed workspace version": {
			Document: validDocument(), ListFields: FieldIDListChange{Mode: ChangeSet, Value: &fields},
			ExpectedWorkspaceVersion: pointer(""),
		},
	}
	for name, create := range invalid {
		t.Run(name, func(t *testing.T) {
			if err := ValidateSubmitRequest(deadlineContext(t), validSubmit(CommandOperation{Create: &create})); err == nil {
				t.Fatal("ValidateSubmitRequest() unexpectedly succeeded")
			}
		})
	}
}

func TestExactlyOneOperationArm(t *testing.T) {
	for name, operation := range map[string]CommandOperation{
		"zero": {},
		"multiple": {Create: validCreateOperation().Create, Delete: &DeleteLayout{
			LayoutID: "layout-main", ExpectedResourceVersion: "resource-v1", ExpectedWorkspaceVersion: "workspace-v1",
		}},
	} {
		t.Run(name, func(t *testing.T) {
			if err := ValidateSubmitRequest(deadlineContext(t), validSubmit(operation)); err == nil {
				t.Fatal("ValidateSubmitRequest() unexpectedly succeeded")
			}
		})
	}
}

func TestCompleteDocumentSerializationCoversEveryAuthoredFact(t *testing.T) {
	document := validDocument()
	if err := ValidateLayoutDocument(document); err != nil {
		t.Fatalf("ValidateLayoutDocument() error = %v", err)
	}
	raw, err := json.Marshal(document)
	if err != nil {
		t.Fatal(err)
	}
	var decoded LayoutDocument
	if err := json.Unmarshal(raw, &decoded); err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(decoded, document) {
		t.Fatalf("round trip differs\n got: %#v\nwant: %#v", decoded, document)
	}
	if len(decoded.Sections) != 2 || len(decoded.Sections[0].FieldLabels) != 2 ||
		len(decoded.Sections[0].FieldBehaviors) != 2 || decoded.Sections[0].Fields[1] != "OwnerId.Name" ||
		(*decoded.Sections[0].FieldBehaviors[0].Behaviors)[0].Behavior != "  Required  " ||
		len(decoded.RelatedLists[0].PrefillMappings) != 1 || len(decoded.RecordPanels) != 1 ||
		len(decoded.HeaderActions.Actions[0].Extensions) != 2 || !decoded.QuickActions.Authored ||
		len(decoded.QuickActions.Actions) != 0 || len(decoded.RecordStates[0].Links) != 1 ||
		len(decoded.PrimaryHeaderActions) != 2 {
		t.Fatal("fixture did not retain every authored family")
	}
}

func TestSectionIRRepresentsIndependentStoredFactsLosslessly(t *testing.T) {
	document := validDocument()
	section := document.Sections[0]

	// These facts come from three separate Core fields: the ordered Fields
	// slice, FieldLabels map, and FieldBehaviors map. Labels and behaviors for
	// Orphaned__c must survive even though it is not a rendered placement.
	if !reflect.DeepEqual(section.Fields, []string{"Name", "OwnerId.Name"}) {
		t.Fatalf("ordered placements changed: %v", section.Fields)
	}
	if got := section.FieldLabels[1]; got.FieldReference != "Orphaned__c" || got.Label != "Retained orphan" {
		t.Fatalf("orphan label changed: %#v", got)
	}
	if got := (*section.FieldBehaviors[0].Behaviors)[0]; got.Mode != "Create" || got.Behavior != "  Required  " {
		t.Fatalf("verbatim behavior changed: %#v", got)
	}
	if got := section.FieldBehaviors[1]; got.FieldReference != "Orphaned__c" {
		t.Fatalf("orphan behavior changed: %#v", got)
	}
	if err := ValidateLayoutDocument(document); err != nil {
		t.Fatalf("lossless source-derived vector rejected: %v", err)
	}
}

func TestSectionStoredPointerPresenceIsSemantic(t *testing.T) {
	tests := map[string]func(*LayoutSection, bool){
		"kind": func(section *LayoutSection, present bool) {
			section.Kind = nil
			if present {
				section.Kind = pointer("")
			}
		},
		"tab": func(section *LayoutSection, present bool) {
			section.TabID = nil
			if present {
				section.TabID = pointer("")
			}
		},
		"component name": func(section *LayoutSection, present bool) {
			section.ComponentName = nil
			if present {
				section.ComponentName = pointer("")
			}
		},
	}
	for name, set := range tests {
		t.Run(name, func(t *testing.T) {
			absent := validDocument()
			set(&absent.Sections[0], false)
			presentEmpty := validDocument()
			set(&presentEmpty.Sections[0], true)
			if err := ValidateLayoutDocument(absent); err != nil {
				t.Fatalf("absent rejected: %v", err)
			}
			if err := ValidateLayoutDocument(presentEmpty); err != nil {
				t.Fatalf("present-empty rejected: %v", err)
			}
			absentDigest, err := RequestDigest(createRequestForDocument(absent))
			if err != nil {
				t.Fatal(err)
			}
			presentDigest, err := RequestDigest(createRequestForDocument(presentEmpty))
			if err != nil {
				t.Fatal(err)
			}
			if absentDigest == presentDigest {
				t.Fatal("absent and present-empty collapsed to one identity")
			}
		})
	}
}

func TestStoredEmptyLayoutKindIsRepresentable(t *testing.T) {
	document := validDocument()
	document.Kind = ""
	if err := ValidateLayoutDocument(document); err != nil {
		t.Fatalf("stored empty layout kind rejected: %v", err)
	}
}

func TestActionStoredPointerPresenceIsSemantic(t *testing.T) {
	tests := map[string]func(*LayoutAction, bool){
		"icon": func(action *LayoutAction, present bool) {
			action.Icon = nil
			if present {
				action.Icon = pointer("")
			}
		},
		"target object": func(action *LayoutAction, present bool) {
			action.TargetObjectID = nil
			if present {
				action.TargetObjectID = pointer("")
			}
		},
		"component": func(action *LayoutAction, present bool) {
			action.Component = nil
			if present {
				action.Component = pointer("")
			}
		},
		"visibility": func(action *LayoutAction, present bool) {
			action.VisibilityCondition = nil
			if present {
				action.VisibilityCondition = pointer("")
			}
		},
	}
	for name, set := range tests {
		t.Run(name, func(t *testing.T) {
			absent := validDocument()
			set(&absent.HeaderActions.Actions[0], false)
			presentEmpty := validDocument()
			set(&presentEmpty.HeaderActions.Actions[0], true)
			if err := ValidateLayoutDocument(absent); err != nil {
				t.Fatalf("absent rejected: %v", err)
			}
			if err := ValidateLayoutDocument(presentEmpty); err != nil {
				t.Fatalf("present-empty rejected: %v", err)
			}
			absentDigest, err := RequestDigest(createRequestForDocument(absent))
			if err != nil {
				t.Fatal(err)
			}
			presentDigest, err := RequestDigest(createRequestForDocument(presentEmpty))
			if err != nil {
				t.Fatal(err)
			}
			if absentDigest == presentDigest {
				t.Fatal("absent and present-empty collapsed to one identity")
			}
		})
	}
}

func TestOptionalPresenceMatchesStoredCoreFacts(t *testing.T) {
	pointerFields := map[reflect.Type][]string{
		reflect.TypeOf(LayoutSection{}):   {"TabID", "Kind", "ComponentName", "VisibilityCondition"},
		reflect.TypeOf(LayoutAction{}):    {"Icon", "TargetObjectID", "Component", "VisibilityCondition"},
		reflect.TypeOf(RecordStateRule{}): {"Banner", "ReadOnly"},
	}
	valueOnly := []reflect.Type{
		reflect.TypeOf(RelatedList{}), reflect.TypeOf(RecordPanel{}),
		reflect.TypeOf(RecordStateBanner{}), reflect.TypeOf(RecordStateLink{}),
		reflect.TypeOf(RecordStateActionOverride{}), reflect.TypeOf(RecordStateReadOnly{}),
	}
	for typeOf, names := range pointerFields {
		want := make(map[string]struct{}, len(names))
		for _, name := range names {
			want[name] = struct{}{}
		}
		for index := 0; index < typeOf.NumField(); index++ {
			field := typeOf.Field(index)
			_, expected := want[field.Name]
			if got := field.Type.Kind() == reflect.Pointer; got != expected {
				t.Fatalf("%s.%s pointer = %v, want %v", typeOf.Name(), field.Name, got, expected)
			}
		}
	}
	for _, typeOf := range valueOnly {
		for index := 0; index < typeOf.NumField(); index++ {
			field := typeOf.Field(index)
			if field.Type.Kind() == reflect.Pointer {
				t.Fatalf("%s.%s invents presence not retained by Core", typeOf.Name(), field.Name)
			}
		}
	}
}

func TestPhysicalFieldReferencesAndVerbatimBehaviorGrammar(t *testing.T) {
	document := validDocument()
	if err := ValidateLayoutDocument(document); err != nil {
		t.Fatalf("dotted references and exact vocabularies rejected: %v", err)
	}
	emptyVocabulary := validDocument()
	emptyVocabulary.Sections[0].FieldBehaviors = []FieldBehaviorGroup{{
		FieldReference: "Name", Behaviors: pointer([]FieldBehavior{{Mode: "", Behavior: ""}}),
	}}
	if err := ValidateLayoutDocument(emptyVocabulary); err != nil {
		t.Fatalf("L0 interpreted an app-owned empty behavior key/value: %v", err)
	}
	for name, mutate := range map[string]func(*LayoutDocument){
		"leading dot":     func(value *LayoutDocument) { value.Sections[0].Fields[1] = ".Name" },
		"empty segment":   func(value *LayoutDocument) { value.Sections[0].Fields[1] = "OwnerId..Name" },
		"invalid segment": func(value *LayoutDocument) { value.Sections[0].Fields[1] = "Owner-Id.Name" },
		"behavior control": func(value *LayoutDocument) {
			(*value.Sections[0].FieldBehaviors[0].Behaviors)[0].Behavior = "required\n"
		},
	} {
		t.Run(name, func(t *testing.T) {
			value := validDocument()
			mutate(&value)
			if err := ValidateLayoutDocument(value); err == nil {
				t.Fatal("invalid stored grammar accepted")
			}
		})
	}
}

func TestProtocolIdentitiesRejectControlAndFormatCharacters(t *testing.T) {
	for name, mutate := range map[string]func(*SubmitRequest){
		"command LF":      func(value *SubmitRequest) { value.CommandID = "command\n1" },
		"idempotency TAB": func(value *SubmitRequest) { value.IdempotencyKey = "key\t1" },
		"layout CR":       func(value *SubmitRequest) { value.Operation.Create.Document.LayoutID = "layout\rmain" },
		"layout name LF":  func(value *SubmitRequest) { value.Operation.Create.Document.Name = "Main\nLayout" },
		"command zero-width space": func(value *SubmitRequest) {
			value.CommandID = "command\u200b1"
		},
		"resource bidi override": func(value *SubmitRequest) {
			value.Operation.Create.Document.LayoutID = "layout\u202emain"
		},
		"command line separator": func(value *SubmitRequest) { value.CommandID = "command\u20281" },
		"idempotency paragraph separator": func(value *SubmitRequest) {
			value.IdempotencyKey = "key\u20291"
		},
		"workspace LF": func(value *SubmitRequest) {
			*value.Operation.Create.ExpectedWorkspaceVersion = "workspace\nv1"
		},
	} {
		t.Run(name, func(t *testing.T) {
			request := validSubmit(validCreateOperation())
			mutate(&request)
			if err := ValidateSubmitRequest(deadlineContext(t), request); err == nil {
				t.Fatal("control-bearing protocol identity accepted")
			}
		})
	}

	document := validDocument()
	document.Sections[0].Label = "Line one\nLine two"
	document.Sections[0].VisibilityCondition = pointer("Status == 'Open'\n&& Active")
	if err := ValidateLayoutDocument(document); err != nil {
		t.Fatalf("authored multiline text rejected: %v", err)
	}
}

func TestRequestValidatorsShareCommonLifecycleClassification(t *testing.T) {
	digest := "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
	validators := map[string]func(context.Context) error{
		"submit": func(ctx context.Context) error {
			return ValidateSubmitRequest(ctx, validSubmit(validCreateOperation()))
		},
		"status": func(ctx context.Context) error {
			return ValidateGetCommandRequest(ctx, GetCommandRequest{CommandID: "command-1", RequestDigest: digest})
		},
		"workspace": func(ctx context.Context) error {
			return ValidateWorkspaceRequest(ctx, WorkspaceRequest{ObjectID: "Account"})
		},
		"stored": func(ctx context.Context) error {
			return ValidateStoredLayoutRequest(ctx, StoredLayoutRequest{LayoutID: "layout-main"})
		},
	}
	canceled, cancel := context.WithCancel(context.Background())
	cancel()
	expired, expire := context.WithDeadline(context.Background(), time.Now().Add(-time.Second))
	defer expire()
	contexts := map[string]struct {
		ctx      context.Context
		category FailureCategory
	}{
		"nil":                          {ctx: nil, category: FailureInvalidRequest},
		"no deadline":                  {ctx: context.Background(), category: FailureInvalidRequest},
		"canceled":                     {ctx: canceled, category: FailureCanceled},
		"expired":                      {ctx: expired, category: FailureDeadlineExceeded},
		"past deadline with nil error": {ctx: pastDeadlineNilErrorContext{}, category: FailureInvalidRequest},
	}
	for validatorName, validate := range validators {
		for contextName, test := range contexts {
			t.Run(validatorName+"/"+contextName, func(t *testing.T) {
				failure, ok := validate(test.ctx).(*Failure)
				if !ok || failure.Category != test.category {
					t.Fatalf("lifecycle result = %#v, want category %q", failure, test.category)
				}
			})
		}
	}
}

type pastDeadlineNilErrorContext struct{}

func (pastDeadlineNilErrorContext) Deadline() (time.Time, bool) {
	return time.Now().Add(-time.Second), true
}

func (pastDeadlineNilErrorContext) Done() <-chan struct{} { return nil }
func (pastDeadlineNilErrorContext) Err() error            { return nil }
func (pastDeadlineNilErrorContext) Value(any) any         { return nil }

func TestChangePresenceAndAuthoredEmpty(t *testing.T) {
	preserve := preserveChanges()
	if err := ValidateLayoutDocumentChanges(preserve); err != nil {
		t.Fatalf("PRESERVE rejected: %v", err)
	}
	empty := []string{}
	preserve.Tabs = Change[[]string]{Mode: ChangeSet, Value: &empty}
	preserve.Kind = Change[string]{Mode: ChangeClear}
	preserve.RecordPanels = Change[[]RecordPanel]{Mode: ChangeClear}
	preserve.HeaderActions = Change[ActionPlacement]{Mode: ChangeSet, Value: &ActionPlacement{Authored: true, Actions: []LayoutAction{}}}
	if err := ValidateLayoutDocumentChanges(preserve); err != nil {
		t.Fatalf("SET empty / CLEAR rejected: %v", err)
	}
	setEmptyKind := preserveChanges()
	setEmptyKind.Kind = Change[string]{Mode: ChangeSet, Value: pointer("")}
	if err := ValidateLayoutDocumentChanges(setEmptyKind); err == nil {
		t.Fatal("SET empty kind duplicates CLEAR semantics")
	}

	for name, mutate := range map[string]func(*LayoutDocumentChanges){
		"preserve with value": func(changes *LayoutDocumentChanges) { changes.Name.Value = pointer("x") },
		"set without value":   func(changes *LayoutDocumentChanges) { changes.Name.Mode = ChangeSet },
		"clear required name": func(changes *LayoutDocumentChanges) { changes.Name.Mode = ChangeClear },
		"clear with value": func(changes *LayoutDocumentChanges) {
			changes.Tabs = Change[[]string]{Mode: ChangeClear, Value: &empty}
		},
	} {
		t.Run(name, func(t *testing.T) {
			changes := preserveChanges()
			mutate(&changes)
			if err := ValidateLayoutDocumentChanges(changes); err == nil {
				t.Fatal("invalid presence unexpectedly accepted")
			}
		})
	}
}

func TestDuplicateIdentitiesSelectorsKeysAndOrdering(t *testing.T) {
	tests := map[string]func(*LayoutDocument){
		"section identity": func(document *LayoutDocument) { document.Sections = append(document.Sections, document.Sections[0]) },
		"field identity case folded": func(document *LayoutDocument) {
			document.Sections[1].Fields = []string{"name"}
		},
		"tab identity": func(document *LayoutDocument) { document.Tabs = append(document.Tabs, "details") },
		"related target": func(document *LayoutDocument) {
			copy := document.RelatedLists[0]
			copy.RelatedListID = "another-list"
			document.RelatedLists = append(document.RelatedLists, copy)
		},
		"action identity": func(document *LayoutDocument) {
			document.HeaderActions.Actions = append(document.HeaderActions.Actions, document.HeaderActions.Actions[0])
		},
		"record state identity": func(document *LayoutDocument) {
			document.RecordStates = append(document.RecordStates, document.RecordStates[0])
		},
		"primary action identity": func(document *LayoutDocument) {
			document.PrimaryHeaderActions = append(document.PrimaryHeaderActions, "host:edit")
		},
		"configuration key order": func(document *LayoutDocument) {
			document.HeaderActions.Actions[0].Configuration = []NamedValue{
				{Name: "z", Value: nullValue()}, {Name: "a", Value: nullValue()},
			}
		},
		"behavior order": func(document *LayoutDocument) {
			document.Sections[0].FieldBehaviors = []FieldBehaviorGroup{{
				FieldReference: "Name", Behaviors: pointer([]FieldBehavior{
					{Mode: "view", Behavior: "shown"},
					{Mode: "create", Behavior: "required"},
				}),
			}}
		},
		"field label order": func(document *LayoutDocument) {
			document.Sections[0].FieldLabels = []FieldLabel{
				{FieldReference: "Orphaned__c", Label: "orphan"},
				{FieldReference: "Name", Label: "name"},
			}
		},
		"field label identity case folded": func(document *LayoutDocument) {
			document.Sections[0].FieldLabels = []FieldLabel{
				{FieldReference: "Name", Label: "name"},
				{FieldReference: "name", Label: "duplicate"},
			}
		},
		"field behavior mode identity": func(document *LayoutDocument) {
			document.Sections[0].FieldBehaviors = []FieldBehaviorGroup{{
				FieldReference: "Name", Behaviors: pointer([]FieldBehavior{
					{Mode: "Create", Behavior: "required"},
					{Mode: "Create", Behavior: "hidden"},
				}),
			}}
		},
		"field behavior identity case folded": func(document *LayoutDocument) {
			document.Sections[0].FieldBehaviors = []FieldBehaviorGroup{
				{FieldReference: "Name"},
				{FieldReference: "name"},
			}
		},
		"prefill order": func(document *LayoutDocument) {
			document.RelatedLists[0].PrefillMappings = []PrefillMapping{
				{TargetFieldID: "Zed", SourceFieldID: "Id"}, {TargetFieldID: "Alpha", SourceFieldID: "Id"},
			}
		},
		"prefill target identity case folded": func(document *LayoutDocument) {
			document.RelatedLists[0].PrefillMappings = []PrefillMapping{
				{TargetFieldID: "ParentId", SourceFieldID: "Id"},
				{TargetFieldID: "parentid", SourceFieldID: "OwnerId"},
			}
		},
	}
	for name, mutate := range tests {
		t.Run(name, func(t *testing.T) {
			document := validDocument()
			mutate(&document)
			if err := ValidateLayoutDocument(document); err == nil {
				t.Fatal("invalid document unexpectedly accepted")
			}
		})
	}

	ref := validViewRef(42)
	workspace := Workspace{
		ObjectID: "Account", WorkspaceVersion: "workspace-v2", ViewRef: ref, Receipts: validReceipts(42),
		Layouts: []LayoutSummary{{
			LayoutID: "layout-main", ObjectID: "Account", Name: "Main", Kind: "Detail", IsDefault: true,
			ResourceVersion: "resource-v1",
		}},
		Assignments: []LayoutAssignmentSummary{
			{AssignmentID: "assignment-0", ObjectID: "Account", LayoutID: "layout-main", Selectors: AssignmentSelectors{}, Specificity: 0, ResourceVersion: "resource-default"},
			{AssignmentID: "assignment-a", ObjectID: "Account", LayoutID: "layout-main", Selectors: AssignmentSelectors{ProfileID: pointer("Sales")}, Specificity: 1, ResourceVersion: "resource-a"},
			{AssignmentID: "assignment-b", ObjectID: "Account", LayoutID: "layout-main", Selectors: AssignmentSelectors{ProfileID: pointer("sales")}, Specificity: 1, ResourceVersion: "resource-b"},
		},
	}
	if err := ValidateWorkspaceResponse(WorkspaceRequest{ObjectID: "Account", ViewRef: &ref}, workspace); err == nil {
		t.Fatal("duplicate selector tuple unexpectedly accepted")
	}
}

func TestAssignmentSelectorsHaveOnlyTypedDimensions(t *testing.T) {
	typeOfSelectors := reflect.TypeOf(AssignmentSelectors{})
	got := make([]string, typeOfSelectors.NumField())
	for index := range got {
		got[index] = typeOfSelectors.Field(index).Name
	}
	want := []string{"ProfileID", "RoleID", "RecordTypeID"}
	if !reflect.DeepEqual(got, want) {
		t.Fatalf("selector fields = %v, want %v", got, want)
	}
	raw, err := json.Marshal(AssignmentSelectors{ProfileID: pointer("p")})
	if err != nil {
		t.Fatal(err)
	}
	if strings.Contains(string(raw), "dimension") || strings.Contains(string(raw), "key") {
		t.Fatalf("generic selector vocabulary leaked: %s", raw)
	}
	for name, selector := range map[string]*string{
		"empty": pointer(""), "control": pointer("profile\n1"), "surrounding whitespace": pointer(" profile-a "),
	} {
		t.Run(name, func(t *testing.T) {
			operation := CommandOperation{AssignmentUpsert: &UpsertLayoutAssignment{
				ObjectID: "Account", LayoutID: "layout-main", Selectors: AssignmentSelectors{ProfileID: selector},
				ExpectedWorkspaceVersion: "workspace-v1",
			}}
			if err := ValidateSubmitRequest(deadlineContext(t), validSubmit(operation)); err == nil {
				t.Fatal("malformed resolved selector accepted")
			}
		})
	}
}

func TestWildcardAssignmentIsReadOnlyThroughSetDefaultOwnership(t *testing.T) {
	workspace := validWorkspace(42)
	ref := workspace.ViewRef
	if err := ValidateWorkspaceResponse(WorkspaceRequest{ObjectID: "Account", ViewRef: &ref}, workspace); err != nil {
		t.Fatalf("selectorless wildcard read projection rejected: %v", err)
	}
	request := validSubmit(CommandOperation{AssignmentUpsert: &UpsertLayoutAssignment{
		ObjectID: "Account", LayoutID: "layout-main", Selectors: AssignmentSelectors{},
		ExpectedWorkspaceVersion: "workspace-v1",
	}})
	if err := ValidateSubmitRequest(deadlineContext(t), request); err == nil {
		t.Fatal("selectorless assignment upsert bypassed SetDefault ownership")
	}
}

func TestWorkspaceDefaultAndWildcardDescribeOneRoutingState(t *testing.T) {
	valid := validWorkspace(42)
	ref := valid.ViewRef
	request := WorkspaceRequest{ObjectID: "Account", ViewRef: &ref}
	if err := ValidateWorkspaceResponse(request, valid); err != nil {
		t.Fatalf("matching default and wildcard rejected: %v", err)
	}
	empty := valid
	empty.Layouts = []LayoutSummary{}
	empty.Assignments = []LayoutAssignmentSummary{}
	if err := ValidateWorkspaceResponse(request, empty); err != nil {
		t.Fatalf("empty workspace rejected: %v", err)
	}
	withoutRouting := validWorkspace(42)
	withoutRouting.Layouts[0].IsDefault = false
	withoutRouting.Assignments = []LayoutAssignmentSummary{}
	if err := ValidateWorkspaceResponse(request, withoutRouting); err != nil {
		t.Fatalf("workspace before its first SetDefault rejected: %v", err)
	}
	caseFolded := validWorkspace(42)
	caseFolded.Layouts[0].LayoutID = "Layout-Main"
	caseFolded.Assignments[0].LayoutID = "layout-main"
	if err := ValidateWorkspaceResponse(request, caseFolded); err != nil {
		t.Fatalf("case-folded routing identity rejected: %v", err)
	}
	routingOrdered := validWorkspace(42)
	routingOrdered.Layouts = []LayoutSummary{
		{LayoutID: "a-layout", ObjectID: "Account", Name: "A", Kind: "Detail", IsDefault: true, ResourceVersion: "resource-a"},
		{LayoutID: "Z-layout", ObjectID: "Account", Name: "Z", Kind: "Detail", ResourceVersion: "resource-z"},
	}
	routingOrdered.Assignments[0].LayoutID = "A-LAYOUT"
	if err := ValidateWorkspaceResponse(request, routingOrdered); err != nil {
		t.Fatalf("case-folded routing order rejected: %v", err)
	}

	for name, mutate := range map[string]func(*Workspace){
		"missing default":  func(value *Workspace) { value.Layouts[0].IsDefault = false },
		"missing wildcard": func(value *Workspace) { value.Assignments = []LayoutAssignmentSummary{} },
		"mismatched wildcard": func(value *Workspace) {
			value.Layouts = append(value.Layouts, LayoutSummary{
				LayoutID: "layout-other", ObjectID: "Account", Name: "Other", Kind: "Detail", ResourceVersion: "resource-other",
			})
			value.Assignments[0].LayoutID = "layout-other"
		},
		"multiple defaults": func(value *Workspace) {
			value.Layouts = append(value.Layouts, LayoutSummary{
				LayoutID: "layout-other", ObjectID: "Account", Name: "Other", Kind: "Detail", IsDefault: true,
				ResourceVersion: "resource-other",
			})
		},
		"case-folded duplicate layouts": func(value *Workspace) {
			value.Layouts[0].LayoutID = "Layout-Main"
			value.Layouts = append(value.Layouts, LayoutSummary{
				LayoutID: "layout-main", ObjectID: "Account", Name: "Duplicate", Kind: "Detail",
				ResourceVersion: "resource-other",
			})
		},
		"bytewise but not routing order": func(value *Workspace) {
			value.Layouts = []LayoutSummary{
				{LayoutID: "Z-layout", ObjectID: "Account", Name: "Z", Kind: "Detail", IsDefault: true, ResourceVersion: "resource-z"},
				{LayoutID: "a-layout", ObjectID: "Account", Name: "A", Kind: "Detail", ResourceVersion: "resource-a"},
			}
			value.Assignments[0].LayoutID = "Z-layout"
		},
	} {
		t.Run(name, func(t *testing.T) {
			workspace := validWorkspace(42)
			mutate(&workspace)
			if err := ValidateWorkspaceResponse(request, workspace); err == nil {
				t.Fatal("inconsistent routing projection accepted")
			}
		})
	}
}

func TestSectionTabReferencesUseKnownPostUpdateTabs(t *testing.T) {
	section := validDocument().Sections[0]
	section.TabID = pointer("old")
	sections := []LayoutSection{section}

	for name, test := range map[string]struct {
		tabs  Change[[]string]
		valid bool
	}{
		"preserved tabs remain unknown": {tabs: Change[[]string]{Mode: ChangePreserve}, valid: true},
		"set contains reference":        {tabs: Change[[]string]{Mode: ChangeSet, Value: pointer([]string{"old"})}, valid: true},
		"set omits reference":           {tabs: Change[[]string]{Mode: ChangeSet, Value: pointer([]string{"new"})}},
		"clear removes reference":       {tabs: Change[[]string]{Mode: ChangeClear}},
	} {
		t.Run(name, func(t *testing.T) {
			changes := preserveChanges()
			changes.Tabs = test.tabs
			changes.Sections = Change[[]LayoutSection]{Mode: ChangeSet, Value: &sections}
			err := ValidateLayoutDocumentChanges(changes)
			if test.valid && err != nil {
				t.Fatalf("valid related changes rejected: %v", err)
			}
			if !test.valid && err == nil {
				t.Fatal("section referenced a tab absent from the known post-update set")
			}
		})
	}
}
