package jsonrpc2

import (
	"context"
	"encoding/json"
	"errors"
	"io"
)

// ReadWriterServer wraps an io.ReadWriter so that it can handle RPC calls.
//
// The HandleNext method will process a single request and response. It should
// be called in a loop until it returns an error.
type ReadWriterServer struct {
	handler Handler
	enc     *json.Encoder
	dec     *json.Decoder
}

// NewReadWriterServer returns a ReadWriterServer which will read/write on the
// given io.ReadWriter.
//
// All methods exported from rpcInterface which accept a context and one other
// value, and return a value and an error, will be considered to be available
// RPC methods, and the ReadWriterServer will execute calls to those methods
// against the given rpcInterface instance.
func NewReadWriterServer(handler Handler, rw io.ReadWriter) *ReadWriterServer {
	return &ReadWriterServer{
		handler: handler,
		enc:     json.NewEncoder(rw),
		dec:     json.NewDecoder(rw),
	}
}

// HandleNext blocks until a single RPC call has been handled. If an error is
// returned, including context errors, then the ReadWriterServer should be
// considered in an unusable state and discarded.
func (h *ReadWriterServer) HandleNext(ctx context.Context) error {
	req, decErr := decodeRequest(h.dec)
	if decErr.Code != 0 {
		encErr := encodeErrorResponse(h.enc, req.ID, decErr)
		return errors.Join(decErr, encErr)
	}

	var rpcErr Error
	res, err := h.handler.ServeRPC(ctx, req)
	if err != nil && !errors.As(err, &rpcErr) {
		// We deliberately don't include the actual error in the response. If
		// doing so is desired this can be achieved using a middleware which
		// translates the non-Error into an Error prior to it reaching this
		// point.
		rpcErr = NewError(errCodeServerError, "unexpected server-side error")
	}

	if rpcErr.Code != 0 {
		return encodeErrorResponse(h.enc, req.ID, rpcErr)
	}

	return encodeResponse(h.enc, req.ID, res)
}