use std::io::{self, Write};
use crossterm::{
event::{self, Event, KeyCode},
execute,
style::{Color, Print, ResetColor, SetForegroundColor},
terminal::{self, Clear, ClearType},
cursor,
};
use crate::git_integration::{
GitIntegrationSystem, GitOperationResult, ConflictStrategy
};
use crate::edit_control::ModifiableEdit;
use crate::classification::ClassifiedEdit;
pub struct GitIntegrationCLI {
pub git_system: GitIntegrationSystem,
pub view_mode: GitViewMode,
pub active_panel: GitPanel,
pub status_message: String,
pub show_help: bool,
pub pending_edits: Vec<(ModifiableEdit, ClassifiedEdit)>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum GitViewMode {
SessionOverview,
BranchManagement,
CommitHistory,
ConflictResolution,
BackupManagement,
}
#[derive(Debug, Clone, PartialEq)]
pub enum GitPanel {
SessionStatus,
Operations,
FileChanges,
Configuration,
}
impl GitIntegrationCLI {
pub fn new(repository_path: std::path::PathBuf) -> Result<Self, String> {
let git_system = GitIntegrationSystem::new(repository_path)?;
Ok(GitIntegrationCLI {
git_system,
view_mode: GitViewMode::SessionOverview,
active_panel: GitPanel::SessionStatus,
status_message: "Git Integration ready".to_string(),
show_help: false,
pending_edits: Vec::new(),
})
}
pub fn start_interactive_session(&mut self) -> Result<(), String> {
terminal::enable_raw_mode().map_err(|e| format!("Failed to enable raw mode: {}", e))?;
let result = self.run_interactive_loop();
terminal::disable_raw_mode().map_err(|e| format!("Failed to disable raw mode: {}", e))?;
execute!(io::stdout(), Clear(ClearType::All), cursor::MoveTo(0, 0)).ok();
result
}
fn run_interactive_loop(&mut self) -> Result<(), String> {
loop {
self.render_interface()?;
if let Ok(Event::Key(key_event)) = event::read() {
match key_event.code {
KeyCode::Char('q') | KeyCode::Esc => {
break;
}
KeyCode::Char('h') => {
self.show_help = !self.show_help;
}
KeyCode::Char('s') => {
self.handle_start_session()?;
}
KeyCode::Char('e') => {
self.handle_end_session()?;
}
KeyCode::Char('c') => {
self.handle_commit_changes()?;
}
KeyCode::Char('b') => {
self.handle_create_backup()?;
}
KeyCode::Char('m') => {
self.handle_merge_session()?;
}
KeyCode::Tab => {
self.cycle_panels();
}
KeyCode::F(1) => {
self.view_mode = GitViewMode::SessionOverview;
}
KeyCode::F(2) => {
self.view_mode = GitViewMode::BranchManagement;
}
KeyCode::F(3) => {
self.view_mode = GitViewMode::CommitHistory;
}
KeyCode::F(4) => {
self.view_mode = GitViewMode::ConflictResolution;
}
KeyCode::F(5) => {
self.view_mode = GitViewMode::BackupManagement;
}
KeyCode::Char('1') => {
self.active_panel = GitPanel::SessionStatus;
}
KeyCode::Char('2') => {
self.active_panel = GitPanel::Operations;
}
KeyCode::Char('3') => {
self.active_panel = GitPanel::FileChanges;
}
KeyCode::Char('4') => {
self.active_panel = GitPanel::Configuration;
}
_ => {}
}
}
}
Ok(())
}
fn render_interface(&self) -> Result<(), String> {
execute!(io::stdout(), Clear(ClearType::All), cursor::MoveTo(0, 0))
.map_err(|e| format!("Failed to clear screen: {}", e))?;
self.render_header()?;
match self.view_mode {
GitViewMode::SessionOverview => self.render_session_overview()?,
GitViewMode::BranchManagement => self.render_branch_management()?,
GitViewMode::CommitHistory => self.render_commit_history()?,
GitViewMode::ConflictResolution => self.render_conflict_resolution()?,
GitViewMode::BackupManagement => self.render_backup_management()?,
}
self.render_footer()?;
if self.show_help {
self.render_help_panel()?;
}
io::stdout().flush().map_err(|e| format!("Failed to flush stdout: {}", e))?;
Ok(())
}
fn render_header(&self) -> Result<(), String> {
execute!(io::stdout(),
SetForegroundColor(Color::Cyan),
Print("╔══════════════════════════════════════════════════════════════════════════════╗\n"),
Print("║ 🔧 SOMA-CORE Git Integration Interface ║\n"),
Print("╚══════════════════════════════════════════════════════════════════════════════╝\n"),
ResetColor
).map_err(|e| format!("Failed to render header: {}", e))?;
execute!(io::stdout(),
SetForegroundColor(Color::Yellow),
Print(&format!("Session: {} | Branch: {} | View: {:?}\n",
self.git_system.session_state.session_id,
self.git_system.session_state.current_branch,
self.view_mode
)),
ResetColor,
Print("\n")
).map_err(|e| format!("Failed to render session status: {}", e))?;
Ok(())
}
fn render_session_overview(&self) -> Result<(), String> {
let session = &self.git_system.session_state;
execute!(io::stdout(),
SetForegroundColor(Color::Green),
Print("📊 Session Overview\n"),
Print("═══════════════════\n"),
ResetColor,
Print(&format!("Session ID: {}\n", session.session_id)),
Print(&format!("Start Time: {:?}\n", session.start_time)),
Print(&format!("Current Branch: {}\n", session.current_branch)),
Print(&format!("Original Branch: {}\n", session.original_branch)),
Print(&format!("Edits Applied: {}\n", session.edits_applied.len())),
Print(&format!("Commits: {}\n", session.commit_history.len())),
Print(&format!("Backup Points: {}\n", session.backup_points.len())),
Print("\n"),
SetForegroundColor(Color::Cyan),
Print("🔧 Git Configuration\n"),
Print("═══════════════════\n"),
ResetColor,
Print(&format!("Auto Commit: {}\n", self.git_system.config.auto_commit)),
Print(&format!("Session Branches: {}\n", self.git_system.config.session_branches)),
Print(&format!("Backup Frequency: {} minutes\n", self.git_system.config.backup_frequency_minutes)),
Print(&format!("CI/CD Integration: {}\n", self.git_system.config.cicd_integration)),
Print(&format!("Conflict Strategy: {:?}\n", self.git_system.config.conflict_strategy)),
Print("\n")
).map_err(|e| format!("Failed to render session overview: {}", e))?;
if !self.pending_edits.is_empty() {
execute!(io::stdout(),
SetForegroundColor(Color::Yellow),
Print("📝 Pending Edits\n"),
Print("════════════════\n"),
ResetColor
).map_err(|e| format!("Failed to render pending edits header: {}", e))?;
for (i, (edit, classification)) in self.pending_edits.iter().enumerate() {
let classification_color = match &classification.category {
crate::classification::EditCategory::Critical { .. } => Color::Red,
crate::classification::EditCategory::Experimental { .. } => Color::Yellow,
crate::classification::EditCategory::Safe { .. } => Color::Green,
crate::classification::EditCategory::Cosmetic { .. } => Color::Blue,
};
execute!(io::stdout(),
SetForegroundColor(classification_color),
Print(&format!("{}. {} - {:?}\n",
i + 1,
edit.base_edit.file,
classification.category
)),
ResetColor
).map_err(|e| format!("Failed to render pending edit: {}", e))?;
}
execute!(io::stdout(), Print("\n")).ok();
}
Ok(())
}
fn render_branch_management(&self) -> Result<(), String> {
execute!(io::stdout(),
SetForegroundColor(Color::Green),
Print("🌿 Branch Management\n"),
Print("══════════════════\n"),
ResetColor,
Print(&format!("Current Branch: {}\n", self.git_system.session_state.current_branch)),
Print(&format!("Original Branch: {}\n", self.git_system.session_state.original_branch)),
Print(&format!("Branch Strategy: {:?}\n", self.git_system.branch_manager.naming_strategy)),
Print(&format!("Auto Cleanup: {}\n", self.git_system.branch_manager.auto_cleanup)),
Print("\n"),
SetForegroundColor(Color::Cyan),
Print("Available Operations:\n"),
ResetColor,
Print("• Create new session branch\n"),
Print("• Switch branch\n"),
Print("• Merge session to main\n"),
Print("• Delete session branch\n"),
Print("\n")
).map_err(|e| format!("Failed to render branch management: {}", e))?;
Ok(())
}
fn render_commit_history(&self) -> Result<(), String> {
execute!(io::stdout(),
SetForegroundColor(Color::Green),
Print("📚 Commit History\n"),
Print("═══════════════\n"),
ResetColor
).map_err(|e| format!("Failed to render commit history header: {}", e))?;
if self.git_system.session_state.commit_history.is_empty() {
execute!(io::stdout(),
SetForegroundColor(Color::Yellow),
Print("No commits in current session\n"),
ResetColor
).map_err(|e| format!("Failed to render no commits message: {}", e))?;
} else {
for (i, commit) in self.git_system.session_state.commit_history.iter().enumerate() {
execute!(io::stdout(),
SetForegroundColor(Color::Cyan),
Print(&format!("{}. {}\n", i + 1, &commit.hash[..8])),
ResetColor,
Print(&format!(" Message: {}\n", commit.message)),
Print(&format!(" Files: {}\n", commit.files_changed.join(", "))),
Print(&format!(" Time: {:?}\n", commit.timestamp)),
Print("\n")
).map_err(|e| format!("Failed to render commit: {}", e))?;
}
}
Ok(())
}
fn render_conflict_resolution(&self) -> Result<(), String> {
execute!(io::stdout(),
SetForegroundColor(Color::Red),
Print("⚠️ Conflict Resolution\n"),
Print("═════════════════════\n"),
ResetColor,
Print("Conflict Resolution Strategies:\n"),
Print("• Prefer Local Changes\n"),
Print("• Prefer Remote Changes\n"),
Print("• Interactive Resolution\n"),
Print("• Classification-Based Resolution\n"),
Print("• Cognitive Operator Resolution\n"),
Print("\n"),
SetForegroundColor(Color::Yellow),
Print("Current Strategy: "),
ResetColor,
Print(&format!("{:?}\n", self.git_system.conflict_resolver.strategies.first().unwrap_or(&ConflictStrategy::Interactive))),
Print("\n")
).map_err(|e| format!("Failed to render conflict resolution: {}", e))?;
Ok(())
}
fn render_backup_management(&self) -> Result<(), String> {
execute!(io::stdout(),
SetForegroundColor(Color::Green),
Print("💾 Backup Management\n"),
Print("══════════════════\n"),
ResetColor
).map_err(|e| format!("Failed to render backup management header: {}", e))?;
if self.git_system.session_state.backup_points.is_empty() {
execute!(io::stdout(),
SetForegroundColor(Color::Yellow),
Print("No backup points created\n"),
ResetColor
).map_err(|e| format!("Failed to render no backups message: {}", e))?;
} else {
for (i, backup) in self.git_system.session_state.backup_points.iter().enumerate() {
execute!(io::stdout(),
SetForegroundColor(Color::Cyan),
Print(&format!("{}. Backup: {}\n", i + 1, backup.id)),
ResetColor,
Print(&format!(" Description: {}\n", backup.description)),
Print(&format!(" State Hash: {}\n", &backup.state_hash[..8])),
Print(&format!(" Time: {:?}\n", backup.timestamp)),
Print("\n")
).map_err(|e| format!("Failed to render backup: {}", e))?;
}
}
execute!(io::stdout(),
SetForegroundColor(Color::Cyan),
Print("Backup Configuration:\n"),
ResetColor,
Print(&format!("Compression: {}\n", self.git_system.backup_manager.compress_backups)),
Print(&format!("Incremental: {}\n", self.git_system.backup_manager.incremental)),
Print("\n")
).map_err(|e| format!("Failed to render backup config: {}", e))?;
Ok(())
}
fn render_footer(&self) -> Result<(), String> {
execute!(io::stdout(),
SetForegroundColor(Color::Cyan),
Print("─".repeat(80)),
Print("\n"),
Print("Commands: [s]tart session | [e]nd session | [c]ommit | [b]ackup | [m]erge | [h]elp | [q]uit\n"),
Print("Views: F1-Overview | F2-Branches | F3-Commits | F4-Conflicts | F5-Backups | Tab-Cycle Panels\n"),
ResetColor
).map_err(|e| format!("Failed to render footer: {}", e))?;
if !self.status_message.is_empty() {
execute!(io::stdout(),
SetForegroundColor(Color::Yellow),
Print(&format!("Status: {}\n", self.status_message)),
ResetColor
).map_err(|e| format!("Failed to render status: {}", e))?;
}
Ok(())
}
fn render_help_panel(&self) -> Result<(), String> {
execute!(io::stdout(),
cursor::MoveTo(20, 8),
SetForegroundColor(Color::White),
Print("╔═══════════════════════════════════════╗\n"),
cursor::MoveTo(20, 9),
Print("║ 🔧 Git Help ║\n"),
cursor::MoveTo(20, 10),
Print("╠═══════════════════════════════════════╣\n"),
cursor::MoveTo(20, 11),
Print("║ s - Start new Git session ║\n"),
cursor::MoveTo(20, 12),
Print("║ e - End current session ║\n"),
cursor::MoveTo(20, 13),
Print("║ c - Commit current changes ║\n"),
cursor::MoveTo(20, 14),
Print("║ b - Create backup point ║\n"),
cursor::MoveTo(20, 15),
Print("║ m - Merge session to main ║\n"),
cursor::MoveTo(20, 16),
Print("║ F1-F5 - Switch view modes ║\n"),
cursor::MoveTo(20, 17),
Print("║ Tab - Cycle through panels ║\n"),
cursor::MoveTo(20, 18),
Print("║ h - Toggle this help ║\n"),
cursor::MoveTo(20, 19),
Print("║ q/Esc - Quit ║\n"),
cursor::MoveTo(20, 20),
Print("╚═══════════════════════════════════════╝\n"),
ResetColor
).map_err(|e| format!("Failed to render help panel: {}", e))?;
Ok(())
}
fn cycle_panels(&mut self) {
self.active_panel = match self.active_panel {
GitPanel::SessionStatus => GitPanel::Operations,
GitPanel::Operations => GitPanel::FileChanges,
GitPanel::FileChanges => GitPanel::Configuration,
GitPanel::Configuration => GitPanel::SessionStatus,
};
}
fn handle_start_session(&mut self) -> Result<(), String> {
match self.git_system.start_session(None) {
Ok(message) => {
self.status_message = message;
}
Err(e) => {
self.status_message = format!("Error starting session: {}", e);
}
}
Ok(())
}
fn handle_end_session(&mut self) -> Result<(), String> {
match self.git_system.end_session(false) {
Ok(message) => {
self.status_message = message;
}
Err(e) => {
self.status_message = format!("Error ending session: {}", e);
}
}
Ok(())
}
fn handle_commit_changes(&mut self) -> Result<(), String> {
let message = "SOMA Interactive Commit";
match self.git_system.commit_current_changes(message) {
Ok(hash) => {
self.status_message = format!("Committed successfully: {}", &hash[..8]);
}
Err(e) => {
self.status_message = format!("Commit error: {}", e);
}
}
Ok(())
}
fn handle_create_backup(&mut self) -> Result<(), String> {
match self.git_system.backup_manager.create_backup_point("Interactive backup".to_string()) {
Ok(backup) => {
self.status_message = format!("Backup created: {}", backup.id);
let mut session_state = self.git_system.session_state.clone();
session_state.backup_points.push(backup);
self.git_system.session_state = session_state;
}
Err(e) => {
self.status_message = format!("Backup error: {}", e);
}
}
Ok(())
}
fn handle_merge_session(&mut self) -> Result<(), String> {
match self.git_system.merge_session_to_main() {
Ok(()) => {
self.status_message = "Session merged to main successfully".to_string();
}
Err(e) => {
self.status_message = format!("Merge error: {}", e);
}
}
Ok(())
}
pub fn add_pending_edits(&mut self, edits: Vec<(ModifiableEdit, ClassifiedEdit)>) {
self.pending_edits.extend(edits);
}
pub fn process_pending_edits(&mut self) -> Result<Vec<GitOperationResult>, String> {
if self.pending_edits.is_empty() {
return Ok(Vec::new());
}
let (edits, classifications): (Vec<_>, Vec<_>) = self.pending_edits.drain(..).unzip();
let results = self.git_system.apply_edits_with_git(edits, classifications)?;
self.status_message = format!("Processed {} edits with Git integration", results.len());
Ok(results)
}
}
pub fn interactive_git_workflow_decision(
edits: Vec<ModifiableEdit>,
classifications: Vec<ClassifiedEdit>,
repository_path: std::path::PathBuf
) -> Result<bool, String> {
println!("🔧 Git Integration Available");
println!("════════════════════════════");
println!("Would you like to use Git integration for these edits?");
println!();
println!("Git integration provides:");
println!("• Automatic session branching");
println!("• Intelligent commit strategies based on edit classification");
println!("• Conflict resolution with cognitive operators");
println!("• Automated backup points");
println!("• CI/CD pipeline integration");
println!();
print!("Use Git integration? [y/N]: ");
io::stdout().flush().map_err(|e| format!("Failed to flush stdout: {}", e))?;
let mut input = String::new();
io::stdin().read_line(&mut input).map_err(|e| format!("Failed to read input: {}", e))?;
if input.trim().to_lowercase().starts_with('y') {
let mut git_cli = GitIntegrationCLI::new(repository_path)?;
git_cli.add_pending_edits(edits.into_iter().zip(classifications).collect());
println!("Launching Git Integration interface...");
git_cli.start_interactive_session()?;
let results = git_cli.process_pending_edits()?;
println!("Git integration completed with {} operations", results.len());
Ok(true)
} else {
println!("Proceeding without Git integration");
Ok(false)
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
use std::process::Command;
fn create_test_git_repo() -> (TempDir, std::path::PathBuf) {
let temp_dir = TempDir::new().unwrap();
let repo_path = temp_dir.path().to_path_buf();
Command::new("git")
.arg("init")
.current_dir(&repo_path)
.output()
.unwrap();
Command::new("git")
.args(&["config", "user.name", "SOMA Test"])
.current_dir(&repo_path)
.output()
.unwrap();
Command::new("git")
.args(&["config", "user.email", "soma@test.com"])
.current_dir(&repo_path)
.output()
.unwrap();
(temp_dir, repo_path)
}
#[test]
fn test_git_integration_cli_creation() {
let (_temp_dir, repo_path) = create_test_git_repo();
let git_cli = GitIntegrationCLI::new(repo_path);
assert!(git_cli.is_ok());
}
#[test]
fn test_view_mode_cycling() {
let (_temp_dir, repo_path) = create_test_git_repo();
let mut git_cli = GitIntegrationCLI::new(repo_path).unwrap();
assert_eq!(git_cli.active_panel, GitPanel::SessionStatus);
git_cli.cycle_panels();
assert_eq!(git_cli.active_panel, GitPanel::Operations);
git_cli.cycle_panels();
assert_eq!(git_cli.active_panel, GitPanel::FileChanges);
}
#[test]
fn test_session_operations() {
let (_temp_dir, repo_path) = create_test_git_repo();
let mut git_cli = GitIntegrationCLI::new(repo_path).unwrap();
let start_result = git_cli.handle_start_session();
assert!(start_result.is_ok());
let backup_result = git_cli.handle_create_backup();
assert!(backup_result.is_ok());
let end_result = git_cli.handle_end_session();
assert!(end_result.is_ok());
}
#[test]
fn test_pending_edits_management() {
let (_temp_dir, repo_path) = create_test_git_repo();
let git_cli = GitIntegrationCLI::new(repo_path).unwrap();
assert_eq!(git_cli.pending_edits.len(), 0);
}
}