package error

import "errors"

// Class describes a top-level operational error class that can be used by
// higher layers to route failures to the appropriate UI or transport handler.
type Class string

const (
	// ClassConnection marks connectivity and transport failures talking to remote services.
	ClassConnection Class = "connection"
	// ClassStorage marks local persistence and filesystem related failures.
	ClassStorage Class = "storage"
	// ClassService marks remote service contract and processing failures.
	ClassService Class = "service"
)

// ClassifiedError wraps another error with a top-level error class.
//
// The wrapped error remains available through Unwrap so existing callers may
// continue using errors.Is or errors.As against the original cause.
type ClassifiedError struct {
	class Class
	err   error
}

// Error returns either the wrapped error text or, when no wrapped error is available,
// the textual representation of the class itself.
func (e *ClassifiedError) Error() string {
	if e == nil {
		return ""
	}
	if e.err != nil {
		return e.err.Error()
	}
	return string(e.class)
}

// Unwrap exposes the wrapped cause for standard Go error inspection.
func (e *ClassifiedError) Unwrap() error {
	if e == nil {
		return nil
	}
	return e.err
}

// Class returns the top-level class recorded on this error.
func (e *ClassifiedError) Class() Class {
	if e == nil {
		return ""
	}
	return e.class
}

// Is reports class equality so errors.Is(err, ErrConnection) style checks remain possible.
func (e *ClassifiedError) Is(target error) bool {
	t, ok := target.(*ClassifiedError)
	if !ok {
		return false
	}
	return e.class != "" && e.class == t.class
}

var (
	// ErrConnection is a class sentinel for connection related failures.
	ErrConnection = &ClassifiedError{class: ClassConnection}
	// ErrStorage is a class sentinel for storage related failures.
	ErrStorage = &ClassifiedError{class: ClassStorage}
	// ErrService is a class sentinel for service related failures.
	ErrService = &ClassifiedError{class: ClassService}
)

// WrapConnection wraps err with the connection class unless it is already classified.
func WrapConnection(err error) error {
	return wrapClass(ClassConnection, err)
}

// WrapStorage wraps err with the storage class unless it is already classified.
func WrapStorage(err error) error {
	return wrapClass(ClassStorage, err)
}

// WrapService wraps err with the service class unless it is already classified.
func WrapService(err error) error {
	return wrapClass(ClassService, err)
}

// IsConnection reports whether err is classified as a connection failure.
func IsConnection(err error) bool {
	return errors.Is(err, ErrConnection)
}

// IsStorage reports whether err is classified as a storage failure.
func IsStorage(err error) bool {
	return errors.Is(err, ErrStorage)
}

// IsService reports whether err is classified as a service failure.
func IsService(err error) bool {
	return errors.Is(err, ErrService)
}

func wrapClass(class Class, err error) error {
	if err == nil {
		return nil
	}
	if existing, ok := errors.AsType[*ClassifiedError](err); ok && existing.class == class {
		return err
	}
	return &ClassifiedError{class: class, err: err}
}
