///|
/// Why the model stopped generating, as in the `stop_reason` of a
/// `MessageResponse`.
pub using @gen {type StopReason}

///|
/// Token counts billed for a message.
pub using @gen {type Usage}

///|
/// A model as `list_models` and `retrieve_model` return it.
pub using @gen {type ModelInfo}

///|
/// One page of `list_models`.
pub using @gen {type ListResponseModelInfo}

///|
/// A model lifecycle stage that `list_models` can filter by.
pub using @gen {type ModelsListLifecycleItem}

///|
/// A content block of an input message: text, image, document, tool use or
/// result, and others.
pub using @gen {type InputContentBlock}

///|
/// How the model may use the request's tools; the `tool_choice_*` helpers build
/// the common ones.
pub using @gen {type ToolChoice}

///|
/// Whether and how the model thinks before answering.
pub using @gen {type ThinkingConfigParam}

///|
/// Output settings of a request: the effort level and a structured output
/// format.
pub using @gen {type OutputConfig}

///|
/// Request metadata: an opaque id of the end user.
pub using @gen {type Metadata}

///|
/// The top-level changes in a `message_delta` stream event, such as the stop
/// reason.
pub using @gen {type MessageDelta}

///|
/// The cumulative token counts in a `message_delta` stream event.
pub using @gen {type MessageDeltaUsage}

///|
/// The full Messages request, for `create_message_params` and
/// `stream_message_params`.
pub using @gen {type CreateMessageParams}

///|
/// A raw Messages stream event as the server sends it.
pub using @gen {type MessageStreamEvent}

///|
/// Selects the credential header used by the API.
#derive(Eq, Debug)
#non_exhaustive
pub(all) enum AuthHeader {
  XApiKey
  Bearer
}

///|
/// Compares authentication-header choices.
pub extend AuthHeader with Eq::{equal, not_equal}

///|
/// Debug representation of an authentication-header choice.
pub extend AuthHeader with @debug.Debug::{to_repr}

///|
/// The error body returned by the Anthropic API.
///
/// ```mbt check
/// test {
///   let error : @anthropic.ApiErrorBody = {
///     type_: "invalid_request_error",
///     message: "invalid request",
///   }
///   assert_eq(error.message, "invalid request")
/// }
/// ```
#derive(Eq, Debug)
pub(all) struct ApiErrorBody {
  type_ : String
  message : String
}

///|
/// Compares API error bodies field by field.
pub extend ApiErrorBody with Eq::{equal, not_equal}

///|
/// Debug representation of an API error body.
pub extend ApiErrorBody with @debug.Debug::{to_repr}

///|
/// A participant in a Messages API conversation. `System` is a
/// mid-conversation system message, which only some models accept and which
/// must directly follow a user turn; a system prompt for the whole
/// conversation goes in `MessageRequest.system`. A response is always
/// `Assistant`.
#derive(Eq, Debug)
#non_exhaustive
pub(all) enum Role {
  User
  Assistant
  System
}

///|
/// Compares message roles.
pub extend Role with Eq::{equal, not_equal}

///|
/// Debug representation of a message role.
pub extend Role with @debug.Debug::{to_repr}

///|
/// Input content as plain text or typed content blocks. A block the generated
/// vocabulary does not know is carried losslessly as
/// `@gen.InputContentBlock::Unknown`.
#derive(Eq, Debug)
#non_exhaustive
pub(all) enum InputContent {
  Text(String)
  Blocks(Array[InputContentBlock])
}

///|
/// Compares input content, including typed blocks.
pub extend InputContent with Eq::{equal, not_equal}

///|
/// Debug representation of input content.
pub extend InputContent with @debug.Debug::{to_repr}

///|
/// A client tool definition for the Messages API.
///
/// ```mbt check
/// test {
///   let tool = @anthropic.ToolDef::new(name="lookup", input_schema={
///     "type": "object",
///   })
///   assert_eq(tool.name, "lookup")
/// }
/// ```
#derive(Eq, Debug)
pub(all) struct ToolDef {
  name : String
  description : String?
  input_schema : Json
  strict : Bool?
}

///|
/// Compares tool definitions field by field.
pub extend ToolDef with Eq::{equal, not_equal}

///|
/// Debug representation of a tool definition.
pub extend ToolDef with @debug.Debug::{to_repr}

///|
/// Creates a client tool definition with a JSON Schema input object.
pub fn ToolDef::new(
  name~ : String,
  input_schema~ : Json,
  description? : String,
  strict? : Bool,
) -> ToolDef {
  { name, description, input_schema, strict, }
}

///|
/// One user, assistant or system turn sent to the Messages API.
#derive(Eq, Debug)
pub(all) struct Message {
  role : Role
  content : InputContent
}

///|
/// Compares messages field by field.
pub extend Message with Eq::{equal, not_equal}

///|
/// Debug representation of a message.
pub extend Message with @debug.Debug::{to_repr}

///|
/// Creates a user message containing plain text.
///
/// ```mbt check
/// test {
///   assert_eq(@anthropic.Message::user("hello"), {
///     role: @anthropic.User,
///     content: @anthropic.Text("hello"),
///   })
/// }
/// ```
pub fn Message::user(text : String) -> Message {
  { role: User, content: Text(text), }
}

///|
/// Creates an assistant message containing plain text.
///
/// ```mbt check
/// test {
///   assert_eq(@anthropic.Message::assistant("hello"), {
///     role: @anthropic.Assistant,
///     content: @anthropic.Text("hello"),
///   })
/// }
/// ```
pub fn Message::assistant(text : String) -> Message {
  { role: Assistant, content: Text(text), }
}

///|
/// Creates a mid-conversation system message containing plain text. Place it
/// right after a user turn; see `Role`.
///
/// ```mbt check
/// test {
///   assert_eq(@anthropic.Message::system("answer in French"), {
///     role: @anthropic.System,
///     content: @anthropic.Text("answer in French"),
///   })
/// }
/// ```
pub fn Message::system(text : String) -> Message {
  { role: System, content: Text(text), }
}

///|
/// Creates a user message from typed content blocks (images, documents, tool
/// results, ...).
pub fn Message::user_blocks(blocks : Array[InputContentBlock]) -> Message {
  { role: User, content: Blocks(blocks.copy()), }
}

///|
/// Creates an assistant message from typed content blocks.
pub fn Message::assistant_blocks(blocks : Array[InputContentBlock]) -> Message {
  { role: Assistant, content: Blocks(blocks.copy()), }
}

///|
/// Creates a user message containing one tool result block.
///
/// ```mbt check
/// test {
///   let message = @anthropic.Message::tool_result(
///     tool_use_id="toolu_1",
///     content="done",
///   )
///   assert_eq(message.role, @anthropic.User)
/// }
/// ```
pub fn Message::tool_result(
  tool_use_id~ : String,
  content~ : String,
  is_error? : Bool,
) -> Message {
  {
    role: User,
    content: Blocks([
      @gen.InputContentBlock::ToolResult(
        @gen.RequestToolResultBlock::new(
          tool_use_id~,
          type_="tool_result",
          content=@gen.RequestToolResultBlockContent::Text(content),
          is_error?,
        ),
      ),
    ]),
  }
}

///|
/// The `type` tag of a content block's JSON, or `""` when it has none.
fn block_type(json : Json) -> String {
  match json {
    Object(fields) =>
      match fields.get("type") {
        Some(String(kind)) => kind
        _ => ""
      }
    _ => ""
  }
}

///|
/// Turns a response into the assistant turn to send back, block for block:
/// text, thinking (with its signature), tool use and every other block the
/// API returned are replayed as input blocks. A block the request vocabulary
/// cannot represent is passed through as raw JSON.
pub fn Message::from_response(response : MessageResponse) -> Message {
  let blocks = response.message.content.map(block => {
    let json = block.to_json()
    let converted : InputContentBlock = @json.from_json(json) catch {
      _ => @gen.InputContentBlock::Unknown(block_type(json), json)
    }
    converted
  })
  { role: Assistant, content: Blocks(blocks), }
}

///|
/// Input accepted by buffered and streaming message creation and by token
/// counting.
///
/// The typed fields cover the common surface; `extra` is merged into the JSON
/// body after the typed fields and validated against the generated schema, so
/// any other documented parameter (`service_tier`, `inference_geo`, ...) can be passed
/// without a facade change. For full control build a `@gen.CreateMessageParams`
/// and use `Anthropic::create_message_params`.
#derive(Eq, Debug)
pub(all) struct MessageRequest {
  model : String
  max_tokens : Int32
  messages : Array[Message]
  system : String?
  stop_sequences : Array[String]?
  tools : Array[ToolDef]?
  tool_choice : ToolChoice?
  thinking : ThinkingConfigParam?
  output_config : OutputConfig?
  metadata : Metadata?
  extra : Map[String, Json]
}

///|
/// Compares message requests field by field.
pub extend MessageRequest with Eq::{equal, not_equal}

///|
/// Debug representation of a message request.
pub extend MessageRequest with @debug.Debug::{to_repr}

///|
/// Creates a message request and snapshots its mutable inputs.
///
/// ```mbt check
/// test {
///   let request = @anthropic.MessageRequest::new(
///     model="claude-opus-5",
///     max_tokens=64,
///     messages=[@anthropic.Message::user("hello")],
///   )
///   assert_eq(request.messages.length(), 1)
/// }
/// ```
pub fn MessageRequest::new(
  model~ : String,
  max_tokens~ : Int32,
  messages~ : Array[Message],
  system? : String,
  stop_sequences? : Array[String],
  tools? : Array[ToolDef],
  tool_choice? : ToolChoice,
  thinking? : ThinkingConfigParam,
  output_config? : OutputConfig,
  metadata? : Metadata,
  extra? : Map[String, Json],
) -> MessageRequest {
  {
    model,
    max_tokens,
    messages: messages.map(copy_message),
    system,
    stop_sequences: stop_sequences.map(Array::copy),
    tools: tools.map(Array::copy),
    tool_choice,
    thinking,
    output_config,
    metadata,
    extra: extra.map(Map::copy).unwrap_or({}),
  }
}

///|
fn copy_message(message : Message) -> Message {
  match message.content {
    Text(text) => { ..message, content: Text(text), }
    Blocks(blocks) => { ..message, content: Blocks(blocks.copy()), }
  }
}

///|
/// One output content block. Blocks beyond text, thinking and tool use keep
/// their JSON; the typed form is on `MessageResponse::message`.
#derive(Eq, Debug)
#non_exhaustive
pub(all) enum ContentBlock {
  Text(String)
  Thinking(thinking~ : String, signature~ : String)
  RedactedThinking(String)
  ToolUse(id~ : String, name~ : String, input~ : Json)
  Other(String, Json)
}

///|
/// Compares output content blocks.
pub extend ContentBlock with Eq::{equal, not_equal}

///|
/// Debug representation of an output content block.
pub extend ContentBlock with @debug.Debug::{to_repr}

///|
/// A decoded message: the facade view (`content`), the generated view
/// (`message`) and the original JSON (`raw`).
#derive(Eq, Debug)
pub(all) struct MessageResponse {
  id : String
  model : String
  role : Role
  content : Array[ContentBlock]
  stop_reason : StopReason?
  usage : Usage
  message : @gen.Message
  raw : Json
}

///|
/// Compares decoded messages field by field.
pub extend MessageResponse with Eq::{equal, not_equal}

///|
/// Debug representation of a decoded message.
pub extend MessageResponse with @debug.Debug::{to_repr}

///|
/// Concatenates text blocks in response order and ignores other block kinds.
pub fn MessageResponse::text(self : MessageResponse) -> String {
  let result = StringBuilder()
  for block in self.content {
    if block is Text(text) {
      result.write_string(text)
    }
  }
  result.to_string()
}

///|
/// The tool uses requested by the response, in content order.
pub fn MessageResponse::tool_uses(
  self : MessageResponse,
) -> Array[(String, String, Json)] {
  let result = []
  for block in self.content {
    if block is ToolUse(id~, name~, input~) {
      result.push((id, name, input))
    }
  }
  result
}

///|
/// One event from a streaming message. Event and delta kinds outside the
/// generated vocabulary retain their raw JSON.
#derive(Eq, Debug)
#non_exhaustive
pub(all) enum MessageEvent {
  MessageStart(MessageResponse)
  ContentBlockStart(index~ : Int32, block~ : ContentBlock)
  TextDelta(index~ : Int32, text~ : String)
  ThinkingDelta(index~ : Int32, thinking~ : String)
  SignatureDelta(index~ : Int32, signature~ : String)
  InputJsonDelta(index~ : Int32, partial_json~ : String)
  ContentBlockStop(index~ : Int32)
  MessageDelta(delta~ : MessageDelta, usage~ : MessageDeltaUsage)
  MessageStop
  Ping
  Error(ApiErrorBody)
  Other(String, Json)
}

///|
/// Compares message events, including retained unknown JSON.
pub extend MessageEvent with Eq::{equal, not_equal}

///|
/// Debug representation of a message event.
pub extend MessageEvent with @debug.Debug::{to_repr}