// 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[Json] = @lens.root().json("id")

///|
let command_execution_command_lens : @lens.Lens[Json] = @lens.root().json(
  "command",
)

///|
let command_execution_aggregated_output_lens : @lens.Lens[Json] = @lens.root().json(
  "aggregated_output",
)

///|
let command_execution_exit_code_lens : @lens.PresenceLens[Json] = @lens.root()
  .json("exit_code")
  .nullish()

///|
let item_status_lens : @lens.Lens[Json] = @lens.root().json("status")

///|
let file_update_path_lens : @lens.Lens[Json] = @lens.root().json("path")

///|
let file_update_kind_lens : @lens.Lens[Json] = @lens.root().json("kind")

///|
let mcp_result_content_lens : @lens.Lens[Json] = @lens.root().json("content")

///|
let mcp_result_meta_lens : @lens.PresenceLens[Json] = @lens.root()
  .json("_meta")
  .nullish()

///|
let mcp_result_structured_content_lens : @lens.Lens[Json] = @lens.root().json(
  "structured_content",
)

///|
let mcp_item_server_lens : @lens.Lens[Json] = @lens.root().json("server")

///|
let mcp_item_tool_lens : @lens.Lens[Json] = @lens.root().json("tool")

///|
let mcp_item_arguments_lens : @lens.Lens[Json] = @lens.root().json("arguments")

///|
let mcp_item_result_lens : @lens.PresenceLens[Json] = @lens.root()
  .json("result")
  .nullish()

///|
let mcp_item_error_lens : @lens.PresenceLens[Json] = @lens.root()
  .json("error")
  .nullish()

///|
let file_change_changes_lens : @lens.Lens[Json] = @lens.root().json("changes")

///|
let todo_list_items_lens : @lens.Lens[Json] = @lens.root().json("items")

///|
let thread_item_type_lens : @lens.Lens[Json] = @lens.root().json("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 @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 @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 @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 @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 : String = @json.from_json(
    item_id_lens.get_or_json_decode_error(json, path),
    path=item_id_lens.add_to_json_path(path),
  )
  let command : String = @json.from_json(
    command_execution_command_lens.get_or_json_decode_error(json, path),
    path=command_execution_command_lens.add_to_json_path(path),
  )
  let aggregated_output : String = @json.from_json(
    command_execution_aggregated_output_lens.get_or_json_decode_error(
      json, path,
    ),
    path=command_execution_aggregated_output_lens.add_to_json_path(path),
  )
  let exit_code_value = command_execution_exit_code_lens.get_or_json_decode_error(
    json, path,
  )
  let exit_code = match exit_code_value {
    Some(value) =>
      Some(
        @json.from_json(
          value,
          path=command_execution_exit_code_lens.add_to_json_path(path),
        ),
      )
    None => None
  }
  let status : CommandExecutionStatus = @json.from_json(
    item_status_lens.get_or_json_decode_error(json, path),
    path=item_status_lens.add_to_json_path(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 : String = @json.from_json(
    file_update_path_lens.get_or_json_decode_error(json, path),
    path=file_update_path_lens.add_to_json_path(path),
  )
  let kind : PatchChangeKind = @json.from_json(
    file_update_kind_lens.get_or_json_decode_error(json, path),
    path=file_update_kind_lens.add_to_json_path(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 : Array[Json] = @json.from_json(
    mcp_result_content_lens.get_or_json_decode_error(json, path),
    path=mcp_result_content_lens.add_to_json_path(path),
  )
  let meta = mcp_result_meta_lens.get_or_json_decode_error(json, path)
  let structured_content : Json = @json.from_json(
    mcp_result_structured_content_lens.get_or_json_decode_error(json, path),
    path=mcp_result_structured_content_lens.add_to_json_path(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 : String = @json.from_json(
    item_id_lens.get_or_json_decode_error(json, path),
    path=item_id_lens.add_to_json_path(path),
  )
  let server : String = @json.from_json(
    mcp_item_server_lens.get_or_json_decode_error(json, path),
    path=mcp_item_server_lens.add_to_json_path(path),
  )
  let tool : String = @json.from_json(
    mcp_item_tool_lens.get_or_json_decode_error(json, path),
    path=mcp_item_tool_lens.add_to_json_path(path),
  )
  let arguments : Json = @json.from_json(
    mcp_item_arguments_lens.get_or_json_decode_error(json, path),
    path=mcp_item_arguments_lens.add_to_json_path(path),
  )
  let result_value = mcp_item_result_lens.get_or_json_decode_error(json, path)
  let result = match result_value {
    Some(value) =>
      Some(
        @json.from_json(value, path=mcp_item_result_lens.add_to_json_path(path)),
      )
    None => None
  }
  let error_value = mcp_item_error_lens.get_or_json_decode_error(json, path)
  let error = match error_value {
    Some(value) =>
      Some(
        @json.from_json(value, path=mcp_item_error_lens.add_to_json_path(path)),
      )
    None => None
  }
  let status : McpToolCallStatus = @json.from_json(
    item_status_lens.get_or_json_decode_error(json, path),
    path=item_status_lens.add_to_json_path(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 : String = @json.from_json(
    item_id_lens.get_or_json_decode_error(json, path),
    path=item_id_lens.add_to_json_path(path),
  )
  let changes : Array[FileUpdateChange] = @json.from_json(
    file_change_changes_lens.get_or_json_decode_error(json, path),
    path=file_change_changes_lens.add_to_json_path(path),
  )
  let status : PatchApplyStatus = @json.from_json(
    item_status_lens.get_or_json_decode_error(json, path),
    path=item_status_lens.add_to_json_path(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 : String = @json.from_json(
    item_id_lens.get_or_json_decode_error(json, path),
    path=item_id_lens.add_to_json_path(path),
  )
  let items : Array[TodoItem] = @json.from_json(
    todo_list_items_lens.get_or_json_decode_error(json, path),
    path=todo_list_items_lens.add_to_json_path(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 : String = @json.from_json(
    thread_item_type_lens.get_or_json_decode_error(json, path),
    path=thread_item_type_lens.add_to_json_path(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}",
      )
  }
}