///|
/// Behavioral hints about a tool (all are advisory, not enforced).
pub(all) struct ToolAnnotations {
  title : String?
  read_only_hint : Bool
  destructive_hint : Bool
  idempotent_hint : Bool
  open_world_hint : Bool
} derive(Debug, Eq)

///|
/// Default tool annotations.
pub fn ToolAnnotations::new() -> ToolAnnotations {
  {
    title: None,
    read_only_hint: false,
    destructive_hint: true,
    idempotent_hint: false,
    open_world_hint: true,
  }
}

///|
/// A tool definition registered on an MCP server.
pub(all) struct Tool {
  name : String
  mut title : String?
  mut description : String?
  input_schema : JsonSchema
  mut output_schema : JsonSchema?
  mut annotations : ToolAnnotations?
} derive(Debug, Eq)

///|
/// Create a new tool with the given name and input schema.
pub fn Tool::new(name : String, schema : JsonSchema) -> Tool {
  {
    name,
    title: None,
    description: None,
    input_schema: schema,
    output_schema: None,
    annotations: None,
  }
}

///|
/// Set the tool's human-readable description.
pub fn Tool::describe(self : Tool, desc : String) -> Tool {
  self.description = Some(desc)
  self
}

///|
/// Encode a [Tool] to its JSON representation (for tools/list responses).
pub fn Tool::to_json(self : Tool) -> Json {
  let obj : Map[String, Json] = Map([])
  obj["name"] = Json::string(self.name)
  match self.title {
    Some(t) => obj["title"] = Json::string(t)
    None => ()
  }
  match self.description {
    Some(d) => obj["description"] = Json::string(d)
    None => ()
  }
  obj["inputSchema"] = self.input_schema.to_json()
  match self.output_schema {
    Some(s) => obj["outputSchema"] = s.to_json()
    None => ()
  }
  match self.annotations {
    Some(ann) => {
      let ann_obj : Map[String, Json] = Map([])
      ann_obj["readOnlyHint"] = if ann.read_only_hint {
        Json::boolean(true)
      } else {
        Json::boolean(false)
      }
      ann_obj["destructiveHint"] = if ann.destructive_hint {
        Json::boolean(true)
      } else {
        Json::boolean(false)
      }
      ann_obj["idempotentHint"] = if ann.idempotent_hint {
        Json::boolean(true)
      } else {
        Json::boolean(false)
      }
      ann_obj["openWorldHint"] = if ann.open_world_hint {
        Json::boolean(true)
      } else {
        Json::boolean(false)
      }
      obj["annotations"] = Json::object(ann_obj)
    }
    None => ()
  }
  Json::object(obj)
}

///|
/// Result of calling a tool.
pub(all) struct CallToolResult {
  content : Array[ContentBlock]
  structured_content : Json?
  is_error : Bool
} derive(Debug, Eq)

///|
/// Construct a successful text result.
pub fn CallToolResult::text(text : String) -> CallToolResult {
  { content: [text_content(text)], structured_content: None, is_error: false }
}

///|
/// Construct an error result (business error — the tool ran but failed).
pub fn CallToolResult::error(text : String) -> CallToolResult {
  { content: [text_content(text)], structured_content: None, is_error: true }
}

///|
/// Encode [CallToolResult] to JSON.
pub fn CallToolResult::to_json(self : CallToolResult) -> Json {
  let obj : Map[String, Json] = Map([])
  obj["content"] = content_blocks_to_json(self.content)
  match self.structured_content {
    Some(sc) => obj["structuredContent"] = sc
    None => ()
  }
  obj["isError"] = if self.is_error {
    Json::boolean(true)
  } else {
    Json::boolean(false)
  }
  Json::object(obj)
}

///|
/// Encode an array of tools for a tools/list response.
pub fn tools_to_json(tools : Array[Tool]) -> Json {
  Json::array(tools.map(fn(t) { t.to_json() }))
}