//! Plan-driven 规划骨架（Phase 0 · 维度 0.8 + 0.7）
//!
//! 论证依据：`docs/02-架构设计/单对话并行多轮-设计-2026-06-20.md`（§三 Plan 数据结构 /
//! §十三 0.7 规划校验 / 0.8 planner.rs）+
//! `docs/02-架构设计/单对话并行多轮-Phase0落地路线图-2026-06-20.md`（0.8 + 0.7 节）。
//!
//! ## 定位
//! **纯函数模块**（零 IO 零状态：无 `.await` / 无 db / 无 provider / 无全局状态），
//! 仅数据结构 + 算法。便于单测 + Phase 1 无副作用接入。
//!
//! 提供四项能力：
//! 1. [`SubTask`] / [`Plan`] —— 规划 DAG 数据结构（可并行组 group + 顺序依赖 deps）。
//! 2. [`Plan::validate`] —— 五类非法形态校验（环/悬空/空/重复 id/超深）。
//! 3. [`Plan::to_layers`] —— Kahn 拓扑分层算法，输出可并行层（喂 Phase 2/3 execution 调度）。
//! 4. [`planning_enabled`] —— feature flag 占位（关时单链 ReAct = 旧行为，Phase 1 接入用）。
//!
//! ## 设计原则（对齐设计文档 §十三 决策推翻）
//! - **单一真相源**：Plan 只存 `deps`，**不冗余存 `parallel_groups`**（设计文档明确推翻
//!   旧「deps + parallel_groups 并存」结构 → 删 parallel_groups，运行时 Kahn 分层派生）。
//!   `group` 字段是规划期的「提示性并行组」（可选 hint），与 deps DAG 并行不矛盾即合法。
//! - **非法收集非短路**：validate 一次性收集全部违规，便于规划 LLM 一次性修正。
//! - **Kahn 无副作用**：纯算法，不校验（validate 先行），输入非法时行为见各方法文档。
//!
//! ## 不改现有文件
//! 本文件独立编译；`intent.rs` / `coordinator.rs` 接入留 Phase 1/2（plan_hint.rs 另起）。

// ---- feature flag 占位 ------------------------------------------------------

/// 规划能力总开关（主 loop 规划开关，门控 filter_tool_defs_planned 接入）。
///
/// **当前 `true`** = 主 loop 走 Plan 规划（agentic/mod.rs:633 调 filter_tool_defs_planned）。
/// Phase 1（plan_hint 接入主 loop）已落地，翻 true 启用。
/// 注:`PLAN_HINT_ENABLED`(plan_hint 函数 flag,Phase0a 就绪 true)单一定义在 `plan_hint.rs`,
/// 本模块仅 `PLANNING_ENABLED`(主 loop 规划开关),消除此前双定义矛盾。
/// 双层开关:即使 PLANNING_ENABLED=true,若 plan_hint 内部 PLAN_HINT_ENABLED 关闭,
/// filter_tool_defs_planned 仍退扁平(任一关闭退旧行为,可回退)。
pub const PLANNING_ENABLED: bool = true;

/// Plan 层级硬上限（防爆深，防规划 LLM 退化成超长链）。
///
/// 超过此值的依赖链判 [`PlanError::TooDeep`]。
/// 8 层覆盖典型软件任务分解（需求→设计→编码→测试→…），Phase 2 go/no-go 后可调。
pub const MAX_PLAN_DEPTH: usize = 8;

use std::collections::{HashMap, HashSet};

// ---- 类型别名 ---------------------------------------------------------------

/// 子任务标识。String 而非裸 &str：规划期 LLM 自由生成，不依赖静态工具名表。
pub type SubTaskId = String;

/// 并行组标识（可选 hint）。规划 LLM 标注「这组任务意图上可并行」，
/// Kahn 分层不依赖它（只看 deps DAG），但保留供 UI/审计展示。
pub type GroupId = String;

// ---- SubTask ----------------------------------------------------------------

/// 规划子任务：DAG 中的一个节点。
///
/// 字段对齐设计文档 §三 `SubTask`（id / tools / deps）+ 并行组 hint：
/// - `id`：任务内唯一标识（validate 拒重复）。
/// - `tool_hint`：可用工具名子集（intent/plan_hint 提示，空 Vec 由 validate 拒为
///   [`PlanError::SubtaskWithoutTools`] —— 对齐设计文档枚举；执行期不被强制，仅提示 LLM）。
/// - `deps`：依赖子任务 id 列表（DAG 前驱）。未解析的 id（不存在于 Plan）→
///   [`PlanError::UnknownDep`]（悬空）。
/// - `group`：可选并行组 hint（设计文档 §三 parallel_groups 的非冗余形态——只作提示，
///   不与 deps 并列做真相源）。
/// - `intent`：自由文本意图描述（规划 LLM 给出的「这步做什么」，便于审计/日志）。
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SubTask {
    pub id: SubTaskId,
    /// 可用工具名子集（hint，非强制）。空 → validate 拒（除非允许无工具子任务）。
    pub tool_hint: Vec<String>,
    /// 依赖前驱 id。DAG 边：dep → self。
    pub deps: Vec<SubTaskId>,
    /// 并行组 hint（可选）。同 group 的任务意图上可并行，但 Kahn 不看它。
    pub group: Option<GroupId>,
    /// 意图自由文本（规划 LLM 描述，审计/日志用）。
    pub intent: String,
}

impl SubTask {
    /// 构造便捷器：最小子任务（id + intent，无工具无依赖）。
    #[allow(dead_code)]
    pub fn new(id: impl Into<SubTaskId>, intent: impl Into<String>) -> Self {
        Self {
            id: id.into(),
            tool_hint: Vec::new(),
            deps: Vec::new(),
            group: None,
            intent: intent.into(),
        }
    }

    /// 链式设置 tool_hint。
    #[allow(dead_code)]
    pub fn with_tools(mut self, tools: Vec<String>) -> Self {
        self.tool_hint = tools;
        self
    }

    /// 链式设置 deps。
    #[allow(dead_code)]
    pub fn with_deps(mut self, deps: Vec<SubTaskId>) -> Self {
        self.deps = deps;
        self
    }

    /// 链式设置 group。
    #[allow(dead_code)]
    pub fn with_group(mut self, group: impl Into<GroupId>) -> Self {
        self.group = Some(group.into());
        self
    }
}

// ---- Plan -------------------------------------------------------------------

/// 规划 DAG：子任务集合。
///
/// **不冗余存 `parallel_groups`**（设计文档 §十三 决策推翻：删 parallel_groups，
/// 只存 deps，运行时 [`Plan::to_layers`] Kahn 分层派生——单一真相源）。
///
/// 合法性由 [`Plan::validate`] 校验；非法 Plan 调 [`Plan::to_layers`] 行为见其文档。
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Plan {
    pub tasks: Vec<SubTask>,
}

impl Plan {
    /// 空构造。
    #[allow(dead_code)]
    pub fn new() -> Self {
        Self { tasks: Vec::new() }
    }

    /// 从 tasks 构造。
    #[allow(dead_code)]
    pub fn from_tasks(tasks: Vec<SubTask>) -> Self {
        Self { tasks }
    }

    /// 任务数。
    #[allow(dead_code)]
    pub fn len(&self) -> usize {
        self.tasks.len()
    }

    /// 是否空（空 Plan 判非法：[`PlanError::Empty`]）。
    #[allow(dead_code)]
    pub fn is_empty(&self) -> bool {
        self.tasks.is_empty()
    }

    /// 校验 Plan 合法性（五类非法 + 设计文档四枚举）。
    ///
    /// **收集非短路**：一次性返回所有违规（`Vec<PlanError>`），便于规划 LLM 一次修正。
    /// 合法 → `Ok(())`。
    ///
    /// 五类非法（对齐路线图 0.7）+ 设计文档枚举映射：
    /// | 类别 | 枚举 | 判据 |
    /// |---|---|---|
    /// | 空 | [`PlanError::Empty`] | tasks 为空 |
    /// | 重复 id | [`PlanError::DuplicateId`] | 同 id 出现 ≥2 次 |
    /// | 悬空 dep | [`PlanError::UnknownDep`] | deps 引用不存在的 id（设计文档枚举） |
    /// | 自环 dep | [`PlanError::UnknownDep`] 归类 | self-dep（A 依赖 A，视为悬空+环） |
    /// | 环 | [`PlanError::CycleDetected`] | deps 形成环（设计文档枚举） |
    /// | 无工具 | [`PlanError::SubtaskWithoutTools`] | tool_hint 空（设计文档枚举，可由 `require_tools=false` 关） |
    /// | 超深 | [`PlanError::TooDeep`] | 最长依赖链深度 > [`MAX_PLAN_DEPTH`] |
    ///
    /// 注：[`PlanError::OrphanSubtask`]（设计文档枚举）当前归一不报——
    /// 无 deps 且无后继的孤立任务在 DAG 中合法（入口任务），设计文档「Orphan」语义
    /// 偏「脱网」，Phase 2 go/no-go 后再定是否拒（保留枚举占位，符合预留保留原则）。
    pub fn validate(&self) -> Result<(), Vec<PlanError>> {
        let errs = self.validate_with(ValidateOptions::default());
        if errs.is_empty() {
            Ok(())
        } else {
            Err(errs)
        }
    }

    /// 带选项的校验（`require_tools` 开关：plan_hint 场景可允许无工具子任务）。
    pub fn validate_with(&self, opts: ValidateOptions) -> Vec<PlanError> {
        let mut errs: Vec<PlanError> = Vec::new();

        // 1) 空
        if self.tasks.is_empty() {
            errs.push(PlanError::Empty);
            return errs; // 空 Plan 后续检查无意义
        }

        // 构建 id 索引（同时检测重复 id）
        let mut id_index: HashMap<&SubTaskId, usize> = HashMap::new();
        for (i, t) in self.tasks.iter().enumerate() {
            if t.id.is_empty() {
                errs.push(PlanError::EmptyId { index: i });
                continue;
            }
            if id_index.contains_key(&t.id) {
                errs.push(PlanError::DuplicateId {
                    id: t.id.clone(),
                });
            } else {
                id_index.insert(&t.id, i);
            }
        }

        // 2) 无工具（可选）
        if opts.require_tools {
            for t in &self.tasks {
                if t.tool_hint.is_empty() {
                    errs.push(PlanError::SubtaskWithoutTools { id: t.id.clone() });
                }
            }
        }

        // 3) 悬空 dep / 自环（自环视为 UnknownDep）
        for t in &self.tasks {
            for dep in &t.deps {
                if dep == &t.id {
                    errs.push(PlanError::SelfDep { id: t.id.clone() });
                } else if !id_index.contains_key(dep) {
                    errs.push(PlanError::UnknownDep {
                        from: t.id.clone(),
                        unknown: dep.clone(),
                    });
                }
            }
        }

        // 4) 环 + 超深（先验无悬空才能可靠跑环检测：悬空已报，环检测只看可解析边）
        if !errs.iter().any(|e| {
            matches!(
                e,
                PlanError::UnknownDep { .. } | PlanError::SelfDep { .. }
            )
        }) {
            if let Some(cycle) = self.detect_cycle() {
                errs.push(PlanError::CycleDetected { cycle });
            }
            // 超深（最长链 = Kahn 层数）；环存在时层数无意义，跳过
            if !errs.iter().any(|e| matches!(e, PlanError::CycleDetected { .. })) {
                if let Some(depth) = self.longest_chain_depth() {
                    if depth > opts.max_depth {
                        errs.push(PlanError::TooDeep {
                            depth,
                            max: opts.max_depth,
                        });
                    }
                }
            }
        }

        errs
    }

    /// Kahn 拓扑分层：输出可并行层（同层任务无相互依赖，可并行执行）。
    ///
    /// **算法**：入度 0 入当前层 → 移除（其后继入度 -1）→ 下层。纯算法无副作用。
    ///
    /// **非法 Plan 行为**（调用方应先 validate）：
    /// - 环：返回 `Err(CycleDetected)`（Kahn 无法消除环边，剩余节点 = 环成员）。
    /// - 悬空 dep：忽略悬空边（视作无该前驱），照常分层。
    /// - 空 Plan：返回 `Ok(vec![])`。
    ///
    /// 返回层的顺序 = 执行顺序（第 0 层先执行，同层内可并行）。
    /// 每层 `Vec<SubTask>` 为克隆（Kahn 消费入度，不消费原 Plan）。
    pub fn to_layers(&self) -> Result<Vec<Vec<SubTask>>, CycleError> {
        if self.tasks.is_empty() {
            return Ok(Vec::new());
        }

        let id_to_task: HashMap<&SubTaskId, &SubTask> =
            self.tasks.iter().map(|t| (&t.id, t)).collect();

        // 入度：仅计可解析的 deps（悬空 dep 不计入，避免卡死）
        let mut indegree: HashMap<&SubTaskId, usize> = HashMap::new();
        for t in &self.tasks {
            indegree.entry(&t.id).or_insert(0);
        }
        for t in &self.tasks {
            for dep in &t.deps {
                // dep 存在且非自环才计
                if dep != &t.id && id_to_task.contains_key(dep) {
                    *indegree.entry(&t.id).or_insert(0) += 1;
                }
            }
        }

        let mut layers: Vec<Vec<SubTask>> = Vec::new();
        let mut placed: HashSet<&SubTaskId> = HashSet::new();

        loop {
            // 当前层：入度 0 且未放置（按原 tasks 顺序稳定）
            let current: Vec<&SubTask> = self
                .tasks
                .iter()
                .filter(|t| !placed.contains(&t.id))
                .filter(|t| *indegree.get(&t.id).unwrap_or(&0) == 0)
                .collect();

            if current.is_empty() {
                break;
            }

            // 放置并减后继入度
            for t in &current {
                placed.insert(&t.id);
            }
            // 后继入度 -1：遍历所有任务，若其 deps 含当前层任一 id，则 -1
            let current_ids: HashSet<&SubTaskId> =
                current.iter().map(|t| &t.id).collect();
            for t in &self.tasks {
                if placed.contains(&t.id) {
                    continue;
                }
                for dep in &t.deps {
                    if current_ids.contains(dep) {
                        if let Some(v) = indegree.get_mut(&t.id) {
                            *v = v.saturating_sub(1);
                        }
                    }
                }
            }

            layers.push(current.iter().map(|t| (*t).clone()).collect());
        }

        // 剩余未放置 = 环成员
        if placed.len() < self.tasks.len() {
            let cycle: Vec<SubTaskId> = self
                .tasks
                .iter()
                .filter(|t| !placed.contains(&t.id))
                .map(|t| t.id.clone())
                .collect();
            return Err(CycleError { cycle });
        }

        Ok(layers)
    }

    // ---- 内部算法辅助 ----

    /// 环检测（DFS 三色标记法）。假设无悬空 dep（调用方已过滤）。
    fn detect_cycle(&self) -> Option<Vec<SubTaskId>> {
        let id_to_task: HashMap<&SubTaskId, &SubTask> =
            self.tasks.iter().map(|t| (&t.id, t)).collect();

        // 颜色：0=白(未访问) 1=灰(栈中) 2=黑(完成)
        let mut color: HashMap<&SubTaskId, u8> = HashMap::new();
        for t in &self.tasks {
            color.insert(&t.id, 0);
        }

        // 记录路径用于回溯环
        let mut stack: Vec<&SubTaskId> = Vec::new();
        // finished 节点（防重复 DFS）
        let mut finished: HashSet<&SubTaskId> = HashSet::new();

        for t in &self.tasks {
            if *color.get(&t.id).unwrap() != 0 {
                continue;
            }
            if let Some(cycle) = dfs_cycle(&t.id, &id_to_task, &mut color, &mut stack, &mut finished)
            {
                return Some(cycle);
            }
        }
        None
    }

    /// 最长依赖链深度（= Kahn 层数）。无环前提。返回 None 表示空 Plan。
    fn longest_chain_depth(&self) -> Option<usize> {
        match self.to_layers() {
            Ok(layers) => {
                if layers.is_empty() {
                    None
                } else {
                    Some(layers.len())
                }
            }
            // 环存在：返回 None（depth 无意义，由 CycleDetected 单独报）
            Err(_) => None,
        }
    }
}

// ---- validate 选项 ----------------------------------------------------------

/// validate 行为选项。
#[derive(Debug, Clone, Copy)]
pub struct ValidateOptions {
    /// 是否要求每个子任务有 tool_hint。plan_hint 场景可关（允许纯编排节点）。
    pub require_tools: bool,
    /// 依赖链深度上限。超 → TooDeep。
    pub max_depth: usize,
}

impl Default for ValidateOptions {
    fn default() -> Self {
        Self {
            require_tools: true,
            max_depth: MAX_PLAN_DEPTH,
        }
    }
}

// ---- 错误类型 ---------------------------------------------------------------

/// Plan 校验错误（设计文档 §十三 0.7 枚举 + 路线图 0.7 五类非法补充）。
///
/// 对齐设计文档枚举：`CycleDetected / UnknownDep / OrphanSubtask / SubtaskWithoutTools`，
/// 并补充路线图要求的五类非法（Empty/DuplicateId/TooDeep）+ 实现细化（SelfDep/EmptyId）。
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PlanError {
    /// 空 Plan（路线图五类非法之一）。
    Empty,
    /// 子任务 id 为空字符串。
    EmptyId { index: usize },
    /// 重复 id（路线图五类非法之一）。
    DuplicateId { id: SubTaskId },
    /// 悬空依赖：deps 引用不存在的 id（设计文档枚举 UnknownDep）。
    UnknownDep { from: SubTaskId, unknown: SubTaskId },
    /// 自环依赖：A 依赖 A（归为悬空的特例，单独报便于定位）。
    SelfDep { id: SubTaskId },
    /// 环（设计文档枚举 CycleDetected）。`cycle` = 环上节点 id 序列。
    CycleDetected { cycle: Vec<SubTaskId> },
    /// 子任务无工具（设计文档枚举 SubtaskWithoutTools）。
    SubtaskWithoutTools { id: SubTaskId },
    /// 依赖链超深（路线图五类非法之一）。
    TooDeep { depth: usize, max: usize },
    /// 孤立子任务（设计文档枚举 OrphanSubtask 占位，当前 validate 不报，保留供 Phase 2 决策）。
    #[allow(dead_code)]
    OrphanSubtask { id: SubTaskId },
}

impl std::fmt::Display for PlanError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            PlanError::Empty => write!(f, "Plan 为空"),
            PlanError::EmptyId { index } => write!(f, "第 {} 个子任务 id 为空", index),
            PlanError::DuplicateId { id } => write!(f, "重复子任务 id: {}", id),
            PlanError::UnknownDep { from, unknown } => {
                write!(f, "子任务 {} 依赖不存在的 id: {}", from, unknown)
            }
            PlanError::SelfDep { id } => write!(f, "子任务 {} 自环依赖", id),
            PlanError::CycleDetected { cycle } => {
                write!(f, "检测到环: {}", cycle.join(" -> "))
            }
            PlanError::SubtaskWithoutTools { id } => write!(f, "子任务 {} 无 tool_hint", id),
            PlanError::TooDeep { depth, max } => {
                write!(f, "依赖链深度 {} 超上限 {}", depth, max)
            }
            PlanError::OrphanSubtask { id } => write!(f, "孤立子任务: {}", id),
        }
    }
}

impl std::error::Error for PlanError {}

/// Kahn 分层时检测到环（[`Plan::to_layers`] 返回）。
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CycleError {
    /// 环上（或因环无法放置的）节点 id 列表。
    pub cycle: Vec<SubTaskId>,
}

impl std::fmt::Display for CycleError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Kahn 分层遇到环: {}", self.cycle.join(" -> "))
    }
}

impl std::error::Error for CycleError {}

// ---- DFS 环检测辅助（模块级，借用 checker 规避 self-borrow） ----------------

fn dfs_cycle<'a>(
    start: &'a SubTaskId,
    id_to_task: &HashMap<&'a SubTaskId, &'a SubTask>,
    color: &mut HashMap<&'a SubTaskId, u8>,
    stack: &mut Vec<&'a SubTaskId>,
    finished: &mut HashSet<&'a SubTaskId>,
) -> Option<Vec<SubTaskId>> {
    // 显式栈 DFS，避免递归（深度大时不爆栈）
    // stack 元素：(节点, 其 deps 的迭代位置)——此处简化：每节点完整遍历 deps
    // 改用工作栈 + 回溯标记
    struct Frame<'b> {
        node: &'b SubTaskId,
        dep_idx: usize,
    }
    let mut work: Vec<Frame<'a>> = vec![Frame { node: start, dep_idx: 0 }];
    color.insert(start, 1); // 灰
    stack.push(start);

    while let Some(frame) = work.last_mut() {
        let node = frame.node;
        let task = match id_to_task.get(node) {
            Some(t) => *t,
            None => {
                // 不应发生（调用方已过滤悬空），兜底回溯
                work.pop();
                color.insert(node, 2);
                stack.pop();
                continue;
            }
        };
        let deps = &task.deps;

        if frame.dep_idx < deps.len() {
            let next_id = &deps[frame.dep_idx];
            frame.dep_idx += 1;
            // 自环跳过（SelfDep 已单独报）
            if next_id == node {
                continue;
            }
            let next = match id_to_task.get(next_id) {
                Some(t) => *t,
                None => continue, // 悬空跳过
            };
            let next_color = *color.get(&next.id).unwrap_or(&0);
            match next_color {
                1 => {
                    // 命中灰节点 → 环。回溯 stack 找环段。
                    let mut cycle: Vec<SubTaskId> = Vec::new();
                    let mut found = false;
                    for s in stack.iter() {
                        if *s == &next.id {
                            found = true;
                        }
                        if found {
                            cycle.push((*s).clone());
                        }
                    }
                    cycle.push(next.id.clone()); // 闭合
                    return Some(cycle);
                }
                0 => {
                    color.insert(&next.id, 1);
                    stack.push(&next.id);
                    work.push(Frame {
                        node: &next.id,
                        dep_idx: 0,
                    });
                }
                _ => {} // 黑：已 finish，跳过
            }
        } else {
            // 当前节点 deps 耗尽 → 完成
            let done_node = frame.node;
            color.insert(done_node, 2);
            finished.insert(done_node);
            work.pop();
            stack.pop();
        }
    }
    None
}

// ---- 单测（对齐 intent.rs 52 测密度）---------------------------------------

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

    // 辅助：构造一个带工具的最小子任务
    fn t(id: &str) -> SubTask {
        SubTask {
            id: id.to_string(),
            tool_hint: vec!["read_file".to_string()],
            deps: Vec::new(),
            group: None,
            intent: format!("task-{}", id),
        }
    }

    fn t_dep(id: &str, deps: &[&str]) -> SubTask {
        let mut s = t(id);
        s.deps = deps.iter().map(|s| s.to_string()).collect();
        s
    }

    // 变体：deps 用 String（动态 id 场景，如循环构造的 n0/n1/...）
    fn t_dep_s<I: Into<String>>(id: &str, deps: I) -> SubTask {
        let mut s = t(id);
        s.deps = vec![deps.into()];
        s
    }

    // ===== 0.8 Plan/SubTask 结构 =====

    #[test]
    fn subtask_builder_chains() {
        let s = SubTask::new("a", "do a")
            .with_tools(vec!["read_file".into()])
            .with_deps(vec!["b".into()])
            .with_group("g1");
        assert_eq!(s.id, "a");
        assert_eq!(s.intent, "do a");
        assert_eq!(s.tool_hint, vec!["read_file".to_string()]);
        assert_eq!(s.deps, vec!["b".to_string()]);
        assert_eq!(s.group.as_deref(), Some("g1"));
    }

    #[test]
    fn subtask_default_builder_minimal() {
        let s = SubTask::new("x", "");
        assert!(s.tool_hint.is_empty());
        assert!(s.deps.is_empty());
        assert!(s.group.is_none());
    }

    #[test]
    fn plan_new_empty() {
        let p = Plan::new();
        assert!(p.is_empty());
        assert_eq!(p.len(), 0);
    }

    #[test]
    fn plan_from_tasks_len() {
        let p = Plan::from_tasks(vec![t("a"), t("b")]);
        assert_eq!(p.len(), 2);
        assert!(!p.is_empty());
    }

    #[test]
    fn plan_clone_eq() {
        let p = Plan::from_tasks(vec![t("a")]);
        let q = p.clone();
        assert_eq!(p, q);
    }

    #[test]
    fn feature_flags_default_on() {
        // PLANNING_ENABLED 当前开(主 loop 走 filter_tool_defs_planned 规划),Phase 1 已落地。
        // PLAN_HINT_ENABLED(plan_hint 函数 flag)在 plan_hint.rs 单一定义,此处不再断言。
        assert!(PLANNING_ENABLED);
    }

    #[test]
    fn max_plan_depth_is_reasonable() {
        // 8 层覆盖典型软件任务分解
        assert!(MAX_PLAN_DEPTH >= 4);
        assert!(MAX_PLAN_DEPTH <= 16);
    }

    // ===== 0.7 validate: 合法 Plan 通过 =====

    #[test]
    fn validate_single_task_ok() {
        let p = Plan::from_tasks(vec![t("a")]);
        assert!(p.validate().is_ok(), "单任务无依赖应合法");
    }

    #[test]
    fn validate_linear_chain_ok() {
        // a -> b -> c
        let p = Plan::from_tasks(vec![t("a"), t_dep("b", &["a"]), t_dep("c", &["b"])]);
        assert!(p.validate().is_ok(), "线性链应合法");
    }

    #[test]
    fn validate_diamond_ok() {
        // a -> b,c -> d (菱形)
        let p = Plan::from_tasks(vec![
            t("a"),
            t_dep("b", &["a"]),
            t_dep("c", &["a"]),
            t_dep("d", &["b", "c"]),
        ]);
        assert!(p.validate().is_ok(), "菱形 DAG 应合法");
    }

    #[test]
    fn validate_parallel_group_ok() {
        // 同 group 三个无依赖任务
        let mut a = t("a");
        a.group = Some("g1".into());
        let mut b = t("b");
        b.group = Some("g1".into());
        let mut c = t("c");
        c.group = Some("g1".into());
        let p = Plan::from_tasks(vec![a, b, c]);
        assert!(p.validate().is_ok(), "并行组无依赖应合法");
    }

    #[test]
    fn validate_require_tools_off_allows_empty_tools() {
        let mut s = t("a");
        s.tool_hint.clear();
        let p = Plan::from_tasks(vec![s]);
        let errs = p.validate_with(ValidateOptions {
            require_tools: false,
            max_depth: MAX_PLAN_DEPTH,
        });
        assert!(errs.is_empty(), "关 require_tools 后无工具应放过");
    }

    // ===== 0.7 validate: 五类非法各拒 =====

    #[test]
    fn validate_empty_rejected() {
        let p = Plan::new();
        let r = p.validate();
        assert_eq!(r, Err(vec![PlanError::Empty]));
    }

    #[test]
    fn validate_duplicate_id_rejected() {
        let p = Plan::from_tasks(vec![t("a"), t("a")]);
        let errs = p.validate().unwrap_err();
        assert!(errs.contains(&PlanError::DuplicateId { id: "a".into() }));
    }

    #[test]
    fn validate_dangling_dep_rejected() {
        // b 依赖不存在的 x
        let p = Plan::from_tasks(vec![t("a"), t_dep("b", &["x"])]);
        let errs = p.validate().unwrap_err();
        assert!(errs.contains(&PlanError::UnknownDep {
            from: "b".into(),
            unknown: "x".into()
        }));
    }

    #[test]
    fn validate_self_dep_rejected() {
        let p = Plan::from_tasks(vec![t_dep("a", &["a"])]);
        let errs = p.validate().unwrap_err();
        assert!(errs.contains(&PlanError::SelfDep { id: "a".into() }));
    }

    #[test]
    fn validate_cycle_two_node_rejected() {
        // a <-> b
        let p = Plan::from_tasks(vec![t_dep("a", &["b"]), t_dep("b", &["a"])]);
        let errs = p.validate().unwrap_err();
        assert!(
            errs.iter().any(|e| matches!(e, PlanError::CycleDetected { .. })),
            "双向环应拒为 CycleDetected, errs={:?}", errs
        );
    }

    #[test]
    fn validate_cycle_three_node_rejected() {
        // a -> b -> c -> a
        let p = Plan::from_tasks(vec![
            t_dep("a", &["c"]),
            t_dep("b", &["a"]),
            t_dep("c", &["b"]),
        ]);
        let errs = p.validate().unwrap_err();
        assert!(errs
            .iter()
            .any(|e| matches!(e, PlanError::CycleDetected { .. })));
    }

    #[test]
    fn validate_cycle_self_loop_is_selfdep_not_cycle() {
        // 自环优先报 SelfDep，不进入环检测（validate 早退条件）
        let p = Plan::from_tasks(vec![t_dep("a", &["a"]), t("b")]);
        let errs = p.validate().unwrap_err();
        assert!(errs.contains(&PlanError::SelfDep { id: "a".into() }));
        // SelfDep 触发环检测跳过（不误报）
        assert!(!errs
            .iter()
            .any(|e| matches!(e, PlanError::CycleDetected { .. })));
    }

    #[test]
    fn validate_subtask_without_tools_rejected() {
        let mut s = t("a");
        s.tool_hint.clear();
        let p = Plan::from_tasks(vec![s]);
        let errs = p.validate().unwrap_err();
        assert!(errs.contains(&PlanError::SubtaskWithoutTools { id: "a".into() }));
    }

    #[test]
    fn validate_too_deep_rejected() {
        // 9 节点线性链（深度 9 > MAX_PLAN_DEPTH=8）
        let mut tasks: Vec<SubTask> = vec![t("n0")];
        for i in 1..9 {
            tasks.push(t_dep_s(&format!("n{}", i), format!("n{}", i - 1)));
        }
        let p = Plan::from_tasks(tasks);
        let errs = p.validate().unwrap_err();
        assert!(
            errs.iter().any(|e| matches!(e, PlanError::TooDeep { depth: 9, max: 8 })),
            "9 层应判 TooDeep, errs={:?}", errs
        );
    }

    #[test]
    fn validate_depth_exactly_at_limit_ok() {
        // 8 节点线性链（深度 8 == MAX_PLAN_DEPTH，不超，应合法）
        let mut tasks: Vec<SubTask> = vec![t("n0")];
        for i in 1..8 {
            tasks.push(t_dep_s(&format!("n{}", i), format!("n{}", i - 1)));
        }
        let p = Plan::from_tasks(tasks);
        assert!(p.validate().is_ok(), "8 层(==上限)应合法");
    }

    #[test]
    fn validate_empty_id_rejected() {
        let mut s = t("a");
        s.id = String::new();
        let p = Plan::from_tasks(vec![s]);
        let errs = p.validate().unwrap_err();
        assert!(errs
            .iter()
            .any(|e| matches!(e, PlanError::EmptyId { index: 0 })));
    }

    #[test]
    fn validate_collects_multiple_errors() {
        // 同时：重复 id + 悬空 dep + 无工具
        let mut no_tools = t("a");
        no_tools.tool_hint.clear();
        let p = Plan::from_tasks(vec![
            no_tools,         // 无工具
            t("a"),           // 重复 id（同 "a"）
            t_dep("b", &["x"]), // 悬空 x
        ]);
        let errs = p.validate().unwrap_err();
        assert!(errs.len() >= 3, "应一次收集 ≥3 类违规, got {} 条: {:?}", errs.len(), errs);
        assert!(errs.contains(&PlanError::DuplicateId { id: "a".into() }));
        assert!(errs.contains(&PlanError::SubtaskWithoutTools { id: "a".into() }));
        assert!(errs.contains(&PlanError::UnknownDep {
            from: "b".into(),
            unknown: "x".into()
        }));
    }

    #[test]
    fn validate_dangling_does_not_mask_cycle() {
        // 悬空 + 环共存：悬空报，环因早退不报（设计：悬空优先，环检测跳过）
        let p = Plan::from_tasks(vec![
            t_dep("a", &["b"]),
            t_dep("b", &["a"]),
            t_dep("c", &["x"]), // 悬空
        ]);
        let errs = p.validate().unwrap_err();
        assert!(errs.contains(&PlanError::UnknownDep {
            from: "c".into(),
            unknown: "x".into()
        }));
    }

    #[test]
    fn plan_error_display_nonempty() {
        let e = PlanError::CycleDetected {
            cycle: vec!["a".into(), "b".into(), "a".into()],
        };
        let s = format!("{}", e);
        assert!(s.contains("环"));
        assert!(s.contains("a -> b"));
    }

    #[test]
    fn cycle_error_display() {
        let e = CycleError {
            cycle: vec!["a".into()],
        };
        assert!(format!("{}", e).contains("环"));
    }

    // ===== 0.7 Kahn to_layers =====

    #[test]
    fn layers_empty_plan() {
        let p = Plan::new();
        assert_eq!(p.to_layers().unwrap(), Vec::<Vec<SubTask>>::new());
    }

    #[test]
    fn layers_single_task_one_layer() {
        let p = Plan::from_tasks(vec![t("a")]);
        let layers = p.to_layers().unwrap();
        assert_eq!(layers.len(), 1);
        assert_eq!(layers[0].len(), 1);
        assert_eq!(layers[0][0].id, "a");
    }

    #[test]
    fn layers_linear_chain_n_layers() {
        // a -> b -> c → 3 层各 1 节点
        let p = Plan::from_tasks(vec![t("a"), t_dep("b", &["a"]), t_dep("c", &["b"])]);
        let layers = p.to_layers().unwrap();
        assert_eq!(layers.len(), 3);
        assert_eq!(layers[0][0].id, "a");
        assert_eq!(layers[1][0].id, "b");
        assert_eq!(layers[2][0].id, "c");
    }

    #[test]
    fn layers_parallel_group_one_layer() {
        // a,b,c 无依赖 → 同一层（可并行）
        let p = Plan::from_tasks(vec![t("a"), t("b"), t("c")]);
        let layers = p.to_layers().unwrap();
        assert_eq!(layers.len(), 1, "无依赖应单层: {:?}", layers);
        let ids: Vec<&str> = layers[0].iter().map(|t| t.id.as_str()).collect();
        assert_eq!(ids, vec!["a", "b", "c"]); // 保持原顺序稳定
    }

    #[test]
    fn layers_diamond_two_then_one() {
        // a -> b,c -> d
        // 层 0: [a], 层 1: [b, c]（并行）, 层 2: [d]
        let p = Plan::from_tasks(vec![
            t("a"),
            t_dep("b", &["a"]),
            t_dep("c", &["a"]),
            t_dep("d", &["b", "c"]),
        ]);
        let layers = p.to_layers().unwrap();
        assert_eq!(layers.len(), 3);
        assert_eq!(layer_ids(&layers, 0), vec!["a"]);
        assert_eq!(layer_ids(&layers, 1), vec!["b", "c"]);
        assert_eq!(layer_ids(&layers, 2), vec!["d"]);
    }

    #[test]
    fn layers_preserves_input_order_within_layer() {
        // 多个无依赖节点，同层应保持 Plan 内顺序（稳定）
        let p = Plan::from_tasks(vec![t("z"), t("y"), t("x")]);
        let layers = p.to_layers().unwrap();
        assert_eq!(layer_ids(&layers, 0), vec!["z", "y", "x"]);
    }

    #[test]
    fn layers_cycle_returns_err() {
        // a <-> b
        let p = Plan::from_tasks(vec![t_dep("a", &["b"]), t_dep("b", &["a"])]);
        let err = p.to_layers().unwrap_err();
        // 环成员 a,b 均无法放置
        let cycle_set: HashSet<&str> =
            err.cycle.iter().map(|s| s.as_str()).collect();
        assert!(cycle_set.contains("a"));
        assert!(cycle_set.contains("b"));
    }

    #[test]
    fn layers_self_dep_treated_as_no_indeg() {
        // 自环：A 依赖 A。Kahn 视为无该前驱（不计入度），A 仍可放置
        let p = Plan::from_tasks(vec![t_dep("a", &["a"]), t_dep("b", &["a"])]);
        let layers = p.to_layers().unwrap();
        assert_eq!(layers.len(), 2);
        assert_eq!(layer_ids(&layers, 0), vec!["a"]);
        assert_eq!(layer_ids(&layers, 1), vec!["b"]);
    }

    #[test]
    fn layers_dangling_dep_ignored() {
        // b 依赖不存在的 x —— Kahn 忽略悬空边，b 视为入度 0 入首层
        let p = Plan::from_tasks(vec![t("a"), t_dep("b", &["x"])]);
        let layers = p.to_layers().unwrap();
        assert_eq!(layers.len(), 1, "悬空边忽略后 a,b 同层: {:?}", layers);
        let ids: HashSet<&str> =
            layers[0].iter().map(|t| t.id.as_str()).collect();
        assert!(ids.contains("a"));
        assert!(ids.contains("b"));
    }

    #[test]
    fn layers_deep_chain_count_matches_validate_depth() {
        // 5 节点链 → 5 层 = longest_chain_depth
        let mut tasks: Vec<SubTask> = vec![t("n0")];
        for i in 1..5 {
            tasks.push(t_dep_s(&format!("n{}", i), format!("n{}", i - 1)));
        }
        let p = Plan::from_tasks(tasks);
        let layers = p.to_layers().unwrap();
        assert_eq!(layers.len(), 5);
    }

    #[test]
    fn layers_complex_dag() {
        // 复杂 DAG:
        //   a   b     (层0)
        //    \ / \
        //     c   d   (层1: c 依赖 a,b; d 依赖 b)
        //     |   |
        //     e   f   (层2: e 依赖 c; f 依赖 d)
        //      \ /
        //       g     (层3: g 依赖 e,f)
        let p = Plan::from_tasks(vec![
            t("a"),
            t("b"),
            t_dep("c", &["a", "b"]),
            t_dep("d", &["b"]),
            t_dep("e", &["c"]),
            t_dep("f", &["d"]),
            t_dep("g", &["e", "f"]),
        ]);
        let layers = p.to_layers().unwrap();
        assert_eq!(layers.len(), 4);
        assert_eq!(layer_ids(&layers, 0), vec!["a", "b"]);
        assert_eq!(layer_ids(&layers, 1), vec!["c", "d"]);
        assert_eq!(layer_ids(&layers, 2), vec!["e", "f"]);
        assert_eq!(layer_ids(&layers, 3), vec!["g"]);
    }

    #[test]
    fn layers_returns_clones_not_references() {
        // 返回 Vec<SubTask>（owned），修改不影响原 Plan
        let p = Plan::from_tasks(vec![t("a")]);
        let mut layers = p.to_layers().unwrap();
        layers[0][0].intent = "modified".into();
        assert_eq!(p.tasks[0].intent, "task-a", "原 Plan 不应被改");
    }

    // 辅助
    fn layer_ids(layers: &[Vec<SubTask>], i: usize) -> Vec<&str> {
        layers[i].iter().map(|t| t.id.as_str()).collect()
    }
}
