//! Bot command service — domain logic for dispatching slash commands.
//!
//! Extracted from `http/bot_command.rs` so that argument parsing and dispatch
//! are independently testable without an HTTP layer.
//!
//! Conventions: `docs/architecture/service-modules.md`
//!
//! # Structure
//! - `mod.rs` (this file) — public API and typed `Error` type
//! - `parse.rs` — pure argument parsing, no I/O
//! - `io.rs` — all side-effectful calls (transport handlers, stores, agent pool)

pub(super) mod io;
/// Pure argument parsing for bot slash commands.
pub mod parse;

use crate::agents::AgentPool;
use std::path::Path;
use std::sync::Arc;

// ── Error type ────────────────────────────────────────────────────────────────

/// Typed errors returned by `service::bot_command::execute`.
///
/// HTTP handlers map these to specific status codes:
/// - [`Error::UnknownCommand`] → 404 Not Found
/// - [`Error::BadArgs`] → 400 Bad Request
/// - [`Error::CommandFailed`] → 500 Internal Server Error
#[derive(Debug)]
#[allow(dead_code)] // CommandFailed is part of the public API contract; not yet reachable
pub enum Error {
    /// The command keyword does not match any registered command.
    UnknownCommand(String),
    /// The command exists but the provided arguments are invalid.
    BadArgs(String),
    /// The command ran but failed with an internal error.
    CommandFailed(String),
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::UnknownCommand(msg) | Self::BadArgs(msg) | Self::CommandFailed(msg) => {
                write!(f, "{msg}")
            }
        }
    }
}

// ── Public API ────────────────────────────────────────────────────────────────

/// Execute a bot command and return the markdown response.
///
/// Dispatches to the same handlers used by the Matrix and Slack bots. The
/// `cmd` argument is the lower-cased command keyword (e.g. `"status"`,
/// `"start"`). The `args` argument is any text after the keyword, already
/// trimmed.
///
/// # Errors
/// - [`Error::UnknownCommand`] if the command keyword is not registered.
/// - [`Error::BadArgs`] if the arguments fail validation.
/// - [`Error::CommandFailed`] if command execution raises an internal error.
pub async fn execute(
    cmd: &str,
    args: &str,
    project_root: &Path,
    agents: &Arc<AgentPool>,
) -> Result<String, Error> {
    match cmd {
        "assign" => {
            let parsed = parse::parse_assign(args).map_err(Error::BadArgs)?;
            Ok(io::call_assign(&parsed, project_root, agents).await)
        }
        "start" => {
            let parsed = parse::parse_start(args).map_err(Error::BadArgs)?;
            Ok(io::call_start(&parsed, project_root, agents).await)
        }
        "delete" => {
            let number = parse::parse_number("delete", args).map_err(Error::BadArgs)?;
            Ok(io::call_delete(&number, project_root, agents).await)
        }
        "rmtree" => {
            let number = parse::parse_number("rmtree", args).map_err(Error::BadArgs)?;
            Ok(io::call_rmtree(&number, project_root, agents).await)
        }
        "timer" => io::call_timer(args, project_root)
            .await
            .map_err(Error::BadArgs),
        "htop" => Ok(io::call_htop(args, agents)),
        _ => match io::call_sync(cmd, args, project_root, agents) {
            Some(response) => Ok(response),
            None => Err(Error::UnknownCommand(format!(
                "Unknown command: `/{cmd}`. Type `/help` to see available commands."
            ))),
        },
    }
}
