///|
/// Parsing for Anthropic's streaming Messages events and `tool_use` blocks.
///
/// Anthropic streams a sequence of typed SSE events rather than OpenAI's
/// uniform `chat.completion.chunk`s:
///
/// - `message_start` — carries the initial message shell and input usage.
/// - `content_block_start` — a new content block (text or tool_use) begins.
/// - `content_block_delta` — an incremental `text_delta` or
///   `input_json_delta` for the current block.
/// - `content_block_stop` — the current block ends.
/// - `message_delta` — top-level updates (stop_reason, output usage).
/// - `message_stop` — the message is complete.
///
/// This module converts a single Anthropic event payload into the common
/// `StreamChunk`, so the same accumulation logic can be reused.

///|
/// Parse one Anthropic streaming event, given its `event:` type and `data:`
/// JSON payload, into a common `StreamChunk`.
///
/// Returns `None` for events that carry no usable delta (message_start,
/// content_block_start/stop, ping, etc.). Raises `LLMError::Decode` on
/// malformed JSON.
pub fn parse_anthropic_event(
  event_type : String,
  payload : String,
) -> StreamChunk? raise LLMError {
  let json = @json.parse(payload) catch {
    err => raise Decode("anthropic event: " + err.to_string())
  }
  guard json is Object(obj) else { return None }
  match event_type {
    "content_block_delta" => parse_content_block_delta(obj)
    "content_block_start" => parse_content_block_start(obj)
    "message_delta" => parse_message_delta(obj)
    _ => None
  }
}

///|
/// Handle `content_block_delta`: either a `text_delta` (assistant text) or an
/// `input_json_delta` (streamed tool-call arguments).
fn parse_content_block_delta(obj : Map[String, Json]) -> StreamChunk? {
  let index = match obj.get("index") {
    Some(Number(n, ..)) => n.to_int()
    _ => 0
  }
  guard obj.get("delta") is Some(Object(delta)) else { return None }
  match delta.get("type") {
    Some(String("text_delta")) =>
      match delta.get("text") {
        Some(String(t)) =>
          Some({ content: Some(t), tool_calls: [], finish_reason: None })
        _ => None
      }
    Some(String("input_json_delta")) =>
      match delta.get("partial_json") {
        Some(String(j)) =>
          Some({
            content: None,
            tool_calls: [{ index, id: None, name: None, arguments: Some(j) }],
            finish_reason: None,
          })
        _ => None
      }
    _ => None
  }
}

///|
/// Handle `content_block_start`: if it opens a `tool_use` block, emit a
/// tool-call fragment carrying the id and name so the accumulator can begin
/// assembling arguments.
fn parse_content_block_start(obj : Map[String, Json]) -> StreamChunk? {
  let index = match obj.get("index") {
    Some(Number(n, ..)) => n.to_int()
    _ => 0
  }
  guard obj.get("content_block") is Some(Object(block)) else { return None }
  guard block.get("type") is Some(String("tool_use")) else { return None }
  let id = match block.get("id") {
    Some(String(s)) => Some(s)
    _ => None
  }
  let name = match block.get("name") {
    Some(String(s)) => Some(s)
    _ => None
  }
  Some({
    content: None,
    tool_calls: [{ index, id, name, arguments: None }],
    finish_reason: None,
  })
}

///|
/// Handle `message_delta`: extract the `stop_reason` if present.
fn parse_message_delta(obj : Map[String, Json]) -> StreamChunk? {
  guard obj.get("delta") is Some(Object(delta)) else { return None }
  match delta.get("stop_reason") {
    Some(String(s)) =>
      Some({
        content: None,
        tool_calls: [],
        finish_reason: Some(anthropic_stop_reason(s)),
      })
    _ => None
  }
}

///|
/// Parse the `tool_use` content blocks from a complete (non-streaming)
/// Anthropic response into common `ToolCall`s.
///
/// Anthropic represents tool calls as `{"type":"tool_use","id":...,
/// "name":...,"input":{...}}` blocks in the response `content` array; the
/// `input` object is re-serialized into the `arguments` JSON string to match
/// the OpenAI shape.
pub fn parse_anthropic_tool_calls(json : Json) -> Array[ToolCall] {
  let out = []
  guard json is Object(obj) else { return out }
  guard obj.get("content") is Some(Array(blocks)) else { return out }
  for block in blocks {
    guard block is Object(b) else { continue }
    guard b.get("type") is Some(String("tool_use")) else { continue }
    let id = match b.get("id") {
      Some(String(s)) => s
      _ => ""
    }
    let name = match b.get("name") {
      Some(String(s)) => s
      _ => ""
    }
    let arguments = match b.get("input") {
      Some(input) => input.stringify()
      None => "{}"
    }
    out.push({ id, function: { name, arguments } })
  }
  out
}