///|
/// Lets the model decide whether to call a tool.
pub fn tool_choice_auto(disable_parallel_tool_use? : Bool) -> ToolChoice {
  @gen.ToolChoice::Auto(
    @gen.ToolChoiceAuto::new(type_="auto", disable_parallel_tool_use?),
  )
}

///|
/// Requires the model to call some tool.
pub fn tool_choice_any(disable_parallel_tool_use? : Bool) -> ToolChoice {
  @gen.ToolChoice::Any(
    @gen.ToolChoiceAny::new(type_="any", disable_parallel_tool_use?),
  )
}

///|
/// Requires the model to call the named tool.
pub fn tool_choice_tool(
  name : String,
  disable_parallel_tool_use? : Bool,
) -> ToolChoice {
  @gen.ToolChoice::Tool(
    @gen.ToolChoiceTool::new(name~, type_="tool", disable_parallel_tool_use?),
  )
}

///|
/// Forbids tool calls.
pub fn tool_choice_none() -> ToolChoice {
  @gen.ToolChoice::None(@gen.ToolChoiceNone::new(type_="none"))
}

///|
/// Reconstructs streamed tool-use inputs by content block index.
///
/// ```mbt check
/// test {
///   let tools = @anthropic.ToolUseAccumulator::new()
///   tools.feed(
///     @anthropic.ContentBlockStart(
///       index=0,
///       block=@anthropic.ToolUse(id="toolu_1", name="lookup", input={}),
///     ),
///   )
///   tools.feed(@anthropic.InputJsonDelta(index=0, partial_json="{}"))
///   tools.feed(@anthropic.ContentBlockStop(index=0))
///   assert_eq(tools.finish()[0].2, {})
/// }
/// ```
pub struct ToolUseAccumulator {
  priv calls : Array[PendingToolUse]
}

///|
/// Creates an empty tool-use accumulator.
pub fn ToolUseAccumulator::new() -> ToolUseAccumulator {
  { calls: [], }
}

///|
/// Consumes one message event. Invalid partial JSON is retained without raising.
pub fn ToolUseAccumulator::feed(
  self : ToolUseAccumulator,
  event : MessageEvent,
) -> Unit {
  match event {
    ContentBlockStart(index~, block=ToolUse(id~, name~, ..)) => {
      let pending : PendingToolUse = {
        index,
        id,
        name,
        arguments: StringBuilder(),
        complete: false,
      }
      match self.calls.search_by(call => call.index == index) {
        Some(position) => self.calls[position] = pending
        None => self.calls.push(pending)
      }
    }
    InputJsonDelta(index~, partial_json~) =>
      if self.calls.search_by(call => call.index == index) is Some(position) {
        self.calls[position].arguments.write_string(partial_json)
      }
    ContentBlockStop(index~) =>
      if self.calls.search_by(call => call.index == index) is Some(position) {
        self.calls[position].complete = true
      }
    _ => ()
  }
}

///|
/// Returns completed tool uses ordered by block index.
/// Invalid accumulated JSON raises `@json.ParseError` here rather than from `feed`.
pub fn ToolUseAccumulator::finish(
  self : ToolUseAccumulator,
) -> Array[(String, String, Json)] raise @json.ParseError {
  let pending = self.calls.filter(call => call.complete)
  pending.sort_by((left, right) => left.index - right.index)
  pending.map(call => {
    (call.id, call.name, @json.parse(call.arguments.to_string()))
  })
}

///|
priv struct PendingToolUse {
  index : Int
  id : String
  name : String
  arguments : StringBuilder
  mut complete : Bool
}