///|
pub struct PromptArgument {
  name : String
  title : String?
  description : String?
  required : Bool
} derive(Debug, Eq)

///|
/// A typed prompt declares the arguments it accepts through this trait.
pub(open) trait PromptArguments {
  fn prompt_arguments() -> Array[PromptArgument]
}

///|
/// Construct a prompt argument descriptor.
pub fn PromptArgument::PromptArgument(
  name~ : String,
  title? : String,
  description? : String,
  required? : Bool = false,
) -> PromptArgument {
  { name, title, description, required }
}

///|
fn validate_prompt_argument_names(
  arguments : Array[PromptArgument],
  context : String,
) -> Unit raise McpError {
  let names : Map[String, Unit] = Map([])
  for argument in arguments {
    if names.contains(argument.name) {
      raise InvalidParams(
        "\{context} contains duplicate argument name: \{argument.name}",
      )
    }
    names[argument.name] = ()
  }
}

///|
pub impl ToJson for PromptArgument with fn to_json(self : PromptArgument) -> Json {
  let fields : Map[String, Json] = { "name": Json::string(self.name) }
  fields.set_opt("title", self.title.map(Json::string))
  fields.set_opt("description", self.description.map(Json::string))
  if self.required {
    fields["required"] = Json::boolean(true)
  }
  Json::object(fields)
}

///|
pub impl @json.FromJson for PromptArgument with fn from_json(
  value : Json,
  path : @json.JsonPath,
) -> PromptArgument raise {
  guard value is Object(fields) else {
    raise JsonDecodeError((path, "expected object"))
  }
  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 required = match fields.get("required") {
    Some(True) => true
    Some(False | Null) | None => false
    Some(_) =>
      raise JsonDecodeError((path.add_key("required"), "expected boolean"))
  }
  PromptArgument(name~, title?, description?, required~)
}

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

///|
pub struct Prompt {
  name : String
  title : String?
  description : String?
  arguments : Array[PromptArgument]
  icons : Array[Icon]
  meta : Map[String, Json]
} derive(Debug, Eq)

///|
pub fn Prompt::Prompt(
  name~ : String,
  title? : String,
  description? : String,
  arguments? : Array[PromptArgument] = [],
  icons? : Array[Icon] = [],
  meta? : Map[String, Json] = {},
) -> Prompt raise McpError {
  if name.is_empty() {
    raise InvalidParams("prompt name must not be empty")
  }
  validate_prompt_argument_names(arguments, "prompt \{name}")
  validate_meta_map(meta)
  {
    name,
    title,
    description,
    arguments: arguments.copy(),
    icons: icons.map(icon => icon.copy()),
    meta: meta.copy_json(),
  }
}

///|
pub impl ToJson for Prompt with fn to_json(self : Prompt) -> Json {
  let fields : Map[String, Json] = { "name": Json::string(self.name) }
  fields.set_opt("title", self.title.map(Json::string))
  fields.set_opt("description", self.description.map(Json::string))
  if !self.arguments.is_empty() {
    fields["arguments"] = self.arguments
      .map(fn(arg) { ToJson::to_json(arg) })
      .to_json()
  }
  if !self.icons.is_empty() {
    fields["icons"] = self.icons
      .map(fn(icon) { ToJson::to_json(icon) })
      .to_json()
  }
  if !self.meta.is_empty() {
    fields["_meta"] = Json::object(self.meta.copy_json())
  }
  Json::object(fields)
}

///|
pub impl @json.FromJson for Prompt with fn from_json(
  value : Json,
  path : @json.JsonPath,
) -> Prompt raise {
  guard value is Object(fields) else {
    raise JsonDecodeError((path, "expected object"))
  }
  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 arguments : Array[PromptArgument] = match fields.get("arguments") {
    Some(Array(arr)) => {
      let result : Array[PromptArgument] = []
      for i, it in arr {
        result.push(
          @json.from_json(it, path=path.add_key("arguments").add_index(i)),
        )
      }
      result
    }
    Some(Null) | None => []
    Some(_) =>
      raise JsonDecodeError((path.add_key("arguments"), "expected array"))
  }
  validate_prompt_argument_names(arguments, "prompt.arguments") catch {
    error => raise JsonDecodeError((path.add_key("arguments"), error.message()))
  }
  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 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"))
  }
  Prompt(name~, title?, description?, arguments~, icons~, meta~) catch {
    error => raise JsonDecodeError((path, error.message()))
  }
}

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

///|
pub struct PromptMessage {
  role : Role
  content : Content
} derive(Debug, Eq)

///|
pub fn PromptMessage::PromptMessage(
  role~ : Role,
  content~ : Content,
) -> PromptMessage {
  { role, content }
}

///|
pub fn PromptMessage::user(text : String) -> PromptMessage {
  PromptMessage(role=User, content=Content::text(text))
}

///|
pub fn PromptMessage::assistant(text : String) -> PromptMessage {
  PromptMessage(role=Assistant, content=Content::text(text))
}

///|
pub impl ToJson for PromptMessage with fn to_json(self : PromptMessage) -> Json {
  Json::object({
    "role": ToJson::to_json(self.role),
    "content": ToJson::to_json(self.content),
  })
}

///|
pub impl @json.FromJson for PromptMessage with fn from_json(
  value : Json,
  path : @json.JsonPath,
) -> PromptMessage raise {
  guard value is Object(fields) else {
    raise JsonDecodeError((path, "expected object"))
  }
  guard fields is { "role": role_val, .. } else {
    raise JsonDecodeError((path.add_key("role"), "role is required"))
  }
  let role : Role = @json.from_json(role_val, path=path.add_key("role"))
  guard fields is { "content": content_val, .. } else {
    raise JsonDecodeError((path.add_key("content"), "content is required"))
  }
  let content : Content = @json.from_json(
    content_val,
    path=path.add_key("content"),
  )
  PromptMessage(role~, content~)
}

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

///|
pub struct GetPromptResult {
  description : String?
  messages : Array[PromptMessage]
  meta : ResultMeta?
} derive(Debug, Eq)

///|
/// Construct a prompt result and retain an owned message array.
pub fn GetPromptResult::GetPromptResult(
  messages~ : Array[PromptMessage],
  description? : String,
  meta? : ResultMeta,
) -> GetPromptResult {
  { description, messages: messages.copy(), meta: meta.map(fn(m) { m.copy() }) }
}

///|
pub impl ToJson for GetPromptResult with fn to_json(self : GetPromptResult) -> Json {
  let fields : Map[String, Json] = {
    "messages": self.messages
    .map(fn(message) { ToJson::to_json(message) })
    .to_json(),
  }
  fields.set_opt("description", self.description.map(Json::string))
  if self.meta is Some(meta) {
    fields["_meta"] = ToJson::to_json(meta)
  }
  Json::object(fields)
}

///|
pub impl @json.FromJson for GetPromptResult with fn from_json(
  value : Json,
  path : @json.JsonPath,
) -> GetPromptResult raise {
  guard value is Object(fields) else {
    raise JsonDecodeError((path, "expected object"))
  }
  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 messages : Array[PromptMessage] = match fields.get("messages") {
    Some(Array(arr)) => {
      let result : Array[PromptMessage] = []
      for i, it in arr {
        result.push(
          @json.from_json(it, path=path.add_key("messages").add_index(i)),
        )
      }
      result
    }
    Some(_) =>
      raise JsonDecodeError((path.add_key("messages"), "expected array"))
    None =>
      raise JsonDecodeError((path.add_key("messages"), "messages is required"))
  }
  let meta : ResultMeta? = match fields.get("_meta") {
    Some(v) => Some(@json.from_json(v, path=path.add_key("_meta")))
    None => None
  }
  GetPromptResult(messages~, description?, meta?)
}

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

///|
/// Construct a single user prompt message result.
pub fn GetPromptResult::user(
  text : String,
  description? : String,
) -> GetPromptResult {
  GetPromptResult(messages=[PromptMessage::user(text)], description?)
}

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

///|
pub impl ToJson for ListPromptsResult with fn to_json(self : ListPromptsResult) -> Json {
  let fields : Map[String, Json] = {
    "resultType": ToJson::to_json(ResultType::Complete),
    "prompts": self.prompts.map(fn(p) { ToJson::to_json(p) }).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 ListPromptsResult with fn from_json(
  value : Json,
  path : @json.JsonPath,
) -> ListPromptsResult raise {
  guard value is Object(fields) else {
    raise JsonDecodeError((path, "expected object"))
  }
  let prompts : Array[Prompt] = match fields.get("prompts") {
    Some(Array(arr)) => {
      let result : Array[Prompt] = []
      for i, it in arr {
        result.push(
          @json.from_json(it, path=path.add_key("prompts").add_index(i)),
        )
      }
      result
    }
    Some(_) =>
      raise JsonDecodeError((path.add_key("prompts"), "expected array"))
    None =>
      raise JsonDecodeError((path.add_key("prompts"), "prompts 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
  }
  { prompts, next_cursor, cache_policy, meta }
}

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

///|
/// Parameters for `prompts/get`.
pub struct GetPromptParams {
  name : String
  arguments : Map[String, String]
} derive(Debug, Eq)

///|
pub fn GetPromptParams::GetPromptParams(
  name~ : String,
  arguments? : Map[String, String] = Map([]),
) -> GetPromptParams raise McpError {
  guard !name.is_empty() else {
    raise InvalidParams("prompt name must not be empty")
  }
  { name, arguments }
}

///|
pub impl @json.FromJson for GetPromptParams with fn from_json(
  value : Json,
  path : @json.JsonPath,
) -> GetPromptParams raise {
  guard value is Object(fields) else {
    raise JsonDecodeError((path, "expected object"))
  }
  let name = match fields.get("name") {
    Some(String(name)) if !name.is_empty() => name
    Some(String(_)) =>
      raise JsonDecodeError(
        (path.add_key("name"), "prompt name must not be empty"),
      )
    Some(_) => raise JsonDecodeError((path.add_key("name"), "expected string"))
    None => raise JsonDecodeError((path.add_key("name"), "name is required"))
  }
  let arguments : Map[String, String] = match fields.get("arguments") {
    Some(Object(map)) => {
      let result : Map[String, String] = Map([])
      for k, v in map {
        match v {
          String(s) => result[k] = s
          _ =>
            raise JsonDecodeError(
              (path.add_key("arguments").add_key(k), "expected string"),
            )
        }
      }
      result
    }
    Some(Null) | None => Map([])
    Some(_) =>
      raise JsonDecodeError((path.add_key("arguments"), "expected object"))
  }
  GetPromptParams(name~, arguments~) catch {
    error => raise JsonDecodeError((path, error.message()))
  }
}

///|
pub impl ToJson for GetPromptParams with fn to_json(self : GetPromptParams) -> Json {
  let fields : Map[String, Json] = { "name": Json::string(self.name) }
  if !self.arguments.is_empty() {
    let args : Map[String, Json] = Map([])
    for k, v in self.arguments {
      args[k] = Json::string(v)
    }
    fields["arguments"] = Json::object(args)
  }
  Json::object(fields)
}

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