// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause

// Package vizerror provides types and utility funcs for handling visible errors
// that are safe to display to end users.
package vizerror

import (
	"errors"
	"fmt"
)

// Error is an error that is safe to display to end users.
type Error struct {
	publicErr error // visible to end users
	wrapped   error // internal
}

// Error implements the error interface. The returned string is safe to display
// to end users.
func (e Error) Error() string {
	return e.publicErr.Error()
}

// New returns an error that formats as the given text. It always returns a vizerror.Error.
func New(publicMsg string) error {
	err := errors.New(publicMsg)
	return Error{
		publicErr: err,
		wrapped:   err,
	}
}

// Errorf returns an Error with the specified publicMsgFormat and values. It always returns a vizerror.Error.
//
// Warning: avoid using an error as one of the format arguments, as this will cause the text
// of that error to be displayed to the end user (which is probably not what you want).
func Errorf(publicMsgFormat string, a ...any) error {
	err := fmt.Errorf(publicMsgFormat, a...)
	return Error{
		publicErr: err,
		wrapped:   err,
	}
}

// Unwrap returns the underlying error.
//
// If the Error was constructed using [WrapWithMessage], this is the wrapped (internal) error
// and not the user-visible error message.
func (e Error) Unwrap() error {
	return e.wrapped
}

// Wrap wraps publicErr with a vizerror.Error.
//
// Deprecated: this is almost always the wrong thing to do. Are you really sure
// you know exactly what err.Error() will stringify to and be safe to show to
// users? [WrapWithMessage] is probably what you want.
func Wrap(publicErr error) error {
	if publicErr == nil {
		return nil
	}
	return Error{publicErr: publicErr, wrapped: publicErr}
}

// WrapWithMessage wraps the given error with a message that's safe to display
// to end users. The text of the wrapped error will not be displayed to end
// users.
//
// WrapWithMessage should almost always be preferred to [Wrap].
func WrapWithMessage(wrapped error, publicMsg string) error {
	return Error{
		publicErr: errors.New(publicMsg),
		wrapped:   wrapped,
	}
}

// As returns the first vizerror.Error in err's chain.
func As(err error) (e Error, ok bool) {
	ok = errors.As(err, &e)
	return e, ok
}
