use crate::common::{ActorPayload, AffectedObject, SourcePayload, TargetRef};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CommandEnvelope<T> {
    pub name: String,
    pub command_id: String,
    pub idempotency_key: Option<String>,
    pub actor: ActorPayload,
    pub source: SourcePayload,
    pub target: Option<TargetRef>,
    pub payload: T,
    pub reason: Option<String>,
    pub refs: Vec<String>,
    pub dry_run: bool,
    pub validate_only: bool,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CommandResult {
    pub ok: bool,
    pub command_id: String,
    pub event_ids: Vec<String>,
    pub affected_objects: Vec<AffectedObject>,
    pub revision: Option<u64>,
    pub warnings: Vec<String>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CreateWorkspace {
    pub name: String,
    pub slug: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CreatePage {
    pub title: String,
    pub parent_page_id: Option<String>,
    pub position: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UpdatePageTitle {
    pub page_id: String,
    pub title: String,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CreateDocumentPage {
    pub page_id: String,
    pub parent_page_id: Option<String>,
    pub title: String,
    pub workspace_seed_id: String,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MoveDocumentPage {
    pub page_id: String,
    pub parent_page_id: Option<String>,
    pub sort_order: i64,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DeleteDocumentPage {
    pub page_id: String,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RestoreDocumentPage {
    pub page_id: String,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DuplicateDocumentPage {
    pub source_page_id: String,
    pub new_page_id: String,
    pub title: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CopyTreeDocumentPages {
    pub items_json: String,
    pub target_parent_page_id: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UpdatePageStats {
    pub page_id: String,
    pub word_count: i64,
    pub character_count: i64,
    pub block_count: i64,
    pub todo_total: i64,
    pub todo_done: i64,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UpdatePageOptions {
    pub page_id: String,
    pub wide_layout: Option<bool>,
    pub small_text: Option<bool>,
    pub show_heading_numbers: Option<bool>,
    pub show_toc: Option<bool>,
    pub show_structure: Option<bool>,
    pub protect_editing: Option<bool>,
    pub show_word_count: Option<bool>,
    pub collapse_backlinks: Option<bool>,
    pub page_font: Option<String>,
    pub layout_density: Option<String>,
    pub hide_child_pages: Option<bool>,
    pub show_block_ref_count: Option<bool>,
    pub embed_default_block_id: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReplaceMediaAssetStorage {
    pub asset_id: String,
    pub storage_id: String,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SavePageContent {
    pub page_id: String,
    pub workspace_id: Option<String>,
    pub revision: Option<u64>,
    pub content_json: String,
    pub conflict_detection_key: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PatchBlock {
    pub page_id: String,
    pub block_id: String,
    pub workspace_id: Option<String>,
    pub revision: Option<u64>,
    pub block_snapshot_json: String,
    pub conflict_detection_key: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PatchPageBlock {
    pub page_id: String,
    pub block_id: String,
    pub workspace_id: Option<String>,
    pub revision: Option<u64>,
    pub block_snapshot_json: String,
    pub conflict_detection_key: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PutMindmap {
    pub document_id: String,
    pub mindmap_id: String,
    pub data_json: String,
    pub create_only: bool,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InsertBlock {
    pub page_id: String,
    pub block_type: String,
    pub content: String,
    pub parent_block_id: Option<String>,
    pub prev_block_id: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UpdateBlock {
    pub block_id: String,
    pub patch_content: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MoveBlock {
    pub block_id: String,
    pub new_parent_block_id: Option<String>,
    pub new_page_id: Option<String>,
    pub prev_block_id: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EmbedBlock {
    pub source_document_id: String,
    pub source_block_id: String,
    pub target_document_id: String,
    pub target_block_id: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DeleteBlock {
    pub block_id: String,
}

// ── CommandContext / when clause ────────────────────────────────────

/// CommandContext — 命令执行上下文的 key-value 集合。
///
/// 对应 VS Code 的 context key 概念。每个 key 表示当前 UI/workspace/editor 的状态片段。
/// 菜单项、快捷键、按钮的 enablement 通过 `when` 表达式对 context 求值得到。
///
/// 第一阶段支持 key 列表：
/// - `workspace.sourceKind` — 工作区类型 ("local_folder" | "convex" | ...)
/// - `workspace.readonly` — bool，工作区是否只读
/// - `tree.focusKind` — 当前聚焦的树类型 ("file_tree" | "page_tree" | "none")
/// - `tree.selectionCount` — 当前选中行数 (i64)
/// - `tree.selectionResourceKind` — 选中资源的类型 ("page" | "folder" | "mindmap" | "asset" | ...)
/// - `editor.dirty` — bool，当前编辑器是否有未保存修改
/// - `editor.hasSelection` — bool，编辑器是否有文本选区
/// - `ai.canWrite` — bool，当前会话是否允许 AI 写入
#[derive(Debug, Clone, PartialEq)]
pub struct CommandContext {
    /// 工作区来源类型
    pub workspace_source_kind: Option<String>,
    /// 工作区是否只读
    pub workspace_readonly: bool,
    /// 树焦点类型
    pub tree_focus_kind: Option<String>,
    /// 树选择计数
    pub tree_selection_count: i64,
    /// 选择资源的 kind
    pub tree_selection_resource_kind: Option<String>,
    /// 编辑器是否 dirty
    pub editor_dirty: bool,
    /// 编辑器是否有选区
    pub editor_has_selection: bool,
    /// AI 是否可写
    pub ai_can_write: bool,
}

impl CommandContext {
    pub fn new() -> Self {
        Self {
            workspace_source_kind: None,
            workspace_readonly: false,
            tree_focus_kind: None,
            tree_selection_count: 0,
            tree_selection_resource_kind: None,
            editor_dirty: false,
            editor_has_selection: false,
            ai_can_write: true,
        }
    }

    /// 获取命名 context key 的值（用于 when 表达式求值）。
    pub fn get(&self, key: &str) -> Option<CommandContextValue> {
        match key {
            "workspace.sourceKind" => self
                .workspace_source_kind
                .as_deref()
                .map(|s| CommandContextValue::String(s.to_string())),
            "workspace.readonly" => Some(CommandContextValue::Bool(self.workspace_readonly)),
            "tree.focusKind" => self
                .tree_focus_kind
                .as_deref()
                .map(|s| CommandContextValue::String(s.to_string())),
            "tree.selectionCount" => Some(CommandContextValue::Number(self.tree_selection_count)),
            "tree.selectionResourceKind" => self
                .tree_selection_resource_kind
                .as_deref()
                .map(|s| CommandContextValue::String(s.to_string())),
            "editor.dirty" => Some(CommandContextValue::Bool(self.editor_dirty)),
            "editor.hasSelection" => Some(CommandContextValue::Bool(self.editor_has_selection)),
            "ai.canWrite" => Some(CommandContextValue::Bool(self.ai_can_write)),
            _ => None,
        }
    }

    /// 用 builder 模式设置 workspace 只读。
    pub fn with_workspace_readonly(mut self, readonly: bool) -> Self {
        self.workspace_readonly = readonly;
        self
    }

    /// 用 builder 模式设置 workspace source kind。
    pub fn with_workspace_source_kind(mut self, kind: &str) -> Self {
        self.workspace_source_kind = Some(kind.to_string());
        self
    }

    /// 用 builder 模式设置 tree focus kind。
    pub fn with_tree_focus_kind(mut self, kind: &str) -> Self {
        self.tree_focus_kind = Some(kind.to_string());
        self
    }

    /// 用 builder 模式设置 tree selection count。
    pub fn with_tree_selection_count(mut self, count: i64) -> Self {
        self.tree_selection_count = count;
        self
    }

    /// 用 builder 模式设置 selection resource kind。
    pub fn with_tree_selection_resource_kind(mut self, kind: &str) -> Self {
        self.tree_selection_resource_kind = Some(kind.to_string());
        self
    }

    /// 用 builder 模式设置 editor dirty。
    pub fn with_editor_dirty(mut self, dirty: bool) -> Self {
        self.editor_dirty = dirty;
        self
    }

    /// 用 builder 模式设置 editor has selection。
    pub fn with_editor_has_selection(mut self, has_selection: bool) -> Self {
        self.editor_has_selection = has_selection;
        self
    }

    /// 用 builder 模式设置 ai can write。
    pub fn with_ai_can_write(mut self, can_write: bool) -> Self {
        self.ai_can_write = can_write;
        self
    }
}

impl Default for CommandContext {
    fn default() -> Self {
        Self::new()
    }
}

/// 命令上下文中的值类型。
#[derive(Debug, Clone, PartialEq)]
pub enum CommandContextValue {
    Bool(bool),
    Number(i64),
    String(String),
}

impl CommandContextValue {
    fn as_bool(&self) -> Option<bool> {
        match self {
            CommandContextValue::Bool(b) => Some(*b),
            _ => None,
        }
    }

    fn as_string(&self) -> Option<&str> {
        match self {
            CommandContextValue::String(s) => Some(s.as_str()),
            _ => None,
        }
    }
}

/// WhenExprToken — when 表达式的最小 token 类型。
#[derive(Debug, Clone, PartialEq)]
pub enum WhenExprToken {
    /// `key`
    Key(String),
    /// `!key`
    NotKey(String),
    /// `key == value`
    Equal(String, String),
    /// `key != value`
    NotEqual(String, String),
    /// `&&`
    And,
    /// `||`
    Or,
    /// `(` ... `)`
    LParen,
    RParen,
}

/// WhenExpr — when 表达式的 AST 节点。
/// 支持：`key`、`!key`、`key == value`、`key != value`、`&&`、`||`、括号分组。
#[derive(Debug, Clone, PartialEq)]
pub enum WhenExprNode {
    Key(String),
    NotKey(String),
    Equal(String, String),
    NotEqual(String, String),
    And(Box<WhenExprNode>, Box<WhenExprNode>),
    Or(Box<WhenExprNode>, Box<WhenExprNode>),
}

/// 简单的递归下降 when 表达式解析器。
/// 语法：
///   expr     = or_expr
///   or_expr  = and_expr ("||" and_expr)*
///   and_expr = primary ("&&" primary)*
///   primary  = "(" expr ")" | "!" key | "key == value" | "key != value" | key
pub fn parse_when_expr(input: &str) -> Result<WhenExprNode, String> {
    let tokens = tokenize(input)?;
    let (node, rest) = parse_or(&tokens)?;
    if !rest.is_empty() {
        return Err(format!("when 表达式末尾有多余 token: {:?}", rest));
    }
    Ok(node)
}

fn tokenize(input: &str) -> Result<Vec<WhenExprToken>, String> {
    let mut tokens = Vec::new();
    let mut chars = input.chars().peekable();

    while let Some(&ch) = chars.peek() {
        if ch.is_whitespace() {
            chars.next();
            continue;
        }
        match ch {
            '(' => {
                chars.next();
                tokens.push(WhenExprToken::LParen);
            }
            ')' => {
                chars.next();
                tokens.push(WhenExprToken::RParen);
            }
            '!' => {
                chars.next();
                // ! 后面跟 key（不能是 "("）
                let key = read_identifier(&mut chars)?;
                tokens.push(WhenExprToken::NotKey(key));
            }
            '|' => {
                chars.next();
                if chars.next() == Some('|') {
                    tokens.push(WhenExprToken::Or);
                } else {
                    return Err("期望 || 运算符，但只看到一个 |".to_string());
                }
            }
            '&' => {
                chars.next();
                if chars.next() == Some('&') {
                    tokens.push(WhenExprToken::And);
                } else {
                    return Err("期望 && 运算符，但只看到一个 &".to_string());
                }
            }
            '=' => {
                return Err("意外的 = 符号，应为 ==".to_string());
            }
            _ if is_ident_start(ch) => {
                let key = read_identifier(&mut chars)?;
                // 检查后面是否跟着 == 或 !=
                let mut next_chars = chars.clone();
                let after_ws = next_chars.find(|c| !c.is_whitespace());
                let is_comparison = matches!(after_ws, Some('=') | Some('!'));
                if is_comparison {
                    // 跳过空白
                    while chars.peek().map_or(false, |c| c.is_whitespace()) {
                        chars.next();
                    }
                    let op_start = chars.next(); // = or !
                    let op_next = chars.next(); // =
                    if op_start == Some('=') && op_next == Some('=') {
                        // ==
                        while chars.peek().map_or(false, |c| c.is_whitespace()) {
                            chars.next();
                        }
                        let value = read_value(&mut chars)?;
                        tokens.push(WhenExprToken::Equal(key, value));
                    } else if op_start == Some('!') && op_next == Some('=') {
                        // !=
                        while chars.peek().map_or(false, |c| c.is_whitespace()) {
                            chars.next();
                        }
                        let value = read_value(&mut chars)?;
                        tokens.push(WhenExprToken::NotEqual(key, value));
                    } else {
                        return Err(format!("key '{}' 后面的运算符需为 == 或 !=", key));
                    }
                } else {
                    tokens.push(WhenExprToken::Key(key));
                }
            }
            _ => {
                return Err(format!("意外的字符: '{}'", ch));
            }
        }
    }

    Ok(tokens)
}

fn read_identifier(chars: &mut std::iter::Peekable<std::str::Chars>) -> Result<String, String> {
    let mut ident = String::new();
    while let Some(&ch) = chars.peek() {
        if ch.is_alphanumeric() || ch == '_' || ch == '.' {
            ident.push(ch);
            chars.next();
        } else {
            break;
        }
    }
    if ident.is_empty() {
        return Err("期望标识符".to_string());
    }
    Ok(ident)
}

fn read_value(chars: &mut std::iter::Peekable<std::str::Chars>) -> Result<String, String> {
    let mut value = String::new();
    // 跳过空白 (已经在调用前跳过了)
    // 检查是否有引号
    if chars.peek() == Some(&'"') || chars.peek() == Some(&'\'') {
        let quote = chars.next().unwrap();
        while let Some(&ch) = chars.peek() {
            if ch == quote {
                chars.next();
                return Ok(value);
            }
            value.push(ch);
            chars.next();
        }
        return Err("未闭合的引号".to_string());
    }
    // 无引号值：读取到下一个空白或运算符为止
    while let Some(&ch) = chars.peek() {
        if ch.is_whitespace() || ch == ')' || ch == '|' || ch == '&' {
            break;
        }
        value.push(ch);
        chars.next();
    }
    if value.is_empty() {
        return Err("期望值".to_string());
    }
    Ok(value)
}

fn is_ident_start(ch: char) -> bool {
    ch.is_ascii_alphabetic() || ch == '_'
}

fn parse_or<'a>(
    tokens: &'a [WhenExprToken],
) -> Result<(WhenExprNode, &'a [WhenExprToken]), String> {
    let (mut left, mut rest) = parse_and(tokens)?;
    while let Some(WhenExprToken::Or) = rest.first() {
        rest = &rest[1..];
        let (right, new_rest) = parse_and(rest)?;
        left = WhenExprNode::Or(Box::new(left), Box::new(right));
        rest = new_rest;
    }
    Ok((left, rest))
}

fn parse_and<'a>(
    tokens: &'a [WhenExprToken],
) -> Result<(WhenExprNode, &'a [WhenExprToken]), String> {
    let (mut left, mut rest) = parse_primary(tokens)?;
    while let Some(WhenExprToken::And) = rest.first() {
        rest = &rest[1..];
        let (right, new_rest) = parse_primary(rest)?;
        left = WhenExprNode::And(Box::new(left), Box::new(right));
        rest = new_rest;
    }
    Ok((left, rest))
}

fn parse_primary<'a>(
    tokens: &'a [WhenExprToken],
) -> Result<(WhenExprNode, &'a [WhenExprToken]), String> {
    if let Some(token) = tokens.first() {
        match token {
            WhenExprToken::Key(key) => Ok((WhenExprNode::Key(key.clone()), &tokens[1..])),
            WhenExprToken::NotKey(key) => Ok((WhenExprNode::NotKey(key.clone()), &tokens[1..])),
            WhenExprToken::Equal(key, value) => Ok((
                WhenExprNode::Equal(key.clone(), value.clone()),
                &tokens[1..],
            )),
            WhenExprToken::NotEqual(key, value) => Ok((
                WhenExprNode::NotEqual(key.clone(), value.clone()),
                &tokens[1..],
            )),
            WhenExprToken::LParen => {
                let (node, rest) = parse_or(&tokens[1..])?;
                if let Some(WhenExprToken::RParen) = rest.first() {
                    Ok((node, &rest[1..]))
                } else {
                    Err("缺少闭合的 ')'".to_string())
                }
            }
            _ => Err(format!("期望表达式，得到 {:?}", token)),
        }
    } else {
        Err("期望表达式，但没有更多 token".to_string())
    }
}

/// 对 `CommandContext` 求值一个 when 表达式。
pub fn evaluate_when(ctx: &CommandContext, expr: &WhenExprNode) -> bool {
    match expr {
        WhenExprNode::Key(key) => ctx.get(key).map_or(false, |v| match v {
            CommandContextValue::Bool(b) => b,
            CommandContextValue::Number(n) => n != 0,
            CommandContextValue::String(s) => !s.is_empty(),
        }),
        WhenExprNode::NotKey(key) => !ctx.get(key).map_or(false, |v| match v {
            CommandContextValue::Bool(b) => b,
            CommandContextValue::Number(n) => n != 0,
            CommandContextValue::String(s) => !s.is_empty(),
        }),
        WhenExprNode::Equal(key, expected) => ctx.get(key).map_or(false, |v| {
            v.as_string().map_or(false, |s| s == expected)
                || expected
                    .parse::<i64>()
                    .ok()
                    .is_some_and(|expected_number| {
                        matches!(v, CommandContextValue::Number(n) if n == expected_number)
                    })
                || v.as_bool().map_or(false, |b| {
                    expected == "true" && b || expected == "false" && !b
                })
        }),
        WhenExprNode::NotEqual(key, expected) => ctx.get(key).map_or(true, |v| {
            v.as_string().map_or(true, |s| s != expected)
                && expected
                    .parse::<i64>()
                    .ok()
                    .is_none_or(|expected_number| {
                        !matches!(v, CommandContextValue::Number(n) if n == expected_number)
                    })
                && v.as_bool().map_or(true, |b| {
                    !(expected == "true" && b || expected == "false" && !b)
                })
        }),
        WhenExprNode::And(left, right) => evaluate_when(ctx, left) && evaluate_when(ctx, right),
        WhenExprNode::Or(left, right) => evaluate_when(ctx, left) || evaluate_when(ctx, right),
    }
}

/// 解析并求值一条 when 表达式字符串。
pub fn check_when(ctx: &CommandContext, when_expr: &str) -> Result<bool, String> {
    let expr = parse_when_expr(when_expr)?;
    Ok(evaluate_when(ctx, &expr))
}

/// 检查一个命令在指定上下文中是否应 enabled。
/// 如果 when 表达式为 None 或空，默认返回 true。
pub fn is_command_enabled(ctx: &CommandContext, when: Option<&str>) -> bool {
    match when {
        None | Some("") => true,
        Some(expr) => check_when(ctx, expr).unwrap_or(true),
    }
}

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

    #[test]
    fn command_context_when_clause() {
        let ctx = CommandContext::new()
            .with_workspace_readonly(false)
            .with_workspace_source_kind("local_folder")
            .with_tree_focus_kind("file_tree")
            .with_tree_selection_count(1)
            .with_tree_selection_resource_kind("page")
            .with_editor_dirty(false)
            .with_ai_can_write(true);

        // 基础 key 表达式（bool）
        assert!(check_when(&ctx, "ai.canWrite").unwrap());
        assert!(!check_when(&ctx, "editor.dirty").unwrap());
        assert!(!check_when(&ctx, "workspace.readonly").unwrap());

        // !key
        assert!(check_when(&ctx, "!editor.dirty").unwrap());
        assert!(!check_when(&ctx, "!ai.canWrite").unwrap());

        // key == value
        assert!(check_when(&ctx, "tree.focusKind == file_tree").unwrap());
        assert!(!check_when(&ctx, "tree.focusKind == page_tree").unwrap());

        // key != value
        assert!(check_when(&ctx, "tree.focusKind != page_tree").unwrap());
        assert!(!check_when(&ctx, "tree.focusKind != file_tree").unwrap());

        // && 组合
        assert!(check_when(&ctx, "ai.canWrite && !workspace.readonly").unwrap());
        assert!(!check_when(&ctx, "ai.canWrite && workspace.readonly").unwrap());
        assert!(!check_when(&ctx, "editor.dirty && ai.canWrite").unwrap());

        // || 组合
        assert!(check_when(&ctx, "editor.dirty || ai.canWrite").unwrap());
        assert!(!check_when(&ctx, "editor.dirty || workspace.readonly").unwrap());

        // 括号分组
        assert!(check_when(&ctx, "(ai.canWrite && !workspace.readonly) || editor.dirty").unwrap());
        assert!(!check_when(&ctx, "editor.dirty && (ai.canWrite || workspace.readonly)").unwrap());
    }

    #[test]
    fn command_context_disables_write_commands_for_readonly_workspace() {
        let write_commands = vec![
            (
                "tree.node.create",
                Some("!workspace.readonly && ai.canWrite"),
            ),
            ("tree.node.rename", Some("!workspace.readonly")),
            ("tree.node.delete", Some("!workspace.readonly")),
            ("tree.node.move", Some("!workspace.readonly")),
        ];

        let ctx_readonly = CommandContext::new()
            .with_workspace_readonly(true)
            .with_ai_can_write(true);

        let ctx_writable = CommandContext::new()
            .with_workspace_readonly(false)
            .with_ai_can_write(true);

        for (_name, when) in &write_commands {
            // 只读上下文应禁用所有写命令
            assert!(
                !is_command_enabled(&ctx_readonly, *when),
                "只读上下文不应启用命令 {:?}",
                _name
            );
            // 可写上下文应启用
            assert!(
                is_command_enabled(&ctx_writable, *when),
                "可写上下文应启用命令 {:?}",
                _name
            );
        }
    }

    #[test]
    fn command_context_filters_resource_commands_by_kind() {
        let folder_commands = vec![
            ("tree.node.create", Some("tree.focusKind == file_tree")),
            (
                "tree.folder.rename",
                Some("tree.selectionResourceKind == folder"),
            ),
        ];
        let page_commands = vec![
            ("page.open", Some("tree.selectionResourceKind == page")),
            ("page.delete", Some("tree.selectionResourceKind == page")),
        ];
        let mindmap_commands = vec![(
            "mindmap.open",
            Some("tree.selectionResourceKind == mindmap"),
        )];

        let ctx_folder = CommandContext::new()
            .with_tree_focus_kind("file_tree")
            .with_tree_selection_resource_kind("folder")
            .with_tree_selection_count(1);

        let ctx_page = CommandContext::new()
            .with_tree_focus_kind("file_tree")
            .with_tree_selection_resource_kind("page")
            .with_tree_selection_count(1);

        let ctx_mindmap = CommandContext::new()
            .with_tree_focus_kind("file_tree")
            .with_tree_selection_resource_kind("mindmap")
            .with_tree_selection_count(1);

        // folder 上下文
        for (_name, when) in &folder_commands {
            assert!(
                is_command_enabled(&ctx_folder, *when),
                "folder ctx 应启用命令 {:?}",
                _name
            );
        }
        for (_name, when) in &page_commands {
            assert!(
                !is_command_enabled(&ctx_folder, *when),
                "folder ctx 不应启用 page 命令 {:?}",
                _name
            );
        }

        // page 上下文
        for (_name, when) in &page_commands {
            assert!(
                is_command_enabled(&ctx_page, *when),
                "page ctx 应启用 page 命令 {:?}",
                _name
            );
        }
        assert!(
            !is_command_enabled(&ctx_page, Some("tree.selectionResourceKind == mindmap")),
            "page ctx 不应启用 mindmap 命令"
        );

        // mindmap 上下文
        for (_name, when) in &mindmap_commands {
            assert!(
                is_command_enabled(&ctx_mindmap, *when),
                "mindmap ctx 应启用 mindmap 命令 {:?}",
                _name
            );
        }
    }

    #[test]
    fn command_context_default_is_allow() {
        let ctx = CommandContext::new();
        // 没有 when 表达式 → true
        assert!(is_command_enabled(&ctx, None));
        assert!(is_command_enabled(&ctx, Some("")));
    }

    #[test]
    fn command_context_number_comparison() {
        let ctx = CommandContext::new().with_tree_selection_count(3);

        // 数字值在 bool 语境下：非零为 true
        assert!(check_when(&ctx, "tree.selectionCount").unwrap());
        assert!(check_when(&ctx, "tree.selectionCount == 3").unwrap());
        assert!(check_when(&ctx, "tree.selectionCount != 1").unwrap());
        assert!(!check_when(&ctx, "tree.selectionCount == 1").unwrap());
        let ctx_zero = CommandContext::new().with_tree_selection_count(0);
        assert!(!check_when(&ctx_zero, "tree.selectionCount").unwrap());
        assert!(check_when(&ctx_zero, "tree.selectionCount == 0").unwrap());
    }

    #[test]
    fn command_context_parse_error_returns_default() {
        let ctx = CommandContext::new();
        // 解析错误 → is_command_enabled 返回 true（默认允许）
        assert!(is_command_enabled(&ctx, Some("invalid syntax &&&")));
    }

    #[test]
    fn command_context_ai_can_write_gates_ai_actions() {
        let ctx_can_write = CommandContext::new().with_ai_can_write(true);
        let ctx_readonly = CommandContext::new().with_ai_can_write(false);

        assert!(is_command_enabled(&ctx_can_write, Some("ai.canWrite")));
        assert!(!is_command_enabled(&ctx_readonly, Some("ai.canWrite")));
    }
}
