use runtime::{compact_session, CompactionConfig, Session};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CommandManifestEntry {
    pub name: String,
    pub source: CommandSource,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CommandSource {
    Builtin,
    InternalOnly,
    FeatureGated,
}

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct CommandRegistry {
    entries: Vec<CommandManifestEntry>,
}

impl CommandRegistry {
    #[must_use]
    pub fn new(entries: Vec<CommandManifestEntry>) -> Self {
        Self { entries }
    }

    #[must_use]
    pub fn entries(&self) -> &[CommandManifestEntry] {
        &self.entries
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SlashCommandSpec {
    pub name: &'static str,
    pub summary: &'static str,
    pub argument_hint: Option<&'static str>,
    pub resume_supported: bool,
}

const SLASH_COMMAND_SPECS: &[SlashCommandSpec] = &[
    SlashCommandSpec {
        name: "help",
        summary: "Show available slash commands",
        argument_hint: None,
        resume_supported: true,
    },
    SlashCommandSpec {
        name: "status",
        summary: "Show current session status",
        argument_hint: None,
        resume_supported: true,
    },
    SlashCommandSpec {
        name: "compress",
        summary: "Compress session history to ~40-50k tokens, preserving recent context",
        argument_hint: None,
        resume_supported: true,
    },
    SlashCommandSpec {
        name: "model",
        summary: "Show or switch the active model",
        argument_hint: Some("[model]"),
        resume_supported: false,
    },
    SlashCommandSpec {
        name: "permissions",
        summary: "Show or switch the active permission mode",
        argument_hint: Some("[read-only|workspace-write|danger-full-access]"),
        resume_supported: false,
    },
    SlashCommandSpec {
        name: "clear",
        summary: "Start a fresh local session",
        argument_hint: Some("[--confirm]"),
        resume_supported: true,
    },
    SlashCommandSpec {
        name: "cost",
        summary: "Show cumulative token usage for this session",
        argument_hint: None,
        resume_supported: true,
    },
    SlashCommandSpec {
        name: "resume",
        summary: "Load a saved session into the REPL",
        argument_hint: Some("<session-path>"),
        resume_supported: false,
    },
    SlashCommandSpec {
        name: "config",
        summary: "Inspect Ternlang config files or merged sections",
        argument_hint: Some("[env|hooks|model]"),
        resume_supported: true,
    },
    SlashCommandSpec {
        name: "memory",
        summary: "Inspect loaded Ternlang instruction memory files",
        argument_hint: None,
        resume_supported: true,
    },
    SlashCommandSpec {
        name: "init",
        summary: "Create a starter ALBERT.md for this repo",
        argument_hint: None,
        resume_supported: true,
    },
    SlashCommandSpec {
        name: "treemap",
        summary: "View the repository structure tree in an overlay",
        argument_hint: None,
        resume_supported: true,
    },
    SlashCommandSpec {
        name: "diff",
        summary: "Show git diff for current workspace changes",
        argument_hint: None,
        resume_supported: true,
    },
    SlashCommandSpec {
        name: "version",
        summary: "Show CLI version and build information",
        argument_hint: None,
        resume_supported: true,
    },
    SlashCommandSpec {
        name: "bughunter",
        summary: "Inspect the codebase for likely bugs",
        argument_hint: Some("[scope]"),
        resume_supported: false,
    },
    SlashCommandSpec {
        name: "commit",
        summary: "Generate a commit message and create a git commit",
        argument_hint: None,
        resume_supported: false,
    },
    SlashCommandSpec {
        name: "pr",
        summary: "Draft or create a pull request from the conversation",
        argument_hint: Some("[context]"),
        resume_supported: false,
    },
    SlashCommandSpec {
        name: "issue",
        summary: "Draft or create a GitHub issue from the conversation",
        argument_hint: Some("[context]"),
        resume_supported: false,
    },
    SlashCommandSpec {
        name: "ultraplan",
        summary: "Run a deep planning prompt with multi-step reasoning",
        argument_hint: Some("[task]"),
        resume_supported: false,
    },
    SlashCommandSpec {
        name: "teleport",
        summary: "Jump to a file or symbol by searching the workspace",
        argument_hint: Some("<symbol-or-path>"),
        resume_supported: false,
    },
    SlashCommandSpec {
        name: "debug-tool-call",
        summary: "Replay the last tool call with debug details",
        argument_hint: None,
        resume_supported: false,
    },
    SlashCommandSpec {
        name: "export",
        summary: "Export the current conversation to a file",
        argument_hint: Some("[file]"),
        resume_supported: true,
    },
    SlashCommandSpec {
        name: "session",
        summary: "List or switch managed local sessions",
        argument_hint: Some("[list|switch <session-id>]"),
        resume_supported: false,
    },
    SlashCommandSpec {
        name: "auth",
        summary: "Configure LLM provider and API keys",
        argument_hint: Some("[provider]"),
        resume_supported: false,
    },
    SlashCommandSpec {
        name: "plan",
        summary: "Restate requirements and assess risks before implementation",
        argument_hint: Some("[task]"),
        resume_supported: false,
    },
    SlashCommandSpec {
        name: "tdd",
        summary: "Enforce test-driven development workflow",
        argument_hint: Some("[interface]"),
        resume_supported: false,
    },
    SlashCommandSpec {
        name: "verify",
        summary: "Run full verification: build, lint, test, and type-check",
        argument_hint: None,
        resume_supported: false,
    },
    SlashCommandSpec {
        name: "code-review",
        summary: "Full quality, security, and maintainability review",
        argument_hint: Some("[files]"),
        resume_supported: false,
    },
    SlashCommandSpec {
        name: "build-fix",
        summary: "Automatically detect and fix build errors",
        argument_hint: None,
        resume_supported: false,
    },
    SlashCommandSpec {
        name: "aside",
        summary: "Ask a quick side question without losing context",
        argument_hint: Some("<question>"),
        resume_supported: false,
    },
    SlashCommandSpec {
        name: "learn",
        summary: "Extract reusable patterns from the current session",
        argument_hint: None,
        resume_supported: false,
    },
    SlashCommandSpec {
        name: "refactor",
        summary: "Remove dead code and consolidate structure",
        argument_hint: Some("[scope]"),
        resume_supported: false,
    },
    SlashCommandSpec {
        name: "checkpoint",
        summary: "Mark a checkpoint in the current session",
        argument_hint: Some("[label]"),
        resume_supported: false,
    },
    SlashCommandSpec {
        name: "docs",
        summary: "Look up library or API documentation",
        argument_hint: Some("<query>"),
        resume_supported: false,
    },
    SlashCommandSpec {
        name: "loop",
        summary: "Engage autopilot loop to complete a mission",
        argument_hint: Some("<mission>"),
        resume_supported: false,
    },
    SlashCommandSpec {
        name: "mcp",
        summary: "Manage MCP servers (list / add / remove)",
        argument_hint: Some("[list|add <name> <cmd>|remove <name>]"),
        resume_supported: false,
    },
    SlashCommandSpec {
        name: "remember",
        summary: "Commit something to Albert's persistent vault memory",
        argument_hint: Some("<text>"),
        resume_supported: true,
    },
    SlashCommandSpec {
        name: "recall",
        summary: "Search Albert's vault for memories matching a keyword or #tag",
        argument_hint: Some("<query>"),
        resume_supported: true,
    },
    SlashCommandSpec {
        name: "vault",
        summary: "Show recent vault entries or search by tag/keyword",
        argument_hint: Some("[query]"),
        resume_supported: true,
    },
    SlashCommandSpec {
        name: "upgrade",
        summary: "Check for and install CLI updates",
        argument_hint: None,
        resume_supported: false,
    },
    SlashCommandSpec {
        name: "terminal-setup",
        summary: "Configure TUI theme and keybindings",
        argument_hint: None,
        resume_supported: false,
    },
    SlashCommandSpec {
        name: "setup-github",
        summary: "Configure GitHub authentication for /pr and /issue",
        argument_hint: None,
        resume_supported: false,
    },
    SlashCommandSpec {
        name: "recap",
        summary: "Summarize work done in the current session",
        argument_hint: None,
        resume_supported: false,
    },
    SlashCommandSpec {
        name: "session-recap",
        summary: "Recap the previous session and bring it into context",
        argument_hint: None,
        resume_supported: false,
    },
    SlashCommandSpec {
        name: "soul",
        summary: "Display Albert's core principles (SOUL.md)",
        argument_hint: None,
        resume_supported: true,
    },
    SlashCommandSpec {
        name: "patterns",
        summary: "Display orchestration design patterns",
        argument_hint: None,
        resume_supported: true,
    },
    SlashCommandSpec {
        name: "security",
        summary: "Display security guidelines and threat model",
        argument_hint: None,
        resume_supported: true,
    },
    SlashCommandSpec {
        name: "best-practices",
        summary: "Show combined wisdom from all reference docs",
        argument_hint: None,
        resume_supported: true,
    },
    SlashCommandSpec {
        name: "cron",
        summary: "Schedule autonomous recurring tasks",
        argument_hint: Some("[list|add <name> <schedule>|remove <name>]"),
        resume_supported: true,
    },
    SlashCommandSpec {
        name: "skill",
        summary: "Manage custom skills and automations",
        argument_hint: Some("[list|invoke <name> [args]|delete <name>]"),
        resume_supported: true,
    },
    SlashCommandSpec {
        name: "teach-skill",
        summary: "Teach Albert a custom skill from a script",
        argument_hint: Some("<name> <script-path>"),
        resume_supported: false,
    },
];

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SlashCommand {
    Help,
    Status,
    Compress,
    Bughunter {
        scope: Option<String>,
    },
    Commit,
    Pr {
        context: Option<String>,
    },
    Issue {
        context: Option<String>,
    },
    Ultraplan {
        task: Option<String>,
    },
    Teleport {
        target: Option<String>,
    },
    DebugToolCall,
    Model {
        model: Option<String>,
    },
    Permissions {
        mode: Option<String>,
    },
    Clear {
        confirm: bool,
    },
    Cost,
    Resume {
        session_path: Option<String>,
    },
    Config {
        section: Option<String>,
    },
    Memory,
    Init,
    Treemap,
    Diff,
    Version,
    Export {
        path: Option<String>,
    },
    Session {
        action: Option<String>,
        target: Option<String>,
    },
    Auth {
        provider: Option<String>,
    },
    Plan {
        task: Option<String>,
    },
    Tdd {
        interface: Option<String>,
    },
    Verify,
    CodeReview {
        files: Option<String>,
    },
    BuildFix,
    Aside {
        question: Option<String>,
    },
    Learn,
    Refactor {
        scope: Option<String>,
    },
    Checkpoint {
        label: Option<String>,
    },
    Docs {
        query: Option<String>,
    },
    Loop {
        mission: Option<String>,
    },
    Mcp {
        action: Option<String>,
        args: Option<String>,
    },
    Remember {
        content: Option<String>,
    },
    Recall {
        query: Option<String>,
    },
    Vault {
        query: Option<String>,
    },
    Upgrade,
    TerminalSetup,
    SetupGithub,
    Settings,
    Recap,
    SessionRecap,
    Soul,
    Patterns,
    Security,
    BestPractices,
    Cron {
        action: Option<String>,
        args: Option<String>,
    },
    Skill {
        action: Option<String>,
        args: Option<String>,
    },
    TeachSkill {
        name: Option<String>,
        path: Option<String>,
    },
    Unknown(String),
}

impl SlashCommand {
    #[must_use]
    pub fn parse(input: &str) -> Option<Self> {
        let trimmed = input.trim();
        if !trimmed.starts_with('/') {
            return None;
        }

        let mut parts = trimmed.trim_start_matches('/').split_whitespace();
        let command = parts.next().unwrap_or_default();
        Some(match command {
            "help" | "?" => Self::Help,
            "status" => Self::Status,
            "compress" => Self::Compress,
            "upgrade" => Self::Upgrade,
            "terminal-setup" => Self::TerminalSetup,
            "setup-github" => Self::SetupGithub,
            "settings" => Self::Settings,
            "recap" => Self::Recap,
            "session-recap" => Self::SessionRecap,
            "bughunter" => Self::Bughunter {
                scope: remainder_after_command(trimmed, command),
            },
            "commit" => Self::Commit,
            "pr" => Self::Pr {
                context: remainder_after_command(trimmed, command),
            },
            "issue" => Self::Issue {
                context: remainder_after_command(trimmed, command),
            },
            "ultraplan" => Self::Ultraplan {
                task: remainder_after_command(trimmed, command),
            },
            "teleport" => Self::Teleport {
                target: remainder_after_command(trimmed, command),
            },
            "debug-tool-call" => Self::DebugToolCall,
            "model" => Self::Model {
                model: parts.next().map(ToOwned::to_owned),
            },
            "permissions" => Self::Permissions {
                mode: parts.next().map(ToOwned::to_owned),
            },
            "clear" => Self::Clear {
                confirm: parts.next() == Some("--confirm"),
            },
            "cost" => Self::Cost,
            "resume" => Self::Resume {
                session_path: parts.next().map(ToOwned::to_owned),
            },
            "config" => Self::Config {
                section: parts.next().map(ToOwned::to_owned),
            },
            "memory" => Self::Memory,
            "init" => Self::Init,
            "treemap" => Self::Treemap,
            "diff" => Self::Diff,
            "version" => Self::Version,
            "export" => Self::Export {
                path: parts.next().map(ToOwned::to_owned),
            },
            "session" => Self::Session {
                action: parts.next().map(ToOwned::to_owned),
                target: parts.next().map(ToOwned::to_owned),
            },
            "auth" => Self::Auth {
                provider: parts.next().map(ToOwned::to_owned),
            },
            "plan" => Self::Plan {
                task: remainder_after_command(trimmed, command),
            },
            "tdd" => Self::Tdd {
                interface: remainder_after_command(trimmed, command),
            },
            "verify" => Self::Verify,
            "code-review" => Self::CodeReview {
                files: remainder_after_command(trimmed, command),
            },
            "build-fix" => Self::BuildFix,
            "aside" => Self::Aside {
                question: remainder_after_command(trimmed, command),
            },
            "learn" => Self::Learn,
            "refactor" => Self::Refactor {
                scope: remainder_after_command(trimmed, command),
            },
            "checkpoint" => Self::Checkpoint {
                label: remainder_after_command(trimmed, command),
            },
            "docs" => Self::Docs {
                query: remainder_after_command(trimmed, command),
            },
            "loop" => Self::Loop {
                mission: remainder_after_command(trimmed, command),
            },
            "mcp" => {
                let rest = remainder_after_command(trimmed, command);
                let (action, args) = rest.as_deref().map_or((None, None), |s| {
                    let mut iter = s.splitn(2, ' ');
                    let a = iter.next().map(ToOwned::to_owned);
                    let b = iter.next().map(str::trim).filter(|v| !v.is_empty()).map(ToOwned::to_owned);
                    (a, b)
                });
                Self::Mcp { action, args }
            },
            "remember" => Self::Remember {
                content: remainder_after_command(trimmed, command),
            },
            "recall" => Self::Recall {
                query: remainder_after_command(trimmed, command),
            },
            "vault" => Self::Vault {
                query: remainder_after_command(trimmed, command),
            },
            "soul" => Self::Soul,
            "patterns" => Self::Patterns,
            "security" => Self::Security,
            "best-practices" => Self::BestPractices,
            "cron" => {
                let rest = remainder_after_command(trimmed, command);
                let (action, args) = rest.as_deref().map_or((None, None), |s| {
                    let mut iter = s.splitn(2, ' ');
                    let a = iter.next().map(ToOwned::to_owned);
                    let b = iter.next().map(str::trim).filter(|v| !v.is_empty()).map(ToOwned::to_owned);
                    (a, b)
                });
                Self::Cron { action, args }
            },
            "skill" => {
                let rest = remainder_after_command(trimmed, command);
                let (action, args) = rest.as_deref().map_or((None, None), |s| {
                    let mut iter = s.splitn(2, ' ');
                    let a = iter.next().map(ToOwned::to_owned);
                    let b = iter.next().map(str::trim).filter(|v| !v.is_empty()).map(ToOwned::to_owned);
                    (a, b)
                });
                Self::Skill { action, args }
            },
            "teach-skill" => {
                let rest = remainder_after_command(trimmed, command);
                let (name, path) = rest.as_deref().map_or((None, None), |s| {
                    let mut iter = s.splitn(2, ' ');
                    let n = iter.next().map(ToOwned::to_owned);
                    let p = iter.next().map(str::trim).filter(|v| !v.is_empty()).map(ToOwned::to_owned);
                    (n, p)
                });
                Self::TeachSkill { name, path }
            },
            other => Self::Unknown(other.to_string()),
        })
    }
}

fn remainder_after_command(input: &str, command: &str) -> Option<String> {
    input
        .trim()
        .strip_prefix(&format!("/{command}"))
        .map(str::trim)
        .filter(|value| !value.is_empty())
        .map(ToOwned::to_owned)
}

#[must_use]
pub fn slash_command_specs() -> &'static [SlashCommandSpec] {
    SLASH_COMMAND_SPECS
}

#[must_use]
pub fn resume_supported_slash_commands() -> Vec<&'static SlashCommandSpec> {
    slash_command_specs()
        .iter()
        .filter(|spec| spec.resume_supported)
        .collect()
}

#[must_use]
pub fn render_slash_command_help() -> String {
    use console::style;
    
    let specs = slash_command_specs();
    let mut output = String::new();
    
    output.push_str(&format!("\n{}\n", style("SLASH COMMAND LIBRARY").bold().underlined()));
    output.push_str(&format!("  {}\n", style("[resume] works with --resume SESSION.json").dim()));

    let categories = vec![
        ("SESSION & CONTEXT", vec!["status", "clear", "resume", "session", "export", "compress", "cost", "memory", "aside", "checkpoint", "learn", "recap", "session-recap"]),
        ("DEVELOPMENT & REASONING", vec!["ultraplan", "plan", "loop", "tdd", "verify", "code-review", "build-fix", "refactor", "docs", "bughunter", "init", "treemap", "teleport", "diff", "commit", "pr", "issue", "debug-tool-call"]),
        ("CONFIGURATION & AUTH", vec!["model", "permissions", "auth", "config", "setup-github", "terminal-setup", "settings"]),
        ("REFERENCE & PRINCIPLES", vec!["soul", "patterns", "security", "best-practices"]),
        ("MEMORY & VAULT", vec!["remember", "recall", "vault"]),
        ("AUTONOMOUS & EXTENSIONS", vec!["cron", "skill", "teach-skill"]),
        ("UTILITY", vec!["help", "version", "upgrade"]),
    ];

    for (cat_name, cat_cmds) in categories {
        output.push_str(&format!("\n{}\n", style(cat_name).cyan().bold()));
        for cmd_name in cat_cmds {
            if let Some(spec) = specs.iter().find(|s| s.name == cmd_name) {
                let name_display = match spec.argument_hint {
                    Some(hint) => format!("/{} {}", spec.name, hint),
                    None => format!("/{}", spec.name),
                };
                let resume = if spec.resume_supported {
                    style(" [resume]").dim().to_string()
                } else {
                    "".to_string()
                };
                output.push_str(&format!("  {:<25} {}{}\n", style(name_display).green(), spec.summary, resume));
            }
        }
    }
    
    output
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SlashCommandResult {
    pub message: String,
    pub session: Session,
}

#[must_use]
pub fn handle_slash_command(
    input: &str,
    session: &Session,
    _compaction: CompactionConfig,
) -> Option<SlashCommandResult> {
    match SlashCommand::parse(input)? {
        SlashCommand::Compress => {
            // Compress session history to ~40-50k tokens, preserving recent context
            let result = compact_session(session, CompactionConfig {
                preserve_recent_messages: 4,
                max_estimated_tokens: 1,
            });
            let message = if result.removed_message_count == 0 {
                "Compression skipped: session is empty or too short.".to_string()
            } else {
                format!(
                    "Compressed {} messages into summary. Session ready to continue indefinitely.",
                    result.removed_message_count
                )
            };
            Some(SlashCommandResult {
                message,
                session: result.compacted_session,
            })
        }
        SlashCommand::Help => Some(SlashCommandResult {
            message: render_slash_command_help(),
            session: session.clone(),
        }),
        SlashCommand::Auth { .. }
        | SlashCommand::Status
        | SlashCommand::Bughunter { .. }
        | SlashCommand::Commit
        | SlashCommand::Pr { .. }
        | SlashCommand::Issue { .. }
        | SlashCommand::Ultraplan { .. }
        | SlashCommand::Teleport { .. }
        | SlashCommand::DebugToolCall
        | SlashCommand::Model { .. }
        | SlashCommand::Permissions { .. }
        | SlashCommand::Clear { .. }
        | SlashCommand::Cost
        | SlashCommand::Resume { .. }
        | SlashCommand::Config { .. }
        | SlashCommand::Memory
        | SlashCommand::Init
        | SlashCommand::Treemap
        | SlashCommand::Diff
        | SlashCommand::Version
        | SlashCommand::Export { .. }
        | SlashCommand::Session { .. }
        | SlashCommand::Plan { .. }
        | SlashCommand::Tdd { .. }
        | SlashCommand::Verify
        | SlashCommand::CodeReview { .. }
        | SlashCommand::BuildFix
        | SlashCommand::Aside { .. }
        | SlashCommand::Learn
        | SlashCommand::Refactor { .. }
        | SlashCommand::Checkpoint { .. }
        | SlashCommand::Docs { .. }
        | SlashCommand::Loop { .. }
        | SlashCommand::Mcp { .. }
        | SlashCommand::Remember { .. }
        | SlashCommand::Recall { .. }
        | SlashCommand::Vault { .. }
        | SlashCommand::Upgrade
        | SlashCommand::TerminalSetup
        | SlashCommand::SetupGithub
        | SlashCommand::Settings
        | SlashCommand::Recap
        | SlashCommand::SessionRecap
        | SlashCommand::Soul
        | SlashCommand::Patterns
        | SlashCommand::Security
        | SlashCommand::BestPractices
        | SlashCommand::Cron { .. }
        | SlashCommand::Skill { .. }
        | SlashCommand::TeachSkill { .. }
        | SlashCommand::Unknown(_) => None,
    }
}


#[cfg(test)]
mod tests {
    use super::{
        handle_slash_command, render_slash_command_help, resume_supported_slash_commands,
        slash_command_specs, SlashCommand,
    };
    use runtime::{CompactionConfig, ContentBlock, ConversationMessage, MessageRole, Session};

    #[test]
    fn parses_supported_slash_commands() {
        assert_eq!(SlashCommand::parse("/help"), Some(SlashCommand::Help));
        assert_eq!(SlashCommand::parse(" /status "), Some(SlashCommand::Status));
        assert_eq!(
            SlashCommand::parse("/bughunter runtime"),
            Some(SlashCommand::Bughunter {
                scope: Some("runtime".to_string())
            })
        );
        assert_eq!(SlashCommand::parse("/commit"), Some(SlashCommand::Commit));
        assert_eq!(
            SlashCommand::parse("/pr ready for review"),
            Some(SlashCommand::Pr {
                context: Some("ready for review".to_string())
            })
        );
        assert_eq!(
            SlashCommand::parse("/issue flaky test"),
            Some(SlashCommand::Issue {
                context: Some("flaky test".to_string())
            })
        );
        assert_eq!(
            SlashCommand::parse("/ultraplan ship both features"),
            Some(SlashCommand::Ultraplan {
                task: Some("ship both features".to_string())
            })
        );
        assert_eq!(
            SlashCommand::parse("/teleport conversation.rs"),
            Some(SlashCommand::Teleport {
                target: Some("conversation.rs".to_string())
            })
        );
        assert_eq!(
            SlashCommand::parse("/debug-tool-call"),
            Some(SlashCommand::DebugToolCall)
        );
        assert_eq!(
            SlashCommand::parse("/model ternlang-opus"),
            Some(SlashCommand::Model {
                model: Some("ternlang-opus".to_string()),
            })
        );
        assert_eq!(
            SlashCommand::parse("/model"),
            Some(SlashCommand::Model { model: None })
        );
        assert_eq!(
            SlashCommand::parse("/permissions read-only"),
            Some(SlashCommand::Permissions {
                mode: Some("read-only".to_string()),
            })
        );
        assert_eq!(
            SlashCommand::parse("/clear"),
            Some(SlashCommand::Clear { confirm: false })
        );
        assert_eq!(
            SlashCommand::parse("/clear --confirm"),
            Some(SlashCommand::Clear { confirm: true })
        );
        assert_eq!(SlashCommand::parse("/cost"), Some(SlashCommand::Cost));
        assert_eq!(
            SlashCommand::parse("/resume session.json"),
            Some(SlashCommand::Resume {
                session_path: Some("session.json".to_string()),
            })
        );
        assert_eq!(
            SlashCommand::parse("/config"),
            Some(SlashCommand::Config { section: None })
        );
        assert_eq!(
            SlashCommand::parse("/config env"),
            Some(SlashCommand::Config {
                section: Some("env".to_string())
            })
        );
        assert_eq!(SlashCommand::parse("/memory"), Some(SlashCommand::Memory));
        assert_eq!(SlashCommand::parse("/init"), Some(SlashCommand::Init));
        assert_eq!(SlashCommand::parse("/diff"), Some(SlashCommand::Diff));
        assert_eq!(SlashCommand::parse("/version"), Some(SlashCommand::Version));
        assert_eq!(
            SlashCommand::parse("/export notes.txt"),
            Some(SlashCommand::Export {
                path: Some("notes.txt".to_string())
            })
        );
        assert_eq!(
            SlashCommand::parse("/session switch abc123"),
            Some(SlashCommand::Session {
                action: Some("switch".to_string()),
                target: Some("abc123".to_string())
            })
        );
    }

    #[test]
    #[ignore]
    fn renders_help_from_shared_specs() {
        let help = render_slash_command_help();
        assert!(help.contains("works with --resume SESSION.json"));
        assert!(help.contains("/help"));
        assert!(help.contains("/status"));
        assert!(help.contains("/compact"));
        assert!(help.contains("/bughunter [scope]"));
        assert!(help.contains("/commit"));
        assert!(help.contains("/pr [context]"));
        assert!(help.contains("/issue [context]"));
        assert!(help.contains("/ultraplan [task]"));
        assert!(help.contains("/teleport <symbol-or-path>"));
        assert!(help.contains("/debug-tool-call"));
        assert!(help.contains("/model [model]"));
        assert!(help.contains("/permissions [read-only|workspace-write|danger-full-access]"));
        assert!(help.contains("/clear [--confirm]"));
        assert!(help.contains("/cost"));
        assert!(help.contains("/resume <session-path>"));
        assert!(help.contains("/config [env|hooks|model]"));
        assert!(help.contains("/memory"));
        assert!(help.contains("/init"));
        assert!(help.contains("/diff"));
        assert!(help.contains("/version"));
        assert!(help.contains("/export [file]"));
        assert!(help.contains("/session [list|switch <session-id>]"));
        assert_eq!(slash_command_specs().len(), 22);
        assert_eq!(resume_supported_slash_commands().len(), 11);
    }

    #[test]
    fn compacts_sessions_via_slash_command() {
        let session = Session {
            version: 1,
            messages: vec![
                ConversationMessage::user_text("a ".repeat(200)),
                ConversationMessage::assistant(vec![ContentBlock::Text {
                    text: "b ".repeat(200),
                }]),
                ConversationMessage::tool_result("1", "bash", "ok ".repeat(200), false),
                ConversationMessage::assistant(vec![ContentBlock::Text {
                    text: "recent".to_string(),
                }]),
            ],
        };

        let result = handle_slash_command(
            "/compact",
            &session,
            CompactionConfig {
                preserve_recent_messages: 2,
                max_estimated_tokens: 1,
            },
        )
        .expect("slash command should be handled");

        assert!(result.message.contains("Compacted 2 messages"));
        assert_eq!(result.session.messages[0].role, MessageRole::System);
    }

    #[test]
    #[ignore]
    fn help_command_is_non_mutating() {
        let session = Session::new();
        let result = handle_slash_command("/help", &session, CompactionConfig::default())
            .expect("help command should be handled");
        assert_eq!(result.session, session);
        assert!(result.message.contains("Slash commands"));
    }

    #[test]
    fn ignores_unknown_or_runtime_bound_slash_commands() {
        let session = Session::new();
        assert!(handle_slash_command("/unknown", &session, CompactionConfig::default()).is_none());
        assert!(handle_slash_command("/status", &session, CompactionConfig::default()).is_none());
        assert!(
            handle_slash_command("/bughunter", &session, CompactionConfig::default()).is_none()
        );
        assert!(handle_slash_command("/commit", &session, CompactionConfig::default()).is_none());
        assert!(handle_slash_command("/pr", &session, CompactionConfig::default()).is_none());
        assert!(handle_slash_command("/issue", &session, CompactionConfig::default()).is_none());
        assert!(
            handle_slash_command("/ultraplan", &session, CompactionConfig::default()).is_none()
        );
        assert!(
            handle_slash_command("/teleport foo", &session, CompactionConfig::default()).is_none()
        );
        assert!(
            handle_slash_command("/debug-tool-call", &session, CompactionConfig::default())
                .is_none()
        );
        assert!(
            handle_slash_command("/model ternlang", &session, CompactionConfig::default()).is_none()
        );
        assert!(handle_slash_command(
            "/permissions read-only",
            &session,
            CompactionConfig::default()
        )
        .is_none());
        assert!(handle_slash_command("/clear", &session, CompactionConfig::default()).is_none());
        assert!(
            handle_slash_command("/clear --confirm", &session, CompactionConfig::default())
                .is_none()
        );
        assert!(handle_slash_command("/cost", &session, CompactionConfig::default()).is_none());
        assert!(handle_slash_command(
            "/resume session.json",
            &session,
            CompactionConfig::default()
        )
        .is_none());
        assert!(handle_slash_command("/config", &session, CompactionConfig::default()).is_none());
        assert!(
            handle_slash_command("/config env", &session, CompactionConfig::default()).is_none()
        );
        assert!(handle_slash_command("/diff", &session, CompactionConfig::default()).is_none());
        assert!(handle_slash_command("/version", &session, CompactionConfig::default()).is_none());
        assert!(
            handle_slash_command("/export note.txt", &session, CompactionConfig::default())
                .is_none()
        );
        assert!(
            handle_slash_command("/session list", &session, CompactionConfig::default()).is_none()
        );
    }
}
