///|
/// Message covers all jsonrpc2 message types.
/// They share no common functionality, but are a closed set of concrete types
/// that are allowed to implement this interface. The message types are Request
/// and Response.
pub enum Message {
  // Request is a jsonrpc2 request message.
  Request(Request)
  // Response is a jsonrpc2 response message.
  Response(Response)
  // BatchRequest is an array of requests.
  BatchRequest(Array[Request])
  // BatchResponse is an array of responses.
  BatchResponse(Array[Response])
} derive(Debug, Eq)

///|
pub impl Show for Message with fn output(self, logger) {
  match self {
    Request(req) =>
      logger.write_string(
        (
          $|Request(\{req.to_string()})
        ),
      )
    Response(res) =>
      logger.write_string(
        (
          $|Response(\{res.to_string()})
        ),
      )
    BatchRequest(arr_req) =>
      logger.write_string(
        (
          $|BatchRequest(\{Repr(arr_req).to_string()})
        ),
      )
    BatchResponse(arr_res) =>
      logger.write_string(
        (
          $|BatchResponse(\{Repr(arr_res).to_string()})
        ),
      )
  }
}

///|
test "Message show interface" {
  let m1 = new_notification("m", { "a": "b" }.to_json())
  inspect(
    m1,
    content=(
      #|Request({id: None, method_: "m", params: Object({ "a": String("b") })})
    ),
  )
  let m2 = new_call(ID::string("2"), "m", { "a": "b" }.to_json())
  inspect(
    m2,
    content=(
      #|Request({id: Some(String("2")), method_: "m", params: Object({ "a": String("b") })})
    ),
  )
  let m3 = new_response(ID::number(3), Ok({ "a": "b" }.to_json()))
  inspect(
    m3,
    content=(
      #|Response({id: Number(3), result: Ok(Object({ "a": String("b") }))})
    ),
  )
  let m4 = new_batch_request([m1, m2])
  inspect(
    m4,
    content=(
      #|BatchRequest([
      #|  { id: None, method_: "m", params: Object({ "a": String("b") }) },
      #|  {
      #|    id: Some(String("2")),
      #|    method_: "m",
      #|    params: Object({ "a": String("b") }),
      #|  },
      #|])
    ),
  )
  let m5 = new_batch_response([m1, m2, m3])
  inspect(
    m5,
    content=(
      #|BatchResponse([{ id: Number(3), result: Ok(Object({ "a": String("b") })) }])
    ),
  )
}

///|
pub impl ToJson for Message with fn to_json(self) {
  match self {
    Request(req) => req.to_json()
    Response(res) => res.to_json()
    BatchRequest(arr_req) => arr_req.to_json()
    BatchResponse(arr_res) => arr_res.to_json()
  }
}

///|
pub impl @json.FromJson for Message with fn from_json(json, path) {
  try {
    return Request(@json.from_json(json, path~))
  } catch {
    _ => ()
  }
  try {
    return Response(@json.from_json(json, path~))
  } catch {
    _ => ()
  }
  try {
    return BatchRequest(@json.from_json(json, path~))
  } catch {
    _ => ()
  }
  try {
    return BatchResponse(@json.from_json(json, path~))
  } catch {
    _ => ()
  }
  raise @json.JsonDecodeError((path, "expected request(s) or response(s)"))
}

///|
/// new_notification constructs a new notification message for the supplied
/// method and parameters.
pub fn new_notification(method_ : String, params : Json) -> Message {
  Request({ id: None, method_, params })
}

///|
/// new_call constructs a new call message for the supplied ID, method and
/// parameters.
pub fn new_call(id : ID, method_ : String, params : Json) -> Message {
  Request({ id: Some(id), method_, params })
}

///|
/// new_response constructs a new Response message.
pub fn new_response(id : ID, result : Result[Json, WireError]) -> Message {
  Response({ id, result })
}

///|
/// new_batch_request constructs a new batch request message for the supplied
/// requests.
pub fn new_batch_request(messages : Array[Message]) -> Message {
  let requests = messages.filter_map(Message::as_request)
  BatchRequest(requests)
}

///|
/// new_batch_response constructs a new batch response message for the supplied
/// responses.
pub fn new_batch_response(messages : Array[Message]) -> Message {
  let responses = messages.filter_map(Message::as_response)
  BatchResponse(responses)
}

///|
pub fn Message::as_call(self : Message) -> Request? {
  guard self is Request(req) else { return None }
  guard req.id is Some(_) else { return None }
  Some(req)
}

///|
pub fn Message::as_notification(self : Message) -> Request? {
  guard self is Request(req) else { return None }
  guard req.id is None else { return None }
  Some(req)
}

///|
pub fn Message::as_request(self : Message) -> Request? {
  if self is Request(req) {
    Some(req)
  } else {
    None
  }
}

///|
pub fn Message::as_response(self : Message) -> Response? {
  if self is Response(res) {
    Some(res)
  } else {
    None
  }
}

///|
/// as_batch_request returns the message as a batch request if it is one.
pub fn Message::as_batch_request(self : Message) -> Array[Request]? {
  if self is BatchRequest(arr_req) {
    Some(arr_req)
  } else {
    None
  }
}

///|
/// as_batch_response returns the message as a batch response if it is one.
pub fn Message::as_batch_response(self : Message) -> Array[Response]? {
  if self is BatchResponse(arr_res) {
    Some(arr_res)
  } else {
    None
  }
}