// Package jsonrpc provides generic JSON-RPC 2.0 types and utilities
// that can be shared between LSP and other JSON-RPC based protocols.
package jsonrpc

import (
	"errors"
	"fmt"
	"strconv"

	"github.com/microsoft/typescript-go/internal/json"
)

// JSONRPCVersion represents the JSON-RPC version field, always "2.0".
type JSONRPCVersion struct{}

const jsonRPCVersion = `"2.0"`

func (JSONRPCVersion) MarshalJSON() ([]byte, error) {
	return []byte(jsonRPCVersion), nil
}

var ErrInvalidJSONRPCVersion = errors.New("invalid JSON-RPC version")

func (*JSONRPCVersion) UnmarshalJSON(data []byte) error {
	if string(data) != jsonRPCVersion {
		return ErrInvalidJSONRPCVersion
	}
	return nil
}

// ID represents a JSON-RPC message ID, which can be either a string or integer.
type ID struct {
	str string
	int int32
}

// NewID creates an ID from an IntegerOrString value.
func NewID(rawValue IntegerOrString) *ID {
	if rawValue.String != nil {
		return &ID{str: *rawValue.String}
	}
	return &ID{int: *rawValue.Integer}
}

// NewIDString creates a string ID.
func NewIDString(str string) *ID {
	return &ID{str: str}
}

// NewIDInt creates an integer ID.
func NewIDInt(i int32) *ID {
	return &ID{int: i}
}

func (id *ID) String() string {
	if id.str != "" {
		return id.str
	}
	return strconv.Itoa(int(id.int))
}

func (id *ID) MarshalJSON() ([]byte, error) {
	if id.str != "" {
		return json.Marshal(id.str)
	}
	return json.Marshal(id.int)
}

func (id *ID) UnmarshalJSON(data []byte) error {
	*id = ID{}
	if len(data) > 0 && data[0] == '"' {
		return json.Unmarshal(data, &id.str)
	}
	return json.Unmarshal(data, &id.int)
}

func (id *ID) TryInt() (int32, bool) {
	if id == nil || id.str != "" {
		return 0, false
	}
	return id.int, true
}

func (id *ID) MustInt() int32 {
	if id.str != "" {
		panic("ID is not an integer")
	}
	return id.int
}

// IntegerOrString is a helper type for creating IDs.
type IntegerOrString struct {
	Integer *int32
	String  *string
}

// ResponseError represents a JSON-RPC error response.
type ResponseError struct {
	Code    int32  `json:"code"`
	Message string `json:"message"`
	Data    any    `json:"data,omitzero"`
}

func (r *ResponseError) String() string {
	if r == nil {
		return ""
	}
	data, err := json.Marshal(r.Data)
	if err != nil {
		return fmt.Sprintf("[%d]: %s\n%v", r.Code, r.Message, data)
	}
	return fmt.Sprintf("[%d]: %s", r.Code, r.Message)
}

func (r *ResponseError) Error() string {
	return r.String()
}

// Standard JSON-RPC error codes.
const (
	CodeParseError     int32 = -32700
	CodeInvalidRequest int32 = -32600
	CodeMethodNotFound int32 = -32601
	CodeInvalidParams  int32 = -32602
	CodeInternalError  int32 = -32603
)

// MessageKind indicates what type of message this is.
type MessageKind int

const (
	MessageKindNotification MessageKind = iota
	MessageKindRequest
	MessageKindResponse
)

// Message represents a raw JSON-RPC message that can be a request, notification, or response.
// Unlike lsproto.Message, this keeps params/result as raw JSON for generic handling.
type Message struct {
	JSONRPC JSONRPCVersion `json:"jsonrpc"`
	ID      *ID            `json:"id,omitzero"`
	Method  string         `json:"method,omitzero"`
	Params  json.Value     `json:"params,omitzero"`
	Result  json.Value     `json:"result,omitzero"`
	Error   *ResponseError `json:"error,omitzero"`
}

// Kind returns the kind of message this is.
func (m *Message) Kind() MessageKind {
	if m.ID != nil && m.Method == "" {
		return MessageKindResponse
	}
	if m.ID == nil {
		return MessageKindNotification
	}
	return MessageKindRequest
}

// IsRequest returns true if this message is a request (has ID and method).
func (m *Message) IsRequest() bool {
	return m.ID != nil && m.Method != ""
}

// IsNotification returns true if this message is a notification (has method but no ID).
func (m *Message) IsNotification() bool {
	return m.ID == nil && m.Method != ""
}

// IsResponse returns true if this message is a response (has ID but no method).
func (m *Message) IsResponse() bool {
	return m.ID != nil && m.Method == ""
}

// RequestMessage is a convenience type for creating request/notification messages.
type RequestMessage struct {
	JSONRPC JSONRPCVersion `json:"jsonrpc"`
	ID      *ID            `json:"id,omitzero"`
	Method  string         `json:"method"`
	Params  any            `json:"params,omitzero"`
}

// ResponseMessage is a convenience type for creating response messages.
type ResponseMessage struct {
	JSONRPC JSONRPCVersion `json:"jsonrpc"`
	ID      *ID            `json:"id,omitzero"`
	Result  any            `json:"result,omitzero"`
	Error   *ResponseError `json:"error,omitzero"`
}
