#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InvocationKind {
    Command,
    Query,
    Job,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ToolExecutionMode {
    Plan,
    Result,
    ExplainPlan,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ToolInvocation {
    pub tool: String,
    pub kind: InvocationKind,
    pub mode: ToolExecutionMode,
    pub args_json: String,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ToolEffect {
    Read,
    Write,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ToolSpec {
    pub name: &'static str,
    pub display_name: &'static str,
    pub description: &'static str,
    pub toolset_id: &'static str,
    pub invocation_kind: InvocationKind,
    pub effect: ToolEffect,
    pub requires_confirmation: bool,
    pub input_schema_json: &'static str,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ToolSetSpec {
    pub id: &'static str,
    pub display_name: &'static str,
    pub description: &'static str,
    pub write_toolset: bool,
    pub tool_names: &'static [&'static str],
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ToolRegistry {
    pub toolsets: &'static [ToolSetSpec],
    pub tools: &'static [ToolSpec],
}

impl ToolRegistry {
    pub const fn new(toolsets: &'static [ToolSetSpec], tools: &'static [ToolSpec]) -> Self {
        Self { toolsets, tools }
    }

    pub fn tool(&self, name: &str) -> Option<&'static ToolSpec> {
        self.tools.iter().find(|spec| spec.name == name)
    }

    pub fn toolset(&self, id: &str) -> Option<&'static ToolSetSpec> {
        self.toolsets.iter().find(|spec| spec.id == id)
    }

    pub fn tool_names(&self) -> Vec<&'static str> {
        self.tools.iter().map(|spec| spec.name).collect()
    }

    pub fn tool_names_in_set(&self, id: &str) -> Option<Vec<&'static str>> {
        self.toolset(id).map(|toolset| toolset.tool_names.to_vec())
    }

    pub fn tools_in_set(&self, id: &str) -> Option<Vec<&'static ToolSpec>> {
        self.toolset(id).map(|toolset| {
            toolset
                .tool_names
                .iter()
                .filter_map(|tool_name| self.tool(tool_name))
                .collect()
        })
    }
}

pub const DOC_TOOL_GET: ToolSpec = ToolSpec {
    name: "doc_get",
    display_name: "文档读取",
    description: "读取当前文档快照，用于查看页面内容和结构。",
    toolset_id: "toolset.doc_read",
    invocation_kind: InvocationKind::Query,
    effect: ToolEffect::Read,
    requires_confirmation: false,
    input_schema_json: r#"{"type":"object","properties":{"maxBlocks":{"type":"integer","minimum":10,"maximum":240}}}"#,
};

pub const SEARCH_WEB_TOOL: ToolSpec = ToolSpec {
    name: "search_web",
    display_name: "联网检索",
    description: "使用 SearxNG 进行联网检索，返回标题、URL 与摘要。",
    toolset_id: "toolset.readonly",
    invocation_kind: InvocationKind::Query,
    effect: ToolEffect::Read,
    requires_confirmation: false,
    input_schema_json: r#"{"type":"object","required":["query"],"properties":{"query":{"type":"string"},"count":{"type":"integer","minimum":1,"maximum":10}}}"#,
};

pub const DOC_TOOL_FIND: ToolSpec = ToolSpec {
    name: "doc_find",
    display_name: "文档查找",
    description: "在当前文档范围内查找片段、标题或结构信息。",
    toolset_id: "toolset.doc_read",
    invocation_kind: InvocationKind::Query,
    effect: ToolEffect::Read,
    requires_confirmation: false,
    input_schema_json: r#"{"type":"object","required":["query"],"properties":{"query":{"type":"string"},"maxResults":{"type":"integer","minimum":1,"maximum":30}}}"#,
};

pub const DOCS_TOOL_SEARCH: ToolSpec = ToolSpec {
    name: "docs_search",
    display_name: "跨页文档搜索",
    description: "在工作区内搜索文档标题、正文、导图、表格与 OCR 结果。",
    toolset_id: "toolset.docs_read",
    invocation_kind: InvocationKind::Query,
    effect: ToolEffect::Read,
    requires_confirmation: false,
    input_schema_json: r#"{"type":"object","required":["query"],"properties":{"query":{"type":"string"},"workspaceId":{"type":"string"},"limit":{"type":"integer","minimum":1,"maximum":30},"includeDeleted":{"type":"boolean"},"pageId":{"type":"string"},"titleOnly":{"type":"boolean"},"exact":{"type":"boolean"},"includeOcr":{"type":"boolean"},"timeRange":{"type":"string"},"timeField":{"type":"string"},"customRangeFrom":{"type":"string"},"customRangeTo":{"type":"string"}}}"#,
};

pub const DOCS_TOOL_READ: ToolSpec = ToolSpec {
    name: "docs_read",
    display_name: "跨页文档读取",
    description: "读取指定文档的标题、正文摘要与可选 content 快照。",
    toolset_id: "toolset.docs_read",
    invocation_kind: InvocationKind::Query,
    effect: ToolEffect::Read,
    requires_confirmation: false,
    input_schema_json: r#"{"type":"object","required":["documentId"],"properties":{"documentId":{"type":"string"},"maxChars":{"type":"integer","minimum":200,"maximum":20000},"includeContent":{"type":"boolean"}}}"#,
};

pub const IMAGE_READ_TOOL: ToolSpec = ToolSpec {
    name: "image_read",
    display_name: "读取图片",
    description: "读取图片/附件 OCR 信息并返回归一化结果。",
    toolset_id: "toolset.media_read",
    invocation_kind: InvocationKind::Query,
    effect: ToolEffect::Read,
    requires_confirmation: false,
    input_schema_json: r#"{"type":"object","properties":{"assetId":{"type":"string"},"fileUrl":{"type":"string"},"attachmentRef":{"type":"string"}}}"#,
};

pub const DOC_TOOL_INSERT_BLOCKS: ToolSpec = ToolSpec {
    name: "doc_insert_blocks",
    display_name: "插入块",
    description: "在文档中插入一个或多个块，是写入类操作。",
    toolset_id: "toolset.doc_write",
    invocation_kind: InvocationKind::Command,
    effect: ToolEffect::Write,
    requires_confirmation: true,
    input_schema_json: r#"{"type":"object","required":["blocks"],"properties":{"afterBlockId":{"type":"string"},"beforeBlockId":{"type":"string"},"blocks":{"type":"array","minItems":1,"maxItems":20}}}"#,
};

pub const DOC_TOOL_REPLACE_RANGE: ToolSpec = ToolSpec {
    name: "doc_replace_range",
    display_name: "替换范围",
    description: "替换文档中的块范围，是写入类操作。",
    toolset_id: "toolset.doc_write",
    invocation_kind: InvocationKind::Command,
    effect: ToolEffect::Write,
    requires_confirmation: true,
    input_schema_json: r#"{"type":"object","required":["blockId","text"],"properties":{"blockId":{"type":"string"},"text":{"type":"string"},"mode":{"enum":["replace","append","prepend"]}}}"#,
};

pub const SLASH_RUN_TOOL: ToolSpec = ToolSpec {
    name: "slash_run",
    display_name: "斜杠命令",
    description: "解析并规范化斜杠命令，用于创建或重命名页面。",
    toolset_id: "toolset.slash_write",
    invocation_kind: InvocationKind::Command,
    effect: ToolEffect::Write,
    requires_confirmation: true,
    input_schema_json: r#"{"type":"object","properties":{"text":{"type":"string"},"command":{"enum":["new_doc","rename_doc"]},"params":{"type":"object"},"reason":{"type":"string"}}}"#,
};

pub const MINDMAP_TOOL_GET: ToolSpec = ToolSpec {
    name: "mindmap_get",
    display_name: "导图读取",
    description: "读取当前思维导图的节点摘要，用于定位节点和规划改动。",
    toolset_id: "toolset.mindmap_read",
    invocation_kind: InvocationKind::Query,
    effect: ToolEffect::Read,
    requires_confirmation: false,
    input_schema_json: r#"{"type":"object","properties":{"maxNodes":{"type":"integer","minimum":10,"maximum":300}}}"#,
};

pub const MINDMAP_TOOL_GET_SUBTREE: ToolSpec = ToolSpec {
    name: "mindmap_get_subtree",
    display_name: "导图子树读取",
    description: "读取指定节点 uid 的子树摘要，供 AI 精确查看局部结构。",
    toolset_id: "toolset.mindmap_read",
    invocation_kind: InvocationKind::Query,
    effect: ToolEffect::Read,
    requires_confirmation: false,
    input_schema_json: r#"{"type":"object","required":["uid"],"properties":{"uid":{"type":"string"},"depth":{"type":"integer","minimum":0,"maximum":6},"maxNodes":{"type":"integer","minimum":5,"maximum":200}}}"#,
};

pub const MINDMAP_TOOL_PUT: ToolSpec = ToolSpec {
    name: "mindmap_put",
    display_name: "导图覆盖写入",
    description: "使用完整思维导图树覆盖当前导图，适合 CLI 或 AI 在拿到完整快照后直接写回。",
    toolset_id: "toolset.mindmap_write",
    invocation_kind: InvocationKind::Command,
    effect: ToolEffect::Write,
    requires_confirmation: true,
    input_schema_json: r#"{"type":"object","required":["data"],"properties":{"data":{"type":"object"},"createOnly":{"type":"boolean"}}}"#,
};

pub const MINDMAP_TOOL_APPLY_OPS: ToolSpec = ToolSpec {
    name: "mindmap_apply_ops",
    display_name: "导图增量写入",
    description: "对思维导图应用增量 ops，并返回新的整棵树快照。",
    toolset_id: "toolset.mindmap_write",
    invocation_kind: InvocationKind::Command,
    effect: ToolEffect::Write,
    requires_confirmation: true,
    input_schema_json: r#"{"type":"object","required":["ops"],"properties":{"ops":{"type":"array","minItems":1,"maxItems":80},"reason":{"type":"string"},"targetUid":{"type":"string"},"searchResults":{"type":"array"}}}"#,
};

pub const MINDMAP_TOOL_EXPAND_NODE: ToolSpec = ToolSpec {
    name: "mindmap_expand_node",
    display_name: "补完思维导图节点",
    description: "对 AI 生成的补完候选做统一归一化，再应用到当前思维导图并返回新树。",
    toolset_id: "toolset.mindmap_write",
    invocation_kind: InvocationKind::Command,
    effect: ToolEffect::Write,
    requires_confirmation: true,
    input_schema_json: r#"{"type":"object","required":["targetUid"],"properties":{"targetUid":{"type":"string"},"instruction":{"type":"string"},"ops":{"type":"array","maxItems":80},"searchResults":{"type":"array"},"reason":{"type":"string"}}}"#,
};

pub const MINDMAP_TOOL_EMPTY_TRASH: ToolSpec = ToolSpec {
    name: "mindmap_empty_trash",
    display_name: "清空导图回收站",
    description: "校验并标准化清空导图回收站所需的工作区对象，供 route 继续执行实际写入。",
    toolset_id: "toolset.mindmap_write",
    invocation_kind: InvocationKind::Command,
    effect: ToolEffect::Write,
    requires_confirmation: true,
    input_schema_json: r#"{"type":"object","required":["workspaceId"],"properties":{"workspaceId":{"type":"string"}}}"#,
};

pub const MINDMAP_TOOL_OUTLINE_TO_MINDMAP: ToolSpec = ToolSpec {
    name: "mindmap_outline_to_mindmap",
    display_name: "大纲转导图",
    description: "把结构化大纲转换为标准思维导图树，统一 uid、refs 与 page 链接生成规则。",
    toolset_id: "toolset.mindmap_write",
    invocation_kind: InvocationKind::Command,
    effect: ToolEffect::Write,
    requires_confirmation: true,
    input_schema_json: r#"{"type":"object","required":["rootTitle","pageLinkPattern","outline"],"properties":{"rootTitle":{"type":"string"},"pageLinkPattern":{"type":"string"},"outline":{"type":"array","items":{"type":"object","required":["title","level","page"],"properties":{"title":{"type":"string"},"level":{"type":"integer","minimum":1,"maximum":6},"page":{"type":"integer","minimum":1}}}}}}"#,
};

pub const BRIDGE_TOOL_REQUEST_GET: ToolSpec = ToolSpec {
    name: "bridge_request_get",
    display_name: "请求回查",
    description: "按 request_id 查询统一 command log 与 domain event 视图。",
    toolset_id: "toolset.observe_read",
    invocation_kind: InvocationKind::Query,
    effect: ToolEffect::Read,
    requires_confirmation: false,
    input_schema_json: r#"{"type":"object","required":["workspaceId","requestId"],"properties":{"workspaceId":{"type":"string"},"requestId":{"type":"string"},"commandId":{"type":"string"}}}"#,
};

pub const BRIDGE_TOOL_TRACE_GET: ToolSpec = ToolSpec {
    name: "bridge_trace_get",
    display_name: "链路回查",
    description: "按 trace_id 查询统一 command log 与 domain event 视图。",
    toolset_id: "toolset.observe_read",
    invocation_kind: InvocationKind::Query,
    effect: ToolEffect::Read,
    requires_confirmation: false,
    input_schema_json: r#"{"type":"object","required":["workspaceId","traceId"],"properties":{"workspaceId":{"type":"string"},"traceId":{"type":"string"},"commandId":{"type":"string"}}}"#,
};

pub const BRIDGE_TOOL_COMMAND_GET: ToolSpec = ToolSpec {
    name: "bridge_command_get",
    display_name: "命令回查",
    description: "按 command_id 查询统一 command log 与 domain event 视图。",
    toolset_id: "toolset.observe_read",
    invocation_kind: InvocationKind::Query,
    effect: ToolEffect::Read,
    requires_confirmation: false,
    input_schema_json: r#"{"type":"object","required":["workspaceId","commandId"],"properties":{"workspaceId":{"type":"string"},"commandId":{"type":"string"}}}"#,
};

pub const REPLAY_TOOL_EVENTS: ToolSpec = ToolSpec {
    name: "event_replay",
    display_name: "事件回放",
    description: "基于统一事件流做回放预演，输出将被追平的 event 与派生批次摘要。",
    toolset_id: "toolset.recovery_job",
    invocation_kind: InvocationKind::Job,
    effect: ToolEffect::Write,
    requires_confirmation: true,
    input_schema_json: r#"{"type":"object","required":["workspaceId"],"properties":{"workspaceId":{"type":"string"},"lastProcessedEventId":{"type":"string"},"lastProcessedAt":{"type":"string"}}}"#,
};

pub const INDEX_TOOL_REBUILD: ToolSpec = ToolSpec {
    name: "index_rebuild",
    display_name: "索引重建",
    description: "根据事件流重建全文索引游标与派生批次，作为回放和恢复前置命令。",
    toolset_id: "toolset.recovery_job",
    invocation_kind: InvocationKind::Job,
    effect: ToolEffect::Write,
    requires_confirmation: true,
    input_schema_json: r#"{"type":"object","required":["workspaceId"],"properties":{"workspaceId":{"type":"string"},"lastProcessedEventId":{"type":"string"},"lastProcessedAt":{"type":"string"}}}"#,
};

pub const ONLYOFFICE_TOOL_SESSION_RESOLVE: ToolSpec = ToolSpec {
    name: "onlyoffice_session_resolve",
    display_name: "OnlyOffice 会话解析",
    description: "解析 OnlyOffice 的附件/页面/用户上下文，返回统一 session 与 asset 边界。",
    toolset_id: "toolset.onlyoffice_service",
    invocation_kind: InvocationKind::Job,
    effect: ToolEffect::Read,
    requires_confirmation: false,
    input_schema_json: r#"{"type":"object","required":["assetId"],"properties":{"assetId":{"type":"string"},"workspaceId":{"type":"string"},"documentId":{"type":"string"},"userId":{"type":"string"},"sessionId":{"type":"string"}}}"#,
};

pub const ONLYOFFICE_TOOL_SIGN: ToolSpec = ToolSpec {
    name: "onlyoffice_sign",
    display_name: "OnlyOffice 签名",
    description: "为 OnlyOffice config/document/editorConfig 生成 JWT 签名。",
    toolset_id: "toolset.onlyoffice_service",
    invocation_kind: InvocationKind::Job,
    effect: ToolEffect::Read,
    requires_confirmation: false,
    input_schema_json: r#"{"type":"object","properties":{"config":{"type":"object"},"secret":{"type":"string"}}}"#,
};

pub const ONLYOFFICE_TOOL_PREPARE_PROXY: ToolSpec = ToolSpec {
    name: "onlyoffice_prepare_proxy",
    display_name: "OnlyOffice 代理预处理",
    description: "校验 proxy 回源目标并返回同源回源所需的上游 URL 与请求头。",
    toolset_id: "toolset.onlyoffice_service",
    invocation_kind: InvocationKind::Job,
    effect: ToolEffect::Read,
    requires_confirmation: false,
    input_schema_json: r#"{"type":"object","required":["encodedUrl"],"properties":{"encodedUrl":{"type":"string"},"method":{"enum":["GET","HEAD"]},"range":{"type":"string"}}}"#,
};

pub const ONLYOFFICE_TOOL_PREPARE_CALLBACK: ToolSpec = ToolSpec {
    name: "onlyoffice_prepare_callback",
    display_name: "OnlyOffice callback 预处理",
    description: "解析 callback 状态、下载地址和 session 边界，供写回链继续执行。",
    toolset_id: "toolset.onlyoffice_service",
    invocation_kind: InvocationKind::Job,
    effect: ToolEffect::Write,
    requires_confirmation: false,
    input_schema_json: r#"{"type":"object","required":["assetId","status","onlyofficeInternalUrl"],"properties":{"assetId":{"type":"string"},"documentId":{"type":"string"},"workspaceId":{"type":"string"},"userId":{"type":"string"},"sessionId":{"type":"string"},"status":{"type":"integer"},"url":{"type":"string"},"key":{"type":"string"},"onlyofficeInternalUrl":{"type":"string"}}}"#,
};

pub const ONLYOFFICE_TOOL_PREPARE_FORCESAVE: ToolSpec = ToolSpec {
    name: "onlyoffice_prepare_forcesave",
    display_name: "OnlyOffice forcesave 预处理",
    description: "生成触发 forcesave 所需的请求序列与 JWT 负载。",
    toolset_id: "toolset.onlyoffice_service",
    invocation_kind: InvocationKind::Job,
    effect: ToolEffect::Write,
    requires_confirmation: false,
    input_schema_json: r#"{"type":"object","required":["assetId","key","onlyofficeInternalUrl"],"properties":{"assetId":{"type":"string"},"key":{"type":"string"},"onlyofficeInternalUrl":{"type":"string"},"secret":{"type":"string"}}}"#,
};

pub const DOC_TOOLSET_READ: ToolSetSpec = ToolSetSpec {
    id: "toolset.doc_read",
    display_name: "文档读取",
    description: "只读文档工具集合。",
    write_toolset: false,
    tool_names: &["doc_get", "doc_find"],
};

pub const DOCS_TOOLSET_READ: ToolSetSpec = ToolSetSpec {
    id: "toolset.docs_read",
    display_name: "跨页文档读取",
    description: "跨页面搜索与读取文档的工具集合。",
    write_toolset: false,
    tool_names: &["docs_search", "docs_read"],
};

pub const READONLY_TOOLSET: ToolSetSpec = ToolSetSpec {
    id: "toolset.readonly",
    display_name: "只读工具",
    description: "Rust 只读工具集合。",
    write_toolset: false,
    tool_names: &["search_web"],
};

pub const MEDIA_TOOLSET: ToolSetSpec = ToolSetSpec {
    id: "toolset.media_read",
    display_name: "媒体读取",
    description: "Rust 媒体读取工具集合。",
    write_toolset: false,
    tool_names: &["image_read"],
};

pub const DOC_TOOLSET_WRITE: ToolSetSpec = ToolSetSpec {
    id: "toolset.doc_write",
    display_name: "文档写入",
    description: "写入文档工具集合。",
    write_toolset: true,
    tool_names: &["doc_insert_blocks", "doc_replace_range"],
};

pub const MINDMAP_TOOLSET_READ: ToolSetSpec = ToolSetSpec {
    id: "toolset.mindmap_read",
    display_name: "导图读取",
    description: "只读思维导图工具集合。",
    write_toolset: false,
    tool_names: &["mindmap_get", "mindmap_get_subtree"],
};

pub const MINDMAP_TOOLSET_WRITE: ToolSetSpec = ToolSetSpec {
    id: "toolset.mindmap_write",
    display_name: "导图写入",
    description: "写入思维导图的工具集合。",
    write_toolset: true,
    tool_names: &[
        "mindmap_put",
        "mindmap_apply_ops",
        "mindmap_expand_node",
        "mindmap_empty_trash",
        "mindmap_outline_to_mindmap",
    ],
};

pub const OBSERVE_TOOLSET_READ: ToolSetSpec = ToolSetSpec {
    id: "toolset.observe_read",
    display_name: "统一观测",
    description: "按 request、trace、command 回查统一日志与事件视图。",
    write_toolset: false,
    tool_names: &[
        "bridge_request_get",
        "bridge_trace_get",
        "bridge_command_get",
    ],
};

pub const RECOVERY_TOOLSET_JOB: ToolSetSpec = ToolSetSpec {
    id: "toolset.recovery_job",
    display_name: "恢复与重建",
    description: "统一事件回放、索引重建与恢复类任务集合。",
    write_toolset: true,
    tool_names: &["event_replay", "index_rebuild"],
};

pub const ONLYOFFICE_TOOLSET_SERVICE: ToolSetSpec = ToolSetSpec {
    id: "toolset.onlyoffice_service",
    display_name: "OnlyOffice 服务边界",
    description: "供 Web route / CLI / AI 共享的 OnlyOffice 对象适配工具集合。",
    write_toolset: true,
    tool_names: &[
        "onlyoffice_session_resolve",
        "onlyoffice_sign",
        "onlyoffice_prepare_proxy",
        "onlyoffice_prepare_callback",
        "onlyoffice_prepare_forcesave",
    ],
};

pub const SLASH_TOOLSET_WRITE: ToolSetSpec = ToolSetSpec {
    id: "toolset.slash_write",
    display_name: "斜杠命令",
    description: "Rust 斜杠命令工具集合。",
    write_toolset: true,
    tool_names: &["slash_run"],
};

pub const DOC_TOOL_REGISTRY: ToolRegistry = ToolRegistry::new(
    &[
        READONLY_TOOLSET,
        MEDIA_TOOLSET,
        SLASH_TOOLSET_WRITE,
        DOCS_TOOLSET_READ,
        DOC_TOOLSET_READ,
        DOC_TOOLSET_WRITE,
        MINDMAP_TOOLSET_READ,
        MINDMAP_TOOLSET_WRITE,
        OBSERVE_TOOLSET_READ,
        RECOVERY_TOOLSET_JOB,
        ONLYOFFICE_TOOLSET_SERVICE,
    ],
    &[
        SEARCH_WEB_TOOL,
        DOC_TOOL_GET,
        DOC_TOOL_FIND,
        DOCS_TOOL_SEARCH,
        DOCS_TOOL_READ,
        IMAGE_READ_TOOL,
        DOC_TOOL_INSERT_BLOCKS,
        DOC_TOOL_REPLACE_RANGE,
        SLASH_RUN_TOOL,
        MINDMAP_TOOL_GET,
        MINDMAP_TOOL_GET_SUBTREE,
        MINDMAP_TOOL_PUT,
        MINDMAP_TOOL_APPLY_OPS,
        MINDMAP_TOOL_EXPAND_NODE,
        MINDMAP_TOOL_EMPTY_TRASH,
        MINDMAP_TOOL_OUTLINE_TO_MINDMAP,
        BRIDGE_TOOL_REQUEST_GET,
        BRIDGE_TOOL_TRACE_GET,
        BRIDGE_TOOL_COMMAND_GET,
        REPLAY_TOOL_EVENTS,
        INDEX_TOOL_REBUILD,
        ONLYOFFICE_TOOL_SESSION_RESOLVE,
        ONLYOFFICE_TOOL_SIGN,
        ONLYOFFICE_TOOL_PREPARE_PROXY,
        ONLYOFFICE_TOOL_PREPARE_CALLBACK,
        ONLYOFFICE_TOOL_PREPARE_FORCESAVE,
    ],
);

pub fn default_tool_registry() -> &'static ToolRegistry {
    &DOC_TOOL_REGISTRY
}

pub fn tool_mode_label(mode: &ToolExecutionMode) -> &'static str {
    match mode {
        ToolExecutionMode::Plan => "plan",
        ToolExecutionMode::Result => "result",
        ToolExecutionMode::ExplainPlan => "explain-plan",
    }
}

pub fn invocation_kind_label(kind: &InvocationKind) -> &'static str {
    match kind {
        InvocationKind::Command => "command",
        InvocationKind::Query => "query",
        InvocationKind::Job => "job",
    }
}

pub fn tool_effect_label(effect: &ToolEffect) -> &'static str {
    match effect {
        ToolEffect::Read => "read",
        ToolEffect::Write => "write",
    }
}
