///|
priv struct CodexInput {
  messages : Array[CodexMessage]
  tools : Array[CodexTool]?
}

///|
priv struct CodexMessage {
  role : String
  content : String
  tool_call_id : String?
  name : String?
  arguments : String?
}

///|
impl ToJson for CodexMessage with fn to_json(self : CodexMessage) -> Json {
  let json : Map[String, Json] = {
    "role": Json::string(self.role),
    "content": Json::string(self.content),
  }
  if self.tool_call_id is Some(id) {
    json["tool_call_id"] = Json::string(id)
  }
  if self.name is Some(name) {
    json["name"] = Json::string(name)
  }
  if self.arguments is Some(args) {
    json["arguments"] = Json::string(args)
  }
  Json::object(json)
}

///|
priv struct CodexTool {
  type_ : String
  name : String
  description : String?
  parameters : Json
}

///|
impl ToJson for CodexTool with fn to_json(self : CodexTool) -> Json {
  let json : Map[String, Json] = {
    "type": Json::string(self.type_),
    "name": Json::string(self.name),
    "parameters": self.parameters,
  }
  if self.description is Some(desc) {
    json["description"] = Json::string(desc)
  }
  Json::object(json)
}

///|
fn convert_anthropic_to_codex(
  request : AnthropicRequest,
  _model : String,
) -> CodexInput {
  let messages : Array[CodexMessage] = []
  if request.system is Some(system) && !system.is_empty() {
    messages.push({
      role: "system",
      content: system,
      tool_call_id: None,
      name: None,
      arguments: None,
    })
  }
  for msg in request.messages {
    let content_str = extract_text_content(msg.content)
    match msg.role {
      "user" =>
        if content_str.contains("tool_use_id") {
          let tool_msg = parse_tool_result(content_str)
          messages.push({
            role: "tool",
            content: tool_msg.content,
            tool_call_id: Some(tool_msg.tool_use_id),
            name: None,
            arguments: None,
          })
        } else {
          messages.push({
            role: "user",
            content: content_str,
            tool_call_id: None,
            name: None,
            arguments: None,
          })
        }
      "assistant" => {
        let tool_calls = extract_tool_calls(msg.content)
        if tool_calls.is_empty() {
          messages.push({
            role: "assistant",
            content: content_str,
            tool_call_id: None,
            name: None,
            arguments: None,
          })
        } else {
          for tc in tool_calls {
            messages.push({
              role: "assistant",
              content: "",
              tool_call_id: None,
              name: Some(tc.name),
              arguments: Some(tc.arguments),
            })
          }
        }
      }
      _ =>
        messages.push({
          role: msg.role,
          content: content_str,
          tool_call_id: None,
          name: None,
          arguments: None,
        })
    }
  }
  let tools : Array[CodexTool]? = match request.tools {
    Some(ts) if !ts.is_empty() =>
      Some(
        ts
        .iter()
        .map(fn(t) {
          {
            type_: "function",
            name: t.name,
            description: t.description,
            parameters: t.input_schema,
          }
        })
        .collect(),
      )
    _ => None
  }
  { messages, tools }
}

///|
fn extract_text_content(content : AnthropicContent) -> String {
  match content {
    Text(t) => t
    Blocks(blocks) => {
      let texts : Array[String] = []
      for block in blocks {
        if block.text is Some(text) {
          texts.push(text)
        }
      }
      texts.join("\n")
    }
  }
}

///|
priv struct ToolCall {
  name : String
  arguments : String
}

///|
priv struct ToolResult {
  tool_use_id : String
  content : String
}

///|
fn extract_tool_calls(content : AnthropicContent) -> Array[ToolCall] {
  let result : Array[ToolCall] = []
  match content {
    Blocks(blocks) =>
      for block in blocks {
        if block.type_ == "tool_use" {
          let name = match block.text {
            Some(n) => n
            None => continue
          }
          result.push({ name, arguments: "{}" })
        }
      }
    _ => ()
  }
  result
}

///|
fn parse_tool_result(content : String) -> ToolResult {
  let json_result : Result[Json, Error] = moon_result(() => @json.parse(content))
  let tool_use_id = match json_result {
    Ok(Object(obj)) =>
      match obj.get("tool_use_id") {
        Some(String(s)) => s
        _ => "unknown"
      }
    _ => "unknown"
  }
  { tool_use_id, content }
}

///|
fn build_codex_request(
  input : CodexInput,
  model : String,
  stream : Bool,
) -> Json {
  let json : Map[String, Json] = {
    "model": Json::string(model),
    "input": input.messages.to_json(),
    "stream": stream.to_json(),
    "store": Json::boolean(false),
  }
  if input.tools is Some(tools) && !tools.is_empty() {
    json["tools"] = tools.to_json()
  }
  Json::object(json)
}

///|
fn build_anthropic_response(
  id : String,
  model : String,
  content : String,
  tool_calls : Array[ToolCallInfo],
  input_tokens : Int,
  output_tokens : Int,
  cache_read_input_tokens? : Int = 0,
  cache_creation_input_tokens? : Int = 0,
) -> AnthropicResponse {
  let response_blocks : Array[AnthropicResponseBlock] = []
  if !content.is_empty() {
    response_blocks.push({
      type_: "text",
      text: Some(content),
      name: None,
      input: None,
      id: None,
    })
  }
  for tc in tool_calls {
    let args_json_result : Result[Json, Error] = moon_result(() => {
      @json.parse(tc.arguments)
    })
    let args_json = match args_json_result {
      Ok(j) => j
      Err(_) => Json::object({})
    }
    response_blocks.push({
      type_: "tool_use",
      text: None,
      name: Some(tc.name),
      input: Some(args_json),
      id: Some(tc.id),
    })
  }
  let stop_reason = if tool_calls.is_empty() { "end_turn" } else { "tool_use" }
  {
    id,
    type_: "message",
    role: "assistant",
    content: response_blocks,
    model,
    stop_reason: Some(stop_reason),
    stop_sequence: None,
    usage: {
      input_tokens,
      output_tokens,
      cache_read_input_tokens: optional_positive_int(cache_read_input_tokens),
      cache_creation_input_tokens: optional_positive_int(
        cache_creation_input_tokens,
      ),
    },
  }
}

///|
fn optional_positive_int(value : Int) -> Int? {
  if value > 0 {
    Some(value)
  } else {
    None
  }
}

///|
priv struct ToolCallInfo {
  id : String
  name : String
  arguments : String
}

///|
fn init {
  let tool_call : ToolCallInfo = { id: "id", name: "name", arguments: "{}" }
  ignore(tool_call)
}