//! Shell service — command safety, path sandboxing, and output helpers.
//!
//! Extracted from `http/mcp/shell_tools.rs` following the conventions in
//! `docs/architecture/service-modules.md`:
//! - `mod.rs` (this file) — public API, typed [`Error`], orchestration
//! - `io.rs` — the ONLY place that performs side effects (filesystem checks)
//! - `path_guard.rs` — pure command-safety checks and output utilities

/// Side-effectful shell I/O — filesystem permission checks.
pub mod io;
/// Cargo / rustc diagnostic parser — extracts structured errors and warnings
/// from raw cargo output. Used by run_check / run_build / run_lint MCP tools
/// (bug 886).
pub mod parse_diagnostics;
/// Pure command-safety checks, blocked-binary lists, and output truncation.
pub mod path_guard;

#[allow(unused_imports)]
pub use path_guard::{
    BLOCKED_BINARIES, BLOCKED_PATTERNS, extract_count, is_dangerous, parse_test_counts,
    truncate_output,
};

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

/// Typed errors returned by `service::shell` functions.
///
/// HTTP handlers map these to status codes:
/// - [`Error::DangerousCommand`] → 400 Bad Request
/// - [`Error::PathNotAllowed`] → 400 Bad Request (sandbox violation)
/// - [`Error::Validation`] → 400 Bad Request
/// - [`Error::Io`] → 500 Internal Server Error
/// - [`Error::UpstreamFailure`] → 500 Internal Server Error
#[allow(dead_code)]
#[derive(Debug)]
pub enum Error {
    /// The command matches a blocked pattern or binary.
    DangerousCommand(String),
    /// The working directory is outside the allowed sandbox.
    PathNotAllowed(String),
    /// A required argument is missing or has an invalid value.
    Validation(String),
    /// A filesystem or process I/O operation failed.
    Io(String),
    /// An upstream dependency (e.g. the shell) returned an unexpected error.
    UpstreamFailure(String),
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::DangerousCommand(msg) => write!(f, "Dangerous command: {msg}"),
            Self::PathNotAllowed(msg) => write!(f, "Path not allowed: {msg}"),
            Self::Validation(msg) => write!(f, "Validation error: {msg}"),
            Self::Io(msg) => write!(f, "I/O error: {msg}"),
            Self::UpstreamFailure(msg) => write!(f, "Upstream failure: {msg}"),
        }
    }
}

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

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

    #[test]
    fn error_display_dangerous_command() {
        let e = Error::DangerousCommand("rm -rf / detected".to_string());
        assert!(e.to_string().contains("Dangerous command"));
    }

    #[test]
    fn error_display_path_not_allowed() {
        let e = Error::PathNotAllowed("outside worktrees".to_string());
        assert!(e.to_string().contains("Path not allowed"));
    }

    #[test]
    fn error_display_validation() {
        let e = Error::Validation("must be absolute".to_string());
        assert!(e.to_string().contains("Validation error"));
    }

    #[test]
    fn error_display_io() {
        let e = Error::Io("disk full".to_string());
        assert!(e.to_string().contains("I/O error"));
    }

    #[test]
    fn error_display_upstream_failure() {
        let e = Error::UpstreamFailure("bash not found".to_string());
        assert!(e.to_string().contains("Upstream failure"));
    }
}
