package commonv1

type FailureCategory string

const (
	FailureInvalidRequest  FailureCategory = "INVALID_REQUEST"
	FailureUnauthenticated FailureCategory = "UNAUTHENTICATED"
	FailureForbidden       FailureCategory = "FORBIDDEN"
	FailureMissing         FailureCategory = "MISSING"
	// FailureConflict means the requested mutation cannot commit against the
	// current live state (for example, a uniqueness or referential conflict).
	// It is distinct from STALE_STATE, which asks the caller to refresh an
	// admitted Catalog/security snapshot before retrying.
	FailureConflict          FailureCategory = "CONFLICT"
	FailureCanceled          FailureCategory = "CANCELED"
	FailureDeadlineExceeded  FailureCategory = "DEADLINE_EXCEEDED"
	FailureResourceExhausted FailureCategory = "RESOURCE_EXHAUSTED"
	FailureIncompatible      FailureCategory = "INCOMPATIBLE"
	FailureStaleState        FailureCategory = "STALE_STATE"
	FailureUnavailable       FailureCategory = "UNAVAILABLE"
	FailureInternal          FailureCategory = "INTERNAL_FAILURE"
)

type InvalidComponent string

// ConflictReason describes a safe recovery condition, never an internal cause.
type ConflictReason string

const (
	ConflictRevisionMismatch ConflictReason = "REVISION_MISMATCH"
	ConflictReferenced       ConflictReason = "REFERENCED"
	ConflictDefaultRequired  ConflictReason = "DEFAULT_REQUIRED"
)

const (
	InvalidComponentUnspecified       InvalidComponent = ""
	InvalidComponentObjectID          InvalidComponent = "OBJECT_ID"
	InvalidComponentRecordID          InvalidComponent = "RECORD_ID"
	InvalidComponentField             InvalidComponent = "SELECTED_ROOT_FIELD"
	InvalidComponentAppInstallationID InvalidComponent = "APP_INSTALLATION_ID"
	InvalidComponentCapability        InvalidComponent = "RUNTIME_CAPABILITY"
	InvalidComponentDeadline          InvalidComponent = "DEADLINE"
	InvalidComponentQueryPlan         InvalidComponent = "QUERY_PLAN"
	InvalidComponentQuerySelection    InvalidComponent = "QUERY_SELECTION"
	InvalidComponentPredicate         InvalidComponent = "QUERY_PREDICATE"
	InvalidComponentRelationshipField InvalidComponent = "RELATIONSHIP_FIELD"
	InvalidComponentResponse          InvalidComponent = "RESPONSE"
	InvalidComponentCommandID         InvalidComponent = "COMMAND_ID"
	InvalidComponentOperation         InvalidComponent = "OPERATION"
	InvalidComponentFieldValue        InvalidComponent = "FIELD_VALUE"
	InvalidComponentBatchMode         InvalidComponent = "BATCH_MODE"
	InvalidComponentCommandData       InvalidComponent = "COMMAND_DATA"
	InvalidComponentSavedViewID       InvalidComponent = "SAVED_VIEW_ID"
)

type LimitKind string

const (
	LimitSelectedFields    LimitKind = "SELECTED_FIELDS"
	LimitSemanticRequest   LimitKind = "SEMANTIC_REQUEST_BYTES"
	LimitSemanticResponse  LimitKind = "SEMANTIC_RESPONSE_BYTES"
	LimitObjectID          LimitKind = "OBJECT_IDENTIFIER_BYTES"
	LimitFieldID           LimitKind = "FIELD_IDENTIFIER_BYTES"
	LimitRecordID          LimitKind = "RECORD_IDENTIFIER_BYTES"
	LimitRecordCount       LimitKind = "RECORD_COUNT"
	LimitQueryPredicate    LimitKind = "QUERY_PREDICATE_NODES"
	LimitQueryDepth        LimitKind = "QUERY_NESTING_DEPTH"
	LimitQueryOrderBy      LimitKind = "QUERY_ORDER_BY_COUNT"
	LimitQuerySubqueries   LimitKind = "QUERY_SUBQUERY_COUNT"
	LimitQueryOffset       LimitKind = "QUERY_OFFSET"
	LimitAppInstallationID LimitKind = "APP_INSTALLATION_IDENTIFIER_BYTES"
	LimitCapabilityCount   LimitKind = "CAPABILITY_COUNT"
	LimitCapabilityID      LimitKind = "CAPABILITY_IDENTIFIER_BYTES"
	LimitAdmission         LimitKind = "ADMISSION"
	LimitOperationCount    LimitKind = "OPERATION_COUNT"
	LimitFieldCount        LimitKind = "FIELD_COUNT"
	LimitCommandID         LimitKind = "COMMAND_IDENTIFIER_BYTES"
	LimitWireFrame         LimitKind = "WIRE_FRAME_BYTES"
	LimitUploadBytes       LimitKind = "UPLOAD_BYTES"
)

type StaleAxis string

const (
	StaleAxisCatalog  StaleAxis = "CATALOG_REVISION"
	StaleAxisSecurity StaleAxis = "SECURITY_EPOCH"
)

// Failure carries only reviewed public details. Internal causes, SQL, raw
// authority, topology, and permission rules are intentionally unrepresentable.
type Failure struct {
	Category FailureCategory
	Receipts *Receipts

	InvalidComponent InvalidComponent
	CanonicalField   string

	LimitKind LimitKind
	Maximum   uint64

	RequiredCapability string
	SupportedMajor     uint32
	SupportedMinorMin  uint32
	SupportedMinorMax  uint32

	StaleAxis        StaleAxis
	RequiredRevision uint64
	ServedRevision   uint64

	// RetryAfterMillis is an optional producer delay for UNAVAILABLE and
	// fail-fast ADMISSION capacity failures. Zero means no explicit delay;
	// capability-specific retry policy remains independently versioned.
	RetryAfterMillis uint64
	RequestID        string
	ConflictReason   ConflictReason
}

// ValidFailureConflictReason rejects unknown reasons and reasons attached to
// categories that cannot represent a conflict. Empty retains generic conflict.
func ValidFailureConflictReason(failure *Failure) bool {
	if failure == nil {
		return false
	}
	if failure.ConflictReason == "" {
		return true
	}
	if failure.Category != FailureConflict {
		return false
	}
	switch failure.ConflictReason {
	case ConflictRevisionMismatch, ConflictReferenced, ConflictDefaultRequired:
		return true
	default:
		return false
	}
}

// ValidFailureRetryAfter rejects a delay attached to a failure whose execution
// semantics do not permit producer-directed retry advice.
func ValidFailureRetryAfter(failure *Failure) bool {
	if failure == nil {
		return false
	}
	if failure.RetryAfterMillis == 0 {
		return true
	}
	return failure.Category == FailureUnavailable ||
		(failure.Category == FailureResourceExhausted && failure.LimitKind == LimitAdmission)
}

func (f *Failure) Error() string {
	if f == nil {
		return ""
	}
	return string(f.Category)
}
