use crate::edit_control::{
ApprovalLevel, ApprovalRecord, ApprovalRole, ApprovalState,
GranularApprovalSystem, ModifiableEdit
};
use crossterm::{
cursor, execute, style, terminal,
event::{self, Event, KeyCode, KeyEvent, KeyModifiers},
style::{Color, SetForegroundColor, ResetColor},
terminal::ClearType,
};
use std::io::{self, Write};
use std::time::SystemTime;
#[derive(Debug, Clone, PartialEq)]
pub enum GranularApprovalDecision {
Approve(ApprovalRole),
ApproveWithComments(ApprovalRole, String),
RequestEscalation(String),
Delegate(ApprovalRole, String), ConditionalApproval(ApprovalRole, Vec<String>), Reject(String),
MoreInfo,
Skip,
}
pub struct GranularApprovalInterface {
approval_system: GranularApprovalSystem,
current_user_role: ApprovalRole,
current_user_id: String,
#[allow(dead_code)]
approval_history: Vec<ApprovalRecord>,
}
impl GranularApprovalInterface {
pub fn new(user_role: ApprovalRole, user_id: String) -> Self {
Self {
approval_system: GranularApprovalSystem::new(),
current_user_role: user_role,
current_user_id: user_id,
approval_history: Vec::new(),
}
}
pub fn process_approval_request(
&mut self,
edit: &mut ModifiableEdit
) -> Result<GranularApprovalDecision, Box<dyn std::error::Error>> {
execute!(io::stdout(), terminal::Clear(ClearType::All), cursor::MoveTo(0, 0))?;
let required_level = self.approval_system.determine_required_level(edit);
self.display_edit_overview(edit, &required_level)?;
self.display_approval_status(edit, &required_level)?;
self.display_approval_options(&required_level)?;
self.get_approval_decision(edit, &required_level)
}
fn display_edit_overview(
&self,
edit: &ModifiableEdit,
required_level: &ApprovalLevel
) -> Result<(), Box<dyn std::error::Error>> {
println!("âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ");
println!("â đ GRANULAR APPROVAL REQUIRED â");
println!("âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ");
println!("\nđ Edit Details:");
println!(" File: {}", edit.base_edit.file);
println!(" Lines: {:?}", edit.base_edit.line_range);
println!(" Confidence: {:.1}%", edit.get_effective_confidence() * 100.0);
println!(" File: {}", edit.base_edit.file);
println!(" Reason: {}", edit.base_edit.reason);
print!("\nđ¯ Required Approval Level: ");
match required_level {
ApprovalLevel::Auto => {
execute!(io::stdout(),
SetForegroundColor(Color::Green),
style::Print("AUTO"),
ResetColor
)?;
println!(" (Automatic approval)");
}
ApprovalLevel::Low => {
execute!(io::stdout(),
SetForegroundColor(Color::Blue),
style::Print("LOW"),
ResetColor
)?;
println!(" (Single reviewer required)");
}
ApprovalLevel::Medium => {
execute!(io::stdout(),
SetForegroundColor(Color::Yellow),
style::Print("MEDIUM"),
ResetColor
)?;
println!(" (Senior reviewer or 2 junior reviewers)");
}
ApprovalLevel::High => {
execute!(io::stdout(),
SetForegroundColor(Color::Magenta),
style::Print("HIGH"),
ResetColor
)?;
println!(" (Lead/Architect approval required)");
}
ApprovalLevel::Critical => {
execute!(io::stdout(),
SetForegroundColor(Color::Red),
style::Print("CRITICAL"),
ResetColor
)?;
println!(" (Multiple leads + security review)");
}
}
self.display_risk_assessment(edit)?;
Ok(())
}
fn display_risk_assessment(&self, edit: &ModifiableEdit) -> Result<(), Box<dyn std::error::Error>> {
println!("\nâ ī¸ Risk Assessment:");
let lines_changed = edit.base_edit.new_code.lines().count();
let confidence = edit.get_effective_confidence();
if lines_changed > self.approval_system.policy.risk_thresholds.lines_changed_high_risk {
execute!(io::stdout(), SetForegroundColor(Color::Red))?;
println!(" đ´ Large change: {} lines modified", lines_changed);
} else if lines_changed > 20 {
execute!(io::stdout(), SetForegroundColor(Color::Yellow))?;
println!(" đĄ Medium change: {} lines modified", lines_changed);
} else {
execute!(io::stdout(), SetForegroundColor(Color::Green))?;
println!(" đĸ Small change: {} lines modified", lines_changed);
}
execute!(io::stdout(), ResetColor)?;
if confidence < self.approval_system.policy.risk_thresholds.confidence_threshold_escalation {
execute!(io::stdout(), SetForegroundColor(Color::Red))?;
println!(" đ´ Low confidence: {:.1}% (may require escalation)", confidence * 100.0);
} else if confidence < 0.8 {
execute!(io::stdout(), SetForegroundColor(Color::Yellow))?;
println!(" đĄ Medium confidence: {:.1}%", confidence * 100.0);
} else {
execute!(io::stdout(), SetForegroundColor(Color::Green))?;
println!(" đĸ High confidence: {:.1}%", confidence * 100.0);
}
execute!(io::stdout(), ResetColor)?;
let code_lower = edit.base_edit.new_code.to_lowercase();
for pattern in &self.approval_system.policy.risk_thresholds.security_sensitive_patterns {
if code_lower.contains(pattern) {
execute!(io::stdout(), SetForegroundColor(Color::Red))?;
println!(" đ´ Security-sensitive: Contains '{}'", pattern);
execute!(io::stdout(), ResetColor)?;
break;
}
}
Ok(())
}
fn display_approval_status(
&self,
edit: &ModifiableEdit,
required_level: &ApprovalLevel
) -> Result<(), Box<dyn std::error::Error>> {
println!("\nđ Current Approval Status:");
let current_approvals = match &edit.approval_state {
ApprovalState::GranularPending { current_approvals, .. } => current_approvals,
ApprovalState::PartiallyApproved { approvals, .. } => approvals,
ApprovalState::EscalationRequired { approvals, .. } => approvals,
_ => {
println!(" âŗ No approvals yet");
return Ok(());
}
};
if current_approvals.is_empty() {
println!(" âŗ No approvals yet");
return Ok(());
}
let mut temp_system = GranularApprovalSystem::new();
for approval in current_approvals {
let _ = temp_system.add_approval(approval.clone());
}
let status = temp_system.get_approval_status(required_level);
println!(" đ Progress: {}/{} approvals", status.approval_count,
self.calculate_required_approvals(required_level));
println!(" đ¯ Current Level: {:?}", status.current_level);
println!(" â
Sufficient: {}", if status.is_sufficient { "Yes" } else { "No" });
if let Some(next_role) = status.next_required_role {
println!(" đ¤ Next Required: {:?}", next_role);
}
println!("\nđ Approval History:");
for (i, approval) in current_approvals.iter().enumerate() {
let _timestamp = approval.timestamp
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
println!(" {}. {:?} by {} ({}s ago)",
i + 1,
approval.approver_role,
approval.approver_id,
SystemTime::now().duration_since(approval.timestamp)
.unwrap_or_default().as_secs());
if let Some(comments) = &approval.comments {
println!(" đŦ \"{comments}\"");
}
}
Ok(())
}
fn display_approval_options(&self, required_level: &ApprovalLevel) -> Result<(), Box<dyn std::error::Error>> {
println!("\nđī¸ Approval Options:");
println!(" Current User: {} ({:?})", self.current_user_id, self.current_user_role);
let can_approve = self.can_user_approve_at_level(required_level);
if can_approve {
execute!(io::stdout(), SetForegroundColor(Color::Green))?;
println!(" [a] â
Approve");
execute!(io::stdout(), ResetColor)?;
println!(" [c] đŦ Approve with Comments");
} else {
execute!(io::stdout(), SetForegroundColor(Color::DarkGrey))?;
println!(" [a] â Approve (insufficient role)");
execute!(io::stdout(), ResetColor)?;
}
println!(" [e] đē Request Escalation");
println!(" [d] đĨ Delegate to Another Reviewer");
println!(" [o] â ī¸ Conditional Approval (with conditions)");
println!(" [r] â Reject");
println!(" [i] âšī¸ More Information");
println!(" [s] âī¸ Skip");
println!(" [v] đī¸ View Code Changes");
Ok(())
}
fn get_approval_decision(
&mut self,
edit: &ModifiableEdit,
required_level: &ApprovalLevel
) -> Result<GranularApprovalDecision, Box<dyn std::error::Error>> {
print!("\nđ¤ Your decision: ");
io::stdout().flush()?;
loop {
if let Event::Key(KeyEvent { code, modifiers: KeyModifiers::NONE, .. }) = event::read()? {
match code {
KeyCode::Char('a') => {
if self.can_user_approve_at_level(required_level) {
return Ok(GranularApprovalDecision::Approve(self.current_user_role.clone()));
} else {
println!("\nâ Insufficient role for approval at this level");
print!("đ¤ Your decision: ");
io::stdout().flush()?;
}
}
KeyCode::Char('c') => {
if self.can_user_approve_at_level(required_level) {
let comments = self.get_approval_comments()?;
return Ok(GranularApprovalDecision::ApproveWithComments(
self.current_user_role.clone(),
comments
));
} else {
println!("\nâ Insufficient role for approval at this level");
print!("đ¤ Your decision: ");
io::stdout().flush()?;
}
}
KeyCode::Char('e') => {
let reason = self.get_escalation_reason()?;
return Ok(GranularApprovalDecision::RequestEscalation(reason));
}
KeyCode::Char('d') => {
let (target_role, reason) = self.get_delegation_info()?;
return Ok(GranularApprovalDecision::Delegate(target_role, reason));
}
KeyCode::Char('o') => {
let conditions = self.get_approval_conditions()?;
return Ok(GranularApprovalDecision::ConditionalApproval(
self.current_user_role.clone(),
conditions
));
}
KeyCode::Char('r') => {
let reason = self.get_rejection_reason()?;
return Ok(GranularApprovalDecision::Reject(reason));
}
KeyCode::Char('i') => {
return Ok(GranularApprovalDecision::MoreInfo);
}
KeyCode::Char('s') => {
return Ok(GranularApprovalDecision::Skip);
}
KeyCode::Char('v') => {
self.display_code_changes(edit)?;
println!("\nPress any key to continue...");
event::read()?;
self.display_approval_options(required_level)?;
print!("đ¤ Your decision: ");
io::stdout().flush()?;
}
KeyCode::Esc => {
return Ok(GranularApprovalDecision::Skip);
}
_ => {
println!("\nâ Invalid option. Please try again.");
print!("đ¤ Your decision: ");
io::stdout().flush()?;
}
}
}
}
}
fn display_code_changes(&self, edit: &ModifiableEdit) -> Result<(), Box<dyn std::error::Error>> {
execute!(io::stdout(), terminal::Clear(ClearType::All), cursor::MoveTo(0, 0))?;
println!("âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ");
println!("â đ CODE CHANGES â");
println!("âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ");
println!("\nđ File: {}", edit.base_edit.file);
println!("đ Lines: {:?}", edit.base_edit.line_range);
println!("\nđ Proposed Code:");
println!("{}", "â".repeat(70));
for (i, line) in edit.base_edit.new_code.lines().enumerate() {
println!("{:3}: {}", i + 1, line);
}
println!("{}", "â".repeat(70));
if !edit.modifications.is_empty() {
println!("\nđ Final Code (with modifications):");
println!("{}", "â".repeat(70));
let final_code = edit.compute_final_code();
for (i, line) in final_code.lines().enumerate() {
println!("{:3}: {}", i + 1, line);
}
println!("{}", "â".repeat(70));
}
Ok(())
}
fn get_approval_comments(&self) -> Result<String, Box<dyn std::error::Error>> {
println!("\nđŦ Enter approval comments (press Enter when done):");
print!("Comments: ");
io::stdout().flush()?;
let mut comments = String::new();
io::stdin().read_line(&mut comments)?;
Ok(comments.trim().to_string())
}
fn get_escalation_reason(&self) -> Result<String, Box<dyn std::error::Error>> {
println!("\nđē Enter escalation reason:");
print!("Reason: ");
io::stdout().flush()?;
let mut reason = String::new();
io::stdin().read_line(&mut reason)?;
Ok(reason.trim().to_string())
}
fn get_delegation_info(&self) -> Result<(ApprovalRole, String), Box<dyn std::error::Error>> {
println!("\nđĨ Delegate to which role?");
println!(" [1] Junior");
println!(" [2] Senior");
println!(" [3] Lead");
println!(" [4] Architect");
println!(" [5] Security");
print!("Choice: ");
io::stdout().flush()?;
let mut choice = String::new();
io::stdin().read_line(&mut choice)?;
let target_role = match choice.trim() {
"1" => ApprovalRole::Junior,
"2" => ApprovalRole::Senior,
"3" => ApprovalRole::Lead,
"4" => ApprovalRole::Architect,
"5" => ApprovalRole::Security,
_ => return Err("Invalid role selection".into()),
};
println!("Enter delegation reason:");
print!("Reason: ");
io::stdout().flush()?;
let mut reason = String::new();
io::stdin().read_line(&mut reason)?;
Ok((target_role, reason.trim().to_string()))
}
fn get_approval_conditions(&self) -> Result<Vec<String>, Box<dyn std::error::Error>> {
println!("\nâ ī¸ Enter conditions for approval (one per line, empty line to finish):");
let mut conditions = Vec::new();
let mut line_num = 1;
loop {
print!("Condition {}: ", line_num);
io::stdout().flush()?;
let mut condition = String::new();
io::stdin().read_line(&mut condition)?;
let condition = condition.trim();
if condition.is_empty() {
break;
}
conditions.push(condition.to_string());
line_num += 1;
}
Ok(conditions)
}
fn get_rejection_reason(&self) -> Result<String, Box<dyn std::error::Error>> {
println!("\nâ Enter rejection reason:");
print!("Reason: ");
io::stdout().flush()?;
let mut reason = String::new();
io::stdin().read_line(&mut reason)?;
Ok(reason.trim().to_string())
}
fn can_user_approve_at_level(&self, required_level: &ApprovalLevel) -> bool {
match required_level {
ApprovalLevel::Auto => true, ApprovalLevel::Low => matches!(
self.current_user_role,
ApprovalRole::Senior | ApprovalRole::Lead | ApprovalRole::Architect | ApprovalRole::Security
),
ApprovalLevel::Medium => matches!(
self.current_user_role,
ApprovalRole::Senior | ApprovalRole::Lead | ApprovalRole::Architect | ApprovalRole::Security
),
ApprovalLevel::High => matches!(
self.current_user_role,
ApprovalRole::Lead | ApprovalRole::Architect | ApprovalRole::Security
),
ApprovalLevel::Critical => matches!(
self.current_user_role,
ApprovalRole::Architect | ApprovalRole::Security
),
}
}
fn calculate_required_approvals(&self, level: &ApprovalLevel) -> usize {
match level {
ApprovalLevel::Auto => 0,
ApprovalLevel::Low => 1,
ApprovalLevel::Medium => 2,
ApprovalLevel::High => 1,
ApprovalLevel::Critical => 2,
}
}
pub fn apply_decision(
&mut self,
edit: &mut ModifiableEdit,
decision: GranularApprovalDecision
) -> Result<(), Box<dyn std::error::Error>> {
match decision {
GranularApprovalDecision::Approve(role) => {
let approval = ApprovalRecord {
approver_id: self.current_user_id.clone(),
approver_role: role,
level: self.approval_system.determine_required_level(edit),
timestamp: SystemTime::now(),
comments: None,
delegation_chain: Vec::new(),
};
edit.add_approval_record(approval)?;
edit.evaluate_granular_approval()?;
}
GranularApprovalDecision::ApproveWithComments(role, comments) => {
let approval = ApprovalRecord {
approver_id: self.current_user_id.clone(),
approver_role: role,
level: self.approval_system.determine_required_level(edit),
timestamp: SystemTime::now(),
comments: Some(comments),
delegation_chain: Vec::new(),
};
edit.add_approval_record(approval)?;
edit.evaluate_granular_approval()?;
}
GranularApprovalDecision::RequestEscalation(reason) => {
let current_level = self.approval_system.determine_required_level(edit);
let required_level = match current_level {
ApprovalLevel::Auto => ApprovalLevel::Low,
ApprovalLevel::Low => ApprovalLevel::Medium,
ApprovalLevel::Medium => ApprovalLevel::High,
ApprovalLevel::High => ApprovalLevel::Critical,
ApprovalLevel::Critical => ApprovalLevel::Critical, };
edit.approval_state = ApprovalState::EscalationRequired {
current_level,
required_level,
reason,
approvals: Vec::new(),
};
}
GranularApprovalDecision::Reject(reason) => {
edit.approval_state = ApprovalState::Rejected;
println!("â Edit rejected: {}", reason);
}
GranularApprovalDecision::ConditionalApproval(_role, conditions) => {
edit.approval_state = ApprovalState::Conditional { conditions };
}
_ => {
println!("âšī¸ Decision noted: {:?}", decision);
}
}
Ok(())
}
}
pub fn create_approval_record(
approver_id: String,
role: ApprovalRole,
level: ApprovalLevel,
comments: Option<String>
) -> ApprovalRecord {
ApprovalRecord {
approver_id,
approver_role: role,
level,
timestamp: SystemTime::now(),
comments,
delegation_chain: Vec::new(),
}
}
pub fn process_edit_with_granular_approval(
edit: &mut ModifiableEdit,
user_role: ApprovalRole,
user_id: String
) -> Result<(), Box<dyn std::error::Error>> {
let mut approval_interface = GranularApprovalInterface::new(user_role, user_id);
let decision = approval_interface.process_approval_request(edit)?;
approval_interface.apply_decision(edit, decision)?;
Ok(())
}