1use freneng::{perform_renames, FrenError, EnginePreviewResult, FileRename, log_audit_from_result};
7use freneng::history::save_history;
8use crate::ui::interactive_edit;
9use std::io::{self, Write};
10use std::env;
11use std::path::PathBuf;
12
13pub async fn handle_rename_command(
30 preview_result: EnginePreviewResult,
31 overwrite: bool,
32 yes: bool,
33 interactive: bool,
34 command: String,
35 pattern: Option<String>,
36 enable_audit: bool,
37) -> Result<(), FrenError> {
38 if !preview_result.warnings.is_empty() {
42 println!("\nWARNINGS:");
43 for warning in &preview_result.warnings {
44 println!(" - {}", warning);
45 }
46 }
47
48 if preview_result.has_empty_names {
50 eprintln!("\nERROR: One or more files would have an empty name. Renaming aborted.");
51 eprintln!("Please check your pattern and ensure it generates valid filenames.");
52 std::process::exit(1);
53 }
54
55 let mut renames = preview_result.renames;
56
57 if interactive {
59 if !interactive_edit(&mut renames) {
60 println!("Interactive editing cancelled.");
61 return Ok(());
62 }
63
64 let has_empty = renames.iter().any(|r| r.new_name.trim().is_empty());
66 if has_empty {
67 eprintln!("\nERROR: One or more files would have an empty name. Renaming aborted.");
68 eprintln!("Please check your pattern and ensure it generates valid filenames.");
69 std::process::exit(1);
70 }
71 }
72
73 if !yes && !interactive {
75 renames = prompt_each_rename(&renames)?;
76 if renames.is_empty() {
77 println!("No files to rename.");
78 return Ok(());
79 }
80 }
81
82 match perform_renames(&renames, overwrite).await {
84 Ok(execution) => {
85 if !execution.skipped.is_empty() {
87 for (path, reason) in &execution.skipped {
88 println!("Skipping {}: {}", path.display(), reason);
89 }
90 }
91
92 if !execution.errors.is_empty() {
93 eprintln!("\nErrors:");
94 for (path, err) in &execution.errors {
95 eprintln!(" {}: {}", path.display(), err);
96 }
97 }
98
99 println!("\nSuccessfully renamed {} file(s).", execution.successful.len());
100
101 if let Err(e) = save_history(execution.successful.clone()).await {
103 eprintln!("Warning: Failed to save rename history: {}", e);
104 }
105
106 if enable_audit {
108 let working_dir = env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
109
110 if let Err(e) = log_audit_from_result(
111 &command,
112 pattern,
113 working_dir,
114 &execution,
115 ).await {
116 eprintln!("Warning: Failed to write audit log: {}", e);
117 }
118 }
119
120 Ok(())
121 }
122 Err(e) => {
123 eprintln!("Error performing renames: {}", e);
124 Err(e)
125 }
126 }
127}
128
129fn prompt_each_rename(renames: &[FileRename]) -> Result<Vec<FileRename>, FrenError> {
133 println!("\nConfirm each rename (y=yes, s=skip, a=apply all remaining, q=abort):");
134 println!("{:-<80}", "");
135
136 let mut to_apply = Vec::new();
137 let mut apply_all = false;
138
139 for (i, rename) in renames.iter().enumerate() {
140 if apply_all {
141 to_apply.push(rename.clone());
142 continue;
143 }
144
145 let old = rename.old_path.file_name().and_then(|n| n.to_str()).unwrap_or("?");
146 let new = &rename.new_name;
147
148 loop {
149 print!("\n[{}] {} -> {} (y/s/a/q): ", i + 1, old, new);
150 io::stdout().flush().map_err(|e| FrenError::Pattern(format!("IO error: {}", e)))?;
151
152 let mut input = String::new();
153 io::stdin().read_line(&mut input).map_err(|e| FrenError::Pattern(format!("IO error: {}", e)))?;
154 let input = input.trim().to_lowercase();
155
156 match input.as_str() {
157 "y" | "yes" => {
158 to_apply.push(rename.clone());
159 break;
160 }
161 "s" | "skip" => {
162 break;
164 }
165 "a" | "all" => {
166 to_apply.push(rename.clone());
168 apply_all = true;
169 break;
170 }
171 "q" | "quit" | "abort" => {
172 println!("Aborted.");
173 return Ok(Vec::new());
174 }
175 _ => {
176 println!("Invalid choice. Use: y (yes), s (skip), a (all), q (abort)");
177 continue;
178 }
179 }
180 }
181 }
182
183 Ok(to_apply)
184}
185