///|
/// Resource descriptor returned by `resources/list`.
///
/// `size`, when known, is the raw resource byte length before base64 encoding
/// or tokenization.
pub struct Resource {
  uri : String
  name : String
  title : String?
  description : String?
  mime_type : String?
  icons : Array[Icon]
  annotations : Annotations?
  size : Int64?
  meta : Map[String, Json]
} derive(Debug, Eq)

///|
/// Construct a text resource descriptor.
pub fn Resource::text(
  uri~ : String,
  name~ : String,
  title? : String,
  description? : String,
  mime_type? : String = "text/plain",
  annotations? : Annotations,
  size? : Int64,
  meta? : Map[String, Json] = {},
) -> Resource raise McpError {
  Resource::Resource(
    uri~,
    name~,
    title?,
    description?,
    mime_type~,
    annotations?,
    size?,
    meta~,
  )
}

///|
/// Construct a resource descriptor.
pub fn Resource::Resource(
  uri~ : String,
  name~ : String,
  title? : String,
  description? : String,
  mime_type? : String,
  icons? : Array[Icon] = [],
  annotations? : Annotations,
  size? : Int64,
  meta? : Map[String, Json] = {},
) -> Resource raise McpError {
  if uri.is_empty() {
    raise InvalidParams("resource uri must not be empty")
  }
  if name.is_empty() {
    raise InvalidParams("resource name must not be empty")
  }
  if size is Some(value) && value < 0L {
    raise InvalidParams("resource size must be non-negative")
  }
  validate_meta_map(meta)
  {
    uri,
    name,
    title,
    description,
    mime_type,
    icons: icons.map(icon => icon.copy()),
    annotations: annotations.map(fn(a) { a.copy() }),
    size,
    meta: meta.copy_json(),
  }
}

///|
pub impl ToJson for Resource with fn to_json(self : Resource) -> Json {
  let fields : Map[String, Json] = {
    "uri": Json::string(self.uri),
    "name": Json::string(self.name),
  }
  fields.set_opt("title", self.title.map(Json::string))
  fields.set_opt("description", self.description.map(Json::string))
  fields.set_opt("mimeType", self.mime_type.map(Json::string))
  if !self.icons.is_empty() {
    fields["icons"] = self.icons
      .map(fn(icon) { ToJson::to_json(icon) })
      .to_json()
  }
  if self.annotations is Some(annotations) {
    fields["annotations"] = ToJson::to_json(annotations)
  }
  fields.set_opt(
    "size",
    self.size.map(fn(size) { Json::number(size.to_double(), repr="\{size}") }),
  )
  if !self.meta.is_empty() {
    fields["_meta"] = Json::object(self.meta.copy_json())
  }
  Json::object(fields)
}

///|
pub impl @json.FromJson for Resource with fn from_json(
  value : Json,
  path : @json.JsonPath,
) -> Resource raise {
  guard value is Object(fields) else {
    raise JsonDecodeError((path, "expected object"))
  }
  let uri = match fields.get("uri") {
    Some(String(s)) => s
    Some(_) => raise JsonDecodeError((path.add_key("uri"), "expected string"))
    None => raise JsonDecodeError((path.add_key("uri"), "uri is required"))
  }
  let name = match fields.get("name") {
    Some(String(s)) => s
    Some(_) => raise JsonDecodeError((path.add_key("name"), "expected string"))
    None => raise JsonDecodeError((path.add_key("name"), "name is required"))
  }
  let title = match fields.get("title") {
    Some(String(s)) => Some(s)
    Some(Null) | None => None
    Some(_) => raise JsonDecodeError((path.add_key("title"), "expected string"))
  }
  let description = match fields.get("description") {
    Some(String(s)) => Some(s)
    Some(Null) | None => None
    Some(_) =>
      raise JsonDecodeError((path.add_key("description"), "expected string"))
  }
  let mime_type = match fields.get("mimeType") {
    Some(String(s)) => Some(s)
    Some(Null) | None => None
    Some(_) =>
      raise JsonDecodeError((path.add_key("mimeType"), "expected string"))
  }
  let icons : Array[Icon] = match fields.get("icons") {
    Some(Array(arr)) => {
      let result : Array[Icon] = []
      for i, it in arr {
        result.push(
          @json.from_json(it, path=path.add_key("icons").add_index(i)),
        )
      }
      result
    }
    Some(Null) | None => []
    Some(_) => raise JsonDecodeError((path.add_key("icons"), "expected array"))
  }
  let annotations : Annotations? = match fields.get("annotations") {
    Some(v) => Some(@json.from_json(v, path=path.add_key("annotations")))
    None => None
  }
  let size : Int64? = match fields.get("size") {
    Some(Number(s, ..)) => {
      if s < 0.0 || s != s.to_int64().to_double() {
        raise JsonDecodeError(
          (path.add_key("size"), "expected non-negative integer"),
        )
      }
      Some(s.to_int64())
    }
    Some(Null) | None => None
    Some(_) => raise JsonDecodeError((path.add_key("size"), "expected number"))
  }
  let meta : Map[String, Json] = match fields.get("_meta") {
    Some(Object(m)) => {
      validate_meta_map(m) catch {
        error => raise JsonDecodeError((path.add_key("_meta"), error.message()))
      }
      m.copy_json()
    }
    Some(Null) | None => Map([])
    Some(_) => raise JsonDecodeError((path.add_key("_meta"), "expected object"))
  }
  Resource(
    uri~,
    name~,
    title?,
    description?,
    mime_type?,
    icons~,
    annotations?,
    size?,
    meta~,
  ) catch {
    error => raise JsonDecodeError((path, error.message()))
  }
}

///|
pub fn Resource::from_json(
  value : Json,
  path? : @json.JsonPath,
) -> Resource raise McpError {
  decode_json(value, path?)
}

///|
/// Resource template descriptor returned by `resources/templates/list`.
pub struct ResourceTemplate {
  uri_template : String
  name : String
  title : String?
  description : String?
  mime_type : String?
  icons : Array[Icon]
  annotations : Annotations?
  meta : Map[String, Json]
} derive(Debug, Eq)

///|
/// Construct a resource template descriptor.
pub fn ResourceTemplate::ResourceTemplate(
  uri_template~ : String,
  name~ : String,
  title? : String,
  description? : String,
  mime_type? : String,
  icons? : Array[Icon] = [],
  annotations? : Annotations,
  meta? : Map[String, Json] = {},
) -> ResourceTemplate raise McpError {
  validate_meta_map(meta)
  {
    uri_template,
    name,
    title,
    description,
    mime_type,
    icons: icons.map(icon => icon.copy()),
    annotations: annotations.map(fn(a) { a.copy() }),
    meta: meta.copy_json(),
  }
}

///|
pub impl ToJson for ResourceTemplate with fn to_json(self : ResourceTemplate) -> Json {
  let fields : Map[String, Json] = {
    "uriTemplate": Json::string(self.uri_template),
    "name": Json::string(self.name),
  }
  fields.set_opt("title", self.title.map(Json::string))
  fields.set_opt("description", self.description.map(Json::string))
  fields.set_opt("mimeType", self.mime_type.map(Json::string))
  if !self.icons.is_empty() {
    fields["icons"] = self.icons
      .map(fn(icon) { ToJson::to_json(icon) })
      .to_json()
  }
  if self.annotations is Some(annotations) {
    fields["annotations"] = ToJson::to_json(annotations)
  }
  if !self.meta.is_empty() {
    fields["_meta"] = Json::object(self.meta.copy_json())
  }
  Json::object(fields)
}

///|
pub impl @json.FromJson for ResourceTemplate with fn from_json(
  value : Json,
  path : @json.JsonPath,
) -> ResourceTemplate raise {
  guard value is Object(fields) else {
    raise JsonDecodeError((path, "expected object"))
  }
  let uri_template = match fields.get("uriTemplate") {
    Some(String(s)) => s
    Some(_) =>
      raise JsonDecodeError((path.add_key("uriTemplate"), "expected string"))
    None =>
      raise JsonDecodeError(
        (path.add_key("uriTemplate"), "uriTemplate is required"),
      )
  }
  let name = match fields.get("name") {
    Some(String(s)) => s
    Some(_) => raise JsonDecodeError((path.add_key("name"), "expected string"))
    None => raise JsonDecodeError((path.add_key("name"), "name is required"))
  }
  let title = match fields.get("title") {
    Some(String(s)) => Some(s)
    Some(Null) | None => None
    Some(_) => raise JsonDecodeError((path.add_key("title"), "expected string"))
  }
  let description = match fields.get("description") {
    Some(String(s)) => Some(s)
    Some(Null) | None => None
    Some(_) =>
      raise JsonDecodeError((path.add_key("description"), "expected string"))
  }
  let mime_type = match fields.get("mimeType") {
    Some(String(s)) => Some(s)
    Some(Null) | None => None
    Some(_) =>
      raise JsonDecodeError((path.add_key("mimeType"), "expected string"))
  }
  let icons : Array[Icon] = match fields.get("icons") {
    Some(Array(arr)) => {
      let result : Array[Icon] = []
      for i, it in arr {
        result.push(
          @json.from_json(it, path=path.add_key("icons").add_index(i)),
        )
      }
      result
    }
    Some(Null) | None => []
    Some(_) => raise JsonDecodeError((path.add_key("icons"), "expected array"))
  }
  let annotations : Annotations? = match fields.get("annotations") {
    Some(v) => Some(@json.from_json(v, path=path.add_key("annotations")))
    None => None
  }
  let meta : Map[String, Json] = match fields.get("_meta") {
    Some(Object(m)) => {
      validate_meta_map(m) catch {
        error => raise JsonDecodeError((path.add_key("_meta"), error.message()))
      }
      m.copy_json()
    }
    Some(Null) | None => Map([])
    Some(_) => raise JsonDecodeError((path.add_key("_meta"), "expected object"))
  }
  ResourceTemplate(
    uri_template~,
    name~,
    title?,
    description?,
    mime_type?,
    icons~,
    annotations?,
    meta~,
  ) catch {
    error => raise JsonDecodeError((path, error.message()))
  }
}

///|
pub fn ResourceTemplate::from_json(
  value : Json,
  path? : @json.JsonPath,
) -> ResourceTemplate raise McpError {
  decode_json(value, path?)
}

///|
/// Resource contents returned by `resources/read`.
pub(all) enum ResourceContent {
  ResourceText(
    uri~ : String,
    text~ : String,
    mime_type~ : String?,
    meta~ : Json?
  )
  ResourceBlob(
    uri~ : String,
    blob~ : String,
    mime_type~ : String?,
    meta~ : Json?
  )
} derive(Debug, Eq)

///|
pub impl ToJson for ResourceContent with fn to_json(self : ResourceContent) -> Json {
  let fields : Map[String, Json] = Map([])
  match self {
    ResourceText(uri~, text~, mime_type~, meta~) => {
      fields["uri"] = Json::string(uri)
      fields["text"] = Json::string(text)
      fields.set_opt("mimeType", mime_type.map(Json::string))
      fields.set_opt("_meta", meta.map(fn(m) { m.copy() }))
    }
    ResourceBlob(uri~, blob~, mime_type~, meta~) => {
      fields["uri"] = Json::string(uri)
      fields["blob"] = Json::string(blob)
      fields.set_opt("mimeType", mime_type.map(Json::string))
      fields.set_opt("_meta", meta.map(fn(m) { m.copy() }))
    }
  }
  Json::object(fields)
}

///|
pub impl @json.FromJson for ResourceContent with fn from_json(
  value : Json,
  path : @json.JsonPath,
) -> ResourceContent raise {
  guard value is Object(fields) else {
    raise JsonDecodeError((path, "expected object"))
  }
  let uri = match fields.get("uri") {
    Some(String(u)) => u
    Some(_) => raise JsonDecodeError((path.add_key("uri"), "expected string"))
    None => raise JsonDecodeError((path.add_key("uri"), "uri is required"))
  }
  let mime_type = match fields.get("mimeType") {
    Some(String(m)) => Some(m)
    Some(Null) | None => None
    Some(_) =>
      raise JsonDecodeError((path.add_key("mimeType"), "expected string"))
  }
  let meta = fields.get("_meta")
  match (fields.get("text"), fields.get("blob")) {
    (Some(_), Some(_)) =>
      raise JsonDecodeError((path, "cannot contain both text and blob"))
    (Some(String(text)), None) =>
      ResourceContent::text(uri~, text~, mime_type?, meta?)
    (None, Some(String(blob))) =>
      ResourceContent::blob(uri~, blob~, mime_type?, meta?)
    (Some(_), None) | (None, Some(_)) =>
      raise JsonDecodeError((path, "text/blob must be a string"))
    (None, None) =>
      raise JsonDecodeError((path, "requires text or blob content"))
  }
}

///|
pub fn ResourceContent::from_json(
  value : Json,
  path? : @json.JsonPath,
) -> ResourceContent raise McpError {
  decode_json(value, path?)
}

///|
/// Construct text resource contents.
pub fn ResourceContent::text(
  uri~ : String,
  text~ : String,
  mime_type? : String,
  meta? : Json,
) -> ResourceContent {
  ResourceText(uri~, text~, mime_type~, meta~)
}

///|
/// Construct blob resource contents from base64-encoded data.
pub fn ResourceContent::blob(
  uri~ : String,
  blob~ : String,
  mime_type? : String,
  meta? : Json,
) -> ResourceContent {
  ResourceBlob(uri~, blob~, mime_type~, meta~)
}

///|
pub struct ReadResourceResult {
  contents : Array[ResourceContent]
  cache_policy : CachePolicy?
  meta : ResultMeta?
} derive(Debug, Eq)

///|
/// Construct a resource read result with typed contents and caching advice.
pub fn ReadResourceResult::ReadResourceResult(
  contents~ : Array[ResourceContent],
  cache_policy? : CachePolicy,
  meta? : ResultMeta,
) -> ReadResourceResult {
  {
    contents: contents.copy(),
    cache_policy,
    meta: meta.map(fn(m) { m.copy() }),
  }
}

///|
pub impl ToJson for ReadResourceResult with fn to_json(
  self : ReadResourceResult,
) -> Json {
  let fields : Map[String, Json] = {
    "contents": self.contents
    .map(fn(content) { ToJson::to_json(content) })
    .to_json(),
  }
  if self.cache_policy is Some(policy) {
    policy.put(fields)
  }
  if self.meta is Some(meta) {
    fields["_meta"] = ToJson::to_json(meta)
  }
  Json::object(fields)
}

///|
pub impl @json.FromJson for ReadResourceResult with fn from_json(
  value : Json,
  path : @json.JsonPath,
) -> ReadResourceResult raise {
  guard value is Object(fields) else {
    raise JsonDecodeError((path, "expected object"))
  }
  let contents : Array[ResourceContent] = match fields.get("contents") {
    Some(Array(arr)) => {
      let result : Array[ResourceContent] = []
      for i, it in arr {
        result.push(
          @json.from_json(it) catch {
            JsonDecodeError((_, msg)) =>
              raise JsonDecodeError(
                (path.add_key("contents").add_index(i), msg),
              )
          },
        )
      }
      result
    }
    Some(_) =>
      raise JsonDecodeError((path.add_key("contents"), "expected array"))
    None =>
      raise JsonDecodeError((path.add_key("contents"), "contents is required"))
  }
  let cache_policy : CachePolicy? = if fields.contains("ttlMs") {
    Some(
      @json.from_json(value) catch {
        JsonDecodeError((_, msg)) => raise JsonDecodeError((path, msg))
      },
    )
  } else {
    None
  }
  let meta : ResultMeta? = match fields.get("_meta") {
    Some(v) => Some(@json.from_json(v, path=path.add_key("_meta")))
    None => None
  }
  ReadResourceResult(contents~, cache_policy?, meta?)
}

///|
pub fn ReadResourceResult::from_json(
  value : Json,
  path? : @json.JsonPath,
) -> ReadResourceResult raise McpError {
  decode_json(value, path?)
}

///|
/// Construct a single text resource read result.
pub fn ReadResourceResult::text(
  uri~ : String,
  text~ : String,
  mime_type? : String,
) -> ReadResourceResult {
  ReadResourceResult(contents=[ResourceContent::text(uri~, text~, mime_type?)])
}

///|
/// Canonical decoded result of `resources/list`.
pub struct ListResourcesResult {
  resources : Array[Resource]
  next_cursor : String?
  cache_policy : CachePolicy
  meta : ResultMeta?
} derive(Debug, Eq)

///|
pub impl ToJson for ListResourcesResult with fn to_json(
  self : ListResourcesResult,
) -> Json {
  let fields : Map[String, Json] = {
    "resultType": ToJson::to_json(ResultType::Complete),
    "resources": self.resources.map(fn(r) { ToJson::to_json(r) }).to_json(),
  }
  fields.set_opt("nextCursor", self.next_cursor.map(Json::string))
  self.cache_policy.put(fields)
  if self.meta is Some(meta) {
    fields["_meta"] = ToJson::to_json(meta)
  }
  Json::object(fields)
}

///|
pub impl @json.FromJson for ListResourcesResult with fn from_json(
  value : Json,
  path : @json.JsonPath,
) -> ListResourcesResult raise {
  guard value is Object(fields) else {
    raise JsonDecodeError((path, "expected object"))
  }
  let resources : Array[Resource] = match fields.get("resources") {
    Some(Array(arr)) => {
      let result : Array[Resource] = []
      for i, it in arr {
        result.push(
          @json.from_json(it, path=path.add_key("resources").add_index(i)),
        )
      }
      result
    }
    Some(_) =>
      raise JsonDecodeError((path.add_key("resources"), "expected array"))
    None =>
      raise JsonDecodeError(
        (path.add_key("resources"), "resources is required"),
      )
  }
  let next_cursor = match fields.get("nextCursor") {
    Some(String(s)) => Some(s)
    Some(Null) | None => None
    Some(_) =>
      raise JsonDecodeError((path.add_key("nextCursor"), "expected string"))
  }
  let cache_policy : CachePolicy = @json.from_json(value) catch {
    JsonDecodeError((_, msg)) => raise JsonDecodeError((path, msg))
  }
  let meta : ResultMeta? = match fields.get("_meta") {
    Some(v) => Some(@json.from_json(v, path=path.add_key("_meta")))
    None => None
  }
  { resources, next_cursor, cache_policy, meta }
}

///|
pub fn ListResourcesResult::from_json(
  value : Json,
  path? : @json.JsonPath,
) -> ListResourcesResult raise McpError {
  decode_json(value, path?)
}

///|
/// Canonical decoded result of `resources/templates/list`.
pub struct ListResourceTemplatesResult {
  resource_templates : Array[ResourceTemplate]
  next_cursor : String?
  cache_policy : CachePolicy
  meta : ResultMeta?
} derive(Debug, Eq)

///|
pub impl ToJson for ListResourceTemplatesResult with fn to_json(
  self : ListResourceTemplatesResult,
) -> Json {
  let fields : Map[String, Json] = {
    "resultType": ToJson::to_json(ResultType::Complete),
    "resourceTemplates": self.resource_templates
    .map(fn(t) { ToJson::to_json(t) })
    .to_json(),
  }
  fields.set_opt("nextCursor", self.next_cursor.map(Json::string))
  self.cache_policy.put(fields)
  if self.meta is Some(meta) {
    fields["_meta"] = ToJson::to_json(meta)
  }
  Json::object(fields)
}

///|
pub impl @json.FromJson for ListResourceTemplatesResult with fn from_json(
  value : Json,
  path : @json.JsonPath,
) -> ListResourceTemplatesResult raise {
  guard value is Object(fields) else {
    raise JsonDecodeError((path, "expected object"))
  }
  let resource_templates : Array[ResourceTemplate] = match
    fields.get("resourceTemplates") {
    Some(Array(arr)) => {
      let result : Array[ResourceTemplate] = []
      for i, it in arr {
        result.push(
          @json.from_json(
            it,
            path=path.add_key("resourceTemplates").add_index(i),
          ),
        )
      }
      result
    }
    Some(_) =>
      raise JsonDecodeError(
        (path.add_key("resourceTemplates"), "expected array"),
      )
    None =>
      raise JsonDecodeError(
        (path.add_key("resourceTemplates"), "resourceTemplates is required"),
      )
  }
  let next_cursor = match fields.get("nextCursor") {
    Some(String(s)) => Some(s)
    Some(Null) | None => None
    Some(_) =>
      raise JsonDecodeError((path.add_key("nextCursor"), "expected string"))
  }
  let cache_policy : CachePolicy = @json.from_json(value) catch {
    JsonDecodeError((_, msg)) => raise JsonDecodeError((path, msg))
  }
  let meta : ResultMeta? = match fields.get("_meta") {
    Some(v) => Some(@json.from_json(v, path=path.add_key("_meta")))
    None => None
  }
  { resource_templates, next_cursor, cache_policy, meta }
}

///|
pub fn ListResourceTemplatesResult::from_json(
  value : Json,
  path? : @json.JsonPath,
) -> ListResourceTemplatesResult raise McpError {
  decode_json(value, path?)
}

///|
/// Parameters for `resources/read`.
pub struct ReadResourceParams {
  uri : String
} derive(Debug, Eq)

///|
pub fn ReadResourceParams::ReadResourceParams(
  uri~ : String,
) -> ReadResourceParams raise McpError {
  guard !uri.is_empty() else {
    raise InvalidParams("resource uri must not be empty")
  }
  { uri, }
}

///|
pub impl @json.FromJson for ReadResourceParams with fn from_json(
  value : Json,
  path : @json.JsonPath,
) -> ReadResourceParams raise {
  guard value is Object(fields) else {
    raise JsonDecodeError((path, "expected object"))
  }
  let uri = match fields.get("uri") {
    Some(String(uri)) if !uri.is_empty() => uri
    Some(String(_)) =>
      raise JsonDecodeError(
        (path.add_key("uri"), "resource uri must not be empty"),
      )
    Some(_) => raise JsonDecodeError((path.add_key("uri"), "expected string"))
    None => raise JsonDecodeError((path.add_key("uri"), "uri is required"))
  }
  ReadResourceParams(uri~) catch {
    error => raise JsonDecodeError((path, error.message()))
  }
}

///|
pub impl ToJson for ReadResourceParams with fn to_json(
  self : ReadResourceParams,
) -> Json {
  Json::object({ "uri": Json::string(self.uri) })
}

///|
pub fn ReadResourceParams::from_json(
  value : Json,
  path? : @json.JsonPath,
) -> ReadResourceParams raise McpError {
  decode_json(value, path?)
}