//! 任务类 AI 工具声明式注册(register_task_tools 6 个迁入)。
//!
//! 迁自 `tool_registry.rs::register_task_tools`(原 6 个 list/create/update/advance/
//! delete/get_count),改用 `declare_tool!` 宏。
//!
//! 迁移策略(handler 逻辑零变更):
//! - handler body 逐字照搬原 `register_task_tools` 内 async move 块(逻辑等价),
//!   仅闭包包装(`{ let db = db.clone(); Box::new(move |args| { let db = db.clone();
//!   Box::pin(async move { ... }) }) }`)改由 `declare_tool!` 宏生成。
//! - name/desc/schema/risk 与原手写定义逐字一致。
//! - 复用常量/类型:MAX_LIST_RESULTS(super::tool_registry 单真相源)、new_id/TaskStatus/
//!   TaskRecord/now_millis(各 pub(crate) 来源,与原 register_task_tools 引用同源)。
//!
//! 等价性验证:基线测试 `test_build_ai_tool_registry_baseline_tool_count` 仍断言 48 总量 +
//! 工具名集合稳定(防 rename / 漏注册)。

use std::sync::Arc;

use df_ai::ai_tools::{object_schema, AiToolRegistry, RiskLevel};
use df_ai::declare_tool;
use df_storage::db::Database;
use df_storage::models::TaskRecord;
use df_types::types::{new_id, TaskStatus};

use crate::commands::now_millis;
use crate::commands::ai::tool_registry::MAX_LIST_RESULTS;

/// 注册 6 个任务类工具(list/create/update/advance/delete/get_count)到 `$registry`。
///
/// 与原手写 register(name, desc, schema, risk, handler) 语义 1:1:
/// - name/desc/schema 字符串与 JSON Schema 逐字照搬原定义
/// - risk 与原一致(list/get_count=Low,create/update/advance=Medium,delete=High)
/// - handler body 与原 async move 块逐字一致(逻辑零变更)
///
/// 唯一差异:闭包包装改由 `declare_tool!` 宏生成,handler body 直接写业务逻辑。
pub fn register(registry: &mut AiToolRegistry, db: &Arc<Database>) {
    declare_tool!(
        registry,
        db: Arc<Database>,
        "list_tasks",
        "列出任务，可按 project_id/status 筛选(status: todo/in_progress/in_review/testing/blocked/done/cancelled)，支持 offset/limit 分页。返回 items、total、has_more。默认 limit=50",
        RiskLevel::Low,
        schema: object_schema(vec![("project_id", "string", false), ("status", "string", false), ("offset", "integer", false), ("limit", "integer", false)]),
        args => {
            let repo = df_storage::crud::TaskRepo::new(&db);
            let mut tasks = if let Some(pid) = args.get("project_id").and_then(|v| v.as_str()) {
                repo.query("project_id", pid).await?
            } else {
                repo.list_all().await?
            };
            // 按状态过滤(可选):todo/in_progress/in_review/testing/blocked/done/cancelled
            if let Some(status) = args.get("status").and_then(|v| v.as_str()) {
                tasks.retain(|t| t.status.as_str() == status);
            }
            let total = tasks.len();
            let offset = args["offset"].as_u64().unwrap_or(0) as usize;
            let limit = args["limit"].as_u64().unwrap_or(MAX_LIST_RESULTS as u64).min(MAX_LIST_RESULTS as u64) as usize;
            let page_items: Vec<_> = tasks.into_iter().skip(offset).take(limit).collect();
            let has_more = (offset + page_items.len()) < total;
            Ok(serde_json::json!({ "items": page_items, "total": total, "has_more": has_more }))
        }
    );

    declare_tool!(
        registry,
        db: Arc<Database>,
        "create_task",
        "在指定项目下创建新任务，可选传 idea_id 关联灵感(1对1 单向)。知识图谱 Phase 1 扩展三个可选参数:queue(管理维度池 backlog/todo/decision,默认 todo,新建不可直接落 active/done 须经 move_task_queue 流转)、parent_id(父任务 ID,限 1 级嵌套,父任务自身不能有 parent_id,IPC 层校验防孙任务)、content_json(结构化需求规格 JSON 字符串 {background,acceptance_criteria[],scope[],technical_design},须合法 JSON)",
        RiskLevel::Medium,
        schema: object_schema(vec![
            ("project_id", "string", true), ("title", "string", true),
            ("description", "string", false), ("priority", "integer", false),
            ("idea_id", "string", false),
            ("queue", "string", false), ("parent_id", "string", false),
            ("content_json", "string", false),
        ]),
        args => {
            let project_id = args["project_id"].as_str().ok_or_else(|| anyhow::anyhow!("缺少 project_id"))?;
            let title = args["title"].as_str().ok_or_else(|| anyhow::anyhow!("缺少 title"))?;
            let repo = df_storage::crud::TaskRepo::new(&db);

            // ── 知识图谱 Phase 1:queue/parent_id/content_json 校验(对齐 commands::task::create_task IPC)──
            // queue:白名单 + 空串默认 todo。create_task 时 status 恒 todo,仅 backlog/todo/decision
            // 合法(active/done 须经 move_task_queue 流转,新建直接落违反一致性约束)。
            const TASK_QUEUE_VALUES: &[&str] = &["backlog", "todo", "decision", "active", "done"];
            let queue = match args.get("queue").and_then(|v| v.as_str()) {
                None | Some("") => "todo".to_string(),
                Some(q) => {
                    let q = q.trim();
                    if !TASK_QUEUE_VALUES.contains(&q) {
                        anyhow::bail!("非法 queue 值 {:?},合法值: {:?}", q, TASK_QUEUE_VALUES);
                    }
                    // 一致性:create_task 时 status 恒 todo,仅 backlog/todo/decision 合法
                    match q {
                        "backlog" | "todo" | "decision" => q.to_string(),
                        other => anyhow::bail!(
                            "新建任务 queue 不可直接落 {:?}(须经 move_task_queue 流转),当前仅允许 backlog/todo/decision",
                            other
                        ),
                    }
                }
            };

            // parent_id:1 级嵌套铁律(对标设计 §2.1 D2)。空串/缺省=None;
            // 非空时校验父任务存在 + 父任务自身无 parent_id(防孙任务)。
            let parent_id = args
                .get("parent_id")
                .and_then(|v| v.as_str())
                .map(|s| s.trim().to_string())
                .filter(|s| !s.is_empty());
            if let Some(pid) = &parent_id {
                let parent = repo
                    .get_by_id(pid)
                    .await?
                    .ok_or_else(|| anyhow::anyhow!("父任务 {pid} 不存在,无法创建子任务"))?;
                if parent.parent_id.is_some() {
                    anyhow::bail!(
                        "违反 1 级嵌套铁律:父任务 {pid} 自身是子任务(parent_id={:?}),不允许在其下创建孙任务",
                        parent.parent_id
                    );
                }
            }

            // content_json:轻量 JSON 合法性校验(非空时须是合法 JSON),结构细节由 AI 消费层负责。
            let content_json = match args.get("content_json").and_then(|v| v.as_str()) {
                Some(c) if !c.trim().is_empty() => {
                    serde_json::from_str::<serde_json::Value>(c)
                        .map_err(|e| anyhow::anyhow!("content_json 不是合法 JSON: {e}"))?;
                    Some(c.to_string())
                }
                _ => None,
            };

            let record = TaskRecord {
                id: new_id(), project_id: project_id.to_string(), title: title.to_string(),
                description: args["description"].as_str().unwrap_or("").to_string(),
                // priority 默认 2(medium):与 commands::task::default_priority 一致,新任务默认中优先级(非 high)
                status: TaskStatus::Todo, priority: args["priority"].as_i64().unwrap_or(2) as i32,
                branch_name: None, assignee: None, workflow_def_id: None, base_branch: None,
                review_rounds: 0,
                output_json: None,
                // F-260619-01 可选关联灵感(空字符串/缺省视为不关联)
                idea_id: args.get("idea_id").and_then(|v| v.as_str()).filter(|s| !s.is_empty()).map(String::from),
                // 知识图谱 Phase 1 V29 三列:经上方校验的 queue / parent_id / content_json
                queue,
                parent_id,
                content_json,
                created_at: now_millis(), updated_at: now_millis(),
            };
            let id = record.id.clone();
            repo.insert(record).await?;
            Ok(serde_json::json!({ "id": id, "title": title, "status": "todo" }))
        }
    );

    declare_tool!(
        registry,
        db: Arc<Database>,
        "update_task",
        "更新任务的指定字段（title/description/priority/assignee 等），需要提供任务 ID、字段名和新值。注意：status 改动须走 advance_task 工具（状态机推进，7 态 todo/in_progress/in_review/testing/blocked/done/cancelled，按 target_status 推进不可跳跃），本工具不接受 status 字段（防绕过状态机）",
        RiskLevel::Medium,
        schema: object_schema(vec![("id", "string", true), ("field", "string", true), ("value", "string", true)]),
        args => {
            let id = args["id"].as_str().ok_or_else(|| anyhow::anyhow!("缺少 id"))?;
            let field = args["field"].as_str().ok_or_else(|| anyhow::anyhow!("缺少 field"))?;
            let value = args["value"].as_str().ok_or_else(|| anyhow::anyhow!("缺少 value"))?;
            // F-03 收口(子项3):status 改动必须走 advance_task 工具(状态机推进),
            // 本工具显式拒绝 status 字段,防 AI 绕过状态机直接改库。
            // 对齐 Agent B df-storage tasks 白名单移 status——双重防御(schema 拒绝 + 白名单拒绝)。
            if field == "status" {
                anyhow::bail!(
                    "status 改动须走 advance_task 工具(状态机推进,7 态不可跳跃),本工具不接受 status 字段"
                );
            }
            // 复用 df-storage CRUD 白名单(按表隔离),与 update_field 校验同源
            if !df_storage::crud::is_allowed_column("tasks", field) {
                anyhow::bail!("不允许更新字段 '{}'", field);
            }
            let repo = df_storage::crud::TaskRepo::new(&db);
            // 透传 affected:0 行(id 不存在 / 已软删)→ updated=false + title 空,
            // 让 AI 知道 id 不对而非假成功(对齐 update_idea)。
            let updated = repo.update_field(id, field, value).await?;
            // UX-260619-04: 返回 title 供前端卡片友好展示(对齐 advance_task 返 TaskRecord,
            // 防裸 UUID id 辨识度低)。update_field 返 bool 不含 record,单独 get_by_id 取 title。
            let title = repo.get_by_id(id).await?.map(|t| t.title).unwrap_or_default();
            Ok(serde_json::json!({ "id": id, "title": title, "field": field, "updated": updated }))
        }
    );

    // F-03 收口(子项1):advance_task AI 工具 — 推进任务状态的唯一合法路径。
    // D-260616-03:推进链业务逻辑(状态机校验 + 原子 CAS + review_rounds 累加)落
    // df-nodes::task_advance_node::advance_task_atomic,本 handler 直接调它(与 IPC
    // commands::task::advance_task 同源,thin 转发),无需 AppHandle/State/EventBus。
    // 风险 Medium:status 变更属状态机推进(非破坏),但改变任务流转语义需人工知情。
    declare_tool!(
        registry,
        db: Arc<Database>,
        "advance_task",
        "推进任务状态，必须走状态机(7 态: todo/in_progress/in_review/testing/blocked/done/cancelled)，按 target_status 推进，不可跳跃(非法转换会被状态机拦截)。退回转换(in_review→in_progress 等)自动累加 review_rounds。返回推进后的最新 TaskRecord",
        RiskLevel::Medium,
        schema: object_schema(vec![("id", "string", true), ("target_status", "string", true)]),
        args => {
            let id = args["id"].as_str().ok_or_else(|| anyhow::anyhow!("缺少 id"))?;
            let target_status = args["target_status"].as_str()
                .ok_or_else(|| anyhow::anyhow!("缺少 target_status"))?;
            // 复用 df-nodes 推进链核心逻辑(状态机 + 原子 CAS + review_rounds),
            // 与 commands::task::advance_task IPC 同源,避免双轨。
            let repo = df_storage::crud::TaskRepo::new(&db);
            let updated = df_nodes::task_advance_node::advance_task_atomic(
                &repo, id, target_status,
            ).await?;
            // 返回推进后的 TaskRecord(含新 status / 累加后的 review_rounds),供 LLM 确认推进结果。
            Ok(serde_json::to_value(&updated)?)
        }
    );

    // FR-D6:delete_task 对齐 commands::task::delete_task(D-260616-02 软删除)
    // tasks 表 V14 加 deleted_at,delete_task 改调 soft_delete 移入回收站(对标 delete_project)
    declare_tool!(
        registry,
        db: Arc<Database>,
        "delete_task",
        "删除任务（移入回收站，可恢复）",
        RiskLevel::High,
        schema: object_schema(vec![("id", "string", true)]),
        args => {
            let id = args["id"].as_str().ok_or_else(|| anyhow::anyhow!("缺少 id"))?;
            let repo = df_storage::crud::TaskRepo::new(&db);
            let deleted = repo.soft_delete(id).await?;
            Ok(serde_json::json!({ "deleted": deleted, "id": id }))
        }
    );

    // ── 探总量工具 (Low risk, F-260616-08) ──
    declare_tool!(
        registry,
        db: Arc<Database>,
        "get_task_count",
        "获取任务总数（未删除任务），用于分页策略判断。返回 { total: usize }",
        RiskLevel::Low,
        schema: object_schema(vec![("project_id", "string", false)]),
        args => {
            let repo = df_storage::crud::TaskRepo::new(&db);
            let tasks = if let Some(pid) = args.get("project_id").and_then(|v| v.as_str()) {
                repo.query("project_id", pid).await?
            } else {
                repo.list_all().await?
            };
            Ok(serde_json::json!({ "total": tasks.len() }))
        }
    );
}
