rs_hack/
editor.rs

1use anyhow::{Context, Result};
2use proc_macro2::{LineColumn, Span};
3use syn::{
4    parse_str, File, Item, ItemEnum, ItemStruct,
5    Fields, Field, spanned::Spanned, Arm, ExprMatch, ExprStruct,
6    visit_mut::VisitMut, Expr,
7};
8use quote::ToTokens;
9
10use crate::operations::*;
11use crate::path_resolver::PathResolver;
12use prettyplease;
13
14pub struct RustEditor {
15    content: String,
16    syntax_tree: File,
17    line_offsets: Vec<usize>, // Byte offset for each line start
18}
19
20impl RustEditor {
21    pub fn new(content: &str) -> Result<Self> {
22        let syntax_tree: File = syn::parse_str(content)
23            .context("Failed to parse Rust code")?;
24
25        let line_offsets = Self::compute_line_offsets(content);
26
27        Ok(Self {
28            content: content.to_string(),
29            syntax_tree,
30            line_offsets,
31        })
32    }
33
34    /// Format a field without extra spaces (e.g., "pub name: String" not "pub name : String")
35    fn format_field(field: &Field) -> String {
36        let mut result = String::new();
37
38        // Add visibility
39        if let syn::Visibility::Public(_) = field.vis {
40            result.push_str("pub ");
41        }
42
43        // Add field name
44        if let Some(ident) = &field.ident {
45            result.push_str(&ident.to_string());
46        }
47
48        // Add colon and type (no space before colon)
49        result.push_str(": ");
50
51        // Format type without extra spaces
52        let type_str = field.ty.to_token_stream().to_string();
53        let type_str = type_str.replace(" < ", "<").replace(" >", ">");
54        result.push_str(&type_str);
55
56        result
57    }
58    
59    fn compute_line_offsets(content: &str) -> Vec<usize> {
60        let mut offsets = vec![0];
61        for (i, ch) in content.char_indices() {
62            if ch == '\n' {
63                offsets.push(i + 1);
64            }
65        }
66        offsets
67    }
68    
69    pub fn apply_operation(&mut self, op: &Operation) -> Result<ModificationResult> {
70        match op {
71            Operation::AddStructField(op) => self.add_struct_field(op),
72            Operation::UpdateStructField(op) => self.update_struct_field(op),
73            Operation::RemoveStructField(op) => self.remove_struct_field(op),
74            Operation::AddStructLiteralField(op) => self.add_struct_literal_field(op),
75            Operation::AddEnumVariant(op) => self.add_enum_variant(op),
76            Operation::UpdateEnumVariant(op) => self.update_enum_variant(op),
77            Operation::RemoveEnumVariant(op) => self.remove_enum_variant(op),
78            Operation::AddMatchArm(op) => self.add_match_arm(op),
79            Operation::UpdateMatchArm(op) => self.update_match_arm(op),
80            Operation::RemoveMatchArm(op) => self.remove_match_arm(op),
81            Operation::AddImplMethod(op) => self.add_impl_method(op),
82            Operation::AddUseStatement(op) => self.add_use_statement(op),
83            Operation::AddDerive(op) => self.add_derive(op),
84            Operation::Transform(op) => self.transform(op),
85            Operation::RenameEnumVariant(op) => self.rename_enum_variant(op),
86            Operation::RenameFunction(op) => self.rename_function(op),
87            Operation::AddDocComment(op) => self.add_doc_comment_surgical(
88                &op.target_type,
89                &op.name,
90                &op.doc_comment,
91                &op.style,
92            ),
93            Operation::UpdateDocComment(op) => self.update_doc_comment_surgical(
94                &op.target_type,
95                &op.name,
96                &op.doc_comment,
97                &DocCommentStyle::Line, // Default to line style for updates
98            ),
99            Operation::RemoveDocComment(op) => self.remove_doc_comment_surgical(
100                &op.target_type,
101                &op.name,
102            ),
103        }
104    }
105    
106    pub(crate) fn add_struct_field(&mut self, op: &AddStructFieldOp) -> Result<ModificationResult> {
107        let mut modified_nodes = Vec::new();
108
109        // Find the struct and clone it to avoid borrowing issues
110        let item_struct = self.syntax_tree.items.iter()
111            .find_map(|item| {
112                if let Item::Struct(s) = item {
113                    if s.ident == op.struct_name {
114                        return Some(s.clone());
115                    }
116                }
117                None
118            })
119            .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
120
121        // Check if the struct matches the where filter (if specified)
122        if let Some(ref where_filter) = op.where_filter {
123            if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
124                // Struct doesn't match filter - skip without error
125                return Ok(ModificationResult {
126                    changed: false,
127                    modified_nodes: vec![],
128                });
129            }
130        }
131
132        // If literal_default is NOT provided, only modify the definition
133        if op.literal_default.is_none() {
134            // Create backup of original struct before modification
135            let backup_node = BackupNode {
136                node_type: "ItemStruct".to_string(),
137                identifier: op.struct_name.clone(),
138                original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
139                location: self.span_to_location(item_struct.span()),
140            };
141
142            // Insert the field into the struct definition
143            let modified = self.insert_struct_field(&item_struct, op)
144                .context("Failed to add field to struct definition")?;
145
146            if !modified {
147                return Ok(ModificationResult {
148                    changed: false,
149                    modified_nodes: vec![],
150                });
151            }
152
153            return Ok(ModificationResult {
154                changed: true,
155                modified_nodes: vec![backup_node],
156            });
157        }
158
159        // If literal_default IS provided:
160        // 1. Try to add to definition (idempotent - silently skips if field exists OR if field_def is incomplete)
161        // 2. Always update literals
162        let literal_default = op.literal_default.as_ref().unwrap();
163
164        // Check if field_def contains a type (has ':')
165        // If it doesn't, skip definition modification (literals-only mode)
166        let has_type = op.field_def.contains(':');
167
168        let mut def_modified = false;
169        if has_type {
170            // Create backup before any modifications
171            let backup_node = BackupNode {
172                node_type: "ItemStruct".to_string(),
173                identifier: op.struct_name.clone(),
174                original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
175                location: self.span_to_location(item_struct.span()),
176            };
177
178            // Try to insert field into definition (idempotent - returns false if already exists)
179            def_modified = self.insert_struct_field(&item_struct, op)
180                .context("Failed to add field to struct definition")?;
181
182            if def_modified {
183                modified_nodes.push(backup_node);
184                // Re-parse the content to update syntax_tree with the struct field changes
185                self.syntax_tree = syn::parse_str(&self.content)
186                    .context("Failed to re-parse content after adding struct field")?;
187                self.line_offsets = Self::compute_line_offsets(&self.content);
188            }
189        }
190
191        // Always update literals when literal_default is provided
192        // Extract field name from field_def (e.g., "return_type: Option<Type>" -> "return_type" or just "return_type")
193        let field_name = op.field_def.split(':')
194            .next()
195            .map(|s| s.trim().to_string())
196            .context("Failed to extract field name from field definition")?;
197
198        // Create the AddStructLiteralFieldOp
199        let literal_op = AddStructLiteralFieldOp {
200            struct_name: op.struct_name.clone(),
201            field_def: format!("{}: {}", field_name, literal_default),
202            position: op.position.clone(),
203            struct_path: None,  // Path resolution not available from struct field operations
204        };
205
206        // Update all struct literals
207        let literal_result = self.add_struct_literal_field(&literal_op)
208            .context("Failed to update struct literals")?;
209        modified_nodes.extend(literal_result.modified_nodes);
210
211        Ok(ModificationResult {
212            changed: true,
213            modified_nodes,
214        })
215    }
216    
217    fn insert_struct_field(&mut self, item_struct: &ItemStruct, op: &AddStructFieldOp) -> Result<bool> {
218        if let Fields::Named(ref fields) = item_struct.fields {
219            // Parse the new field
220            let field_code = format!("struct Dummy {{ {} }}", op.field_def);
221            let dummy: ItemStruct = parse_str(&field_code)
222                .context("Failed to parse field definition")?;
223
224            let new_field = if let Fields::Named(ref nf) = dummy.fields {
225                nf.named.first()
226                    .context("No field found in definition")?
227                    .clone()
228            } else {
229                anyhow::bail!("Expected named field");
230            };
231
232            // Check if field already exists
233            let new_field_name = new_field.ident.as_ref()
234                .map(|i| i.to_string())
235                .context("Field must have a name")?;
236
237            if fields.named.iter().any(|f| {
238                f.ident.as_ref().map(|i| i.to_string()) == Some(new_field_name.clone())
239            }) {
240                // Field already exists, skip adding
241                return Ok(false);
242            }
243            
244            // Determine insertion point
245            let insert_pos = match &op.position {
246                InsertPosition::First => {
247                    if let Some(first_field) = fields.named.first() {
248                        self.span_to_byte_offset(first_field.span().start())
249                    } else {
250                        // Empty struct, insert after the opening brace
251                        let brace_pos = self.span_to_byte_offset(fields.brace_token.span.join().start());
252                        brace_pos + 1
253                    }
254                }
255                InsertPosition::Last => {
256                    if let Some(last_field) = fields.named.last() {
257                        let end = self.span_to_byte_offset(last_field.span().end());
258                        // Find the comma or end
259                        self.find_after_field_end(end)
260                    } else {
261                        // Empty struct
262                        let brace_pos = self.span_to_byte_offset(fields.brace_token.span.join().start());
263                        brace_pos + 1
264                    }
265                }
266                InsertPosition::After(name) => {
267                    let field = fields.named.iter()
268                        .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(name.clone()))
269                        .with_context(|| format!("Field '{}' not found", name))?;
270                    let end = self.span_to_byte_offset(field.span().end());
271                    self.find_after_field_end(end)
272                }
273                InsertPosition::Before(name) => {
274                    let field = fields.named.iter()
275                        .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(name.clone()))
276                        .with_context(|| format!("Field '{}' not found", name))?;
277                    self.span_to_byte_offset(field.span().start())
278                }
279            };
280            
281            // Format the new field
282            let indent = self.get_indentation(insert_pos);
283            let field_str = Self::format_field(&new_field);
284            let insert_text = if matches!(op.position, InsertPosition::First) {
285                format!("\n{}{},", indent, field_str)
286            } else {
287                format!("\n{}{},", indent, field_str)
288            };
289
290            self.content.insert_str(insert_pos, &insert_text);
291            return Ok(true);
292        }
293        
294        anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
295    }
296
297    pub(crate) fn update_struct_field(&mut self, op: &UpdateStructFieldOp) -> Result<ModificationResult> {
298        // Find the struct and clone it to avoid borrowing issues
299        let item_struct = self.syntax_tree.items.iter()
300            .find_map(|item| {
301                if let Item::Struct(s) = item {
302                    if s.ident == op.struct_name {
303                        return Some(s.clone());
304                    }
305                }
306                None
307            })
308            .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
309
310        // Check if the struct matches the where filter (if specified)
311        if let Some(ref where_filter) = op.where_filter {
312            if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
313                // Struct doesn't match filter - skip without error
314                return Ok(ModificationResult {
315                    changed: false,
316                    modified_nodes: vec![],
317                });
318            }
319        }
320
321        // Create backup of original struct before modification
322        let backup_node = BackupNode {
323            node_type: "ItemStruct".to_string(),
324            identifier: op.struct_name.clone(),
325            original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
326            location: self.span_to_location(item_struct.span()),
327        };
328
329        let modified = self.replace_struct_field(&item_struct, op)?;
330
331        Ok(ModificationResult {
332            changed: modified,
333            modified_nodes: if modified { vec![backup_node] } else { vec![] },
334        })
335    }
336
337    fn replace_struct_field(&mut self, item_struct: &ItemStruct, op: &UpdateStructFieldOp) -> Result<bool> {
338        if let Fields::Named(ref fields) = item_struct.fields {
339            // Parse the new field definition to get the field name
340            let field_code = format!("struct Dummy {{ {} }}", op.field_def);
341            let dummy: ItemStruct = parse_str(&field_code)
342                .context("Failed to parse field definition")?;
343
344            let new_field = if let Fields::Named(ref nf) = dummy.fields {
345                nf.named.first()
346                    .context("No field found in definition")?
347                    .clone()
348            } else {
349                anyhow::bail!("Expected named field");
350            };
351
352            // Extract the field name from the parsed field
353            let field_name = new_field.ident.as_ref()
354                .map(|i| i.to_string())
355                .context("Field must have a name")?;
356
357            // Find the existing field
358            let existing_field = fields.named.iter()
359                .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(field_name.clone()))
360                .ok_or_else(|| anyhow::anyhow!("Field '{}' not found in struct '{}'", field_name, op.struct_name))?;
361
362            // Get the span of the existing field
363            let start = self.span_to_byte_offset(existing_field.span().start());
364            let end = self.span_to_byte_offset(existing_field.span().end());
365
366            // Format and replace the field
367            let new_field_str = Self::format_field(&new_field);
368
369            // Remove the old field and insert the new one
370            self.content.replace_range(start..end, &new_field_str);
371
372            return Ok(true);
373        }
374
375        anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
376    }
377
378    pub(crate) fn remove_struct_field(&mut self, op: &RemoveStructFieldOp) -> Result<ModificationResult> {
379        // Find the struct and clone it to avoid borrowing issues
380        let item_struct = self.syntax_tree.items.iter()
381            .find_map(|item| {
382                if let Item::Struct(s) = item {
383                    if s.ident == op.struct_name {
384                        return Some(s.clone());
385                    }
386                }
387                None
388            })
389            .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
390
391        // Check if the struct matches the where filter (if specified)
392        if let Some(ref where_filter) = op.where_filter {
393            if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
394                // Struct doesn't match filter - skip without error
395                return Ok(ModificationResult {
396                    changed: false,
397                    modified_nodes: vec![],
398                });
399            }
400        }
401
402        // Create backup of original struct before modification
403        let backup_node = BackupNode {
404            node_type: "ItemStruct".to_string(),
405            identifier: op.struct_name.clone(),
406            original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
407            location: self.span_to_location(item_struct.span()),
408        };
409
410        if let Fields::Named(ref fields) = item_struct.fields {
411            // Find the field to remove
412            let field_to_remove = fields.named.iter()
413                .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(op.field_name.clone()))
414                .ok_or_else(|| anyhow::anyhow!("Field '{}' not found in struct '{}'", op.field_name, op.struct_name))?;
415
416            // Get the span including the comma
417            let start = self.span_to_byte_offset(field_to_remove.span().start());
418            let mut end = self.span_to_byte_offset(field_to_remove.span().end());
419
420            // Find and include the comma and any trailing whitespace/newline
421            while end < self.content.len() {
422                match self.content.as_bytes()[end] as char {
423                    ',' => {
424                        end += 1;
425                        // Also consume the newline after the comma if present
426                        if end < self.content.len() && self.content.as_bytes()[end] == b'\n' {
427                            end += 1;
428                        }
429                        break;
430                    }
431                    ' ' | '\t' => end += 1,
432                    '\n' => {
433                        end += 1;
434                        break;
435                    }
436                    _ => break,
437                }
438            }
439
440            // Also need to remove leading whitespace/indentation on the same line
441            let mut line_start = start;
442            while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
443                line_start -= 1;
444            }
445
446            // Check if there's only whitespace between line_start and start
447            let before_field = &self.content[line_start..start];
448            if before_field.trim().is_empty() {
449                // Remove the whole line
450                self.content.replace_range(line_start..end, "");
451            } else {
452                // Just remove the field and comma
453                self.content.replace_range(start..end, "");
454            }
455
456            return Ok(ModificationResult {
457                changed: true,
458                modified_nodes: vec![backup_node],
459            });
460        }
461
462        anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
463    }
464
465    pub(crate) fn add_struct_literal_field(&mut self, op: &AddStructLiteralFieldOp) -> Result<ModificationResult> {
466        // Parse the field name from field_def (e.g., "return_type: None" -> "return_type")
467        let field_name = op.field_def.split(':')
468            .next()
469            .map(|s| s.trim().to_string())
470            .context("Field definition must contain ':'")?;
471
472        // Create a path resolver if a canonical path was provided
473        let path_resolver = if let Some(struct_path) = &op.struct_path {
474            let mut resolver = PathResolver::new(struct_path)
475                .ok_or_else(|| anyhow::anyhow!("Invalid struct path: {}", struct_path))?;
476
477            // Scan the file for use statements to build the alias map
478            resolver.scan_file(&self.syntax_tree);
479            Some(resolver)
480        } else {
481            None
482        };
483
484        // Collect backups of all struct literal expressions that will be modified
485        let backup_nodes = self.collect_struct_literal_backups(&op.struct_name, path_resolver.as_ref());
486
487        // Use a visitor to find and modify all struct literals
488        let mut visitor = StructLiteralFieldAdder {
489            struct_name: op.struct_name.clone(),
490            field_def: op.field_def.clone(),
491            field_name,
492            position: op.position.clone(),
493            path_resolver,
494            modified: false,
495        };
496
497        visitor.visit_file_mut(&mut self.syntax_tree);
498
499        if visitor.modified {
500            // Reformat the entire file for struct literals
501            self.content = prettyplease::unparse(&self.syntax_tree);
502            Ok(ModificationResult {
503                changed: true,
504                modified_nodes: backup_nodes,
505            })
506        } else {
507            Ok(ModificationResult {
508                changed: false,
509                modified_nodes: vec![],
510            })
511        }
512    }
513
514    /// Collect backups of all struct literal expressions for a given struct name
515    fn collect_struct_literal_backups(&self, struct_name: &str, path_resolver: Option<&PathResolver>) -> Vec<BackupNode> {
516        use syn::visit::Visit;
517
518        struct LiteralCollector<'a> {
519            struct_name: String,
520            path_resolver: Option<&'a PathResolver>,
521            backups: Vec<BackupNode>,
522            counter: usize,
523        }
524
525        impl<'ast, 'a> Visit<'ast> for LiteralCollector<'a> {
526            fn visit_expr(&mut self, node: &'ast Expr) {
527                if let Expr::Struct(expr_struct) = node {
528                    let matches = if let Some(resolver) = self.path_resolver {
529                        // Use PathResolver for safe matching
530                        resolver.matches_target(&expr_struct.path)
531                    } else {
532                        // Fallback to legacy pattern matching
533                        // - "Rectangle" → only Rectangle { ... } (no :: prefix)
534                        // - "*::Rectangle" → any path ending with Rectangle (View::Rectangle, etc.)
535                        // - "View::Rectangle" → exact match only View::Rectangle
536
537                        if self.struct_name.contains("::") {
538                            // Pattern contains :: - check for exact or wildcard match
539                            if self.struct_name.starts_with("*::") {
540                                // Wildcard: *::Rectangle matches any path ending with Rectangle
541                                let target_name = &self.struct_name[3..]; // Skip "*::"
542                                expr_struct.path.segments.last()
543                                    .map(|seg| seg.ident.to_string() == target_name)
544                                    .unwrap_or(false)
545                            } else {
546                                // Exact path match: View::Rectangle
547                                let path_str = expr_struct.path.segments.iter()
548                                    .map(|seg| seg.ident.to_string())
549                                    .collect::<Vec<_>>()
550                                    .join("::");
551                                path_str == self.struct_name
552                            }
553                        } else {
554                            // No :: in pattern - only match pure struct literals (no path qualifier)
555                            expr_struct.path.segments.len() == 1
556                                && expr_struct.path.segments.last()
557                                    .map(|seg| seg.ident.to_string() == self.struct_name)
558                                    .unwrap_or(false)
559                        }
560                    };
561
562                    if matches {
563                        self.backups.push(BackupNode {
564                            node_type: "ExprStruct".to_string(),
565                            identifier: format!("{}#{}", self.struct_name, self.counter),
566                            original_content: expr_struct.to_token_stream().to_string(),
567                            location: NodeLocation {
568                                line: 0, // We don't have precise location info in visitor
569                                column: 0,
570                                end_line: 0,
571                                end_column: 0,
572                            },
573                        });
574                        self.counter += 1;
575                    }
576                }
577                syn::visit::visit_expr(self, node);
578            }
579        }
580
581        let mut collector = LiteralCollector {
582            struct_name: struct_name.to_string(),
583            path_resolver,
584            backups: Vec::new(),
585            counter: 0,
586        };
587
588        collector.visit_file(&self.syntax_tree);
589        collector.backups
590    }
591
592    pub(crate) fn add_enum_variant(&mut self, op: &AddEnumVariantOp) -> Result<ModificationResult> {
593        // Find the enum and clone it to avoid borrowing issues
594        let item_enum = self.syntax_tree.items.iter()
595            .find_map(|item| {
596                if let Item::Enum(e) = item {
597                    if e.ident == op.enum_name {
598                        return Some(e.clone());
599                    }
600                }
601                None
602            })
603            .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
604
605        // Check if the enum matches the where filter (if specified)
606        if let Some(ref where_filter) = op.where_filter {
607            if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
608                // Enum doesn't match filter - skip without error
609                return Ok(ModificationResult {
610                    changed: false,
611                    modified_nodes: vec![],
612                });
613            }
614        }
615
616        // Create backup of original enum before modification
617        let backup_node = BackupNode {
618            node_type: "ItemEnum".to_string(),
619            identifier: op.enum_name.clone(),
620            original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
621            location: self.span_to_location(item_enum.span()),
622        };
623
624        let modified = self.insert_enum_variant(&item_enum, op)?;
625
626        Ok(ModificationResult {
627            changed: modified,
628            modified_nodes: if modified { vec![backup_node] } else { vec![] },
629        })
630    }
631    
632    fn insert_enum_variant(&mut self, item_enum: &ItemEnum, op: &AddEnumVariantOp) -> Result<bool> {
633        // Parse the new variant
634        let variant_code = format!("enum Dummy {{ {} }}", op.variant_def);
635        let dummy: ItemEnum = parse_str(&variant_code)
636            .context("Failed to parse variant definition")?;
637
638        let new_variant = dummy.variants.first()
639            .context("No variant found in definition")?
640            .clone();
641
642        // Check if variant already exists
643        let variant_name = new_variant.ident.to_string();
644        if item_enum.variants.iter().any(|v| v.ident.to_string() == variant_name) {
645            // Variant already exists, skip adding
646            return Ok(false);
647        }
648
649        // Determine insertion point
650        let insert_pos = match &op.position {
651            InsertPosition::First => {
652                if let Some(first_var) = item_enum.variants.first() {
653                    self.span_to_byte_offset(first_var.span().start())
654                } else {
655                    let brace_pos = self.span_to_byte_offset(item_enum.brace_token.span.join().start());
656                    brace_pos + 1
657                }
658            }
659            InsertPosition::Last => {
660                if let Some(last_var) = item_enum.variants.last() {
661                    let end = self.span_to_byte_offset(last_var.span().end());
662                    self.find_after_field_end(end)
663                } else {
664                    let brace_pos = self.span_to_byte_offset(item_enum.brace_token.span.join().start());
665                    brace_pos + 1
666                }
667            }
668            InsertPosition::After(name) => {
669                let variant = item_enum.variants.iter()
670                    .find(|v| v.ident.to_string() == *name)
671                    .with_context(|| format!("Variant '{}' not found", name))?;
672                let end = self.span_to_byte_offset(variant.span().end());
673                self.find_after_field_end(end)
674            }
675            InsertPosition::Before(name) => {
676                let variant = item_enum.variants.iter()
677                    .find(|v| v.ident.to_string() == *name)
678                    .with_context(|| format!("Variant '{}' not found", name))?;
679                self.span_to_byte_offset(variant.span().start())
680            }
681        };
682        
683        let indent = self.get_indentation(insert_pos);
684        let variant_str = new_variant.to_token_stream().to_string();
685        let insert_text = format!("\n{}{},", indent, variant_str);
686        
687        self.content.insert_str(insert_pos, &insert_text);
688        Ok(true)
689    }
690
691    fn update_enum_variant(&mut self, op: &UpdateEnumVariantOp) -> Result<ModificationResult> {
692        // Find the enum and clone it
693        let item_enum = self.syntax_tree.items.iter()
694            .find_map(|item| {
695                if let Item::Enum(e) = item {
696                    if e.ident == op.enum_name {
697                        return Some(e.clone());
698                    }
699                }
700                None
701            })
702            .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
703
704        // Check if the enum matches the where filter (if specified)
705        if let Some(ref where_filter) = op.where_filter {
706            if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
707                // Enum doesn't match filter - skip without error
708                return Ok(ModificationResult {
709                    changed: false,
710                    modified_nodes: vec![],
711                });
712            }
713        }
714
715        // Create backup of original enum before modification
716        let backup_node = BackupNode {
717            node_type: "ItemEnum".to_string(),
718            identifier: op.enum_name.clone(),
719            original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
720            location: self.span_to_location(item_enum.span()),
721        };
722
723        // Parse the new variant to get its name
724        let variant_code = format!("enum Dummy {{ {} }}", op.variant_def);
725        let dummy: ItemEnum = parse_str(&variant_code)
726            .context("Failed to parse variant definition")?;
727
728        let new_variant = dummy.variants.first()
729            .context("No variant found in definition")?
730            .clone();
731
732        let variant_name = new_variant.ident.to_string();
733
734        // Find the existing variant
735        let existing_variant = item_enum.variants.iter()
736            .find(|v| v.ident.to_string() == variant_name)
737            .ok_or_else(|| anyhow::anyhow!("Variant '{}' not found in enum '{}'", variant_name, op.enum_name))?;
738
739        // Get the span
740        let start = self.span_to_byte_offset(existing_variant.span().start());
741        let end = self.span_to_byte_offset(existing_variant.span().end());
742
743        // Format and replace
744        let variant_str = new_variant.to_token_stream().to_string();
745        self.content.replace_range(start..end, &variant_str);
746
747        Ok(ModificationResult {
748            changed: true,
749            modified_nodes: vec![backup_node],
750        })
751    }
752
753    pub(crate) fn remove_enum_variant(&mut self, op: &RemoveEnumVariantOp) -> Result<ModificationResult> {
754        // Find the enum
755        let item_enum = self.syntax_tree.items.iter()
756            .find_map(|item| {
757                if let Item::Enum(e) = item {
758                    if e.ident == op.enum_name {
759                        return Some(e.clone());
760                    }
761                }
762                None
763            })
764            .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
765
766        // Check if the enum matches the where filter (if specified)
767        if let Some(ref where_filter) = op.where_filter {
768            if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
769                // Enum doesn't match filter - skip without error
770                return Ok(ModificationResult {
771                    changed: false,
772                    modified_nodes: vec![],
773                });
774            }
775        }
776
777        // Create backup of original enum before modification
778        let backup_node = BackupNode {
779            node_type: "ItemEnum".to_string(),
780            identifier: op.enum_name.clone(),
781            original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
782            location: self.span_to_location(item_enum.span()),
783        };
784
785        // Find the variant to remove
786        let variant_to_remove = item_enum.variants.iter()
787            .find(|v| v.ident.to_string() == op.variant_name)
788            .ok_or_else(|| anyhow::anyhow!("Variant '{}' not found in enum '{}'", op.variant_name, op.enum_name))?;
789
790        // Get the span including comma
791        let start = self.span_to_byte_offset(variant_to_remove.span().start());
792        let mut end = self.span_to_byte_offset(variant_to_remove.span().end());
793
794        // Find and include the comma and trailing whitespace
795        while end < self.content.len() {
796            match self.content.as_bytes()[end] as char {
797                ',' => {
798                    end += 1;
799                    if end < self.content.len() && self.content.as_bytes()[end] == b'\n' {
800                        end += 1;
801                    }
802                    break;
803                }
804                ' ' | '\t' => end += 1,
805                '\n' => {
806                    end += 1;
807                    break;
808                }
809                _ => break,
810            }
811        }
812
813        // Remove leading whitespace on the line
814        let mut line_start = start;
815        while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
816            line_start -= 1;
817        }
818
819        let before_variant = &self.content[line_start..start];
820        if before_variant.trim().is_empty() {
821            self.content.replace_range(line_start..end, "");
822        } else {
823            self.content.replace_range(start..end, "");
824        }
825
826        Ok(ModificationResult {
827            changed: true,
828            modified_nodes: vec![backup_node],
829        })
830    }
831
832    pub(crate) fn add_match_arm(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
833        if op.auto_detect {
834            // Auto-detect mode: find all missing enum variants
835            self.add_missing_match_arms(op)
836        } else {
837            // Normal mode: add a single match arm
838            self.add_single_match_arm(op)
839        }
840    }
841
842    fn add_single_match_arm(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
843        // Parse the pattern and body by creating a dummy match expression
844        let dummy_match = format!("match () {{ {} => {}, }}", op.pattern, op.body);
845        let expr: syn::Expr = parse_str(&dummy_match)
846            .with_context(|| format!("Failed to parse pattern/body: {} => {}", op.pattern, op.body))?;
847
848        // Extract the arm from the dummy match
849        let arm = if let syn::Expr::Match(match_expr) = expr {
850            match_expr.arms.into_iter().next()
851                .context("Failed to extract arm from dummy match")?
852        } else {
853            anyhow::bail!("Expected match expression");
854        };
855
856        // Collect backup of function before modification
857        let backup_node = if let Some(ref fn_name) = op.function_name {
858            self.get_function_backup(fn_name)?
859        } else {
860            // If no function specified, we'll backup all modified functions later
861            // For now, create a generic backup
862            BackupNode {
863                node_type: "Unknown".to_string(),
864                identifier: "match_expression".to_string(),
865                original_content: String::new(),
866                location: NodeLocation {
867                    line: 0,
868                    column: 0,
869                    end_line: 0,
870                    end_column: 0,
871                },
872            }
873        };
874
875        // Find and modify match expressions
876        let mut visitor = MatchArmAdder {
877            target_function: op.function_name.clone(),
878            arm_to_add: arm,
879            modified: false,
880            current_function: None,
881            modified_function: None,
882        };
883
884        visitor.visit_file_mut(&mut self.syntax_tree);
885
886        if visitor.modified {
887            // Replace just the modified function
888            self.replace_modified_functions(&visitor.modified_function)?;
889            Ok(ModificationResult {
890                changed: true,
891                modified_nodes: vec![backup_node],
892            })
893        } else {
894            Ok(ModificationResult {
895                changed: false,
896                modified_nodes: vec![],
897            })
898        }
899    }
900
901    /// Format a single item to string using prettyplease
902    fn unparse_item(&self, item: &Item) -> String {
903        let temp_file = syn::File {
904            shebang: None,
905            attrs: Vec::new(),
906            items: vec![item.clone()],
907        };
908        prettyplease::unparse(&temp_file).trim().to_string()
909    }
910
911    /// Get backup of a function before modification
912    fn get_function_backup(&self, fn_name: &str) -> Result<BackupNode> {
913        for item in &self.syntax_tree.items {
914            if let Item::Fn(f) = item {
915                if f.sig.ident == fn_name {
916                    return Ok(BackupNode {
917                        node_type: "ItemFn".to_string(),
918                        identifier: fn_name.to_string(),
919                        original_content: self.unparse_item(&Item::Fn(f.clone())),
920                        location: self.span_to_location(f.span()),
921                    });
922                }
923            }
924        }
925        anyhow::bail!("Function '{}' not found", fn_name)
926    }
927
928    fn add_missing_match_arms(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
929        // Get the enum name
930        let enum_name = op.enum_name.as_ref()
931            .ok_or_else(|| anyhow::anyhow!("enum_name is required for auto-detect"))?;
932
933        // Find all enum variants
934        let enum_variants = self.find_enum_variants(enum_name)?;
935
936        if enum_variants.is_empty() {
937            anyhow::bail!("Enum '{}' not found or has no variants", enum_name);
938        }
939
940        // Find existing match arms
941        let existing_patterns = self.find_existing_match_patterns(&op.function_name);
942
943        // Determine missing variants
944        let mut missing_variants = Vec::new();
945        for variant in &enum_variants {
946            let pattern = format!("{}::{}", enum_name, variant);
947            let pattern_normalized = pattern.replace(" ", "");
948
949            let exists = existing_patterns.iter().any(|p| {
950                p.replace(" ", "") == pattern_normalized
951            });
952
953            if !exists {
954                missing_variants.push(variant.clone());
955            }
956        }
957
958        if missing_variants.is_empty() {
959            println!("All enum variants already covered in match expressions");
960            return Ok(ModificationResult {
961                changed: false,
962                modified_nodes: vec![],
963            });
964        }
965
966        // Get backup of function before modification
967        let backup_node = if let Some(ref fn_name) = op.function_name {
968            self.get_function_backup(fn_name)?
969        } else {
970            BackupNode {
971                node_type: "Unknown".to_string(),
972                identifier: "match_expression".to_string(),
973                original_content: String::new(),
974                location: NodeLocation {
975                    line: 0,
976                    column: 0,
977                    end_line: 0,
978                    end_column: 0,
979                },
980            }
981        };
982
983        // Add ALL missing match arms in one pass using a visitor
984        let mut arms_to_add = Vec::new();
985        for variant in &missing_variants {
986            let pattern = format!("{}::{}", enum_name, variant);
987            let dummy_match = format!("match () {{ {} => {}, }}", pattern, op.body);
988            let expr: syn::Expr = parse_str(&dummy_match)
989                .with_context(|| format!("Failed to parse pattern/body: {} => {}", pattern, op.body))?;
990
991            if let syn::Expr::Match(match_expr) = expr {
992                if let Some(arm) = match_expr.arms.into_iter().next() {
993                    arms_to_add.push((pattern.clone(), arm));
994                }
995            }
996        }
997
998        // Find and modify match expressions with all arms at once
999        let mut visitor = MultiMatchArmAdder {
1000            target_function: op.function_name.clone(),
1001            arms_to_add,
1002            modified: false,
1003            current_function: None,
1004            modified_function: None,
1005        };
1006
1007        visitor.visit_file_mut(&mut self.syntax_tree);
1008
1009        if visitor.modified {
1010            // Print what was added
1011            for variant in &missing_variants {
1012                println!("Added match arm for: {}::{}", enum_name, variant);
1013            }
1014
1015            // Replace just the modified function
1016            self.replace_modified_functions(&visitor.modified_function)?;
1017            Ok(ModificationResult {
1018                changed: true,
1019                modified_nodes: vec![backup_node],
1020            })
1021        } else {
1022            Ok(ModificationResult {
1023                changed: false,
1024                modified_nodes: vec![],
1025            })
1026        }
1027    }
1028
1029    fn find_enum_variants(&self, enum_name: &str) -> Result<Vec<String>> {
1030        // Find the enum in the syntax tree
1031        for item in &self.syntax_tree.items {
1032            if let Item::Enum(e) = item {
1033                if e.ident == enum_name {
1034                    let variants: Vec<String> = e.variants.iter()
1035                        .map(|v| v.ident.to_string())
1036                        .collect();
1037                    return Ok(variants);
1038                }
1039            }
1040        }
1041
1042        Ok(Vec::new())
1043    }
1044
1045    fn find_existing_match_patterns(&self, function_name: &Option<String>) -> Vec<String> {
1046        use syn::visit::Visit;
1047
1048        struct PatternCollector {
1049            target_function: Option<String>,
1050            current_function: Option<String>,
1051            patterns: Vec<String>,
1052        }
1053
1054        impl<'ast> Visit<'ast> for PatternCollector {
1055            fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
1056                let prev_fn = self.current_function.clone();
1057                self.current_function = Some(node.sig.ident.to_string());
1058                syn::visit::visit_item_fn(self, node);
1059                self.current_function = prev_fn;
1060            }
1061
1062            fn visit_expr_match(&mut self, node: &'ast ExprMatch) {
1063                // Check if we're in the right function (if specified)
1064                if let Some(ref target) = self.target_function {
1065                    if self.current_function.as_ref() != Some(target) {
1066                        syn::visit::visit_expr_match(self, node);
1067                        return;
1068                    }
1069                }
1070
1071                // Collect all patterns
1072                for arm in &node.arms {
1073                    self.patterns.push(arm.pat.to_token_stream().to_string());
1074                }
1075
1076                syn::visit::visit_expr_match(self, node);
1077            }
1078        }
1079
1080        let mut collector = PatternCollector {
1081            target_function: function_name.clone(),
1082            current_function: None,
1083            patterns: Vec::new(),
1084        };
1085
1086        collector.visit_file(&self.syntax_tree);
1087        collector.patterns
1088    }
1089
1090    pub(crate) fn update_match_arm(&mut self, op: &UpdateMatchArmOp) -> Result<ModificationResult> {
1091        // Get backup of function before modification
1092        let backup_node = if let Some(ref fn_name) = op.function_name {
1093            self.get_function_backup(fn_name)?
1094        } else {
1095            BackupNode {
1096                node_type: "Unknown".to_string(),
1097                identifier: "match_expression".to_string(),
1098                original_content: String::new(),
1099                location: NodeLocation {
1100                    line: 0,
1101                    column: 0,
1102                    end_line: 0,
1103                    end_column: 0,
1104                },
1105            }
1106        };
1107
1108        // Parse the new body
1109        let new_body: syn::Expr = parse_str(&op.new_body)
1110            .with_context(|| format!("Failed to parse new body: {}", op.new_body))?;
1111
1112        // Find and modify match expressions
1113        let mut visitor = MatchArmUpdater {
1114            target_function: op.function_name.clone(),
1115            pattern_to_match: op.pattern.clone(),
1116            new_body,
1117            modified: false,
1118            current_function: None,
1119            modified_function: None,
1120        };
1121
1122        visitor.visit_file_mut(&mut self.syntax_tree);
1123
1124        if visitor.modified {
1125            // Replace just the modified function
1126            self.replace_modified_functions(&visitor.modified_function)?;
1127            Ok(ModificationResult {
1128                changed: true,
1129                modified_nodes: vec![backup_node],
1130            })
1131        } else {
1132            anyhow::bail!("Pattern '{}' not found in any match expression", op.pattern)
1133        }
1134    }
1135
1136    pub(crate) fn remove_match_arm(&mut self, op: &RemoveMatchArmOp) -> Result<ModificationResult> {
1137        // Get backup of function before modification
1138        let backup_node = if let Some(ref fn_name) = op.function_name {
1139            self.get_function_backup(fn_name)?
1140        } else {
1141            BackupNode {
1142                node_type: "Unknown".to_string(),
1143                identifier: "match_expression".to_string(),
1144                original_content: String::new(),
1145                location: NodeLocation {
1146                    line: 0,
1147                    column: 0,
1148                    end_line: 0,
1149                    end_column: 0,
1150                },
1151            }
1152        };
1153
1154        // Find and modify match expressions
1155        let mut visitor = MatchArmRemover {
1156            target_function: op.function_name.clone(),
1157            pattern_to_remove: op.pattern.clone(),
1158            modified: false,
1159            current_function: None,
1160            modified_function: None,
1161        };
1162
1163        visitor.visit_file_mut(&mut self.syntax_tree);
1164
1165        if visitor.modified {
1166            // Replace just the modified function
1167            self.replace_modified_functions(&visitor.modified_function)?;
1168            Ok(ModificationResult {
1169                changed: true,
1170                modified_nodes: vec![backup_node],
1171            })
1172        } else {
1173            anyhow::bail!("Pattern '{}' not found in any match expression", op.pattern)
1174        }
1175    }
1176
1177    pub(crate) fn add_impl_method(&mut self, op: &AddImplMethodOp) -> Result<ModificationResult> {
1178        // Parse the method definition
1179        let method_code = format!("impl Dummy {{ {} }}", op.method_def);
1180        let dummy: syn::ItemImpl = parse_str(&method_code)
1181            .context("Failed to parse method definition")?;
1182
1183        let new_method = dummy.items.first()
1184            .context("No method found in definition")?
1185            .clone();
1186
1187        // Get the method name for idempotency check
1188        let method_name = match &new_method {
1189            syn::ImplItem::Fn(f) => f.sig.ident.to_string(),
1190            _ => anyhow::bail!("Only method definitions are supported"),
1191        };
1192
1193        // Find the impl block
1194        let impl_index = self.syntax_tree.items.iter().position(|item| {
1195            if let Item::Impl(impl_block) = item {
1196                // Check if this is the right impl block
1197                if let syn::Type::Path(type_path) = &*impl_block.self_ty {
1198                    if let Some(segment) = type_path.path.segments.last() {
1199                        return segment.ident == op.target;
1200                    }
1201                }
1202            }
1203            false
1204        }).ok_or_else(|| anyhow::anyhow!("impl block for '{}' not found", op.target))?;
1205
1206        // Check if method already exists (idempotent)
1207        let impl_block = match &self.syntax_tree.items[impl_index] {
1208            Item::Impl(i) => i,
1209            _ => unreachable!(),
1210        };
1211
1212        let method_exists = impl_block.items.iter().any(|item| {
1213            if let syn::ImplItem::Fn(f) = item {
1214                f.sig.ident == method_name
1215            } else {
1216                false
1217            }
1218        });
1219
1220        if method_exists {
1221            return Ok(ModificationResult {
1222                changed: false,
1223                modified_nodes: vec![],
1224            });
1225        }
1226
1227        // Create backup of original impl block before modification
1228        let backup_node = BackupNode {
1229            node_type: "ItemImpl".to_string(),
1230            identifier: op.target.clone(),
1231            original_content: self.unparse_item(&self.syntax_tree.items[impl_index].clone()),
1232            location: self.span_to_location(impl_block.span()),
1233        };
1234
1235        // Get the span before modification
1236        let impl_span = impl_block.span();
1237
1238        // Add the method to the impl block
1239        match &mut self.syntax_tree.items[impl_index] {
1240            Item::Impl(impl_block) => {
1241                // Add based on position
1242                match &op.position {
1243                    InsertPosition::First => {
1244                        impl_block.items.insert(0, new_method);
1245                    }
1246                    InsertPosition::Last => {
1247                        impl_block.items.push(new_method);
1248                    }
1249                    InsertPosition::After(name) => {
1250                        let pos = impl_block.items.iter().position(|item| {
1251                            if let syn::ImplItem::Fn(f) = item {
1252                                f.sig.ident == name
1253                            } else {
1254                                false
1255                            }
1256                        }).with_context(|| format!("Method '{}' not found", name))?;
1257                        impl_block.items.insert(pos + 1, new_method);
1258                    }
1259                    InsertPosition::Before(name) => {
1260                        let pos = impl_block.items.iter().position(|item| {
1261                            if let syn::ImplItem::Fn(f) = item {
1262                                f.sig.ident == name
1263                            } else {
1264                                false
1265                            }
1266                        }).with_context(|| format!("Method '{}' not found", name))?;
1267                        impl_block.items.insert(pos, new_method);
1268                    }
1269                }
1270            }
1271            _ => unreachable!(),
1272        }
1273
1274        // Use prettyplease to format just this impl block
1275        self.replace_formatted_item(impl_index, impl_span)?;
1276
1277        Ok(ModificationResult {
1278            changed: true,
1279            modified_nodes: vec![backup_node],
1280        })
1281    }
1282
1283    pub(crate) fn add_use_statement(&mut self, op: &AddUseStatementOp) -> Result<ModificationResult> {
1284        // Parse the use statement
1285        let use_code = format!("use {};", op.use_path);
1286        let use_item: syn::ItemUse = parse_str(&use_code)
1287            .context("Failed to parse use statement")?;
1288
1289        // Check if this use statement already exists (idempotent)
1290        let use_exists = self.syntax_tree.items.iter().any(|item| {
1291            if let Item::Use(existing_use) = item {
1292                // Compare the use trees
1293                existing_use.tree.to_token_stream().to_string() ==
1294                    use_item.tree.to_token_stream().to_string()
1295            } else {
1296                false
1297            }
1298        });
1299
1300        if use_exists {
1301            return Ok(ModificationResult {
1302                changed: false,
1303                modified_nodes: vec![],
1304            });
1305        }
1306
1307        // Create a simple backup for use statements (track by line position)
1308        let backup_node = BackupNode {
1309            node_type: "ItemUse".to_string(),
1310            identifier: op.use_path.clone(),
1311            original_content: format!("use {};", op.use_path),
1312            location: NodeLocation {
1313                line: 0,
1314                column: 0,
1315                end_line: 0,
1316                end_column: 0,
1317            },
1318        };
1319
1320        // Find the position to insert the use statement
1321        let insert_index = match &op.position {
1322            InsertPosition::First => 0,
1323            InsertPosition::Last => {
1324                // Find the last use statement
1325                self.syntax_tree.items.iter()
1326                    .rposition(|item| matches!(item, Item::Use(_)))
1327                    .map(|i| i + 1)
1328                    .unwrap_or(0)
1329            }
1330            InsertPosition::After(path) => {
1331                // Find the use statement matching the path
1332                let pos = self.syntax_tree.items.iter().position(|item| {
1333                    if let Item::Use(u) = item {
1334                        u.tree.to_token_stream().to_string().contains(path)
1335                    } else {
1336                        false
1337                    }
1338                }).with_context(|| format!("Use statement for '{}' not found", path))?;
1339                pos + 1
1340            }
1341            InsertPosition::Before(path) => {
1342                // Find the use statement matching the path
1343                self.syntax_tree.items.iter().position(|item| {
1344                    if let Item::Use(u) = item {
1345                        u.tree.to_token_stream().to_string().contains(path)
1346                    } else {
1347                        false
1348                    }
1349                }).with_context(|| format!("Use statement for '{}' not found", path))?
1350            }
1351        };
1352
1353        // Insert the use statement into the AST
1354        self.syntax_tree.items.insert(insert_index, Item::Use(use_item));
1355
1356        // Find the byte position in the source where we need to insert
1357        // We want to insert at the beginning of a line
1358        let insert_line_pos = if insert_index == 0 {
1359            // Insert at very beginning
1360            0
1361        } else {
1362            // Insert after the previous item
1363            let prev_item = &self.syntax_tree.items[insert_index - 1];
1364            let span = prev_item.span();
1365            let end_pos = self.span_to_byte_offset(span.end());
1366
1367            // Find the end of this line (where the newline is)
1368            let mut line_end = end_pos;
1369            while line_end < self.content.len() && self.content.as_bytes()[line_end] != b'\n' {
1370                line_end += 1;
1371            }
1372            // Move past the newline to the start of the next line
1373            if line_end < self.content.len() {
1374                line_end + 1
1375            } else {
1376                // At end of file, add a newline first
1377                self.content.push('\n');
1378                self.content.len()
1379            }
1380        };
1381
1382        // Format the use statement
1383        let use_str = format!("use {};\n", op.use_path);
1384
1385        // Insert the use statement
1386        self.content.insert_str(insert_line_pos, &use_str);
1387
1388        Ok(ModificationResult {
1389            changed: true,
1390            modified_nodes: vec![backup_node],
1391        })
1392    }
1393
1394    pub(crate) fn add_derive(&mut self, op: &AddDeriveOp) -> Result<ModificationResult> {
1395        // Find the target item (struct or enum)
1396        let item_index = self.syntax_tree.items.iter().position(|item| {
1397            match (&op.target_type as &str, item) {
1398                ("struct", Item::Struct(s)) => s.ident == op.target_name,
1399                ("enum", Item::Enum(e)) => e.ident == op.target_name,
1400                _ => false,
1401            }
1402        }).ok_or_else(|| anyhow::anyhow!("{} '{}' not found", op.target_type, op.target_name))?;
1403
1404        // Get the item and check for existing derives
1405        let (existing_derives, item_span, item_attrs) = match &self.syntax_tree.items[item_index] {
1406            Item::Struct(s) => (Self::extract_derives(&s.attrs), s.span(), &s.attrs),
1407            Item::Enum(e) => (Self::extract_derives(&e.attrs), e.span(), &e.attrs),
1408            _ => (Vec::new(), proc_macro2::Span::call_site(), &Vec::new() as &Vec<syn::Attribute>),
1409        };
1410
1411        // Check if the item matches the where filter (if specified)
1412        if let Some(ref where_filter) = op.where_filter {
1413            if !self.matches_where_filter(item_attrs, where_filter)? {
1414                // Item doesn't match filter - skip without error
1415                return Ok(ModificationResult {
1416                    changed: false,
1417                    modified_nodes: vec![],
1418                });
1419            }
1420        }
1421
1422        // Create backup of original item before modification
1423        let backup_node = BackupNode {
1424            node_type: if op.target_type == "struct" { "ItemStruct" } else { "ItemEnum" }.to_string(),
1425            identifier: op.target_name.clone(),
1426            original_content: self.unparse_item(&self.syntax_tree.items[item_index].clone()),
1427            location: self.span_to_location(item_span),
1428        };
1429
1430        // Filter out derives that already exist (idempotent)
1431        let new_derives: Vec<String> = op.derives.iter()
1432            .filter(|d| !existing_derives.contains(&d.to_string()))
1433            .cloned()
1434            .collect();
1435
1436        if new_derives.is_empty() {
1437            // All derives already exist
1438            return Ok(ModificationResult {
1439                changed: false,
1440                modified_nodes: vec![],
1441            });
1442        }
1443
1444        // Combine existing and new derives
1445        let mut all_derives = existing_derives;
1446        all_derives.extend(new_derives);
1447
1448        // Convert to string refs for the update function
1449        let all_derives_refs: Vec<&str> = all_derives.iter().map(|s| s.as_str()).collect();
1450
1451        // Update the AST item's attributes
1452        match &mut self.syntax_tree.items[item_index] {
1453            Item::Struct(s) => {
1454                Self::update_derive_attr(&mut s.attrs, &all_derives_refs)?;
1455            }
1456            Item::Enum(e) => {
1457                Self::update_derive_attr(&mut e.attrs, &all_derives_refs)?;
1458            }
1459            _ => unreachable!(),
1460        }
1461
1462        // Use prettyplease to format just this item
1463        self.replace_formatted_item(item_index, item_span)?;
1464
1465        Ok(ModificationResult {
1466            changed: true,
1467            modified_nodes: vec![backup_node],
1468        })
1469    }
1470
1471    /// Replace an item in the content with a formatted version
1472    fn replace_formatted_item(&mut self, item_index: usize, original_span: Span) -> Result<()> {
1473        // Get the item start and end positions from the original source
1474        let item_start_pos = self.span_to_byte_offset(original_span.start());
1475        let item_end_pos = self.span_to_byte_offset(original_span.end());
1476
1477        // Find the actual start (including attributes)
1478        let mut actual_start = item_start_pos;
1479
1480        // Search backwards for attributes
1481        let mut temp_pos = item_start_pos;
1482        while temp_pos > 0 {
1483            // Move to previous line
1484            temp_pos = temp_pos.saturating_sub(1);
1485            let mut line_start = temp_pos;
1486            while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
1487                line_start -= 1;
1488            }
1489
1490            let line = if temp_pos < self.content.len() {
1491                &self.content[line_start..temp_pos + 1]
1492            } else {
1493                &self.content[line_start..]
1494            };
1495            let trimmed = line.trim();
1496
1497            if trimmed.starts_with("#[") {
1498                actual_start = line_start;
1499                temp_pos = line_start;
1500            } else if trimmed.is_empty() {
1501                temp_pos = line_start;
1502            } else {
1503                break;
1504            }
1505
1506            if line_start == 0 {
1507                break;
1508            }
1509        }
1510
1511        // Create a temporary file with just this item for pretty formatting
1512        let item_clone = self.syntax_tree.items[item_index].clone();
1513        let temp_file = syn::File {
1514            shebang: None,
1515            attrs: Vec::new(),
1516            items: vec![item_clone],
1517        };
1518
1519        // Format the item using prettyplease
1520        let formatted = prettyplease::unparse(&temp_file);
1521        let formatted = formatted.trim();
1522
1523        // Replace in content
1524        self.content.replace_range(actual_start..item_end_pos, formatted);
1525
1526        Ok(())
1527    }
1528
1529    /// Extract existing derive traits from attributes - returns owned Strings
1530    fn extract_derives(attrs: &[syn::Attribute]) -> Vec<String> {
1531        for attr in attrs {
1532            if attr.path().is_ident("derive") {
1533                if let Ok(syn::Meta::List(meta_list)) = attr.meta.clone().try_into() {
1534                    let tokens_str = meta_list.tokens.to_string();
1535                    return tokens_str
1536                        .split(',')
1537                        .map(|s| s.trim().to_string())
1538                        .collect();
1539                }
1540            }
1541        }
1542        Vec::new()
1543    }
1544
1545    /// Check if an item matches the where filter criteria
1546    /// Supports filters like:
1547    /// - "derives_trait:Clone" - matches if item derives Clone
1548    /// - "derives_trait:Clone,Debug" - matches if item derives Clone OR Debug
1549    fn matches_where_filter(&self, attrs: &[syn::Attribute], where_filter: &str) -> Result<bool> {
1550        // Parse the filter: "derives_trait:Clone,Debug"
1551        if let Some(filter_value) = where_filter.strip_prefix("derives_trait:") {
1552            let required_traits: Vec<&str> = filter_value.split(',').map(|s| s.trim()).collect();
1553            let existing_derives = Self::extract_derives(attrs);
1554
1555            // Check if ANY of the required traits are present
1556            for required_trait in required_traits {
1557                if existing_derives.iter().any(|d| d == required_trait) {
1558                    return Ok(true);
1559                }
1560            }
1561            return Ok(false);
1562        }
1563
1564        // Unknown filter type - default to match (don't break existing behavior)
1565        Ok(true)
1566    }
1567
1568    /// Update or create derive attribute in the attribute list
1569    fn update_derive_attr(attrs: &mut Vec<syn::Attribute>, derives: &[&str]) -> Result<()> {
1570        let derive_str = derives.join(", ");
1571
1572        // Parse a dummy struct with the derive to extract the attribute
1573        let dummy = format!("#[derive({})]\nstruct Dummy;", derive_str);
1574        let parsed: syn::ItemStruct = parse_str(&dummy)
1575            .context("Failed to parse derive attribute")?;
1576
1577        let new_attr = parsed.attrs.into_iter()
1578            .find(|a| a.path().is_ident("derive"))
1579            .context("Failed to extract derive attribute")?;
1580
1581        // Find existing derive attribute and replace it
1582        if let Some(pos) = attrs.iter().position(|a| a.path().is_ident("derive")) {
1583            attrs[pos] = new_attr;
1584        } else {
1585            // Add new derive attribute at the beginning
1586            attrs.insert(0, new_attr);
1587        }
1588
1589        Ok(())
1590    }
1591
1592    /// Replace the modified function(s) in the content with formatted versions
1593    fn replace_modified_functions(&mut self, modified_function: &Option<String>) -> Result<()> {
1594        // If no specific function was targeted, format the entire file
1595        if modified_function.is_none() {
1596            self.content = prettyplease::unparse(&self.syntax_tree);
1597            return Ok(());
1598        }
1599
1600        // Parse the ORIGINAL content to get the correct spans
1601        let original_syntax_tree: File = syn::parse_str(&self.content)
1602            .context("Failed to re-parse original content")?;
1603
1604        let function_name = modified_function.as_ref().unwrap();
1605
1606        // Find the function in the ORIGINAL syntax tree to get correct byte positions
1607        let original_fn = original_syntax_tree.items.iter()
1608            .find_map(|item| {
1609                if let Item::Fn(f) = item {
1610                    if f.sig.ident == function_name {
1611                        return Some(f.clone());
1612                    }
1613                }
1614                None
1615            })
1616            .ok_or_else(|| anyhow::anyhow!("Function '{}' not found in original", function_name))?;
1617
1618        // Get the span of the original function (these are the correct byte positions)
1619        let start = self.span_to_byte_offset(original_fn.span().start());
1620        let end = self.span_to_byte_offset(original_fn.span().end());
1621
1622        // Find the MODIFIED function in the modified syntax tree
1623        let modified_fn = self.syntax_tree.items.iter()
1624            .find_map(|item| {
1625                if let Item::Fn(f) = item {
1626                    if f.sig.ident == function_name {
1627                        return Some(f.clone());
1628                    }
1629                }
1630                None
1631            })
1632            .ok_or_else(|| anyhow::anyhow!("Function '{}' not found in modified AST", function_name))?;
1633
1634        // Format just the modified function using prettyplease
1635        let dummy_file = syn::File {
1636            shebang: None,
1637            attrs: Vec::new(),
1638            items: vec![Item::Fn(modified_fn)],
1639        };
1640
1641        let formatted_fn = prettyplease::unparse(&dummy_file);
1642
1643        // Extract just the function (remove any extra newlines at start/end)
1644        let formatted_fn = formatted_fn.trim();
1645
1646        // Replace the function in the original content using original spans
1647        self.content.replace_range(start..end, formatted_fn);
1648
1649        Ok(())
1650    }
1651    
1652    fn span_to_byte_offset(&self, pos: LineColumn) -> usize {
1653        let line_idx = pos.line.saturating_sub(1);
1654        if line_idx < self.line_offsets.len() {
1655            self.line_offsets[line_idx] + pos.column
1656        } else {
1657            self.content.len()
1658        }
1659    }
1660    
1661    fn find_after_field_end(&self, pos: usize) -> usize {
1662        // Look for comma or newline after the field
1663        let mut i = pos;
1664        while i < self.content.len() {
1665            match self.content.as_bytes()[i] as char {
1666                ',' => return i + 1,
1667                '\n' => return i + 1,
1668                _ => i += 1,
1669            }
1670        }
1671        pos
1672    }
1673    
1674    fn get_indentation(&self, pos: usize) -> String {
1675        // Find the start of the current line
1676        let mut line_start = pos;
1677        while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
1678            line_start -= 1;
1679        }
1680        
1681        // Count spaces/tabs at the start of the line
1682        let mut indent = String::new();
1683        let mut i = line_start;
1684        while i < self.content.len() {
1685            match self.content.as_bytes()[i] as char {
1686                ' ' | '\t' => {
1687                    indent.push(self.content.as_bytes()[i] as char);
1688                    i += 1;
1689                }
1690                _ => break,
1691            }
1692        }
1693        
1694        // If we're inserting in an empty struct/enum, add default indentation
1695        if indent.is_empty() {
1696            "    ".to_string()
1697        } else {
1698            indent
1699        }
1700    }
1701    
1702    pub fn to_string(&self) -> String {
1703        self.content.clone()
1704    }
1705
1706    /// Inspect and list AST nodes (e.g., struct literals) in the file
1707    pub(crate) fn inspect(&self, node_type: &str, name_filter: Option<&str>) -> Result<Vec<crate::operations::InspectResult>> {
1708        use syn::visit::Visit;
1709        use crate::operations::InspectResult;
1710
1711        let mut results = Vec::new();
1712
1713        match node_type {
1714            "struct-literal" => {
1715                // Find all struct literal expressions
1716                struct StructLiteralVisitor<'a> {
1717                    results: &'a mut Vec<InspectResult>,
1718                    name_filter: Option<&'a str>,
1719                    editor: &'a RustEditor,
1720                }
1721
1722                impl<'ast, 'a> Visit<'ast> for StructLiteralVisitor<'a> {
1723                    fn visit_expr_struct(&mut self, node: &'ast syn::ExprStruct) {
1724                        // Match based on pattern:
1725                        // - "Rectangle" → only Rectangle { ... } (no :: prefix)
1726                        // - "*::Rectangle" → any path ending with Rectangle (View::Rectangle, etc.)
1727                        // - "View::Rectangle" → exact match only View::Rectangle
1728
1729                        let filter = match self.name_filter {
1730                            Some(f) => f,
1731                            None => {
1732                                // No filter - match anything
1733                                let struct_name = node.path.segments.last()
1734                                    .map(|seg| seg.ident.to_string())
1735                                    .unwrap_or_default();
1736
1737                                let snippet = self.editor.format_expr_struct(node);
1738                                let location = self.editor.span_to_location(node.span());
1739
1740                                self.results.push(InspectResult {
1741                                    file_path: String::new(),
1742                                    node_type: "ExprStruct".to_string(),
1743                                    identifier: struct_name,
1744                                    location,
1745                                    snippet,
1746                                });
1747
1748                                syn::visit::visit_expr_struct(self, node);
1749                                return;
1750                            }
1751                        };
1752
1753                        // Check if this struct literal matches the filter pattern
1754                        let matches = if filter.contains("::") {
1755                            // Pattern contains :: - check for exact or wildcard match
1756                            if filter.starts_with("*::") {
1757                                // Wildcard: *::Rectangle matches any path ending with Rectangle
1758                                let target_name = &filter[3..]; // Skip "*::"
1759                                node.path.segments.last()
1760                                    .map(|seg| seg.ident.to_string() == target_name)
1761                                    .unwrap_or(false)
1762                            } else {
1763                                // Exact path match: View::Rectangle
1764                                let path_str = node.path.segments.iter()
1765                                    .map(|seg| seg.ident.to_string())
1766                                    .collect::<Vec<_>>()
1767                                    .join("::");
1768                                path_str == filter
1769                            }
1770                        } else {
1771                            // No :: in pattern - only match pure struct literals (no path qualifier)
1772                            node.path.get_ident()
1773                                .map(|ident| ident.to_string() == filter)
1774                                .unwrap_or(false)
1775                        };
1776
1777                        if !matches {
1778                            syn::visit::visit_expr_struct(self, node);
1779                            return;
1780                        }
1781
1782                        // Get the struct name for the identifier
1783                        let struct_name = node.path.segments.last()
1784                            .map(|seg| seg.ident.to_string())
1785                            .unwrap_or_default();
1786
1787                        // Format the struct literal
1788                        let snippet = self.editor.format_expr_struct(node);
1789                        let location = self.editor.span_to_location(node.span());
1790
1791                        self.results.push(InspectResult {
1792                            file_path: String::new(), // Will be filled in by caller
1793                            node_type: "ExprStruct".to_string(),
1794                            identifier: struct_name,
1795                            location,
1796                            snippet,
1797                        });
1798
1799                        // Continue visiting nested expressions
1800                        syn::visit::visit_expr_struct(self, node);
1801                    }
1802                }
1803
1804                let mut visitor = StructLiteralVisitor {
1805                    results: &mut results,
1806                    name_filter,
1807                    editor: self,
1808                };
1809
1810                // Visit all items in the file
1811                for item in &self.syntax_tree.items {
1812                    syn::visit::visit_item(&mut visitor, item);
1813                }
1814            }
1815            "match-arm" => {
1816                // Find all match arms
1817                struct MatchArmVisitor<'a> {
1818                    results: &'a mut Vec<InspectResult>,
1819                    pattern_filter: Option<&'a str>,
1820                    editor: &'a RustEditor,
1821                }
1822
1823                impl<'ast, 'a> Visit<'ast> for MatchArmVisitor<'a> {
1824                    fn visit_expr_match(&mut self, node: &'ast syn::ExprMatch) {
1825                        // Iterate through all arms in this match expression
1826                        for arm in &node.arms {
1827                            // Convert pattern to string for matching
1828                            let pat = &arm.pat;
1829                            let pattern_str = quote::quote!(#pat).to_string();
1830
1831                            // Apply pattern filter if specified
1832                            if let Some(filter) = self.pattern_filter {
1833                                // Normalize both for comparison (remove spaces)
1834                                let normalized_pattern = pattern_str.replace(" ", "");
1835                                let normalized_filter = filter.replace(" ", "");
1836
1837                                if !normalized_pattern.contains(&normalized_filter) {
1838                                    continue;
1839                                }
1840                            }
1841
1842                            // Format the match arm (pattern => body)
1843                            let snippet = self.editor.format_match_arm(arm);
1844                            let location = self.editor.span_to_location(arm.span());
1845
1846                            self.results.push(InspectResult {
1847                                file_path: String::new(), // Will be filled in by caller
1848                                node_type: "MatchArm".to_string(),
1849                                identifier: pattern_str.replace(" ", ""),
1850                                location,
1851                                snippet,
1852                            });
1853                        }
1854
1855                        // Continue visiting nested expressions
1856                        syn::visit::visit_expr_match(self, node);
1857                    }
1858                }
1859
1860                let mut visitor = MatchArmVisitor {
1861                    results: &mut results,
1862                    pattern_filter: name_filter,
1863                    editor: self,
1864                };
1865
1866                // Visit all items in the file
1867                for item in &self.syntax_tree.items {
1868                    syn::visit::visit_item(&mut visitor, item);
1869                }
1870            }
1871            "enum-usage" => {
1872                // Find all enum variant usages (paths like Operator::Error)
1873                struct EnumUsageVisitor<'a> {
1874                    results: &'a mut Vec<InspectResult>,
1875                    path_filter: Option<&'a str>,
1876                    editor: &'a RustEditor,
1877                }
1878
1879                impl<'ast, 'a> Visit<'ast> for EnumUsageVisitor<'a> {
1880                    fn visit_expr_path(&mut self, node: &'ast syn::ExprPath) {
1881                        // Convert path to string
1882                        let path = &node.path;
1883                        let path_str = quote::quote!(#path).to_string();
1884
1885                        // Apply path filter if specified
1886                        if let Some(filter) = self.path_filter {
1887                            // Normalize both for comparison (remove spaces)
1888                            let normalized_path = path_str.replace(" ", "");
1889                            let normalized_filter = filter.replace(" ", "");
1890
1891                            if !normalized_path.contains(&normalized_filter) {
1892                                syn::visit::visit_expr_path(self, node);
1893                                return;
1894                            }
1895                        }
1896
1897                        // Format the path expression
1898                        let snippet = self.editor.format_expr_path(node);
1899                        let location = self.editor.span_to_location(node.span());
1900
1901                        self.results.push(InspectResult {
1902                            file_path: String::new(), // Will be filled in by caller
1903                            node_type: "ExprPath".to_string(),
1904                            identifier: path_str.replace(" ", ""),
1905                            location,
1906                            snippet,
1907                        });
1908
1909                        // Continue visiting nested expressions
1910                        syn::visit::visit_expr_path(self, node);
1911                    }
1912                }
1913
1914                let mut visitor = EnumUsageVisitor {
1915                    results: &mut results,
1916                    path_filter: name_filter,
1917                    editor: self,
1918                };
1919
1920                // Visit all items in the file
1921                for item in &self.syntax_tree.items {
1922                    syn::visit::visit_item(&mut visitor, item);
1923                }
1924            }
1925            "function-call" => {
1926                // Find all function call expressions
1927                struct FunctionCallVisitor<'a> {
1928                    results: &'a mut Vec<InspectResult>,
1929                    name_filter: Option<&'a str>,
1930                    editor: &'a RustEditor,
1931                }
1932
1933                impl<'ast, 'a> Visit<'ast> for FunctionCallVisitor<'a> {
1934                    fn visit_expr_call(&mut self, node: &'ast syn::ExprCall) {
1935                        // Extract function name from the call expression
1936                        let func_name = if let syn::Expr::Path(expr_path) = &*node.func {
1937                            // Get the last segment of the path as the function name
1938                            expr_path.path.segments.last()
1939                                .map(|seg| seg.ident.to_string())
1940                                .unwrap_or_default()
1941                        } else {
1942                            // For other expression types, use quote to convert to string
1943                            quote::quote!(#node.func).to_string()
1944                        };
1945
1946                        // Apply name filter if specified
1947                        if let Some(filter) = self.name_filter {
1948                            if func_name != filter {
1949                                syn::visit::visit_expr_call(self, node);
1950                                return;
1951                            }
1952                        }
1953
1954                        // Format the function call
1955                        let snippet = self.editor.format_expr_call(node);
1956                        let location = self.editor.span_to_location(node.span());
1957
1958                        self.results.push(InspectResult {
1959                            file_path: String::new(), // Will be filled in by caller
1960                            node_type: "ExprCall".to_string(),
1961                            identifier: func_name,
1962                            location,
1963                            snippet,
1964                        });
1965
1966                        // Continue visiting nested expressions
1967                        syn::visit::visit_expr_call(self, node);
1968                    }
1969                }
1970
1971                let mut visitor = FunctionCallVisitor {
1972                    results: &mut results,
1973                    name_filter,
1974                    editor: self,
1975                };
1976
1977                // Visit all items in the file
1978                for item in &self.syntax_tree.items {
1979                    syn::visit::visit_item(&mut visitor, item);
1980                }
1981            }
1982            "method-call" => {
1983                // Find all method call expressions
1984                struct MethodCallVisitor<'a> {
1985                    results: &'a mut Vec<InspectResult>,
1986                    name_filter: Option<&'a str>,
1987                    editor: &'a RustEditor,
1988                }
1989
1990                impl<'ast, 'a> Visit<'ast> for MethodCallVisitor<'a> {
1991                    fn visit_expr_method_call(&mut self, node: &'ast syn::ExprMethodCall) {
1992                        // Extract method name
1993                        let method_name = node.method.to_string();
1994
1995                        // Apply name filter if specified
1996                        if let Some(filter) = self.name_filter {
1997                            if method_name != filter {
1998                                syn::visit::visit_expr_method_call(self, node);
1999                                return;
2000                            }
2001                        }
2002
2003                        // Format the method call
2004                        let snippet = self.editor.format_expr_method_call(node);
2005                        let location = self.editor.span_to_location(node.span());
2006
2007                        self.results.push(InspectResult {
2008                            file_path: String::new(), // Will be filled in by caller
2009                            node_type: "ExprMethodCall".to_string(),
2010                            identifier: method_name,
2011                            location,
2012                            snippet,
2013                        });
2014
2015                        // Continue visiting nested expressions
2016                        syn::visit::visit_expr_method_call(self, node);
2017                    }
2018                }
2019
2020                let mut visitor = MethodCallVisitor {
2021                    results: &mut results,
2022                    name_filter,
2023                    editor: self,
2024                };
2025
2026                // Visit all items in the file
2027                for item in &self.syntax_tree.items {
2028                    syn::visit::visit_item(&mut visitor, item);
2029                }
2030            }
2031            "identifier" => {
2032                // Find all identifier references
2033                struct IdentifierVisitor<'a> {
2034                    results: &'a mut Vec<InspectResult>,
2035                    name_filter: Option<&'a str>,
2036                    editor: &'a RustEditor,
2037                }
2038
2039                impl<'ast, 'a> Visit<'ast> for IdentifierVisitor<'a> {
2040                    fn visit_ident(&mut self, node: &'ast syn::Ident) {
2041                        // Extract identifier name
2042                        let ident_name = node.to_string();
2043
2044                        // Apply name filter if specified
2045                        if let Some(filter) = self.name_filter {
2046                            if ident_name != filter {
2047                                syn::visit::visit_ident(self, node);
2048                                return;
2049                            }
2050                        }
2051
2052                        // Format the identifier
2053                        let snippet = self.editor.format_ident(node);
2054                        let location = self.editor.span_to_location(node.span());
2055
2056                        self.results.push(InspectResult {
2057                            file_path: String::new(), // Will be filled in by caller
2058                            node_type: "Ident".to_string(),
2059                            identifier: ident_name,
2060                            location,
2061                            snippet,
2062                        });
2063
2064                        // Continue visiting
2065                        syn::visit::visit_ident(self, node);
2066                    }
2067                }
2068
2069                let mut visitor = IdentifierVisitor {
2070                    results: &mut results,
2071                    name_filter,
2072                    editor: self,
2073                };
2074
2075                // Visit all items in the file
2076                for item in &self.syntax_tree.items {
2077                    syn::visit::visit_item(&mut visitor, item);
2078                }
2079            }
2080            "type-ref" => {
2081                // Find all type path usages
2082                struct TypeRefVisitor<'a> {
2083                    results: &'a mut Vec<InspectResult>,
2084                    name_filter: Option<&'a str>,
2085                    editor: &'a RustEditor,
2086                }
2087
2088                impl<'ast, 'a> Visit<'ast> for TypeRefVisitor<'a> {
2089                    fn visit_type_path(&mut self, node: &'ast syn::TypePath) {
2090                        // Extract type name (last segment of path)
2091                        let type_name = node.path.segments.last()
2092                            .map(|seg| seg.ident.to_string())
2093                            .unwrap_or_default();
2094
2095                        // Apply name filter if specified
2096                        if let Some(filter) = self.name_filter {
2097                            if type_name != filter {
2098                                syn::visit::visit_type_path(self, node);
2099                                return;
2100                            }
2101                        }
2102
2103                        // Format the type path
2104                        let snippet = self.editor.format_type_path(node);
2105                        let location = self.editor.span_to_location(node.span());
2106
2107                        // Get full path for identifier
2108                        let path = &node.path;
2109                        let path_str = quote::quote!(#path).to_string();
2110
2111                        self.results.push(InspectResult {
2112                            file_path: String::new(), // Will be filled in by caller
2113                            node_type: "TypePath".to_string(),
2114                            identifier: path_str.replace(" ", ""),
2115                            location,
2116                            snippet,
2117                        });
2118
2119                        // Continue visiting
2120                        syn::visit::visit_type_path(self, node);
2121                    }
2122                }
2123
2124                let mut visitor = TypeRefVisitor {
2125                    results: &mut results,
2126                    name_filter,
2127                    editor: self,
2128                };
2129
2130                // Visit all items in the file
2131                for item in &self.syntax_tree.items {
2132                    syn::visit::visit_item(&mut visitor, item);
2133                }
2134            }
2135            "macro-call" => {
2136                // Find all macro call expressions
2137                struct MacroCallVisitor<'a> {
2138                    results: &'a mut Vec<InspectResult>,
2139                    name_filter: Option<&'a str>,
2140                    editor: &'a RustEditor,
2141                }
2142
2143                impl<'ast, 'a> Visit<'ast> for MacroCallVisitor<'a> {
2144                    fn visit_expr_macro(&mut self, node: &'ast syn::ExprMacro) {
2145                        // Extract macro name from the path
2146                        let macro_name = node.mac.path.segments.last()
2147                            .map(|seg| seg.ident.to_string())
2148                            .unwrap_or_default();
2149
2150                        // Apply name filter if specified
2151                        if let Some(filter) = self.name_filter {
2152                            if macro_name != filter {
2153                                syn::visit::visit_expr_macro(self, node);
2154                                return;
2155                            }
2156                        }
2157
2158                        // Format the macro call
2159                        let snippet = self.editor.format_expr_macro(node);
2160                        let location = self.editor.span_to_location(node.span());
2161
2162                        self.results.push(InspectResult {
2163                            file_path: String::new(), // Will be filled in by caller
2164                            node_type: "ExprMacro".to_string(),
2165                            identifier: macro_name,
2166                            location,
2167                            snippet,
2168                        });
2169
2170                        // Continue visiting nested expressions
2171                        syn::visit::visit_expr_macro(self, node);
2172                    }
2173
2174                    fn visit_stmt(&mut self, node: &'ast syn::Stmt) {
2175                        // Also catch macro calls at statement level (e.g., println! as statement)
2176                        if let syn::Stmt::Macro(macro_stmt) = node {
2177                            let macro_name = macro_stmt.mac.path.segments.last()
2178                                .map(|seg| seg.ident.to_string())
2179                                .unwrap_or_default();
2180
2181                            // Apply name filter if specified
2182                            if let Some(filter) = self.name_filter {
2183                                if macro_name != filter {
2184                                    syn::visit::visit_stmt(self, node);
2185                                    return;
2186                                }
2187                            }
2188
2189                            // Format the macro call
2190                            let snippet = self.editor.format_stmt_macro(macro_stmt);
2191                            let location = self.editor.span_to_location(macro_stmt.span());
2192
2193                            self.results.push(InspectResult {
2194                                file_path: String::new(), // Will be filled in by caller
2195                                node_type: "StmtMacro".to_string(),
2196                                identifier: macro_name,
2197                                location,
2198                                snippet,
2199                            });
2200                        }
2201
2202                        // Continue visiting
2203                        syn::visit::visit_stmt(self, node);
2204                    }
2205                }
2206
2207                let mut visitor = MacroCallVisitor {
2208                    results: &mut results,
2209                    name_filter,
2210                    editor: self,
2211                };
2212
2213                // Visit all items in the file
2214                for item in &self.syntax_tree.items {
2215                    syn::visit::visit_item(&mut visitor, item);
2216                }
2217            }
2218            _ => anyhow::bail!("Unsupported node type: {}", node_type),
2219        }
2220
2221        Ok(results)
2222    }
2223
2224    /// Format an ExprStruct node as a string - extracts original source
2225    fn format_expr_struct(&self, expr: &syn::ExprStruct) -> String {
2226        // Extract the original source code from the file content using the span
2227        let start = self.span_to_byte_offset(expr.span().start());
2228        let end = self.span_to_byte_offset(expr.span().end());
2229
2230        // Get the original text and collapse to single line
2231        let original = &self.content[start..end];
2232
2233        // Replace multiple whitespace/newlines with single space for single-line format
2234        original.split_whitespace().collect::<Vec<_>>().join(" ")
2235    }
2236
2237    /// Format a match arm as a string - extracts original source
2238    fn format_match_arm(&self, arm: &syn::Arm) -> String {
2239        // Extract the original source code from the file content using the span
2240        let start = self.span_to_byte_offset(arm.span().start());
2241        let end = self.span_to_byte_offset(arm.span().end());
2242
2243        // Get the original text and collapse to single line
2244        let original = &self.content[start..end];
2245
2246        // Replace multiple whitespace/newlines with single space for single-line format
2247        original.split_whitespace().collect::<Vec<_>>().join(" ")
2248    }
2249
2250    /// Format an ExprPath node as a string - extracts original source
2251    fn format_expr_path(&self, expr: &syn::ExprPath) -> String {
2252        // Extract the original source code from the file content using the span
2253        let start = self.span_to_byte_offset(expr.span().start());
2254        let end = self.span_to_byte_offset(expr.span().end());
2255
2256        // Get the original text and collapse to single line
2257        let original = &self.content[start..end];
2258
2259        // Replace multiple whitespace/newlines with single space for single-line format
2260        original.split_whitespace().collect::<Vec<_>>().join(" ")
2261    }
2262
2263    /// Format an ExprCall node as a string - extracts original source
2264    fn format_expr_call(&self, expr: &syn::ExprCall) -> String {
2265        // Extract the original source code from the file content using the span
2266        let start = self.span_to_byte_offset(expr.span().start());
2267        let end = self.span_to_byte_offset(expr.span().end());
2268
2269        // Get the original text and collapse to single line
2270        let original = &self.content[start..end];
2271
2272        // Replace multiple whitespace/newlines with single space for single-line format
2273        original.split_whitespace().collect::<Vec<_>>().join(" ")
2274    }
2275
2276    /// Format an ExprMethodCall node as a string - extracts original source
2277    fn format_expr_method_call(&self, expr: &syn::ExprMethodCall) -> String {
2278        // Extract the original source code from the file content using the span
2279        let start = self.span_to_byte_offset(expr.span().start());
2280        let end = self.span_to_byte_offset(expr.span().end());
2281
2282        // Get the original text and collapse to single line
2283        let original = &self.content[start..end];
2284
2285        // Replace multiple whitespace/newlines with single space for single-line format
2286        original.split_whitespace().collect::<Vec<_>>().join(" ")
2287    }
2288
2289    /// Format an Ident node as a string - just return the identifier
2290    fn format_ident(&self, ident: &syn::Ident) -> String {
2291        ident.to_string()
2292    }
2293
2294    /// Format a TypePath node as a string - extracts original source
2295    fn format_type_path(&self, ty: &syn::TypePath) -> String {
2296        // Extract the original source code from the file content using the span
2297        let start = self.span_to_byte_offset(ty.span().start());
2298        let end = self.span_to_byte_offset(ty.span().end());
2299
2300        // Get the original text and collapse to single line
2301        let original = &self.content[start..end];
2302
2303        // Replace multiple whitespace/newlines with single space for single-line format
2304        original.split_whitespace().collect::<Vec<_>>().join(" ")
2305    }
2306
2307    /// Format an ExprMacro node as a string - extracts original source
2308    fn format_expr_macro(&self, expr: &syn::ExprMacro) -> String {
2309        // Extract the original source code from the file content using the span
2310        let start = self.span_to_byte_offset(expr.span().start());
2311        let end = self.span_to_byte_offset(expr.span().end());
2312
2313        // Get the original text and collapse to single line
2314        let original = &self.content[start..end];
2315
2316        // Replace multiple whitespace/newlines with single space for single-line format
2317        original.split_whitespace().collect::<Vec<_>>().join(" ")
2318    }
2319
2320    /// Format a StmtMacro node as a string - extracts original source
2321    fn format_stmt_macro(&self, stmt: &syn::StmtMacro) -> String {
2322        // Extract the original source code from the file content using the span
2323        let start = self.span_to_byte_offset(stmt.span().start());
2324        let end = self.span_to_byte_offset(stmt.span().end());
2325
2326        // Get the original text and collapse to single line
2327        let original = &self.content[start..end];
2328
2329        // Replace multiple whitespace/newlines with single space for single-line format
2330        original.split_whitespace().collect::<Vec<_>>().join(" ")
2331    }
2332
2333    /// Find the index of an item by type and name
2334    #[allow(dead_code)]
2335    pub(crate) fn find_item_index(&self, node_type: &str, name: &str) -> Result<usize> {
2336        for (index, item) in self.syntax_tree.items.iter().enumerate() {
2337            match (node_type, item) {
2338                ("struct", Item::Struct(s)) if s.ident == name => {
2339                    return Ok(index);
2340                }
2341                ("enum", Item::Enum(e)) if e.ident == name => {
2342                    return Ok(index);
2343                }
2344                ("fn", Item::Fn(f)) if f.sig.ident == name => {
2345                    return Ok(index);
2346                }
2347                ("impl", Item::Impl(impl_block)) => {
2348                    // For impl blocks, match on the self_ty
2349                    if let syn::Type::Path(type_path) = &*impl_block.self_ty {
2350                        if let Some(segment) = type_path.path.segments.last() {
2351                            if segment.ident == name {
2352                                return Ok(index);
2353                            }
2354                        }
2355                    }
2356                }
2357                _ => {}
2358            }
2359        }
2360
2361        anyhow::bail!("Item '{}' of type '{}' not found", name, node_type)
2362    }
2363
2364    /// Replace an item at a specific index with a new item
2365    #[allow(dead_code)]
2366    pub(crate) fn replace_item_at_index(&mut self, index: usize, new_item: Item) -> Result<()> {
2367        if index >= self.syntax_tree.items.len() {
2368            anyhow::bail!("Index {} out of bounds", index);
2369        }
2370
2371        // Replace the item in the syntax tree
2372        self.syntax_tree.items[index] = new_item;
2373
2374        // Reformat the entire file using prettyplease
2375        self.content = prettyplease::unparse(&self.syntax_tree);
2376
2377        // Recompute line offsets
2378        self.line_offsets = Self::compute_line_offsets(&self.content);
2379
2380        Ok(())
2381    }
2382
2383    pub fn find_node(&self, node_type: &str, name: &str) -> Result<Vec<NodeLocation>> {
2384        let mut locations = Vec::new();
2385        
2386        for item in &self.syntax_tree.items {
2387            match (node_type, item) {
2388                ("struct", Item::Struct(s)) if s.ident == name => {
2389                    locations.push(self.span_to_location(s.span()));
2390                }
2391                ("enum", Item::Enum(e)) if e.ident == name => {
2392                    locations.push(self.span_to_location(e.span()));
2393                }
2394                ("fn", Item::Fn(f)) if f.sig.ident == name => {
2395                    locations.push(self.span_to_location(f.span()));
2396                }
2397                _ => {}
2398            }
2399        }
2400        
2401        if locations.is_empty() {
2402            anyhow::bail!("Node '{}' of type '{}' not found", name, node_type);
2403        }
2404        
2405        Ok(locations)
2406    }
2407    
2408    fn span_to_location(&self, span: Span) -> NodeLocation {
2409        let start = span.start();
2410        let end = span.end();
2411
2412        NodeLocation {
2413            line: start.line,
2414            column: start.column,
2415            end_line: end.line,
2416            end_column: end.column,
2417        }
2418    }
2419
2420    /// Generic transform operation - find matching nodes and apply action
2421    pub(crate) fn transform(&mut self, op: &crate::operations::TransformOp) -> Result<ModificationResult> {
2422        use crate::operations::{InspectResult, TransformAction};
2423
2424        // First, use inspect to find all matching nodes
2425        let matches = self.inspect(&op.node_type, op.name_filter.as_deref())?;
2426
2427        // Apply content filter if specified
2428        let filtered_matches: Vec<InspectResult> = if let Some(ref content_filter) = op.content_filter {
2429            matches.into_iter()
2430                .filter(|m| m.snippet.contains(content_filter))
2431                .collect()
2432        } else {
2433            matches
2434        };
2435
2436        if filtered_matches.is_empty() {
2437            return Ok(ModificationResult {
2438                changed: false,
2439                modified_nodes: vec![],
2440            });
2441        }
2442
2443        // Now apply the transformation action to each match
2444        // We need to work backwards through the file to avoid offset issues
2445        let mut sorted_matches = filtered_matches;
2446        sorted_matches.sort_by(|a, b| {
2447            b.location.line.cmp(&a.location.line)
2448                .then(b.location.column.cmp(&a.location.column))
2449        });
2450
2451        let mut modified_nodes = Vec::new();
2452
2453        for match_result in &sorted_matches {
2454            // Create backup node
2455            let backup_node = BackupNode {
2456                node_type: match_result.node_type.clone(),
2457                identifier: match_result.identifier.clone(),
2458                original_content: match_result.snippet.clone(),
2459                location: match_result.location.clone(),
2460            };
2461
2462            // Find the byte offsets for this node
2463            let start_offset = self.line_column_to_byte_offset(
2464                match_result.location.line,
2465                match_result.location.column
2466            )?;
2467            let end_offset = self.line_column_to_byte_offset(
2468                match_result.location.end_line,
2469                match_result.location.end_column
2470            )?;
2471
2472            // Extract the original text
2473            let original_text = &self.content[start_offset..end_offset];
2474
2475            // Apply the action
2476            let replacement = match &op.action {
2477                TransformAction::Comment => {
2478                    // Comment out the code
2479                    format!("// {}", original_text.replace("\n", "\n// "))
2480                }
2481                TransformAction::Remove => {
2482                    // Remove the entire node
2483                    String::new()
2484                }
2485                TransformAction::Replace { with } => {
2486                    // Replace with provided code
2487                    with.clone()
2488                }
2489            };
2490
2491            // Replace in content
2492            self.content.replace_range(start_offset..end_offset, &replacement);
2493
2494            // Recompute line offsets after each change
2495            self.line_offsets = Self::compute_line_offsets(&self.content);
2496
2497            modified_nodes.push(backup_node);
2498        }
2499
2500        // Re-parse the content if we made changes
2501        if !modified_nodes.is_empty() {
2502            // Don't reparse for now - we're doing text-level operations
2503            // self.syntax_tree = syn::parse_str(&self.content)
2504            //     .context("Failed to re-parse content after transformation")?;
2505        }
2506
2507        Ok(ModificationResult {
2508            changed: !modified_nodes.is_empty(),
2509            modified_nodes,
2510        })
2511    }
2512
2513    /// Rename an enum variant across the entire file
2514    pub(crate) fn rename_enum_variant(&mut self, op: &crate::operations::RenameEnumVariantOp) -> Result<ModificationResult> {
2515        use crate::operations::EditMode;
2516
2517        // Create a path resolver if a canonical path was provided
2518        let path_resolver = if let Some(enum_path) = &op.enum_path {
2519            let mut resolver = PathResolver::new(enum_path)
2520                .ok_or_else(|| anyhow::anyhow!("Invalid enum path: {}", enum_path))?;
2521
2522            // Scan the file for use statements to build the alias map
2523            resolver.scan_file(&self.syntax_tree);
2524            Some(resolver)
2525        } else {
2526            None
2527        };
2528
2529        match op.edit_mode {
2530            EditMode::Surgical => {
2531                // Use non-mutating visitor to collect replacement locations
2532                use syn::visit::Visit;
2533                use crate::surgical::Replacement;
2534
2535                let mut collector = EnumVariantReplacementCollector {
2536                    enum_name: op.enum_name.clone(),
2537                    old_variant: op.old_variant.clone(),
2538                    new_variant: op.new_variant.clone(),
2539                    path_resolver,
2540                    replacements: Vec::new(),
2541                };
2542
2543                collector.visit_file(&self.syntax_tree);
2544
2545                if collector.replacements.is_empty() {
2546                    return Ok(ModificationResult {
2547                        changed: false,
2548                        modified_nodes: vec![],
2549                    });
2550                }
2551
2552                // Apply surgical edits to original content
2553                self.content = crate::surgical::apply_surgical_edits(&self.content, collector.replacements);
2554
2555                // Recompute line offsets
2556                self.line_offsets = Self::compute_line_offsets(&self.content);
2557
2558                // Re-parse the modified content
2559                self.syntax_tree = syn::parse_str(&self.content)
2560                    .context("Failed to re-parse after surgical edit")?;
2561
2562                let backup_node = BackupNode {
2563                    node_type: "EnumVariantRename".to_string(),
2564                    identifier: format!("{}::{} -> {} (surgical)", op.enum_name, op.old_variant, op.new_variant),
2565                    original_content: format!("Renamed {} to {} in enum {} (surgical mode)", op.old_variant, op.new_variant, op.enum_name),
2566                    location: NodeLocation {
2567                        line: 1,
2568                        column: 0,
2569                        end_line: 1,
2570                        end_column: 0,
2571                    },
2572                };
2573
2574                Ok(ModificationResult {
2575                    changed: true,
2576                    modified_nodes: vec![backup_node],
2577                })
2578            }
2579            EditMode::Reformat => {
2580                // Use mutating visitor (original behavior)
2581                let mut renamer = EnumVariantRenamer {
2582                    enum_name: op.enum_name.clone(),
2583                    old_variant: op.old_variant.clone(),
2584                    new_variant: op.new_variant.clone(),
2585                    path_resolver,
2586                    modified: false,
2587                };
2588
2589                // Visit and mutate the syntax tree
2590                renamer.visit_file_mut(&mut self.syntax_tree);
2591
2592                if !renamer.modified {
2593                    return Ok(ModificationResult {
2594                        changed: false,
2595                        modified_nodes: vec![],
2596                    });
2597                }
2598
2599                // Reformat the entire file using prettyplease
2600                self.content = prettyplease::unparse(&self.syntax_tree);
2601
2602                // Recompute line offsets
2603                self.line_offsets = Self::compute_line_offsets(&self.content);
2604
2605                // Create a backup node for the entire file operation
2606                let backup_node = BackupNode {
2607                    node_type: "EnumVariantRename".to_string(),
2608                    identifier: format!("{}::{} -> {}", op.enum_name, op.old_variant, op.new_variant),
2609                    original_content: format!("Renamed {} to {} in enum {}", op.old_variant, op.new_variant, op.enum_name),
2610                    location: NodeLocation {
2611                        line: 1,
2612                        column: 0,
2613                        end_line: 1,
2614                        end_column: 0,
2615                    },
2616                };
2617
2618                Ok(ModificationResult {
2619                    changed: true,
2620                    modified_nodes: vec![backup_node],
2621                })
2622            }
2623        }
2624    }
2625
2626    /// Rename a function across the entire file
2627    pub(crate) fn rename_function(&mut self, op: &crate::operations::RenameFunctionOp) -> Result<ModificationResult> {
2628        use crate::operations::EditMode;
2629
2630        // Create a path resolver if a canonical path was provided
2631        let path_resolver = if let Some(function_path) = &op.function_path {
2632            let mut resolver = PathResolver::new(function_path)
2633                .ok_or_else(|| anyhow::anyhow!("Invalid function path: {}", function_path))?;
2634
2635            // Scan the file for use statements to build the alias map
2636            resolver.scan_file(&self.syntax_tree);
2637            Some(resolver)
2638        } else {
2639            None
2640        };
2641
2642        match op.edit_mode {
2643            EditMode::Surgical => {
2644                // Use non-mutating visitor to collect replacement locations
2645                use syn::visit::Visit;
2646
2647                let mut collector = FunctionReplacementCollector {
2648                    old_name: op.old_name.clone(),
2649                    new_name: op.new_name.clone(),
2650                    path_resolver,
2651                    replacements: Vec::new(),
2652                };
2653
2654                collector.visit_file(&self.syntax_tree);
2655
2656                if collector.replacements.is_empty() {
2657                    return Ok(ModificationResult {
2658                        changed: false,
2659                        modified_nodes: vec![],
2660                    });
2661                }
2662
2663                // Apply surgical edits to original content
2664                self.content = crate::surgical::apply_surgical_edits(&self.content, collector.replacements);
2665
2666                // Recompute line offsets
2667                self.line_offsets = Self::compute_line_offsets(&self.content);
2668
2669                // Re-parse the modified content
2670                self.syntax_tree = syn::parse_str(&self.content)
2671                    .context("Failed to re-parse after surgical edit")?;
2672
2673                let backup_node = BackupNode {
2674                    node_type: "FunctionRename".to_string(),
2675                    identifier: format!("{} -> {} (surgical)", op.old_name, op.new_name),
2676                    original_content: format!("Renamed {} to {} (surgical mode)", op.old_name, op.new_name),
2677                    location: NodeLocation {
2678                        line: 1,
2679                        column: 0,
2680                        end_line: 1,
2681                        end_column: 0,
2682                    },
2683                };
2684
2685                Ok(ModificationResult {
2686                    changed: true,
2687                    modified_nodes: vec![backup_node],
2688                })
2689            }
2690            EditMode::Reformat => {
2691                // Use mutating visitor (original behavior)
2692                let mut renamer = FunctionRenamer {
2693                    old_name: op.old_name.clone(),
2694                    new_name: op.new_name.clone(),
2695                    path_resolver,
2696                    modified: false,
2697                };
2698
2699                // Visit and mutate the syntax tree
2700                renamer.visit_file_mut(&mut self.syntax_tree);
2701
2702                if !renamer.modified {
2703                    return Ok(ModificationResult {
2704                        changed: false,
2705                        modified_nodes: vec![],
2706                    });
2707                }
2708
2709                // Reformat the entire file using prettyplease
2710                self.content = prettyplease::unparse(&self.syntax_tree);
2711
2712                // Recompute line offsets
2713                self.line_offsets = Self::compute_line_offsets(&self.content);
2714
2715                // Create a backup node for the entire file operation
2716                let backup_node = BackupNode {
2717                    node_type: "FunctionRename".to_string(),
2718                    identifier: format!("{} -> {}", op.old_name, op.new_name),
2719                    original_content: format!("Renamed {} to {}", op.old_name, op.new_name),
2720                    location: NodeLocation {
2721                        line: 1,
2722                        column: 0,
2723                        end_line: 1,
2724                        end_column: 0,
2725                    },
2726                };
2727
2728                Ok(ModificationResult {
2729                    changed: true,
2730                    modified_nodes: vec![backup_node],
2731                })
2732            }
2733        }
2734    }
2735
2736    /// Convert line/column to byte offset
2737    fn line_column_to_byte_offset(&self, line: usize, column: usize) -> Result<usize> {
2738        if line == 0 || line > self.line_offsets.len() {
2739            anyhow::bail!("Line {} out of range", line);
2740        }
2741
2742        let line_start = self.line_offsets[line - 1];
2743        Ok(line_start + column)
2744    }
2745}
2746
2747// Visitor for adding match arms
2748struct MatchArmAdder {
2749    target_function: Option<String>,
2750    arm_to_add: Arm,
2751    modified: bool,
2752    current_function: Option<String>,
2753    modified_function: Option<String>,
2754}
2755
2756impl VisitMut for MatchArmAdder {
2757    fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
2758        let prev_fn = self.current_function.clone();
2759        self.current_function = Some(node.sig.ident.to_string());
2760
2761        // Continue visiting nested items
2762        syn::visit_mut::visit_item_fn_mut(self, node);
2763
2764        self.current_function = prev_fn;
2765    }
2766
2767    fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
2768        // Check if we're in the right function (if specified)
2769        if let Some(ref target) = self.target_function {
2770            if self.current_function.as_ref() != Some(target) {
2771                // Continue visiting nested expressions
2772                syn::visit_mut::visit_expr_match_mut(self, node);
2773                return;
2774            }
2775        }
2776
2777        // Check if the pattern already exists (idempotent)
2778        let pattern_str = self.arm_to_add.pat.to_token_stream().to_string();
2779        let already_exists = node.arms.iter().any(|arm| {
2780            arm.pat.to_token_stream().to_string() == pattern_str
2781        });
2782
2783        if !already_exists {
2784            // Add the arm to the end
2785            node.arms.push(self.arm_to_add.clone());
2786            self.modified = true;
2787            self.modified_function = self.current_function.clone();
2788        }
2789
2790        // Continue visiting nested expressions
2791        syn::visit_mut::visit_expr_match_mut(self, node);
2792    }
2793}
2794
2795// Visitor for updating match arms
2796struct MatchArmUpdater {
2797    target_function: Option<String>,
2798    pattern_to_match: String,
2799    new_body: syn::Expr,
2800    modified: bool,
2801    current_function: Option<String>,
2802    modified_function: Option<String>,
2803}
2804
2805impl VisitMut for MatchArmUpdater {
2806    fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
2807        let prev_fn = self.current_function.clone();
2808        self.current_function = Some(node.sig.ident.to_string());
2809
2810        syn::visit_mut::visit_item_fn_mut(self, node);
2811
2812        self.current_function = prev_fn;
2813    }
2814
2815    fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
2816        // Check if we're in the right function (if specified)
2817        if let Some(ref target) = self.target_function {
2818            if self.current_function.as_ref() != Some(target) {
2819                syn::visit_mut::visit_expr_match_mut(self, node);
2820                return;
2821            }
2822        }
2823
2824        // Find and update the matching arm
2825        for arm in &mut node.arms {
2826            let pattern_str = arm.pat.to_token_stream().to_string();
2827            // Normalize whitespace for comparison
2828            let pattern_normalized = pattern_str.replace(" ", "");
2829            let target_normalized = self.pattern_to_match.replace(" ", "");
2830
2831            if pattern_normalized == target_normalized {
2832                arm.body = Box::new(self.new_body.clone());
2833                self.modified = true;
2834                self.modified_function = self.current_function.clone();
2835                break;
2836            }
2837        }
2838
2839        syn::visit_mut::visit_expr_match_mut(self, node);
2840    }
2841}
2842
2843// Visitor for removing match arms
2844struct MatchArmRemover {
2845    target_function: Option<String>,
2846    pattern_to_remove: String,
2847    modified: bool,
2848    current_function: Option<String>,
2849    modified_function: Option<String>,
2850}
2851
2852impl VisitMut for MatchArmRemover {
2853    fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
2854        let prev_fn = self.current_function.clone();
2855        self.current_function = Some(node.sig.ident.to_string());
2856
2857        syn::visit_mut::visit_item_fn_mut(self, node);
2858
2859        self.current_function = prev_fn;
2860    }
2861
2862    fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
2863        // Check if we're in the right function (if specified)
2864        if let Some(ref target) = self.target_function {
2865            if self.current_function.as_ref() != Some(target) {
2866                syn::visit_mut::visit_expr_match_mut(self, node);
2867                return;
2868            }
2869        }
2870
2871        // Find and remove the matching arm
2872        let mut index_to_remove = None;
2873        for (i, arm) in node.arms.iter().enumerate() {
2874            let pattern_str = arm.pat.to_token_stream().to_string();
2875            // Normalize whitespace for comparison
2876            let pattern_normalized = pattern_str.replace(" ", "");
2877            let target_normalized = self.pattern_to_remove.replace(" ", "");
2878
2879            if pattern_normalized == target_normalized {
2880                index_to_remove = Some(i);
2881                break;
2882            }
2883        }
2884
2885        if let Some(index) = index_to_remove {
2886            node.arms.remove(index);
2887            self.modified = true;
2888            self.modified_function = self.current_function.clone();
2889        }
2890
2891        syn::visit_mut::visit_expr_match_mut(self, node);
2892    }
2893}
2894
2895// Visitor for adding multiple match arms at once (for auto-detect)
2896struct MultiMatchArmAdder {
2897    target_function: Option<String>,
2898    arms_to_add: Vec<(String, Arm)>,  // (pattern_string, arm)
2899    modified: bool,
2900    current_function: Option<String>,
2901    modified_function: Option<String>,
2902}
2903
2904impl VisitMut for MultiMatchArmAdder {
2905    fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
2906        let prev_fn = self.current_function.clone();
2907        self.current_function = Some(node.sig.ident.to_string());
2908
2909        syn::visit_mut::visit_item_fn_mut(self, node);
2910
2911        self.current_function = prev_fn;
2912    }
2913
2914    fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
2915        // Check if we're in the right function (if specified)
2916        if let Some(ref target) = self.target_function {
2917            if self.current_function.as_ref() != Some(target) {
2918                syn::visit_mut::visit_expr_match_mut(self, node);
2919                return;
2920            }
2921        }
2922
2923        // Add all missing arms
2924        for (pattern_str, arm) in &self.arms_to_add {
2925            // Check if the pattern already exists (idempotent)
2926            let already_exists = node.arms.iter().any(|existing_arm| {
2927                existing_arm.pat.to_token_stream().to_string() == *pattern_str
2928            });
2929
2930            if !already_exists {
2931                node.arms.push(arm.clone());
2932                self.modified = true;
2933                self.modified_function = self.current_function.clone();
2934            }
2935        }
2936
2937        syn::visit_mut::visit_expr_match_mut(self, node);
2938    }
2939}
2940
2941// Visitor for adding fields to struct literal expressions
2942struct StructLiteralFieldAdder {
2943    struct_name: String,
2944    field_def: String,
2945    field_name: String,
2946    position: InsertPosition,
2947    path_resolver: Option<PathResolver>,
2948    modified: bool,
2949}
2950
2951impl VisitMut for StructLiteralFieldAdder {
2952    fn visit_expr_mut(&mut self, node: &mut Expr) {
2953        // Check if this is a struct literal expression
2954        if let Expr::Struct(expr_struct) = node {
2955            let is_match = if let Some(resolver) = &self.path_resolver {
2956                // Use PathResolver for safe matching
2957                resolver.matches_target(&expr_struct.path)
2958            } else {
2959                // Fallback to legacy pattern matching
2960                // - "Rectangle" → only Rectangle { ... } (no :: prefix)
2961                // - "*::Rectangle" → any path ending with Rectangle (View::Rectangle, etc.)
2962                // - "View::Rectangle" → exact match only View::Rectangle
2963
2964                if self.struct_name.contains("::") {
2965                    // Pattern contains :: - check for exact or wildcard match
2966                    if self.struct_name.starts_with("*::") {
2967                        // Wildcard: *::Rectangle matches any path ending with Rectangle
2968                        let target_name = &self.struct_name[3..]; // Skip "*::"
2969                        expr_struct.path.segments.last()
2970                            .map(|seg| seg.ident.to_string() == target_name)
2971                            .unwrap_or(false)
2972                    } else {
2973                        // Exact path match: View::Rectangle
2974                        let path_str = expr_struct.path.segments.iter()
2975                            .map(|seg| seg.ident.to_string())
2976                            .collect::<Vec<_>>()
2977                            .join("::");
2978                        path_str == self.struct_name
2979                    }
2980                } else {
2981                    // No :: in pattern - only match pure struct literals (no path qualifier)
2982                    expr_struct.path.segments.len() == 1
2983                        && expr_struct.path.segments.last()
2984                            .map(|seg| seg.ident.to_string())
2985                            .as_ref() == Some(&self.struct_name)
2986                }
2987            };
2988
2989            if is_match {
2990                // Check if field already exists (idempotent)
2991                let field_exists = expr_struct.fields.iter().any(|fv| {
2992                    fv.member.to_token_stream().to_string() == self.field_name
2993                });
2994
2995                if !field_exists {
2996                    // Parse the field value from field_def
2997                    // field_def is like "return_type: None"
2998                    let field_value_code = format!("{{ {} }}", self.field_def);
2999                    if let Ok(expr) = parse_str::<ExprStruct>(&format!("Dummy {}", field_value_code)) {
3000                        if let Some(new_fv) = expr.fields.first() {
3001                            // Determine where to insert
3002                            match &self.position {
3003                                InsertPosition::First => {
3004                                    expr_struct.fields.insert(0, new_fv.clone());
3005                                    self.modified = true;
3006                                }
3007                                InsertPosition::Last => {
3008                                    expr_struct.fields.push(new_fv.clone());
3009                                    self.modified = true;
3010                                }
3011                                InsertPosition::After(after_field) => {
3012                                    // Find the position of the field to insert after
3013                                    if let Some(pos) = expr_struct.fields.iter().position(|fv| {
3014                                        fv.member.to_token_stream().to_string() == *after_field
3015                                    }) {
3016                                        expr_struct.fields.insert(pos + 1, new_fv.clone());
3017                                        self.modified = true;
3018                                    }
3019                                }
3020                                InsertPosition::Before(before_field) => {
3021                                    // Find the position of the field to insert before
3022                                    if let Some(pos) = expr_struct.fields.iter().position(|fv| {
3023                                        fv.member.to_token_stream().to_string() == *before_field
3024                                    }) {
3025                                        expr_struct.fields.insert(pos, new_fv.clone());
3026                                        self.modified = true;
3027                                    }
3028                                }
3029                            }
3030                        }
3031                    }
3032                }
3033            }
3034        }
3035
3036        // IMPORTANT: Visit children AFTER processing this node
3037        // This ensures we traverse into nested expressions
3038        syn::visit_mut::visit_expr_mut(self, node);
3039    }
3040}
3041
3042// Visitor for renaming enum variants
3043struct EnumVariantRenamer {
3044    enum_name: String,
3045    old_variant: String,
3046    new_variant: String,
3047    path_resolver: Option<PathResolver>,
3048    modified: bool,
3049}
3050
3051impl EnumVariantRenamer {
3052    /// Rename a path segment if it matches using path resolution.
3053    ///
3054    /// This method handles various path forms:
3055    /// - Simple paths: `EnumName::Variant`
3056    /// - Qualified paths: `crate::module::EnumName::Variant`
3057    /// - Imported paths: `Variant` (when enum is imported via use statement)
3058    ///
3059    /// When a PathResolver is configured, it validates that paths refer to
3060    /// the correct enum before renaming.
3061    fn rename_path(&mut self, path: &mut syn::Path) {
3062        // Check if the path ends with EnumName::VariantName
3063        let segments: Vec<_> = path.segments.iter().collect();
3064        let len = segments.len();
3065
3066        if len >= 2 {
3067            // Path has at least enum and variant segments
3068            let potential_variant = &segments[len - 1];
3069            let potential_enum = &segments[len - 2];
3070
3071            if potential_enum.ident == self.enum_name
3072                && potential_variant.ident == self.old_variant
3073            {
3074                // Path ends with EnumName::VariantName
3075
3076                // If we have a path resolver, validate the enum path
3077                if let Some(resolver) = &self.path_resolver {
3078                    // Extract just the enum path (everything except the variant)
3079                    let enum_path = syn::Path {
3080                        leading_colon: path.leading_colon,
3081                        segments: path.segments.iter()
3082                            .take(len - 1)
3083                            .cloned()
3084                            .collect(),
3085                    };
3086
3087                    // Only rename if the enum path matches our target
3088                    if resolver.matches_target(&enum_path) {
3089                        path.segments[len - 1].ident = syn::Ident::new(
3090                            &self.new_variant,
3091                            path.segments[len - 1].ident.span()
3092                        );
3093                        self.modified = true;
3094                    }
3095                } else {
3096                    // No resolver - use simple matching (backward compatible)
3097                    // Only match if it's exactly EnumName::Variant (2 segments)
3098                    if len == 2 {
3099                        path.segments[1].ident = syn::Ident::new(
3100                            &self.new_variant,
3101                            path.segments[1].ident.span()
3102                        );
3103                        self.modified = true;
3104                    }
3105                }
3106            }
3107        } else if len == 1 {
3108            // Single segment path - check if it's an imported variant
3109            if segments[0].ident == self.old_variant {
3110                // When using path resolver, we don't rename single-segment paths
3111                // unless they're explicitly imported (which would be unusual for variants)
3112                // For backward compatibility, we still rename them when no resolver is present
3113                if self.path_resolver.is_none() {
3114                    path.segments[0].ident = syn::Ident::new(
3115                        &self.new_variant,
3116                        path.segments[0].ident.span()
3117                    );
3118                    self.modified = true;
3119                }
3120            }
3121        }
3122    }
3123}
3124
3125impl VisitMut for EnumVariantRenamer {
3126    /// Rename in enum definition
3127    fn visit_item_enum_mut(&mut self, node: &mut syn::ItemEnum) {
3128        if node.ident == self.enum_name {
3129            for variant in &mut node.variants {
3130                if variant.ident == self.old_variant {
3131                    variant.ident = syn::Ident::new(&self.new_variant, variant.ident.span());
3132                    self.modified = true;
3133                }
3134            }
3135        }
3136
3137        // Continue visiting nested items
3138        syn::visit_mut::visit_item_enum_mut(self, node);
3139    }
3140
3141    /// Rename in patterns (match arms, let bindings, function parameters, etc.)
3142    fn visit_pat_mut(&mut self, pat: &mut syn::Pat) {
3143        match pat {
3144            syn::Pat::TupleStruct(tuple_struct) => {
3145                self.rename_path(&mut tuple_struct.path);
3146            }
3147            syn::Pat::Struct(struct_pat) => {
3148                self.rename_path(&mut struct_pat.path);
3149            }
3150            syn::Pat::Path(path_pat) => {
3151                self.rename_path(&mut path_pat.path);
3152            }
3153            _ => {}
3154        }
3155
3156        // Continue visiting nested patterns
3157        syn::visit_mut::visit_pat_mut(self, pat);
3158    }
3159
3160    /// Rename in expressions (constructor calls, references, etc.)
3161    fn visit_expr_mut(&mut self, expr: &mut syn::Expr) {
3162        match expr {
3163            syn::Expr::Path(expr_path) => {
3164                self.rename_path(&mut expr_path.path);
3165            }
3166            syn::Expr::Call(call) => {
3167                if let syn::Expr::Path(path) = &mut *call.func {
3168                    self.rename_path(&mut path.path);
3169                }
3170            }
3171            syn::Expr::Struct(struct_expr) => {
3172                self.rename_path(&mut struct_expr.path);
3173            }
3174            _ => {}
3175        }
3176
3177        // Continue visiting nested expressions
3178        syn::visit_mut::visit_expr_mut(self, expr);
3179    }
3180}
3181
3182// Non-mutating visitor for collecting replacement locations (surgical mode)
3183struct EnumVariantReplacementCollector {
3184    enum_name: String,
3185    old_variant: String,
3186    new_variant: String,
3187    path_resolver: Option<PathResolver>,
3188    replacements: Vec<crate::surgical::Replacement>,
3189}
3190
3191impl EnumVariantReplacementCollector {
3192    /// Check if a path matches and collect replacement if it does
3193    fn collect_path_replacement(&mut self, path: &syn::Path) {
3194        let segments: Vec<_> = path.segments.iter().collect();
3195        let len = segments.len();
3196
3197        if len >= 2 {
3198            let potential_variant = &segments[len - 1];
3199            let potential_enum = &segments[len - 2];
3200
3201            if potential_enum.ident == self.enum_name
3202                && potential_variant.ident == self.old_variant
3203            {
3204                // Path ends with EnumName::VariantName
3205
3206                // Validate with path resolver if available
3207                let should_rename = if let Some(resolver) = &self.path_resolver {
3208                    let enum_path = syn::Path {
3209                        leading_colon: path.leading_colon,
3210                        segments: path.segments.iter()
3211                            .take(len - 1)
3212                            .cloned()
3213                            .collect(),
3214                    };
3215                    resolver.matches_target(&enum_path)
3216                } else {
3217                    // No resolver - only match exactly 2 segments
3218                    len == 2
3219                };
3220
3221                if should_rename {
3222                    let span = potential_variant.ident.span();
3223                    let start = span.start();
3224                    let end = span.end();
3225
3226                    self.replacements.push(crate::surgical::Replacement::new(
3227                        start,
3228                        end,
3229                        self.new_variant.clone(),
3230                    ));
3231                }
3232            }
3233        } else if len == 1 && self.path_resolver.is_none() {
3234            // Single segment - only without path resolver (backward compat)
3235            if segments[0].ident == self.old_variant {
3236                let span = segments[0].ident.span();
3237                let start = span.start();
3238                let end = span.end();
3239
3240                self.replacements.push(crate::surgical::Replacement::new(
3241                    start,
3242                    end,
3243                    self.new_variant.clone(),
3244                ));
3245            }
3246        }
3247    }
3248}
3249
3250impl<'ast> syn::visit::Visit<'ast> for EnumVariantReplacementCollector {
3251    fn visit_item_enum(&mut self, node: &'ast syn::ItemEnum) {
3252        if node.ident == self.enum_name {
3253            for variant in &node.variants {
3254                if variant.ident == self.old_variant {
3255                    let span = variant.ident.span();
3256                    let start = span.start();
3257                    let end = span.end();
3258
3259                    self.replacements.push(crate::surgical::Replacement::new(
3260                        start,
3261                        end,
3262                        self.new_variant.clone(),
3263                    ));
3264                }
3265            }
3266        }
3267        syn::visit::visit_item_enum(self, node);
3268    }
3269
3270    fn visit_pat(&mut self, pat: &'ast syn::Pat) {
3271        match pat {
3272            syn::Pat::TupleStruct(tuple_struct) => {
3273                self.collect_path_replacement(&tuple_struct.path);
3274            }
3275            syn::Pat::Struct(struct_pat) => {
3276                self.collect_path_replacement(&struct_pat.path);
3277            }
3278            syn::Pat::Path(path_pat) => {
3279                self.collect_path_replacement(&path_pat.path);
3280            }
3281            _ => {}
3282        }
3283        syn::visit::visit_pat(self, pat);
3284    }
3285
3286    fn visit_expr(&mut self, expr: &'ast syn::Expr) {
3287        match expr {
3288            syn::Expr::Path(expr_path) => {
3289                self.collect_path_replacement(&expr_path.path);
3290            }
3291            syn::Expr::Call(call) => {
3292                if let syn::Expr::Path(path) = &*call.func {
3293                    self.collect_path_replacement(&path.path);
3294                }
3295            }
3296            syn::Expr::Struct(struct_expr) => {
3297                self.collect_path_replacement(&struct_expr.path);
3298            }
3299            _ => {}
3300        }
3301        syn::visit::visit_expr(self, expr);
3302    }
3303}
3304
3305// Mutating visitor for renaming functions (reformat mode)
3306struct FunctionRenamer {
3307    old_name: String,
3308    new_name: String,
3309    path_resolver: Option<PathResolver>,
3310    modified: bool,
3311}
3312
3313impl FunctionRenamer {
3314    /// Rename a function identifier if it matches
3315    fn rename_ident(&mut self, ident: &mut syn::Ident) {
3316        if ident == &self.old_name {
3317            *ident = syn::Ident::new(&self.new_name, ident.span());
3318            self.modified = true;
3319        }
3320    }
3321
3322    /// Check if a path matches our target function (with path resolution)
3323    fn matches_target_function(&self, path: &syn::Path) -> bool {
3324        if let Some(resolver) = &self.path_resolver {
3325            resolver.matches_target(path)
3326        } else {
3327            // Simple matching: just check if the last segment is our function name
3328            path.segments.len() == 1 && path.segments.last().unwrap().ident == self.old_name
3329        }
3330    }
3331}
3332
3333impl VisitMut for FunctionRenamer {
3334    fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
3335        // Rename function definition
3336        self.rename_ident(&mut node.sig.ident);
3337        syn::visit_mut::visit_item_fn_mut(self, node);
3338    }
3339
3340    fn visit_expr_mut(&mut self, expr: &mut syn::Expr) {
3341        match expr {
3342            syn::Expr::Call(call) => {
3343                // Rename function calls
3344                if let syn::Expr::Path(expr_path) = &mut *call.func {
3345                    if self.matches_target_function(&expr_path.path) {
3346                        if let Some(last_seg) = expr_path.path.segments.last_mut() {
3347                            self.rename_ident(&mut last_seg.ident);
3348                        }
3349                    }
3350                }
3351            }
3352            syn::Expr::Path(expr_path) => {
3353                // Rename function references (not calls)
3354                if self.matches_target_function(&expr_path.path) {
3355                    if let Some(last_seg) = expr_path.path.segments.last_mut() {
3356                        self.rename_ident(&mut last_seg.ident);
3357                    }
3358                }
3359            }
3360            _ => {}
3361        }
3362        syn::visit_mut::visit_expr_mut(self, expr);
3363    }
3364}
3365
3366// Non-mutating visitor for collecting function replacement locations (surgical mode)
3367struct FunctionReplacementCollector {
3368    old_name: String,
3369    new_name: String,
3370    path_resolver: Option<PathResolver>,
3371    replacements: Vec<crate::surgical::Replacement>,
3372}
3373
3374impl FunctionReplacementCollector {
3375    /// Collect replacement for a function identifier
3376    fn collect_replacement(&mut self, ident: &syn::Ident) {
3377        if ident == &self.old_name {
3378            let span = ident.span();
3379            let start = span.start();
3380            let end = span.end();
3381
3382            self.replacements.push(crate::surgical::Replacement::new(
3383                start,
3384                end,
3385                self.new_name.clone(),
3386            ));
3387        }
3388    }
3389
3390    /// Check if a path matches our target function
3391    fn matches_target_function(&self, path: &syn::Path) -> bool {
3392        if let Some(resolver) = &self.path_resolver {
3393            resolver.matches_target(path)
3394        } else {
3395            // Simple matching
3396            path.segments.len() == 1 && path.segments.last().unwrap().ident == self.old_name
3397        }
3398    }
3399}
3400
3401impl<'ast> syn::visit::Visit<'ast> for FunctionReplacementCollector {
3402    fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
3403        // Collect function definition rename
3404        self.collect_replacement(&node.sig.ident);
3405        syn::visit::visit_item_fn(self, node);
3406    }
3407
3408    fn visit_expr(&mut self, expr: &'ast syn::Expr) {
3409        match expr {
3410            syn::Expr::Call(call) => {
3411                // Collect function call renames
3412                if let syn::Expr::Path(expr_path) = &*call.func {
3413                    if self.matches_target_function(&expr_path.path) {
3414                        if let Some(last_seg) = expr_path.path.segments.last() {
3415                            self.collect_replacement(&last_seg.ident);
3416                        }
3417                    }
3418                }
3419                // Visit call arguments but NOT the func (already handled above)
3420                for arg in &call.args {
3421                    syn::visit::visit_expr(self, arg);
3422                }
3423                // Don't call the default visitor which would re-visit call.func
3424                return;
3425            }
3426            syn::Expr::Path(expr_path) => {
3427                // Collect function reference renames (not calls)
3428                if self.matches_target_function(&expr_path.path) {
3429                    if let Some(last_seg) = expr_path.path.segments.last() {
3430                        self.collect_replacement(&last_seg.ident);
3431                    }
3432                }
3433            }
3434            _ => {}
3435        }
3436        syn::visit::visit_expr(self, expr);
3437    }
3438}
3439
3440// ============================================================================
3441// Doc Comment Operations
3442// ============================================================================
3443
3444/// Generate a documentation comment in the specified style
3445fn generate_doc_comment(text: &str, style: &DocCommentStyle) -> String {
3446    match style {
3447        DocCommentStyle::Line => {
3448            // Split by newlines and add /// prefix to each line
3449            text.lines()
3450                .map(|line| {
3451                    if line.trim().is_empty() {
3452                        "///".to_string()
3453                    } else {
3454                        format!("/// {}", line)
3455                    }
3456                })
3457                .collect::<Vec<_>>()
3458                .join("\n")
3459        }
3460        DocCommentStyle::Block => {
3461            // Simple block comment
3462            if text.contains('\n') {
3463                // Multi-line block comment
3464                let lines = text.lines()
3465                    .map(|line| format!(" * {}", line))
3466                    .collect::<Vec<_>>()
3467                    .join("\n");
3468                format!("/**\n{}\n */", lines)
3469            } else {
3470                // Single-line block comment
3471                format!("/** {} */", text)
3472            }
3473        }
3474    }
3475}
3476
3477/// Find the byte position and indentation of a target item
3478struct TargetFinder {
3479    target_type: String,
3480    target_name: String,
3481    found_position: Option<(usize, String)>, // (line_number, indentation)
3482}
3483
3484impl TargetFinder {
3485    fn new(target_type: String, target_name: String) -> Self {
3486        Self {
3487            target_type,
3488            target_name,
3489            found_position: None,
3490        }
3491    }
3492}
3493
3494impl<'ast> syn::visit::Visit<'ast> for TargetFinder {
3495    fn visit_item_struct(&mut self, node: &'ast ItemStruct) {
3496        if self.target_type == "struct" && node.ident.to_string() == self.target_name {
3497            // Use struct_token span to get the line where "struct" keyword appears,
3498            // not the first line of the item (which includes doc comments)
3499            let line = node.struct_token.span.start().line;
3500            self.found_position = Some((line, String::new()));
3501        }
3502        syn::visit::visit_item_struct(self, node);
3503    }
3504
3505    fn visit_item_enum(&mut self, node: &'ast ItemEnum) {
3506        if self.target_type == "enum" && node.ident.to_string() == self.target_name {
3507            // Use enum_token span to get the line where "enum" keyword appears
3508            let line = node.enum_token.span.start().line;
3509            self.found_position = Some((line, String::new()));
3510        }
3511        syn::visit::visit_item_enum(self, node);
3512    }
3513
3514    fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
3515        if self.target_type == "function" && node.sig.ident.to_string() == self.target_name {
3516            // Use fn_token span to get the line where "fn" keyword appears
3517            let line = node.sig.fn_token.span.start().line;
3518            self.found_position = Some((line, String::new()));
3519        }
3520        syn::visit::visit_item_fn(self, node);
3521    }
3522}
3523
3524impl RustEditor {
3525    /// Add a documentation comment to a target item using surgical editing
3526    pub fn add_doc_comment_surgical(
3527        &mut self,
3528        target_type: &str,
3529        target_name: &str,
3530        doc_text: &str,
3531        style: &DocCommentStyle,
3532    ) -> Result<ModificationResult> {
3533        use syn::visit::Visit;
3534
3535        // Find the target item
3536        let mut finder = TargetFinder::new(
3537            target_type.to_string(),
3538            target_name.to_string(),
3539        );
3540        finder.visit_file(&self.syntax_tree);
3541
3542        if let Some((line_num, _indent)) = finder.found_position {
3543            // Lines are 1-indexed from syn, convert to 0-indexed
3544            let line_idx = line_num.saturating_sub(1);
3545
3546            // Generate the doc comment
3547            let comment = generate_doc_comment(doc_text, style);
3548
3549            // Find the actual line in the source
3550            let lines: Vec<&str> = self.content.lines().collect();
3551            if line_idx >= lines.len() {
3552                anyhow::bail!("Target not found at line {}", line_num);
3553            }
3554
3555            let target_line = lines[line_idx].to_string(); // Clone to avoid borrow issues
3556            let target_line_len = target_line.len();
3557
3558            // Detect indentation from the target line
3559            let indent = target_line
3560                .chars()
3561                .take_while(|c| c.is_whitespace())
3562                .collect::<String>();
3563
3564            // Build the new content with comment inserted
3565            let mut new_lines: Vec<String> = lines.iter().map(|s| s.to_string()).collect();
3566
3567            // Insert comment lines before the target
3568            let comment_lines: Vec<String> = comment
3569                .lines()
3570                .map(|line| format!("{}{}", indent, line))
3571                .collect();
3572
3573            // Insert in reverse order to maintain indices
3574            for (i, comment_line) in comment_lines.iter().rev().enumerate() {
3575                new_lines.insert(line_idx, comment_line.clone());
3576            }
3577
3578            // Update content
3579            self.content = new_lines.join("\n");
3580
3581            // Re-parse to update syntax tree
3582            self.syntax_tree = syn::parse_str(&self.content)
3583                .context("Failed to re-parse after adding comment")?;
3584
3585            Ok(ModificationResult {
3586                changed: true,
3587                modified_nodes: vec![BackupNode {
3588                    node_type: target_type.to_string(),
3589                    identifier: target_name.to_string(),
3590                    original_content: target_line,
3591                    location: NodeLocation {
3592                        line: line_num,
3593                        column: 1,
3594                        end_line: line_num,
3595                        end_column: target_line_len,
3596                    },
3597                }],
3598            })
3599        } else {
3600            anyhow::bail!("Target {} '{}' not found", target_type, target_name)
3601        }
3602    }
3603
3604    /// Update an existing documentation comment on a target item using surgical editing
3605    pub fn update_doc_comment_surgical(
3606        &mut self,
3607        target_type: &str,
3608        target_name: &str,
3609        doc_text: &str,
3610        style: &DocCommentStyle,
3611    ) -> Result<ModificationResult> {
3612        use syn::visit::Visit;
3613
3614        // Find the target item
3615        let mut finder = TargetFinder::new(
3616            target_type.to_string(),
3617            target_name.to_string(),
3618        );
3619        finder.visit_file(&self.syntax_tree);
3620
3621        if let Some((line_num, _indent)) = finder.found_position {
3622            // Lines are 1-indexed from syn, convert to 0-indexed
3623            let line_idx = line_num.saturating_sub(1);
3624
3625            // Find the actual line in the source
3626            let lines: Vec<&str> = self.content.lines().collect();
3627            if line_idx >= lines.len() {
3628                anyhow::bail!("Target not found at line {}", line_num);
3629            }
3630
3631            let target_line = lines[line_idx].to_string(); // Clone to avoid borrow issues
3632            let target_line_len = target_line.len();
3633
3634            // Detect indentation from the target line
3635            let indent = target_line
3636                .chars()
3637                .take_while(|c| c.is_whitespace())
3638                .collect::<String>();
3639
3640            // Scan backwards from target to find existing doc comment lines
3641            let mut doc_comment_start = line_idx;
3642            while doc_comment_start > 0 {
3643                let prev_line = lines[doc_comment_start - 1].trim();
3644                if prev_line.starts_with("///") || prev_line.starts_with("//!") ||
3645                   prev_line.starts_with("/**") || prev_line.starts_with("/*!") ||
3646                   (prev_line.starts_with("*") && !prev_line.starts_with("*/")) ||
3647                   prev_line == "*/" {
3648                    doc_comment_start -= 1;
3649                } else {
3650                    break;
3651                }
3652            }
3653
3654            // Build new content with old comments removed and new ones inserted
3655            let mut new_lines: Vec<String> = Vec::new();
3656
3657            // Add lines before the doc comments
3658            for i in 0..doc_comment_start {
3659                new_lines.push(lines[i].to_string());
3660            }
3661
3662            // Generate and add new doc comment
3663            let comment = generate_doc_comment(doc_text, style);
3664            let comment_lines: Vec<String> = comment
3665                .lines()
3666                .map(|line| format!("{}{}", indent, line))
3667                .collect();
3668
3669            for comment_line in comment_lines {
3670                new_lines.push(comment_line);
3671            }
3672
3673            // Add remaining lines (from target onwards)
3674            for i in line_idx..lines.len() {
3675                new_lines.push(lines[i].to_string());
3676            }
3677
3678            // Update content
3679            self.content = new_lines.join("\n");
3680
3681            // Re-parse to update syntax tree
3682            self.syntax_tree = syn::parse_str(&self.content)
3683                .context("Failed to re-parse after updating comment")?;
3684
3685            Ok(ModificationResult {
3686                changed: true,
3687                modified_nodes: vec![BackupNode {
3688                    node_type: target_type.to_string(),
3689                    identifier: target_name.to_string(),
3690                    original_content: target_line,
3691                    location: NodeLocation {
3692                        line: line_num,
3693                        column: 1,
3694                        end_line: line_num,
3695                        end_column: target_line_len,
3696                    },
3697                }],
3698            })
3699        } else {
3700            anyhow::bail!("Target {} '{}' not found", target_type, target_name)
3701        }
3702    }
3703
3704    /// Remove a documentation comment from a target item using surgical editing
3705    pub fn remove_doc_comment_surgical(
3706        &mut self,
3707        target_type: &str,
3708        target_name: &str,
3709    ) -> Result<ModificationResult> {
3710        use syn::visit::Visit;
3711
3712        // Find the target item
3713        let mut finder = TargetFinder::new(
3714            target_type.to_string(),
3715            target_name.to_string(),
3716        );
3717        finder.visit_file(&self.syntax_tree);
3718
3719        if let Some((line_num, _indent)) = finder.found_position {
3720            // Lines are 1-indexed from syn, convert to 0-indexed
3721            let line_idx = line_num.saturating_sub(1);
3722
3723            // Find the actual line in the source
3724            let lines: Vec<&str> = self.content.lines().collect();
3725            if line_idx >= lines.len() {
3726                anyhow::bail!("Target not found at line {}", line_num);
3727            }
3728
3729            let target_line = lines[line_idx].to_string(); // Clone to avoid borrow issues
3730            let target_line_len = target_line.len();
3731
3732            // Scan backwards from target to find existing doc comment lines
3733            let mut doc_comment_start = line_idx;
3734            while doc_comment_start > 0 {
3735                let prev_line = lines[doc_comment_start - 1].trim();
3736                if prev_line.starts_with("///") || prev_line.starts_with("//!") ||
3737                   prev_line.starts_with("/**") || prev_line.starts_with("/*!") ||
3738                   (prev_line.starts_with("*") && !prev_line.starts_with("*/")) ||
3739                   prev_line == "*/" {
3740                    doc_comment_start -= 1;
3741                } else {
3742                    break;
3743                }
3744            }
3745
3746            // Build new content with doc comments removed
3747            let mut new_lines: Vec<String> = Vec::new();
3748
3749            // Add lines before the doc comments
3750            for i in 0..doc_comment_start {
3751                new_lines.push(lines[i].to_string());
3752            }
3753
3754            // Skip the doc comment lines (from doc_comment_start to line_idx)
3755
3756            // Add remaining lines (from target onwards)
3757            for i in line_idx..lines.len() {
3758                new_lines.push(lines[i].to_string());
3759            }
3760
3761            // Update content
3762            self.content = new_lines.join("\n");
3763
3764            // Re-parse to update syntax tree
3765            self.syntax_tree = syn::parse_str(&self.content)
3766                .context("Failed to re-parse after removing comment")?;
3767
3768            Ok(ModificationResult {
3769                changed: true,
3770                modified_nodes: vec![BackupNode {
3771                    node_type: target_type.to_string(),
3772                    identifier: target_name.to_string(),
3773                    original_content: target_line,
3774                    location: NodeLocation {
3775                        line: line_num,
3776                        column: 1,
3777                        end_line: line_num,
3778                        end_column: target_line_len,
3779                    },
3780                }],
3781            })
3782        } else {
3783            anyhow::bail!("Target {} '{}' not found", target_type, target_name)
3784        }
3785    }
3786}