use serde_json::{json, Value};

pub const MANIFEST_SCHEMA_VERSION: &str = "mnote.hermes_tool_manifest.v1";
pub const TOOL_SCHEMA_VERSION: &str = "mnote.hermes_tool.v1";

pub fn manifest() -> Value {
    json!({
        "schemaVersion": MANIFEST_SCHEMA_VERSION,
        "plugin": {
            "name": "mnote",
            "description": "mnote 页面、树、artifact 与 edge 工具",
            "runtimeOwner": "mnote-web",
            "writeOwner": "rust-runtime-kernel"
        },
        "tools": [
            doc_fetch_tool(),
            doc_find_tool(),
            block_fetch_tool(),
            doc_plan_update_tool(),
            block_replace_tool(),
            block_insert_after_tool(),
            block_delete_tool(),
            block_move_after_tool(),
            doc_apply_block_ops_tool(),
            doc_markdown_edit_tool(),
            page_get_tool(),
            page_save_tool(),
            available_tool("mnote.page.update_title", "更新当前页面标题", ["page.write"]),
            available_tool("mnote.page.update_options", "更新当前页面设置", ["page.write"]),
            mindmap_fetch_tool(),
            mindmap_apply_ops_tool(),
            office_fetch_summary_tool(),
            office_propose_changes_tool(),
            available_tool("mnote.artifact.create_summary", "为当前页面创建或更新 AI Summary", ["artifact.write"]),
            available_tool("mnote.artifact.create_ai_note", "基于当前页面创建新的 AI Note", ["artifact.write"])
        ]
    })
}

fn base_identity_properties() -> Value {
    json!({
        "workspaceId": { "type": "string" },
        "documentId": { "type": "string" },
        "sessionId": { "type": "string" },
        "runId": { "type": "string" },
        "toolCallId": { "type": "string" },
        "traceId": { "type": "string" },
        "actorId": { "type": "string" },
        "actorType": { "type": "string" }
    })
}

fn doc_fetch_tool() -> Value {
    let mut properties = base_identity_properties();
    if let Value::Object(map) = &mut properties {
        map.insert(
            "scope".into(),
            json!({ "type": "string", "default": "full" }),
        );
        map.insert(
            "detail".into(),
            json!({ "type": "string", "default": "with_ids" }),
        );
        map.insert(
            "format".into(),
            json!({ "type": "string", "enum": ["json", "markdown", "text", "page_xml"], "default": "json" }),
        );
        map.insert("blockId".into(), json!({ "type": "string" }));
        map.insert(
            "selectedBlockIds".into(),
            json!({ "type": "array", "items": { "type": "string" } }),
        );
        map.insert(
            "allowedTargetBlockIds".into(),
            json!({ "type": "array", "items": { "type": "string" } }),
        );
        map.insert("query".into(), json!({ "type": "string" }));
        map.insert(
            "maxBlocks".into(),
            json!({ "type": "integer", "default": 120 }),
        );
    }
    json!({
        "name": "mnote.doc.fetch",
        "description": "读取当前页面的 canonical block projection，支持 full/outline/block/keyword 范围",
        "schemaVersion": TOOL_SCHEMA_VERSION,
        "capabilityScope": ["page.read"],
        "status": "available",
        "annotations": tool_annotations(true, false, true, false),
        "inputSchema": {
            "type": "object",
            "required": ["workspaceId", "documentId", "sessionId", "runId", "toolCallId", "traceId", "actorId"],
            "properties": properties
        }
    })
}

fn doc_find_tool() -> Value {
    let mut properties = base_identity_properties();
    if let Value::Object(map) = &mut properties {
        map.insert("query".into(), json!({ "type": "string" }));
        map.insert(
            "match".into(),
            json!({ "type": "string", "default": "text" }),
        );
        map.insert("limit".into(), json!({ "type": "integer", "default": 20 }));
    }
    json!({
        "name": "mnote.doc.find",
        "description": "在 Page Aggregate block projection 中按文本、类型或 blockId 查找块",
        "schemaVersion": TOOL_SCHEMA_VERSION,
        "capabilityScope": ["page.read"],
        "status": "available",
        "annotations": tool_annotations(true, false, true, false),
        "inputSchema": {
            "type": "object",
            "required": ["workspaceId", "documentId", "sessionId", "runId", "toolCallId", "traceId", "actorId", "query"],
            "properties": properties
        }
    })
}

fn block_fetch_tool() -> Value {
    let mut properties = base_identity_properties();
    if let Value::Object(map) = &mut properties {
        map.insert("blockId".into(), json!({ "type": "string" }));
        map.insert(
            "includeChildren".into(),
            json!({ "type": "boolean", "default": true }),
        );
        map.insert(
            "contextBefore".into(),
            json!({ "type": "integer", "default": 1 }),
        );
        map.insert(
            "contextAfter".into(),
            json!({ "type": "integer", "default": 1 }),
        );
        map.insert(
            "format".into(),
            json!({ "type": "string", "enum": ["json", "markdown", "text", "page_xml"], "default": "json" }),
        );
    }
    json!({
        "name": "mnote.block.fetch",
        "description": "读取单个块及同父级上下文",
        "schemaVersion": TOOL_SCHEMA_VERSION,
        "capabilityScope": ["block.read", "page.read"],
        "status": "available",
        "annotations": tool_annotations(true, false, true, false),
        "inputSchema": {
            "type": "object",
            "required": ["workspaceId", "documentId", "sessionId", "runId", "toolCallId", "traceId", "actorId", "blockId"],
            "properties": properties
        }
    })
}

fn doc_plan_update_tool() -> Value {
    write_tool(
        "mnote.doc.plan_update",
        "生成页面块更新 dry-run 计划，不直接写入",
        ["page.write", "block.write"],
        json!({
            "command": { "type": "string" },
            "blockId": { "type": "string" },
            "anchorBlockId": { "type": "string" },
            "content": { "type": ["string", "object", "array"] },
            "query": { "type": "string" },
            "revision": { "type": ["number", "string"] },
            "conflictDetectionKey": { "type": "string" }
        }),
        [],
    )
}

fn block_replace_tool() -> Value {
    write_tool(
        "mnote.block.replace",
        "兼容块写工具：替换指定块内容；本地 Markdown 普通编辑优先使用 agent 原生 patch/diff，必要时再走 Rust page.body.write 兼容链路",
        ["block.write", "page.write"],
        json!({
            "blockId": { "type": "string" },
            "content": { "type": ["string", "object", "array"] },
            "revision": { "type": ["number", "string"] },
            "conflictDetectionKey": { "type": "string" },
            "blockRevisionRef": { "type": "string" },
            "allowedTargetBlockIds": {
                "type": "array",
                "items": { "type": "string" }
            }
        }),
        [
            "blockId",
            "content",
            "revision",
            "conflictDetectionKey",
            "blockRevisionRef",
        ],
    )
}

fn block_insert_after_tool() -> Value {
    let mut tool = write_tool(
        "mnote.block.insert_after",
        "在指定块后插入新块；真实写入走 Rust page.body.save 链路",
        ["block.write", "page.write"],
        json!({
            "anchorBlockId": { "type": "string" },
            "content": { "type": ["string", "object", "array"] },
            "block": { "type": "object" },
            "blocks": {
                "type": "array",
                "minItems": 1,
                "maxItems": 20,
                "items": { "type": ["string", "object", "array"] }
            },
            "revision": { "type": ["number", "string"] },
            "conflictDetectionKey": { "type": "string" },
            "anchorRevisionRef": { "type": "string" },
            "allowedTargetBlockIds": {
                "type": "array",
                "items": { "type": "string" }
            }
        }),
        [
            "anchorBlockId",
            "revision",
            "conflictDetectionKey",
            "anchorRevisionRef",
        ],
    );
    if let Some(schema) = tool.get_mut("inputSchema").and_then(Value::as_object_mut) {
        schema.insert(
            "anyOf".into(),
            json!([
                { "required": ["content"] },
                { "required": ["block"] },
                { "required": ["blocks"] }
            ]),
        );
    }
    tool
}

fn block_move_after_tool() -> Value {
    write_tool(
        "mnote.block.move_after",
        "受限同父级普通叶子块移动；真实写入走 Rust page.body.save 链路",
        ["block.write", "page.write"],
        json!({
            "blockId": { "type": "string" },
            "anchorBlockId": { "type": "string" },
            "revision": { "type": ["number", "string"] },
            "conflictDetectionKey": { "type": "string" },
            "blockRevisionRef": { "type": "string" },
            "anchorRevisionRef": { "type": "string" },
            "allowedTargetBlockIds": {
                "type": "array",
                "items": { "type": "string" }
            }
        }),
        [
            "blockId",
            "anchorBlockId",
            "revision",
            "conflictDetectionKey",
            "blockRevisionRef",
            "anchorRevisionRef",
        ],
    )
}

fn block_delete_tool() -> Value {
    write_tool(
        "mnote.block.delete",
        "删除指定无子块普通块；真实写入走 Rust page.body.save 链路",
        ["block.write", "page.write"],
        json!({
            "blockId": { "type": "string" },
            "revision": { "type": ["number", "string"] },
            "conflictDetectionKey": { "type": "string" },
            "blockRevisionRef": { "type": "string" },
            "allowedTargetBlockIds": {
                "type": "array",
                "items": { "type": "string" }
            }
        }),
        [
            "blockId",
            "revision",
            "conflictDetectionKey",
            "blockRevisionRef",
        ],
    )
}

fn doc_apply_block_ops_tool() -> Value {
    write_tool(
        "mnote.doc.apply_block_ops",
        "一次性应用多个块级操作；Rust 侧统一读取最新 projection、生成 canonical content 并一次保存，适合页面 AI 小段落增删改移动快路径",
        ["block.write", "page.write"],
        json!({
            "operations": {
                "type": "array",
                "items": {
                    "type": "object",
                    "properties": {
                        "op": { "type": "string" },
                        "blockId": { "type": "string" },
                        "anchorBlockId": { "type": "string" },
                        "matchText": { "type": "string" },
                        "anchorText": { "type": "string" },
                        "afterText": { "type": "string" },
                        "allowedTargetBlockIds": {
                            "type": "array",
                            "items": { "type": "string" }
                        },
                        "content": { "type": ["string", "object", "array"] }
                    }
                }
            }
        }),
        ["operations"],
    )
}

fn write_tool(
    name: &str,
    description: &str,
    scope: impl IntoIterator<Item = &'static str>,
    extra_properties: Value,
    extra_required: impl IntoIterator<Item = &'static str>,
) -> Value {
    let mut properties = base_identity_properties();
    if let Value::Object(map) = &mut properties {
        map.insert("dryRun".into(), json!({ "type": "boolean" }));
        map.insert("idempotencyKey".into(), json!({ "type": "string" }));
        if let Value::Object(extra) = extra_properties {
            for (key, value) in extra {
                map.insert(key, value);
            }
        }
    }
    let mut required = vec![
        "workspaceId",
        "documentId",
        "sessionId",
        "runId",
        "toolCallId",
        "traceId",
        "actorId",
        "dryRun",
        "idempotencyKey",
    ];
    required.extend(extra_required);
    json!({
        "name": name,
        "description": description,
        "schemaVersion": TOOL_SCHEMA_VERSION,
        "capabilityScope": scope.into_iter().collect::<Vec<_>>(),
        "status": "available",
        "annotations": tool_annotations(false, false, false, false),
        "inputSchema": {
            "type": "object",
            "required": required,
            "properties": properties
        }
    })
}

fn page_get_tool() -> Value {
    json!({
        "name": "mnote.page.get",
        "description": "读取当前页面 Page Aggregate 摘要",
        "schemaVersion": TOOL_SCHEMA_VERSION,
        "capabilityScope": ["page.read"],
        "status": "available",
        "annotations": tool_annotations(true, false, true, false),
        "inputSchema": {
            "type": "object",
            "required": ["workspaceId", "documentId", "sessionId", "runId", "toolCallId", "traceId", "actorId"],
            "properties": {
                "workspaceId": { "type": "string" },
                "documentId": { "type": "string" },
                "sessionId": { "type": "string" },
                "runId": { "type": "string" },
                "toolCallId": { "type": "string" },
                "traceId": { "type": "string" },
                "actorId": { "type": "string" },
                "actorType": { "type": "string" },
                "includeBody": { "type": "boolean", "default": true },
                "includeOptions": { "type": "boolean", "default": true },
                "includeBlocks": { "type": "boolean", "default": true }
            }
        }
    })
}

fn page_save_tool() -> Value {
    json!({
        "name": "mnote.page.save",
        "description": "粗粒度兼容兜底：保存当前页面正文；本地 Markdown 普通编辑优先使用 agent 原生 patch/diff，只有整页覆盖/追加且其它工具无法表达时使用",
        "schemaVersion": TOOL_SCHEMA_VERSION,
        "capabilityScope": ["page.write"],
        "status": "available",
        "annotations": tool_annotations(false, true, false, false),
        "inputSchema": {
            "type": "object",
            "required": ["workspaceId", "documentId", "sessionId", "runId", "toolCallId", "traceId", "actorId", "content", "dryRun", "idempotencyKey"],
            "properties": {
                "workspaceId": { "type": "string" },
                "documentId": { "type": "string" },
                "sessionId": { "type": "string" },
                "runId": { "type": "string" },
                "toolCallId": { "type": "string" },
                "traceId": { "type": "string" },
                "actorId": { "type": "string" },
                "actorType": { "type": "string" },
                "content": {
                    "description": "要写入的正文块数组、{blocks:[...]}、TipTap content 数组或纯文本",
                    "type": ["array", "object", "string"]
                },
                "mode": {
                    "type": "string",
                    "enum": ["replace", "append", "prepend"],
                    "default": "replace"
                },
                "dryRun": { "type": "boolean" },
                "idempotencyKey": { "type": "string" }
            }
        }
    })
}

fn available_tool(
    name: &str,
    description: &str,
    scope: impl IntoIterator<Item = &'static str>,
) -> Value {
    json!({
        "name": name,
        "description": description,
        "schemaVersion": TOOL_SCHEMA_VERSION,
        "capabilityScope": scope.into_iter().collect::<Vec<_>>(),
        "status": "available",
        "annotations": tool_annotations(false, false, false, false)
    })
}

fn doc_markdown_edit_tool() -> Value {
    let mut tool = write_tool(
        "mnote.doc.markdown_edit",
        "兼容 / 远端代理 fallback：通过文本级搜索替换编辑 markdown 内容。local-first 本地 workspace 默认优先让 agent 原生 patch/diff 直接编辑授权文件；仅在需要 MNote 兼容工具、远端代理或结构校验时使用。",
        ["block.write", "page.write"],
        json!({
            "operations": {
                "type": "array",
                "description": "搜索替换操作列表",
                "items": {
                    "type": "object",
                    "properties": {
                        "search": { "type": "string", "description": "要搜索的原文片段" },
                        "replace": { "type": "string", "description": "替换后的新文本" }
                    },
                    "required": ["search", "replace"]
                }
            },
            "full_content": {
                "type": "string",
                "description": "完整修改后的 markdown 文本（替代 operations）"
            }
        }),
        [],
    );
    if let Some(schema) = tool.get_mut("inputSchema").and_then(Value::as_object_mut) {
        schema.insert(
            "anyOf".into(),
            json!([
                { "required": ["operations"] },
                { "required": ["full_content"] }
            ]),
        );
    }
    tool
}

fn resource_identity_properties(resource_id_name: &str) -> Value {
    let mut properties = base_identity_properties();
    if let Value::Object(map) = &mut properties {
        map.insert("rootUri".into(), json!({ "type": "string" }));
        map.insert("sourceKind".into(), json!({ "type": "string" }));
        map.insert(resource_id_name.into(), json!({ "type": "string" }));
        map.insert("resourcePath".into(), json!({ "type": "string" }));
        map.insert(
            "aiAccessScope".into(),
            json!({
                "type": "object",
                "properties": {
                    "permissionLevel": { "type": "string" },
                    "allowedResourceIds": { "type": "array", "items": { "type": "string" } },
                    "allowedRoots": { "type": "array" }
                }
            }),
        );
    }
    properties
}

fn mindmap_fetch_tool() -> Value {
    let mut properties = resource_identity_properties("mindmapId");
    if let Value::Object(map) = &mut properties {
        map.insert(
            "scope".into(),
            json!({ "type": "string", "enum": ["tree", "subtree", "markdown_summary"], "default": "tree" }),
        );
        map.insert("nodeId".into(), json!({ "type": "string" }));
    }
    json!({
        "name": "mnote.mindmap.fetch",
        "description": "读取授权 root 内的 mindmap resource 结构或 markdown summary",
        "schemaVersion": TOOL_SCHEMA_VERSION,
        "capabilityScope": ["resource.read", "mindmap.read"],
        "status": "available",
        "annotations": tool_annotations(true, false, true, false),
        "inputSchema": {
            "type": "object",
            "required": ["workspaceId", "documentId", "mindmapId", "sessionId", "runId", "toolCallId", "traceId", "actorId"],
            "properties": properties
        }
    })
}

fn mindmap_apply_ops_tool() -> Value {
    write_tool(
        "mnote.mindmap.apply_ops",
        "对授权 mindmap resource 生成或执行结构操作；本地 workspace 默认建议 agent 直接编辑授权文件，必要时才走该工具",
        ["resource.write", "mindmap.write"],
        json!({
            "mindmapId": { "type": "string" },
            "rootUri": { "type": "string" },
            "sourceKind": { "type": "string" },
            "resourcePath": { "type": "string" },
            "expectedRevision": { "type": ["number", "string"] },
            "ops": {
                "type": "array",
                "items": { "type": "object" }
            },
            "aiAccessScope": { "type": "object" }
        }),
        ["mindmapId", "ops"],
    )
}

fn office_fetch_summary_tool() -> Value {
    let mut properties = resource_identity_properties("assetId");
    if let Value::Object(map) = &mut properties {
        map.insert(
            "extractMode".into(),
            json!({ "type": "string", "enum": ["text", "outline", "metadata"], "default": "metadata" }),
        );
    }
    json!({
        "name": "mnote.office.fetch_summary",
        "description": "读取授权 Office resource 的元数据和轻量文本摘要；不直接写二进制文件",
        "schemaVersion": TOOL_SCHEMA_VERSION,
        "capabilityScope": ["resource.read", "office.read"],
        "status": "available",
        "annotations": tool_annotations(true, false, true, false),
        "inputSchema": {
            "type": "object",
            "required": ["workspaceId", "documentId", "assetId", "sessionId", "runId", "toolCallId", "traceId", "actorId"],
            "properties": properties
        }
    })
}

fn office_propose_changes_tool() -> Value {
    let mut properties = resource_identity_properties("assetId");
    if let Value::Object(map) = &mut properties {
        map.insert("instructions".into(), json!({ "type": "string" }));
        map.insert(
            "basisRevision".into(),
            json!({ "type": ["number", "string"] }),
        );
    }
    json!({
        "name": "mnote.office.propose_changes",
        "description": "基于授权 Office resource 生成修改建议；真实写入仍由 OnlyOffice / officecli 完成",
        "schemaVersion": TOOL_SCHEMA_VERSION,
        "capabilityScope": ["resource.read", "office.read"],
        "status": "available",
        "annotations": tool_annotations(true, false, true, false),
        "inputSchema": {
            "type": "object",
            "required": ["workspaceId", "documentId", "assetId", "sessionId", "runId", "toolCallId", "traceId", "actorId", "instructions"],
            "properties": properties
        }
    })
}

fn tool_annotations(
    readonly: bool,
    destructive: bool,
    idempotent: bool,
    requires_approval: bool,
) -> Value {
    json!({
        "readonly": readonly,
        "destructive": destructive,
        "idempotent": idempotent,
        "readOnly": readonly,
        "requiresWritePermission": !readonly,
        "requiresApproval": requires_approval,
        "approvalMode": if requires_approval { "review" } else { "yolo" },
        "runtimeOwner": "mnote-web",
        "writeOwner": "rust-runtime-kernel",
        "selectionEffect": if readonly { "preserve" } else { "may_change" }
    })
}
