/// Tool base classes mirroring fastmcp/tools/base.py.
///
/// Python's subclass-based dispatch (FunctionTool, TransformedTool, provider
/// tools overriding `run`) maps to a struct carrying a run closure: every
/// concrete tool in fastmcp is ultimately function-backed.

///|
/// Derive a display title from a tool name.
///
/// The MCP spec says clients should fall back to `name` for display when
/// `title` is absent, but some clients instead drop the tool entirely. Always
/// emitting a title avoids depending on that fallback.
pub fn default_title(name : String) -> String {
  let buf = StringBuilder::StringBuilder()
  let mut cap_next = true
  for ch in name {
    if ch == '_' || ch == '-' {
      buf.write_char(' ')
      cap_next = true
    } else if cap_next {
      buf.write_char(ch.to_ascii_uppercase())
      cap_next = false
    } else {
      buf.write_char(ch)
    }
  }
  buf.to_string()
}

///|
/// Whether a tool name is valid per the MCP specification (SEP-986):
/// 1-64 characters of letters, digits, underscores and hyphens.
pub fn is_valid_tool_name(name : String) -> Bool {
  if name.is_empty() || name.length() > 64 {
    return false
  }
  for ch in name {
    let valid = (ch >= 'a' && ch <= 'z') ||
      (ch >= 'A' && ch <= 'Z') ||
      (ch >= '0' && ch <= '9') ||
      ch == '_' ||
      ch == '-'
    if !valid {
      return false
    }
  }
  true
}

///|
/// The canonical result of running a tool.
pub(all) struct ToolResult {
  content : Array[@types.ContentBlock]
  structured_content : Map[String, Json]?
  meta : Map[String, Json]?
  is_error : Bool
  /// Preserved wire representation when this result wraps a protocol result.
  raw_mcp_result : @types.CallToolResult?
  /// The client-input request when this leg resolved to an input ask
  /// (SEP-2322); `content` is always empty in that case.
  input_required : @types.InputRequiredResult?
} derive(Debug, Eq)

///|
/// Create a tool result from explicit parts; content or structured content
/// must be provided.
pub fn ToolResult::new(
  content? : Array[@types.ContentBlock],
  structured_content? : Map[String, Json],
  meta? : Map[String, Json],
  is_error~ : Bool = false,
) -> ToolResult raise @exceptions.FastMCPError {
  let content = match content {
    Some(blocks) => blocks
    None =>
      match structured_content {
        Some(_) => []
        None =>
          raise @exceptions.Validation(
            "Either content or structured_content must be provided",
          )
      }
  }
  {
    content,
    structured_content,
    meta,
    is_error,
    raw_mcp_result: None,
    input_required: None,
  }
}

///|
/// Wrap a protocol result while preserving its exact wire representation.
pub fn ToolResult::from_mcp_result(
  result : @types.CallToolResult,
) -> ToolResult {
  {
    content: result.content,
    structured_content: result.structured_content,
    meta: result.meta,
    is_error: result.is_error,
    raw_mcp_result: Some(result),
    input_required: None,
  }
}

///|
/// Wrap a client-input ask (SEP-2322) as an ordinary tool result.
pub fn ToolResult::input_required(
  input_required : @types.InputRequiredResult,
) -> ToolResult {
  {
    content: [],
    structured_content: None,
    meta: None,
    is_error: false,
    raw_mcp_result: None,
    input_required: Some(input_required),
  }
}

///|
/// Whether this result carries a client-input ask (SEP-2322).
pub fn ToolResult::is_input_required(self : ToolResult) -> Bool {
  self.input_required is Some(_)
}

///|
/// Convert to the protocol result, keeping the original wire representation
/// when one was preserved.
pub fn ToolResult::to_mcp_result(self : ToolResult) -> @types.CallToolResult {
  match self.raw_mcp_result {
    Some(raw) => raw
    None =>
      {
        content: self.content,
        structured_content: self.structured_content,
        is_error: self.is_error,
        cache_control: None,
        meta: self.meta,
      }
  }
}

///|
/// A registered tool.
pub(all) struct Tool {
  component : @component.ComponentBase
  /// JSON schema for the tool's input parameters.
  parameters : Json
  /// JSON schema for the tool's structured output.
  output_schema : Json?
  annotations : @types.ToolAnnotations?
  /// Task execution configuration (SEP-1686).
  execution : @types.ToolExecution?
  /// Execution timeout in seconds; `None` applies no timeout.
  timeout : Double?
  run_fn : async (Map[String, Json]) -> ToolResult raise @exceptions.FastMCPError
}

///|
pub impl @component.Component for Tool with fn base(self) {
  self.component
}

///|
pub impl @component.Component for Tool with fn identifier(self) {
  self.component.name
}

///|
pub impl @component.Component for Tool with fn key(self) {
  self.component.key(self.component.name)
}

///|
pub impl @component.Component for Tool with fn get_meta(self) {
  self.component.get_meta()
}

///|
/// Register a tool. `parameters` is the JSON schema of its arguments and
/// `run_fn` executes it.
pub fn Tool::new(
  name : String,
  parameters~ : Json,
  run_fn~ : async (Map[String, Json]) -> ToolResult raise @exceptions.FastMCPError,
  version? : String,
  title? : String,
  description? : String,
  icons? : Array[@types.Icon],
  tags~ : @set.Set[String] = @set.Set::from_array([]),
  meta? : Map[String, Json],
  annotations? : @types.ToolAnnotations,
  output_schema? : Json,
  execution? : @types.ToolExecution,
  timeout? : Double,
  task_config~ : @tasks.TaskConfig = @tasks.TaskConfig::new(
    mode=@tasks.Forbidden,
  ),
) -> Tool raise @exceptions.FastMCPError {
  if !is_valid_tool_name(name) {
    raise @exceptions.Validation(
      "Invalid tool name '\{name}': must be 1-64 characters of letters, digits, underscores and hyphens",
    )
  }
  {
    component: @component.ComponentBase::new(
      "tool",
      name,
      version?,
      title?,
      description?,
      icons?,
      tags~,
      meta?,
      task_config~,
    ),
    parameters,
    output_schema,
    annotations,
    execution,
    timeout,
    run_fn,
  }
}

///|
/// Run the tool with arguments.
pub async fn Tool::run(
  self : Tool,
  arguments : Map[String, Json],
) -> ToolResult raise @exceptions.FastMCPError {
  (self.run_fn)(arguments)
}

///|
/// Server entry point for tool execution; subclasses customise dispatch by
/// supplying a different `run_fn` at construction.
pub async fn Tool::dispatch(
  self : Tool,
  arguments : Map[String, Json],
) -> ToolResult raise @exceptions.FastMCPError {
  self.run(arguments)
}

///|
/// Convert the tool to its wire representation.
///
/// Title precedence: explicit title, then annotation title, then a title
/// derived from the name.
pub fn Tool::to_mcp_tool(self : Tool) -> @types.Tool {
  let title = match self.component.title {
    Some(title) => Some(title)
    None =>
      match self.annotations {
        Some(annotations) => annotations.title
        None => None
      }
  }
  let title = Some(title.unwrap_or(default_title(self.component.name)))
  let mut execution = self.execution
  if self.component.task_config.supports_tasks() && execution is None {
    execution = Some({ task_support: self.component.task_config.mode, })
  }
  {
    name: self.component.name,
    title,
    description: self.component.description,
    input_schema: self.parameters,
    output_schema: self.output_schema,
    icons: self.component.icons,
    annotations: self.annotations,
    execution,
    meta: Some(Tool::get_meta(self)),
  }
}

///|
/// Convert a raw return value to a `ToolResult`, honouring the declared
/// output schema.
pub fn Tool::convert_result(
  self : Tool,
  raw_value : Json,
) -> ToolResult raise @exceptions.FastMCPError {
  let content : Array[@types.ContentBlock] = convert_to_content(raw_value) catch {
    _ => raise @exceptions.Tool("Could not convert tool result")
  }
  let is_content = is_content_result(raw_value)
  // Content-block results skip structured content unless an output schema
  // obliges it.
  if self.output_schema is None && is_content {
    return ToolResult::new(content~)
  }
  guard raw_value is Object(structured) else {
    // A non-object value cannot back structured content.
    if self.output_schema is Some(_) {
      raise @exceptions.Tool(
        "Could not serialise the return value of tool '\{self.component.name}' for its output schema",
      )
    }
    return ToolResult::new(content~)
  }
  if self.output_schema is None {
    // No schema: structured content only for objects.
    return ToolResult::new(content~, structured_content=structured)
  }
  // Has output schema: wrap when x-fastmcp-wrap-result is set.
  let wrap_result = match self.output_schema {
    Some(Object(schema)) =>
      schema.get("x-fastmcp-wrap-result") == Some(Json::boolean(true))
    _ => false
  }
  if wrap_result {
    let wrapped : Map[String, Json] = { "result": Json(structured) }
    let fastmcp : Map[String, Json] = { "wrap_result": Json::boolean(true) }
    let meta : Map[String, Json] = { "fastmcp": Json(fastmcp) }
    ToolResult::new(content~, structured_content=wrapped, meta~)
  } else {
    ToolResult::new(content~, structured_content=structured)
  }
}

///|
pub extend Tool with @component.Component::{base, identifier, key, get_meta}

///|
pub extend ToolResult with Debug::{to_repr}

///|
pub extend ToolResult with Eq::{not_equal, equal}