package layoutauthoringv1

import "testing"

func TestCompleteDocumentCollectionsHaveOneCanonicalRepresentation(t *testing.T) {
	tests := map[string]struct {
		empty func(*LayoutDocument)
		nil   func(*LayoutDocument)
	}{
		"section component configuration": {
			empty: func(document *LayoutDocument) { document.Sections[0].ComponentConfiguration = []NamedValue{} },
			nil:   func(document *LayoutDocument) { document.Sections[0].ComponentConfiguration = nil },
		},
		"section fields": {
			empty: func(document *LayoutDocument) { document.Sections[0].Fields = []string{} },
			nil:   func(document *LayoutDocument) { document.Sections[0].Fields = nil },
		},
		"section field labels": {
			empty: func(document *LayoutDocument) { document.Sections[0].FieldLabels = []FieldLabel{} },
			nil:   func(document *LayoutDocument) { document.Sections[0].FieldLabels = nil },
		},
		"section field behaviors": {
			empty: func(document *LayoutDocument) { document.Sections[0].FieldBehaviors = []FieldBehaviorGroup{} },
			nil:   func(document *LayoutDocument) { document.Sections[0].FieldBehaviors = nil },
		},
		"related-list prefill mappings": {
			empty: func(document *LayoutDocument) { document.RelatedLists[0].PrefillMappings = []PrefillMapping{} },
			nil:   func(document *LayoutDocument) { document.RelatedLists[0].PrefillMappings = nil },
		},
		"record-panel renderer configuration": {
			empty: func(document *LayoutDocument) { document.RecordPanels[0].RendererConfiguration = []NamedValue{} },
			nil:   func(document *LayoutDocument) { document.RecordPanels[0].RendererConfiguration = nil },
		},
		"action configuration": {
			empty: func(document *LayoutDocument) { document.HeaderActions.Actions[0].Configuration = []NamedValue{} },
			nil:   func(document *LayoutDocument) { document.HeaderActions.Actions[0].Configuration = nil },
		},
		"action extensions": {
			empty: func(document *LayoutDocument) { document.HeaderActions.Actions[0].Extensions = []NamedValue{} },
			nil:   func(document *LayoutDocument) { document.HeaderActions.Actions[0].Extensions = nil },
		},
		"record-state links": {
			empty: func(document *LayoutDocument) {
				document.RecordStates[0].Banner = nil
				document.RecordStates[0].Links = []RecordStateLink{}
			},
			nil: func(document *LayoutDocument) {
				document.RecordStates[0].Banner = nil
				document.RecordStates[0].Links = nil
			},
		},
		"record-state action overrides": {
			empty: func(document *LayoutDocument) {
				document.RecordStates[0].ActionOverrides = []RecordStateActionOverride{}
			},
			nil: func(document *LayoutDocument) { document.RecordStates[0].ActionOverrides = nil },
		},
		"record-state banner link ids": {
			empty: func(document *LayoutDocument) { document.RecordStates[0].Banner.LinkIDs = []string{} },
			nil:   func(document *LayoutDocument) { document.RecordStates[0].Banner.LinkIDs = nil },
		},
		"extension list": {
			empty: func(document *LayoutDocument) {
				items := []Value{}
				document.HeaderActions.Actions[0].Configuration = []NamedValue{{Name: "nested", Value: Value{Kind: ValueList, List: &items}}}
			},
			nil: func(document *LayoutDocument) {
				items := []Value(nil)
				document.HeaderActions.Actions[0].Configuration = []NamedValue{{Name: "nested", Value: Value{Kind: ValueList, List: &items}}}
			},
		},
		"extension object": {
			empty: func(document *LayoutDocument) {
				entries := []NamedValue{}
				document.HeaderActions.Actions[0].Configuration = []NamedValue{{Name: "nested", Value: Value{Kind: ValueObject, Object: &entries}}}
			},
			nil: func(document *LayoutDocument) {
				entries := []NamedValue(nil)
				document.HeaderActions.Actions[0].Configuration = []NamedValue{{Name: "nested", Value: Value{Kind: ValueObject, Object: &entries}}}
			},
		},
	}

	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			emptyDocument := validDocument()
			test.empty(&emptyDocument)
			if err := ValidateLayoutDocument(emptyDocument); err != nil {
				t.Fatalf("non-nil empty collection rejected: %v", err)
			}

			nilDocument := validDocument()
			test.nil(&nilDocument)
			if err := ValidateLayoutDocument(nilDocument); err == nil {
				t.Fatal("nil collection unexpectedly accepted")
			}

			emptyRequest := createRequestForDocument(emptyDocument)
			if err := ValidateSubmitRequest(deadlineContext(t), emptyRequest); err != nil {
				t.Fatalf("request with non-nil empty collection rejected: %v", err)
			}
			nilRequest := createRequestForDocument(nilDocument)
			if err := ValidateSubmitRequest(deadlineContext(t), nilRequest); err == nil {
				t.Fatal("request with nil collection unexpectedly accepted")
			}

			emptyDigest, err := RequestDigest(emptyRequest)
			if err != nil {
				t.Fatal(err)
			}
			nilDigest, err := RequestDigest(nilRequest)
			if err != nil {
				t.Fatal(err)
			}
			if emptyDigest == nilDigest {
				t.Fatal("canonical encoder did not distinguish nil from authored empty")
			}
		})
	}
}

func TestFieldBehaviorInnerMapPresenceIsSemantic(t *testing.T) {
	nilInner := validDocument()
	nilInner.Sections[0].FieldBehaviors[0].Behaviors = nil
	if err := ValidateLayoutDocument(nilInner); err != nil {
		t.Fatalf("present field with nil inner behavior map rejected: %v", err)
	}

	emptyInner := validDocument()
	emptyInner.Sections[0].FieldBehaviors[0].Behaviors = pointer([]FieldBehavior{})
	if err := ValidateLayoutDocument(emptyInner); err != nil {
		t.Fatalf("present field with empty inner behavior map rejected: %v", err)
	}

	nilDigest, err := RequestDigest(createRequestForDocument(nilInner))
	if err != nil {
		t.Fatal(err)
	}
	emptyDigest, err := RequestDigest(createRequestForDocument(emptyInner))
	if err != nil {
		t.Fatal(err)
	}
	if nilDigest == emptyDigest {
		t.Fatal("nil and empty inner behavior maps have the same canonical identity")
	}

	ambiguousInner := validDocument()
	nilBehaviors := []FieldBehavior(nil)
	ambiguousInner.Sections[0].FieldBehaviors[0].Behaviors = &nilBehaviors
	if err := ValidateLayoutDocument(ambiguousInner); err == nil {
		t.Fatal("pointer to a nil behavior slice introduced a third inner-map representation")
	}
}

func TestSetCollectionsRejectNilAndAcceptAuthoredEmpty(t *testing.T) {
	tests := map[string]struct {
		empty func(*LayoutDocumentChanges)
		nil   func(*LayoutDocumentChanges)
	}{
		"compact fields": {
			empty: func(changes *LayoutDocumentChanges) {
				changes.CompactFields = Change[[]string]{Mode: ChangeSet, Value: pointer([]string{})}
			},
			nil: func(changes *LayoutDocumentChanges) {
				changes.CompactFields = Change[[]string]{Mode: ChangeSet, Value: pointer([]string(nil))}
			},
		},
		"tabs": {
			empty: func(changes *LayoutDocumentChanges) {
				changes.Tabs = Change[[]string]{Mode: ChangeSet, Value: pointer([]string{})}
			},
			nil: func(changes *LayoutDocumentChanges) {
				changes.Tabs = Change[[]string]{Mode: ChangeSet, Value: pointer([]string(nil))}
			},
		},
		"sections": {
			empty: func(changes *LayoutDocumentChanges) {
				changes.Sections = Change[[]LayoutSection]{Mode: ChangeSet, Value: pointer([]LayoutSection{})}
			},
			nil: func(changes *LayoutDocumentChanges) {
				changes.Sections = Change[[]LayoutSection]{Mode: ChangeSet, Value: pointer([]LayoutSection(nil))}
			},
		},
		"related lists": {
			empty: func(changes *LayoutDocumentChanges) {
				changes.RelatedLists = Change[[]RelatedList]{Mode: ChangeSet, Value: pointer([]RelatedList{})}
			},
			nil: func(changes *LayoutDocumentChanges) {
				changes.RelatedLists = Change[[]RelatedList]{Mode: ChangeSet, Value: pointer([]RelatedList(nil))}
			},
		},
		"record panels": {
			empty: func(changes *LayoutDocumentChanges) {
				changes.RecordPanels = Change[[]RecordPanel]{Mode: ChangeSet, Value: pointer([]RecordPanel{})}
			},
			nil: func(changes *LayoutDocumentChanges) {
				changes.RecordPanels = Change[[]RecordPanel]{Mode: ChangeSet, Value: pointer([]RecordPanel(nil))}
			},
		},
		"header actions": {
			empty: func(changes *LayoutDocumentChanges) {
				changes.HeaderActions = Change[ActionPlacement]{Mode: ChangeSet, Value: &ActionPlacement{Authored: true, Actions: []LayoutAction{}}}
			},
			nil: func(changes *LayoutDocumentChanges) {
				changes.HeaderActions = Change[ActionPlacement]{Mode: ChangeSet, Value: &ActionPlacement{Authored: true, Actions: nil}}
			},
		},
		"quick actions": {
			empty: func(changes *LayoutDocumentChanges) {
				changes.QuickActions = Change[ActionPlacement]{Mode: ChangeSet, Value: &ActionPlacement{Authored: true, Actions: []LayoutAction{}}}
			},
			nil: func(changes *LayoutDocumentChanges) {
				changes.QuickActions = Change[ActionPlacement]{Mode: ChangeSet, Value: &ActionPlacement{Authored: true, Actions: nil}}
			},
		},
		"record states": {
			empty: func(changes *LayoutDocumentChanges) {
				changes.RecordStates = Change[[]RecordStateRule]{Mode: ChangeSet, Value: pointer([]RecordStateRule{})}
			},
			nil: func(changes *LayoutDocumentChanges) {
				changes.RecordStates = Change[[]RecordStateRule]{Mode: ChangeSet, Value: pointer([]RecordStateRule(nil))}
			},
		},
		"primary header actions": {
			empty: func(changes *LayoutDocumentChanges) {
				changes.PrimaryHeaderActions = Change[[]string]{Mode: ChangeSet, Value: pointer([]string{})}
			},
			nil: func(changes *LayoutDocumentChanges) {
				changes.PrimaryHeaderActions = Change[[]string]{Mode: ChangeSet, Value: pointer([]string(nil))}
			},
		},
	}

	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			emptyChanges := preserveChanges()
			test.empty(&emptyChanges)
			if err := ValidateLayoutDocumentChanges(emptyChanges); err != nil {
				t.Fatalf("SET authored-empty rejected: %v", err)
			}

			nilChanges := preserveChanges()
			test.nil(&nilChanges)
			if err := ValidateLayoutDocumentChanges(nilChanges); err == nil {
				t.Fatal("SET nil collection unexpectedly accepted")
			}
		})
	}
}

func createRequestForDocument(document LayoutDocument) SubmitRequest {
	fields := []string{"Name"}
	return validSubmit(CommandOperation{Create: &CreateLayout{
		Document: document,
		ListFields: FieldIDListChange{
			Mode:  ChangeSet,
			Value: &fields,
		},
		ExpectedWorkspaceVersion: pointer("workspace-v1"),
	}})
}
