///|
fn valid_meta_key(key : String) -> Bool {
  let segments = key.split("/").to_array()
  if segments.length() > 2 {
    return false
  }
  let name = segments[segments.length() - 1].to_owned()
  if !name.is_empty() {
    let chars = name.to_array()
    let endpoint = fn(char : Char) {
      char.is_ascii_alphabetic() || char.is_ascii_digit()
    }
    if !endpoint(chars[0]) ||
      !endpoint(chars[chars.length() - 1]) ||
      !chars.all(fn(char) {
        endpoint(char) || char == '-' || char == '_' || char == '.'
      }) {
      return false
    }
  }
  if segments.length() == 2 {
    let labels = segments[0].split(".").to_array()
    if labels.is_empty() {
      return false
    }
    for label in labels {
      let chars = label.to_array()
      if chars.is_empty() ||
        !chars[0].is_ascii_alphabetic() ||
        !(chars[chars.length() - 1].is_ascii_alphabetic() ||
        chars[chars.length() - 1].is_ascii_digit()) ||
        !chars.all(fn(char) {
          char.is_ascii_alphabetic() || char.is_ascii_digit() || char == '-'
        }) {
        return false
      }
    }
  }
  true
}

///|
fn validate_meta_map(values : Map[String, Json]) -> Unit raise McpError {
  for key, _ in values {
    if !valid_meta_key(key) {
      raise InvalidParams("invalid _meta key: \{key}")
    }
  }
}

///|
/// Metadata required on every MCP 2026-07-28 request.
pub struct RequestMeta {
  protocol_version : String
  client_info : Implementation?
  client_capabilities : ClientCapabilities
  progress_token : Json?
  log_level : LoggingLevel?
  extra : Map[String, Json]
} derive(Debug, Eq)

///|
pub fn RequestMeta::RequestMeta(
  protocol_version? : String = PROTOCOL_VERSION,
  client_info? : Implementation,
  client_capabilities? : ClientCapabilities = ClientCapabilities::empty(),
  progress_token? : Json,
  log_level? : LoggingLevel,
  extra? : Map[String, Json] = {},
) -> RequestMeta raise McpError {
  if protocol_version.is_empty() {
    raise InvalidParams("protocol version must not be empty")
  }
  validate_meta_map(extra)
  {
    protocol_version,
    client_info: client_info.map(value => value.copy()),
    client_capabilities: client_capabilities.copy(),
    progress_token: progress_token.map(fn(p) { p.copy() }),
    log_level,
    extra: extra.copy_json(),
  }
}

///|
pub impl ToJson for RequestMeta with fn to_json(self : RequestMeta) -> Json {
  // Vendor metadata is inserted first so protocol-reserved keys always win.
  let fields = self.extra.copy_json()
  fields.set_opt("progressToken", self.progress_token.map(fn(p) { p.copy() }))
  fields["io.modelcontextprotocol/protocolVersion"] = Json::string(
    self.protocol_version,
  )
  if self.client_info is Some(value) {
    fields["io.modelcontextprotocol/clientInfo"] = ToJson::to_json(value)
  }
  fields["io.modelcontextprotocol/clientCapabilities"] = ToJson::to_json(
    self.client_capabilities,
  )
  if self.log_level is Some(value) {
    fields["io.modelcontextprotocol/logLevel"] = ToJson::to_json(value)
  }
  Json::object(fields)
}

///|
fn validate_progress_token(
  value : Json,
  context : String,
) -> Unit raise McpError {
  match value {
    String(_) | Number(_) => ()
    _ =>
      raise InvalidParams("\{context}.progressToken must be a string or number")
  }
}

///|
pub impl @json.FromJson for RequestMeta with fn from_json(
  value : Json,
  path : @json.JsonPath,
) -> RequestMeta raise {
  guard value is Object(fields) else {
    raise JsonDecodeError((path, "expected object"))
  }
  let protocol_version = match
    fields.get("io.modelcontextprotocol/protocolVersion") {
    Some(String(s)) => s
    Some(_) =>
      raise JsonDecodeError(
        (
          path.add_key("io.modelcontextprotocol/protocolVersion"),
          "expected string",
        ),
      )
    None =>
      raise JsonDecodeError(
        (
          path.add_key("io.modelcontextprotocol/protocolVersion"),
          "protocolVersion is required",
        ),
      )
  }
  guard fields.get("io.modelcontextprotocol/clientCapabilities")
    is Some(caps_val) else {
    raise JsonDecodeError(
      (
        path.add_key("io.modelcontextprotocol/clientCapabilities"),
        "clientCapabilities is required",
      ),
    )
  }
  let client_capabilities : ClientCapabilities = @json.from_json(
    caps_val,
    path=path.add_key("io.modelcontextprotocol/clientCapabilities"),
  )
  let extra : Map[String, Json] = Map([])
  for key, item in fields {
    match key {
      "progressToken"
      | "io.modelcontextprotocol/protocolVersion"
      | "io.modelcontextprotocol/clientInfo"
      | "io.modelcontextprotocol/clientCapabilities"
      | "io.modelcontextprotocol/logLevel" => ()
      _ => {
        if !valid_meta_key(key) {
          raise JsonDecodeError(
            (path.add_key(key), "invalid _meta key: \{key}"),
          )
        }
        extra[key] = item.copy()
      }
    }
  }
  let client_info : Implementation? = match
    fields.get("io.modelcontextprotocol/clientInfo") {
    Some(v) =>
      Some(
        @json.from_json(
          v,
          path=path.add_key("io.modelcontextprotocol/clientInfo"),
        ),
      )
    None => None
  }
  let progress_token = match fields.get("progressToken") {
    Some(String(_) | Number(_) as t) => Some(t)
    Some(Null) | None => None
    Some(_) =>
      raise JsonDecodeError(
        (path.add_key("progressToken"), "expected string or number"),
      )
  }
  let log_level : LoggingLevel? = match
    fields.get("io.modelcontextprotocol/logLevel") {
    Some(v) =>
      Some(
        @json.from_json(
          v,
          path=path.add_key("io.modelcontextprotocol/logLevel"),
        ),
      )
    None => None
  }
  RequestMeta(
    protocol_version~,
    client_info?,
    client_capabilities~,
    progress_token?,
    log_level?,
    extra~,
  ) catch {
    error => raise JsonDecodeError((path, error.message()))
  }
}

///|
/// Decode the request `_meta` object at the MCP protocol boundary.
pub fn RequestMeta::from_json(
  value : Json,
  path? : @json.JsonPath,
) -> RequestMeta raise McpError {
  decode_json(value, path?)
}

///|
/// Per-call overrides and MRTR retry data used by request builders.
pub struct RequestOptions {
  progress_token : Json?
  log_level : LoggingLevel?
  input_responses : Map[String, InputResponse]?
  request_state : String?
  meta : Map[String, Json]
} derive(Debug, Eq)

///|
pub fn RequestOptions::RequestOptions(
  progress_token? : Json,
  log_level? : LoggingLevel,
  input_responses? : Map[String, InputResponse],
  request_state? : String,
  meta? : Map[String, Json] = {},
) -> RequestOptions {
  let copied_responses : Map[String, InputResponse]? = input_responses.map(fn(
    responses,
  ) {
    let copied : Map[String, InputResponse] = Map([])
    for key, response in responses {
      copied[key] = InputResponse(response.result)
    }
    copied
  })
  {
    progress_token: progress_token.map(fn(p) { p.copy() }),
    log_level,
    input_responses: copied_responses,
    request_state,
    meta: meta.copy_json(),
  }
}

///|
/// Transport-provided request hooks. Built-in defaults are inert, while
/// streaming adapters can emit progress and expose cancellation without
/// storing connection state on `Server`.
pub struct RequestHooks {
  priv emit_notification : async (Json) -> Unit raise McpError
  priv is_cancelled : () -> Bool
  priv transport_meta : Json?
  priv last_progress : Ref[Double?]
}

///|
pub fn RequestHooks::RequestHooks(
  emit_notification? : async (Json) -> Unit raise McpError = fn(_) { () },
  is_cancelled? : () -> Bool = fn() { false },
  transport_meta? : Json,
) -> RequestHooks {
  { emit_notification, is_cancelled, transport_meta, last_progress: Ref(None) }
}

///|
pub fn RequestOptions::request_meta(
  self : RequestOptions,
  client_capabilities~ : ClientCapabilities,
  client_info? : Implementation,
  protocol_version? : String = PROTOCOL_VERSION,
) -> RequestMeta raise McpError {
  validate_meta_map(self.meta)
  RequestMeta(
    protocol_version~,
    client_info?,
    client_capabilities~,
    progress_token?=self.progress_token,
    log_level?=self.log_level,
    extra=self.meta,
  )
}

///|
/// Add common metadata and MRTR retry fields to method-specific parameters.
pub fn RequestOptions::request_params(
  self : RequestOptions,
  params : Map[String, Json],
  client_capabilities~ : ClientCapabilities,
  client_info? : Implementation,
  protocol_version? : String = PROTOCOL_VERSION,
) -> Json raise McpError {
  let fields = params.copy()
  fields["_meta"] = ToJson::to_json(
    self.request_meta(client_capabilities~, client_info?, protocol_version~),
  )
  if self.input_responses is Some(responses) {
    let values : Map[String, Json] = Map([])
    for key, response in responses {
      values[key] = ToJson::to_json(response)
    }
    fields["inputResponses"] = Json::object(values)
  }
  fields.set_opt("requestState", self.request_state.map(Json::string))
  Json::object(fields)
}

///|
/// Request-scoped protocol data passed to application handlers.
pub struct RequestContext {
  request_id : Json
  method_name : String
  meta : RequestMeta
  input_responses : Map[String, InputResponse]?
  request_state : String?
  transport_meta : Json?
  hooks : RequestHooks
}

///|
pub fn RequestContext::RequestContext(
  request_id~ : Json,
  method_name~ : String,
  meta~ : RequestMeta,
  input_responses? : Map[String, InputResponse],
  request_state? : String,
  hooks? : RequestHooks = RequestHooks(),
) -> RequestContext raise McpError {
  if !(request_id is (String(_) | Number(_))) {
    raise InvalidRequest("request id must be a string or number")
  }
  {
    request_id,
    method_name,
    meta,
    input_responses,
    request_state,
    transport_meta: hooks.transport_meta.map(fn(m) { m.copy() }),
    hooks,
  }
}

///|
pub fn RequestContext::is_cancelled(self : RequestContext) -> Bool {
  (self.hooks.is_cancelled)()
}

///|
/// Emit request-scoped progress using the token supplied by the client.
pub async fn RequestContext::report_progress(
  self : RequestContext,
  progress~ : Double,
  total? : Double,
  message? : String,
) -> Unit raise McpError {
  guard self.meta.progress_token is Some(token) else {
    raise InvalidRequest("request did not provide a progress token")
  }
  if progress < 0.0 || progress.is_nan() || progress.is_inf() {
    raise InvalidParams("progress must be a finite non-negative number")
  }
  if self.hooks.last_progress.val is Some(previous) && progress <= previous {
    raise InvalidParams("progress must increase with each notification")
  }
  if total is Some(limit) &&
    (limit <= 0.0 || limit.is_nan() || limit.is_inf() || progress > limit) {
    raise InvalidParams(
      "progress total must be finite, positive, and not below progress",
    )
  }
  (self.hooks.emit_notification)(
    ProgressNotification(progress_token=token, progress~, total?, message?).to_json(),
  )
  self.hooks.last_progress.val = Some(progress)
}