package usefulerror

import (
	"errors"
	"strings"
)

// UsefulError is an interface that can be implemented for custom error types
// that are actually useful for the user. Think of this as a way out of showing
// weird internal errors to the user, which actually don't help them
type UsefulError interface {
	// Error returns a string that is useful for the user.
	// Maintains compatibility with the standard error interface.
	Error() string

	// HumanError returns a string that is more human-readable.
	HumanError() string

	// Help returns a string that provides help or guidance specific to the
	// business logic of the error.
	Help() string

	// AdditionalHelp returns a string that provides additional help or guidance
	// This is useful for providing specific tooling related instructions such
	// as common line flags to use to fix the error.
	AdditionalHelp() string

	// Code returns a string that can be used to identify the error types
	// Meant for programmatic use, such as logging or categorization
	Code() string
}

type usefulErrorBuilder struct {
	originalError  error
	humanError     string
	help           string
	additionalHelp string
	code           string
	msg            string
}

var _ UsefulError = (*usefulErrorBuilder)(nil)

func Useful() *usefulErrorBuilder {
	return &usefulErrorBuilder{}
}

func (b *usefulErrorBuilder) Wrap(originalError error) *usefulErrorBuilder {
	b.originalError = originalError
	return b
}

// WithHumanError sets a string that is more human-readable.
func (b *usefulErrorBuilder) WithHumanError(humanError string) *usefulErrorBuilder {
	b.humanError = humanError
	return b
}

// WithHelp sets a string that provides additional help or guidance.
func (b *usefulErrorBuilder) WithHelp(help string) *usefulErrorBuilder {
	b.help = help
	return b
}

// WithCode sets a code that can be used to identify the error types.
func (b *usefulErrorBuilder) WithCode(code string) *usefulErrorBuilder {
	b.code = code
	return b
}

// WithMsg sets a message that is useful for the user, but not necessarily human-readable.
func (b *usefulErrorBuilder) Msg(msg string) *usefulErrorBuilder {
	b.msg = msg
	return b
}

// WithAdditionalHelp sets a string that provides additional help or guidance.
func (b *usefulErrorBuilder) WithAdditionalHelp(additionalHelp string) *usefulErrorBuilder {
	b.additionalHelp = additionalHelp
	return b
}

// Error implements the standard error interface. It returns the original error's message if present;
// otherwise, it returns a constructed message based on the code and msg fields, or "unknown error" if none are set.
func (b *usefulErrorBuilder) Error() string {
	if b.originalError != nil {
		return b.originalError.Error()
	}

	if b.msg == "" {
		return "unknown error"
	}

	msgParts := []string{}
	if b.code != "" {
		msgParts = append(msgParts, b.code)
	}

	if b.msg != "" {
		msgParts = append(msgParts, b.msg)
	}

	return strings.Join(msgParts, ": ")
}

// HumanError returns a string that is more human-readable.
func (b *usefulErrorBuilder) HumanError() string {
	if b.humanError == "" {
		return "An error occurred, but no human-readable message is available."
	}

	return b.humanError
}

// Help returns a string that provides additional help or guidance.
func (b *usefulErrorBuilder) Help() string {
	if b.help == "" {
		return "No additional help is available for this error."
	}

	return b.help
}

// Code returns a string that can be used to identify the error types.
func (b *usefulErrorBuilder) Code() string {
	if b.code == "" {
		return "unknown"
	}

	return b.code
}

// AdditionalHelp returns a string that provides additional help or guidance.
func (b *usefulErrorBuilder) AdditionalHelp() string {
	if b.additionalHelp == "" {
		return "No additional help is available for this error."
	}

	return b.additionalHelp
}

// AsUsefulError attempts to convert a given error into a UsefulError.
func AsUsefulError(err error) (UsefulError, bool) {
	if err == nil {
		return nil, false
	}

	var usefulErr *usefulErrorBuilder
	if errors.As(err, &usefulErr) {
		return usefulErr, true
	}

	if usefulErr, ok := err.(UsefulError); ok {
		return usefulErr, true
	}

	return nil, false
}
