use crate::app::{Action, AppState};
use std::path::PathBuf;
use super::types::{WorkflowState, WorkflowStep, WorkflowProgress};
#[derive(Debug, Clone)]
pub struct WorkflowContext {
pub state: WorkflowState,
pub message: String,
pub next_steps: Vec<WorkflowStep>,
pub progress: Option<WorkflowProgress>,
}
impl WorkflowContext {
#[inline]
fn step(key: &str, label: &str, action: Option<Action>, description: &str) -> WorkflowStep {
WorkflowStep {
key: key.to_string(),
label: label.to_string(),
action,
description: description.to_string(),
}
}
fn conflict_resolution_steps() -> Vec<WorkflowStep> {
vec![
Self::step(
"g",
"Open guided resolution",
Some(Action::ShowConflictsGuided),
"Get step-by-step help resolving conflicts",
),
]
}
fn commit_step(label: &str, description: &str) -> WorkflowStep {
Self::step("c", label, Some(Action::StartCommit), description)
}
pub fn detect(state: &AppState) -> Self {
let repo_path = PathBuf::from(&state.repo_path);
if repo_path.join(".git/rebase-merge").exists()
|| repo_path.join(".git/rebase-apply").exists() {
return Self::rebase_context(state);
}
if repo_path.join(".git/MERGE_HEAD").exists() {
return Self::merge_context(state);
}
if repo_path.join(".git/CHERRY_PICK_HEAD").exists() {
return Self::cherry_pick_context(state);
}
if repo_path.join(".git/REVERT_HEAD").exists() {
return Self::revert_context(state);
}
let has_conflicts = state.status_entries.iter().any(|e| e.conflict);
if has_conflicts {
return Self::conflicts_context(state);
}
if state.commit_mode {
return Self::committing_context(state);
}
let has_staged = state.status_entries.iter().any(|e| e.staged);
let has_unstaged = state.status_entries.iter().any(|e| e.unstaged);
if has_staged || has_unstaged {
return Self::staging_context(state);
}
Self::clean_context(state)
}
fn rebase_context(_state: &AppState) -> Self {
Self {
state: WorkflowState::RebaseInProgress,
message: "Rebase feature removed".to_string(),
next_steps: vec![],
progress: None,
}
}
fn merge_context(state: &AppState) -> Self {
let has_conflicts = state.status_entries.iter().any(|e| e.conflict);
if has_conflicts {
let mut steps = Self::conflict_resolution_steps();
steps.push(Self::step(
"o",
"Open conflicts in editor",
None,
"Edit conflict files manually",
));
Self {
state: WorkflowState::MergeInProgress,
message: "Merge in progress - conflicts detected".to_string(),
next_steps: steps,
progress: None,
}
} else {
Self {
state: WorkflowState::MergeInProgress,
message: "Merge in progress - ready to commit".to_string(),
next_steps: vec![Self::commit_step(
"Commit merge",
"Complete the merge by committing",
)],
progress: None,
}
}
}
fn cherry_pick_context(state: &AppState) -> Self {
let has_conflicts = state.status_entries.iter().any(|e| e.conflict);
if has_conflicts {
let mut steps = Self::conflict_resolution_steps();
steps.push(Self::commit_step(
"Commit after resolving",
"Complete the cherry-pick after resolving conflicts",
));
Self {
state: WorkflowState::CherryPickInProgress,
message: "Cherry-pick in progress - conflicts detected".to_string(),
next_steps: steps,
progress: None,
}
} else {
Self {
state: WorkflowState::CherryPickInProgress,
message: "Cherry-pick in progress - ready to commit".to_string(),
next_steps: vec![Self::commit_step(
"Commit cherry-pick",
"Complete the cherry-pick",
)],
progress: None,
}
}
}
fn revert_context(_state: &AppState) -> Self {
Self {
state: WorkflowState::RevertInProgress,
message: "Revert in progress".to_string(),
next_steps: vec![Self::commit_step(
"Commit revert",
"Complete the revert",
)],
progress: None,
}
}
fn conflicts_context(state: &AppState) -> Self {
let conflict_count = state.status_entries.iter().filter(|e| e.conflict).count();
let mut steps = Self::conflict_resolution_steps();
steps.push(Self::step(
"n",
"Next conflict",
Some(Action::StatusNextConflict),
"Navigate to next conflict",
));
steps.push(Self::step(
"p",
"Previous conflict",
Some(Action::StatusPrevConflict),
"Navigate to previous conflict",
));
Self {
state: WorkflowState::Conflicts,
message: format!("{} conflict(s) detected", conflict_count),
next_steps: steps,
progress: None,
}
}
fn committing_context(_state: &AppState) -> Self {
Self {
state: WorkflowState::Committing,
message: "Entering commit message".to_string(),
next_steps: vec![
Self::step(
"Enter",
"Commit",
Some(Action::CommitSubmit),
"Commit with the entered message",
),
Self::step(
"Esc",
"Cancel",
Some(Action::CancelCommit),
"Cancel commit and return",
),
],
progress: None,
}
}
fn staging_context(state: &AppState) -> Self {
let staged_count = state.status_entries.iter().filter(|e| e.staged).count();
let unstaged_count = state.status_entries.iter().filter(|e| e.unstaged).count();
let message = if staged_count > 0 && unstaged_count > 0 {
format!("{} staged, {} unstaged changes", staged_count, unstaged_count)
} else if staged_count > 0 {
format!("{} staged change(s) ready to commit", staged_count)
} else {
format!("{} unstaged change(s)", unstaged_count)
};
let mut steps = vec![];
if unstaged_count > 0 {
steps.push(Self::step(
"s",
"Stage selected",
Some(Action::StageSelectedFile),
"Stage the selected file",
));
steps.push(Self::step(
"S",
"Stage all",
Some(Action::StageAllFiles),
"Stage all unstaged files",
));
}
if staged_count > 0 {
steps.push(Self::commit_step(
"Commit",
"Commit staged changes",
));
}
Self {
state: WorkflowState::Staging,
message,
next_steps: steps,
progress: None,
}
}
fn clean_context(_state: &AppState) -> Self {
Self {
state: WorkflowState::Clean,
message: "Working directory clean".to_string(),
next_steps: vec![
Self::step(
"b",
"View branches",
Some(Action::FocusNext),
"Switch branches or create new ones",
),
Self::step(
"l",
"View log",
Some(Action::FocusNext),
"Browse commit history",
),
Self::step(
"P",
"Push",
Some(Action::Push),
"Push commits to remote",
),
],
progress: None,
}
}
pub fn format_display(&self) -> String {
let mut lines = vec![self.message.clone()];
if let Some(ref progress) = self.progress {
lines.push(format!("Progress: {}/{} - {}", progress.current, progress.total, progress.step_name));
}
if !self.next_steps.is_empty() {
lines.push(String::new());
lines.push("Next steps:".to_string());
for step in &self.next_steps {
lines.push(format!(" [{}] {} - {}", step.key, step.label, step.description));
}
}
lines.join("\n")
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::git::parsers::status::StatusEntry;
#[test]
fn test_detect_clean_state() {
let state = AppState::new();
let ctx = WorkflowContext::detect(&state);
assert_eq!(ctx.state, WorkflowState::Clean);
assert!(ctx.message.contains("clean"));
}
#[test]
fn test_detect_staging_state() {
let mut state = AppState::new();
state.status_entries.push(StatusEntry {
path: "file.txt".to_string(),
staged: false,
unstaged: true,
conflict: false,
});
let ctx = WorkflowContext::detect(&state);
assert_eq!(ctx.state, WorkflowState::Staging);
assert!(ctx.message.contains("unstaged"));
}
#[test]
fn test_detect_committing_state() {
let mut state = AppState::new();
state.commit_mode = true;
let ctx = WorkflowContext::detect(&state);
assert_eq!(ctx.state, WorkflowState::Committing);
assert_eq!(ctx.next_steps[0].key, "Enter");
}
}