//! Git operations service — worktree path validation and git command execution.
//!
//! Extracted from `http/mcp/git_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 (git processes, filesystem)
//! - `path_guard.rs` — pure path-prefix safety checks
//! - `porcelain.rs` — pure git porcelain output parsers

/// Side-effectful git command execution (add, commit, diff, log, status).
pub mod io;
/// Pure worktree path-prefix safety checks.
pub mod path_guard;
/// Pure git porcelain output parsers.
pub mod porcelain;

pub use io::DirtyFiles;
#[allow(unused_imports)]
pub use path_guard::is_under_root;
pub use porcelain::parse_git_status_porcelain;

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

/// Typed errors returned by `service::git_ops` functions.
///
/// HTTP handlers map these to status codes:
/// - [`Error::NotFound`] → 404 Not Found
/// - [`Error::Validation`] → 400 Bad Request
/// - [`Error::Conflict`] → 409 Conflict
/// - [`Error::PathNotAllowed`] → 400 Bad Request (sandbox violation)
/// - [`Error::Io`] → 500 Internal Server Error
/// - [`Error::UpstreamFailure`] → 500 Internal Server Error
#[allow(dead_code)]
#[derive(Debug)]
pub enum Error {
    /// The requested worktree or path does not exist.
    NotFound(String),
    /// A required argument is missing or has an invalid value.
    Validation(String),
    /// The git operation cannot proceed due to a conflicting state.
    Conflict(String),
    /// The path is outside the allowed sandbox.
    PathNotAllowed(String),
    /// A filesystem or git I/O operation failed.
    Io(String),
    /// An upstream git command 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::NotFound(msg) => write!(f, "Not found: {msg}"),
            Self::Validation(msg) => write!(f, "Validation error: {msg}"),
            Self::Conflict(msg) => write!(f, "Conflict: {msg}"),
            Self::PathNotAllowed(msg) => write!(f, "Path not allowed: {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_not_found() {
        let e = Error::NotFound("worktree missing".to_string());
        assert!(e.to_string().contains("Not found"));
    }

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

    #[test]
    fn error_display_conflict() {
        let e = Error::Conflict("uncommitted changes".to_string());
        assert!(e.to_string().contains("Conflict"));
    }

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

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

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