package connector

import (
	"bytes"
	"crypto/sha256"
	"encoding/json"
	"fmt"
	"io"
	"strings"
	"unicode"

	"github.com/nexuscrm/shared/pkg/constants"
	contract "github.com/pingcap-inc/nexuscrm-contract/pkg/connector"
)

// ConnectorPushActionSlotID is retained only for legacy test fixtures that need
// a deterministic action coordinate. New durable PushToConnector ownership must
// use the pack-owned slotLineageId literal instead.
func ConnectorPushActionSlotID(flowDeveloperName, flowActionName string) string {
	return strings.TrimSpace(flowDeveloperName) + "/" + strings.TrimSpace(flowActionName)
}

// ParseConnectorPushDurableMetadataContractFromPayload builds one durable
// PushToConnector v2 metadata contract from a source-backed app-pack component
// payload. The owner is the pack name supplied by the installer, not a payload
// or Flow input parameter.
func ParseConnectorPushDurableMetadataContractFromPayload(raw []byte, owner string) (contract.ConnectorPushDurableMetadataContract, error) {
	owner = strings.TrimSpace(owner)
	if owner == "" {
		return contract.ConnectorPushDurableMetadataContract{}, fmt.Errorf("connector_push_contract: owner must not be blank")
	}
	fields, err := parseConnectorPushContractFields(raw)
	if err != nil {
		return contract.ConnectorPushDurableMetadataContract{}, err
	}

	rejectedDerivedFields := map[string]string{
		"connectorOwnerKey":  "connectorOwnerKey is derived from the pack owner",
		"requesterOwnerPack": "requesterOwnerPack is derived from the pack owner",
		"actionSlotId":       "actionSlotId is derived from slotLineageId",
		"actionType":         "actionType is fixed to PushToConnector",
	}
	for name, message := range rejectedDerivedFields {
		if _, ok := fields[name]; ok {
			return contract.ConnectorPushDurableMetadataContract{}, fmt.Errorf("connector_push_contract: %s", message)
		}
	}

	allowed := map[string]struct{}{
		"flowDeveloperName":           {},
		"flowActionName":              {},
		"slotLineageId":               {},
		"previousSlotLineageIds":      {},
		"sourceObject":                {},
		"destinationSlotId":           {},
		"sameOriginWritePolicy":       {},
		"profileOwnerPack":            {},
		"profileName":                 {},
		"targetObject":                {},
		"bodyRoot":                    {},
		"createPathTemplate":          {},
		"updatePathTemplate":          {},
		"responseIdPath":              {},
		"remoteIdField":               {},
		"remoteIdProvenanceField":     {},
		"diagnosticErrorFieldPaths":   {},
		"createConflictResolution":    {},
		"associations":                {},
		"historicalReconciliationKey": {},
		"recoverySourceEligibility":   {},
	}
	for name := range fields {
		if _, ok := allowed[name]; !ok {
			return contract.ConnectorPushDurableMetadataContract{}, fmt.Errorf("connector_push_contract: unknown field %q", name)
		}
	}
	if _, err := connectorPushHistoricalReconciliationKey(fields); err != nil {
		return contract.ConnectorPushDurableMetadataContract{}, err
	}
	if _, err := connectorPushRecoverySourceEligibility(fields); err != nil {
		return contract.ConnectorPushDurableMetadataContract{}, err
	}

	flowDeveloperName, err := connectorPushContractLiteralString(fields, "flowDeveloperName", true)
	if err != nil {
		return contract.ConnectorPushDurableMetadataContract{}, err
	}
	flowActionName, err := connectorPushContractLiteralString(fields, "flowActionName", true)
	if err != nil {
		return contract.ConnectorPushDurableMetadataContract{}, err
	}
	slotLineageID, err := connectorPushContractLiteralString(fields, "slotLineageId", true)
	if err != nil {
		return contract.ConnectorPushDurableMetadataContract{}, err
	}
	previousSlotLineageIDs, err := connectorPushContractLiteralStringList(fields, "previousSlotLineageIds")
	if err != nil {
		return contract.ConnectorPushDurableMetadataContract{}, err
	}
	sourceObject, err := connectorPushContractLiteralString(fields, "sourceObject", true)
	if err != nil {
		return contract.ConnectorPushDurableMetadataContract{}, err
	}
	destinationSlotID, err := connectorPushContractLiteralString(fields, "destinationSlotId", true)
	if err != nil {
		return contract.ConnectorPushDurableMetadataContract{}, err
	}
	sameOriginWritePolicy, err := connectorPushSameOriginWritePolicy(fields)
	if err != nil {
		return contract.ConnectorPushDurableMetadataContract{}, err
	}
	profileOwnerPack, err := connectorPushContractLiteralString(fields, "profileOwnerPack", true)
	if err != nil {
		return contract.ConnectorPushDurableMetadataContract{}, err
	}
	profileName, err := connectorPushContractLiteralString(fields, "profileName", true)
	if err != nil {
		return contract.ConnectorPushDurableMetadataContract{}, err
	}
	targetObject, err := connectorPushContractLiteralString(fields, "targetObject", true)
	if err != nil {
		return contract.ConnectorPushDurableMetadataContract{}, err
	}
	bodyRoot, err := connectorPushContractLiteralString(fields, "bodyRoot", false)
	if err != nil {
		return contract.ConnectorPushDurableMetadataContract{}, err
	}
	if err := validateConnectorPushContractBodyRoot(bodyRoot); err != nil {
		return contract.ConnectorPushDurableMetadataContract{}, err
	}
	createPathTemplate, err := connectorPushContractLiteralString(fields, "createPathTemplate", true)
	if err != nil {
		return contract.ConnectorPushDurableMetadataContract{}, err
	}
	updatePathTemplate, err := connectorPushContractLiteralString(fields, "updatePathTemplate", true)
	if err != nil {
		return contract.ConnectorPushDurableMetadataContract{}, err
	}
	responseIDPath, err := connectorPushContractLiteralString(fields, "responseIdPath", true)
	if err != nil {
		return contract.ConnectorPushDurableMetadataContract{}, err
	}
	remoteIDField, err := connectorPushContractLiteralString(fields, "remoteIdField", true)
	if err != nil {
		return contract.ConnectorPushDurableMetadataContract{}, err
	}
	remoteIDProvenanceField, err := connectorPushContractLiteralString(fields, "remoteIdProvenanceField", false)
	if err != nil {
		return contract.ConnectorPushDurableMetadataContract{}, err
	}
	diagnosticErrorFieldPaths, err := connectorPushContractLiteralStringList(fields, "diagnosticErrorFieldPaths")
	if err != nil {
		return contract.ConnectorPushDurableMetadataContract{}, err
	}
	createConflictResolution, err := connectorPushCreateConflictResolution(fields)
	if err != nil {
		return contract.ConnectorPushDurableMetadataContract{}, err
	}
	associations, err := connectorPushAssociationContracts(fields)
	if err != nil {
		return contract.ConnectorPushDurableMetadataContract{}, err
	}
	if createConflictResolution != nil &&
		createConflictResolution.NoOldSnapshotPolicy != "" &&
		len(associations) > 0 {
		return contract.ConnectorPushDurableMetadataContract{}, fmt.Errorf("connector_push_contract: no-old-snapshot conflict policy does not permit associations")
	}
	if err := validateConnectorPushContractBusinessKeySegment("flowDeveloperName", flowDeveloperName); err != nil {
		return contract.ConnectorPushDurableMetadataContract{}, err
	}
	if err := validateConnectorPushContractBusinessKeySegment("flowActionName", flowActionName); err != nil {
		return contract.ConnectorPushDurableMetadataContract{}, err
	}
	if err := validateConnectorPushContractBusinessKeySegment("slotLineageId", slotLineageID); err != nil {
		return contract.ConnectorPushDurableMetadataContract{}, err
	}
	previousSeen := map[string]struct{}{}
	for _, previousSlotLineageID := range previousSlotLineageIDs {
		if err := validateConnectorPushContractBusinessKeySegment("previousSlotLineageIds", previousSlotLineageID); err != nil {
			return contract.ConnectorPushDurableMetadataContract{}, err
		}
		if previousSlotLineageID == slotLineageID {
			return contract.ConnectorPushDurableMetadataContract{}, fmt.Errorf("connector_push_contract: previousSlotLineageIds must not include slotLineageId")
		}
		lower := strings.ToLower(previousSlotLineageID)
		if _, ok := previousSeen[lower]; ok {
			return contract.ConnectorPushDurableMetadataContract{}, fmt.Errorf("connector_push_contract: previousSlotLineageIds contains duplicate %q", previousSlotLineageID)
		}
		previousSeen[lower] = struct{}{}
	}
	if err := validateConnectorPushContractBusinessKeySegment("sourceObject", sourceObject); err != nil {
		return contract.ConnectorPushDurableMetadataContract{}, err
	}
	if err := validateConnectorPushContractBusinessKeySegment("destinationSlotId", destinationSlotID); err != nil {
		return contract.ConnectorPushDurableMetadataContract{}, err
	}

	return contract.ConnectorPushDurableMetadataContract{
		ConnectorOwnerKey:         owner,
		ActionSlotID:              slotLineageID,
		PreviousActionSlotIDs:     previousSlotLineageIDs,
		ActionType:                constants.ActionTypePushToConnector,
		SameOriginWritePolicy:     sameOriginWritePolicy,
		FlowDeveloperName:         flowDeveloperName,
		FlowActionName:            flowActionName,
		SourceObject:              sourceObject,
		DestinationSlotID:         destinationSlotID,
		ProfileOwnerPack:          profileOwnerPack,
		ProfileName:               profileName,
		TargetObject:              targetObject,
		BodyRoot:                  bodyRoot,
		CreatePathTemplate:        createPathTemplate,
		UpdatePathTemplate:        updatePathTemplate,
		ResponseIDPath:            responseIDPath,
		RemoteIDField:             remoteIDField,
		RemoteIDProvenanceField:   remoteIDProvenanceField,
		DiagnosticErrorFieldPaths: diagnosticErrorFieldPaths,
		CreateConflictResolution:  createConflictResolution,
		Associations:              associations,
	}, nil
}

// ParseConnectorPushRecoverySourceEligibilityFromPayload reads the optional
// recovery eligibility sidecar from the same source-backed connector contract
// payload. It deliberately remains outside ConnectorPushDurableMetadataContract
// so enabling recovery cannot change existing durable identities or hashes.
func ParseConnectorPushRecoverySourceEligibilityFromPayload(raw []byte) (*contract.ConnectorPushRecoverySourceEligibilityContract, error) {
	fields, err := parseConnectorPushContractFields(raw)
	if err != nil {
		return nil, err
	}
	return connectorPushRecoverySourceEligibility(fields)
}

func connectorPushRecoverySourceEligibility(fields map[string]json.RawMessage) (*contract.ConnectorPushRecoverySourceEligibilityContract, error) {
	raw, ok := fields["recoverySourceEligibility"]
	if !ok {
		return nil, nil
	}
	dec := json.NewDecoder(bytes.NewReader(raw))
	dec.DisallowUnknownFields()
	var item struct {
		RequiredFalseBooleanFields []string `json:"requiredFalseBooleanFields"`
	}
	if err := dec.Decode(&item); err != nil {
		return nil, fmt.Errorf("connector_push_contract: recoverySourceEligibility must be an object with requiredFalseBooleanFields: %w", err)
	}
	if tok, err := dec.Token(); err == nil {
		return nil, fmt.Errorf("connector_push_contract: recoverySourceEligibility has unexpected trailing token %v", tok)
	} else if err != io.EOF {
		return nil, fmt.Errorf("connector_push_contract: recoverySourceEligibility has invalid trailing JSON: %w", err)
	}
	if len(item.RequiredFalseBooleanFields) == 0 {
		return nil, fmt.Errorf("connector_push_contract: recoverySourceEligibility.requiredFalseBooleanFields must not be empty")
	}
	seen := make(map[string]struct{}, len(item.RequiredFalseBooleanFields))
	for index, field := range item.RequiredFalseBooleanFields {
		field = strings.TrimSpace(field)
		if field == "" || connectorPushContractLooksDynamic(field) {
			return nil, fmt.Errorf("connector_push_contract: recoverySourceEligibility.requiredFalseBooleanFields[%d] must be a pack-owned literal field name", index)
		}
		if err := validateConnectorPushSimplePayloadField("recoverySourceEligibility.requiredFalseBooleanFields", field); err != nil {
			return nil, err
		}
		key := strings.ToLower(field)
		if _, duplicate := seen[key]; duplicate {
			return nil, fmt.Errorf("connector_push_contract: recoverySourceEligibility.requiredFalseBooleanFields contains duplicate %q", field)
		}
		seen[key] = struct{}{}
		item.RequiredFalseBooleanFields[index] = field
	}
	return &contract.ConnectorPushRecoverySourceEligibilityContract{
		RequiredFalseBooleanFields: append([]string(nil), item.RequiredFalseBooleanFields...),
	}, nil
}

// ConnectorPushHistoricalReconciliationBinding is the validated, immutable
// metadata authority for one protected historical reconciliation lane. It is a
// transient read model assembled from one source-backed contract sidecar and
// its exact outbound mapping; it is not part of the ordinary connector-push
// DTO or body fingerprint.
type ConnectorPushHistoricalReconciliationBinding struct {
	Contract    contract.ConnectorPushDurableMetadataContract
	Key         contract.ConnectorPushHistoricalReconciliationKeyContract
	Mapping     contract.ConnectorFieldMappingInput
	Fingerprint string
}

// ParseConnectorPushHistoricalReconciliationKeyFromPayload reads the optional
// sidecar from the same source-backed connector_push_contract payload. Absence
// returns nil. Presence is strict and fail-closed: only one non-blank literal
// sourceField is accepted.
func ParseConnectorPushHistoricalReconciliationKeyFromPayload(raw []byte) (*contract.ConnectorPushHistoricalReconciliationKeyContract, error) {
	fields, err := parseConnectorPushContractFields(raw)
	if err != nil {
		return nil, err
	}
	return connectorPushHistoricalReconciliationKey(fields)
}

func connectorPushHistoricalReconciliationKey(fields map[string]json.RawMessage) (*contract.ConnectorPushHistoricalReconciliationKeyContract, error) {
	raw, ok := fields["historicalReconciliationKey"]
	if !ok {
		return nil, nil
	}
	dec := json.NewDecoder(bytes.NewReader(raw))
	dec.DisallowUnknownFields()
	var item struct {
		SourceField string `json:"sourceField"`
	}
	if err := dec.Decode(&item); err != nil {
		return nil, fmt.Errorf("connector_push_contract: historicalReconciliationKey must be an object with one literal sourceField: %w", err)
	}
	if tok, err := dec.Token(); err == nil {
		return nil, fmt.Errorf("connector_push_contract: historicalReconciliationKey has unexpected trailing token %v", tok)
	} else if err != io.EOF {
		return nil, fmt.Errorf("connector_push_contract: historicalReconciliationKey has invalid trailing JSON: %w", err)
	}
	sourceField := strings.TrimSpace(item.SourceField)
	if sourceField == "" {
		return nil, fmt.Errorf("connector_push_contract: historicalReconciliationKey.sourceField must not be blank")
	}
	if connectorPushContractLooksDynamic(sourceField) {
		return nil, fmt.Errorf("connector_push_contract: historicalReconciliationKey.sourceField must be a pack-owned literal string; Flow or record references are not allowed")
	}
	if err := validateConnectorPushSimplePayloadField("historicalReconciliationKey.sourceField", sourceField); err != nil {
		return nil, err
	}
	return &contract.ConnectorPushHistoricalReconciliationKeyContract{SourceField: sourceField}, nil
}

// ResolveConnectorPushHistoricalReconciliationBinding cross-validates one
// explicit sidecar against the installed contract, outbound mappings, and
// source schema. Exactly one direct scalar mapping must resolve; transforms,
// value maps, defaults, omit policies, and projection-only rows are rejected.
func ResolveConnectorPushHistoricalReconciliationBinding(
	c contract.ConnectorPushDurableMetadataContract,
	key contract.ConnectorPushHistoricalReconciliationKeyContract,
	mappings []contract.ConnectorFieldMappingInput,
	opts contract.ConnectorPushContractValidationOptions,
) (ConnectorPushHistoricalReconciliationBinding, error) {
	sourceField := strings.TrimSpace(key.SourceField)
	if sourceField == "" {
		return ConnectorPushHistoricalReconciliationBinding{}, fmt.Errorf("connector_push_contract: historicalReconciliationKey.sourceField must not be blank")
	}
	if connectorPushContractLooksDynamic(sourceField) {
		return ConnectorPushHistoricalReconciliationBinding{}, fmt.Errorf("connector_push_contract: historicalReconciliationKey.sourceField must be a pack-owned literal string; Flow or record references are not allowed")
	}
	if err := validateConnectorPushSimplePayloadField("historicalReconciliationKey.sourceField", sourceField); err != nil {
		return ConnectorPushHistoricalReconciliationBinding{}, err
	}
	if err := validateConnectorPushHistoricalReconciliationSourceFieldSchema(c, sourceField, opts); err != nil {
		return ConnectorPushHistoricalReconciliationBinding{}, err
	}

	matched := make([]contract.ConnectorFieldMappingInput, 0, 1)
	for _, row := range mappings {
		if !connectorPushContractOwnsOutboundMapping(c, row) {
			continue
		}
		if strings.TrimSpace(row.SourceField) != sourceField {
			continue
		}
		matched = append(matched, row)
	}
	if len(matched) != 1 {
		return ConnectorPushHistoricalReconciliationBinding{}, fmt.Errorf(
			"connector_push_contract %s/%s historicalReconciliationKey.sourceField %q must resolve to exactly one owned outbound connector_field_mapping; got %d",
			strings.TrimSpace(c.FlowDeveloperName),
			strings.TrimSpace(c.FlowActionName),
			sourceField,
			len(matched),
		)
	}
	mapping := matched[0]
	targetField := strings.TrimSpace(mapping.TargetField)
	if targetField == "" {
		return ConnectorPushHistoricalReconciliationBinding{}, fmt.Errorf("connector_push_contract: historical reconciliation mapping targetField must not be blank")
	}
	if err := validateConnectorPushSimplePayloadField("historical reconciliation mapping targetField", targetField); err != nil {
		return ConnectorPushHistoricalReconciliationBinding{}, err
	}
	if strings.TrimSpace(mapping.TransformExpression) != "" {
		return ConnectorPushHistoricalReconciliationBinding{}, fmt.Errorf("connector_push_contract: historical reconciliation mapping must not declare TransformExpression")
	}
	if strings.TrimSpace(mapping.ValueMap) != "" {
		return ConnectorPushHistoricalReconciliationBinding{}, fmt.Errorf("connector_push_contract: historical reconciliation mapping must not declare ValueMap")
	}
	if strings.TrimSpace(mapping.DefaultValue) != "" {
		return ConnectorPushHistoricalReconciliationBinding{}, fmt.Errorf("connector_push_contract: historical reconciliation mapping must not declare DefaultValue")
	}
	if strings.TrimSpace(mapping.OnUnmappedValue) != "" {
		return ConnectorPushHistoricalReconciliationBinding{}, fmt.Errorf("connector_push_contract: historical reconciliation mapping must not declare OnUnmappedValue")
	}
	if mapping.ProjectionOnly {
		return ConnectorPushHistoricalReconciliationBinding{}, fmt.Errorf("connector_push_contract: historical reconciliation mapping must not be projection-only")
	}

	key.SourceField = sourceField
	mapping.Source = strings.TrimSpace(mapping.Source)
	mapping.SourceObject = strings.TrimSpace(mapping.SourceObject)
	mapping.TargetObject = strings.TrimSpace(mapping.TargetObject)
	mapping.ActionType = strings.TrimSpace(mapping.ActionType)
	mapping.SourceField = sourceField
	mapping.TargetField = targetField
	mapping.Direction = strings.TrimSpace(mapping.Direction)
	mapping.TransformExpression = strings.TrimSpace(mapping.TransformExpression)
	mapping.ConflictPolicy = strings.TrimSpace(mapping.ConflictPolicy)
	mapping.DefaultValue = strings.TrimSpace(mapping.DefaultValue)
	mapping.ValueMap = strings.TrimSpace(mapping.ValueMap)
	mapping.OnUnmappedValue = strings.TrimSpace(mapping.OnUnmappedValue)

	fingerprint, err := connectorPushHistoricalReconciliationFingerprint(c, key, mapping)
	if err != nil {
		return ConnectorPushHistoricalReconciliationBinding{}, err
	}
	return ConnectorPushHistoricalReconciliationBinding{
		Contract:    c,
		Key:         key,
		Mapping:     mapping,
		Fingerprint: fingerprint,
	}, nil
}

func validateConnectorPushHistoricalReconciliationSourceFieldSchema(c contract.ConnectorPushDurableMetadataContract, sourceField string, opts contract.ConnectorPushContractValidationOptions) error {
	sourceObject := strings.TrimSpace(c.SourceObject)
	fields, ok := opts.ObjectFields[strings.ToLower(sourceObject)]
	if !ok {
		return fmt.Errorf("connector_push_contract: historicalReconciliationKey.sourceField schema is required to validate %s.%s", sourceObject, sourceField)
	}
	field, err := connectorPushFieldMetadata(fields, sourceField)
	if err != nil {
		return fmt.Errorf("connector_push_contract: historicalReconciliationKey.sourceField %s.%s %w", sourceObject, sourceField, err)
	}
	fieldType := constants.NormalizeFieldType(field.Type)
	switch fieldType {
	case constants.FieldTypeString,
		constants.FieldTypeInt,
		constants.FieldTypeDouble,
		constants.FieldTypeCurrency,
		constants.FieldTypeDate,
		constants.FieldTypeDatetime,
		constants.FieldTypeBoolean,
		constants.FieldTypePicklist,
		constants.FieldTypeEmail,
		constants.FieldTypePhone,
		constants.FieldTypeTextarea,
		constants.FieldTypeUrl,
		constants.FieldTypePercent,
		constants.FieldTypeFormula,
		constants.FieldTypeRollupSummary,
		constants.FieldTypeRichtextarea,
		constants.FieldTypeAutonumber,
		constants.FieldTypeEncryptedString,
		constants.FieldTypePassword:
		return nil
	case constants.FieldTypeReference,
		constants.FieldTypeMasterDetail,
		constants.FieldTypeMultiPicklist,
		constants.FieldTypeJson:
		return fmt.Errorf("connector_push_contract: historicalReconciliationKey.sourceField %s.%s has non-scalar Type=%q", sourceObject, sourceField, strings.TrimSpace(field.Type))
	case "":
		return fmt.Errorf("connector_push_contract: historicalReconciliationKey.sourceField %s.%s has blank Type", sourceObject, sourceField)
	default:
		return fmt.Errorf("connector_push_contract: historicalReconciliationKey.sourceField %s.%s has unsupported Type=%q", sourceObject, sourceField, strings.TrimSpace(field.Type))
	}
}

func connectorPushHistoricalReconciliationFingerprint(c contract.ConnectorPushDurableMetadataContract, key contract.ConnectorPushHistoricalReconciliationKeyContract, mapping contract.ConnectorFieldMappingInput) (string, error) {
	canonical := struct {
		Version             string `json:"version"`
		ConnectorOwnerKey   string `json:"connectorOwnerKey"`
		ActionSlotID        string `json:"actionSlotId"`
		ActionType          string `json:"actionType"`
		FlowDeveloperName   string `json:"flowDeveloperName"`
		FlowActionName      string `json:"flowActionName"`
		SourceObject        string `json:"sourceObject"`
		DestinationSlotID   string `json:"destinationSlotId"`
		ProfileOwnerPack    string `json:"profileOwnerPack"`
		ProfileName         string `json:"profileName"`
		TargetObject        string `json:"targetObject"`
		RemoteIDField       string `json:"remoteIdField"`
		KeySourceField      string `json:"keySourceField"`
		MappingSource       string `json:"mappingSource"`
		MappingSourceObject string `json:"mappingSourceObject"`
		MappingTargetObject string `json:"mappingTargetObject"`
		MappingActionType   string `json:"mappingActionType"`
		MappingSourceField  string `json:"mappingSourceField"`
		MappingTargetField  string `json:"mappingTargetField"`
		MappingDirection    string `json:"mappingDirection"`
		MappingTransform    string `json:"mappingTransformExpression"`
		MappingConflict     string `json:"mappingConflictPolicy"`
		MappingDefault      string `json:"mappingDefaultValue"`
		MappingValueMap     string `json:"mappingValueMap"`
		MappingOnUnmapped   string `json:"mappingOnUnmappedValue"`
		MappingSortOrder    int    `json:"mappingSortOrder"`
		MappingProjection   bool   `json:"mappingProjectionOnly"`
	}{
		Version:             "connector-push-historical-reconciliation/v1",
		ConnectorOwnerKey:   strings.TrimSpace(c.ConnectorOwnerKey),
		ActionSlotID:        strings.TrimSpace(c.ActionSlotID),
		ActionType:          strings.TrimSpace(c.ActionType),
		FlowDeveloperName:   strings.TrimSpace(c.FlowDeveloperName),
		FlowActionName:      strings.TrimSpace(c.FlowActionName),
		SourceObject:        strings.TrimSpace(c.SourceObject),
		DestinationSlotID:   strings.TrimSpace(c.DestinationSlotID),
		ProfileOwnerPack:    strings.TrimSpace(c.ProfileOwnerPack),
		ProfileName:         strings.TrimSpace(c.ProfileName),
		TargetObject:        strings.TrimSpace(c.TargetObject),
		RemoteIDField:       strings.TrimSpace(c.RemoteIDField),
		KeySourceField:      strings.TrimSpace(key.SourceField),
		MappingSource:       strings.TrimSpace(mapping.Source),
		MappingSourceObject: strings.TrimSpace(mapping.SourceObject),
		MappingTargetObject: strings.TrimSpace(mapping.TargetObject),
		MappingActionType:   strings.TrimSpace(mapping.ActionType),
		MappingSourceField:  strings.TrimSpace(mapping.SourceField),
		MappingTargetField:  strings.TrimSpace(mapping.TargetField),
		MappingDirection:    strings.TrimSpace(mapping.Direction),
		MappingTransform:    strings.TrimSpace(mapping.TransformExpression),
		MappingConflict:     strings.TrimSpace(mapping.ConflictPolicy),
		MappingDefault:      strings.TrimSpace(mapping.DefaultValue),
		MappingValueMap:     strings.TrimSpace(mapping.ValueMap),
		MappingOnUnmapped:   strings.TrimSpace(mapping.OnUnmappedValue),
		MappingSortOrder:    mapping.SortOrder,
		MappingProjection:   mapping.ProjectionOnly,
	}
	raw, err := json.Marshal(canonical)
	if err != nil {
		return "", fmt.Errorf("connector_push_contract: historical reconciliation fingerprint: %w", err)
	}
	digest := sha256.Sum256(raw)
	return fmt.Sprintf("%x", digest[:]), nil
}

func connectorPushSameOriginWritePolicy(fields map[string]json.RawMessage) (string, error) {
	policy, err := connectorPushContractLiteralString(fields, "sameOriginWritePolicy", false)
	if err != nil {
		return "", err
	}
	if policy == "" {
		return contract.ConnectorPushSameOriginWritePolicySuppress, nil
	}
	switch policy {
	case contract.ConnectorPushSameOriginWritePolicySuppress, contract.ConnectorPushSameOriginWritePolicyAllowPatchExisting:
		return policy, nil
	default:
		return "", fmt.Errorf("connector_push_contract: sameOriginWritePolicy must be %q or %q", contract.ConnectorPushSameOriginWritePolicySuppress, contract.ConnectorPushSameOriginWritePolicyAllowPatchExisting)
	}
}

// ValidateConnectorPushDurableMetadataContracts rejects contracts that cannot
// be backed by pack-owned outbound connector_field_mapping rows. Runtime later
// consumes the mapping resolver; this install-time check ensures the durable
// write target already has the same app-pack owner and tuple.
func ValidateConnectorPushDurableMetadataContracts(contracts []contract.ConnectorPushDurableMetadataContract, mappings []contract.ConnectorFieldMappingInput) error {
	return ValidateConnectorPushDurableMetadataContractsWithOptions(contracts, mappings, contract.ConnectorPushContractValidationOptions{})
}

func ValidateConnectorPushDurableMetadataContractsWithOptions(contracts []contract.ConnectorPushDurableMetadataContract, mappings []contract.ConnectorFieldMappingInput, opts contract.ConnectorPushContractValidationOptions) error {
	seen := map[string]int{}
	associationTargets := map[string]connectorPushAssociationTargetEntry{}
	for i, c := range contracts {
		if err := addConnectorPushAssociationTargetEntry(associationTargets, i, c, c.ActionSlotID); err != nil {
			return err
		}
		for _, previousActionSlotID := range c.PreviousActionSlotIDs {
			if err := addConnectorPushAssociationTargetEntry(associationTargets, i, c, previousActionSlotID); err != nil {
				return err
			}
		}
	}
	// seenRemoteIDLink enforces that a given (ConnectorOwnerKey, SourceObject,
	// RemoteIDField) maps to at most ONE durable link identity within this pack. The
	// evidence ActiveKey is (owner, object, record, RemoteIDField); the durable LinkKey
	// additionally includes ActionSlotID, ActionType, and DestinationSlotID. So two
	// contracts that SHARE (owner, object, RemoteIDField) but declare a DIFFERENT
	// durable identity (any of ActionSlotID/ActionType/DestinationSlotID) would resolve
	// the SAME evidence row yet finalize DIFFERENT links — one link could adopt
	// another's authored evidence and PATCH the wrong remote object. Distinct
	// connector-push link identities on one source object must therefore use distinct
	// remote-id fields. (Distinct fields are SAFE — they key distinct evidence buckets;
	// the same field on a DIFFERENT object is also safe.)
	type remoteIDLinkEntry struct {
		index        int
		linkIdentity string
		actionSlot   string
		destination  string
	}
	seenRemoteIDLink := map[string]remoteIDLinkEntry{}
	for i := range contracts {
		c := &contracts[i]
		key := strings.ToLower(strings.Join([]string{
			strings.TrimSpace(c.ConnectorOwnerKey),
			strings.TrimSpace(c.ActionSlotID),
			strings.TrimSpace(c.ActionType),
			strings.TrimSpace(c.SourceObject),
			strings.TrimSpace(c.DestinationSlotID),
		}, "\x00"))
		if prev, ok := seen[key]; ok {
			return fmt.Errorf("connector_push_contract %d duplicates durable identity declared by contract %d", i, prev)
		}
		seen[key] = i

		remoteIDField := strings.TrimSpace(c.RemoteIDField)
		if remoteIDField != "" {
			ownerObjectFieldKey := strings.ToLower(strings.Join([]string{
				strings.TrimSpace(c.ConnectorOwnerKey),
				strings.TrimSpace(c.SourceObject),
				remoteIDField,
			}, "\x00"))
			actionSlot := strings.TrimSpace(c.ActionSlotID)
			destination := strings.TrimSpace(c.DestinationSlotID)
			linkIdentity := strings.ToLower(strings.Join([]string{
				actionSlot,
				strings.TrimSpace(c.ActionType),
				destination,
			}, "\x00"))
			if prev, ok := seenRemoteIDLink[ownerObjectFieldKey]; ok && prev.linkIdentity != linkIdentity {
				return fmt.Errorf("connector_push_contract %d shares RemoteIDField %q for (%s,%s) with contract %d but maps a different durable link identity (actionSlot=%q dest=%q vs actionSlot=%q dest=%q); a remote-id field must map to exactly one connector-push link identity per source object",
					i, remoteIDField,
					strings.TrimSpace(c.ConnectorOwnerKey), strings.TrimSpace(c.SourceObject),
					prev.index, prev.actionSlot, prev.destination, actionSlot, destination)
			}
			seenRemoteIDLink[ownerObjectFieldKey] = remoteIDLinkEntry{index: i, linkIdentity: linkIdentity, actionSlot: actionSlot, destination: destination}
		}

		if !connectorPushContractHasOwnedOutboundMapping(*c, mappings) {
			return fmt.Errorf(
				"connector_push_contract %s/%s requires an outbound connector_field_mapping owned by %s for %s→%s(%s)",
				strings.TrimSpace(c.FlowDeveloperName),
				strings.TrimSpace(c.FlowActionName),
				strings.TrimSpace(c.ConnectorOwnerKey),
				strings.TrimSpace(c.SourceObject),
				strings.TrimSpace(c.TargetObject),
				constants.ActionTypePushToConnector,
			)
		}
		for j, assoc := range c.Associations {
			if err := validateConnectorPushAssociationContract(j, assoc); err != nil {
				return err
			}
			if err := validateConnectorPushAssociationSourceLookupField(i, j, *c, assoc, opts); err != nil {
				return err
			}
			if err := validateConnectorPushAssociationPayloadFieldOwnership(i, j, *c, assoc, mappings); err != nil {
				return err
			}
			targetResolution, err := validateConnectorPushAssociationTargetContract(i, j, *c, assoc, associationTargets)
			if err != nil {
				return err
			}
			c.Associations[j].TargetPreviousActionSlotIDs = targetResolution.previousActionSlotIDs
			c.Associations[j].TargetProfileOwnerPack = targetResolution.profileOwnerPack
			c.Associations[j].TargetProfileName = targetResolution.profileName
		}
	}
	return nil
}

type connectorPushAssociationTargetResolution struct {
	previousActionSlotIDs []string
	profileOwnerPack      string
	profileName           string
}

type connectorPushAssociationTargetEntry struct {
	contract contract.ConnectorPushDurableMetadataContract
	count    int
}

func addConnectorPushAssociationTargetEntry(targets map[string]connectorPushAssociationTargetEntry, index int, c contract.ConnectorPushDurableMetadataContract, actionSlotID string) error {
	actionSlotID = strings.TrimSpace(actionSlotID)
	if actionSlotID == "" {
		return nil
	}
	key := connectorPushAssociationTargetKey(
		c.ConnectorOwnerKey,
		c.SourceObject,
		actionSlotID,
		c.DestinationSlotID,
		c.TargetObject,
		c.RemoteIDField,
	)
	entry := targets[key]
	if entry.count == 0 {
		entry.contract = c
	}
	entry.count++
	targets[key] = entry
	return nil
}

func validateConnectorPushAssociationTargetContract(i, j int, c contract.ConnectorPushDurableMetadataContract, assoc contract.ConnectorPushAssociationContract, targets map[string]connectorPushAssociationTargetEntry) (connectorPushAssociationTargetResolution, error) {
	key := connectorPushAssociationTargetKey(
		c.ConnectorOwnerKey,
		assoc.SourceObject,
		assoc.TargetActionSlotID,
		assoc.TargetDestinationSlotID,
		assoc.TargetObject,
		assoc.RemoteIDField,
	)
	if target := targets[key]; target.count == 1 {
		if strings.TrimSpace(target.contract.ProfileOwnerPack) == strings.TrimSpace(c.ProfileOwnerPack) &&
			strings.TrimSpace(target.contract.ProfileName) == strings.TrimSpace(c.ProfileName) {
			return connectorPushAssociationTargetResolution{
				previousActionSlotIDs: connectorPushAssociationTargetFallbackActionSlotIDs(target.contract, assoc.TargetActionSlotID),
				profileOwnerPack:      strings.TrimSpace(target.contract.ProfileOwnerPack),
				profileName:           strings.TrimSpace(target.contract.ProfileName),
			}, nil
		}
		return connectorPushAssociationTargetResolution{}, fmt.Errorf(
			"connector_push_contract %d associations[%d] target profile must match source contract profile for sourceObject=%q actionSlot=%q destinationSlot=%q targetObject=%q remoteIdField=%q",
			i,
			j,
			strings.TrimSpace(assoc.SourceObject),
			strings.TrimSpace(assoc.TargetActionSlotID),
			strings.TrimSpace(assoc.TargetDestinationSlotID),
			strings.TrimSpace(assoc.TargetObject),
			strings.TrimSpace(assoc.RemoteIDField),
		)
	}
	return connectorPushAssociationTargetResolution{}, fmt.Errorf(
		"connector_push_contract %d associations[%d] target must resolve to exactly one connector_push_contract owned by %s for sourceObject=%q actionSlot=%q destinationSlot=%q targetObject=%q remoteIdField=%q",
		i,
		j,
		strings.TrimSpace(c.ConnectorOwnerKey),
		strings.TrimSpace(assoc.SourceObject),
		strings.TrimSpace(assoc.TargetActionSlotID),
		strings.TrimSpace(assoc.TargetDestinationSlotID),
		strings.TrimSpace(assoc.TargetObject),
		strings.TrimSpace(assoc.RemoteIDField),
	)
}

func connectorPushAssociationTargetFallbackActionSlotIDs(c contract.ConnectorPushDurableMetadataContract, authored string) []string {
	authored = strings.TrimSpace(authored)
	seen := map[string]struct{}{}
	if authored != "" {
		seen[strings.ToLower(authored)] = struct{}{}
	}
	values := append([]string{c.ActionSlotID}, c.PreviousActionSlotIDs...)
	out := make([]string, 0, len(values))
	for _, value := range values {
		value = strings.TrimSpace(value)
		if value == "" {
			continue
		}
		key := strings.ToLower(value)
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		out = append(out, value)
	}
	return out
}

func validateConnectorPushAssociationSourceLookupField(i, j int, c contract.ConnectorPushDurableMetadataContract, assoc contract.ConnectorPushAssociationContract, opts contract.ConnectorPushContractValidationOptions) error {
	if assoc.Junction != nil {
		return validateConnectorPushAssociationJunctionLookupFields(i, j, c, assoc, opts)
	}
	if len(opts.ObjectFields) == 0 {
		if opts.RequireSourceLookupFieldSchema {
			return fmt.Errorf("connector_push_contract %d associations[%d].sourceLookupField schema is required to validate %s.%s", i, j, strings.TrimSpace(c.SourceObject), strings.TrimSpace(assoc.SourceLookupField))
		}
		return nil
	}
	sourceObject := strings.TrimSpace(c.SourceObject)
	sourceLookupField := strings.TrimSpace(assoc.SourceLookupField)
	if sourceObject == "" || sourceLookupField == "" {
		return nil
	}
	fields, ok := opts.ObjectFields[strings.ToLower(sourceObject)]
	if !ok {
		return fmt.Errorf("connector_push_contract %d associations[%d].sourceLookupField %s.%s cannot be validated because sourceObject metadata is missing", i, j, sourceObject, sourceLookupField)
	}
	var field *contract.ConnectorPushObjectFieldMetadata
	for idx := range fields {
		if strings.TrimSpace(fields[idx].APIName) == sourceLookupField {
			field = &fields[idx]
			break
		}
	}
	if field == nil {
		return fmt.Errorf("connector_push_contract %d associations[%d].sourceLookupField %s.%s is not declared on sourceObject", i, j, sourceObject, sourceLookupField)
	}
	fieldType := strings.TrimSpace(field.Type)
	if !strings.EqualFold(fieldType, string(constants.FieldTypeReference)) && !strings.EqualFold(fieldType, string(constants.FieldTypeMasterDetail)) {
		return fmt.Errorf("connector_push_contract %d associations[%d].sourceLookupField %s.%s has Type=%q, want reference/masterdetail", i, j, sourceObject, sourceLookupField, fieldType)
	}
	targetObject := strings.TrimSpace(assoc.SourceObject)
	for _, referenceTo := range field.ReferenceTo {
		if strings.TrimSpace(referenceTo) == targetObject {
			return nil
		}
	}
	return fmt.Errorf("connector_push_contract %d associations[%d].sourceLookupField %s.%s ReferenceTo=%v does not include sourceObject=%q", i, j, sourceObject, sourceLookupField, field.ReferenceTo, targetObject)
}

func validateConnectorPushAssociationJunctionLookupFields(i, j int, c contract.ConnectorPushDurableMetadataContract, assoc contract.ConnectorPushAssociationContract, opts contract.ConnectorPushContractValidationOptions) error {
	junction := assoc.Junction
	if junction == nil {
		return nil
	}
	if len(opts.ObjectFields) == 0 {
		if opts.RequireSourceLookupFieldSchema {
			return fmt.Errorf("connector_push_contract %d associations[%d].junction schema is required to validate %s", i, j, strings.TrimSpace(junction.Object))
		}
		return nil
	}
	junctionObject := strings.TrimSpace(junction.Object)
	fields, ok := opts.ObjectFields[strings.ToLower(junctionObject)]
	if !ok {
		return fmt.Errorf("connector_push_contract %d associations[%d].junction object %s metadata is missing", i, j, junctionObject)
	}
	sourceField, err := connectorPushLookupFieldMetadata(fields, junction.SourceLookupField)
	if err != nil {
		return fmt.Errorf("connector_push_contract %d associations[%d].junction.sourceLookupField %s.%s %w", i, j, junctionObject, strings.TrimSpace(junction.SourceLookupField), err)
	}
	if err := validateConnectorPushLookupReference(sourceField, strings.TrimSpace(c.SourceObject)); err != nil {
		return fmt.Errorf("connector_push_contract %d associations[%d].junction.sourceLookupField %s.%s %w", i, j, junctionObject, strings.TrimSpace(junction.SourceLookupField), err)
	}
	targetField, err := connectorPushLookupFieldMetadata(fields, junction.TargetLookupField)
	if err != nil {
		return fmt.Errorf("connector_push_contract %d associations[%d].junction.targetLookupField %s.%s %w", i, j, junctionObject, strings.TrimSpace(junction.TargetLookupField), err)
	}
	if err := validateConnectorPushLookupReference(targetField, strings.TrimSpace(assoc.SourceObject)); err != nil {
		return fmt.Errorf("connector_push_contract %d associations[%d].junction.targetLookupField %s.%s %w", i, j, junctionObject, strings.TrimSpace(junction.TargetLookupField), err)
	}
	for k, filter := range junction.Filters {
		if _, err := connectorPushFieldMetadata(fields, filter.Field); err != nil {
			return fmt.Errorf("connector_push_contract %d associations[%d].junction.filters[%d].field %s.%s %w", i, j, k, junctionObject, strings.TrimSpace(filter.Field), err)
		}
	}
	return nil
}

func connectorPushLookupFieldMetadata(fields []contract.ConnectorPushObjectFieldMetadata, apiName string) (*contract.ConnectorPushObjectFieldMetadata, error) {
	field, err := connectorPushFieldMetadata(fields, apiName)
	if err != nil {
		return nil, err
	}
	fieldType := strings.TrimSpace(field.Type)
	if !strings.EqualFold(fieldType, string(constants.FieldTypeReference)) && !strings.EqualFold(fieldType, string(constants.FieldTypeMasterDetail)) {
		return nil, fmt.Errorf("has Type=%q, want reference/masterdetail", fieldType)
	}
	return field, nil
}

func connectorPushFieldMetadata(fields []contract.ConnectorPushObjectFieldMetadata, apiName string) (*contract.ConnectorPushObjectFieldMetadata, error) {
	apiName = strings.TrimSpace(apiName)
	for idx := range fields {
		if strings.TrimSpace(fields[idx].APIName) == apiName {
			return &fields[idx], nil
		}
	}
	return nil, fmt.Errorf("is not declared")
}

func validateConnectorPushLookupReference(field *contract.ConnectorPushObjectFieldMetadata, targetObject string) error {
	if field == nil {
		return fmt.Errorf("is not declared")
	}
	for _, referenceTo := range field.ReferenceTo {
		if strings.TrimSpace(referenceTo) == targetObject {
			return nil
		}
	}
	return fmt.Errorf("ReferenceTo=%v does not include sourceObject=%q", field.ReferenceTo, targetObject)
}

func validateConnectorPushAssociationPayloadFieldOwnership(i, j int, c contract.ConnectorPushDurableMetadataContract, assoc contract.ConnectorPushAssociationContract, mappings []contract.ConnectorFieldMappingInput) error {
	payloadField := strings.TrimSpace(assoc.PayloadField)
	if payloadField == "" {
		return nil
	}
	if err := validateConnectorPushSimplePayloadField(fmt.Sprintf("associations[%d].payloadField", j), payloadField); err != nil {
		return err
	}
	if bodyRoot := strings.TrimSpace(c.BodyRoot); bodyRoot != "" && payloadField == bodyRoot {
		return fmt.Errorf("connector_push_contract %d associations[%d].payloadField %q conflicts with bodyRoot", i, j, payloadField)
	}
	for _, row := range mappings {
		if !connectorPushContractOwnsOutboundMapping(c, row) {
			continue
		}
		if payloadField == strings.TrimSpace(row.TargetField) {
			return fmt.Errorf("connector_push_contract %d associations[%d].payloadField %q conflicts with outbound connector_field_mapping targetField", i, j, payloadField)
		}
	}
	return nil
}

func connectorPushAssociationTargetKey(owner, sourceObject, actionSlotID, destinationSlotID, targetObject, remoteIDField string) string {
	return strings.Join([]string{
		strings.TrimSpace(owner),
		strings.TrimSpace(sourceObject),
		strings.TrimSpace(actionSlotID),
		strings.TrimSpace(destinationSlotID),
		strings.TrimSpace(targetObject),
		strings.TrimSpace(remoteIDField),
	}, "\x00")
}

func connectorPushContractHasOwnedOutboundMapping(c contract.ConnectorPushDurableMetadataContract, mappings []contract.ConnectorFieldMappingInput) bool {
	for _, row := range mappings {
		if connectorPushContractOwnsOutboundMapping(c, row) {
			return true
		}
	}
	return false
}

func connectorPushContractOwnsOutboundMapping(c contract.ConnectorPushDurableMetadataContract, row contract.ConnectorFieldMappingInput) bool {
	if strings.TrimSpace(row.Source) != strings.TrimSpace(c.ConnectorOwnerKey) {
		return false
	}
	if strings.TrimSpace(row.SourceObject) != strings.TrimSpace(c.SourceObject) {
		return false
	}
	if strings.TrimSpace(row.TargetObject) != strings.TrimSpace(c.TargetObject) {
		return false
	}
	if strings.TrimSpace(row.ActionType) != constants.ActionTypePushToConnector {
		return false
	}
	direction := strings.TrimSpace(row.Direction)
	return direction == contract.DirectionOutbound || direction == contract.DirectionBidirectional
}

func parseConnectorPushContractFields(raw []byte) (map[string]json.RawMessage, error) {
	dec := json.NewDecoder(bytes.NewReader(raw))
	dec.DisallowUnknownFields()
	var fields map[string]json.RawMessage
	if err := dec.Decode(&fields); err != nil {
		return nil, fmt.Errorf("connector_push_contract: failed to parse payload: %w", err)
	}
	if fields == nil {
		return nil, fmt.Errorf("connector_push_contract: payload must be a JSON object")
	}
	if tok, err := dec.Token(); err == nil {
		return nil, fmt.Errorf("connector_push_contract: unexpected trailing token %v in payload", tok)
	} else if err != io.EOF {
		return nil, fmt.Errorf("connector_push_contract: invalid trailing JSON in payload: %w", err)
	}
	return fields, nil
}

func connectorPushContractLiteralString(fields map[string]json.RawMessage, name string, required bool) (string, error) {
	raw, ok := fields[name]
	if !ok {
		if required {
			return "", fmt.Errorf("connector_push_contract: %s must not be blank", name)
		}
		return "", nil
	}
	var literal string
	if err := json.Unmarshal(raw, &literal); err != nil {
		var shaped map[string]json.RawMessage
		if mapErr := json.Unmarshal(raw, &shaped); mapErr == nil {
			if _, hasElementReference := shaped["elementReference"]; hasElementReference {
				return "", fmt.Errorf("connector_push_contract: %s must be a pack-owned literal string; elementReference is not allowed", name)
			}
		}
		return "", fmt.Errorf("connector_push_contract: %s must be a pack-owned literal string", name)
	}
	literal = strings.TrimSpace(literal)
	if literal == "" {
		if required {
			return "", fmt.Errorf("connector_push_contract: %s must not be blank", name)
		}
		return "", nil
	}
	if connectorPushContractLooksDynamic(literal) {
		return "", fmt.Errorf("connector_push_contract: %s must be a pack-owned literal string; Flow or record references are not allowed", name)
	}
	return literal, nil
}

func connectorPushContractLiteralStringList(fields map[string]json.RawMessage, name string) ([]string, error) {
	raw, ok := fields[name]
	if !ok {
		return nil, nil
	}
	var literals []string
	if err := json.Unmarshal(raw, &literals); err != nil {
		var shaped map[string]json.RawMessage
		if mapErr := json.Unmarshal(raw, &shaped); mapErr == nil {
			if _, hasElementReference := shaped["elementReference"]; hasElementReference {
				return nil, fmt.Errorf("connector_push_contract: %s must be a pack-owned literal string list; elementReference is not allowed", name)
			}
		}
		return nil, fmt.Errorf("connector_push_contract: %s must be a pack-owned literal string list", name)
	}
	out := make([]string, 0, len(literals))
	for _, literal := range literals {
		literal = strings.TrimSpace(literal)
		if literal == "" {
			return nil, fmt.Errorf("connector_push_contract: %s must not contain blank values", name)
		}
		if connectorPushContractLooksDynamic(literal) {
			return nil, fmt.Errorf("connector_push_contract: %s must be a pack-owned literal string list; Flow or record references are not allowed", name)
		}
		out = append(out, literal)
	}
	return out, nil
}

func connectorPushCreateConflictResolution(fields map[string]json.RawMessage) (*contract.ConnectorPushCreateConflictResolutionContract, error) {
	raw, ok := fields["createConflictResolution"]
	if !ok {
		return nil, nil
	}
	dec := json.NewDecoder(bytes.NewReader(raw))
	dec.DisallowUnknownFields()
	var item struct {
		StatusCodes     []int             `json:"statusCodes"`
		DiagnosticMatch map[string]string `json:"diagnosticMatch"`
		Identity        struct {
			SourceField     string `json:"sourceField"`
			TargetProperty  string `json:"targetProperty"`
			RequestBodyPath string `json:"requestBodyPath"`
			Normalization   string `json:"normalization"`
		} `json:"identity"`
		Resolve struct {
			Method            string `json:"method"`
			PathTemplate      string `json:"pathTemplate"`
			ResponseIDPath    string `json:"responseIdPath"`
			RequireExactlyOne bool   `json:"requireExactlyOne"`
		} `json:"resolve"`
		OnResolved          string `json:"onResolved"`
		NoOldSnapshotPolicy string `json:"noOldSnapshotPolicy"`
	}
	if err := dec.Decode(&item); err != nil {
		return nil, fmt.Errorf("connector_push_contract: createConflictResolution must be an object: %w", err)
	}
	if tok, err := dec.Token(); err == nil {
		return nil, fmt.Errorf("connector_push_contract: createConflictResolution has unexpected trailing token %v", tok)
	} else if err != io.EOF {
		return nil, fmt.Errorf("connector_push_contract: createConflictResolution has invalid trailing JSON: %w", err)
	}

	if len(item.StatusCodes) == 0 {
		return nil, fmt.Errorf("connector_push_contract: createConflictResolution.statusCodes must not be empty")
	}
	seenStatus := map[int]struct{}{}
	for _, status := range item.StatusCodes {
		if status < 400 || status > 599 {
			return nil, fmt.Errorf("connector_push_contract: createConflictResolution.statusCodes contains non-error status %d", status)
		}
		if _, ok := seenStatus[status]; ok {
			return nil, fmt.Errorf("connector_push_contract: createConflictResolution.statusCodes contains duplicate %d", status)
		}
		seenStatus[status] = struct{}{}
	}
	if len(item.DiagnosticMatch) == 0 {
		return nil, fmt.Errorf("connector_push_contract: createConflictResolution.diagnosticMatch must not be empty")
	}
	diagnosticMatch := make(map[string]string, len(item.DiagnosticMatch))
	for path, value := range item.DiagnosticMatch {
		path = strings.TrimSpace(path)
		value = strings.TrimSpace(value)
		if path == "" || value == "" {
			return nil, fmt.Errorf("connector_push_contract: createConflictResolution.diagnosticMatch must not contain blank path or value")
		}
		if connectorPushContractLooksDynamic(path) || connectorPushContractLooksDynamic(value) {
			return nil, fmt.Errorf("connector_push_contract: createConflictResolution.diagnosticMatch must be pack-owned literal metadata")
		}
		if err := validateConnectorPushDottedPath("createConflictResolution.diagnosticMatch", path); err != nil {
			return nil, err
		}
		diagnosticMatch[path] = value
	}

	identity := contract.ConnectorPushConflictIdentityContract{
		SourceField:     strings.TrimSpace(item.Identity.SourceField),
		TargetProperty:  strings.TrimSpace(item.Identity.TargetProperty),
		RequestBodyPath: strings.TrimSpace(item.Identity.RequestBodyPath),
		Normalization:   strings.TrimSpace(item.Identity.Normalization),
	}
	if identity.RequestBodyPath == "" {
		return nil, fmt.Errorf("connector_push_contract: createConflictResolution.identity.requestBodyPath must not be blank")
	}
	for name, value := range map[string]string{
		"createConflictResolution.identity.sourceField":     identity.SourceField,
		"createConflictResolution.identity.targetProperty":  identity.TargetProperty,
		"createConflictResolution.identity.requestBodyPath": identity.RequestBodyPath,
		"createConflictResolution.identity.normalization":   identity.Normalization,
	} {
		if connectorPushContractLooksDynamic(value) {
			return nil, fmt.Errorf("connector_push_contract: %s must be pack-owned literal metadata", name)
		}
	}
	if err := validateConnectorPushDottedPath("createConflictResolution.identity.requestBodyPath", identity.RequestBodyPath); err != nil {
		return nil, err
	}
	if identity.Normalization != "" && identity.Normalization != contract.ConnectorPushConflictNormalizationEmail {
		return nil, fmt.Errorf("connector_push_contract: createConflictResolution.identity.normalization must be blank or %q", contract.ConnectorPushConflictNormalizationEmail)
	}

	resolve := contract.ConnectorPushConflictResolverContract{
		Method:            strings.ToUpper(strings.TrimSpace(item.Resolve.Method)),
		PathTemplate:      strings.TrimSpace(item.Resolve.PathTemplate),
		ResponseIDPath:    strings.TrimSpace(item.Resolve.ResponseIDPath),
		RequireExactlyOne: item.Resolve.RequireExactlyOne,
	}
	if resolve.Method != "GET" {
		return nil, fmt.Errorf("connector_push_contract: createConflictResolution.resolve.method must be GET")
	}
	if resolve.PathTemplate == "" || !strings.Contains(resolve.PathTemplate, "{identity}") {
		return nil, fmt.Errorf("connector_push_contract: createConflictResolution.resolve.pathTemplate must contain {identity}")
	}
	if connectorPushContractLooksDynamic(resolve.PathTemplate) {
		return nil, fmt.Errorf("connector_push_contract: createConflictResolution.resolve.pathTemplate must be pack-owned literal metadata")
	}
	if !strings.HasPrefix(resolve.PathTemplate, "/") {
		return nil, fmt.Errorf("connector_push_contract: createConflictResolution.resolve.pathTemplate must start with /")
	}
	if resolve.ResponseIDPath == "" {
		return nil, fmt.Errorf("connector_push_contract: createConflictResolution.resolve.responseIdPath must not be blank")
	}
	if err := validateConnectorPushDottedPath("createConflictResolution.resolve.responseIdPath", resolve.ResponseIDPath); err != nil {
		return nil, err
	}
	if !resolve.RequireExactlyOne {
		return nil, fmt.Errorf("connector_push_contract: createConflictResolution.resolve.requireExactlyOne must be true")
	}
	onResolved := strings.TrimSpace(item.OnResolved)
	if onResolved != contract.ConnectorPushConflictOnResolvedAdoptLinkThenPatch {
		return nil, fmt.Errorf("connector_push_contract: createConflictResolution.onResolved must be %q", contract.ConnectorPushConflictOnResolvedAdoptLinkThenPatch)
	}
	noOldSnapshotPolicy := strings.TrimSpace(item.NoOldSnapshotPolicy)
	if noOldSnapshotPolicy != "" &&
		noOldSnapshotPolicy != contract.ConnectorPushConflictNoOldSnapshotAdoptLinkOnly &&
		noOldSnapshotPolicy != contract.ConnectorPushConflictNoOldSnapshotAdoptLinkThenPatchAuthorizedFrozenCreatePayload {
		return nil, fmt.Errorf("connector_push_contract: createConflictResolution.noOldSnapshotPolicy must be blank, %q, or %q", contract.ConnectorPushConflictNoOldSnapshotAdoptLinkOnly, contract.ConnectorPushConflictNoOldSnapshotAdoptLinkThenPatchAuthorizedFrozenCreatePayload)
	}

	return &contract.ConnectorPushCreateConflictResolutionContract{
		StatusCodes:         append([]int(nil), item.StatusCodes...),
		DiagnosticMatch:     diagnosticMatch,
		Identity:            identity,
		Resolve:             resolve,
		OnResolved:          onResolved,
		NoOldSnapshotPolicy: noOldSnapshotPolicy,
	}, nil
}

func validateConnectorPushDottedPath(name, path string) error {
	path = strings.Trim(strings.TrimSpace(path), ".")
	if path == "" {
		return fmt.Errorf("connector_push_contract: %s must not be blank", name)
	}
	for _, part := range strings.Split(path, ".") {
		if part == "" {
			return fmt.Errorf("connector_push_contract: %s contains an empty segment", name)
		}
		if connectorPushContractLooksDynamic(part) {
			return fmt.Errorf("connector_push_contract: %s must be pack-owned literal metadata", name)
		}
		if err := validateConnectorPushSimplePayloadField(name, part); err != nil {
			return err
		}
	}
	return nil
}

func connectorPushAssociationContracts(fields map[string]json.RawMessage) ([]contract.ConnectorPushAssociationContract, error) {
	raw, ok := fields["associations"]
	if !ok {
		return nil, nil
	}
	dec := json.NewDecoder(bytes.NewReader(raw))
	dec.DisallowUnknownFields()
	var items []struct {
		SourceLookupField string `json:"sourceLookupField"`
		Junction          *struct {
			Object            string `json:"object"`
			SourceLookupField string `json:"sourceLookupField"`
			TargetLookupField string `json:"targetLookupField"`
			Filters           []struct {
				Field  string          `json:"field"`
				Equals json.RawMessage `json:"equals"`
			} `json:"filters"`
			Limit int `json:"limit"`
		} `json:"junction"`
		SourceObject            string          `json:"sourceObject"`
		RemoteIDField           string          `json:"remoteIdField"`
		TargetActionSlotID      string          `json:"targetActionSlotId"`
		TargetDestinationSlotID string          `json:"targetDestinationSlotId"`
		TargetObject            string          `json:"targetObject"`
		PayloadField            string          `json:"payloadField"`
		PayloadTemplate         json.RawMessage `json:"payloadTemplate"`
		PayloadMergeMode        string          `json:"payloadMergeMode"`
		InlinePhases            []string        `json:"inlinePhases"`
		AssociationPath         string          `json:"associationPath"`
		AssociationMethod       string          `json:"associationMethod"`
		CalloutBodyTemplate     json.RawMessage `json:"calloutBodyTemplate"`
		CalloutPhases           []string        `json:"calloutPhases"`
	}
	if err := dec.Decode(&items); err != nil {
		return nil, fmt.Errorf("connector_push_contract: associations must be an array of objects: %w", err)
	}
	if tok, err := dec.Token(); err == nil {
		return nil, fmt.Errorf("connector_push_contract: associations has unexpected trailing token %v", tok)
	} else if err != io.EOF {
		return nil, fmt.Errorf("connector_push_contract: associations has invalid trailing JSON: %w", err)
	}
	out := make([]contract.ConnectorPushAssociationContract, 0, len(items))
	for i, item := range items {
		junction, err := connectorPushAssociationJunctionContract(i, item.Junction)
		if err != nil {
			return nil, err
		}
		payloadTemplate, err := connectorPushAssociationTemplate(item.PayloadTemplate)
		if err != nil {
			return nil, fmt.Errorf("connector_push_contract: associations[%d].payloadTemplate: %w", i, err)
		}
		calloutBodyTemplate, err := connectorPushAssociationTemplate(item.CalloutBodyTemplate)
		if err != nil {
			return nil, fmt.Errorf("connector_push_contract: associations[%d].calloutBodyTemplate: %w", i, err)
		}
		assoc := contract.ConnectorPushAssociationContract{
			SourceLookupField:       strings.TrimSpace(item.SourceLookupField),
			Junction:                junction,
			SourceObject:            strings.TrimSpace(item.SourceObject),
			RemoteIDField:           strings.TrimSpace(item.RemoteIDField),
			TargetActionSlotID:      strings.TrimSpace(item.TargetActionSlotID),
			TargetDestinationSlotID: strings.TrimSpace(item.TargetDestinationSlotID),
			TargetObject:            strings.TrimSpace(item.TargetObject),
			PayloadField:            strings.TrimSpace(item.PayloadField),
			PayloadTemplate:         payloadTemplate,
			PayloadMergeMode:        strings.TrimSpace(item.PayloadMergeMode),
			InlinePhases:            connectorPushAssociationPhaseList(item.InlinePhases),
			AssociationPath:         strings.TrimSpace(item.AssociationPath),
			AssociationMethod:       strings.TrimSpace(item.AssociationMethod),
			CalloutBodyTemplate:     calloutBodyTemplate,
			CalloutPhases:           connectorPushAssociationPhaseList(item.CalloutPhases),
		}
		if err := validateConnectorPushAssociationContract(i, assoc); err != nil {
			return nil, err
		}
		out = append(out, assoc)
	}
	return out, nil
}

func connectorPushAssociationJunctionContract(i int, item *struct {
	Object            string `json:"object"`
	SourceLookupField string `json:"sourceLookupField"`
	TargetLookupField string `json:"targetLookupField"`
	Filters           []struct {
		Field  string          `json:"field"`
		Equals json.RawMessage `json:"equals"`
	} `json:"filters"`
	Limit int `json:"limit"`
}) (*contract.ConnectorPushAssociationJunctionContract, error) {
	if item == nil {
		return nil, nil
	}
	filters := make([]contract.ConnectorPushAssociationJunctionFilter, 0, len(item.Filters))
	for j, filter := range item.Filters {
		equals, err := connectorPushAssociationJunctionFilterEquals(filter.Equals)
		if err != nil {
			return nil, fmt.Errorf("connector_push_contract: associations[%d].junction.filters[%d].equals: %w", i, j, err)
		}
		filters = append(filters, contract.ConnectorPushAssociationJunctionFilter{
			Field:  strings.TrimSpace(filter.Field),
			Equals: equals,
		})
	}
	limit := item.Limit
	if limit == 0 {
		limit = 1
	}
	junction := &contract.ConnectorPushAssociationJunctionContract{
		Object:            strings.TrimSpace(item.Object),
		SourceLookupField: strings.TrimSpace(item.SourceLookupField),
		TargetLookupField: strings.TrimSpace(item.TargetLookupField),
		Filters:           filters,
		Limit:             limit,
	}
	if err := validateConnectorPushAssociationJunctionContract(fmt.Sprintf("connector_push_contract: associations[%d].junction", i), junction); err != nil {
		return nil, err
	}
	return junction, nil
}

func connectorPushAssociationJunctionFilterEquals(raw json.RawMessage) (interface{}, error) {
	if len(raw) == 0 {
		return nil, fmt.Errorf("must be present")
	}
	dec := json.NewDecoder(bytes.NewReader(raw))
	dec.UseNumber()
	var value interface{}
	if err := dec.Decode(&value); err != nil {
		return nil, err
	}
	switch v := value.(type) {
	case string:
		if connectorPushContractLooksDynamic(v) {
			return nil, fmt.Errorf("must be pack-owned literal metadata")
		}
		return v, nil
	case bool, json.Number:
		return v, nil
	case nil:
		return nil, fmt.Errorf("must not be null")
	case []interface{}, map[string]interface{}:
		return nil, fmt.Errorf("must be a scalar pack-owned literal")
	default:
		return nil, fmt.Errorf("has unsupported literal type %T", value)
	}
}

func connectorPushAssociationTemplate(raw json.RawMessage) (interface{}, error) {
	if len(raw) == 0 || strings.TrimSpace(string(raw)) == "null" {
		return nil, nil
	}
	var out interface{}
	if err := json.Unmarshal(raw, &out); err != nil {
		return nil, err
	}
	return out, nil
}

func connectorPushAssociationPhaseList(values []string) []string {
	out := make([]string, 0, len(values))
	for _, value := range values {
		if value = strings.TrimSpace(value); value != "" {
			out = append(out, value)
		}
	}
	return out
}

func validateConnectorPushAssociationContract(i int, assoc contract.ConnectorPushAssociationContract) error {
	prefix := fmt.Sprintf("connector_push_contract: associations[%d]", i)
	required := map[string]string{
		"sourceObject":            assoc.SourceObject,
		"remoteIdField":           assoc.RemoteIDField,
		"targetActionSlotId":      assoc.TargetActionSlotID,
		"targetDestinationSlotId": assoc.TargetDestinationSlotID,
		"targetObject":            assoc.TargetObject,
	}
	if assoc.Junction == nil {
		required["sourceLookupField"] = assoc.SourceLookupField
	} else {
		if strings.TrimSpace(assoc.SourceLookupField) != "" {
			return fmt.Errorf("%s.sourceLookupField must be blank when junction is declared", prefix)
		}
		if err := validateConnectorPushAssociationJunctionContract(prefix+".junction", assoc.Junction); err != nil {
			return err
		}
	}
	for field, value := range required {
		if strings.TrimSpace(value) == "" {
			return fmt.Errorf("%s.%s must not be blank", prefix, field)
		}
		if connectorPushContractLooksDynamic(value) {
			return fmt.Errorf("%s.%s must be pack-owned literal metadata", prefix, field)
		}
	}
	for field, value := range map[string]string{
		"payloadField":      assoc.PayloadField,
		"payloadMergeMode":  assoc.PayloadMergeMode,
		"associationPath":   assoc.AssociationPath,
		"associationMethod": assoc.AssociationMethod,
	} {
		if strings.TrimSpace(value) != "" && connectorPushContractLooksDynamic(value) {
			return fmt.Errorf("%s.%s must be pack-owned literal metadata", prefix, field)
		}
	}
	if connectorPushAssociationTemplateLooksDynamic(assoc.PayloadTemplate) {
		return fmt.Errorf("%s.payloadTemplate must be pack-owned literal metadata", prefix)
	}
	if connectorPushAssociationTemplateLooksDynamic(assoc.CalloutBodyTemplate) {
		return fmt.Errorf("%s.calloutBodyTemplate must be pack-owned literal metadata", prefix)
	}
	hasInlinePayload := assoc.PayloadTemplate != nil
	hasCallout := strings.TrimSpace(assoc.AssociationPath) != ""
	if !hasInlinePayload {
		if strings.TrimSpace(assoc.PayloadField) != "" {
			return fmt.Errorf("%s.payloadField requires payloadTemplate", prefix)
		}
		if strings.TrimSpace(assoc.PayloadMergeMode) != "" {
			return fmt.Errorf("%s.payloadMergeMode requires payloadTemplate", prefix)
		}
		if len(assoc.InlinePhases) > 0 {
			return fmt.Errorf("%s.inlinePhases requires payloadTemplate", prefix)
		}
	}
	if !hasCallout {
		if strings.TrimSpace(assoc.AssociationMethod) != "" {
			return fmt.Errorf("%s.associationMethod requires associationPath", prefix)
		}
		if len(assoc.CalloutPhases) > 0 {
			return fmt.Errorf("%s.calloutPhases requires associationPath", prefix)
		}
		if assoc.CalloutBodyTemplate != nil {
			return fmt.Errorf("%s.calloutBodyTemplate requires associationPath", prefix)
		}
	}
	if !hasInlinePayload && !hasCallout {
		return fmt.Errorf("%s must declare an inline payload or association callout", prefix)
	}
	if assoc.PayloadTemplate != nil {
		if strings.TrimSpace(assoc.PayloadField) == "" {
			return fmt.Errorf("%s.payloadField must not be blank when payloadTemplate is set", prefix)
		}
		switch strings.TrimSpace(assoc.PayloadMergeMode) {
		case "append", "set":
		default:
			return fmt.Errorf("%s.payloadMergeMode must be one of: append, set", prefix)
		}
		if err := validateConnectorPushAssociationPhases(prefix+".inlinePhases", assoc.InlinePhases); err != nil {
			return err
		}
		if len(assoc.InlinePhases) == 0 {
			return fmt.Errorf("%s.inlinePhases must not be blank when payloadTemplate is set", prefix)
		}
		for _, phase := range assoc.InlinePhases {
			if strings.TrimSpace(phase) == "create" && connectorPushAssociationTemplateReferencesFrom(assoc.PayloadTemplate) {
				return fmt.Errorf("%s.payloadTemplate must not reference {fromObject} or {fromId} for create inline associations", prefix)
			}
		}
	}
	if strings.TrimSpace(assoc.AssociationPath) != "" {
		if strings.TrimSpace(assoc.AssociationMethod) == "" {
			return fmt.Errorf("%s.associationMethod must not be blank when associationPath is set", prefix)
		}
		if !connectorPushAssociationMethodIsDurablyIdempotent(assoc.AssociationMethod) {
			return fmt.Errorf("%s.associationMethod must be PUT until association callouts have a durable sub-attempt lifecycle", prefix)
		}
		if err := validateConnectorPushAssociationPhases(prefix+".calloutPhases", assoc.CalloutPhases); err != nil {
			return err
		}
		if len(assoc.CalloutPhases) == 0 {
			return fmt.Errorf("%s.calloutPhases must not be blank when associationPath is set", prefix)
		}
		for _, phase := range assoc.CalloutPhases {
			if strings.TrimSpace(phase) == "create" {
				return fmt.Errorf("%s.calloutPhases does not support create; create associations must be inline payload metadata", prefix)
			}
		}
	}
	if err := validateConnectorPushContractBusinessKeySegment(prefix+".targetActionSlotId", assoc.TargetActionSlotID); err != nil {
		return err
	}
	if err := validateConnectorPushContractBusinessKeySegment(prefix+".targetDestinationSlotId", assoc.TargetDestinationSlotID); err != nil {
		return err
	}
	return nil
}

func validateConnectorPushAssociationJunctionContract(prefix string, junction *contract.ConnectorPushAssociationJunctionContract) error {
	if junction == nil {
		return nil
	}
	for field, value := range map[string]string{
		"object":            junction.Object,
		"sourceLookupField": junction.SourceLookupField,
		"targetLookupField": junction.TargetLookupField,
	} {
		if strings.TrimSpace(value) == "" {
			return fmt.Errorf("%s.%s must not be blank", prefix, field)
		}
		if connectorPushContractLooksDynamic(value) {
			return fmt.Errorf("%s.%s must be pack-owned literal metadata", prefix, field)
		}
		if err := validateConnectorPushSimplePayloadField(prefix+"."+field, value); err != nil {
			return err
		}
	}
	if junction.Limit != 1 {
		return fmt.Errorf("%s.limit must be 1", prefix)
	}
	for i, filter := range junction.Filters {
		filterPrefix := fmt.Sprintf("%s.filters[%d]", prefix, i)
		if strings.TrimSpace(filter.Field) == "" {
			return fmt.Errorf("%s.field must not be blank", filterPrefix)
		}
		if connectorPushContractLooksDynamic(filter.Field) {
			return fmt.Errorf("%s.field must be pack-owned literal metadata", filterPrefix)
		}
		if err := validateConnectorPushSimplePayloadField(filterPrefix+".field", filter.Field); err != nil {
			return err
		}
		if filter.Equals == nil {
			return fmt.Errorf("%s.equals must not be null", filterPrefix)
		}
		if connectorPushAssociationTemplateLooksDynamic(filter.Equals) {
			return fmt.Errorf("%s.equals must be pack-owned literal metadata", filterPrefix)
		}
	}
	return nil
}

func connectorPushAssociationTemplateLooksDynamic(value interface{}) bool {
	switch v := value.(type) {
	case nil:
		return false
	case string:
		return connectorPushContractLooksDynamic(v)
	case []interface{}:
		for _, item := range v {
			if connectorPushAssociationTemplateLooksDynamic(item) {
				return true
			}
		}
	case map[string]interface{}:
		for key, item := range v {
			if connectorPushAssociationTemplateKeyLooksDynamic(key) || connectorPushAssociationTemplateLooksDynamic(item) {
				return true
			}
		}
	}
	return false
}

func connectorPushAssociationTemplateKeyLooksDynamic(key string) bool {
	if connectorPushContractLooksDynamic(key) {
		return true
	}
	for _, token := range []string{"{fromObject}", "{fromId}", "{toObject}", "{toId}"} {
		if strings.Contains(key, token) {
			return true
		}
	}
	return false
}

func connectorPushAssociationTemplateReferencesFrom(value interface{}) bool {
	switch v := value.(type) {
	case nil:
		return false
	case string:
		return strings.Contains(v, "{fromObject}") || strings.Contains(v, "{fromId}")
	case []interface{}:
		for _, item := range v {
			if connectorPushAssociationTemplateReferencesFrom(item) {
				return true
			}
		}
	case map[string]interface{}:
		for key, item := range v {
			if strings.Contains(key, "{fromObject}") || strings.Contains(key, "{fromId}") || connectorPushAssociationTemplateReferencesFrom(item) {
				return true
			}
		}
	}
	return false
}

func validateConnectorPushAssociationPhases(name string, phases []string) error {
	seen := map[string]struct{}{}
	for _, phase := range phases {
		phase = strings.TrimSpace(phase)
		switch phase {
		case "create", "update":
		default:
			return fmt.Errorf("connector_push_contract: %s contains unsupported phase %q", name, phase)
		}
		if _, ok := seen[phase]; ok {
			return fmt.Errorf("connector_push_contract: %s contains duplicate %q", name, phase)
		}
		seen[phase] = struct{}{}
	}
	return nil
}

func connectorPushAssociationMethodIsDurablyIdempotent(method string) bool {
	return strings.EqualFold(strings.TrimSpace(method), "PUT")
}

func connectorPushContractLooksDynamic(value string) bool {
	trimmed := strings.TrimSpace(value)
	if strings.HasPrefix(trimmed, "$Record") || strings.HasPrefix(trimmed, "$Record__") {
		return true
	}
	if strings.Contains(trimmed, "{!") {
		return true
	}
	return strings.Contains(trimmed, "$Record.")
}

func validateConnectorPushContractBodyRoot(value string) error {
	return validateConnectorPushSimplePayloadField("bodyRoot", value)
}

func validateConnectorPushSimplePayloadField(name, value string) error {
	value = strings.TrimSpace(value)
	if value == "" {
		return nil
	}
	for _, r := range value {
		if r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r) {
			continue
		}
		return fmt.Errorf("connector_push_contract: %s must be a simple literal field name", name)
	}
	return nil
}

func validateConnectorPushContractBusinessKeySegment(name, value string) error {
	for _, r := range value {
		if r == '/' || unicode.IsControl(r) {
			return fmt.Errorf("connector_push_contract: %s contains an invalid business-key character", name)
		}
	}
	return nil
}
