use super::builtins::{DoctorCommand, parse_doctor_args, parse_effort_args, parse_update_args};
use super::{
    AgentManagerAction, CompactConversationCommand, ScheduleCommandAction, SessionLogExportFormat,
    SessionModeCommand, SlashCommandOutcome, SubprocessManagerAction, handle_slash_command,
};
use vtcode_core::config::types::ReasoningEffortLevel;
use vtcode_core::llm::provider::ResponsesCompactionOptions;
use vtcode_core::skills::command_skill_specs;
use vtcode_core::utils::ansi::AnsiRenderer;
use vtcode_tui::app::InlineHandle;

fn renderer_for_tests() -> AnsiRenderer {
    let (tx, _rx) = tokio::sync::mpsc::unbounded_channel();
    AnsiRenderer::with_inline_ui(InlineHandle::new_for_tests(tx), Default::default())
}

#[test]
fn parse_doctor_defaults_to_full_mode() {
    let mode = parse_doctor_args("", false).expect("should parse");
    assert!(matches!(mode, DoctorCommand::Run { quick: false }));
}

#[test]
fn parse_doctor_defaults_to_interactive_when_inline_ui_available() {
    let mode = parse_doctor_args("", true).expect("should parse");
    assert!(matches!(mode, DoctorCommand::Interactive));
}

#[test]
fn parse_doctor_quick_aliases() {
    let mode = parse_doctor_args("--quick", true).expect("should parse");
    assert!(matches!(mode, DoctorCommand::Run { quick: true }));

    let mode = parse_doctor_args("-q", true).expect("should parse");
    assert!(matches!(mode, DoctorCommand::Run { quick: true }));

    let mode = parse_doctor_args("quick", true).expect("should parse");
    assert!(matches!(mode, DoctorCommand::Run { quick: true }));
}

#[test]
fn parse_doctor_rejects_conflicting_flags() {
    let err = parse_doctor_args("--quick --full", true).expect_err("must reject");
    assert!(err.contains("either --quick or --full"));
}

#[test]
fn parse_update_rejects_conflicting_modes() {
    let err = parse_update_args("check install").expect_err("must reject");
    assert!(err.contains("either 'check' or 'install'"));
}

#[test]
fn parse_effort_defaults_to_picker_mode() {
    let parsed = parse_effort_args("").expect("should parse");
    assert_eq!(parsed, (None, false));
}

#[test]
fn parse_effort_supports_persist_flag_and_level() {
    let parsed = parse_effort_args("--persist xhigh").expect("should parse");
    assert_eq!(parsed, (Some(ReasoningEffortLevel::XHigh), true));

    let parsed = parse_effort_args("high persist").expect("should parse");
    assert_eq!(parsed, (Some(ReasoningEffortLevel::High), true));
}

#[test]
fn parse_effort_rejects_multiple_levels() {
    let err = parse_effort_args("low high").expect_err("must reject");
    assert!(err.contains("at most one effort level"));
}

#[tokio::test]
async fn stop_command_returns_local_stop_outcome() {
    let workspace = std::env::current_dir().expect("workspace");
    let mut renderer = renderer_for_tests();

    let outcome = handle_slash_command("stop", &mut renderer, &workspace)
        .await
        .expect("stop command should parse");

    assert!(matches!(outcome, SlashCommandOutcome::StopAgent));
}

#[tokio::test]
async fn pause_command_is_idle_noop() {
    let workspace = std::env::current_dir().expect("workspace");
    let mut renderer = renderer_for_tests();

    let outcome = handle_slash_command("pause", &mut renderer, &workspace)
        .await
        .expect("pause command should parse");

    assert!(matches!(outcome, SlashCommandOutcome::Handled));
}

#[tokio::test]
async fn donate_command_is_handled() {
    let workspace = std::env::current_dir().expect("workspace");
    let mut renderer = renderer_for_tests();

    let outcome = handle_slash_command("donate", &mut renderer, &workspace)
        .await
        .expect("donate command should parse");

    assert!(matches!(outcome, SlashCommandOutcome::OpenDonateLinks));
}

#[tokio::test]
async fn share_defaults_to_json_and_html_export() {
    let workspace = std::env::current_dir().expect("workspace");
    let mut renderer = renderer_for_tests();

    let outcome = handle_slash_command("share", &mut renderer, &workspace)
        .await
        .expect("share command should parse");

    assert!(matches!(
        outcome,
        SlashCommandOutcome::ShareLog {
            format: SessionLogExportFormat::Both
        }
    ));
}

#[tokio::test]
async fn share_alias_routes_html_export() {
    let workspace = std::env::current_dir().expect("workspace");
    let mut renderer = renderer_for_tests();

    let outcome = handle_slash_command("share html", &mut renderer, &workspace)
        .await
        .expect("share alias should parse");

    assert!(matches!(
        outcome,
        SlashCommandOutcome::ShareLog {
            format: SessionLogExportFormat::Html
        }
    ));
}

#[tokio::test]
async fn removed_share_log_command_no_longer_resolves() {
    let workspace = std::env::current_dir().expect("workspace");
    let mut renderer = renderer_for_tests();

    let outcome = handle_slash_command("share-log html", &mut renderer, &workspace)
        .await
        .expect("removed command should fall through");

    assert!(matches!(
        outcome,
        SlashCommandOutcome::SubmitPrompt { ref prompt } if prompt == "/share-log html"
    ));
}

#[tokio::test]
async fn subprocess_alias_matches_plural_command() {
    let workspace = std::env::current_dir().expect("workspace");
    let mut renderer = renderer_for_tests();

    let outcome = handle_slash_command("subprocess inspect child-1", &mut renderer, &workspace)
        .await
        .expect("subprocess alias should parse");

    assert!(matches!(
        outcome,
        SlashCommandOutcome::ManageSubprocesses {
            action: SubprocessManagerAction::Inspect { ref id }
        } if id == "child-1"
    ));
}

#[tokio::test]
async fn ide_command_returns_toggle_outcome() {
    let workspace = std::env::current_dir().expect("workspace");
    let mut renderer = renderer_for_tests();

    let outcome = handle_slash_command("ide", &mut renderer, &workspace)
        .await
        .expect("ide command should parse");

    assert!(matches!(outcome, SlashCommandOutcome::ToggleIdeContext));
}

#[tokio::test]
async fn effort_command_returns_set_effort_outcome() {
    let workspace = std::env::current_dir().expect("workspace");
    let mut renderer = renderer_for_tests();

    let outcome = handle_slash_command("effort --persist high", &mut renderer, &workspace)
        .await
        .expect("effort command should parse");

    assert!(matches!(
        outcome,
        SlashCommandOutcome::SetEffort {
            level: Some(ReasoningEffortLevel::High),
            persist: true,
        }
    ));
}

#[tokio::test]
async fn memory_command_returns_memory_outcome() {
    let workspace = std::env::current_dir().expect("workspace");
    let mut renderer = renderer_for_tests();

    let outcome = handle_slash_command("memory", &mut renderer, &workspace)
        .await
        .expect("memory command should parse");

    assert!(matches!(outcome, SlashCommandOutcome::ShowMemory));
}

#[tokio::test]
async fn notify_command_uses_default_message() {
    let workspace = std::env::current_dir().expect("workspace");
    let mut renderer = renderer_for_tests();

    let outcome = handle_slash_command("notify", &mut renderer, &workspace)
        .await
        .expect("notify command should parse");

    assert!(matches!(
        outcome,
        SlashCommandOutcome::Notify { ref message }
        if message == "Manual notification from /notify"
    ));
}

#[tokio::test]
async fn notify_command_preserves_custom_message() {
    let workspace = std::env::current_dir().expect("workspace");
    let mut renderer = renderer_for_tests();

    let outcome = handle_slash_command("notify build finished", &mut renderer, &workspace)
        .await
        .expect("notify command should parse");

    assert!(matches!(
        outcome,
        SlashCommandOutcome::Notify { ref message }
        if message == "build finished"
    ));
}

#[tokio::test]
async fn hooks_command_returns_hooks_outcome() {
    let workspace = std::env::current_dir().expect("workspace");
    let mut renderer = renderer_for_tests();

    let outcome = handle_slash_command("hooks", &mut renderer, &workspace)
        .await
        .expect("hooks command should parse");

    assert!(matches!(outcome, SlashCommandOutcome::ShowHooks));
}

#[tokio::test]
async fn config_memory_opens_memory_controls() {
    let workspace = std::env::current_dir().expect("workspace");
    let mut renderer = renderer_for_tests();

    let outcome = handle_slash_command("config memory", &mut renderer, &workspace)
        .await
        .expect("config memory command should parse");

    assert!(matches!(outcome, SlashCommandOutcome::ShowMemoryConfig));
}

#[tokio::test]
async fn config_model_opens_model_settings_tree() {
    let workspace = std::env::current_dir().expect("workspace");
    let mut renderer = renderer_for_tests();

    let outcome = handle_slash_command("config model", &mut renderer, &workspace)
        .await
        .expect("config model command should parse");

    assert!(matches!(
        outcome,
        SlashCommandOutcome::ShowSettingsAtPath { ref path } if path == "model"
    ));
}

#[tokio::test]
async fn ide_command_rejects_arguments() {
    let workspace = std::env::current_dir().expect("workspace");
    let mut renderer = renderer_for_tests();

    let outcome = handle_slash_command("ide extra", &mut renderer, &workspace)
        .await
        .expect("ide command should parse");

    assert!(matches!(outcome, SlashCommandOutcome::Handled));
}

#[tokio::test]
async fn agent_command_opens_active_agents_inspector() {
    let workspace = std::env::current_dir().expect("workspace");
    let mut renderer = renderer_for_tests();

    let outcome = handle_slash_command("agent", &mut renderer, &workspace)
        .await
        .expect("agent command should parse");

    assert!(matches!(
        outcome,
        SlashCommandOutcome::ManageAgents {
            action: AgentManagerAction::Threads
        }
    ));
}

#[tokio::test]
async fn agent_command_supports_direct_inspect_and_close() {
    let workspace = std::env::current_dir().expect("workspace");
    let mut renderer = renderer_for_tests();

    let inspect = handle_slash_command("agent inspect thread-1", &mut renderer, &workspace)
        .await
        .expect("agent inspect should parse");
    assert!(matches!(
        inspect,
        SlashCommandOutcome::ManageAgents {
            action: AgentManagerAction::Inspect { ref id }
        } if id == "thread-1"
    ));

    let close = handle_slash_command("agent close thread-1", &mut renderer, &workspace)
        .await
        .expect("agent close should parse");
    assert!(matches!(
        close,
        SlashCommandOutcome::ManageAgents {
            action: AgentManagerAction::Close { ref id }
        } if id == "thread-1"
    ));
}

#[tokio::test]
async fn agents_create_and_edit_commands_parse_guided_forms() {
    let workspace = std::env::current_dir().expect("workspace");
    let mut renderer = renderer_for_tests();

    let create_default = handle_slash_command("agents create", &mut renderer, &workspace)
        .await
        .expect("agents create should parse");
    assert!(matches!(
        create_default,
        SlashCommandOutcome::ManageAgents {
            action: AgentManagerAction::Create {
                scope: None,
                name: None,
            }
        }
    ));

    let create_project = handle_slash_command("agents create project", &mut renderer, &workspace)
        .await
        .expect("agents create project should parse");
    assert!(matches!(
        create_project,
        SlashCommandOutcome::ManageAgents {
            action: AgentManagerAction::Create {
                scope: Some(super::AgentDefinitionScope::Project),
                name: None,
            }
        }
    ));

    let create_named =
        handle_slash_command("agents create project reviewer", &mut renderer, &workspace)
            .await
            .expect("agents create project <name> should parse");
    assert!(matches!(
        create_named,
        SlashCommandOutcome::ManageAgents {
            action: AgentManagerAction::Create {
                scope: Some(super::AgentDefinitionScope::Project),
                name: Some(ref name),
            }
        } if name == "reviewer"
    ));

    let edit_default = handle_slash_command("agents edit", &mut renderer, &workspace)
        .await
        .expect("agents edit should parse");
    assert!(matches!(
        edit_default,
        SlashCommandOutcome::ManageAgents {
            action: AgentManagerAction::Edit { name: None }
        }
    ));

    let edit_named = handle_slash_command("agents edit reviewer", &mut renderer, &workspace)
        .await
        .expect("agents edit <name> should parse");
    assert!(matches!(
        edit_named,
        SlashCommandOutcome::ManageAgents {
            action: AgentManagerAction::Edit { name: Some(ref name) }
        } if name == "reviewer"
    ));
}

#[tokio::test]
async fn subprocesses_command_supports_toggle_and_refresh() {
    let workspace = std::env::current_dir().expect("workspace");
    let mut renderer = renderer_for_tests();

    let toggle = handle_slash_command("subprocesses toggle", &mut renderer, &workspace)
        .await
        .expect("toggle command should parse");
    assert!(matches!(
        toggle,
        SlashCommandOutcome::ManageSubprocesses {
            action: SubprocessManagerAction::ToggleDefault
        }
    ));

    let refresh = handle_slash_command("subprocesses refresh", &mut renderer, &workspace)
        .await
        .expect("refresh command should parse");
    assert!(matches!(
        refresh,
        SlashCommandOutcome::ManageSubprocesses {
            action: SubprocessManagerAction::Refresh
        }
    ));
}

#[tokio::test]
async fn subprocesses_command_supports_direct_actions() {
    let workspace = std::env::current_dir().expect("workspace");
    let mut renderer = renderer_for_tests();

    let inspect = handle_slash_command("subprocesses inspect bg-1", &mut renderer, &workspace)
        .await
        .expect("inspect command should parse");
    assert!(matches!(
        inspect,
        SlashCommandOutcome::ManageSubprocesses {
            action: SubprocessManagerAction::Inspect { ref id }
        } if id == "bg-1"
    ));

    let stop = handle_slash_command("subprocesses stop bg-1", &mut renderer, &workspace)
        .await
        .expect("stop command should parse");
    assert!(matches!(
        stop,
        SlashCommandOutcome::ManageSubprocesses {
            action: SubprocessManagerAction::Stop { ref id }
        } if id == "bg-1"
    ));

    let cancel = handle_slash_command("subprocesses cancel bg-1", &mut renderer, &workspace)
        .await
        .expect("cancel command should parse");
    assert!(matches!(
        cancel,
        SlashCommandOutcome::ManageSubprocesses {
            action: SubprocessManagerAction::Cancel { ref id }
        } if id == "bg-1"
    ));
}

#[tokio::test]
async fn statusline_command_parses_optional_instructions() {
    let workspace = std::env::current_dir().expect("workspace");
    let mut renderer = renderer_for_tests();

    let outcome = handle_slash_command("statusline show cwd and branch", &mut renderer, &workspace)
        .await
        .expect("statusline should parse");

    assert!(matches!(
        outcome,
        SlashCommandOutcome::StartStatuslineSetup {
            instructions: Some(ref text)
        } if text == "show cwd and branch"
    ));
}

#[tokio::test]
async fn title_command_is_interactive_only() {
    let workspace = std::env::current_dir().expect("workspace");
    let mut renderer = renderer_for_tests();

    let outcome = handle_slash_command("title", &mut renderer, &workspace)
        .await
        .expect("title should parse");

    assert!(matches!(
        outcome,
        SlashCommandOutcome::StartTerminalTitleSetup
    ));
}

#[tokio::test]
async fn interactive_mode_commands_parse_to_expected_outcomes() {
    let workspace = std::env::current_dir().expect("workspace");
    let mut renderer = renderer_for_tests();

    let suggest = handle_slash_command("suggest", &mut renderer, &workspace)
        .await
        .expect("suggest should parse");
    assert!(matches!(
        suggest,
        SlashCommandOutcome::TriggerPromptSuggestions
    ));

    let tasks = handle_slash_command("tasks", &mut renderer, &workspace)
        .await
        .expect("tasks should parse");
    assert!(matches!(tasks, SlashCommandOutcome::ToggleTasksPanel));

    let jobs = handle_slash_command("jobs", &mut renderer, &workspace)
        .await
        .expect("jobs should parse");
    assert!(matches!(jobs, SlashCommandOutcome::ShowJobsPanel));

    let mode = handle_slash_command("mode", &mut renderer, &workspace)
        .await
        .expect("mode should parse");
    assert!(matches!(mode, SlashCommandOutcome::StartModeSelection));

    let auto_mode = handle_slash_command("mode auto", &mut renderer, &workspace)
        .await
        .expect("mode auto should parse");
    assert!(matches!(
        auto_mode,
        SlashCommandOutcome::SetMode {
            mode: SessionModeCommand::Auto
        }
    ));

    let cycle = handle_slash_command("mode cycle", &mut renderer, &workspace)
        .await
        .expect("mode cycle should parse");
    assert!(matches!(cycle, SlashCommandOutcome::CycleMode));
}

#[tokio::test]
async fn schedule_commands_parse_to_interactive_and_direct_outcomes() {
    let workspace = std::env::current_dir().expect("workspace");
    let mut renderer = renderer_for_tests();

    let interactive = handle_slash_command("schedule", &mut renderer, &workspace)
        .await
        .expect("schedule should parse");
    assert!(matches!(
        interactive,
        SlashCommandOutcome::ManageSchedule {
            action: ScheduleCommandAction::Interactive
        }
    ));

    let browse = handle_slash_command("schedule list", &mut renderer, &workspace)
        .await
        .expect("schedule list should parse");
    assert!(matches!(
        browse,
        SlashCommandOutcome::ManageSchedule {
            action: ScheduleCommandAction::Browse
        }
    ));

    let create_interactive = handle_slash_command("schedule create", &mut renderer, &workspace)
        .await
        .expect("schedule create should parse");
    assert!(matches!(
        create_interactive,
        SlashCommandOutcome::ManageSchedule {
            action: ScheduleCommandAction::CreateInteractive
        }
    ));

    let delete_interactive = handle_slash_command("schedule delete", &mut renderer, &workspace)
        .await
        .expect("schedule delete should parse");
    assert!(matches!(
        delete_interactive,
        SlashCommandOutcome::ManageSchedule {
            action: ScheduleCommandAction::DeleteInteractive
        }
    ));

    let delete_direct = handle_slash_command("schedule delete deadbeef", &mut renderer, &workspace)
        .await
        .expect("schedule delete id should parse");
    assert!(matches!(
        delete_direct,
        SlashCommandOutcome::ManageSchedule {
            action: ScheduleCommandAction::Delete { ref id }
        } if id == "deadbeef"
    ));

    let create_direct = handle_slash_command(
        "schedule create --prompt \"check the deployment\" --every 10m",
        &mut renderer,
        &workspace,
    )
    .await
    .expect("schedule create with flags should parse");
    assert!(matches!(
        create_direct,
        SlashCommandOutcome::ManageSchedule {
            action: ScheduleCommandAction::Create { ref input }
        } if input.prompt.as_deref() == Some("check the deployment")
            && input.every.as_deref() == Some("10m")
    ));
}

#[tokio::test]
async fn compact_commands_parse_to_automatic_and_direct_outcomes() {
    let workspace = std::env::current_dir().expect("workspace");
    let mut renderer = renderer_for_tests();

    let automatic = handle_slash_command("compact", &mut renderer, &workspace)
        .await
        .expect("compact should parse");
    assert!(matches!(
        automatic,
        SlashCommandOutcome::CompactConversation {
            command: CompactConversationCommand::Run { ref options }
        } if *options == ResponsesCompactionOptions::default()
    ));

    let edit_prompt = handle_slash_command("compact edit-prompt", &mut renderer, &workspace)
        .await
        .expect("compact edit-prompt should parse");
    assert!(matches!(
        edit_prompt,
        SlashCommandOutcome::CompactConversation {
            command: CompactConversationCommand::EditDefaultPrompt
        }
    ));

    let reset_prompt = handle_slash_command("compact reset-prompt", &mut renderer, &workspace)
        .await
        .expect("compact reset-prompt should parse");
    assert!(matches!(
        reset_prompt,
        SlashCommandOutcome::CompactConversation {
            command: CompactConversationCommand::ResetDefaultPrompt
        }
    ));

    let direct = handle_slash_command(
        "compact --instructions \"keep decisions\" --include reasoning.encrypted_content --store",
        &mut renderer,
        &workspace,
    )
    .await
    .expect("compact flags should parse");
    assert!(matches!(
        direct,
        SlashCommandOutcome::CompactConversation {
            command: CompactConversationCommand::Run { ref options }
        } if options.instructions.as_deref() == Some("keep decisions")
            && options.responses_include.as_deref()
                == Some(&["reasoning.encrypted_content".to_string()][..])
            && options.response_store == Some(true)
    ));
}

#[tokio::test]
async fn prompt_template_invocation_replaces_editor_input() {
    let workspace = tempfile::TempDir::new().expect("workspace");
    let template_dir = workspace.path().join(".vtcode/prompts/templates");
    std::fs::create_dir_all(&template_dir).expect("template dir");
    std::fs::write(
        template_dir.join("review-template.md"),
        "---\ndescription: Review template\n---\nReview $1 against $2.\nArgs: $@",
    )
    .expect("template");

    let mut renderer = renderer_for_tests();
    let outcome = handle_slash_command(
        "review-template src/lib.rs main",
        &mut renderer,
        workspace.path(),
    )
    .await
    .expect("review template should parse");

    assert!(matches!(
        outcome,
        SlashCommandOutcome::ReplaceInput { ref content }
            if content == "Review src/lib.rs against main.\nArgs: src/lib.rs main"
    ));
}

#[tokio::test]
async fn prompt_template_invocation_preserves_quoted_arguments() {
    let workspace = tempfile::TempDir::new().expect("workspace");
    let template_dir = workspace.path().join(".vtcode/prompts/templates");
    std::fs::create_dir_all(&template_dir).expect("template dir");
    std::fs::write(
        template_dir.join("rename-template.md"),
        "---\ndescription: Rename template\n---\nRename $1 to $2",
    )
    .expect("template");

    let mut renderer = renderer_for_tests();
    let outcome = handle_slash_command(
        r#"rename-template "src/old name.rs" "src/new name.rs""#,
        &mut renderer,
        workspace.path(),
    )
    .await
    .expect("rename template should parse");

    assert!(matches!(
        outcome,
        SlashCommandOutcome::ReplaceInput { ref content }
            if content == "Rename src/old name.rs to src/new name.rs"
    ));
}

#[tokio::test]
async fn built_in_slash_command_beats_same_named_template() {
    let workspace = tempfile::TempDir::new().expect("workspace");
    let template_dir = workspace.path().join(".vtcode/prompts/templates");
    std::fs::create_dir_all(&template_dir).expect("template dir");
    std::fs::write(
        template_dir.join("help.md"),
        "---\ndescription: shadow help\n---\nThis should not run.",
    )
    .expect("template");

    let mut renderer = renderer_for_tests();
    let outcome = handle_slash_command("help", &mut renderer, workspace.path())
        .await
        .expect("help should parse");

    assert!(matches!(outcome, SlashCommandOutcome::Handled));
}

#[tokio::test]
async fn review_slash_routes_through_cmd_review_skill() {
    let workspace = tempfile::TempDir::new().expect("workspace");
    let mut renderer = renderer_for_tests();

    let outcome = handle_slash_command("review --last-diff", &mut renderer, workspace.path())
        .await
        .expect("review should parse");

    assert!(matches!(
        outcome,
        SlashCommandOutcome::ManageSkills {
            action: crate::agent::runloop::SkillCommandAction::Use { ref name, ref input }
        } if name == "cmd-review" && input == "--last-diff"
    ));
}

#[tokio::test]
async fn command_alias_typo_routes_through_cmd_command_skill() {
    let workspace = tempfile::TempDir::new().expect("workspace");
    let mut renderer = renderer_for_tests();

    let outcome = handle_slash_command("comman cargo check", &mut renderer, workspace.path())
        .await
        .expect("comman alias should parse");

    assert!(matches!(
        outcome,
        SlashCommandOutcome::ManageSkills {
            action: crate::agent::runloop::SkillCommandAction::Use { ref name, ref input }
        } if name == "cmd-command" && input == "cargo check"
    ));
}

#[tokio::test]
async fn analyze_slash_routes_normalized_scope_through_cmd_analyze_skill() {
    let workspace = tempfile::TempDir::new().expect("workspace");
    let mut renderer = renderer_for_tests();

    let outcome = handle_slash_command("analyze SECURITY", &mut renderer, workspace.path())
        .await
        .expect("analyze should parse");

    assert!(matches!(
        outcome,
        SlashCommandOutcome::ManageSkills {
            action: crate::agent::runloop::SkillCommandAction::Use { ref name, ref input }
        } if name == "cmd-analyze" && input == "security"
    ));
}

#[tokio::test]
async fn invalid_analyze_scope_is_handled_locally() {
    let workspace = tempfile::TempDir::new().expect("workspace");
    let mut renderer = renderer_for_tests();

    let outcome = handle_slash_command("analyze nope", &mut renderer, workspace.path())
        .await
        .expect("analyze should parse");

    assert!(matches!(outcome, SlashCommandOutcome::Handled));
}

#[tokio::test]
async fn unknown_slash_command_falls_back_to_normal_prompt_submission() {
    let workspace = tempfile::TempDir::new().expect("workspace");
    let mut renderer = renderer_for_tests();

    let outcome = handle_slash_command(
        "totally-unknown keep this raw",
        &mut renderer,
        workspace.path(),
    )
    .await
    .expect("unknown slash should pass through");

    assert!(matches!(
        outcome,
        SlashCommandOutcome::SubmitPrompt { ref prompt }
            if prompt == "/totally-unknown keep this raw"
    ));
}

#[tokio::test]
async fn permissions_slash_command_opens_permissions_view() {
    let workspace = tempfile::TempDir::new().expect("workspace");
    let mut renderer = renderer_for_tests();

    let outcome = handle_slash_command("permissions", &mut renderer, workspace.path())
        .await
        .expect("permissions should parse");

    assert!(matches!(outcome, SlashCommandOutcome::ShowPermissions));
}

#[tokio::test]
async fn init_slash_command_parses_force_flag() {
    let workspace = tempfile::TempDir::new().expect("workspace");
    let mut renderer = renderer_for_tests();

    let outcome = handle_slash_command("init --force", &mut renderer, workspace.path())
        .await
        .expect("init should parse");

    assert!(matches!(
        outcome,
        SlashCommandOutcome::InitializeWorkspace { force: true }
    ));
}

#[tokio::test]
async fn every_registered_slash_command_resolves_without_prompt_fallback() {
    let workspace = tempfile::TempDir::new().expect("workspace");

    for spec in command_skill_specs() {
        let mut renderer = renderer_for_tests();
        let outcome = handle_slash_command(spec.slash_name, &mut renderer, workspace.path())
            .await
            .unwrap_or_else(|error| panic!("{} should parse: {error}", spec.slash_name));

        assert!(
            !matches!(outcome, SlashCommandOutcome::SubmitPrompt { .. }),
            "/{} fell through to plain prompt submission",
            spec.slash_name
        );
    }
}

// -- /local command tests --

use super::LocalServerAction;

#[tokio::test]
async fn local_no_args_returns_interactive() {
    let mut renderer = renderer_for_tests();
    let workspace = std::path::PathBuf::from("/tmp");
    let outcome = handle_slash_command("local", &mut renderer, &workspace)
        .await
        .unwrap();
    assert!(matches!(
        outcome,
        SlashCommandOutcome::ManageLocalServer {
            action: LocalServerAction::Interactive
        }
    ));
}

#[tokio::test]
async fn local_status_all() {
    let mut renderer = renderer_for_tests();
    let workspace = std::path::PathBuf::from("/tmp");
    let outcome = handle_slash_command("local status", &mut renderer, &workspace)
        .await
        .unwrap();
    assert!(matches!(
        outcome,
        SlashCommandOutcome::ManageLocalServer {
            action: LocalServerAction::Status { provider: None }
        }
    ));
}

#[tokio::test]
async fn local_status_provider() {
    let mut renderer = renderer_for_tests();
    let workspace = std::path::PathBuf::from("/tmp");
    let outcome = handle_slash_command("local status ollama", &mut renderer, &workspace)
        .await
        .unwrap();
    assert!(matches!(
        outcome,
        SlashCommandOutcome::ManageLocalServer {
            action: LocalServerAction::Status {
                provider: Some(ref p)
            }
        } if p == "ollama"
    ));
}

#[tokio::test]
async fn local_start_provider() {
    let mut renderer = renderer_for_tests();
    let workspace = std::path::PathBuf::from("/tmp");
    let outcome = handle_slash_command("local start lm-studio", &mut renderer, &workspace)
        .await
        .unwrap();
    assert!(matches!(
        outcome,
        SlashCommandOutcome::ManageLocalServer {
            action: LocalServerAction::Start {
                provider: Some(ref p)
            }
        } if p == "lmstudio"
    ));
}

#[tokio::test]
async fn local_stop_llamacpp() {
    let mut renderer = renderer_for_tests();
    let workspace = std::path::PathBuf::from("/tmp");
    let outcome = handle_slash_command("local stop llama.cpp", &mut renderer, &workspace)
        .await
        .unwrap();
    assert!(matches!(
        outcome,
        SlashCommandOutcome::ManageLocalServer {
            action: LocalServerAction::Stop {
                provider: Some(ref p)
            }
        } if p == "llamacpp"
    ));
}

#[tokio::test]
async fn local_troubleshoot_no_provider() {
    let mut renderer = renderer_for_tests();
    let workspace = std::path::PathBuf::from("/tmp");
    let outcome = handle_slash_command("local troubleshoot", &mut renderer, &workspace)
        .await
        .unwrap();
    assert!(matches!(
        outcome,
        SlashCommandOutcome::ManageLocalServer {
            action: LocalServerAction::Troubleshoot { provider: None }
        }
    ));
}

#[tokio::test]
async fn local_configure_provider() {
    let mut renderer = renderer_for_tests();
    let workspace = std::path::PathBuf::from("/tmp");
    let outcome = handle_slash_command("local configure ollama", &mut renderer, &workspace)
        .await
        .unwrap();
    assert!(matches!(
        outcome,
        SlashCommandOutcome::ManageLocalServer {
            action: LocalServerAction::Configure {
                provider: Some(ref p)
            }
        } if p == "ollama"
    ));
}

#[tokio::test]
async fn local_provider_name_only() {
    let mut renderer = renderer_for_tests();
    let workspace = std::path::PathBuf::from("/tmp");
    let outcome = handle_slash_command("local ollama", &mut renderer, &workspace)
        .await
        .unwrap();
    assert!(matches!(
        outcome,
        SlashCommandOutcome::ManageLocalServer {
            action: LocalServerAction::Provider {
                name: ref n
            }
        } if n == "ollama"
    ));
}

#[tokio::test]
async fn local_provider_then_action() {
    let mut renderer = renderer_for_tests();
    let workspace = std::path::PathBuf::from("/tmp");
    let outcome = handle_slash_command("local ollama status", &mut renderer, &workspace)
        .await
        .unwrap();
    assert!(matches!(
        outcome,
        SlashCommandOutcome::ManageLocalServer {
            action: LocalServerAction::Status {
                provider: Some(ref p)
            }
        } if p == "ollama"
    ));
}

#[tokio::test]
async fn local_help_renders_usage() {
    let mut renderer = renderer_for_tests();
    let workspace = std::path::PathBuf::from("/tmp");
    let outcome = handle_slash_command("local help", &mut renderer, &workspace)
        .await
        .unwrap();
    assert!(matches!(outcome, SlashCommandOutcome::Handled));
}
