eazygit 0.5.1

A fast TUI for Git with staging, conflicts, rebase, and palette-first UX
Documentation
//! Effect executor - handles side effect execution and result dispatch.
//!
//! The executor receives Effects from the update function and:
//! 1. Executes the side effect (Git operation, file I/O, etc.)
//! 2. Dispatches a result Msg back to trigger another update cycle
//!
//! ## Phase 2 Status
//! 
//! This is a working skeleton. Many effect handlers return None because
//! the underlying operations are still handled by the Command pattern.
//! Full integration happens in Phase 3.

use crate::core::{Effect, Msg};
use crate::services::git::GitService;
use std::sync::Arc;

/// Executes effects and produces result messages.
pub struct EffectExecutor {
    git_service: Arc<GitService>,
    repo_path: String,
}

impl EffectExecutor {
    /// Create a new effect executor.
    pub fn new(git_service: Arc<GitService>, repo_path: String) -> Self {
        Self { git_service, repo_path }
    }
    
    /// Execute an effect and return the result message.
    /// 
    /// This is the core of the TEA side-effect handling.
    /// Effects are executed and produce Msgs that are dispatched back to update().
    pub async fn execute(&self, effect: Effect) -> Option<Msg> {
        tracing::debug!("Executing effect: {:?}", effect);
        
        match effect {
            Effect::None => None,
            
            Effect::Batch(effects) => {
                // Execute batch effects sequentially
                for e in effects {
                    if let Some(msg) = Box::pin(self.execute(e)).await {
                        return Some(msg);
                    }
                }
                None
            }
            
            // ===== Staging Operations (fully implemented) =====
            
            Effect::GitStage { path } => {
                let path_str = path.to_string_lossy().to_string();
                match self.git_service.stage_file(&self.repo_path, &path_str) {
                    Ok(()) => Some(Msg::StageSuccess { path: path_str }),
                    Err(e) => Some(Msg::StageFailed { 
                        path: path_str, 
                        error: e.to_string() 
                    }),
                }
            }
            
            Effect::GitUnstage { path } => {
                let path_str = path.to_string_lossy().to_string();
                match self.git_service.unstage_file(&self.repo_path, &path_str) {
                    Ok(()) => Some(Msg::StageSuccess { path: path_str }),
                    Err(e) => Some(Msg::StageFailed { 
                        path: path_str, 
                        error: e.to_string() 
                    }),
                }
            }
            
            // Stage all - delegated to existing command pattern for now
            Effect::GitStageAll => {
                // TODO: Integrate in Phase 3
                None
            }
            
            Effect::GitUnstageAll => {
                // TODO: Integrate in Phase 3
                None
            }
            
            // ===== Commit Operations (fully implemented) =====
            
            Effect::GitCommit { message, amend, author_name, author_email } => {
                match self.git_service.commit(
                    &self.repo_path, 
                    &message, 
                    amend,
                    author_name.as_deref(),
                    author_email.as_deref(),
                ) {
                    Ok(()) => {
                        let hash = self.git_service.last_commit_info(&self.repo_path)
                            .map(|info| info.hash)
                            .unwrap_or_else(|_| "unknown".to_string());
                        Some(Msg::CommitSuccess { hash })
                    }
                    Err(e) => Some(Msg::CommitFailed { error: e.to_string() }),
                }
            }
            
            // ===== Push/Pull (fully implemented) =====
            
            Effect::GitPush { force_with_lease } => {
                match self.git_service.push(&self.repo_path, force_with_lease) {
                    Ok(()) => Some(Msg::SetFeedback { 
                        message: Some("Pushed successfully".to_string()) 
                    }),
                    Err(e) => Some(Msg::SetError { 
                        error: Some(e.to_string()), 
                        guidance: None 
                    }),
                }
            }
            
            Effect::GitPull { rebase: _ } => {
                // Handled by existing async job system
                None
            }
            
            Effect::GitFetch => {
                // Handled by existing async job system
                None
            }
            
            // ===== Branch Operations =====
            
            Effect::GitCheckout { branch } => {
                match self.git_service.checkout_branch(&self.repo_path, &branch) {
                    Ok(()) => Some(Msg::SetFeedback { 
                        message: Some(format!("Checked out {}", branch)) 
                    }),
                    Err(e) => Some(Msg::SetError { 
                        error: Some(e.to_string()), 
                        guidance: None 
                    }),
                }
            }
            
            Effect::GitCreateBranch { name, start_point: _ } => {
                match self.git_service.create_branch(&self.repo_path, &name) {
                    Ok(()) => Some(Msg::SetFeedback { 
                        message: Some(format!("Created branch {}", name)) 
                    }),
                    Err(e) => Some(Msg::SetError { 
                        error: Some(e.to_string()), 
                        guidance: None 
                    }),
                }
            }
            
            Effect::GitDeleteBranch { name: _, force: _ } => {
                // TODO: Integrate in Phase 3
                None
            }
            
            // ===== Rebase Operations (delegated to existing service) =====
            
            Effect::GitRebaseStart { base: _, use_root: _, todo_content: _ } => {
                // Handled by rebase service
                None
            }
            
            Effect::GitRebaseContinue { message: _, author_name: _, author_email: _ } => {
                // Handled by rebase service
                None
            }
            
            Effect::GitRebaseAbort => {
                // Handled by rebase service
                None
            }
            
            Effect::GitRebaseSkip => {
                // Handled by rebase service
                None
            }
            
            // ===== Other Git Operations (Phase 3) =====
            
            Effect::GitCherryPick { hash: _ } => None,
            Effect::GitRevert { hash: _ } => None,
            Effect::GitReset { target: _, mode: _ } => None,
            
            // ===== Stash Operations =====
            
            Effect::GitStash { message } => {
                let msg = message.unwrap_or_default();
                match self.git_service.stash_push(&self.repo_path, &msg) {
                    Ok(()) => Some(Msg::SetFeedback { 
                        message: Some("Stashed changes".to_string()) 
                    }),
                    Err(e) => Some(Msg::SetError { 
                        error: Some(e.to_string()), 
                        guidance: None 
                    }),
                }
            }
            
            Effect::GitStashPop { index } => {
                let stash_ref = format!("stash@{{{}}}", index);
                match self.git_service.stash_apply(&self.repo_path, &stash_ref, true) {
                    Ok(()) => Some(Msg::SetFeedback { 
                        message: Some("Applied stash".to_string()) 
                    }),
                    Err(e) => Some(Msg::SetError { 
                        error: Some(e.to_string()), 
                        guidance: None 
                    }),
                }
            }
            
            Effect::GitStashDrop { stash_ref } => {
                match self.git_service.stash_drop(&self.repo_path, &stash_ref) {
                    Ok(()) => Some(Msg::SetFeedback { 
                        message: Some("Dropped stash".to_string()) 
                    }),
                    Err(e) => Some(Msg::SetError { 
                        error: Some(e.to_string()), 
                        guidance: None 
                    }),
                }
            }
            
            // ===== Refresh Operations =====
            
            Effect::RefreshStatus | Effect::RefreshLog | Effect::RefreshBranches | 
            Effect::RefreshStashes | Effect::RefreshAll => {
                // Refresh operations are handled by the existing refresh mechanism
                None
            }
            
            // ===== Diff Computation =====
            
            Effect::ComputeDiff { path, staged } => {
                let path_str = path.to_string_lossy().to_string();
                match self.git_service.diff(&self.repo_path, &path_str, staged, 3) {
                    Ok(content) => Some(Msg::DiffComputed { 
                        path: path_str, 
                        content, 
                        truncated: false 
                    }),
                    Err(_) => None,
                }
            }
            
            // ===== System Operations =====
            
            Effect::OpenInEditor { path: _ } => None,
            
            Effect::SaveConfig { key, value } => {
                if key == "theme" {
                    let _ = crate::config::persist_theme(&value);
                }
                None
            }
            
            Effect::ExecuteCustomCommand { name: _, command: _ } => None,
        }
    }
}