package errors

import (
	stderrors "errors"
)

// InternalConditioner is a neutral, dialect-agnostic marker for an error that
// represents a genuine internal/infrastructure failure (as opposed to a
// business or validation failure). It is intentionally exported so that any
// module — backend platform code AND standalone L1 dialect modules that cannot
// import backend — can implement it on their own error types and have a single
// shared classifier recognise them uniformly.
//
// The marker carries NO transport vocabulary: no HTTP status, no wire code, no
// dialect mapping. Classification is the only fact shared owns; mapping an
// internal condition to a status/code stays above the contract in the dialect.
type InternalConditioner interface {
	error
	// InternalCondition reports whether this error represents an internal
	// (infrastructure) failure. Implementations that are always internal return
	// true unconditionally.
	InternalCondition() bool
}

// IsInternal reports whether err, or anything it wraps, classifies itself as an
// internal/infrastructure failure via InternalConditioner. Boundary code uses
// this to distinguish true infrastructure failures from business failures so a
// dead database is not misreported as a user-facing 4xx.
func IsInternal(err error) bool {
	var c InternalConditioner
	return stderrors.As(err, &c) && c.InternalCondition()
}

// RetriableConditioner marks a transient infrastructure condition for which a
// fresh request may succeed. It deliberately carries no transport or dialect
// vocabulary; HTTP/gRPC/flow boundaries own their respective projections.
type RetriableConditioner interface {
	error
	RetriableCondition() bool
}

// IsRetriable reports whether err, or anything it wraps, is a transient
// infrastructure condition rather than a business denial.
func IsRetriable(err error) bool {
	var c RetriableConditioner
	return stderrors.As(err, &c) && c.RetriableCondition()
}

// InfrastructureCondition is the owning infrastructure classification at an
// error boundary. It resolves the intentional overlap where a retriable
// infrastructure error is also internal, while preserving an outer internal
// wrapper's authority over a retriable cause.
type InfrastructureCondition uint8

const (
	InfrastructureConditionNone InfrastructureCondition = iota
	InfrastructureConditionInternal
	InfrastructureConditionRetriable
)

// ClassifyInfrastructure returns the first infrastructure condition owned by
// an error in a single unwrap chain. A retriable marker is the more specific
// classification when the same error also marks itself internal. Transparent
// wrappers are skipped; an outer classified error owns the result. For joined
// errors, any internal branch wins over retriable so ambiguous failures fail
// closed rather than being declared safe to retry.
func ClassifyInfrastructure(err error) InfrastructureCondition {
	if err == nil {
		return InfrastructureConditionNone
	}
	if retriable, ok := err.(RetriableConditioner); ok && retriable.RetriableCondition() {
		return InfrastructureConditionRetriable
	}
	if internal, ok := err.(InternalConditioner); ok && internal.InternalCondition() {
		return InfrastructureConditionInternal
	}
	if joined, ok := err.(interface{ Unwrap() []error }); ok {
		result := InfrastructureConditionNone
		for _, child := range joined.Unwrap() {
			switch ClassifyInfrastructure(child) {
			case InfrastructureConditionInternal:
				return InfrastructureConditionInternal
			case InfrastructureConditionRetriable:
				result = InfrastructureConditionRetriable
			}
		}
		return result
	}
	return ClassifyInfrastructure(stderrors.Unwrap(err))
}

// UnauthenticatedConditioner marks a failure to establish caller identity.
// It deliberately says nothing about HTTP, gRPC, sessions, or delegation;
// each boundary owns the representation of the neutral condition.
type UnauthenticatedConditioner interface {
	error
	UnauthenticatedCondition() bool
}

// IsUnauthenticated reports whether err, or anything it wraps, represents a
// caller whose identity could not be established.
func IsUnauthenticated(err error) bool {
	var c UnauthenticatedConditioner
	return stderrors.As(err, &c) && c.UnauthenticatedCondition()
}

// PermissionDeniedConditioner marks an authenticated caller that lacks the
// authority required for an operation. It carries no object, App, or transport
// vocabulary.
type PermissionDeniedConditioner interface {
	error
	PermissionDeniedCondition() bool
}

// IsPermissionDenied reports whether err, or anything it wraps, represents an
// authorization denial.
func IsPermissionDenied(err error) bool {
	var c PermissionDeniedConditioner
	return stderrors.As(err, &c) && c.PermissionDeniedCondition()
}

// ConflictConditioner marks a durable business/data conflict. Transient lock
// contention must implement RetriableConditioner instead so a service boundary
// cannot collapse retryable infrastructure pressure into a terminal conflict.
type ConflictConditioner interface {
	error
	ConflictCondition() bool
}

// IsConflict reports whether err, or anything it wraps, is a terminal
// business/data conflict.
func IsConflict(err error) bool {
	var c ConflictConditioner
	return stderrors.As(err, &c) && c.ConflictCondition()
}

// NotFoundConditioner is a neutral classifier for missing-resource conditions.
// It carries no transport or dialect vocabulary; callers decide how a missing
// resource maps to their wire contract.
type NotFoundConditioner interface {
	error
	NotFoundCondition() bool
}

// IsNotFound reports whether err, or anything it wraps, classifies itself as a
// missing-resource condition.
func IsNotFound(err error) bool {
	var c NotFoundConditioner
	return stderrors.As(err, &c) && c.NotFoundCondition()
}

// ValidationFieldConditioner is a neutral classifier for validation failures
// that name one or more affected fields. It carries no dialect wire code or
// transport status; API dialects decide how a field-scoped validation condition
// is rendered.
type ValidationFieldConditioner interface {
	error
	ValidationField() string
	ValidationMessage() string
}

// ValidationField returns the field payload carried by a validation error when
// one is available. The boolean is false when err has no field-scoped validation
// condition in its unwrap chain.
func ValidationField(err error) (string, bool) {
	var c ValidationFieldConditioner
	if !stderrors.As(err, &c) {
		return "", false
	}
	return c.ValidationField(), true
}

// ValidationDetails returns the field and client-facing message carried by a
// validation error. The boolean is false when err has no field-scoped validation
// condition in its unwrap chain.
func ValidationDetails(err error) (field string, message string, ok bool) {
	var c ValidationFieldConditioner
	if !stderrors.As(err, &c) {
		return "", "", false
	}
	return c.ValidationField(), c.ValidationMessage(), true
}

// FieldWriteDeniedConditioner is a neutral classifier for public write-path
// rejections caused by one or more fields the caller cannot write. It carries no
// dialect code or transport status; API dialects decide whether this becomes a
// fields[] envelope, a validation body, or some other shape.
type FieldWriteDeniedConditioner interface {
	error
	FieldWriteDeniedFields() []string
	FieldWriteDeniedMessage() string
}

// FieldWriteDeniedDetails returns the affected fields and client-facing message
// carried by a write-path field-denial error.
func FieldWriteDeniedDetails(err error) (fields []string, message string, ok bool) {
	var c FieldWriteDeniedConditioner
	if !stderrors.As(err, &c) {
		return nil, "", false
	}
	return c.FieldWriteDeniedFields(), c.FieldWriteDeniedMessage(), true
}

// InvalidFieldForWriteConditioner is a neutral classifier for fields that are
// present in a write request but are not legal writable inputs for that
// operation. This is deliberately distinct from FieldWriteDeniedConditioner:
// the latter describes caller authorization, while this condition describes
// request shape or engine-owned field lifecycle.
type InvalidFieldForWriteConditioner interface {
	error
	InvalidFieldForWriteFields() []string
	InvalidFieldForWriteMessage() string
}

// InvalidFieldForWriteDetails returns the fields and client-facing message
// carried by an invalid-write-input error.
func InvalidFieldForWriteDetails(err error) (fields []string, message string, ok bool) {
	var c InvalidFieldForWriteConditioner
	if !stderrors.As(err, &c) {
		return nil, "", false
	}
	return c.InvalidFieldForWriteFields(), c.InvalidFieldForWriteMessage(), true
}

// ValidationRuleConditioner is a neutral classifier for metadata validation-rule
// failures. Rule identity is diagnostic; dialect wire contracts generally expose
// only the authored user-facing message.
type ValidationRuleConditioner interface {
	error
	ValidationRuleMessage() string
}

// ValidationRuleFailure is one neutral metadata-authored validation-rule
// rejection. Field is empty when the rule targets the record as a whole.
type ValidationRuleFailure struct {
	RuleName string
	Message  string
	Field    string
}

// ValidationRuleFailuresConditioner carries every triggered validation rule
// without introducing a transport or product-dialect dependency.
type ValidationRuleFailuresConditioner interface {
	error
	ValidationRuleFailures() []ValidationRuleFailure
}

// ValidationRuleFailures returns all metadata validation-rule failures carried
// by err, preserving evaluation order.
func ValidationRuleFailures(err error) (failures []ValidationRuleFailure, ok bool) {
	var c ValidationRuleFailuresConditioner
	if !stderrors.As(err, &c) {
		return nil, false
	}
	failures = c.ValidationRuleFailures()
	if len(failures) == 0 {
		return nil, false
	}
	return append([]ValidationRuleFailure(nil), failures...), true
}

// ValidationRuleMessage returns the client-facing validation-rule message when
// err carries a metadata validation-rule condition.
func ValidationRuleMessage(err error) (message string, ok bool) {
	var c ValidationRuleConditioner
	if !stderrors.As(err, &c) {
		return "", false
	}
	return c.ValidationRuleMessage(), true
}
