///|
/// Low-level JSON-RPC method handler.
///
/// The argument is the request `params` member, or `None` when `params` is
/// absent. Return a JSON-RPC result value, or raise `RpcError` to send a
/// JSON-RPC error response for calls with an id.
pub type Handler = async (Json?) -> Json raise RpcError

///|
/// JSON-RPC request id used for local request correlation.
pub(all) enum Id {
  String(String)
  Number(Int)
} derive(Eq, Hash, Debug)

///|
pub impl ToJson for Id with fn to_json(self) {
  match self {
    String(value) => Json::string(value)
    Number(value) => Json::number(value.to_double())
  }
}

///|
pub impl @json.FromJson for Id with fn from_json(value, path) {
  match value {
    String(value) => String(value)
    Number(value, ..) => {
      let int = value.to_int()
      guard int.to_double() == value else {
        raise JsonDecodeError((path, "Id::from_json: expected integer id"))
      }
      Number(int)
    }
    _ =>
      raise JsonDecodeError((path, "Id::from_json: expected string or number"))
  }
}

///|
/// Parsed request data used internally by the dispatcher.
struct Request {
  id : Json?
  rpc_method : String
  params : Json?
  notification : Bool
} derive(Eq, Debug)

///|
/// Request parsing failure plus any valid id that could still be detected.
suberror RequestError {
  RequestError(id~ : Json?, error~ : RpcError)
} derive(Eq, Debug)

///|
fn Request::response_id(self : Request) -> Json {
  self.id.unwrap_or(null)
}

///|
fn RequestError::response_id(self : RequestError) -> Json {
  match self {
    RequestError(id~, ..) => id.unwrap_or(null)
  }
}

///|
/// Return true when a JSON value is a legal JSON-RPC request id.
fn is_valid_id(value : Json) -> Bool {
  match value {
    String(_) | Number(_, ..) | Null => true
    _ => false
  }
}

///|
fn request_error(id : Json?) -> RequestError {
  RequestError(
    id~,
    error=RpcError(code=INVALID_REQUEST_CODE, message="Invalid Request"),
  )
}

///|
fn detect_id(obj : Map[String, Json]) -> Json? {
  guard obj is { "id": id, .. } else { None }
  guard is_valid_id(id) else { None }
  Some(id)
}

///|
/// Convert a JSON value to a validated request object.
fn parse_request(value : Json) -> Request raise RequestError {
  guard value is Object(obj) else { raise request_error(None) }
  let detected_id = detect_id(obj)
  guard obj is { "jsonrpc": String("2.0"), "method": String(rpc_method), .. } else {
    raise request_error(detected_id)
  }
  let params = match obj {
    { "params": Array(_) | Object(_) as params, .. } => Some(params)
    { "params": _, .. } => raise request_error(detected_id)
    _ => None
  }
  let id = match obj {
    { "id": id, .. } if is_valid_id(id) => Some(id)
    { "id": _, .. } => raise request_error(None)
    _ => None
  }
  { id, rpc_method, params, notification: id is None }
}

///|
fn success_response(id : Json, result : Json) -> Json {
  let response : Response = { id, result: Ok(result) }
  response.to_json()
}

///|
fn error_response(id : Json, error : RpcError) -> Json {
  let response : Response = { id, result: Err(error) }
  response.to_json()
}

///|
fn unknown_id_error(error : RpcError) -> Json {
  error_response(null, error)
}

///|
enum MessageKind {
  RequestLike
  ResponseLike
  InvalidLike
} derive(Eq, Debug)

///|
fn classify_message(value : Json) -> MessageKind {
  match value {
    Object({ "method": _, .. }) => RequestLike
    Object({ "result": _, .. }) | Object({ "error": _, .. }) => ResponseLike
    _ => InvalidLike
  }
}

///|
struct Response {
  id : Json
  result : Result[Json, RpcError]
} derive(Eq, Debug)

///|
impl ToJson for Response with fn to_json(self) {
  let obj : Map[String, Json] = { "jsonrpc": "2.0", "id": self.id }
  match self.result {
    Ok(result) => obj["result"] = result
    Err(error) => obj["error"] = error.to_json()
  }
  Json::object(obj)
}

///|
impl @json.FromJson for Response with fn from_json(value, path) {
  match value {
    Object({ "jsonrpc": String("2.0"), "id": _, "result": _, "error": _, .. }) =>
      raise JsonDecodeError(
        (path, "Response::from_json: result and error are mutually exclusive"),
      )
    Object({ "jsonrpc": String("2.0"), "id": id, "result": result, .. }) =>
      { id, result: Ok(result) }
    Object({ "jsonrpc": String("2.0"), "id": id, "error": error_json, .. }) => {
      let error : RpcError = @json.from_json(error_json, path~)
      { id, result: Err(error) }
    }
    _ =>
      raise JsonDecodeError(
        (path, "Response::from_json: expected response object"),
      )
  }
}

///|
/// Result for a locally issued request completed by an incoming response.
pub(all) struct CompletedRequest {
  id : Id
  method_name : String
  result : Result[Json, RpcError]
} derive(Eq, Debug)

///|
/// Result of receiving one JSON-RPC document.
///
/// `outgoing` contains response documents to send, `completed` contains local
/// requests matched by responses, and `unexpected` contains response-like
/// documents that did not match any pending request.
pub(all) struct ReceiveResult {
  outgoing : Array[Json]
  completed : Array[CompletedRequest]
  unexpected : Array[Json]
} derive(Eq, Debug)

///|
fn ReceiveResult::ReceiveResult() -> ReceiveResult {
  { outgoing: [], completed: [], unexpected: [] }
}

///|
/// Create a JSON-RPC batch document from request or response documents.
pub fn batch(items : Array[Json]) -> Json {
  Json::array(items)
}