package jsonrpc2

import (
	"bytes"
	"encoding/json"
	"fmt"
)

const (
	// Invalid JSON was received by the server.
	// An error occurred on the server while parsing the JSON text.
	errCodeParse = -32700

	// The JSON sent is not a valid Request object.
	errCodeInvalidRequest = -32600

	// The method does not exist / is not available.
	errCodeMethodNotFound = -32601

	// Invalid method parameter(s).
	errCodeInvalidParams = -32602

	// Reserved for implementation-defined server-errors.
	errCodeServerError = -32000
)

// Error implements the error stuct type defined in the spec, as well as the go
// error interface and the `Is` method for the errors package. Note that the
// `Is` method will check both the type and Code of the given target in order to
// check same-ness.
type Error struct {
	// Code is an identifier for the kind of error being returned. All
	// application-defined error codes should be positive.
	Code    int    `json:"code"`
	Message string `json:"message"`
	Data    any    `json:"data,omitempty"`
}

// NewError returns an error with the given Code and formatted Message.
func NewError(code int, msgFmt string, msgArgs ...any) Error {
	return Error{Code: code, Message: fmt.Sprintf(msgFmt, msgArgs...)}
}

// NewInvalidParamsError returns an error indicating that the provided
// parameters were invalid.
//
// Parameters are considered invalid if they could never possibly be valid in
// the format they were given and always indicate a bug in the caller. For
// example a timedate string in the wrong format.
//
// If a parameter is invalid due to the context it was used in, for example a
// wrong password, a specific error code should be used to indicate that to the
// caller.
func NewInvalidParamsError(msgFmt string, msgArgs ...any) Error {
	return NewError(errCodeInvalidParams, msgFmt, msgArgs...)
}

func (e Error) Error() string {
	buf := new(bytes.Buffer)
	fmt.Fprintf(buf, "[%d] %s", e.Code, e.Message)
	if e.Data != nil {
		fmt.Fprint(buf, "\nExtra data: ")
		if err := json.NewEncoder(buf).Encode(e.Data); err != nil {
			panic(fmt.Sprintf("json encoding Error.Data %#v: %v", e.Data, err))
		}
	}
	return buf.String()
}

func (e Error) Is(target error) bool {
	if tErr, ok := target.(Error); ok {
		return tErr.Code == e.Code
	}
	return false
}

// WithData returns a copy of the Error with the Data file overwritten.
func (e Error) WithData(data any) Error {
	e.Data = data
	return e
}