use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AppServerMethodSpec {
    pub method: &'static str,
    pub params_type: String,
    pub result_type: String,
    pub stability: AppServerMethodStability,
    pub feature_group: &'static str,
    pub idempotency: AppServerIdempotency,
    pub side_effect: AppServerSideEffect,
    #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
    pub notifications: &'static [&'static str],
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum AppServerMethodStability {
    Stable,
    Experimental,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum AppServerIdempotency {
    Idempotent,
    NonIdempotent,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum AppServerSideEffect {
    ReadOnly,
    LocalState,
    ExternalProcess,
}

#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AppServerMethodManifest {
    pub schema_version: u32,
    pub unknown_methods_allowed: bool,
    pub methods: Vec<AppServerMethodSpec>,
}

pub const APP_SERVER_METHOD_MANIFEST_VERSION: u32 = 1;

pub fn app_server_method_manifest() -> AppServerMethodManifest {
    AppServerMethodManifest {
        schema_version: APP_SERVER_METHOD_MANIFEST_VERSION,
        unknown_methods_allowed: true,
        methods: app_server_method_specs(),
    }
}

pub fn app_server_method_specs() -> Vec<AppServerMethodSpec> {
    METHOD_SPECS
        .iter()
        .map(|seed| AppServerMethodSpec {
            method: seed.method,
            params_type: method_type_name(seed.method, "Params"),
            result_type: method_type_name(seed.method, "Result"),
            stability: seed.stability,
            feature_group: seed.feature_group,
            idempotency: seed.idempotency,
            side_effect: seed.side_effect,
            notifications: seed.notifications,
        })
        .collect()
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct AppServerMethodSpecSeed {
    method: &'static str,
    stability: AppServerMethodStability,
    feature_group: &'static str,
    idempotency: AppServerIdempotency,
    side_effect: AppServerSideEffect,
    notifications: &'static [&'static str],
}

fn method_type_name(method: &str, suffix: &str) -> String {
    if let Some(name) = explicit_method_type_name(method, suffix) {
        return name.to_string();
    }
    let mut name = String::new();
    let mut capitalize_next = true;
    for ch in method.chars() {
        if ch.is_ascii_alphanumeric() {
            if capitalize_next {
                name.push(ch.to_ascii_uppercase());
                capitalize_next = false;
            } else {
                name.push(ch);
            }
        } else {
            capitalize_next = true;
        }
    }
    name.push_str(suffix);
    name
}

fn explicit_method_type_name(method: &str, suffix: &str) -> Option<&'static str> {
    match (method, suffix) {
        ("vcs/status", "Params") | ("vcs/lines/list", "Params") => Some("VcsWorkspaceParams"),
        ("vcs/status", "Result") => Some("VcsStatus"),
        ("vcs/changes/read", "Result") => Some("VcsChangedContentPage"),
        ("vcs/lines/list", "Result") => Some("Vec<VcsLineOfWork>"),
        ("vcs/lines/switch", "Params") => Some("VcsLineSwitchParams"),
        ("vcs/lines/switch", "Result")
        | ("vcs/restore", "Result")
        | ("vcs/select", "Result")
        | ("vcs/sync", "Result") => Some("VcsOperationResult"),
        ("vcs/select", "Params") => Some("VcsSelectionParams"),
        ("vcs/snapshot/create", "Result") => Some("VcsSnapshot"),
        _ => None,
    }
}

macro_rules! method_spec {
    ($method:literal, $group:literal, $effect:ident, $idem:ident) => {
        AppServerMethodSpecSeed {
            method: $method,
            stability: AppServerMethodStability::Stable,
            feature_group: $group,
            idempotency: AppServerIdempotency::$idem,
            side_effect: AppServerSideEffect::$effect,
            notifications: &[],
        }
    };
    ($method:literal, $group:literal, $effect:ident, $idem:ident, [$($notification:literal),+ $(,)?]) => {
        AppServerMethodSpecSeed {
            method: $method,
            stability: AppServerMethodStability::Stable,
            feature_group: $group,
            idempotency: AppServerIdempotency::$idem,
            side_effect: AppServerSideEffect::$effect,
            notifications: &[$($notification),+],
        }
    };
}

const METHOD_SPECS: &[AppServerMethodSpecSeed] = &[
    method_spec!("agents/list", "agents", ReadOnly, Idempotent),
    method_spec!("artifact/delete", "artifacts", LocalState, NonIdempotent),
    method_spec!("artifact/grep", "artifacts", ReadOnly, Idempotent),
    method_spec!("artifact/list", "artifacts", ReadOnly, Idempotent),
    method_spec!("artifact/read", "artifacts", ReadOnly, Idempotent),
    method_spec!("artifact/tail", "artifacts", ReadOnly, Idempotent),
    method_spec!("auth/codex/login", "auth", ExternalProcess, NonIdempotent),
    method_spec!("auth/codex/logout", "auth", LocalState, NonIdempotent),
    method_spec!("auth/codex/status", "auth", ReadOnly, Idempotent),
    method_spec!(
        "auth/supergrok/login",
        "auth",
        ExternalProcess,
        NonIdempotent
    ),
    method_spec!("auth/supergrok/logout", "auth", LocalState, NonIdempotent),
    method_spec!("auth/supergrok/status", "auth", ReadOnly, Idempotent),
    method_spec!(
        "automations/cancelRun",
        "automations",
        LocalState,
        NonIdempotent
    ),
    method_spec!(
        "automations/create",
        "automations",
        LocalState,
        NonIdempotent
    ),
    method_spec!(
        "automations/delete",
        "automations",
        LocalState,
        NonIdempotent
    ),
    method_spec!("automations/list", "automations", ReadOnly, Idempotent),
    method_spec!(
        "automations/runNow",
        "automations",
        LocalState,
        NonIdempotent
    ),
    method_spec!("automations/runs", "automations", ReadOnly, Idempotent),
    method_spec!("automations/status", "automations", ReadOnly, Idempotent),
    method_spec!(
        "automations/update",
        "automations",
        LocalState,
        NonIdempotent
    ),
    method_spec!("chrome/browsers/list", "chrome", ReadOnly, Idempotent),
    method_spec!("chrome/debug/console", "chrome", ReadOnly, Idempotent),
    method_spec!("chrome/debug/network", "chrome", ReadOnly, Idempotent),
    method_spec!("chrome/disable", "chrome", LocalState, NonIdempotent),
    method_spec!("chrome/enable", "chrome", LocalState, NonIdempotent),
    method_spec!(
        "chrome/page/action",
        "chrome",
        ExternalProcess,
        NonIdempotent
    ),
    method_spec!("chrome/page/snapshot", "chrome", ReadOnly, Idempotent),
    method_spec!("chrome/permissions/list", "chrome", ReadOnly, Idempotent),
    method_spec!(
        "chrome/permissions/update",
        "chrome",
        LocalState,
        NonIdempotent
    ),
    method_spec!("chrome/reconnect", "chrome", ReadOnly, Idempotent),
    method_spec!("chrome/setMode", "chrome", LocalState, NonIdempotent),
    method_spec!("chrome/status", "chrome", ReadOnly, Idempotent),
    method_spec!(
        "chrome/tabs/activate",
        "chrome",
        ExternalProcess,
        NonIdempotent
    ),
    method_spec!("chrome/tabs/list", "chrome", ReadOnly, Idempotent),
    method_spec!(
        "chrome/tabs/navigate",
        "chrome",
        ExternalProcess,
        NonIdempotent
    ),
    method_spec!("command/exec", "commands", ExternalProcess, NonIdempotent),
    method_spec!("commands/expand", "commands", ReadOnly, Idempotent),
    method_spec!("commands/list", "commands", ReadOnly, Idempotent),
    method_spec!("commands/run", "commands", LocalState, NonIdempotent),
    method_spec!("design/batch_get", "design", ReadOnly, Idempotent),
    method_spec!(
        "design/export_nodes",
        "design",
        LocalState,
        NonIdempotent,
        ["design/exportCompleted"]
    ),
    method_spec!("design/get_editor_state", "design", ReadOnly, Idempotent),
    method_spec!("design/get_guidelines", "design", ReadOnly, Idempotent),
    method_spec!("design/get_screenshot", "design", ReadOnly, Idempotent),
    method_spec!("design/get_variables", "design", ReadOnly, Idempotent),
    method_spec!(
        "design/patch",
        "design",
        LocalState,
        NonIdempotent,
        ["design/documentChanged"]
    ),
    method_spec!("design/read", "design", ReadOnly, Idempotent),
    method_spec!(
        "design/set_selection",
        "design",
        LocalState,
        NonIdempotent,
        ["design/selectionChanged"]
    ),
    method_spec!(
        "design/set_variables",
        "design",
        LocalState,
        NonIdempotent,
        ["design/documentChanged"]
    ),
    method_spec!("design/snapshot_layout", "design", ReadOnly, Idempotent),
    method_spec!("design/spawn_agents", "design", ReadOnly, NonIdempotent),
    method_spec!("discovery/groups", "discovery", ReadOnly, Idempotent),
    method_spec!("discovery/promote", "discovery", LocalState, NonIdempotent),
    method_spec!(
        "discovery/promoted/clear",
        "discovery",
        LocalState,
        NonIdempotent
    ),
    method_spec!("discovery/promoted/list", "discovery", ReadOnly, Idempotent),
    method_spec!("discovery/read", "discovery", ReadOnly, Idempotent),
    method_spec!("discovery/refresh", "discovery", LocalState, NonIdempotent),
    method_spec!("discovery/search", "discovery", ReadOnly, Idempotent),
    method_spec!("eval/report/read", "eval", ReadOnly, Idempotent),
    method_spec!("eval/reports/list", "eval", ReadOnly, Idempotent),
    method_spec!("extensions/list", "extensions", ReadOnly, Idempotent),
    method_spec!("forks/create", "forks", LocalState, NonIdempotent),
    method_spec!("forks/list", "forks", ReadOnly, Idempotent),
    method_spec!("forks/providers/list", "forks", ReadOnly, Idempotent),
    method_spec!("forks/remove", "forks", LocalState, NonIdempotent),
    method_spec!("fs/readDirectory", "filesystem", ReadOnly, Idempotent),
    method_spec!("fs/readFile", "filesystem", ReadOnly, Idempotent),
    method_spec!("hosted/audit/list", "hosted", ReadOnly, Idempotent),
    method_spec!("hosted/hooks/create", "hosted", LocalState, NonIdempotent),
    method_spec!("hosted/hooks/delete", "hosted", LocalState, NonIdempotent),
    method_spec!("hosted/hooks/list", "hosted", ReadOnly, Idempotent),
    method_spec!("hosted/hooks/update", "hosted", LocalState, NonIdempotent),
    method_spec!(
        "hosted/service_accounts/create",
        "hosted",
        LocalState,
        NonIdempotent
    ),
    method_spec!(
        "hosted/service_accounts/list",
        "hosted",
        ReadOnly,
        Idempotent
    ),
    method_spec!(
        "hosted/service_accounts/revoke",
        "hosted",
        LocalState,
        NonIdempotent
    ),
    method_spec!("hosted/tenant/read", "hosted", ReadOnly, Idempotent),
    method_spec!("hosted/tenants/list", "hosted", ReadOnly, Idempotent),
    method_spec!("hosted/usage/read", "hosted", ReadOnly, Idempotent),
    method_spec!("hosted/whoami", "hosted", ReadOnly, Idempotent),
    method_spec!("hunk/list", "plan-review", ReadOnly, Idempotent),
    method_spec!("hunk/read", "plan-review", ReadOnly, Idempotent),
    method_spec!("hunk/rollback", "plan-review", LocalState, NonIdempotent),
    method_spec!("index/proofs/list", "code-index", ReadOnly, Idempotent),
    method_spec!("index/readChunk", "code-index", ReadOnly, Idempotent),
    method_spec!("index/rebuild", "code-index", LocalState, NonIdempotent),
    method_spec!("index/search", "code-index", ReadOnly, Idempotent),
    method_spec!("index/status", "code-index", ReadOnly, Idempotent),
    method_spec!(
        "inference/routing/metrics",
        "inference",
        ReadOnly,
        Idempotent
    ),
    method_spec!(
        "inference/routing/status",
        "inference",
        ReadOnly,
        Idempotent
    ),
    method_spec!("initialize", "app", ReadOnly, Idempotent),
    method_spec!("knowledge/delete", "knowledge", LocalState, NonIdempotent),
    method_spec!(
        "knowledge/links/set",
        "knowledge",
        LocalState,
        NonIdempotent
    ),
    method_spec!("knowledge/list", "knowledge", ReadOnly, Idempotent),
    method_spec!("knowledge/read", "knowledge", ReadOnly, Idempotent),
    method_spec!(
        "knowledge/revisions/list",
        "knowledge",
        ReadOnly,
        Idempotent
    ),
    method_spec!("knowledge/save", "knowledge", LocalState, NonIdempotent),
    method_spec!("knowledge/search", "knowledge", ReadOnly, Idempotent),
    method_spec!("knowledge/update", "knowledge", LocalState, NonIdempotent),
    method_spec!(
        "marketplaces/add",
        "marketplaces",
        LocalState,
        NonIdempotent
    ),
    method_spec!(
        "marketplaces/install_default",
        "marketplaces",
        LocalState,
        NonIdempotent
    ),
    method_spec!("marketplaces/list", "marketplaces", ReadOnly, Idempotent),
    method_spec!("marketplaces/plugin", "marketplaces", ReadOnly, Idempotent),
    method_spec!(
        "marketplaces/refresh",
        "marketplaces",
        LocalState,
        NonIdempotent
    ),
    method_spec!(
        "marketplaces/remove",
        "marketplaces",
        LocalState,
        NonIdempotent
    ),
    method_spec!("marketplaces/search", "marketplaces", ReadOnly, Idempotent),
    method_spec!("media/attachToTurn", "media", LocalState, NonIdempotent),
    method_spec!("media/delete", "media", LocalState, NonIdempotent),
    method_spec!("media/image/generate", "media", LocalState, NonIdempotent),
    method_spec!("media/image/providers/list", "media", ReadOnly, Idempotent),
    method_spec!("media/list", "media", ReadOnly, Idempotent),
    method_spec!("media/read", "media", ReadOnly, Idempotent),
    method_spec!("media/thumbnail", "media", ReadOnly, Idempotent),
    method_spec!("memory/delete", "memory", LocalState, NonIdempotent),
    method_spec!("memory/list", "memory", ReadOnly, Idempotent),
    method_spec!("memory/provider/list", "memory", ReadOnly, Idempotent),
    method_spec!("memory/provider/set", "memory", LocalState, NonIdempotent),
    method_spec!("memory/query", "memory", ReadOnly, Idempotent),
    method_spec!("memory/read", "memory", ReadOnly, Idempotent),
    method_spec!("memory/recall/preview", "memory", ReadOnly, Idempotent),
    method_spec!("memory/save", "memory", LocalState, NonIdempotent),
    method_spec!("memory/update", "memory", LocalState, NonIdempotent),
    method_spec!("model/list", "models", ReadOnly, Idempotent),
    method_spec!("model/select", "models", LocalState, NonIdempotent),
    method_spec!("node/status", "node", ReadOnly, Idempotent),
    method_spec!(
        "packages/approve_extensions",
        "packages",
        LocalState,
        NonIdempotent
    ),
    method_spec!(
        "packages/install",
        "packages",
        ExternalProcess,
        NonIdempotent
    ),
    method_spec!("packages/list", "packages", ReadOnly, Idempotent),
    method_spec!("packages/remove", "packages", LocalState, NonIdempotent),
    method_spec!(
        "packages/set_enabled",
        "packages",
        LocalState,
        NonIdempotent
    ),
    method_spec!(
        "packages/set_filters",
        "packages",
        LocalState,
        NonIdempotent
    ),
    method_spec!("packages/sync", "packages", ExternalProcess, NonIdempotent),
    method_spec!(
        "packages/update",
        "packages",
        ExternalProcess,
        NonIdempotent
    ),
    method_spec!(
        "plan/review/approve",
        "plan-review",
        LocalState,
        NonIdempotent
    ),
    method_spec!(
        "plan/review/comment",
        "plan-review",
        LocalState,
        NonIdempotent
    ),
    method_spec!("plan/review/read", "plan-review", ReadOnly, Idempotent),
    method_spec!(
        "plan/review/reject",
        "plan-review",
        LocalState,
        NonIdempotent
    ),
    method_spec!(
        "plan/review/rewrite",
        "plan-review",
        LocalState,
        NonIdempotent
    ),
    method_spec!("plugins/disable", "plugins", LocalState, NonIdempotent),
    method_spec!("plugins/install", "plugins", LocalState, NonIdempotent),
    method_spec!(
        "plugins/install_all_variants",
        "plugins",
        LocalState,
        NonIdempotent
    ),
    method_spec!("plugins/list_installed", "plugins", ReadOnly, Idempotent),
    method_spec!("plugins/preview_install", "plugins", ReadOnly, Idempotent),
    method_spec!("plugins/uninstall", "plugins", LocalState, NonIdempotent),
    method_spec!("processes/get", "processes", ReadOnly, Idempotent),
    method_spec!("processes/list", "processes", ReadOnly, Idempotent),
    method_spec!("processes/stop", "processes", LocalState, NonIdempotent),
    method_spec!("processes/stopAll", "processes", LocalState, NonIdempotent),
    method_spec!(
        "processes/subscribe",
        "processes",
        ReadOnly,
        Idempotent,
        ["processes/changed"]
    ),
    method_spec!("providers/clear", "providers", LocalState, NonIdempotent),
    method_spec!(
        "providers/configure",
        "providers",
        LocalState,
        NonIdempotent
    ),
    method_spec!("providers/list", "providers", ReadOnly, Idempotent),
    method_spec!("providers/select", "providers", LocalState, NonIdempotent),
    method_spec!("retrieval/metrics", "retrieval", ReadOnly, Idempotent),
    method_spec!("retrieval/promoted", "retrieval", ReadOnly, Idempotent),
    method_spec!(
        "retrieval/recommendations",
        "retrieval",
        ReadOnly,
        Idempotent
    ),
    method_spec!("roadmap/create", "roadmap", LocalState, NonIdempotent),
    method_spec!("roadmap/list", "roadmap", ReadOnly, Idempotent),
    method_spec!("roadmap/patch", "roadmap", LocalState, NonIdempotent),
    method_spec!("roadmap/read", "roadmap", ReadOnly, Idempotent),
    method_spec!("roadmap/task/update", "roadmap", LocalState, NonIdempotent),
    method_spec!(
        "roadmap/thread/attach",
        "roadmap",
        LocalState,
        NonIdempotent
    ),
    method_spec!("roadmap/thread/list", "roadmap", ReadOnly, Idempotent),
    method_spec!(
        "roadmap/thread/spawn",
        "roadmap",
        ExternalProcess,
        NonIdempotent
    ),
    method_spec!("roadmap/validate", "roadmap", ReadOnly, Idempotent),
    method_spec!("runners/delete", "runners", LocalState, NonIdempotent),
    method_spec!("runners/list", "runners", ReadOnly, Idempotent),
    method_spec!("runners/ports", "runners", ReadOnly, Idempotent),
    method_spec!("runners/select", "runners", LocalState, NonIdempotent),
    method_spec!("runners/session", "runners", ReadOnly, Idempotent),
    method_spec!("runners/snapshot", "runners", ReadOnly, Idempotent),
    method_spec!(
        "search_index/clear",
        "search-index",
        LocalState,
        NonIdempotent
    ),
    method_spec!(
        "search_index/rebuild",
        "search-index",
        LocalState,
        NonIdempotent
    ),
    method_spec!("search_index/status", "search-index", ReadOnly, Idempotent),
    method_spec!(
        "search_index/warmup",
        "search-index",
        LocalState,
        NonIdempotent
    ),
    method_spec!("settings/get", "settings", ReadOnly, Idempotent),
    method_spec!(
        "settings/set_default_mode",
        "settings",
        LocalState,
        NonIdempotent
    ),
    method_spec!(
        "settings/set_file_backed_dynamic_context",
        "settings",
        LocalState,
        NonIdempotent
    ),
    method_spec!(
        "settings/set_search_index",
        "settings",
        LocalState,
        NonIdempotent
    ),
    method_spec!("settings/set_shell", "settings", LocalState, NonIdempotent),
    method_spec!(
        "settings/set_web_search",
        "settings",
        LocalState,
        NonIdempotent
    ),
    method_spec!("skills/list", "skills", ReadOnly, Idempotent),
    method_spec!("skills/read", "skills", ReadOnly, Idempotent),
    method_spec!("skills/setEnabled", "skills", LocalState, NonIdempotent),
    method_spec!("skills/setExposure", "skills", LocalState, NonIdempotent),
    method_spec!("speech/providers/list", "speech", ReadOnly, Idempotent),
    method_spec!(
        "speech/synthesis/providers/list",
        "speech",
        ReadOnly,
        Idempotent
    ),
    method_spec!(
        "speech/synthesize",
        "speech",
        ExternalProcess,
        NonIdempotent
    ),
    method_spec!(
        "speech/transcribe",
        "speech",
        ExternalProcess,
        NonIdempotent
    ),
    method_spec!("stats/backfill", "stats", LocalState, NonIdempotent),
    method_spec!("stats/export", "stats", LocalState, NonIdempotent),
    method_spec!("stats/sessions", "stats", ReadOnly, Idempotent),
    method_spec!("stats/summary", "stats", ReadOnly, Idempotent),
    method_spec!("stats/tokens", "stats", ReadOnly, Idempotent),
    method_spec!("stats/tools", "stats", ReadOnly, Idempotent),
    method_spec!("tasks/cancel", "tasks", LocalState, NonIdempotent),
    method_spec!("tasks/get", "tasks", ReadOnly, Idempotent),
    method_spec!("tasks/list", "tasks", ReadOnly, Idempotent),
    method_spec!("tasks/submit", "tasks", LocalState, NonIdempotent),
    method_spec!(
        "tasks/subscribe",
        "tasks",
        ReadOnly,
        Idempotent,
        [
            "task.started",
            "task.output",
            "task.completed",
            "task.failed",
            "task.cancelled",
        ]
    ),
    method_spec!("team/cleanup", "teams", LocalState, NonIdempotent),
    method_spec!("team/list", "teams", ReadOnly, Idempotent),
    method_spec!("team/member/focus", "teams", LocalState, NonIdempotent),
    method_spec!("team/member/interrupt", "teams", LocalState, NonIdempotent),
    method_spec!("team/member/message", "teams", LocalState, NonIdempotent),
    method_spec!("team/member/start", "teams", LocalState, NonIdempotent),
    method_spec!("team/pane/cleanup", "teams", LocalState, NonIdempotent),
    method_spec!("team/pane/focus", "teams", LocalState, NonIdempotent),
    method_spec!("team/read", "teams", ReadOnly, Idempotent),
    method_spec!("team/start", "teams", LocalState, NonIdempotent),
    method_spec!("thread/archive", "thread", LocalState, NonIdempotent),
    method_spec!("thread/attach", "thread", LocalState, NonIdempotent),
    method_spec!("thread/exit_plan", "thread", LocalState, NonIdempotent),
    method_spec!("thread/fork", "thread", LocalState, NonIdempotent),
    method_spec!("thread/fork_status", "thread", ReadOnly, Idempotent),
    method_spec!(
        "thread/goal/clear",
        "thread",
        LocalState,
        NonIdempotent,
        ["thread/goal/cleared"]
    ),
    method_spec!("thread/goal/get", "thread", ReadOnly, Idempotent),
    method_spec!(
        "thread/goal/set",
        "thread",
        LocalState,
        NonIdempotent,
        ["thread/goal/updated"]
    ),
    method_spec!("thread/list", "thread", ReadOnly, Idempotent),
    method_spec!("thread/read", "thread", ReadOnly, Idempotent),
    method_spec!("thread/remove_fork", "thread", LocalState, NonIdempotent),
    method_spec!(
        "thread/resolve_approval",
        "thread",
        LocalState,
        NonIdempotent
    ),
    method_spec!(
        "thread/resolve_user_input",
        "thread",
        LocalState,
        NonIdempotent
    ),
    method_spec!("thread/roadmap/open", "thread", LocalState, NonIdempotent),
    method_spec!("thread/set_mode", "thread", LocalState, NonIdempotent),
    method_spec!("thread/start", "thread", LocalState, NonIdempotent),
    method_spec!("thread/state", "thread", ReadOnly, Idempotent),
    method_spec!("tools/call", "tools", LocalState, NonIdempotent),
    method_spec!("tools/list", "tools", ReadOnly, Idempotent),
    method_spec!("tools/resolve", "tools", LocalState, NonIdempotent),
    method_spec!("turn/interrupt", "turns", LocalState, NonIdempotent),
    method_spec!("turn/start", "turns", LocalState, NonIdempotent),
    method_spec!("turn/steer", "turns", LocalState, NonIdempotent),
    method_spec!("turn/subagentTrace/read", "turns", ReadOnly, Idempotent),
    method_spec!("turn/subagentTraces/list", "turns", ReadOnly, Idempotent),
    method_spec!("vcs/changes/list", "vcs", ReadOnly, Idempotent),
    method_spec!("vcs/changes/read", "vcs", ReadOnly, Idempotent),
    method_spec!("vcs/lines/list", "vcs", ReadOnly, Idempotent),
    method_spec!("vcs/lines/switch", "vcs", LocalState, NonIdempotent),
    method_spec!("vcs/restore", "vcs", LocalState, NonIdempotent),
    method_spec!("vcs/select", "vcs", LocalState, NonIdempotent),
    method_spec!("vcs/snapshot/create", "vcs", LocalState, NonIdempotent),
    method_spec!("vcs/status", "vcs", ReadOnly, Idempotent),
    method_spec!("vcs/sync", "vcs", ExternalProcess, NonIdempotent),
    method_spec!("webwright/artifacts", "webwright", ReadOnly, Idempotent),
    method_spec!("webwright/export", "webwright", LocalState, NonIdempotent),
    method_spec!("webwright/latestRun", "webwright", ReadOnly, Idempotent),
    method_spec!("webwright/prepare", "webwright", LocalState, NonIdempotent),
    method_spec!("webwright/report", "webwright", ReadOnly, Idempotent),
    method_spec!(
        "webwright/rerun",
        "webwright",
        ExternalProcess,
        NonIdempotent
    ),
    method_spec!(
        "webwright/setup",
        "webwright",
        ExternalProcess,
        NonIdempotent
    ),
    method_spec!("webwright/submit", "webwright", LocalState, NonIdempotent),
    method_spec!("webwright/verify", "webwright", ReadOnly, Idempotent),
    method_spec!(
        "webwright/visualJudge",
        "webwright",
        LocalState,
        NonIdempotent
    ),
    method_spec!("workflow/enable", "workflow", LocalState, NonIdempotent),
    method_spec!("workflow/ignore", "workflow", LocalState, NonIdempotent),
    method_spec!("workflow/preview", "workflow", ReadOnly, Idempotent),
    method_spec!("workflow/refresh", "workflow", LocalState, NonIdempotent),
    method_spec!("workflow/remove", "workflow", LocalState, NonIdempotent),
    method_spec!("workflow/scan", "workflow", ReadOnly, Idempotent),
    method_spec!(
        "workflows/approve",
        "workflows",
        LocalState,
        NonIdempotent,
        [
            "workflows/approved",
            "workflows/denied",
            "workflows/queued",
            "workflows/started",
        ]
    ),
    method_spec!("workflows/get", "workflows", ReadOnly, Idempotent),
    method_spec!("workflows/list", "workflows", ReadOnly, Idempotent),
    method_spec!(
        "workflows/pause",
        "workflows",
        LocalState,
        NonIdempotent,
        ["workflows/paused"]
    ),
    method_spec!(
        "workflows/plan",
        "workflows",
        LocalState,
        NonIdempotent,
        ["workflows/drafted", "workflows/approvalRequested"]
    ),
    method_spec!(
        "workflows/restartAgent",
        "workflows",
        LocalState,
        NonIdempotent,
        [
            "workflows/agentQueued",
            "workflows/agentStarted",
            "workflows/agentCompleted",
            "workflows/agentFailed",
        ]
    ),
    method_spec!(
        "workflows/resume",
        "workflows",
        LocalState,
        NonIdempotent,
        ["workflows/resumed"]
    ),
    method_spec!("workflows/save", "workflows", LocalState, NonIdempotent),
    method_spec!(
        "workflows/scripts/delete",
        "workflows",
        LocalState,
        NonIdempotent
    ),
    method_spec!("workflows/scripts/list", "workflows", ReadOnly, Idempotent),
    method_spec!("workflows/scripts/read", "workflows", ReadOnly, Idempotent),
    method_spec!(
        "workflows/stop",
        "workflows",
        LocalState,
        NonIdempotent,
        ["workflows/stopped"]
    ),
    method_spec!(
        "workspace/changes/list",
        "workspace",
        ReadOnly,
        Idempotent,
        ["workspace/changeObserved"]
    ),
    method_spec!("workspace/create", "workspace", LocalState, NonIdempotent),
    method_spec!(
        "workspace/files/children",
        "workspace-files",
        ReadOnly,
        Idempotent
    ),
    method_spec!(
        "workspace/files/query",
        "workspace-files",
        ReadOnly,
        Idempotent
    ),
    method_spec!(
        "workspace/files/read",
        "workspace-files",
        ReadOnly,
        Idempotent
    ),
    method_spec!(
        "workspace/files/rebuild",
        "workspace-files",
        LocalState,
        NonIdempotent,
        ["workspace/files/statusChanged"]
    ),
    method_spec!(
        "workspace/files/status",
        "workspace-files",
        ReadOnly,
        Idempotent
    ),
    method_spec!("workspace/forget", "workspace", LocalState, NonIdempotent),
    method_spec!("workspace/list", "workspace", ReadOnly, Idempotent),
    method_spec!("workspace/update", "workspace", LocalState, NonIdempotent),
];

#[cfg(test)]
mod tests {
    use std::collections::BTreeSet;

    use super::*;

    #[test]
    fn method_manifest_is_sorted_and_unique() {
        let methods = app_server_method_specs();
        let names = methods.iter().map(|spec| spec.method).collect::<Vec<_>>();
        let mut sorted = names.clone();
        sorted.sort_unstable();
        assert_eq!(names, sorted);

        let unique = names.iter().copied().collect::<BTreeSet<_>>();
        assert_eq!(unique.len(), names.len());
    }

    #[test]
    fn method_manifest_covers_broad_app_server_surface() {
        let methods = app_server_method_specs()
            .iter()
            .map(|spec| spec.method)
            .collect::<BTreeSet<_>>();
        for required in [
            "providers/list",
            "settings/get",
            "thread/start",
            "turn/start",
            "thread/resolve_approval",
            "tools/call",
            "commands/list",
            "tasks/submit",
            "webwright/prepare",
            "team/member/message",
            "plan/review/comment",
            "hunk/rollback",
            "workflow/scan",
            "workflows/plan",
            "marketplaces/search",
            "plugins/install",
            "packages/install",
            "packages/list",
            "media/list",
            "memory/query",
            "automations/list",
            "processes/list",
            "speech/providers/list",
            "speech/synthesis/providers/list",
            "speech/synthesize",
            "speech/transcribe",
            "vcs/status",
            "vcs/select",
            "workspace/files/children",
            "workspace/files/query",
            "workspace/files/read",
            "workspace/files/rebuild",
            "workspace/files/status",
        ] {
            assert!(methods.contains(required), "missing {required}");
        }
        assert!(!methods.contains("vcs/extras/list"));
    }

    #[test]
    fn method_manifest_uses_canonical_vcs_type_names() {
        let manifest = app_server_method_manifest();
        let status = manifest
            .methods
            .iter()
            .find(|method| method.method == "vcs/status")
            .expect("vcs/status method");
        assert_eq!(status.params_type, "VcsWorkspaceParams");
        assert_eq!(status.result_type, "VcsStatus");

        let select = manifest
            .methods
            .iter()
            .find(|method| method.method == "vcs/select")
            .expect("vcs/select method");
        assert_eq!(select.params_type, "VcsSelectionParams");
        assert_eq!(select.result_type, "VcsOperationResult");
    }

    #[test]
    fn method_manifest_records_subscription_notifications() {
        let methods = app_server_method_specs();
        let processes = methods
            .iter()
            .find(|spec| spec.method == "processes/subscribe")
            .expect("processes subscribe spec");
        assert_eq!(processes.notifications, ["processes/changed"]);

        let tasks = methods
            .iter()
            .find(|spec| spec.method == "tasks/subscribe")
            .expect("tasks subscribe spec");
        assert!(tasks.notifications.contains(&"task.started"));
        assert!(tasks.notifications.contains(&"task.completed"));

        let workflows_plan = methods
            .iter()
            .find(|spec| spec.method == "workflows/plan")
            .expect("workflows plan spec");
        assert!(workflows_plan.notifications.contains(&"workflows/drafted"));
        assert!(
            workflows_plan
                .notifications
                .contains(&"workflows/approvalRequested")
        );

        let workspace_files_rebuild = methods
            .iter()
            .find(|spec| spec.method == "workspace/files/rebuild")
            .expect("workspace files rebuild spec");
        assert_eq!(
            workspace_files_rebuild.notifications,
            ["workspace/files/statusChanged"]
        );
    }
}
