package errors

import (
	"errors"

	"github.com/hashicorp/go-multierror"
)

// NOTE: this file contains abstractions of endpoint errors. Its goal is not to provide full spectrum of possible errors
// across multiple transport (HTTP/GRPC). Instead, it aims to provide service endpoint with a thin and simple way to return more
// meaningful error data without having to deal with transport details. Also, by doing this, we are able to leverage
// existing error handling mechanisms supported by Go such as wrapping and runtime check with Is() and As().
//
// It is the role of transport layer to unwrap and convert these errors into transport-specific code and
// error messages.

var (
	ErrNotFound        = errors.New("not found")
	ErrInvalidArgument = errors.New("invalid argument")
	ErrUnimplemented   = errors.New("unimplemented")
)

// Error represents an error with details that explain it. This is suitable for building meaningful endpoint response.
type Error struct {
	err     error
	details error
}

// New returns a new Error that wraps an error with given details.
func New(err error, details ...error) *Error {
	switch err := err.(type) {
	case *Error:
		return err
	default:
		e := &Error{
			err:     err,
			details: nil,
		}

		if len(details) == 1 {
			e.details = details[0]
		} else if len(details) > 1 {
			e.details = multierror.Append(e.details, details...)
		}

		return e
	}
}

// Error returns the error message associated with the endpoint error.
func (e *Error) Error() string {
	return e.err.Error()
}

// Unwrap returns the underlying error to support Golang error handling.
func (e *Error) Unwrap() error {
	return e.err
}

// Details returns detailed error(s) that explains this error.
func Details(err error) error {
	e := &Error{}

	if errors.As(err, &e) {
		return e.details
	}

	return nil
}
