///|
/// JSON-RPC 2.0 envelope handling for the MCP stdio transport.
///
/// MCP's stdio transport delimits messages with newlines (a JSON message may
/// not contain a literal newline), so the transport adapter only splits lines
/// and the engine owns everything else: envelope validation, request/response
/// classification, identifier matching and error synthesis.
pub struct RpcMessage {
  raw : Json
  id : Json?
  has_id : Bool
  verb : String?
  params : Json?
  result : Json?
  error : Json?
}

///|
fn[T] opt_is_none(value : T?) -> Bool {
  match value {
    None => true
    Some(_) => false
  }
}

///|
pub fn parse_rpc(value : Json) -> RpcMessage? {
  let fields = match value {
    Object(fields) => fields
    _ => return None
  }
  let id = fields.get("id")
  let verb = match fields.get("method") {
    Some(String(m)) => Some(m)
    _ => None
  }
  if opt_is_none(id) && opt_is_none(verb) {
    return None
  }
  Some({
    raw: value,
    id,
    has_id: id is Some(_),
    verb,
    params: fields.get("params"),
    result: fields.get("result"),
    error: fields.get("error"),
  })
}

///|
/// Stable key for correlating responses with requests. JSON allows string and
/// number identifiers; both are prefixed so `1` and `"1"` cannot collide.
pub fn id_key(id : Json?) -> String {
  match id {
    Some(String(s)) => "s:" + s
    Some(Number(n, ..)) => "n:" + n.to_int().to_string()
    Some(Null) | None => ""
    Some(other) => "j:" + json_canonical(other)
  }
}

///|
pub fn error_response(
  id : Json?,
  code : Int32,
  message : String,
  data : Json?,
) -> Json {
  let error : Array[(String, Json)] = [
    ("code", json_int(code)),
    ("message", json_str(message)),
  ]
  match data {
    Some(payload) => error.push(("data", payload))
    None => ()
  }
  json_obj([
    ("jsonrpc", json_str("2.0")),
    (
      "id",
      match id {
        Some(v) => v
        None => json_null()
      },
    ),
    ("error", json_obj(error)),
  ])
}

///|
pub fn result_response(id : Json?, result : Json) -> Json {
  json_obj([
    ("jsonrpc", json_str("2.0")),
    (
      "id",
      match id {
        Some(v) => v
        None => json_null()
      },
    ),
    ("result", result),
  ])
}

///|
/// Methods the gateway forwards without a policy decision: the MCP handshake,
/// capability discovery and notifications. Anything that can read or change
/// state goes through `tools/call`, `resources/read` or `prompts/get` and is
/// evaluated.
pub fn is_passthrough_method(verb : String) -> Bool {
  verb == "initialize" ||
  verb == "ping" ||
  verb == "tools/list" ||
  verb == "resources/list" ||
  verb == "resources/templates/list" ||
  verb == "prompts/list" ||
  verb == "logging/setLevel" ||
  verb == "completion/complete" ||
  verb.has_prefix("notifications/")
}

///|
/// Capability name used for policy matching when a client requests a resource
/// or a prompt instead of a tool.
pub fn capability_name(verb : String) -> String {
  if verb == "resources/read" {
    "resources.read"
  } else if verb == "prompts/get" {
    "prompts.get"
  } else if verb == "tools/call" {
    "tools.call"
  } else {
    verb
  }
}

///|
/// Arguments used for policy matching. Tool calls carry `params.arguments`;
/// resource and prompt requests carry their own parameter object.
pub fn capability_args(verb : String, params : Json?) -> Json {
  match params {
    Some(Object(fields)) =>
      if verb == "tools/call" {
        match fields.get("arguments") {
          Some(args) => args
          None => Json::object(Map([]))
        }
      } else {
        Json::object(fields)
      }
    _ => Json::object(Map([]))
  }
}

///|
/// Tool name for a `tools/call` request, or `None` when the request is
/// malformed. The engine answers malformed calls with a JSON-RPC error instead
/// of forwarding them, so an upstream server never sees an invalid request.
pub fn tool_name_of(params : Json?) -> String? {
  match params {
    Some(Object(fields)) =>
      match fields.get("name") {
        Some(String(s)) => Some(s)
        _ => None
      }
    _ => None
  }
}