//! `/cd [path]` — change the working directory new dashboard sessions
//! spawn in.
//!
//! With no argument it opens the dashboard's location picker; with a path
//! it changes directly. Both are dashboard affordances — invoked from a
//! non-dashboard surface the dispatcher prints a toast pointing the user
//! at `/dashboard` (see `dispatch_dashboard_open_location_picker` /
//! `dispatch_dashboard_change_location`).

use crate::app::actions::Action;
use crate::slash::command::{CommandExecCtx, CommandResult, SlashCommand};

/// Change the working directory for newly dispatched dashboard sessions.
pub struct CdCommand;

impl SlashCommand for CdCommand {
    fn name(&self) -> &str {
        "cd"
    }

    fn description(&self) -> &str {
        "Change the working directory for new agents"
    }

    fn usage(&self) -> &str {
        "/cd [path]"
    }

    fn takes_args(&self) -> bool {
        true
    }

    fn arg_placeholder(&self) -> Option<&str> {
        Some("path")
    }

    /// `/cd` only makes sense on the dashboard (it changes where the
    /// dashboard dispatches new agents), so hide it from completion on
    /// every other surface — the agent view and the welcome screen.
    fn dashboard_only(&self) -> bool {
        true
    }

    fn run(&self, _ctx: &mut CommandExecCtx, args: &str) -> CommandResult {
        let trimmed = args.trim();
        if trimmed.is_empty() {
            CommandResult::Action(Action::DashboardOpenLocationPicker)
        } else {
            CommandResult::Action(Action::DashboardChangeLocation {
                input: trimmed.to_string(),
            })
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::acp::model_state::ModelState;
    use crate::app::bundle::BundleState;

    /// Build a throwaway exec ctx over the given borrows. Mirrors the
    /// inline ctx construction in `dashboard.rs`'s command tests.
    fn ctx<'a>(models: &'a ModelState, bundle: &'a BundleState) -> CommandExecCtx<'a> {
        CommandExecCtx {
            models,
            session_id: None,
            bundle_state: bundle,
            screen_mode: crate::app::ScreenMode::Inline,
            pager_state: crate::settings::PagerLocalSnapshot {
                multiline_mode: false,
                yolo_mode: false,
                ..crate::settings::PagerLocalSnapshot::default()
            },
        }
    }

    #[test]
    fn no_args_opens_location_picker() {
        let (models, bundle) = (ModelState::default(), BundleState::default());
        let mut c = ctx(&models, &bundle);
        assert!(matches!(
            CdCommand.run(&mut c, ""),
            CommandResult::Action(Action::DashboardOpenLocationPicker)
        ));
    }

    #[test]
    fn whitespace_only_opens_location_picker() {
        let (models, bundle) = (ModelState::default(), BundleState::default());
        let mut c = ctx(&models, &bundle);
        assert!(matches!(
            CdCommand.run(&mut c, "   "),
            CommandResult::Action(Action::DashboardOpenLocationPicker)
        ));
    }

    #[test]
    fn path_arg_changes_location() {
        let (models, bundle) = (ModelState::default(), BundleState::default());
        let mut c = ctx(&models, &bundle);
        match CdCommand.run(&mut c, "  ~/projects/foo  ") {
            CommandResult::Action(Action::DashboardChangeLocation { input }) => {
                assert_eq!(input, "~/projects/foo");
            }
            other => panic!("expected DashboardChangeLocation, got {other:?}"),
        }
    }

    #[test]
    fn metadata() {
        let cmd = CdCommand;
        assert_eq!(cmd.name(), "cd");
        assert!(cmd.takes_args());
        assert_eq!(cmd.arg_placeholder(), Some("path"));
        assert!(!cmd.description().is_empty());
        assert!(!cmd.usage().is_empty());
        // `/cd` is dashboard-only — hidden from completion on every other
        // surface (the agent view, the welcome screen).
        assert!(cmd.dashboard_only(), "/cd must be dashboard-only");
    }
}
