use std::collections::HashMap;
use crate::actions;
use crate::{PlannedEdit, RefactoringContext, RefactoringPlan};
pub async fn plan_rename(
ctx: &RefactoringContext,
def_rel_path: &str,
old_name: &str,
new_name: &str,
force: bool,
) -> Result<RefactoringPlan, String> {
let def_rel_path = def_rel_path.to_string();
let def_abs_path = ctx.root.join(&def_rel_path);
let def_content = std::fs::read_to_string(&def_abs_path)
.map_err(|e| format!("Error reading {}: {}", def_rel_path, e))?;
let loc = actions::locate_symbol(ctx, &def_abs_path, &def_content, old_name)
.ok_or_else(|| format!("Symbol '{}' not found in {}", old_name, def_rel_path))?;
let refs = actions::find_references(ctx, old_name, &def_rel_path).await;
if !force {
let conflicts =
actions::check_conflicts(ctx, &def_abs_path, &def_content, new_name, &refs.importers)
.await;
if !conflicts.is_empty() {
let detail = conflicts
.iter()
.map(|c| format!(" {}", c))
.collect::<Vec<_>>()
.join("\n");
return Err(format!(
"Rename '{}' → '{}' would cause conflicts (use --force to override):\n{}",
old_name, new_name, detail
));
}
}
let mut edits: Vec<PlannedEdit> = vec![];
let mut warnings: Vec<String> = vec![];
if let Some(edit) = actions::plan_rename_in_file(
ctx,
&def_abs_path,
&def_content,
&[loc.start_line],
old_name,
new_name,
) {
edits.push(edit);
}
let mut callers_by_file: HashMap<String, Vec<usize>> = HashMap::new();
for caller in &refs.callers {
callers_by_file
.entry(caller.file.clone())
.or_default()
.push(caller.line);
}
let mut edited_files: std::collections::HashSet<String> = std::collections::HashSet::new();
edited_files.insert(def_rel_path.clone());
for (rel_path, lines) in &callers_by_file {
if rel_path == &def_rel_path {
continue;
}
let abs_path = ctx.root.join(rel_path);
let content = match std::fs::read_to_string(&abs_path) {
Ok(c) => c,
Err(_) => {
warnings.push(format!("Could not read caller file: {}", rel_path));
continue;
}
};
if let Some(edit) =
actions::plan_rename_in_file(ctx, &abs_path, &content, lines, old_name, new_name)
{
edits.push(edit);
edited_files.insert(rel_path.clone());
}
}
let mut importers_by_file: HashMap<String, Vec<usize>> = HashMap::new();
for imp in &refs.importers {
importers_by_file
.entry(imp.file.clone())
.or_default()
.push(imp.line);
}
for (rel_path, lines) in &importers_by_file {
if edited_files.contains(rel_path) {
if let Some(existing) = edits.iter_mut().find(|e| e.file == ctx.root.join(rel_path)) {
let mut current = existing.new_content.clone();
let mut changed = false;
for &line_no in lines {
if let Some(new_content) = ctx
.editor
.rename_identifier_in_line(¤t, line_no, old_name, new_name)
{
current = new_content;
changed = true;
}
}
if changed {
existing.new_content = current;
}
continue;
}
}
let abs_path = ctx.root.join(rel_path);
let content = match std::fs::read_to_string(&abs_path) {
Ok(c) => c,
Err(_) => {
warnings.push(format!("Could not read importer file: {}", rel_path));
continue;
}
};
if let Some(edit) =
actions::plan_rename_in_file(ctx, &abs_path, &content, lines, old_name, new_name)
{
edits.push(edit);
}
}
if ctx.index.is_none() && refs.callers.is_empty() && refs.importers.is_empty() {
warnings.push("Index not available; renamed definition only".to_string());
}
Ok(RefactoringPlan {
operation: "rename".to_string(),
edits,
warnings,
})
}