//! Command dispatch for command mode (the `:` prompt).
//!
//! Each command returns a [`DispatchOutcome`] containing an optional status
//! message and an optional side-effect for the UI layer to perform.
//! A `None` message means the command succeeded silently; `Some(msg)` is shown
//! in the command bar as an error or confirmation.
//!
//! Commands are simple strings; arguments follow a space: `:theme dark`.

/// A side-effect that the UI layer must perform after a successful command.
pub enum CommandEffect {
    /// Apply and persist the given theme (`"dark"` or `"light"`).
    SetTheme(String),
    /// Open the repository at the given filesystem path.
    OpenRepository(String),
}

/// Result of dispatching a command.
pub struct DispatchOutcome {
    /// Message to show in the command bar; `None` = silent success.
    pub message: Option<String>,
    /// Side-effect for the UI layer to carry out.
    pub effect: Option<CommandEffect>,
}

impl DispatchOutcome {
    fn ok() -> Self {
        Self { message: None, effect: None }
    }
    fn err(msg: impl Into<String>) -> Self {
        Self { message: Some(msg.into()), effect: None }
    }
    fn with_effect(effect: CommandEffect) -> Self {
        Self { message: None, effect: Some(effect) }
    }
}

/// Execute a command entered in command mode.
pub fn dispatch(input: &str) -> DispatchOutcome {
    let input = input.trim();
    if input.is_empty() {
        return DispatchOutcome::ok();
    }

    let (cmd, args) = input
        .split_once(' ')
        .map(|(c, a)| (c, a.trim()))
        .unwrap_or((input, ""));

    match cmd {
        // ── Lifecycle ────────────────────────────────────────────────────────
        "q" | "quit" => {
            // TODO Phase 8: call tauri::window::close()
            DispatchOutcome::ok()
        }

        // ── Theme ────────────────────────────────────────────────────────────
        "theme" => match args {
            "dark" | "light" => {
                DispatchOutcome::with_effect(CommandEffect::SetTheme(args.to_string()))
            }
            "" => DispatchOutcome::err("usage: theme <dark|light>"),
            a => DispatchOutcome::err(format!("unknown theme '{a}'")),
        },

        // ── Repository ───────────────────────────────────────────────────────
        "open" => {
            if args.is_empty() {
                DispatchOutcome::err("usage: open <path>")
            } else {
                DispatchOutcome::with_effect(CommandEffect::OpenRepository(args.to_string()))
            }
        }

        _ => DispatchOutcome::err(format!("unknown command '{cmd}'")),
    }
}

// ── Tests ─────────────────────────────────────────────────────────────────────

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

    #[test]
    fn empty_input_is_silent() {
        assert!(dispatch("").message.is_none());
        assert!(dispatch("   ").message.is_none());
    }

    #[test]
    fn quit_is_silent() {
        assert!(dispatch("q").message.is_none());
        assert!(dispatch("quit").message.is_none());
    }

    #[test]
    fn valid_theme_produces_effect() {
        let dark = dispatch("theme dark");
        assert!(dark.message.is_none());
        assert!(matches!(&dark.effect, Some(CommandEffect::SetTheme(t)) if t == "dark"));

        let light = dispatch("theme light");
        assert!(light.message.is_none());
        assert!(matches!(&light.effect, Some(CommandEffect::SetTheme(t)) if t == "light"));
    }

    #[test]
    fn invalid_theme_returns_error() {
        let msg = dispatch("theme solarized").message;
        assert!(msg.is_some());
        assert!(msg.unwrap().contains("solarized"));
    }

    #[test]
    fn theme_without_args_returns_usage() {
        let msg = dispatch("theme").message;
        assert!(msg.is_some());
        assert!(msg.unwrap().contains("usage"));
    }

    #[test]
    fn unknown_command_returns_error() {
        let msg = dispatch("frobnicate").message;
        assert!(msg.is_some());
        assert!(msg.unwrap().contains("frobnicate"));
    }
}
