///|
/// Adapter for the Anthropic Messages API.
///
/// Anthropic's `/v1/messages` endpoint differs from the OpenAI chat API:
///
/// - The `system` prompt is a top-level field, not a message with role
///   `system`.
/// - `max_tokens` is required.
/// - The response carries a top-level `content` array of blocks, and usage is
///   reported as `input_tokens` / `output_tokens`.
///
/// This module converts a `ChatRequest` (OpenAI-shaped) into an Anthropic
/// request body, and converts an Anthropic response back into the common
/// `ChatResponse`, so the rest of the SDK can stay provider-agnostic.

///|
/// Build an Anthropic Messages request body from a common `ChatRequest`.
///
/// System messages are hoisted into the top-level `system` field; all other
/// messages are mapped to Anthropic's `{role, content}` shape. Anthropic
/// requires `max_tokens`, so a default of 1024 is used when unset.
pub fn anthropic_request_body(request : ChatRequest) -> Json {
  let system = StringBuilder::new()
  let messages = []
  for msg in request.messages {
    match msg.role {
      System =>
        match msg.content {
          Str(s) => {
            if system.to_string().length() > 0 {
              system.write_string("\n\n")
            }
            system.write_string(s)
          }
          Parts(_) => ()
        }
      _ =>
        messages.push(
          Json::object({
            "role": Json::string(anthropic_role(msg.role)),
            "content": anthropic_content(msg.content),
          }),
        )
    }
  }
  let obj : Map[String, Json] = {
    "model": Json::string(request.model),
    "messages": Json::array(messages),
    "max_tokens": Json::number(request.max_tokens.unwrap_or(1024).to_double()),
  }
  let sys = system.to_string()
  if sys.length() > 0 {
    obj["system"] = Json::string(sys)
  }
  if request.temperature is Some(t) {
    obj["temperature"] = Json::number(t)
  }
  if request.top_p is Some(p) {
    obj["top_p"] = Json::number(p)
  }
  if request.stop is Some(s) {
    obj["stop_sequences"] = s.to_json()
  }
  Json::object(obj)
}

///|
/// Map a common `Role` to the Anthropic role string. Anthropic only supports
/// `user` and `assistant` turns; `tool` results are represented as `user`.
fn anthropic_role(role : Role) -> String {
  match role {
    Assistant => "assistant"
    _ => "user"
  }
}

///|
/// Convert message content into an Anthropic content value.
fn anthropic_content(content : Content) -> Json {
  match content {
    Str(s) => Json::string(s)
    Parts(parts) => {
      let arr = []
      for p in parts {
        match p {
          Text(t) =>
            arr.push(
              Json::object({
                "type": Json::string("text"),
                "text": Json::string(t),
              }),
            )
          ImageUrl(u) =>
            arr.push(
              Json::object({
                "type": Json::string("image"),
                "source": Json::object({
                  "type": Json::string("url"),
                  "url": Json::string(u),
                }),
              }),
            )
        }
      }
      Json::array(arr)
    }
  }
}

///|
/// Parse an Anthropic Messages response into the common `ChatResponse`.
///
/// The `content` blocks of type `text` are concatenated into a single
/// assistant message; usage is mapped from `input_tokens`/`output_tokens`.
pub fn parse_anthropic_response(json : Json) -> ChatResponse raise LLMError {
  guard json is Object(obj) else {
    raise Decode("anthropic response: expected object")
  }
  let id = match obj.get("id") {
    Some(String(s)) => s
    _ => ""
  }
  let model = match obj.get("model") {
    Some(String(s)) => s
    _ => ""
  }
  let text = StringBuilder::new()
  match obj.get("content") {
    Some(Array(blocks)) =>
      for block in blocks {
        match block {
          Object(b) =>
            match b.get("type") {
              Some(String("text")) =>
                match b.get("text") {
                  Some(String(t)) => text.write_string(t)
                  _ => ()
                }
              _ => ()
            }
          _ => ()
        }
      }
    _ => ()
  }
  let finish_reason = match obj.get("stop_reason") {
    Some(String(s)) => Some(anthropic_stop_reason(s))
    _ => None
  }
  let usage = match obj.get("usage") {
    Some(Object(u)) => {
      let input = match u.get("input_tokens") {
        Some(Number(n, ..)) => n.to_int()
        _ => 0
      }
      let output = match u.get("output_tokens") {
        Some(Number(n, ..)) => n.to_int()
        _ => 0
      }
      Some(Usage::{
        prompt_tokens: input,
        completion_tokens: output,
        total_tokens: input + output,
      })
    }
    _ => None
  }
  let message = Message::assistant(text.to_string())
  {
    id,
    object: "chat.completion",
    created: 0L,
    model,
    choices: [{ index: 0, message, finish_reason }],
    usage,
    system_fingerprint: None,
  }
}

///|
/// Map an Anthropic `stop_reason` to the OpenAI-style `finish_reason`.
fn anthropic_stop_reason(reason : String) -> String {
  match reason {
    "end_turn" => "stop"
    "max_tokens" => "length"
    "stop_sequence" => "stop"
    "tool_use" => "tool_calls"
    other => other
  }
}

///|
/// Perform a chat completion against an Anthropic Messages endpoint.
///
/// This assumes the client's `base_url` targets an Anthropic-compatible host
/// (default `https://api.anthropic.com/v1`). Note that Anthropic requires the
/// `x-api-key` and `anthropic-version` headers; configure these via a client
/// whose auth is set accordingly, or use this against a proxy that injects
/// them. The request/response translation is handled here.
pub async fn Client::chat_anthropic(
  self : Client,
  request : ChatRequest,
) -> ChatResponse raise LLMError {
  let json = self.post_json("/messages", anthropic_request_body(request))
  parse_anthropic_response(json)
}