///|
pub(all) enum CompletionReference {
  PromptRef(name~ : String, title~ : String?)
  ResourceRef(uri~ : String)
} derive(Debug, Eq)

///|
pub fn CompletionReference::prompt(
  name~ : String,
  title? : String,
) -> CompletionReference {
  PromptRef(name~, title~)
}

///|
pub fn CompletionReference::resource(uri~ : String) -> CompletionReference {
  ResourceRef(uri~)
}

///|
pub impl ToJson for CompletionReference with fn to_json(
  self : CompletionReference,
) -> Json {
  match self {
    PromptRef(name~, title~) => {
      let fields : Map[String, Json] = {
        "type": Json::string("ref/prompt"),
        "name": Json::string(name),
      }
      fields.set_opt("title", title.map(Json::string))
      Json::object(fields)
    }
    ResourceRef(uri~) =>
      Json::object({
        "type": Json::string("ref/resource"),
        "uri": Json::string(uri),
      })
  }
}

///|
pub impl @json.FromJson for CompletionReference with fn from_json(
  value : Json,
  path : @json.JsonPath,
) -> CompletionReference raise {
  guard value is Object(fields) else {
    raise JsonDecodeError((path, "expected object"))
  }
  let typ = match fields.get("type") {
    Some(String(s)) => s
    Some(_) => raise JsonDecodeError((path.add_key("type"), "expected string"))
    None => raise JsonDecodeError((path.add_key("type"), "type is required"))
  }
  match typ {
    "ref/prompt" => {
      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"))
      }
      PromptRef(name~, title~)
    }
    "ref/resource" => {
      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"))
      }
      ResourceRef(uri~)
    }
    other =>
      raise JsonDecodeError(
        (path.add_key("type"), "unknown completion reference type: \{other}"),
      )
  }
}

///|
pub struct CompletionRequest {
  reference : CompletionReference
  argument_name : String
  argument_value : String
  context_arguments : Map[String, String]
  meta : Json?
} derive(Debug, Eq)

///|
pub impl @json.FromJson for CompletionRequest with fn from_json(
  value : Json,
  path : @json.JsonPath,
) -> CompletionRequest raise {
  guard value is Object(fields) else {
    raise JsonDecodeError((path, "expected object"))
  }
  let reference : CompletionReference = match fields.get("ref") {
    Some(ref_val) => @json.from_json(ref_val, path=path.add_key("ref"))
    None => raise JsonDecodeError((path.add_key("ref"), "ref is required"))
  }
  let argument = match fields.get("argument") {
    Some(Object(arg_fields)) => arg_fields
    Some(_) =>
      raise JsonDecodeError((path.add_key("argument"), "expected object"))
    None =>
      raise JsonDecodeError((path.add_key("argument"), "argument is required"))
  }
  let argument_name = match argument.get("name") {
    Some(String(s)) => s
    Some(_) =>
      raise JsonDecodeError(
        (path.add_key("argument").add_key("name"), "expected string"),
      )
    None =>
      raise JsonDecodeError(
        (path.add_key("argument").add_key("name"), "name is required"),
      )
  }
  let argument_value = match argument.get("value") {
    Some(String(s)) => s
    Some(_) =>
      raise JsonDecodeError(
        (path.add_key("argument").add_key("value"), "expected string"),
      )
    None =>
      raise JsonDecodeError(
        (path.add_key("argument").add_key("value"), "value is required"),
      )
  }
  let context_arguments : Map[String, String] = match fields.get("context") {
    Some(Object(ctx_fields)) =>
      match ctx_fields.get("arguments") {
        Some(Object(args_fields)) => {
          let result : Map[String, String] = Map([])
          for key, val in args_fields {
            match val {
              String(s) => result[key] = s
              _ =>
                raise JsonDecodeError(
                  (
                    path.add_key("context").add_key("arguments").add_key(key),
                    "expected string",
                  ),
                )
            }
          }
          result
        }
        Some(Null) | None => Map([])
        Some(_) =>
          raise JsonDecodeError(
            (path.add_key("context").add_key("arguments"), "expected object"),
          )
      }
    Some(Null) | None => Map([])
    Some(_) =>
      raise JsonDecodeError((path.add_key("context"), "expected object"))
  }
  let meta = match fields.get("_meta") {
    Some(v) => Some(v.copy())
    None => None
  }
  { reference, argument_name, argument_value, context_arguments, meta }
}

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

///|
pub struct CompleteResult {
  values : Array[String]
  total : Int?
  has_more : Bool
  meta : ResultMeta?
} derive(Debug, Eq)

///|
pub fn CompleteResult::CompleteResult(
  values~ : Array[String],
  total? : Int,
  has_more? : Bool = false,
  meta? : ResultMeta,
) -> CompleteResult raise McpError {
  if values.length() > 100 {
    raise InvalidParams("completion values must not exceed 100 items")
  }
  if total is Some(value) && value < 0 {
    raise InvalidParams("completion total must be non-negative")
  }
  { values: values.copy(), total, has_more, meta: meta.map(fn(m) { m.copy() }) }
}

///|
pub impl ToJson for CompleteResult with fn to_json(self : CompleteResult) -> Json {
  let completion : Map[String, Json] = {
    "values": self.values.map(fn(value) { Json::string(value) }).to_json(),
    "hasMore": Json::boolean(self.has_more),
  }
  if self.total is Some(total) {
    completion["total"] = Json::number(total.to_double())
  }
  let fields : Map[String, Json] = { "completion": Json::object(completion) }
  if self.meta is Some(meta) {
    fields["_meta"] = ToJson::to_json(meta)
  }
  Json::object(fields)
}

///|
pub impl @json.FromJson for CompleteResult with fn from_json(
  value : Json,
  path : @json.JsonPath,
) -> CompleteResult raise {
  guard value is Object(fields) else {
    raise JsonDecodeError((path, "expected object"))
  }
  let completion_val = match fields.get("completion") {
    Some(Object(c)) => c
    Some(_) =>
      raise JsonDecodeError((path.add_key("completion"), "expected object"))
    None =>
      raise JsonDecodeError(
        (path.add_key("completion"), "completion is required"),
      )
  }
  let values_val = match completion_val.get("values") {
    Some(Array(arr)) => arr
    Some(_) =>
      raise JsonDecodeError(
        (path.add_key("completion").add_key("values"), "expected array"),
      )
    None =>
      raise JsonDecodeError(
        (path.add_key("completion").add_key("values"), "values is required"),
      )
  }
  let values : Array[String] = []
  for i, it in values_val {
    match it {
      String(s) => values.push(s)
      _ =>
        raise JsonDecodeError(
          (
            path.add_key("completion").add_key("values").add_index(i),
            "expected string",
          ),
        )
    }
  }
  let total = match completion_val.get("total") {
    Some(Number(n, ..)) => {
      if n < 0.0 || n != n.floor() {
        raise JsonDecodeError(
          (
            path.add_key("completion").add_key("total"),
            "expected non-negative integer",
          ),
        )
      }
      Some(n.to_int())
    }
    Some(Null) | None => None
    Some(_) =>
      raise JsonDecodeError(
        (path.add_key("completion").add_key("total"), "expected number"),
      )
  }
  let has_more = match completion_val.get("hasMore") {
    Some(True) => true
    Some(False) | Some(Null) | None => false
    Some(_) =>
      raise JsonDecodeError(
        (path.add_key("completion").add_key("hasMore"), "expected boolean"),
      )
  }
  let meta : ResultMeta? = match fields.get("_meta") {
    Some(v) => Some(@json.from_json(v, path=path.add_key("_meta")))
    None => None
  }
  CompleteResult(values~, total?, has_more~, meta?) catch {
    error => raise JsonDecodeError((path, error.to_string()))
  }
}

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