package layoutvalidationviewv1

import (
	"fmt"
	"strings"

	"github.com/pingcap-inc/nexuscrm-contract/pkg/appauthority"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/catalogassignmentidentityv1"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/catalogpublicationv1"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/layoutbundlekeyv1"
)

// NormalizeFieldAPIName validates an exact source field API name and folds
// only ASCII A..Z. It performs no trimming or Unicode/locale normalization.
func NormalizeFieldAPIName(value string) (string, error) {
	if len(value) == 0 || len(value) > MaxFieldAPINameBytes {
		return "", fmt.Errorf("FieldAPIName must be 1..%d ASCII bytes", MaxFieldAPINameBytes)
	}
	canonical := make([]byte, len(value))
	for index, character := range []byte(value) {
		switch {
		case character >= 'A' && character <= 'Z':
			canonical[index] = character + ('a' - 'A')
		case character >= 'a' && character <= 'z', character >= '0' && character <= '9', character == '_':
			canonical[index] = character
		default:
			return "", fmt.Errorf("FieldAPIName must match [A-Za-z_][A-Za-z0-9_]*")
		}
	}
	if first := canonical[0]; (first < 'a' || first > 'z') && first != '_' {
		return "", fmt.Errorf("FieldAPIName must match [A-Za-z_][A-Za-z0-9_]*")
	}
	return string(canonical), nil
}

// ValidateFieldKey validates an already-normalized field key.
func ValidateFieldKey(key FieldKey) error {
	if err := catalogassignmentidentityv1.ValidateObjectKey(key.ObjectKey); err != nil {
		return fmt.Errorf("FieldKey.ObjectKey: %w", err)
	}
	value := key.NormalizedFieldAPIName
	if len(value) == 0 || len(value) > MaxFieldAPINameBytes {
		return fmt.Errorf("FieldKey.NormalizedFieldAPIName must be 1..%d bytes", MaxFieldAPINameBytes)
	}
	for index, character := range []byte(value) {
		if index == 0 {
			if (character < 'a' || character > 'z') && character != '_' {
				return fmt.Errorf("FieldKey.NormalizedFieldAPIName must match [a-z_][a-z0-9_]*")
			}
			continue
		}
		if (character < 'a' || character > 'z') && (character < '0' || character > '9') && character != '_' {
			return fmt.Errorf("FieldKey.NormalizedFieldAPIName must match [a-z_][a-z0-9_]*")
		}
	}
	return nil
}

// ValidateNamespacedContractKey validates the shared neutral namespace and
// local-key grammars without consulting a component registry.
func ValidateNamespacedContractKey(key NamespacedContractKey) error {
	if err := appauthority.ValidateNamespaceID(key.NamespaceID); err != nil {
		return fmt.Errorf("NamespacedContractKey.NamespaceID: %w", err)
	}
	if err := validateNamespacedLocalKey(key.LocalKey); err != nil {
		return fmt.Errorf("NamespacedContractKey.LocalKey: %w", err)
	}
	return nil
}

func validateNamespacedLocalKey(value string) error {
	if len(value) == 0 || len(value) > catalogassignmentidentityv1.MaxKeyPartBytes {
		return fmt.Errorf("must be 1..%d ASCII bytes", catalogassignmentidentityv1.MaxKeyPartBytes)
	}
	segments := strings.Split(value, ".")
	for segmentIndex, segment := range segments {
		if len(segment) == 0 || len(segment) > catalogassignmentidentityv1.MaxKeySegmentBytes {
			return fmt.Errorf("segment %d must be 1..%d ASCII bytes", segmentIndex, catalogassignmentidentityv1.MaxKeySegmentBytes)
		}
		for index := 0; index < len(segment); index++ {
			character := segment[index]
			if index == 0 {
				if character < 'a' || character > 'z' {
					return fmt.Errorf("segment %d must begin with a lowercase ASCII letter", segmentIndex)
				}
				continue
			}
			if (character < 'a' || character > 'z') &&
				(character < '0' || character > '9') && character != '-' {
				return fmt.Errorf("segment %d contains a non-canonical byte", segmentIndex)
			}
		}
		if segment[len(segment)-1] == '-' {
			return fmt.Errorf("segment %d must not end in '-'", segmentIndex)
		}
	}
	return nil
}

// ValidateView validates the complete structural value without consulting the
// referenced assignment/required-set artifacts or any authority registry.
// Array counts and exact canonical size are checked before validation creates
// maps or any complete canonical byte buffer.
func ValidateView(view View) error {
	if err := validateArrayBounds(view); err != nil {
		return err
	}
	if _, err := measureCanonicalView(view, MaxCanonicalViewBytes); err != nil {
		return err
	}
	if view.CanonicalizationVersion != CanonicalizationVersion {
		return fmt.Errorf("View.CanonicalizationVersion must equal %q", CanonicalizationVersion)
	}
	if view.KeyGrammarVersion != KeyGrammarVersion {
		return fmt.Errorf("View.KeyGrammarVersion must equal %q", KeyGrammarVersion)
	}
	if view.ObjectKeyNormalizationVersion != ObjectKeyNormalizationVersion {
		return fmt.Errorf("View.ObjectKeyNormalizationVersion must equal %q", ObjectKeyNormalizationVersion)
	}
	if view.FieldKeyNormalizationVersion != FieldKeyNormalizationVersion {
		return fmt.Errorf("View.FieldKeyNormalizationVersion must equal %q", FieldKeyNormalizationVersion)
	}
	if err := catalogpublicationv1.ValidateCatalogSnapshotBinding(view.SourceCatalogSnapshot); err != nil {
		return fmt.Errorf("View.SourceCatalogSnapshot: %w", err)
	}
	if err := appauthority.ValidateDigest(view.CatalogAssignmentIdentityViewDigest); err != nil {
		return fmt.Errorf("View.CatalogAssignmentIdentityViewDigest: %w", err)
	}
	if err := appauthority.ValidateDigest(view.RequiredBundleKeySetDigest); err != nil {
		return fmt.Errorf("View.RequiredBundleKeySetDigest: %w", err)
	}

	objects := make(map[ObjectKey]struct{}, len(view.Objects))
	for index, object := range view.Objects {
		if err := catalogassignmentidentityv1.ValidateObjectKey(object.Key); err != nil {
			return fmt.Errorf("View.Objects[%d].Key: %w", index, err)
		}
		if _, duplicate := objects[object.Key]; duplicate {
			return fmt.Errorf("View.Objects contains duplicate ObjectKey")
		}
		if err := validateCapabilities(fmt.Sprintf("View.Objects[%d].Capabilities", index), object.Capabilities); err != nil {
			return err
		}
		objects[object.Key] = struct{}{}
	}

	fields := make(map[FieldKey]FieldContract, len(view.Fields))
	referencePairs := make(map[relationshipIdentity]struct{})
	for index, field := range view.Fields {
		if err := ValidateFieldKey(field.Key); err != nil {
			return fmt.Errorf("View.Fields[%d].Key: %w", index, err)
		}
		if _, exists := objects[field.Key.ObjectKey]; !exists {
			return fmt.Errorf("View.Fields[%d] names a missing ObjectKey", index)
		}
		if _, duplicate := fields[field.Key]; duplicate {
			return fmt.Errorf("View.Fields contains duplicate FieldKey")
		}
		if err := appauthority.ValidateCapabilityID(string(field.DataTypeID)); err != nil {
			return fmt.Errorf("View.Fields[%d].DataTypeID: %w", index, err)
		}
		if field.EditContractKey == nil {
			if field.Createable || field.Updateable {
				return fmt.Errorf("View.Fields[%d].EditContractKey may be absent only when Createable and Updateable are false", index)
			}
		} else if err := ValidateNamespacedContractKey(*field.EditContractKey); err != nil {
			return fmt.Errorf("View.Fields[%d].EditContractKey: %w", index, err)
		}
		if err := ValidateNamespacedContractKey(field.RenderContractKey); err != nil {
			return fmt.Errorf("View.Fields[%d].RenderContractKey: %w", index, err)
		}
		seenTargets := make(map[ObjectKey]struct{}, len(field.ReferenceTargets))
		for targetIndex, target := range field.ReferenceTargets {
			if err := catalogassignmentidentityv1.ValidateObjectKey(target); err != nil {
				return fmt.Errorf("View.Fields[%d].ReferenceTargets[%d]: %w", index, targetIndex, err)
			}
			if _, exists := objects[target]; !exists {
				return fmt.Errorf("View.Fields[%d].ReferenceTargets[%d] names a missing ObjectKey", index, targetIndex)
			}
			if _, duplicate := seenTargets[target]; duplicate {
				return fmt.Errorf("View.Fields[%d].ReferenceTargets contains a duplicate ObjectKey", index)
			}
			seenTargets[target] = struct{}{}
			referencePairs[relationshipIdentity{Field: field.Key, Target: target}] = struct{}{}
		}
		if field.OwnsPolymorphicDiscriminator &&
			len(field.Key.NormalizedFieldAPIName) > MaxPolymorphicOwnerFieldAPINameBytes {
			return fmt.Errorf(
				"View.Fields[%d].Key.NormalizedFieldAPIName must be at most %d bytes when OwnsPolymorphicDiscriminator is true",
				index,
				MaxPolymorphicOwnerFieldAPINameBytes,
			)
		}
		switch field.RelationshipKind {
		case RelationshipKindNone:
			if len(field.ReferenceTargets) != 0 {
				return fmt.Errorf("View.Fields[%d] RelationshipKind NONE requires no ReferenceTargets", index)
			}
			if field.OwnsPolymorphicDiscriminator {
				return fmt.Errorf("View.Fields[%d] RelationshipKind NONE cannot own a polymorphic discriminator", index)
			}
		case RelationshipKindLookup:
			if len(field.ReferenceTargets) == 0 {
				return fmt.Errorf("View.Fields[%d] RelationshipKind LOOKUP requires at least one ReferenceTarget", index)
			}
			if len(field.ReferenceTargets) > 1 && !field.OwnsPolymorphicDiscriminator {
				return fmt.Errorf("View.Fields[%d] multi-target LOOKUP must own a polymorphic discriminator", index)
			}
		case RelationshipKindMasterDetail:
			if len(field.ReferenceTargets) != 1 {
				return fmt.Errorf("View.Fields[%d] RelationshipKind MASTER_DETAIL requires exactly one ReferenceTarget", index)
			}
			if field.OwnsPolymorphicDiscriminator {
				return fmt.Errorf("View.Fields[%d] RelationshipKind MASTER_DETAIL cannot own a polymorphic discriminator", index)
			}
		default:
			return fmt.Errorf("View.Fields[%d].RelationshipKind must be NONE, LOOKUP, or MASTER_DETAIL", index)
		}
		fields[field.Key] = field
	}

	relationships := make(map[relationshipIdentity]RelationshipContract, len(view.Relationships))
	for index, relationship := range view.Relationships {
		if err := ValidateFieldKey(relationship.ReferenceFieldKey); err != nil {
			return fmt.Errorf("View.Relationships[%d].ReferenceFieldKey: %w", index, err)
		}
		field, exists := fields[relationship.ReferenceFieldKey]
		if !exists {
			return fmt.Errorf("View.Relationships[%d] names a missing ReferenceFieldKey", index)
		}
		if err := catalogassignmentidentityv1.ValidateObjectKey(relationship.TargetObjectKey); err != nil {
			return fmt.Errorf("View.Relationships[%d].TargetObjectKey: %w", index, err)
		}
		if _, exists := objects[relationship.TargetObjectKey]; !exists {
			return fmt.Errorf("View.Relationships[%d] names a missing TargetObjectKey", index)
		}
		identity := relationshipIdentity{Field: relationship.ReferenceFieldKey, Target: relationship.TargetObjectKey}
		if _, duplicate := relationships[identity]; duplicate {
			return fmt.Errorf("View.Relationships contains a duplicate reference-field/target identity")
		}
		if _, referenced := referencePairs[identity]; !referenced {
			return fmt.Errorf("View.Relationships[%d] does not match its field ReferenceTargets", index)
		}
		switch relationship.Kind {
		case RelationshipKindLookup, RelationshipKindMasterDetail:
		default:
			return fmt.Errorf("View.Relationships[%d].Kind must be LOOKUP or MASTER_DETAIL", index)
		}
		if relationship.Kind != field.RelationshipKind {
			return fmt.Errorf("View.Relationships[%d].Kind must equal its field RelationshipKind", index)
		}
		relationships[identity] = relationship
	}
	for identity := range referencePairs {
		if _, exists := relationships[identity]; !exists {
			return fmt.Errorf("View.Relationships does not exactly cover every field ReferenceTarget")
		}
	}

	relatedLists := make(map[relatedListIdentity]struct{}, len(view.RelatedLists))
	for index, relatedList := range view.RelatedLists {
		if err := catalogassignmentidentityv1.ValidateObjectKey(relatedList.ParentObjectKey); err != nil {
			return fmt.Errorf("View.RelatedLists[%d].ParentObjectKey: %w", index, err)
		}
		if _, exists := objects[relatedList.ParentObjectKey]; !exists {
			return fmt.Errorf("View.RelatedLists[%d] names a missing ParentObjectKey", index)
		}
		if err := ValidateFieldKey(relatedList.ReferenceFieldKey); err != nil {
			return fmt.Errorf("View.RelatedLists[%d].ReferenceFieldKey: %w", index, err)
		}
		relationship := relationshipIdentity{Field: relatedList.ReferenceFieldKey, Target: relatedList.ParentObjectKey}
		if _, exists := relationships[relationship]; !exists {
			return fmt.Errorf("View.RelatedLists[%d] does not name a present relationship with the parent as target", index)
		}
		identity := relatedListIdentity{Parent: relatedList.ParentObjectKey, Field: relatedList.ReferenceFieldKey}
		if _, duplicate := relatedLists[identity]; duplicate {
			return fmt.Errorf("View.RelatedLists contains a duplicate complete stable identity")
		}
		if err := validateCapabilities(fmt.Sprintf("View.RelatedLists[%d].Capabilities", index), relatedList.Capabilities); err != nil {
			return err
		}
		relatedLists[identity] = struct{}{}
	}

	actions := make(map[actionIdentity]struct{}, len(view.Actions))
	for index, action := range view.Actions {
		if err := catalogassignmentidentityv1.ValidateObjectKey(action.ObjectKey); err != nil {
			return fmt.Errorf("View.Actions[%d].ObjectKey: %w", index, err)
		}
		if _, exists := objects[action.ObjectKey]; !exists {
			return fmt.Errorf("View.Actions[%d] names a missing ObjectKey", index)
		}
		if err := ValidateNamespacedContractKey(action.Key); err != nil {
			return fmt.Errorf("View.Actions[%d].Key: %w", index, err)
		}
		identity := actionIdentity{Object: action.ObjectKey, Key: action.Key}
		if _, duplicate := actions[identity]; duplicate {
			return fmt.Errorf("View.Actions contains a duplicate complete stable identity")
		}
		if err := appauthority.ValidateCapabilityID(string(action.ActionKindID)); err != nil {
			return fmt.Errorf("View.Actions[%d].ActionKindID: %w", index, err)
		}
		if action.TargetObjectKey != nil {
			if err := catalogassignmentidentityv1.ValidateObjectKey(*action.TargetObjectKey); err != nil {
				return fmt.Errorf("View.Actions[%d].TargetObjectKey: %w", index, err)
			}
			if _, exists := objects[*action.TargetObjectKey]; !exists {
				return fmt.Errorf("View.Actions[%d] names a missing TargetObjectKey", index)
			}
		}
		if action.ContractVersion == 0 {
			return fmt.Errorf("View.Actions[%d].ContractVersion must be positive", index)
		}
		if err := appauthority.ValidateDigest(action.ContractDigest); err != nil {
			return fmt.Errorf("View.Actions[%d].ContractDigest: %w", index, err)
		}
		if err := validateCapabilities(fmt.Sprintf("View.Actions[%d].Capabilities", index), action.Capabilities); err != nil {
			return err
		}
		actions[identity] = struct{}{}
	}

	renderers := make(map[NamespacedContractKey]struct{}, len(view.RendererComponents))
	for index, renderer := range view.RendererComponents {
		if err := ValidateNamespacedContractKey(renderer.Key); err != nil {
			return fmt.Errorf("View.RendererComponents[%d].Key: %w", index, err)
		}
		if _, duplicate := renderers[renderer.Key]; duplicate {
			return fmt.Errorf("View.RendererComponents contains a duplicate Key")
		}
		if renderer.ContractVersion == 0 {
			return fmt.Errorf("View.RendererComponents[%d].ContractVersion must be positive", index)
		}
		if err := appauthority.ValidateDigest(renderer.ContractDigest); err != nil {
			return fmt.Errorf("View.RendererComponents[%d].ContractDigest: %w", index, err)
		}
		if err := validateCapabilities(fmt.Sprintf("View.RendererComponents[%d].Capabilities", index), renderer.Capabilities); err != nil {
			return err
		}
		renderers[renderer.Key] = struct{}{}
	}
	for index, field := range view.Fields {
		if _, exists := renderers[field.RenderContractKey]; !exists {
			return fmt.Errorf("View.Fields[%d].RenderContractKey names no renderer/component contract", index)
		}
		if field.EditContractKey != nil {
			if _, exists := renderers[*field.EditContractKey]; !exists {
				return fmt.Errorf("View.Fields[%d].EditContractKey names no renderer/component contract", index)
			}
		}
	}

	formats := make(map[LayoutFormatVersion]struct{}, len(view.AcceptedLayoutFormats))
	for index, format := range view.AcceptedLayoutFormats {
		if err := appauthority.ValidateCapabilityID(string(format.LayoutFormatVersion)); err != nil {
			return fmt.Errorf("View.AcceptedLayoutFormats[%d].LayoutFormatVersion: %w", index, err)
		}
		if _, duplicate := formats[format.LayoutFormatVersion]; duplicate {
			return fmt.Errorf("View.AcceptedLayoutFormats contains a duplicate LayoutFormatVersion")
		}
		if err := appauthority.ValidateDigest(format.ContractDigest); err != nil {
			return fmt.Errorf("View.AcceptedLayoutFormats[%d].ContractDigest: %w", index, err)
		}
		formats[format.LayoutFormatVersion] = struct{}{}
	}

	evidenceKeys := make(map[layoutbundlekeyv1.BundleKey]struct{}, len(view.BundleAuthorityEvidence))
	for index, evidence := range view.BundleAuthorityEvidence {
		if err := layoutbundlekeyv1.ValidateBundleKey(evidence.BundleKey); err != nil {
			return fmt.Errorf("View.BundleAuthorityEvidence[%d].BundleKey: %w", index, err)
		}
		if _, duplicate := evidenceKeys[evidence.BundleKey]; duplicate {
			return fmt.Errorf("View.BundleAuthorityEvidence contains a duplicate BundleKey")
		}
		if expected := evidenceVersion(evidence.BundleKey.SlotOwnerKind); evidence.EvidenceVersion != expected {
			return fmt.Errorf("View.BundleAuthorityEvidence[%d].EvidenceVersion must equal %q", index, expected)
		}
		if err := appauthority.ValidateDigest(evidence.EvidenceDigest); err != nil {
			return fmt.Errorf("View.BundleAuthorityEvidence[%d].EvidenceDigest: %w", index, err)
		}
		evidenceKeys[evidence.BundleKey] = struct{}{}
	}

	return nil
}

// VerifyReferencedArtifacts verifies the two typed content-addressed neutral
// values named by a structurally valid view. It also enforces exact source
// binding, ACTIVE object closure, and one-to-one required/evidence BundleKey
// coverage. The source-bound caller remains responsible for loading the exact
// canonical artifact bytes. This helper also does not verify authority
// evidence artifacts; callers must verify those owner-specific artifacts
// separately before candidate admission.
func VerifyReferencedArtifacts(
	view View,
	assignment catalogassignmentidentityv1.View,
	requiredSet layoutbundlekeyv1.RequiredBundleKeySet,
) error {
	if err := ValidateView(view); err != nil {
		return err
	}
	if err := catalogassignmentidentityv1.ValidateView(assignment); err != nil {
		return fmt.Errorf("CatalogAssignmentIdentityView: %w", err)
	}
	assignmentDigest, err := catalogassignmentidentityv1.ViewDigest(assignment)
	if err != nil {
		return fmt.Errorf("CatalogAssignmentIdentityView: %w", err)
	}
	if assignmentDigest != view.CatalogAssignmentIdentityViewDigest {
		return fmt.Errorf("CatalogAssignmentIdentityView digest does not match View.CatalogAssignmentIdentityViewDigest")
	}
	if assignment.SourceCatalogSnapshot != view.SourceCatalogSnapshot {
		return fmt.Errorf("CatalogAssignmentIdentityView SourceCatalogSnapshot does not exactly match View.SourceCatalogSnapshot")
	}
	if err := layoutbundlekeyv1.ValidateRequiredBundleKeySet(requiredSet); err != nil {
		return fmt.Errorf("RequiredBundleKeySet: %w", err)
	}
	requiredDigest, err := layoutbundlekeyv1.RequiredBundleKeySetDigest(requiredSet)
	if err != nil {
		return fmt.Errorf("RequiredBundleKeySet: %w", err)
	}
	if requiredDigest != view.RequiredBundleKeySetDigest {
		return fmt.Errorf("RequiredBundleKeySet digest does not match View.RequiredBundleKeySetDigest")
	}

	activeObjects := make(map[ObjectKey]struct{}, len(assignment.Objects))
	for _, identity := range assignment.Objects {
		if identity.State == catalogassignmentidentityv1.IdentityStateActive {
			activeObjects[identity.Key] = struct{}{}
		}
	}
	for index, object := range view.Objects {
		if _, active := activeObjects[object.Key]; !active {
			return fmt.Errorf("View.Objects[%d] does not resolve to an ACTIVE assignment-view object", index)
		}
		delete(activeObjects, object.Key)
	}
	if len(activeObjects) != 0 {
		return fmt.Errorf("View.Objects does not exactly cover every ACTIVE assignment-view object")
	}

	if len(view.BundleAuthorityEvidence) != len(requiredSet.BundleKeys) {
		return fmt.Errorf("View.BundleAuthorityEvidence key set does not exactly equal RequiredBundleKeySet")
	}
	requiredKeys := make(map[layoutbundlekeyv1.BundleKey]struct{}, len(requiredSet.BundleKeys))
	for _, key := range requiredSet.BundleKeys {
		requiredKeys[key] = struct{}{}
	}
	for _, evidence := range view.BundleAuthorityEvidence {
		if _, required := requiredKeys[evidence.BundleKey]; !required {
			return fmt.Errorf("View.BundleAuthorityEvidence key set does not exactly equal RequiredBundleKeySet")
		}
	}
	return nil
}

func validateArrayBounds(view View) error {
	topLevel := []struct {
		name  string
		nil   bool
		count int
	}{
		{"Objects", view.Objects == nil, len(view.Objects)},
		{"Fields", view.Fields == nil, len(view.Fields)},
		{"Relationships", view.Relationships == nil, len(view.Relationships)},
		{"RelatedLists", view.RelatedLists == nil, len(view.RelatedLists)},
		{"Actions", view.Actions == nil, len(view.Actions)},
		{"RendererComponents", view.RendererComponents == nil, len(view.RendererComponents)},
		{"AcceptedLayoutFormats", view.AcceptedLayoutFormats == nil, len(view.AcceptedLayoutFormats)},
		{"BundleAuthorityEvidence", view.BundleAuthorityEvidence == nil, len(view.BundleAuthorityEvidence)},
	}
	for _, array := range topLevel {
		if array.nil {
			return fmt.Errorf("View.%s must be present", array.name)
		}
		if array.count > MaxEntriesPerArray {
			return fmt.Errorf("View.%s exceeds %d entries", array.name, MaxEntriesPerArray)
		}
	}
	for index, object := range view.Objects {
		if object.Capabilities == nil {
			return fmt.Errorf("View.Objects[%d].Capabilities must be present", index)
		}
		if len(object.Capabilities) > MaxEntriesPerArray {
			return fmt.Errorf("View.Objects[%d].Capabilities exceeds %d entries", index, MaxEntriesPerArray)
		}
	}
	for index, field := range view.Fields {
		if field.ReferenceTargets == nil {
			return fmt.Errorf("View.Fields[%d].ReferenceTargets must be present", index)
		}
		if len(field.ReferenceTargets) > MaxEntriesPerArray {
			return fmt.Errorf("View.Fields[%d].ReferenceTargets exceeds %d entries", index, MaxEntriesPerArray)
		}
	}
	for index, relatedList := range view.RelatedLists {
		if relatedList.Capabilities == nil {
			return fmt.Errorf("View.RelatedLists[%d].Capabilities must be present", index)
		}
		if len(relatedList.Capabilities) > MaxEntriesPerArray {
			return fmt.Errorf("View.RelatedLists[%d].Capabilities exceeds %d entries", index, MaxEntriesPerArray)
		}
	}
	for index, action := range view.Actions {
		if action.Capabilities == nil {
			return fmt.Errorf("View.Actions[%d].Capabilities must be present", index)
		}
		if len(action.Capabilities) > MaxEntriesPerArray {
			return fmt.Errorf("View.Actions[%d].Capabilities exceeds %d entries", index, MaxEntriesPerArray)
		}
	}
	for index, renderer := range view.RendererComponents {
		if renderer.Capabilities == nil {
			return fmt.Errorf("View.RendererComponents[%d].Capabilities must be present", index)
		}
		if len(renderer.Capabilities) > MaxEntriesPerArray {
			return fmt.Errorf("View.RendererComponents[%d].Capabilities exceeds %d entries", index, MaxEntriesPerArray)
		}
	}
	return nil
}

func validateCapabilities(label string, capabilities []CapabilityID) error {
	seen := make(map[CapabilityID]struct{}, len(capabilities))
	for index, capability := range capabilities {
		if err := appauthority.ValidateCapabilityID(string(capability)); err != nil {
			return fmt.Errorf("%s[%d]: %w", label, index, err)
		}
		if _, duplicate := seen[capability]; duplicate {
			return fmt.Errorf("%s contains a duplicate CapabilityID", label)
		}
		seen[capability] = struct{}{}
	}
	return nil
}

func evidenceVersion(kind layoutbundlekeyv1.SlotOwnerKind) string {
	switch kind {
	case layoutbundlekeyv1.SlotOwnerKindApp:
		return AppAuthorityEvidenceVersion
	case layoutbundlekeyv1.SlotOwnerKindTenant:
		return TenantAuthorityEvidenceVersion
	case layoutbundlekeyv1.SlotOwnerKindSystem:
		return SystemAuthorityEvidenceVersion
	default:
		return ""
	}
}

type relationshipIdentity struct {
	Field  FieldKey
	Target ObjectKey
}

type relatedListIdentity struct {
	Parent ObjectKey
	Field  FieldKey
}

type actionIdentity struct {
	Object ObjectKey
	Key    NamespacedContractKey
}
