//! Git use cases (ARCHITECTURE §6, L8). Thin orchestration over the [`GitPort`]:
//! validate the root, call the port, and (for state-changing operations) announce
//! [`DomainEvent::GitStateChanged`] so the UI can refresh.

use std::sync::Arc;

use domain::ports::{EventBus, GitCommitInfo, GitFileStatus, GitPort, GraphCommit};
use domain::{DomainEvent, ProjectId, ProjectPath};

use crate::error::AppError;

/// Parses a raw root string into a validated [`ProjectPath`].
fn parse_root(root: &str) -> Result<ProjectPath, AppError> {
    ProjectPath::new(root).map_err(|e| AppError::Invalid(e.to_string()))
}

// ---------------------------------------------------------------------------
// GitStatus
// ---------------------------------------------------------------------------

/// Input for [`GitStatus::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GitStatusInput {
    /// Absolute repository root.
    pub root: String,
}

/// Output of [`GitStatus::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GitStatusOutput {
    /// Changed paths (staged flag per path).
    pub entries: Vec<GitFileStatus>,
}

/// Reports the working-tree status of a repository.
pub struct GitStatus {
    git: Arc<dyn GitPort>,
}

impl GitStatus {
    /// Builds the use case from the [`GitPort`].
    #[must_use]
    pub fn new(git: Arc<dyn GitPort>) -> Self {
        Self { git }
    }

    /// Returns the status.
    ///
    /// # Errors
    /// - [`AppError::Invalid`] for a non-absolute root,
    /// - [`AppError::Git`] if the repo is missing or the operation fails.
    pub async fn execute(&self, input: GitStatusInput) -> Result<GitStatusOutput, AppError> {
        let root = parse_root(&input.root)?;
        let entries = self.git.status(&root).await?;
        Ok(GitStatusOutput { entries })
    }
}

// ---------------------------------------------------------------------------
// GitStage / GitUnstage
// ---------------------------------------------------------------------------

/// Input for [`GitStage::execute`] / [`GitUnstage::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GitStagePathInput {
    /// Absolute repository root.
    pub root: String,
    /// Repo-relative path to (un)stage.
    pub path: String,
}

/// Stages a path (adds it to the index).
pub struct GitStage {
    git: Arc<dyn GitPort>,
}

impl GitStage {
    /// Builds the use case.
    #[must_use]
    pub fn new(git: Arc<dyn GitPort>) -> Self {
        Self { git }
    }

    /// Stages the path.
    ///
    /// # Errors
    /// [`AppError::Invalid`] for a bad root, [`AppError::Git`] on failure.
    pub async fn execute(&self, input: GitStagePathInput) -> Result<(), AppError> {
        let root = parse_root(&input.root)?;
        self.git.stage(&root, &input.path).await?;
        Ok(())
    }
}

/// Unstages a path (resets it to its HEAD state, or removes it when unborn).
pub struct GitUnstage {
    git: Arc<dyn GitPort>,
}

impl GitUnstage {
    /// Builds the use case.
    #[must_use]
    pub fn new(git: Arc<dyn GitPort>) -> Self {
        Self { git }
    }

    /// Unstages the path.
    ///
    /// # Errors
    /// [`AppError::Invalid`] for a bad root, [`AppError::Git`] on failure.
    pub async fn execute(&self, input: GitStagePathInput) -> Result<(), AppError> {
        let root = parse_root(&input.root)?;
        self.git.unstage(&root, &input.path).await?;
        Ok(())
    }
}

// ---------------------------------------------------------------------------
// GitCommit
// ---------------------------------------------------------------------------

/// Input for [`GitCommit::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GitCommitInput {
    /// The project (for the emitted event).
    pub project_id: ProjectId,
    /// Absolute repository root.
    pub root: String,
    /// Commit message.
    pub message: String,
}

/// Output of [`GitCommit::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GitCommitOutput {
    /// The created commit.
    pub commit: GitCommitInfo,
}

/// Commits the staged index, announcing [`DomainEvent::GitStateChanged`].
pub struct GitCommit {
    git: Arc<dyn GitPort>,
    events: Arc<dyn EventBus>,
}

impl GitCommit {
    /// Builds the use case from its ports.
    #[must_use]
    pub fn new(git: Arc<dyn GitPort>, events: Arc<dyn EventBus>) -> Self {
        Self { git, events }
    }

    /// Creates the commit.
    ///
    /// # Errors
    /// - [`AppError::Invalid`] for a bad root or an empty message,
    /// - [`AppError::Git`] on failure.
    pub async fn execute(&self, input: GitCommitInput) -> Result<GitCommitOutput, AppError> {
        if input.message.trim().is_empty() {
            return Err(AppError::Invalid("commit message is empty".to_owned()));
        }
        let root = parse_root(&input.root)?;
        let commit = self.git.commit(&root, &input.message).await?;
        self.events.publish(DomainEvent::GitStateChanged {
            project_id: input.project_id,
        });
        Ok(GitCommitOutput { commit })
    }
}

// ---------------------------------------------------------------------------
// GitBranches
// ---------------------------------------------------------------------------

/// Input for [`GitBranches::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GitBranchesInput {
    /// Absolute repository root.
    pub root: String,
}

/// Output of [`GitBranches::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GitBranchesOutput {
    /// All local branches.
    pub branches: Vec<String>,
    /// The current branch (`None` when detached or unborn).
    pub current: Option<String>,
}

/// Lists local branches and the current one.
pub struct GitBranches {
    git: Arc<dyn GitPort>,
}

impl GitBranches {
    /// Builds the use case.
    #[must_use]
    pub fn new(git: Arc<dyn GitPort>) -> Self {
        Self { git }
    }

    /// Lists branches + current.
    ///
    /// # Errors
    /// [`AppError::Invalid`] for a bad root, [`AppError::Git`] on failure.
    pub async fn execute(&self, input: GitBranchesInput) -> Result<GitBranchesOutput, AppError> {
        let root = parse_root(&input.root)?;
        let branches = self.git.branches(&root).await?;
        let current = self.git.current_branch(&root).await?;
        Ok(GitBranchesOutput { branches, current })
    }
}

// ---------------------------------------------------------------------------
// GitCheckout
// ---------------------------------------------------------------------------

/// Input for [`GitCheckout::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GitCheckoutInput {
    /// The project (for the emitted event).
    pub project_id: ProjectId,
    /// Absolute repository root.
    pub root: String,
    /// Branch to check out.
    pub branch: String,
}

/// Checks out a branch, announcing [`DomainEvent::GitStateChanged`].
pub struct GitCheckout {
    git: Arc<dyn GitPort>,
    events: Arc<dyn EventBus>,
}

impl GitCheckout {
    /// Builds the use case from its ports.
    #[must_use]
    pub fn new(git: Arc<dyn GitPort>, events: Arc<dyn EventBus>) -> Self {
        Self { git, events }
    }

    /// Checks out the branch.
    ///
    /// # Errors
    /// [`AppError::Invalid`] for a bad root, [`AppError::Git`] on failure.
    pub async fn execute(&self, input: GitCheckoutInput) -> Result<(), AppError> {
        let root = parse_root(&input.root)?;
        self.git.checkout(&root, &input.branch).await?;
        self.events.publish(DomainEvent::GitStateChanged {
            project_id: input.project_id,
        });
        Ok(())
    }
}

// ---------------------------------------------------------------------------
// GitLog
// ---------------------------------------------------------------------------

/// Input for [`GitLog::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GitLogInput {
    /// Absolute repository root.
    pub root: String,
    /// Maximum number of commits to return.
    pub limit: usize,
}

/// Output of [`GitLog::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GitLogOutput {
    /// Recent commits, newest first.
    pub commits: Vec<GitCommitInfo>,
}

/// Returns the recent commit log.
pub struct GitLog {
    git: Arc<dyn GitPort>,
}

impl GitLog {
    /// Builds the use case.
    #[must_use]
    pub fn new(git: Arc<dyn GitPort>) -> Self {
        Self { git }
    }

    /// Returns the log.
    ///
    /// # Errors
    /// [`AppError::Invalid`] for a bad root, [`AppError::Git`] on failure.
    pub async fn execute(&self, input: GitLogInput) -> Result<GitLogOutput, AppError> {
        let root = parse_root(&input.root)?;
        let commits = self.git.log(&root, input.limit).await?;
        Ok(GitLogOutput { commits })
    }
}

// ---------------------------------------------------------------------------
// GitInit
// ---------------------------------------------------------------------------

/// Input for [`GitInit::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GitInitInput {
    /// The project (for the emitted event).
    pub project_id: ProjectId,
    /// Absolute repository root.
    pub root: String,
}

/// Initialises a repository at the project root, announcing
/// [`DomainEvent::GitStateChanged`].
pub struct GitInit {
    git: Arc<dyn GitPort>,
    events: Arc<dyn EventBus>,
}

impl GitInit {
    /// Builds the use case from its ports.
    #[must_use]
    pub fn new(git: Arc<dyn GitPort>, events: Arc<dyn EventBus>) -> Self {
        Self { git, events }
    }

    /// Initialises the repository.
    ///
    /// # Errors
    /// [`AppError::Invalid`] for a bad root, [`AppError::Git`] on failure.
    pub async fn execute(&self, input: GitInitInput) -> Result<(), AppError> {
        let root = parse_root(&input.root)?;
        self.git.init(&root).await?;
        self.events.publish(DomainEvent::GitStateChanged {
            project_id: input.project_id,
        });
        Ok(())
    }
}

// ---------------------------------------------------------------------------
// GitGraph
// ---------------------------------------------------------------------------

/// Input for [`GitGraph::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GitGraphInput {
    /// Absolute repository root.
    pub root: String,
    /// Maximum number of commits to return.
    pub limit: usize,
}

/// Output of [`GitGraph::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GitGraphOutput {
    /// Graph commits (topological + time order), newest first.
    pub commits: Vec<GraphCommit>,
}

/// Returns the full commit graph for all local branches.
pub struct GitGraph {
    git: Arc<dyn GitPort>,
}

impl GitGraph {
    /// Builds the use case.
    #[must_use]
    pub fn new(git: Arc<dyn GitPort>) -> Self {
        Self { git }
    }

    /// Returns the graph.
    ///
    /// # Errors
    /// [`AppError::Invalid`] for a bad root, [`AppError::Git`] on failure.
    pub async fn execute(&self, input: GitGraphInput) -> Result<GitGraphOutput, AppError> {
        let root = parse_root(&input.root)?;
        let commits = self.git.log_graph(&root, input.limit).await?;
        Ok(GitGraphOutput { commits })
    }
}
