// MoonBit FromJson implementations for wire formats that cannot use derive(FromJson).
// Corresponding upstream cast: https://github.com/openai/codex/blob/f201c30c52a35f819262865a53df94b6f4ea7a50/sdk/typescript/src/thread.ts#L96-L103
// Item shapes being decoded: https://github.com/openai/codex/blob/f201c30c52a35f819262865a53df94b6f4ea7a50/sdk/typescript/src/items.ts#L5-L128

///|
let item_id_lens_ : @lens.Lens[String] = @lens.root().string("id")

///|
let command_execution_command_lens_ : @lens.Lens[String] = @lens.root().string(
  "command",
)

///|
let command_execution_aggregated_output_lens_ : @lens.Lens[String] = @lens.root().string(
  "aggregated_output",
)

///|
let command_execution_exit_code_lens_ : @lens.PresenceLens[Int] = @lens.root()
  .int("exit_code")
  .nullish()

///|
let command_execution_status_lens_ : @lens.Lens[CommandExecutionStatus] = @lens.root().custom(
  "status",
)

///|
let file_update_path_lens_ : @lens.Lens[String] = @lens.root().string("path")

///|
let file_update_kind_lens_ : @lens.Lens[PatchChangeKind] = @lens.root().custom(
  "kind",
)

///|
/// MCP content blocks are protocol-defined opaque JSON values; only the array shape is fixed.
let mcp_result_content_lens_ : @lens.Lens[Array[Json]] = @lens.root()
  .json("content")
  .array()

///|
/// MCP metadata is provider-defined and intentionally remains opaque JSON.
let mcp_result_meta_lens_ : @lens.PresenceLens[Json] = @lens.root()
  .json("_meta")
  .nullish()

///|
/// Structured MCP output has no SDK-owned schema and intentionally remains raw JSON.
let mcp_result_structured_content_lens_ : @lens.Lens[Json] = @lens.root().json(
  "structured_content",
)

///|
let mcp_item_server_lens_ : @lens.Lens[String] = @lens.root().string("server")

///|
let mcp_item_tool_lens_ : @lens.Lens[String] = @lens.root().string("tool")

///|
/// MCP tool arguments follow each tool's schema and intentionally remain raw JSON.
let mcp_item_arguments_lens_ : @lens.Lens[Json] = @lens.root().json("arguments")

///|
let mcp_item_result_lens_ : @lens.PresenceLens[McpToolCallResult] = @lens.root()
  .custom("result")
  .nullish()

///|
let mcp_item_error_lens_ : @lens.PresenceLens[McpToolCallError] = @lens.root()
  .custom("error")
  .nullish()

///|
let file_change_changes_lens_ : @lens.Lens[Array[FileUpdateChange]] = @lens.root().custom(
  "changes",
)

///|
let todo_list_items_lens_ : @lens.Lens[Array[TodoItem]] = @lens.root().custom(
  "items",
)

///|
let file_change_status_lens_ : @lens.Lens[PatchApplyStatus] = @lens.root().custom(
  "status",
)

///|
let mcp_item_status_lens_ : @lens.Lens[McpToolCallStatus] = @lens.root().custom(
  "status",
)

///|
let thread_item_type_lens_ : @lens.Lens[String] = @lens.root().string("type")

///|
pub impl @json.FromJson for CommandExecutionStatus with fn from_json(json, path) {
  let value : String = @json.from_json(json, path~)
  match value {
    "in_progress" => InProgress
    "completed" => Completed
    "failed" => Failed
    _ =>
      raise @json.JsonDecodeError(
        (path, "unsupported command execution status: \{value}"),
      )
  }
}

///|
pub impl ToJson for CommandExecutionStatus with fn to_json(self) {
  match self {
    InProgress => "in_progress".to_json()
    Completed => "completed".to_json()
    Failed => "failed".to_json()
  }
}

///|
pub impl @json.FromJson for PatchChangeKind with fn from_json(json, path) {
  let value : String = @json.from_json(json, path~)
  match value {
    "add" => Add
    "delete" => Delete
    "update" => Update
    _ =>
      raise @json.JsonDecodeError(
        (path, "unsupported patch change kind: \{value}"),
      )
  }
}

///|
pub impl ToJson for PatchChangeKind with fn to_json(self) {
  match self {
    Add => "add".to_json()
    Delete => "delete".to_json()
    Update => "update".to_json()
  }
}

///|
pub impl @json.FromJson for PatchApplyStatus with fn from_json(json, path) {
  let value : String = @json.from_json(json, path~)
  match value {
    "completed" => PatchCompleted
    "failed" => PatchFailed
    _ =>
      raise @json.JsonDecodeError(
        (path, "unsupported patch apply status: \{value}"),
      )
  }
}

///|
pub impl ToJson for PatchApplyStatus with fn to_json(self) {
  match self {
    PatchCompleted => "completed".to_json()
    PatchFailed => "failed".to_json()
  }
}

///|
pub impl @json.FromJson for McpToolCallStatus with fn from_json(json, path) {
  let value : String = @json.from_json(json, path~)
  match value {
    "in_progress" => McpInProgress
    "completed" => McpCompleted
    "failed" => McpFailed
    _ =>
      raise @json.JsonDecodeError(
        (path, "unsupported MCP tool call status: \{value}"),
      )
  }
}

///|
pub impl ToJson for McpToolCallStatus with fn to_json(self) {
  match self {
    McpInProgress => "in_progress".to_json()
    McpCompleted => "completed".to_json()
    McpFailed => "failed".to_json()
  }
}

///|
pub impl ToJson for FileUpdateChange with fn to_json(self) {
  Json::object({ "path": "\{self.path}".to_json(), "kind": self.kind.to_json() })
}

///|
pub impl ToJson for McpToolCallResult with fn to_json(self) {
  let value : Map[String, Json] = {
    "content": self.content.to_json(),
    "structured_content": self.structured_content,
  }
  match self.meta {
    Some(meta) => value["_meta"] = meta
    None => ()
  }
  Json::object(value)
}

///|
pub impl @json.FromJson for CommandExecutionItem with fn from_json(json, path) {
  guard json is Json::Object(_) else {
    raise @json.JsonDecodeError((path, "command_execution must be an object"))
  }
  let id = item_id_lens_.get_or_json_decode_error(json, path)
  let command = command_execution_command_lens_.get_or_json_decode_error(
    json, path,
  )
  let aggregated_output = command_execution_aggregated_output_lens_.get_or_json_decode_error(
    json, path,
  )
  let exit_code = command_execution_exit_code_lens_.get_or_json_decode_error(
    json, path,
  )
  let status = command_execution_status_lens_.get_or_json_decode_error(
    json, path,
  )
  { id, command, aggregated_output, exit_code, status }
}

///|
pub impl @json.FromJson for FileUpdateChange with fn from_json(json, path) {
  guard json is Json::Object(_) else {
    raise @json.JsonDecodeError((path, "file update must be an object"))
  }
  let path_value = file_update_path_lens_.get_or_json_decode_error(json, path)
  let kind = file_update_kind_lens_.get_or_json_decode_error(json, path)
  { path: @path.Path(path_value), kind }
}

///|
pub impl @json.FromJson for McpToolCallResult with fn from_json(json, path) {
  guard json is Json::Object(_) else {
    raise @json.JsonDecodeError((path, "MCP tool result must be an object"))
  }
  let content = mcp_result_content_lens_.get_or_json_decode_error(json, path)
  let meta = mcp_result_meta_lens_.get_or_json_decode_error(json, path)
  let structured_content = mcp_result_structured_content_lens_.get_or_json_decode_error(
    json, path,
  )
  { content: ReadOnlyArray::from_array(content), meta, structured_content }
}

///|
pub impl @json.FromJson for McpToolCallItem with fn from_json(json, path) {
  guard json is Json::Object(_) else {
    raise @json.JsonDecodeError((path, "mcp_tool_call must be an object"))
  }
  let id = item_id_lens_.get_or_json_decode_error(json, path)
  let server = mcp_item_server_lens_.get_or_json_decode_error(json, path)
  let tool = mcp_item_tool_lens_.get_or_json_decode_error(json, path)
  let arguments = mcp_item_arguments_lens_.get_or_json_decode_error(json, path)
  let result = mcp_item_result_lens_.get_or_json_decode_error(json, path)
  let error = mcp_item_error_lens_.get_or_json_decode_error(json, path)
  let status = mcp_item_status_lens_.get_or_json_decode_error(json, path)
  { id, server, tool, arguments, result, error, status }
}

///|
pub impl @json.FromJson for FileChangeItem with fn from_json(json, path) {
  guard json is Json::Object(_) else {
    raise @json.JsonDecodeError((path, "file_change must be an object"))
  }
  let id = item_id_lens_.get_or_json_decode_error(json, path)
  let changes = file_change_changes_lens_.get_or_json_decode_error(json, path)
  let status = file_change_status_lens_.get_or_json_decode_error(json, path)
  { id, changes: ReadOnlyArray::from_array(changes), status }
}

///|
pub impl @json.FromJson for TodoListItem with fn from_json(json, path) {
  guard json is Json::Object(_) else {
    raise @json.JsonDecodeError((path, "todo_list must be an object"))
  }
  let id = item_id_lens_.get_or_json_decode_error(json, path)
  let items = todo_list_items_lens_.get_or_json_decode_error(json, path)
  { id, items: ReadOnlyArray::from_array(items) }
}

///|
pub impl @json.FromJson for ThreadItem with fn from_json(json, path) {
  guard json is Json::Object(_) else {
    raise @json.JsonDecodeError((path, "thread item must be an object"))
  }
  let item_type = thread_item_type_lens_.get_or_json_decode_error(json, path)
  match item_type {
    "agent_message" => AgentMessage(@json.from_json(json, path~))
    "reasoning" => Reasoning(@json.from_json(json, path~))
    "command_execution" => CommandExecution(@json.from_json(json, path~))
    "file_change" => FileChange(@json.from_json(json, path~))
    "mcp_tool_call" => McpToolCall(@json.from_json(json, path~))
    "web_search" => WebSearch(@json.from_json(json, path~))
    "todo_list" => TodoList(@json.from_json(json, path~))
    "error" => Error(@json.from_json(json, path~))
    _ =>
      raise thread_item_type_lens_.json_decode_error(
        path,
        "unsupported thread item type: \{item_type}",
      )
  }
}