// 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]?
  )
  /// 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)
}

///|
pub impl ToJson for ThreadItem with 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)
    }
    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 from_json(value, path) {
  guard value is Object({ "type": String(ty), .. } as obj) else {
    raise @json.JsonDecodeError((path, "expected ThreadItem"))
  }
  match ty {
    "agent_message" => {
      guard obj is { "id": String(id), "text": String(text), .. } else {
        raise @json.JsonDecodeError((path, "expected AgentMessageItem"))
      }
      AgentMessageItem(id~, text~)
    }
    "reasoning" => {
      guard obj is { "id": String(id), "text": String(text), .. } else {
        raise @json.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 @json.JsonDecodeError(
          (path, "expected CommandExecutionItem, got \{obj}"),
        )
      }
      let exit_code = match exit_code {
        Some(Number(n, ..)) => Some(n.to_int())
        Some(Null) | None => None
        v =>
          raise @json.JsonDecodeError(
            (
              path.add_key("exit_code"),
              "expected integer or null for CommandExecutionItem.exit_code. Got: \{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 @json.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 @json.JsonDecodeError((path, "expected McpToolCallItem"))
      }
      if error is Some(error) {
        guard error is Object({ "message": String(_), .. }) else {
          raise @json.JsonDecodeError(
            (
              path.add_key("error"),
              "expected error object with message string, got \{error}",
            ),
          )
        }
      }
      McpToolCallItem(
        id~,
        server~,
        tool~,
        status=@json.from_json(status, path=path.add_key("status")),
        arguments~,
        result=if result is Some(res) {
          Some(Ok(@json.from_json(res, path=path.add_key("result"))))
        } else if error is Some(Object({ "message": String(msg), .. })) {
          Some(Err(msg))
        } else {
          None
        },
      )
    }
    "web_search" => {
      guard obj is { "id": String(id), "query": String(query), .. } else {
        raise @json.JsonDecodeError((path, "expected WebSearchItem"))
      }
      WebSearchItem(id~, query~)
    }
    "todo_list" => {
      guard obj is { "id": String(id), "items": items, .. } else {
        raise @json.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 @json.JsonDecodeError((path, "expected ErrorItem"))
      }
      ErrorItem(id~, message~)
    }
    _ => raise @json.JsonDecodeError((path, "unknown ThreadItem type: \{ty}"))
  }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

///|
pub struct McpToolCallResult {
  content : Array[Json]
  structured_content : Json
}

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

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