package errors

import (
	"fmt"

	"github.com/go-errors/errors"
)

// interop with pkg/errors
type causer interface {
	Cause() error
}

// Err intelligently creates/handles errors, while preserving the stack trace.
// It works with errors from github.com/pkg/errors too.
func Err(err interface{}, fmtParams ...interface{}) error {
	if err == nil {
		return nil
	}

	if _, ok := err.(causer); ok {
		err = fmt.Errorf("%+v", err)
	} else if errString, ok := err.(string); ok && len(fmtParams) > 0 {
		err = fmt.Errorf(errString, fmtParams...)
	}

	return errors.Wrap(err, 1)
}

// Wrap calls errors.Wrap, in case you want to skip a different amount
func Wrap(err interface{}, skip int) *errors.Error {
	if err == nil {
		return nil
	}

	if _, ok := err.(causer); ok {
		err = fmt.Errorf("%+v", err)
	}

	return errors.Wrap(err, skip+1)
}

// Unwrap returns the original error that was wrapped
func Unwrap(err error) error {
	if err == nil {
		return nil
	}

	deeper := true
	for deeper {
		deeper = false
		if e, ok := err.(*errors.Error); ok {
			err = e.Err
			deeper = true
		}
		if c, ok := err.(causer); ok {
			err = c.Cause()
			deeper = true
		}
	}

	return err
}

// Is compares two wrapped errors to determine if the underlying errors are the same
// It also interops with errors from pkg/errors
func Is(e error, original error) bool {
	if c, ok := e.(causer); ok {
		e = c.Cause()
	}
	if c, ok := original.(causer); ok {
		original = c.Cause()
	}
	return errors.Is(e, original)
}

// Prefix prefixes the message of the error with the given string
func Prefix(prefix string, err interface{}) error {
	if err == nil {
		return nil
	}
	return errors.WrapPrefix(Err(err), prefix, 0)
}

// Trace returns the stack trace
func Trace(err error) string {
	if err == nil {
		return ""
	}
	return string(Err(err).(*errors.Error).Stack())
}

// FullTrace returns the error type, message, and stack trace
func FullTrace(err error) string {
	if err == nil {
		return ""
	}
	return Err(err).(*errors.Error).ErrorStack()
}

// Base returns a simple error with no stack trace attached
func Base(format string, a ...interface{}) error {
	return fmt.Errorf(format, a...)
}

// HasTrace checks if error has a trace attached
func HasTrace(err error) bool {
	_, ok := err.(*errors.Error)
	return ok
}