///|
pub struct ToolDesc {
  description : String
  name : String
  schema : JsonSchema
} derive(ToJson, Eq, Debug)

///|
pub fn ToolDesc::to_openai(
  tool_desc : ToolDesc,
) -> @openai.ChatCompletionToolParam {
  @openai.tool(
    name=tool_desc.name,
    description=tool_desc.description,
    parameters=tool_desc.schema.to_json(),
  )
}

///|
pub fn tool_desc(
  description~ : String,
  name~ : String,
  schema~ : JsonSchema,
) -> ToolDesc {
  { description, name, schema }
}

///|
pub struct Tool[Output] {
  desc : ToolDesc
  priv f : ToolFn[Output]
}

///|
pub(all) struct ToolFn[Output](async (Json) -> ToolResult[Output] noraise)

///|
pub enum ToolResult[Output] {
  Ok(Output)
  Error(Error, String)
} derive(ToJson)

///|
#as_free_fn
pub fn[Output] ToolResult::ok(output : Output) -> ToolResult[Output] {
  Ok(output)
}

///|
pub impl[Output : Show] Show for ToolResult[Output] with fn output(
  self : ToolResult[Output],
  logger : &Logger,
) -> Unit {
  match self {
    Ok(output) => logger.write_string(output.to_string())
    Error(ToolFailure(_), message) => logger.write_string(message)
    Error(error, message) =>
      logger.write_string("\{message}\n\{error.to_json().stringify(indent=2)}")
  }
}

///|
priv suberror ToolFailure {
  ToolFailure(String)
} derive(ToJson)

///|
#as_free_fn
pub fn[Output] ToolResult::error(
  output : String,
  error? : Error = ToolFailure(output),
) -> ToolResult[Output] {
  Error(error, output)
}

///|
/// 
/// - `description`: Tool description that can be read by the LLM.
/// - `name`: Tool name used by the LLM to call the tool.
/// - `schema`: JSON schema to validate LLM-generated output and provide type information to the LLM.
///             `schema` should be an *object type*. Related issue: [#258](https://github.com/vectie/moonclaw/issues/258)
/// - `f`: Function whose `input` must conform to the `schema` defined above.
///
#as_free_fn
pub fn[Output] Tool::new(
  description~ : String,
  name~ : String,
  schema~ : JsonSchema,
  f : ToolFn[Output],
) -> Tool[Output] {
  { desc: { description, name, schema }, f }
}

///|
// TODO(upstream): `as_free_fn` completion not need parens 
#as_free_fn
pub async fn[Output] Tool::call(
  tool : Tool[Output],
  args : Json,
) -> ToolResult[Output] noraise {
  (tool.f)(args)
}

///|
struct AgentTool(Tool[(Json, String)])

///|
pub fn AgentTool::desc(self : AgentTool) -> ToolDesc {
  self.0.desc
}

///|
pub async fn AgentTool::call(
  self : AgentTool,
  args : Json,
) -> ToolResult[(Json, String)] noraise {
  self.0.call(args)
}

///|
/// Convert a Tool[Output] to Tool[Json] by converting outputs to Json
pub fn[Output : ToJson] Tool::to_agent_tool(self : Tool[Output]) -> AgentTool {
  {
    desc: self.desc,
    f: args => {
      match self.call(args) {
        Ok(output) => {
          let json = output.to_json()
          Ok((json, json.stringify()))
        }
        Error(error, output) => Error(error, output)
      }
    },
  }
}