package storeerr

import "errors"

var (
	// ErrUnauthorized means the caller token is invalid.
	ErrUnauthorized = errors.New("unauthorized")
	// ErrForbidden means the caller is authenticated but has no access.
	ErrForbidden = errors.New("forbidden")
	// ErrNotFound means the requested object does not exist.
	ErrNotFound = errors.New("not found")
	// ErrConflict means the requested mutation conflicts with current state.
	ErrConflict = errors.New("conflict")
	// ErrBadRequest means the request is semantically invalid.
	ErrBadRequest = errors.New("bad request")
	// ErrUnprocessable means the request is syntactically valid but cannot be applied.
	ErrUnprocessable = errors.New("unprocessable")
	// ErrGone means the requested token or resource is no longer usable.
	ErrGone = errors.New("gone")
	// ErrSystemNotInitialized means the first admin account has not been created yet.
	ErrSystemNotInitialized = errors.New("system not initialized")
	// ErrSystemAlreadyInitialized means the first-admin bootstrap flow already finished.
	ErrSystemAlreadyInitialized = errors.New("system already initialized")
)

// RequestError preserves a stable public error kind while exposing a more specific message.
type RequestError struct {
	kind    error
	message string
}

// New returns a request-scoped error that unwraps to kind.
func New(kind error, message string) error {
	return RequestError{kind: kind, message: message}
}

func (err RequestError) Error() string {
	return err.message
}

func (err RequestError) Unwrap() error {
	return err.kind
}

// BadRequest returns an error that unwraps to ErrBadRequest.
func BadRequest(message string) error {
	return New(ErrBadRequest, message)
}

// Forbidden returns an error that unwraps to ErrForbidden.
func Forbidden(message string) error {
	return New(ErrForbidden, message)
}

// NotFound returns an error that unwraps to ErrNotFound.
func NotFound(message string) error {
	return New(ErrNotFound, message)
}

// Conflict returns an error that unwraps to ErrConflict.
func Conflict(message string) error {
	return New(ErrConflict, message)
}

// Gone returns an error that unwraps to ErrGone.
func Gone(message string) error {
	return New(ErrGone, message)
}

// Wrapped returns a request-scoped error that unwraps to kind only. The
// declared kind stays authoritative for public classification: the internal
// cause is preserved separately (InternalCause) for logs and structured
// logging and never participates in the errors.Is/As graph consumed by public
// status/code classification. It is the cause-retaining sibling of New, used
// where a store kind must be preserved for classification and the originating
// repository/message error must still be visible to server-side logging
// without ever becoming trusted public prose.
func Wrapped(kind error, message string, cause error) error {
	return wrappedError{kind: kind, message: message, cause: cause}
}

type wrappedError struct {
	kind    error
	message string
	cause   error
}

func (err wrappedError) Error() string { return err.message }

func (err wrappedError) Unwrap() error { return err.kind }

// InternalCause returns the preserved internal cause for logs and wrapping,
// or nil. It is deliberately not part of the Unwrap chain so an internal
// cause that itself carries a store sentinel can never override the declared
// kind in errors.Is/errors.As classification.
func (err wrappedError) InternalCause() error { return err.cause }

// CauseAccessor is implemented by request-scoped errors that preserve an
// internal cause separately from the classification graph.
type CauseAccessor interface {
	error
	InternalCause() error
}

// InternalCause returns the first preserved internal cause carried by a
// CauseAccessor in the error chain, or nil. Transports and structured logging
// use it to surface the originating repository/message error without letting
// it participate in the errors.Is/As graph consumed by public classification.
func InternalCause(err error) error {
	var accessor CauseAccessor
	if errors.As(err, &accessor) && accessor != nil {
		return accessor.InternalCause()
	}
	return nil
}
