// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
pub enum ThreadItem {
  /// Response from the agent. Either natural-language text or JSON when structured output is requested.
  AgentMessageItem(id~ : String, text~ : String)
  /// Agent's reasoning summary.
  ReasoningItem(id~ : String, text~ : String)
  /// A command executed by the agent.
  CommandExecutionItem(
    id~ : String,
    command~ : String,
    aggregated_output~ : String,
    exit_code~ : Int?,
    status~ : CommandExecutionStatus
  )
  /// A set of file changes by the agent. Emitted once the patch succeeds or fails.
  FileChangeItem(
    id~ : String,
    changes~ : Array[FileUpdateChange],
    status~ : PatchApplyStatus
  )
  /// Represents a call to an MCP tool. The item starts when the invocation is dispatched and completes when the MCP server reports success or failure.
  McpToolCallItem(
    id~ : String,
    server~ : String,
    tool~ : String,
    status~ : McpToolCallStatus,
    arguments~ : Json?,
    result~ : Result[McpToolCallResult, String]?
  )
  /// Represents a call to a Codex collab-agent tool.
  CollabToolCallItem(
    id~ : String,
    tool~ : CollabTool,
    sender_thread_id~ : String,
    receiver_thread_ids~ : Array[String],
    prompt~ : String?,
    agents_states~ : Map[String, CollabAgentState],
    status~ : CollabToolCallStatus
  )
  /// Captures a web search request. Completes when results are returned to the agent.
  WebSearchItem(id~ : String, query~ : String)
  /// Tracks the agent's running to-do list. Starts when the plan is issued, updates as steps change, and completes when the turn ends.
  TodoListItem(id~ : String, items~ : Array[TodoItem])
  /// Describes a non-fatal error surfaced as an item.
  ErrorItem(id~ : String, message~ : String)
} derive(Debug)

///|
pub impl ToJson for ThreadItem with fn to_json(item) {
  match item {
    AgentMessageItem(id~, text~) =>
      { "type": "agent_message", "id": id, "text": text }
    ReasoningItem(id~, text~) => { "type": "reasoning", "id": id, "text": text }
    CommandExecutionItem(id~, command~, aggregated_output~, exit_code~, status~) =>
      if exit_code is Some(exit_code) {
        {
          "type": "command_execution",
          "id": id,
          "command": command,
          "aggregated_output": aggregated_output,
          "exit_code": exit_code,
          "status": status,
        }
      } else {
        {
          "type": "command_execution",
          "id": id,
          "command": command,
          "aggregated_output": aggregated_output,
          "status": status,
        }
      }
    FileChangeItem(id~, changes~, status~) =>
      { "type": "file_change", "id": id, "changes": changes, "status": status }
    McpToolCallItem(id~, server~, tool~, status~, arguments~, result~) => {
      let item : Map[String, Json] = {
        "type": "mcp_tool_call",
        "id": id,
        "server": server,
        "tool": tool,
        "status": status,
      }
      if arguments is Some(arguments) {
        item.set("arguments", arguments)
      }
      if result is Some(Ok(res)) {
        item.set("result", res.to_json())
      } else if result is Some(Err(err)) {
        item.set("error", { "message": err })
      }
      Json::object(item)
    }
    CollabToolCallItem(
      id~,
      tool~,
      sender_thread_id~,
      receiver_thread_ids~,
      prompt~,
      agents_states~,
      status~
    ) => {
      let item : Map[String, Json] = {
        "type": "collab_tool_call",
        "id": id,
        "tool": tool,
        "sender_thread_id": sender_thread_id,
        "receiver_thread_ids": receiver_thread_ids,
        "agents_states": agents_states,
        "status": status,
      }
      if prompt is Some(prompt) {
        item.set("prompt", prompt.to_json())
      }
      Json::object(item)
    }
    WebSearchItem(id~, query~) =>
      { "type": "web_search", "id": id, "query": query }
    TodoListItem(id~, items~) =>
      { "type": "todo_list", "id": id, "items": items }
    ErrorItem(id~, message~) =>
      { "type": "error", "id": id, "message": message }
  }
}

///|
pub impl @json.FromJson for ThreadItem with fn from_json(value, path) {
  guard value is Object({ "type": String(ty), .. } as obj) else {
    raise JsonDecodeError((path, "expected ThreadItem"))
  }
  match ty {
    "agent_message" => {
      guard obj is { "id": String(id), "text": String(text), .. } else {
        raise JsonDecodeError((path, "expected AgentMessageItem"))
      }
      AgentMessageItem(id~, text~)
    }
    "reasoning" => {
      guard obj is { "id": String(id), "text": String(text), .. } else {
        raise JsonDecodeError((path, "expected ReasoningItem"))
      }
      ReasoningItem(id~, text~)
    }
    "command_execution" => {
      guard obj
        is {
          "id": String(id),
          "command": String(command),
          "aggregated_output": String(aggregated_output),
          "status": String(_) as status,
          "exit_code"? : exit_code,
          ..
        } else {
        raise JsonDecodeError(
          (path, "expected CommandExecutionItem, got \{@debug.to_repr(obj)}"),
        )
      }
      let exit_code = match exit_code {
        Some(Number(n, ..)) => Some(n.to_int())
        Some(Null) | None => None
        v =>
          raise JsonDecodeError(
            (
              path.add_key("exit_code"),
              "expected integer or null for CommandExecutionItem.exit_code. Got: \{@debug.to_repr(v)}",
            ),
          )
      }
      CommandExecutionItem(
        id~,
        command~,
        aggregated_output~,
        exit_code~,
        status=@json.from_json(status, path=path.add_key("status")),
      )
    }
    "file_change" => {
      guard obj
        is { "id": String(id), "changes": changes, "status": status, .. } else {
        raise JsonDecodeError((path, "expected FileChangeItem"))
      }
      FileChangeItem(
        id~,
        changes=@json.from_json(changes, path=path.add_key("changes")),
        status=@json.from_json(status, path=path.add_key("status")),
      )
    }
    "mcp_tool_call" => {
      guard obj
        is {
          "id": String(id),
          "server": String(server),
          "tool": String(tool),
          "status": status,
          "arguments"? : arguments,
          "result"? : result,
          "error"? : error,
          ..
        } else {
        raise JsonDecodeError((path, "expected McpToolCallItem"))
      }
      if error is Some(error) {
        guard error is Null || error is Object({ "message": String(_), .. }) else {
          raise JsonDecodeError(
            (
              path.add_key("error"),
              "expected null or error object with message string, got \{@debug.to_repr(error)}",
            ),
          )
        }
      }
      McpToolCallItem(
        id~,
        server~,
        tool~,
        status=@json.from_json(status, path=path.add_key("status")),
        arguments~,
        result=if error is Some(Object({ "message": String(msg), .. })) {
          Some(Err(msg))
        } else if result is Some(Null) {
          None
        } else if result is Some(res) {
          Some(Ok(@json.from_json(res, path=path.add_key("result"))))
        } else {
          None
        },
      )
    }
    "collab_tool_call" => {
      guard obj
        is {
          "id": String(id),
          "tool": tool,
          "sender_thread_id": String(sender_thread_id),
          "receiver_thread_ids": receiver_thread_ids,
          "prompt"? : prompt,
          "agents_states": agents_states,
          "status": status,
          ..
        } else {
        raise JsonDecodeError((path, "expected CollabToolCallItem"))
      }
      let prompt = match prompt {
        Some(String(prompt)) => Some(prompt)
        Some(Null) | None => None
        v =>
          raise JsonDecodeError(
            (
              path.add_key("prompt"),
              "expected string or null for CollabToolCallItem.prompt. Got: \{@debug.to_repr(v)}",
            ),
          )
      }
      CollabToolCallItem(
        id~,
        tool=@json.from_json(tool, path=path.add_key("tool")),
        sender_thread_id~,
        receiver_thread_ids=@json.from_json(
          receiver_thread_ids,
          path=path.add_key("receiver_thread_ids"),
        ),
        prompt~,
        agents_states=@json.from_json(
          agents_states,
          path=path.add_key("agents_states"),
        ),
        status=@json.from_json(status, path=path.add_key("status")),
      )
    }
    "web_search" => {
      guard obj is { "id": String(id), "query": String(query), .. } else {
        raise JsonDecodeError((path, "expected WebSearchItem"))
      }
      WebSearchItem(id~, query~)
    }
    "todo_list" => {
      guard obj is { "id": String(id), "items": items, .. } else {
        raise JsonDecodeError((path, "expected TodoListItem"))
      }
      TodoListItem(
        id~,
        items=@json.from_json(items, path=path.add_key("items")),
      )
    }
    "error" => {
      guard obj is { "id": String(id), "message": String(message), .. } else {
        raise JsonDecodeError((path, "expected ErrorItem"))
      }
      ErrorItem(id~, message~)
    }
    _ => raise JsonDecodeError((path, "unknown ThreadItem type: \{ty}"))
  }
}

///|
/// A set of file changes by the agent.
pub struct FileUpdateChange {
  path : String
  kind : PatchChangeKind
} derive(ToJson, FromJson, Debug)

///|
pub impl Show for FileUpdateChange with fn output(self, logger) {
  logger.write_string("{path: \"\{self.path}\", kind: \{self.kind}}")
}

///|
/// An item in the agent's to-do list.
pub struct TodoItem {
  text : String
  completed : Bool
} derive(ToJson, FromJson, Debug)

///|
pub impl Show for TodoItem with fn output(self, logger) {
  logger.write_string("{text: \"\{self.text}\", completed: \{self.completed}}")
}

///|
/// The status of a command execution.
pub enum CommandExecutionStatus {
  InProgress
  Completed
  Failed
  Declined
} derive(Debug)

///|
pub impl Show for CommandExecutionStatus with fn output(status, logger) {
  match status {
    InProgress => logger.write_string("InProgress")
    Completed => logger.write_string("Completed")
    Failed => logger.write_string("Failed")
    Declined => logger.write_string("Declined")
  }
}

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

///|
pub impl @json.FromJson for CommandExecutionStatus with fn from_json(
  value,
  path,
) {
  match value {
    String("completed") => Completed
    String("in_progress") => InProgress
    String("failed") => Failed
    String("declined") => Declined
    _ =>
      raise JsonDecodeError(
        (
          path,
          "expected string 'completed', 'in_progress', 'failed', or 'declined' for CommandExecutionStatus. Got \{@debug.to_repr(value)}",
        ),
      )
  }
}

///|
/// The status of a collab tool call.
pub enum CollabToolCallStatus {
  InProgress
  Completed
  Failed
} derive(Debug)

///|
pub impl Show for CollabToolCallStatus with fn output(status, logger) {
  match status {
    InProgress => logger.write_string("InProgress")
    Completed => logger.write_string("Completed")
    Failed => logger.write_string("Failed")
  }
}

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

///|
pub impl @json.FromJson for CollabToolCallStatus with fn from_json(value, path) {
  match value {
    String("completed") => Completed
    String("in_progress") => InProgress
    String("failed") => Failed
    _ =>
      raise JsonDecodeError(
        (
          path,
          "expected string 'completed', 'in_progress', or 'failed' for CollabToolCallStatus. Got \{@debug.to_repr(value)}",
        ),
      )
  }
}

///|
/// Supported collab tools.
pub enum CollabTool {
  SpawnAgent
  SendInput
  ResumeAgent
  Wait
  CloseAgent
} derive(Debug)

///|
pub impl Show for CollabTool with fn output(tool, logger) {
  match tool {
    SpawnAgent => logger.write_string("SpawnAgent")
    SendInput => logger.write_string("SendInput")
    ResumeAgent => logger.write_string("ResumeAgent")
    Wait => logger.write_string("Wait")
    CloseAgent => logger.write_string("CloseAgent")
  }
}

///|
pub impl ToJson for CollabTool with fn to_json(tool) {
  match tool {
    SpawnAgent => "spawn_agent"
    SendInput => "send_input"
    ResumeAgent => "resume_agent"
    Wait => "wait"
    CloseAgent => "close_agent"
  }
}

///|
pub impl @json.FromJson for CollabTool with fn from_json(value, path) {
  match value {
    String("spawn_agent") => SpawnAgent
    String("send_input") => SendInput
    String("resume_agent") => ResumeAgent
    String("wait") => Wait
    String("close_agent") => CloseAgent
    _ =>
      raise JsonDecodeError(
        (
          path,
          "expected string 'spawn_agent', 'send_input', 'resume_agent', 'wait', or 'close_agent' for CollabTool. Got \{@debug.to_repr(value)}",
        ),
      )
  }
}

///|
/// The status of a collab agent.
pub enum CollabAgentStatus {
  PendingInit
  Running
  Interrupted
  Completed
  Errored
  Shutdown
  NotFound
} derive(Debug)

///|
pub impl Show for CollabAgentStatus with fn output(status, logger) {
  match status {
    PendingInit => logger.write_string("PendingInit")
    Running => logger.write_string("Running")
    Interrupted => logger.write_string("Interrupted")
    Completed => logger.write_string("Completed")
    Errored => logger.write_string("Errored")
    Shutdown => logger.write_string("Shutdown")
    NotFound => logger.write_string("NotFound")
  }
}

///|
pub impl ToJson for CollabAgentStatus with fn to_json(status) {
  match status {
    PendingInit => "pending_init"
    Running => "running"
    Interrupted => "interrupted"
    Completed => "completed"
    Errored => "errored"
    Shutdown => "shutdown"
    NotFound => "not_found"
  }
}

///|
pub impl @json.FromJson for CollabAgentStatus with fn from_json(value, path) {
  match value {
    String("pending_init") => PendingInit
    String("running") => Running
    String("interrupted") => Interrupted
    String("completed") => Completed
    String("errored") => Errored
    String("shutdown") => Shutdown
    String("not_found") => NotFound
    _ =>
      raise JsonDecodeError(
        (
          path,
          "expected string 'pending_init', 'running', 'interrupted', 'completed', 'errored', 'shutdown', or 'not_found' for CollabAgentStatus. Got \{@debug.to_repr(value)}",
        ),
      )
  }
}

///|
/// Last known state of a collab agent.
pub struct CollabAgentState {
  status : CollabAgentStatus
  message : String?
} derive(Debug)

///|
pub impl ToJson for CollabAgentState with fn to_json(state) {
  let obj : Map[String, Json] = { "status": state.status }
  if state.message is Some(message) {
    obj.set("message", message.to_json())
  }
  Json::object(obj)
}

///|
pub impl @json.FromJson for CollabAgentState with fn from_json(value, path) {
  guard value is Object({ "status": status, "message"? : message, .. }) else {
    raise JsonDecodeError((path, "expected CollabAgentState"))
  }
  let message = match message {
    Some(String(message)) => Some(message)
    Some(Null) | None => None
    v =>
      raise JsonDecodeError(
        (
          path.add_key("message"),
          "expected string or null for CollabAgentState.message. Got: \{@debug.to_repr(v)}",
        ),
      )
  }
  { status: @json.from_json(status, path=path.add_key("status")), message }
}

///|
/// Indicates the type of the file change.
pub enum PatchChangeKind {
  Add
  Delete
  Update
} derive(Debug)

///|
pub impl Show for PatchChangeKind with fn output(kind, logger) {
  match kind {
    Add => logger.write_string("Add")
    Delete => logger.write_string("Delete")
    Update => logger.write_string("Update")
  }
}

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

///|
pub impl @json.FromJson for PatchChangeKind with fn from_json(value, path) {
  match value {
    String("add") => Add
    String("delete") => Delete
    String("update") => Update
    _ =>
      raise JsonDecodeError(
        (
          path,
          "expected string 'add', 'delete', or 'update' for PatchChangeKind. Got \{@debug.to_repr(value)}",
        ),
      )
  }
}

///|
/// The status of a file change.
pub enum PatchApplyStatus {
  InProgress
  Completed
  Failed
} derive(Debug)

///|
pub impl Show for PatchApplyStatus with fn output(status, logger) {
  match status {
    InProgress => logger.write_string("InProgress")
    Completed => logger.write_string("Completed")
    Failed => logger.write_string("Failed")
  }
}

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

///|
pub impl @json.FromJson for PatchApplyStatus with fn from_json(value, path) {
  match value {
    String("in_progress") => InProgress
    String("completed") => Completed
    String("failed") => Failed
    _ =>
      raise JsonDecodeError(
        (
          path,
          "expected string 'in_progress', 'completed', or 'failed' for PatchApplyStatus. Got \{@debug.to_repr(value)}",
        ),
      )
  }
}

///|
/// The status of an MCP tool call.
pub enum McpToolCallStatus {
  InProgress
  Completed
  Failed
} derive(Debug)

///|
pub impl Show for McpToolCallStatus with fn output(status, logger) {
  match status {
    InProgress => logger.write_string("InProgress")
    Completed => logger.write_string("Completed")
    Failed => logger.write_string("Failed")
  }
}

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

///|
pub impl @json.FromJson for McpToolCallStatus with fn from_json(value, path) {
  match value {
    String("completed") => Completed
    String("in_progress") => InProgress
    String("failed") => Failed
    _ =>
      raise JsonDecodeError(
        (
          path,
          "expected string 'completed', 'in_progress', or 'failed' for McpToolCallStatus. Got \{@debug.to_repr(value)}",
        ),
      )
  }
}

///|
pub struct McpToolCallResult {
  content : Array[Json]
  structured_content : Json
} derive(Debug)

///|
pub impl ToJson for McpToolCallResult with fn to_json(result) {
  { "content": result.content, "structured_content": result.structured_content }
}

///|
pub impl FromJson for McpToolCallResult with fn from_json(value, path) {
  guard value
    is {
      "content": Array(content),
      "structured_content": structured_content,
      ..
    } else {
    raise JsonDecodeError(
      (
        path,
        "expected McpToolCallResult object with content and structured_content. Got \{@debug.to_repr(value)}",
      ),
    )
  }
  { content, structured_content }
}