//! Environment/info/config/session-admin methods (`workspace.info`,
//! `workspace.load_*`, `workspace.tool_definitions`, plugin management).

use serde::{Deserialize, Serialize};
use serde_json::Value;

use super::WorkspaceRpc;

/// `workspace.info`. `Response` stays the raw [`Value`] to preserve the
/// `WorkspaceOps::workspace_info()` contract; [`WorkspaceInfo`] is the
/// typed shape of that value.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct WorkspaceInfoReq {}

impl WorkspaceRpc for WorkspaceInfoReq {
    const METHOD: &'static str = "workspace.info";
    type Response = Value;
}

/// `workspace.load_project_config` — project config discovered at the
/// workspace root.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct LoadProjectConfigReq {}

impl WorkspaceRpc for LoadProjectConfigReq {
    const METHOD: &'static str = "workspace.load_project_config";
    type Response = Value;
}

/// `workspace.load_permissions` — permission settings discovered at the
/// workspace root.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct LoadPermissionsReq {}

impl WorkspaceRpc for LoadPermissionsReq {
    const METHOD: &'static str = "workspace.load_permissions";
    type Response = Value;
}

/// `workspace.load_envrc` — `.envrc` environment loaded at the workspace
/// root (empty object when absent).
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct LoadEnvrcReq {}

impl WorkspaceRpc for LoadEnvrcReq {
    const METHOD: &'static str = "workspace.load_envrc";
    type Response = Value;
}

/// `workspace.tool_definitions` — tool definitions for a session's
/// finalized toolset.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ToolDefinitionsReq {
    pub session_id: String,
}

impl WorkspaceRpc for ToolDefinitionsReq {
    const METHOD: &'static str = "workspace.tool_definitions";
    type Response = Value;
}

/// `workspace.resolve_file_references` — resolve `@file` references
/// against the workspace root.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ResolveFileReferencesReq {
    pub refs: Vec<String>,
}

impl WorkspaceRpc for ResolveFileReferencesReq {
    const METHOD: &'static str = "workspace.resolve_file_references";
    type Response = Value;
}

/// `workspace.update_tool_config` — replace a session's tool config.
///
/// Rejected with the retryable [`TURN_ACTIVE`](super::envelope::TURN_ACTIVE)
/// wire code while the target session has an active turn and the new config
/// differs; retry at the turn boundary.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct UpdateToolConfigReq {
    /// Deprecated: self-attested and no longer trusted. The server derives
    /// the caller from the hub-bound envelope session and only falls back to
    /// this field when no envelope session is present (old call paths).
    /// Empty means absent: skipped on serialize so typed clients that leave
    /// the default do not send a self-attested `""` (the server also
    /// filters empty to absent for old serializers).
    #[serde(default, skip_serializing_if = "String::is_empty")]
    pub caller_session_id: String,
    pub session_id: String,
    pub new_config: Value,
}

impl WorkspaceRpc for UpdateToolConfigReq {
    const METHOD: &'static str = "workspace.update_tool_config";
    type Response = Value;
}

/// `workspace.drop_session` — drop a workspace session.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct DropSessionReq {
    /// Deprecated: self-attested and no longer trusted. The server derives
    /// the caller from the hub-bound envelope session and only falls back to
    /// this field when no envelope session is present (old call paths).
    /// Empty means absent: skipped on serialize so typed clients that leave
    /// the default do not send a self-attested `""` (the server also
    /// filters empty to absent for old serializers).
    #[serde(default, skip_serializing_if = "String::is_empty")]
    pub caller_session_id: String,
    pub session_id: String,
}

impl WorkspaceRpc for DropSessionReq {
    const METHOD: &'static str = "workspace.drop_session";
    type Response = Value;
}

/// `workspace.configure_mcp` — start MCP servers for the caller's bound session.
/// `mcp_servers` stays raw JSON (the shape is the ACP `McpServer` list)
/// so this crate carries no `agent-client-protocol` dependency.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ConfigureMcpReq {
    pub mcp_servers: Value,
}

impl WorkspaceRpc for ConfigureMcpReq {
    const METHOD: &'static str = "workspace.configure_mcp";
    type Response = Value;
}

/// `workspace.install_plugin` — no-op on the server (installation needs
/// shell-side auth + registry); always returns `null`.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct InstallPluginReq {}

impl WorkspaceRpc for InstallPluginReq {
    const METHOD: &'static str = "workspace.install_plugin";
    type Response = Value;
}

/// `workspace.refresh_plugins` — re-discover plugins at the workspace root.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct RefreshPluginsReq {}

impl WorkspaceRpc for RefreshPluginsReq {
    const METHOD: &'static str = "workspace.refresh_plugins";
    type Response = Value;
}

/// One still-running background terminal command (a slim, dependency-free DTO
/// over `kigi_tools`'s `TaskSnapshot`). `tool_name`, when set, is the
/// model-facing name of the tool that created the task.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct BackgroundTaskSummaryWire {
    pub task_id: String,
    /// The command line that was launched (prefers `display_command`).
    pub command: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub tool_name: Option<String>,
}

/// Response of `workspace.list_background_tasks` — outstanding (not-completed)
/// background terminal tasks only.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ListBackgroundTasksResponse {
    pub tasks: Vec<BackgroundTaskSummaryWire>,
}

/// `workspace.list_background_tasks` — list the outstanding background terminal
/// commands for `session_id`, for post-compaction `<system-reminder>` state.
/// `WorkspaceClient` is session-agnostic, so the caller supplies the hub-bound
/// session id.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ListBackgroundTasksReq {
    pub session_id: String,
}

impl WorkspaceRpc for ListBackgroundTasksReq {
    const METHOD: &'static str = "workspace.list_background_tasks";
    type Response = ListBackgroundTasksResponse;
}

/// One outstanding background terminal task, with the fields client task UI
/// needs (a slim DTO over `kigi_tools`'s `TaskSnapshot`).
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct BackgroundTaskSnapshotWire {
    /// Background task registry id (pairs with the `task.*` push events).
    pub task_id: String,
    /// The launched command line (prefers `display_command`).
    pub command: String,
    /// `bash` | `monitor`.
    pub kind: String,
    /// RFC3339 start timestamp.
    pub started_at: String,
}

/// One live scheduled task (`/loop`), a slim DTO over the scheduler's
/// `ScheduledTask` (pairs with the `scheduled_task.*` push events).
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct ScheduledTaskSnapshotWire {
    pub task_id: String,
    pub prompt: String,
    /// Human-readable schedule, e.g. "every 5 minutes".
    pub human_schedule: String,
    /// RFC3339 timestamp of the next fire.
    pub next_fire_at: String,
    pub recurring: bool,
    /// RFC3339 creation timestamp.
    pub created_at: String,
}

/// Response of `workspace.tasks_snapshot` — outstanding background tasks and
/// live scheduled tasks.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TasksSnapshotResponse {
    pub background_tasks: Vec<BackgroundTaskSnapshotWire>,
    pub scheduled_tasks: Vec<ScheduledTaskSnapshotWire>,
}

/// `workspace.tasks_snapshot` — point-in-time snapshot of the session's
/// outstanding background tasks and scheduled tasks, for task
/// UI rebuild on client attach/reconnect.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TasksSnapshotReq {
    pub session_id: String,
}

impl WorkspaceRpc for TasksSnapshotReq {
    const METHOD: &'static str = "workspace.tasks_snapshot";
    type Response = TasksSnapshotResponse;
}

/// One TODO list item (slim DTO over `kigi_tools`'s `TodoState`). `status`
/// is the snake_case tag: `pending` | `in_progress` | `completed` | `cancelled`.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct TodoSummaryWire {
    pub id: String,
    pub content: String,
    pub status: String,
}

/// Response of `workspace.list_todos` — the full TODO list for the session.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ListTodosResponse {
    pub todos: Vec<TodoSummaryWire>,
}

/// `workspace.list_todos` — list the session's TODO items for post-compaction
/// `<system-reminder>` state. Caller supplies the hub-bound session id.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ListTodosReq {
    pub session_id: String,
}

impl WorkspaceRpc for ListTodosReq {
    const METHOD: &'static str = "workspace.list_todos";
    type Response = ListTodosResponse;
}

/// Typed response of `workspace.info`.
///
/// SYNC: matches the object built by the `workspace.info` dispatch arm
/// in `kigi-workspace/src/hub_server.rs`.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WorkspaceInfo {
    /// `std::env::consts::OS` on the server (e.g. `"linux"`).
    pub os: String,
    /// Shell basename (e.g. `"bash"`); `"sh"` when `$SHELL` is unset.
    pub shell: String,
    pub cwd: String,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn workspace_info_deserializes_server_shape() {
        let raw = serde_json::json!({
            "os": "linux",
            "shell": "bash",
            "cwd": "/workspace",
        });
        let info: WorkspaceInfo = serde_json::from_value(raw).unwrap();
        assert_eq!(info.os, "linux");
        assert_eq!(info.shell, "bash");
        assert_eq!(info.cwd, "/workspace");
    }

    #[test]
    fn workspace_info_ignores_unknown_fields() {
        let raw = serde_json::json!({
            "os": "linux",
            "shell": "zsh",
            "cwd": "/workspace",
            "future_field": 42,
        });
        let info: WorkspaceInfo = serde_json::from_value(raw).unwrap();
        assert_eq!(info.shell, "zsh");
    }

    #[test]
    fn method_constant() {
        assert_eq!(WorkspaceInfoReq::METHOD, "workspace.info");
        assert_eq!(
            LoadProjectConfigReq::METHOD,
            "workspace.load_project_config"
        );
        assert_eq!(LoadPermissionsReq::METHOD, "workspace.load_permissions");
        assert_eq!(LoadEnvrcReq::METHOD, "workspace.load_envrc");
        assert_eq!(ToolDefinitionsReq::METHOD, "workspace.tool_definitions");
        assert_eq!(
            ResolveFileReferencesReq::METHOD,
            "workspace.resolve_file_references"
        );
        assert_eq!(UpdateToolConfigReq::METHOD, "workspace.update_tool_config");
        assert_eq!(DropSessionReq::METHOD, "workspace.drop_session");
        assert_eq!(ConfigureMcpReq::METHOD, "workspace.configure_mcp");
        assert_eq!(InstallPluginReq::METHOD, "workspace.install_plugin");
        assert_eq!(RefreshPluginsReq::METHOD, "workspace.refresh_plugins");
    }
}
