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 prettyplease;
12
13pub struct RustEditor {
14    content: String,
15    syntax_tree: File,
16    line_offsets: Vec<usize>, // Byte offset for each line start
17}
18
19impl RustEditor {
20    pub fn new(content: &str) -> Result<Self> {
21        let syntax_tree: File = syn::parse_str(content)
22            .context("Failed to parse Rust code")?;
23
24        let line_offsets = Self::compute_line_offsets(content);
25
26        Ok(Self {
27            content: content.to_string(),
28            syntax_tree,
29            line_offsets,
30        })
31    }
32
33    /// Format a field without extra spaces (e.g., "pub name: String" not "pub name : String")
34    fn format_field(field: &Field) -> String {
35        let mut result = String::new();
36
37        // Add visibility
38        if let syn::Visibility::Public(_) = field.vis {
39            result.push_str("pub ");
40        }
41
42        // Add field name
43        if let Some(ident) = &field.ident {
44            result.push_str(&ident.to_string());
45        }
46
47        // Add colon and type (no space before colon)
48        result.push_str(": ");
49
50        // Format type without extra spaces
51        let type_str = field.ty.to_token_stream().to_string();
52        let type_str = type_str.replace(" < ", "<").replace(" >", ">");
53        result.push_str(&type_str);
54
55        result
56    }
57    
58    fn compute_line_offsets(content: &str) -> Vec<usize> {
59        let mut offsets = vec![0];
60        for (i, ch) in content.char_indices() {
61            if ch == '\n' {
62                offsets.push(i + 1);
63            }
64        }
65        offsets
66    }
67    
68    pub fn apply_operation(&mut self, op: &Operation) -> Result<ModificationResult> {
69        match op {
70            Operation::AddStructField(op) => self.add_struct_field(op),
71            Operation::UpdateStructField(op) => self.update_struct_field(op),
72            Operation::RemoveStructField(op) => self.remove_struct_field(op),
73            Operation::AddStructLiteralField(op) => self.add_struct_literal_field(op),
74            Operation::AddEnumVariant(op) => self.add_enum_variant(op),
75            Operation::UpdateEnumVariant(op) => self.update_enum_variant(op),
76            Operation::RemoveEnumVariant(op) => self.remove_enum_variant(op),
77            Operation::AddMatchArm(op) => self.add_match_arm(op),
78            Operation::UpdateMatchArm(op) => self.update_match_arm(op),
79            Operation::RemoveMatchArm(op) => self.remove_match_arm(op),
80            Operation::AddImplMethod(op) => self.add_impl_method(op),
81            Operation::AddUseStatement(op) => self.add_use_statement(op),
82            Operation::AddDerive(op) => self.add_derive(op),
83            Operation::Transform(op) => self.transform(op),
84            Operation::RenameEnumVariant(op) => self.rename_enum_variant(op),
85        }
86    }
87    
88    pub(crate) fn add_struct_field(&mut self, op: &AddStructFieldOp) -> Result<ModificationResult> {
89        let mut modified_nodes = Vec::new();
90
91        // Find the struct and clone it to avoid borrowing issues
92        let item_struct = self.syntax_tree.items.iter()
93            .find_map(|item| {
94                if let Item::Struct(s) = item {
95                    if s.ident == op.struct_name {
96                        return Some(s.clone());
97                    }
98                }
99                None
100            })
101            .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
102
103        // Check if the struct matches the where filter (if specified)
104        if let Some(ref where_filter) = op.where_filter {
105            if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
106                // Struct doesn't match filter - skip without error
107                return Ok(ModificationResult {
108                    changed: false,
109                    modified_nodes: vec![],
110                });
111            }
112        }
113
114        // If literal_default is NOT provided, only modify the definition
115        if op.literal_default.is_none() {
116            // Create backup of original struct before modification
117            let backup_node = BackupNode {
118                node_type: "ItemStruct".to_string(),
119                identifier: op.struct_name.clone(),
120                original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
121                location: self.span_to_location(item_struct.span()),
122            };
123
124            // Insert the field into the struct definition
125            let modified = self.insert_struct_field(&item_struct, op)
126                .context("Failed to add field to struct definition")?;
127
128            if !modified {
129                return Ok(ModificationResult {
130                    changed: false,
131                    modified_nodes: vec![],
132                });
133            }
134
135            return Ok(ModificationResult {
136                changed: true,
137                modified_nodes: vec![backup_node],
138            });
139        }
140
141        // If literal_default IS provided:
142        // 1. Try to add to definition (idempotent - silently skips if field exists OR if field_def is incomplete)
143        // 2. Always update literals
144        let literal_default = op.literal_default.as_ref().unwrap();
145
146        // Check if field_def contains a type (has ':')
147        // If it doesn't, skip definition modification (literals-only mode)
148        let has_type = op.field_def.contains(':');
149
150        let mut def_modified = false;
151        if has_type {
152            // Create backup before any modifications
153            let backup_node = BackupNode {
154                node_type: "ItemStruct".to_string(),
155                identifier: op.struct_name.clone(),
156                original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
157                location: self.span_to_location(item_struct.span()),
158            };
159
160            // Try to insert field into definition (idempotent - returns false if already exists)
161            def_modified = self.insert_struct_field(&item_struct, op)
162                .context("Failed to add field to struct definition")?;
163
164            if def_modified {
165                modified_nodes.push(backup_node);
166                // Re-parse the content to update syntax_tree with the struct field changes
167                self.syntax_tree = syn::parse_str(&self.content)
168                    .context("Failed to re-parse content after adding struct field")?;
169                self.line_offsets = Self::compute_line_offsets(&self.content);
170            }
171        }
172
173        // Always update literals when literal_default is provided
174        // Extract field name from field_def (e.g., "return_type: Option<Type>" -> "return_type" or just "return_type")
175        let field_name = op.field_def.split(':')
176            .next()
177            .map(|s| s.trim().to_string())
178            .context("Failed to extract field name from field definition")?;
179
180        // Create the AddStructLiteralFieldOp
181        let literal_op = AddStructLiteralFieldOp {
182            struct_name: op.struct_name.clone(),
183            field_def: format!("{}: {}", field_name, literal_default),
184            position: op.position.clone(),
185        };
186
187        // Update all struct literals
188        let literal_result = self.add_struct_literal_field(&literal_op)
189            .context("Failed to update struct literals")?;
190        modified_nodes.extend(literal_result.modified_nodes);
191
192        Ok(ModificationResult {
193            changed: true,
194            modified_nodes,
195        })
196    }
197    
198    fn insert_struct_field(&mut self, item_struct: &ItemStruct, op: &AddStructFieldOp) -> Result<bool> {
199        if let Fields::Named(ref fields) = item_struct.fields {
200            // Parse the new field
201            let field_code = format!("struct Dummy {{ {} }}", op.field_def);
202            let dummy: ItemStruct = parse_str(&field_code)
203                .context("Failed to parse field definition")?;
204
205            let new_field = if let Fields::Named(ref nf) = dummy.fields {
206                nf.named.first()
207                    .context("No field found in definition")?
208                    .clone()
209            } else {
210                anyhow::bail!("Expected named field");
211            };
212
213            // Check if field already exists
214            let new_field_name = new_field.ident.as_ref()
215                .map(|i| i.to_string())
216                .context("Field must have a name")?;
217
218            if fields.named.iter().any(|f| {
219                f.ident.as_ref().map(|i| i.to_string()) == Some(new_field_name.clone())
220            }) {
221                // Field already exists, skip adding
222                return Ok(false);
223            }
224            
225            // Determine insertion point
226            let insert_pos = match &op.position {
227                InsertPosition::First => {
228                    if let Some(first_field) = fields.named.first() {
229                        self.span_to_byte_offset(first_field.span().start())
230                    } else {
231                        // Empty struct, insert after the opening brace
232                        let brace_pos = self.span_to_byte_offset(fields.brace_token.span.join().start());
233                        brace_pos + 1
234                    }
235                }
236                InsertPosition::Last => {
237                    if let Some(last_field) = fields.named.last() {
238                        let end = self.span_to_byte_offset(last_field.span().end());
239                        // Find the comma or end
240                        self.find_after_field_end(end)
241                    } else {
242                        // Empty struct
243                        let brace_pos = self.span_to_byte_offset(fields.brace_token.span.join().start());
244                        brace_pos + 1
245                    }
246                }
247                InsertPosition::After(name) => {
248                    let field = fields.named.iter()
249                        .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(name.clone()))
250                        .with_context(|| format!("Field '{}' not found", name))?;
251                    let end = self.span_to_byte_offset(field.span().end());
252                    self.find_after_field_end(end)
253                }
254                InsertPosition::Before(name) => {
255                    let field = fields.named.iter()
256                        .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(name.clone()))
257                        .with_context(|| format!("Field '{}' not found", name))?;
258                    self.span_to_byte_offset(field.span().start())
259                }
260            };
261            
262            // Format the new field
263            let indent = self.get_indentation(insert_pos);
264            let field_str = Self::format_field(&new_field);
265            let insert_text = if matches!(op.position, InsertPosition::First) {
266                format!("\n{}{},", indent, field_str)
267            } else {
268                format!("\n{}{},", indent, field_str)
269            };
270
271            self.content.insert_str(insert_pos, &insert_text);
272            return Ok(true);
273        }
274        
275        anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
276    }
277
278    pub(crate) fn update_struct_field(&mut self, op: &UpdateStructFieldOp) -> Result<ModificationResult> {
279        // Find the struct and clone it to avoid borrowing issues
280        let item_struct = self.syntax_tree.items.iter()
281            .find_map(|item| {
282                if let Item::Struct(s) = item {
283                    if s.ident == op.struct_name {
284                        return Some(s.clone());
285                    }
286                }
287                None
288            })
289            .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
290
291        // Check if the struct matches the where filter (if specified)
292        if let Some(ref where_filter) = op.where_filter {
293            if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
294                // Struct doesn't match filter - skip without error
295                return Ok(ModificationResult {
296                    changed: false,
297                    modified_nodes: vec![],
298                });
299            }
300        }
301
302        // Create backup of original struct before modification
303        let backup_node = BackupNode {
304            node_type: "ItemStruct".to_string(),
305            identifier: op.struct_name.clone(),
306            original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
307            location: self.span_to_location(item_struct.span()),
308        };
309
310        let modified = self.replace_struct_field(&item_struct, op)?;
311
312        Ok(ModificationResult {
313            changed: modified,
314            modified_nodes: if modified { vec![backup_node] } else { vec![] },
315        })
316    }
317
318    fn replace_struct_field(&mut self, item_struct: &ItemStruct, op: &UpdateStructFieldOp) -> Result<bool> {
319        if let Fields::Named(ref fields) = item_struct.fields {
320            // Parse the new field definition to get the field name
321            let field_code = format!("struct Dummy {{ {} }}", op.field_def);
322            let dummy: ItemStruct = parse_str(&field_code)
323                .context("Failed to parse field definition")?;
324
325            let new_field = if let Fields::Named(ref nf) = dummy.fields {
326                nf.named.first()
327                    .context("No field found in definition")?
328                    .clone()
329            } else {
330                anyhow::bail!("Expected named field");
331            };
332
333            // Extract the field name from the parsed field
334            let field_name = new_field.ident.as_ref()
335                .map(|i| i.to_string())
336                .context("Field must have a name")?;
337
338            // Find the existing field
339            let existing_field = fields.named.iter()
340                .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(field_name.clone()))
341                .ok_or_else(|| anyhow::anyhow!("Field '{}' not found in struct '{}'", field_name, op.struct_name))?;
342
343            // Get the span of the existing field
344            let start = self.span_to_byte_offset(existing_field.span().start());
345            let end = self.span_to_byte_offset(existing_field.span().end());
346
347            // Format and replace the field
348            let new_field_str = Self::format_field(&new_field);
349
350            // Remove the old field and insert the new one
351            self.content.replace_range(start..end, &new_field_str);
352
353            return Ok(true);
354        }
355
356        anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
357    }
358
359    pub(crate) fn remove_struct_field(&mut self, op: &RemoveStructFieldOp) -> Result<ModificationResult> {
360        // Find the struct and clone it to avoid borrowing issues
361        let item_struct = self.syntax_tree.items.iter()
362            .find_map(|item| {
363                if let Item::Struct(s) = item {
364                    if s.ident == op.struct_name {
365                        return Some(s.clone());
366                    }
367                }
368                None
369            })
370            .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
371
372        // Check if the struct matches the where filter (if specified)
373        if let Some(ref where_filter) = op.where_filter {
374            if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
375                // Struct doesn't match filter - skip without error
376                return Ok(ModificationResult {
377                    changed: false,
378                    modified_nodes: vec![],
379                });
380            }
381        }
382
383        // Create backup of original struct before modification
384        let backup_node = BackupNode {
385            node_type: "ItemStruct".to_string(),
386            identifier: op.struct_name.clone(),
387            original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
388            location: self.span_to_location(item_struct.span()),
389        };
390
391        if let Fields::Named(ref fields) = item_struct.fields {
392            // Find the field to remove
393            let field_to_remove = fields.named.iter()
394                .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(op.field_name.clone()))
395                .ok_or_else(|| anyhow::anyhow!("Field '{}' not found in struct '{}'", op.field_name, op.struct_name))?;
396
397            // Get the span including the comma
398            let start = self.span_to_byte_offset(field_to_remove.span().start());
399            let mut end = self.span_to_byte_offset(field_to_remove.span().end());
400
401            // Find and include the comma and any trailing whitespace/newline
402            while end < self.content.len() {
403                match self.content.as_bytes()[end] as char {
404                    ',' => {
405                        end += 1;
406                        // Also consume the newline after the comma if present
407                        if end < self.content.len() && self.content.as_bytes()[end] == b'\n' {
408                            end += 1;
409                        }
410                        break;
411                    }
412                    ' ' | '\t' => end += 1,
413                    '\n' => {
414                        end += 1;
415                        break;
416                    }
417                    _ => break,
418                }
419            }
420
421            // Also need to remove leading whitespace/indentation on the same line
422            let mut line_start = start;
423            while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
424                line_start -= 1;
425            }
426
427            // Check if there's only whitespace between line_start and start
428            let before_field = &self.content[line_start..start];
429            if before_field.trim().is_empty() {
430                // Remove the whole line
431                self.content.replace_range(line_start..end, "");
432            } else {
433                // Just remove the field and comma
434                self.content.replace_range(start..end, "");
435            }
436
437            return Ok(ModificationResult {
438                changed: true,
439                modified_nodes: vec![backup_node],
440            });
441        }
442
443        anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
444    }
445
446    pub(crate) fn add_struct_literal_field(&mut self, op: &AddStructLiteralFieldOp) -> Result<ModificationResult> {
447        // Parse the field name from field_def (e.g., "return_type: None" -> "return_type")
448        let field_name = op.field_def.split(':')
449            .next()
450            .map(|s| s.trim().to_string())
451            .context("Field definition must contain ':'")?;
452
453        // Collect backups of all struct literal expressions that will be modified
454        let backup_nodes = self.collect_struct_literal_backups(&op.struct_name);
455
456        // Use a visitor to find and modify all struct literals
457        let mut visitor = StructLiteralFieldAdder {
458            struct_name: op.struct_name.clone(),
459            field_def: op.field_def.clone(),
460            field_name,
461            position: op.position.clone(),
462            modified: false,
463        };
464
465        visitor.visit_file_mut(&mut self.syntax_tree);
466
467        if visitor.modified {
468            // Reformat the entire file for struct literals
469            self.content = prettyplease::unparse(&self.syntax_tree);
470            Ok(ModificationResult {
471                changed: true,
472                modified_nodes: backup_nodes,
473            })
474        } else {
475            Ok(ModificationResult {
476                changed: false,
477                modified_nodes: vec![],
478            })
479        }
480    }
481
482    /// Collect backups of all struct literal expressions for a given struct name
483    fn collect_struct_literal_backups(&self, struct_name: &str) -> Vec<BackupNode> {
484        use syn::visit::Visit;
485
486        struct LiteralCollector {
487            struct_name: String,
488            backups: Vec<BackupNode>,
489            counter: usize,
490        }
491
492        impl<'ast> Visit<'ast> for LiteralCollector {
493            fn visit_expr(&mut self, node: &'ast Expr) {
494                if let Expr::Struct(expr_struct) = node {
495                    // Match based on pattern:
496                    // - "Rectangle" → only Rectangle { ... } (no :: prefix)
497                    // - "*::Rectangle" → any path ending with Rectangle (View::Rectangle, etc.)
498                    // - "View::Rectangle" → exact match only View::Rectangle
499
500                    let matches = if self.struct_name.contains("::") {
501                        // Pattern contains :: - check for exact or wildcard match
502                        if self.struct_name.starts_with("*::") {
503                            // Wildcard: *::Rectangle matches any path ending with Rectangle
504                            let target_name = &self.struct_name[3..]; // Skip "*::"
505                            expr_struct.path.segments.last()
506                                .map(|seg| seg.ident.to_string() == target_name)
507                                .unwrap_or(false)
508                        } else {
509                            // Exact path match: View::Rectangle
510                            let path_str = expr_struct.path.segments.iter()
511                                .map(|seg| seg.ident.to_string())
512                                .collect::<Vec<_>>()
513                                .join("::");
514                            path_str == self.struct_name
515                        }
516                    } else {
517                        // No :: in pattern - only match pure struct literals (no path qualifier)
518                        expr_struct.path.segments.len() == 1
519                            && expr_struct.path.segments.last()
520                                .map(|seg| seg.ident.to_string() == self.struct_name)
521                                .unwrap_or(false)
522                    };
523
524                    if matches {
525                        self.backups.push(BackupNode {
526                            node_type: "ExprStruct".to_string(),
527                            identifier: format!("{}#{}", self.struct_name, self.counter),
528                            original_content: expr_struct.to_token_stream().to_string(),
529                            location: NodeLocation {
530                                line: 0, // We don't have precise location info in visitor
531                                column: 0,
532                                end_line: 0,
533                                end_column: 0,
534                            },
535                        });
536                        self.counter += 1;
537                    }
538                }
539                syn::visit::visit_expr(self, node);
540            }
541        }
542
543        let mut collector = LiteralCollector {
544            struct_name: struct_name.to_string(),
545            backups: Vec::new(),
546            counter: 0,
547        };
548
549        collector.visit_file(&self.syntax_tree);
550        collector.backups
551    }
552
553    pub(crate) fn add_enum_variant(&mut self, op: &AddEnumVariantOp) -> Result<ModificationResult> {
554        // Find the enum and clone it to avoid borrowing issues
555        let item_enum = self.syntax_tree.items.iter()
556            .find_map(|item| {
557                if let Item::Enum(e) = item {
558                    if e.ident == op.enum_name {
559                        return Some(e.clone());
560                    }
561                }
562                None
563            })
564            .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
565
566        // Check if the enum matches the where filter (if specified)
567        if let Some(ref where_filter) = op.where_filter {
568            if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
569                // Enum doesn't match filter - skip without error
570                return Ok(ModificationResult {
571                    changed: false,
572                    modified_nodes: vec![],
573                });
574            }
575        }
576
577        // Create backup of original enum before modification
578        let backup_node = BackupNode {
579            node_type: "ItemEnum".to_string(),
580            identifier: op.enum_name.clone(),
581            original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
582            location: self.span_to_location(item_enum.span()),
583        };
584
585        let modified = self.insert_enum_variant(&item_enum, op)?;
586
587        Ok(ModificationResult {
588            changed: modified,
589            modified_nodes: if modified { vec![backup_node] } else { vec![] },
590        })
591    }
592    
593    fn insert_enum_variant(&mut self, item_enum: &ItemEnum, op: &AddEnumVariantOp) -> Result<bool> {
594        // Parse the new variant
595        let variant_code = format!("enum Dummy {{ {} }}", op.variant_def);
596        let dummy: ItemEnum = parse_str(&variant_code)
597            .context("Failed to parse variant definition")?;
598
599        let new_variant = dummy.variants.first()
600            .context("No variant found in definition")?
601            .clone();
602
603        // Check if variant already exists
604        let variant_name = new_variant.ident.to_string();
605        if item_enum.variants.iter().any(|v| v.ident.to_string() == variant_name) {
606            // Variant already exists, skip adding
607            return Ok(false);
608        }
609
610        // Determine insertion point
611        let insert_pos = match &op.position {
612            InsertPosition::First => {
613                if let Some(first_var) = item_enum.variants.first() {
614                    self.span_to_byte_offset(first_var.span().start())
615                } else {
616                    let brace_pos = self.span_to_byte_offset(item_enum.brace_token.span.join().start());
617                    brace_pos + 1
618                }
619            }
620            InsertPosition::Last => {
621                if let Some(last_var) = item_enum.variants.last() {
622                    let end = self.span_to_byte_offset(last_var.span().end());
623                    self.find_after_field_end(end)
624                } else {
625                    let brace_pos = self.span_to_byte_offset(item_enum.brace_token.span.join().start());
626                    brace_pos + 1
627                }
628            }
629            InsertPosition::After(name) => {
630                let variant = item_enum.variants.iter()
631                    .find(|v| v.ident.to_string() == *name)
632                    .with_context(|| format!("Variant '{}' not found", name))?;
633                let end = self.span_to_byte_offset(variant.span().end());
634                self.find_after_field_end(end)
635            }
636            InsertPosition::Before(name) => {
637                let variant = item_enum.variants.iter()
638                    .find(|v| v.ident.to_string() == *name)
639                    .with_context(|| format!("Variant '{}' not found", name))?;
640                self.span_to_byte_offset(variant.span().start())
641            }
642        };
643        
644        let indent = self.get_indentation(insert_pos);
645        let variant_str = new_variant.to_token_stream().to_string();
646        let insert_text = format!("\n{}{},", indent, variant_str);
647        
648        self.content.insert_str(insert_pos, &insert_text);
649        Ok(true)
650    }
651
652    fn update_enum_variant(&mut self, op: &UpdateEnumVariantOp) -> Result<ModificationResult> {
653        // Find the enum and clone it
654        let item_enum = self.syntax_tree.items.iter()
655            .find_map(|item| {
656                if let Item::Enum(e) = item {
657                    if e.ident == op.enum_name {
658                        return Some(e.clone());
659                    }
660                }
661                None
662            })
663            .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
664
665        // Check if the enum matches the where filter (if specified)
666        if let Some(ref where_filter) = op.where_filter {
667            if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
668                // Enum doesn't match filter - skip without error
669                return Ok(ModificationResult {
670                    changed: false,
671                    modified_nodes: vec![],
672                });
673            }
674        }
675
676        // Create backup of original enum before modification
677        let backup_node = BackupNode {
678            node_type: "ItemEnum".to_string(),
679            identifier: op.enum_name.clone(),
680            original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
681            location: self.span_to_location(item_enum.span()),
682        };
683
684        // Parse the new variant to get its name
685        let variant_code = format!("enum Dummy {{ {} }}", op.variant_def);
686        let dummy: ItemEnum = parse_str(&variant_code)
687            .context("Failed to parse variant definition")?;
688
689        let new_variant = dummy.variants.first()
690            .context("No variant found in definition")?
691            .clone();
692
693        let variant_name = new_variant.ident.to_string();
694
695        // Find the existing variant
696        let existing_variant = item_enum.variants.iter()
697            .find(|v| v.ident.to_string() == variant_name)
698            .ok_or_else(|| anyhow::anyhow!("Variant '{}' not found in enum '{}'", variant_name, op.enum_name))?;
699
700        // Get the span
701        let start = self.span_to_byte_offset(existing_variant.span().start());
702        let end = self.span_to_byte_offset(existing_variant.span().end());
703
704        // Format and replace
705        let variant_str = new_variant.to_token_stream().to_string();
706        self.content.replace_range(start..end, &variant_str);
707
708        Ok(ModificationResult {
709            changed: true,
710            modified_nodes: vec![backup_node],
711        })
712    }
713
714    pub(crate) fn remove_enum_variant(&mut self, op: &RemoveEnumVariantOp) -> Result<ModificationResult> {
715        // Find the enum
716        let item_enum = self.syntax_tree.items.iter()
717            .find_map(|item| {
718                if let Item::Enum(e) = item {
719                    if e.ident == op.enum_name {
720                        return Some(e.clone());
721                    }
722                }
723                None
724            })
725            .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
726
727        // Check if the enum matches the where filter (if specified)
728        if let Some(ref where_filter) = op.where_filter {
729            if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
730                // Enum doesn't match filter - skip without error
731                return Ok(ModificationResult {
732                    changed: false,
733                    modified_nodes: vec![],
734                });
735            }
736        }
737
738        // Create backup of original enum before modification
739        let backup_node = BackupNode {
740            node_type: "ItemEnum".to_string(),
741            identifier: op.enum_name.clone(),
742            original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
743            location: self.span_to_location(item_enum.span()),
744        };
745
746        // Find the variant to remove
747        let variant_to_remove = item_enum.variants.iter()
748            .find(|v| v.ident.to_string() == op.variant_name)
749            .ok_or_else(|| anyhow::anyhow!("Variant '{}' not found in enum '{}'", op.variant_name, op.enum_name))?;
750
751        // Get the span including comma
752        let start = self.span_to_byte_offset(variant_to_remove.span().start());
753        let mut end = self.span_to_byte_offset(variant_to_remove.span().end());
754
755        // Find and include the comma and trailing whitespace
756        while end < self.content.len() {
757            match self.content.as_bytes()[end] as char {
758                ',' => {
759                    end += 1;
760                    if end < self.content.len() && self.content.as_bytes()[end] == b'\n' {
761                        end += 1;
762                    }
763                    break;
764                }
765                ' ' | '\t' => end += 1,
766                '\n' => {
767                    end += 1;
768                    break;
769                }
770                _ => break,
771            }
772        }
773
774        // Remove leading whitespace on the line
775        let mut line_start = start;
776        while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
777            line_start -= 1;
778        }
779
780        let before_variant = &self.content[line_start..start];
781        if before_variant.trim().is_empty() {
782            self.content.replace_range(line_start..end, "");
783        } else {
784            self.content.replace_range(start..end, "");
785        }
786
787        Ok(ModificationResult {
788            changed: true,
789            modified_nodes: vec![backup_node],
790        })
791    }
792
793    pub(crate) fn add_match_arm(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
794        if op.auto_detect {
795            // Auto-detect mode: find all missing enum variants
796            self.add_missing_match_arms(op)
797        } else {
798            // Normal mode: add a single match arm
799            self.add_single_match_arm(op)
800        }
801    }
802
803    fn add_single_match_arm(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
804        // Parse the pattern and body by creating a dummy match expression
805        let dummy_match = format!("match () {{ {} => {}, }}", op.pattern, op.body);
806        let expr: syn::Expr = parse_str(&dummy_match)
807            .with_context(|| format!("Failed to parse pattern/body: {} => {}", op.pattern, op.body))?;
808
809        // Extract the arm from the dummy match
810        let arm = if let syn::Expr::Match(match_expr) = expr {
811            match_expr.arms.into_iter().next()
812                .context("Failed to extract arm from dummy match")?
813        } else {
814            anyhow::bail!("Expected match expression");
815        };
816
817        // Collect backup of function before modification
818        let backup_node = if let Some(ref fn_name) = op.function_name {
819            self.get_function_backup(fn_name)?
820        } else {
821            // If no function specified, we'll backup all modified functions later
822            // For now, create a generic backup
823            BackupNode {
824                node_type: "Unknown".to_string(),
825                identifier: "match_expression".to_string(),
826                original_content: String::new(),
827                location: NodeLocation {
828                    line: 0,
829                    column: 0,
830                    end_line: 0,
831                    end_column: 0,
832                },
833            }
834        };
835
836        // Find and modify match expressions
837        let mut visitor = MatchArmAdder {
838            target_function: op.function_name.clone(),
839            arm_to_add: arm,
840            modified: false,
841            current_function: None,
842            modified_function: None,
843        };
844
845        visitor.visit_file_mut(&mut self.syntax_tree);
846
847        if visitor.modified {
848            // Replace just the modified function
849            self.replace_modified_functions(&visitor.modified_function)?;
850            Ok(ModificationResult {
851                changed: true,
852                modified_nodes: vec![backup_node],
853            })
854        } else {
855            Ok(ModificationResult {
856                changed: false,
857                modified_nodes: vec![],
858            })
859        }
860    }
861
862    /// Format a single item to string using prettyplease
863    fn unparse_item(&self, item: &Item) -> String {
864        let temp_file = syn::File {
865            shebang: None,
866            attrs: Vec::new(),
867            items: vec![item.clone()],
868        };
869        prettyplease::unparse(&temp_file).trim().to_string()
870    }
871
872    /// Get backup of a function before modification
873    fn get_function_backup(&self, fn_name: &str) -> Result<BackupNode> {
874        for item in &self.syntax_tree.items {
875            if let Item::Fn(f) = item {
876                if f.sig.ident == fn_name {
877                    return Ok(BackupNode {
878                        node_type: "ItemFn".to_string(),
879                        identifier: fn_name.to_string(),
880                        original_content: self.unparse_item(&Item::Fn(f.clone())),
881                        location: self.span_to_location(f.span()),
882                    });
883                }
884            }
885        }
886        anyhow::bail!("Function '{}' not found", fn_name)
887    }
888
889    fn add_missing_match_arms(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
890        // Get the enum name
891        let enum_name = op.enum_name.as_ref()
892            .ok_or_else(|| anyhow::anyhow!("enum_name is required for auto-detect"))?;
893
894        // Find all enum variants
895        let enum_variants = self.find_enum_variants(enum_name)?;
896
897        if enum_variants.is_empty() {
898            anyhow::bail!("Enum '{}' not found or has no variants", enum_name);
899        }
900
901        // Find existing match arms
902        let existing_patterns = self.find_existing_match_patterns(&op.function_name);
903
904        // Determine missing variants
905        let mut missing_variants = Vec::new();
906        for variant in &enum_variants {
907            let pattern = format!("{}::{}", enum_name, variant);
908            let pattern_normalized = pattern.replace(" ", "");
909
910            let exists = existing_patterns.iter().any(|p| {
911                p.replace(" ", "") == pattern_normalized
912            });
913
914            if !exists {
915                missing_variants.push(variant.clone());
916            }
917        }
918
919        if missing_variants.is_empty() {
920            println!("All enum variants already covered in match expressions");
921            return Ok(ModificationResult {
922                changed: false,
923                modified_nodes: vec![],
924            });
925        }
926
927        // Get backup of function before modification
928        let backup_node = if let Some(ref fn_name) = op.function_name {
929            self.get_function_backup(fn_name)?
930        } else {
931            BackupNode {
932                node_type: "Unknown".to_string(),
933                identifier: "match_expression".to_string(),
934                original_content: String::new(),
935                location: NodeLocation {
936                    line: 0,
937                    column: 0,
938                    end_line: 0,
939                    end_column: 0,
940                },
941            }
942        };
943
944        // Add ALL missing match arms in one pass using a visitor
945        let mut arms_to_add = Vec::new();
946        for variant in &missing_variants {
947            let pattern = format!("{}::{}", enum_name, variant);
948            let dummy_match = format!("match () {{ {} => {}, }}", pattern, op.body);
949            let expr: syn::Expr = parse_str(&dummy_match)
950                .with_context(|| format!("Failed to parse pattern/body: {} => {}", pattern, op.body))?;
951
952            if let syn::Expr::Match(match_expr) = expr {
953                if let Some(arm) = match_expr.arms.into_iter().next() {
954                    arms_to_add.push((pattern.clone(), arm));
955                }
956            }
957        }
958
959        // Find and modify match expressions with all arms at once
960        let mut visitor = MultiMatchArmAdder {
961            target_function: op.function_name.clone(),
962            arms_to_add,
963            modified: false,
964            current_function: None,
965            modified_function: None,
966        };
967
968        visitor.visit_file_mut(&mut self.syntax_tree);
969
970        if visitor.modified {
971            // Print what was added
972            for variant in &missing_variants {
973                println!("Added match arm for: {}::{}", enum_name, variant);
974            }
975
976            // Replace just the modified function
977            self.replace_modified_functions(&visitor.modified_function)?;
978            Ok(ModificationResult {
979                changed: true,
980                modified_nodes: vec![backup_node],
981            })
982        } else {
983            Ok(ModificationResult {
984                changed: false,
985                modified_nodes: vec![],
986            })
987        }
988    }
989
990    fn find_enum_variants(&self, enum_name: &str) -> Result<Vec<String>> {
991        // Find the enum in the syntax tree
992        for item in &self.syntax_tree.items {
993            if let Item::Enum(e) = item {
994                if e.ident == enum_name {
995                    let variants: Vec<String> = e.variants.iter()
996                        .map(|v| v.ident.to_string())
997                        .collect();
998                    return Ok(variants);
999                }
1000            }
1001        }
1002
1003        Ok(Vec::new())
1004    }
1005
1006    fn find_existing_match_patterns(&self, function_name: &Option<String>) -> Vec<String> {
1007        use syn::visit::Visit;
1008
1009        struct PatternCollector {
1010            target_function: Option<String>,
1011            current_function: Option<String>,
1012            patterns: Vec<String>,
1013        }
1014
1015        impl<'ast> Visit<'ast> for PatternCollector {
1016            fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
1017                let prev_fn = self.current_function.clone();
1018                self.current_function = Some(node.sig.ident.to_string());
1019                syn::visit::visit_item_fn(self, node);
1020                self.current_function = prev_fn;
1021            }
1022
1023            fn visit_expr_match(&mut self, node: &'ast ExprMatch) {
1024                // Check if we're in the right function (if specified)
1025                if let Some(ref target) = self.target_function {
1026                    if self.current_function.as_ref() != Some(target) {
1027                        syn::visit::visit_expr_match(self, node);
1028                        return;
1029                    }
1030                }
1031
1032                // Collect all patterns
1033                for arm in &node.arms {
1034                    self.patterns.push(arm.pat.to_token_stream().to_string());
1035                }
1036
1037                syn::visit::visit_expr_match(self, node);
1038            }
1039        }
1040
1041        let mut collector = PatternCollector {
1042            target_function: function_name.clone(),
1043            current_function: None,
1044            patterns: Vec::new(),
1045        };
1046
1047        collector.visit_file(&self.syntax_tree);
1048        collector.patterns
1049    }
1050
1051    pub(crate) fn update_match_arm(&mut self, op: &UpdateMatchArmOp) -> Result<ModificationResult> {
1052        // Get backup of function before modification
1053        let backup_node = if let Some(ref fn_name) = op.function_name {
1054            self.get_function_backup(fn_name)?
1055        } else {
1056            BackupNode {
1057                node_type: "Unknown".to_string(),
1058                identifier: "match_expression".to_string(),
1059                original_content: String::new(),
1060                location: NodeLocation {
1061                    line: 0,
1062                    column: 0,
1063                    end_line: 0,
1064                    end_column: 0,
1065                },
1066            }
1067        };
1068
1069        // Parse the new body
1070        let new_body: syn::Expr = parse_str(&op.new_body)
1071            .with_context(|| format!("Failed to parse new body: {}", op.new_body))?;
1072
1073        // Find and modify match expressions
1074        let mut visitor = MatchArmUpdater {
1075            target_function: op.function_name.clone(),
1076            pattern_to_match: op.pattern.clone(),
1077            new_body,
1078            modified: false,
1079            current_function: None,
1080            modified_function: None,
1081        };
1082
1083        visitor.visit_file_mut(&mut self.syntax_tree);
1084
1085        if visitor.modified {
1086            // Replace just the modified function
1087            self.replace_modified_functions(&visitor.modified_function)?;
1088            Ok(ModificationResult {
1089                changed: true,
1090                modified_nodes: vec![backup_node],
1091            })
1092        } else {
1093            anyhow::bail!("Pattern '{}' not found in any match expression", op.pattern)
1094        }
1095    }
1096
1097    pub(crate) fn remove_match_arm(&mut self, op: &RemoveMatchArmOp) -> Result<ModificationResult> {
1098        // Get backup of function before modification
1099        let backup_node = if let Some(ref fn_name) = op.function_name {
1100            self.get_function_backup(fn_name)?
1101        } else {
1102            BackupNode {
1103                node_type: "Unknown".to_string(),
1104                identifier: "match_expression".to_string(),
1105                original_content: String::new(),
1106                location: NodeLocation {
1107                    line: 0,
1108                    column: 0,
1109                    end_line: 0,
1110                    end_column: 0,
1111                },
1112            }
1113        };
1114
1115        // Find and modify match expressions
1116        let mut visitor = MatchArmRemover {
1117            target_function: op.function_name.clone(),
1118            pattern_to_remove: op.pattern.clone(),
1119            modified: false,
1120            current_function: None,
1121            modified_function: None,
1122        };
1123
1124        visitor.visit_file_mut(&mut self.syntax_tree);
1125
1126        if visitor.modified {
1127            // Replace just the modified function
1128            self.replace_modified_functions(&visitor.modified_function)?;
1129            Ok(ModificationResult {
1130                changed: true,
1131                modified_nodes: vec![backup_node],
1132            })
1133        } else {
1134            anyhow::bail!("Pattern '{}' not found in any match expression", op.pattern)
1135        }
1136    }
1137
1138    pub(crate) fn add_impl_method(&mut self, op: &AddImplMethodOp) -> Result<ModificationResult> {
1139        // Parse the method definition
1140        let method_code = format!("impl Dummy {{ {} }}", op.method_def);
1141        let dummy: syn::ItemImpl = parse_str(&method_code)
1142            .context("Failed to parse method definition")?;
1143
1144        let new_method = dummy.items.first()
1145            .context("No method found in definition")?
1146            .clone();
1147
1148        // Get the method name for idempotency check
1149        let method_name = match &new_method {
1150            syn::ImplItem::Fn(f) => f.sig.ident.to_string(),
1151            _ => anyhow::bail!("Only method definitions are supported"),
1152        };
1153
1154        // Find the impl block
1155        let impl_index = self.syntax_tree.items.iter().position(|item| {
1156            if let Item::Impl(impl_block) = item {
1157                // Check if this is the right impl block
1158                if let syn::Type::Path(type_path) = &*impl_block.self_ty {
1159                    if let Some(segment) = type_path.path.segments.last() {
1160                        return segment.ident == op.target;
1161                    }
1162                }
1163            }
1164            false
1165        }).ok_or_else(|| anyhow::anyhow!("impl block for '{}' not found", op.target))?;
1166
1167        // Check if method already exists (idempotent)
1168        let impl_block = match &self.syntax_tree.items[impl_index] {
1169            Item::Impl(i) => i,
1170            _ => unreachable!(),
1171        };
1172
1173        let method_exists = impl_block.items.iter().any(|item| {
1174            if let syn::ImplItem::Fn(f) = item {
1175                f.sig.ident == method_name
1176            } else {
1177                false
1178            }
1179        });
1180
1181        if method_exists {
1182            return Ok(ModificationResult {
1183                changed: false,
1184                modified_nodes: vec![],
1185            });
1186        }
1187
1188        // Create backup of original impl block before modification
1189        let backup_node = BackupNode {
1190            node_type: "ItemImpl".to_string(),
1191            identifier: op.target.clone(),
1192            original_content: self.unparse_item(&self.syntax_tree.items[impl_index].clone()),
1193            location: self.span_to_location(impl_block.span()),
1194        };
1195
1196        // Get the span before modification
1197        let impl_span = impl_block.span();
1198
1199        // Add the method to the impl block
1200        match &mut self.syntax_tree.items[impl_index] {
1201            Item::Impl(impl_block) => {
1202                // Add based on position
1203                match &op.position {
1204                    InsertPosition::First => {
1205                        impl_block.items.insert(0, new_method);
1206                    }
1207                    InsertPosition::Last => {
1208                        impl_block.items.push(new_method);
1209                    }
1210                    InsertPosition::After(name) => {
1211                        let pos = impl_block.items.iter().position(|item| {
1212                            if let syn::ImplItem::Fn(f) = item {
1213                                f.sig.ident == name
1214                            } else {
1215                                false
1216                            }
1217                        }).with_context(|| format!("Method '{}' not found", name))?;
1218                        impl_block.items.insert(pos + 1, new_method);
1219                    }
1220                    InsertPosition::Before(name) => {
1221                        let pos = impl_block.items.iter().position(|item| {
1222                            if let syn::ImplItem::Fn(f) = item {
1223                                f.sig.ident == name
1224                            } else {
1225                                false
1226                            }
1227                        }).with_context(|| format!("Method '{}' not found", name))?;
1228                        impl_block.items.insert(pos, new_method);
1229                    }
1230                }
1231            }
1232            _ => unreachable!(),
1233        }
1234
1235        // Use prettyplease to format just this impl block
1236        self.replace_formatted_item(impl_index, impl_span)?;
1237
1238        Ok(ModificationResult {
1239            changed: true,
1240            modified_nodes: vec![backup_node],
1241        })
1242    }
1243
1244    pub(crate) fn add_use_statement(&mut self, op: &AddUseStatementOp) -> Result<ModificationResult> {
1245        // Parse the use statement
1246        let use_code = format!("use {};", op.use_path);
1247        let use_item: syn::ItemUse = parse_str(&use_code)
1248            .context("Failed to parse use statement")?;
1249
1250        // Check if this use statement already exists (idempotent)
1251        let use_exists = self.syntax_tree.items.iter().any(|item| {
1252            if let Item::Use(existing_use) = item {
1253                // Compare the use trees
1254                existing_use.tree.to_token_stream().to_string() ==
1255                    use_item.tree.to_token_stream().to_string()
1256            } else {
1257                false
1258            }
1259        });
1260
1261        if use_exists {
1262            return Ok(ModificationResult {
1263                changed: false,
1264                modified_nodes: vec![],
1265            });
1266        }
1267
1268        // Create a simple backup for use statements (track by line position)
1269        let backup_node = BackupNode {
1270            node_type: "ItemUse".to_string(),
1271            identifier: op.use_path.clone(),
1272            original_content: format!("use {};", op.use_path),
1273            location: NodeLocation {
1274                line: 0,
1275                column: 0,
1276                end_line: 0,
1277                end_column: 0,
1278            },
1279        };
1280
1281        // Find the position to insert the use statement
1282        let insert_index = match &op.position {
1283            InsertPosition::First => 0,
1284            InsertPosition::Last => {
1285                // Find the last use statement
1286                self.syntax_tree.items.iter()
1287                    .rposition(|item| matches!(item, Item::Use(_)))
1288                    .map(|i| i + 1)
1289                    .unwrap_or(0)
1290            }
1291            InsertPosition::After(path) => {
1292                // Find the use statement matching the path
1293                let pos = self.syntax_tree.items.iter().position(|item| {
1294                    if let Item::Use(u) = item {
1295                        u.tree.to_token_stream().to_string().contains(path)
1296                    } else {
1297                        false
1298                    }
1299                }).with_context(|| format!("Use statement for '{}' not found", path))?;
1300                pos + 1
1301            }
1302            InsertPosition::Before(path) => {
1303                // Find the use statement matching the path
1304                self.syntax_tree.items.iter().position(|item| {
1305                    if let Item::Use(u) = item {
1306                        u.tree.to_token_stream().to_string().contains(path)
1307                    } else {
1308                        false
1309                    }
1310                }).with_context(|| format!("Use statement for '{}' not found", path))?
1311            }
1312        };
1313
1314        // Insert the use statement into the AST
1315        self.syntax_tree.items.insert(insert_index, Item::Use(use_item));
1316
1317        // Find the byte position in the source where we need to insert
1318        // We want to insert at the beginning of a line
1319        let insert_line_pos = if insert_index == 0 {
1320            // Insert at very beginning
1321            0
1322        } else {
1323            // Insert after the previous item
1324            let prev_item = &self.syntax_tree.items[insert_index - 1];
1325            let span = prev_item.span();
1326            let end_pos = self.span_to_byte_offset(span.end());
1327
1328            // Find the end of this line (where the newline is)
1329            let mut line_end = end_pos;
1330            while line_end < self.content.len() && self.content.as_bytes()[line_end] != b'\n' {
1331                line_end += 1;
1332            }
1333            // Move past the newline to the start of the next line
1334            if line_end < self.content.len() {
1335                line_end + 1
1336            } else {
1337                // At end of file, add a newline first
1338                self.content.push('\n');
1339                self.content.len()
1340            }
1341        };
1342
1343        // Format the use statement
1344        let use_str = format!("use {};\n", op.use_path);
1345
1346        // Insert the use statement
1347        self.content.insert_str(insert_line_pos, &use_str);
1348
1349        Ok(ModificationResult {
1350            changed: true,
1351            modified_nodes: vec![backup_node],
1352        })
1353    }
1354
1355    pub(crate) fn add_derive(&mut self, op: &AddDeriveOp) -> Result<ModificationResult> {
1356        // Find the target item (struct or enum)
1357        let item_index = self.syntax_tree.items.iter().position(|item| {
1358            match (&op.target_type as &str, item) {
1359                ("struct", Item::Struct(s)) => s.ident == op.target_name,
1360                ("enum", Item::Enum(e)) => e.ident == op.target_name,
1361                _ => false,
1362            }
1363        }).ok_or_else(|| anyhow::anyhow!("{} '{}' not found", op.target_type, op.target_name))?;
1364
1365        // Get the item and check for existing derives
1366        let (existing_derives, item_span, item_attrs) = match &self.syntax_tree.items[item_index] {
1367            Item::Struct(s) => (Self::extract_derives(&s.attrs), s.span(), &s.attrs),
1368            Item::Enum(e) => (Self::extract_derives(&e.attrs), e.span(), &e.attrs),
1369            _ => (Vec::new(), proc_macro2::Span::call_site(), &Vec::new() as &Vec<syn::Attribute>),
1370        };
1371
1372        // Check if the item matches the where filter (if specified)
1373        if let Some(ref where_filter) = op.where_filter {
1374            if !self.matches_where_filter(item_attrs, where_filter)? {
1375                // Item doesn't match filter - skip without error
1376                return Ok(ModificationResult {
1377                    changed: false,
1378                    modified_nodes: vec![],
1379                });
1380            }
1381        }
1382
1383        // Create backup of original item before modification
1384        let backup_node = BackupNode {
1385            node_type: if op.target_type == "struct" { "ItemStruct" } else { "ItemEnum" }.to_string(),
1386            identifier: op.target_name.clone(),
1387            original_content: self.unparse_item(&self.syntax_tree.items[item_index].clone()),
1388            location: self.span_to_location(item_span),
1389        };
1390
1391        // Filter out derives that already exist (idempotent)
1392        let new_derives: Vec<String> = op.derives.iter()
1393            .filter(|d| !existing_derives.contains(&d.to_string()))
1394            .cloned()
1395            .collect();
1396
1397        if new_derives.is_empty() {
1398            // All derives already exist
1399            return Ok(ModificationResult {
1400                changed: false,
1401                modified_nodes: vec![],
1402            });
1403        }
1404
1405        // Combine existing and new derives
1406        let mut all_derives = existing_derives;
1407        all_derives.extend(new_derives);
1408
1409        // Convert to string refs for the update function
1410        let all_derives_refs: Vec<&str> = all_derives.iter().map(|s| s.as_str()).collect();
1411
1412        // Update the AST item's attributes
1413        match &mut self.syntax_tree.items[item_index] {
1414            Item::Struct(s) => {
1415                Self::update_derive_attr(&mut s.attrs, &all_derives_refs)?;
1416            }
1417            Item::Enum(e) => {
1418                Self::update_derive_attr(&mut e.attrs, &all_derives_refs)?;
1419            }
1420            _ => unreachable!(),
1421        }
1422
1423        // Use prettyplease to format just this item
1424        self.replace_formatted_item(item_index, item_span)?;
1425
1426        Ok(ModificationResult {
1427            changed: true,
1428            modified_nodes: vec![backup_node],
1429        })
1430    }
1431
1432    /// Replace an item in the content with a formatted version
1433    fn replace_formatted_item(&mut self, item_index: usize, original_span: Span) -> Result<()> {
1434        // Get the item start and end positions from the original source
1435        let item_start_pos = self.span_to_byte_offset(original_span.start());
1436        let item_end_pos = self.span_to_byte_offset(original_span.end());
1437
1438        // Find the actual start (including attributes)
1439        let mut actual_start = item_start_pos;
1440
1441        // Search backwards for attributes
1442        let mut temp_pos = item_start_pos;
1443        while temp_pos > 0 {
1444            // Move to previous line
1445            temp_pos = temp_pos.saturating_sub(1);
1446            let mut line_start = temp_pos;
1447            while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
1448                line_start -= 1;
1449            }
1450
1451            let line = if temp_pos < self.content.len() {
1452                &self.content[line_start..temp_pos + 1]
1453            } else {
1454                &self.content[line_start..]
1455            };
1456            let trimmed = line.trim();
1457
1458            if trimmed.starts_with("#[") {
1459                actual_start = line_start;
1460                temp_pos = line_start;
1461            } else if trimmed.is_empty() {
1462                temp_pos = line_start;
1463            } else {
1464                break;
1465            }
1466
1467            if line_start == 0 {
1468                break;
1469            }
1470        }
1471
1472        // Create a temporary file with just this item for pretty formatting
1473        let item_clone = self.syntax_tree.items[item_index].clone();
1474        let temp_file = syn::File {
1475            shebang: None,
1476            attrs: Vec::new(),
1477            items: vec![item_clone],
1478        };
1479
1480        // Format the item using prettyplease
1481        let formatted = prettyplease::unparse(&temp_file);
1482        let formatted = formatted.trim();
1483
1484        // Replace in content
1485        self.content.replace_range(actual_start..item_end_pos, formatted);
1486
1487        Ok(())
1488    }
1489
1490    /// Extract existing derive traits from attributes - returns owned Strings
1491    fn extract_derives(attrs: &[syn::Attribute]) -> Vec<String> {
1492        for attr in attrs {
1493            if attr.path().is_ident("derive") {
1494                if let Ok(syn::Meta::List(meta_list)) = attr.meta.clone().try_into() {
1495                    let tokens_str = meta_list.tokens.to_string();
1496                    return tokens_str
1497                        .split(',')
1498                        .map(|s| s.trim().to_string())
1499                        .collect();
1500                }
1501            }
1502        }
1503        Vec::new()
1504    }
1505
1506    /// Check if an item matches the where filter criteria
1507    /// Supports filters like:
1508    /// - "derives_trait:Clone" - matches if item derives Clone
1509    /// - "derives_trait:Clone,Debug" - matches if item derives Clone OR Debug
1510    fn matches_where_filter(&self, attrs: &[syn::Attribute], where_filter: &str) -> Result<bool> {
1511        // Parse the filter: "derives_trait:Clone,Debug"
1512        if let Some(filter_value) = where_filter.strip_prefix("derives_trait:") {
1513            let required_traits: Vec<&str> = filter_value.split(',').map(|s| s.trim()).collect();
1514            let existing_derives = Self::extract_derives(attrs);
1515
1516            // Check if ANY of the required traits are present
1517            for required_trait in required_traits {
1518                if existing_derives.iter().any(|d| d == required_trait) {
1519                    return Ok(true);
1520                }
1521            }
1522            return Ok(false);
1523        }
1524
1525        // Unknown filter type - default to match (don't break existing behavior)
1526        Ok(true)
1527    }
1528
1529    /// Update or create derive attribute in the attribute list
1530    fn update_derive_attr(attrs: &mut Vec<syn::Attribute>, derives: &[&str]) -> Result<()> {
1531        let derive_str = derives.join(", ");
1532
1533        // Parse a dummy struct with the derive to extract the attribute
1534        let dummy = format!("#[derive({})]\nstruct Dummy;", derive_str);
1535        let parsed: syn::ItemStruct = parse_str(&dummy)
1536            .context("Failed to parse derive attribute")?;
1537
1538        let new_attr = parsed.attrs.into_iter()
1539            .find(|a| a.path().is_ident("derive"))
1540            .context("Failed to extract derive attribute")?;
1541
1542        // Find existing derive attribute and replace it
1543        if let Some(pos) = attrs.iter().position(|a| a.path().is_ident("derive")) {
1544            attrs[pos] = new_attr;
1545        } else {
1546            // Add new derive attribute at the beginning
1547            attrs.insert(0, new_attr);
1548        }
1549
1550        Ok(())
1551    }
1552
1553    /// Replace the modified function(s) in the content with formatted versions
1554    fn replace_modified_functions(&mut self, modified_function: &Option<String>) -> Result<()> {
1555        // If no specific function was targeted, format the entire file
1556        if modified_function.is_none() {
1557            self.content = prettyplease::unparse(&self.syntax_tree);
1558            return Ok(());
1559        }
1560
1561        // Parse the ORIGINAL content to get the correct spans
1562        let original_syntax_tree: File = syn::parse_str(&self.content)
1563            .context("Failed to re-parse original content")?;
1564
1565        let function_name = modified_function.as_ref().unwrap();
1566
1567        // Find the function in the ORIGINAL syntax tree to get correct byte positions
1568        let original_fn = original_syntax_tree.items.iter()
1569            .find_map(|item| {
1570                if let Item::Fn(f) = item {
1571                    if f.sig.ident == function_name {
1572                        return Some(f.clone());
1573                    }
1574                }
1575                None
1576            })
1577            .ok_or_else(|| anyhow::anyhow!("Function '{}' not found in original", function_name))?;
1578
1579        // Get the span of the original function (these are the correct byte positions)
1580        let start = self.span_to_byte_offset(original_fn.span().start());
1581        let end = self.span_to_byte_offset(original_fn.span().end());
1582
1583        // Find the MODIFIED function in the modified syntax tree
1584        let modified_fn = self.syntax_tree.items.iter()
1585            .find_map(|item| {
1586                if let Item::Fn(f) = item {
1587                    if f.sig.ident == function_name {
1588                        return Some(f.clone());
1589                    }
1590                }
1591                None
1592            })
1593            .ok_or_else(|| anyhow::anyhow!("Function '{}' not found in modified AST", function_name))?;
1594
1595        // Format just the modified function using prettyplease
1596        let dummy_file = syn::File {
1597            shebang: None,
1598            attrs: Vec::new(),
1599            items: vec![Item::Fn(modified_fn)],
1600        };
1601
1602        let formatted_fn = prettyplease::unparse(&dummy_file);
1603
1604        // Extract just the function (remove any extra newlines at start/end)
1605        let formatted_fn = formatted_fn.trim();
1606
1607        // Replace the function in the original content using original spans
1608        self.content.replace_range(start..end, formatted_fn);
1609
1610        Ok(())
1611    }
1612    
1613    fn span_to_byte_offset(&self, pos: LineColumn) -> usize {
1614        let line_idx = pos.line.saturating_sub(1);
1615        if line_idx < self.line_offsets.len() {
1616            self.line_offsets[line_idx] + pos.column
1617        } else {
1618            self.content.len()
1619        }
1620    }
1621    
1622    fn find_after_field_end(&self, pos: usize) -> usize {
1623        // Look for comma or newline after the field
1624        let mut i = pos;
1625        while i < self.content.len() {
1626            match self.content.as_bytes()[i] as char {
1627                ',' => return i + 1,
1628                '\n' => return i + 1,
1629                _ => i += 1,
1630            }
1631        }
1632        pos
1633    }
1634    
1635    fn get_indentation(&self, pos: usize) -> String {
1636        // Find the start of the current line
1637        let mut line_start = pos;
1638        while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
1639            line_start -= 1;
1640        }
1641        
1642        // Count spaces/tabs at the start of the line
1643        let mut indent = String::new();
1644        let mut i = line_start;
1645        while i < self.content.len() {
1646            match self.content.as_bytes()[i] as char {
1647                ' ' | '\t' => {
1648                    indent.push(self.content.as_bytes()[i] as char);
1649                    i += 1;
1650                }
1651                _ => break,
1652            }
1653        }
1654        
1655        // If we're inserting in an empty struct/enum, add default indentation
1656        if indent.is_empty() {
1657            "    ".to_string()
1658        } else {
1659            indent
1660        }
1661    }
1662    
1663    pub fn to_string(&self) -> String {
1664        self.content.clone()
1665    }
1666
1667    /// Inspect and list AST nodes (e.g., struct literals) in the file
1668    pub(crate) fn inspect(&self, node_type: &str, name_filter: Option<&str>) -> Result<Vec<crate::operations::InspectResult>> {
1669        use syn::visit::Visit;
1670        use crate::operations::InspectResult;
1671
1672        let mut results = Vec::new();
1673
1674        match node_type {
1675            "struct-literal" => {
1676                // Find all struct literal expressions
1677                struct StructLiteralVisitor<'a> {
1678                    results: &'a mut Vec<InspectResult>,
1679                    name_filter: Option<&'a str>,
1680                    editor: &'a RustEditor,
1681                }
1682
1683                impl<'ast, 'a> Visit<'ast> for StructLiteralVisitor<'a> {
1684                    fn visit_expr_struct(&mut self, node: &'ast syn::ExprStruct) {
1685                        // Match based on pattern:
1686                        // - "Rectangle" → only Rectangle { ... } (no :: prefix)
1687                        // - "*::Rectangle" → any path ending with Rectangle (View::Rectangle, etc.)
1688                        // - "View::Rectangle" → exact match only View::Rectangle
1689
1690                        let filter = match self.name_filter {
1691                            Some(f) => f,
1692                            None => {
1693                                // No filter - match anything
1694                                let struct_name = node.path.segments.last()
1695                                    .map(|seg| seg.ident.to_string())
1696                                    .unwrap_or_default();
1697
1698                                let snippet = self.editor.format_expr_struct(node);
1699                                let location = self.editor.span_to_location(node.span());
1700
1701                                self.results.push(InspectResult {
1702                                    file_path: String::new(),
1703                                    node_type: "ExprStruct".to_string(),
1704                                    identifier: struct_name,
1705                                    location,
1706                                    snippet,
1707                                });
1708
1709                                syn::visit::visit_expr_struct(self, node);
1710                                return;
1711                            }
1712                        };
1713
1714                        // Check if this struct literal matches the filter pattern
1715                        let matches = if filter.contains("::") {
1716                            // Pattern contains :: - check for exact or wildcard match
1717                            if filter.starts_with("*::") {
1718                                // Wildcard: *::Rectangle matches any path ending with Rectangle
1719                                let target_name = &filter[3..]; // Skip "*::"
1720                                node.path.segments.last()
1721                                    .map(|seg| seg.ident.to_string() == target_name)
1722                                    .unwrap_or(false)
1723                            } else {
1724                                // Exact path match: View::Rectangle
1725                                let path_str = node.path.segments.iter()
1726                                    .map(|seg| seg.ident.to_string())
1727                                    .collect::<Vec<_>>()
1728                                    .join("::");
1729                                path_str == filter
1730                            }
1731                        } else {
1732                            // No :: in pattern - only match pure struct literals (no path qualifier)
1733                            node.path.get_ident()
1734                                .map(|ident| ident.to_string() == filter)
1735                                .unwrap_or(false)
1736                        };
1737
1738                        if !matches {
1739                            syn::visit::visit_expr_struct(self, node);
1740                            return;
1741                        }
1742
1743                        // Get the struct name for the identifier
1744                        let struct_name = node.path.segments.last()
1745                            .map(|seg| seg.ident.to_string())
1746                            .unwrap_or_default();
1747
1748                        // Format the struct literal
1749                        let snippet = self.editor.format_expr_struct(node);
1750                        let location = self.editor.span_to_location(node.span());
1751
1752                        self.results.push(InspectResult {
1753                            file_path: String::new(), // Will be filled in by caller
1754                            node_type: "ExprStruct".to_string(),
1755                            identifier: struct_name,
1756                            location,
1757                            snippet,
1758                        });
1759
1760                        // Continue visiting nested expressions
1761                        syn::visit::visit_expr_struct(self, node);
1762                    }
1763                }
1764
1765                let mut visitor = StructLiteralVisitor {
1766                    results: &mut results,
1767                    name_filter,
1768                    editor: self,
1769                };
1770
1771                // Visit all items in the file
1772                for item in &self.syntax_tree.items {
1773                    syn::visit::visit_item(&mut visitor, item);
1774                }
1775            }
1776            "match-arm" => {
1777                // Find all match arms
1778                struct MatchArmVisitor<'a> {
1779                    results: &'a mut Vec<InspectResult>,
1780                    pattern_filter: Option<&'a str>,
1781                    editor: &'a RustEditor,
1782                }
1783
1784                impl<'ast, 'a> Visit<'ast> for MatchArmVisitor<'a> {
1785                    fn visit_expr_match(&mut self, node: &'ast syn::ExprMatch) {
1786                        // Iterate through all arms in this match expression
1787                        for arm in &node.arms {
1788                            // Convert pattern to string for matching
1789                            let pat = &arm.pat;
1790                            let pattern_str = quote::quote!(#pat).to_string();
1791
1792                            // Apply pattern filter if specified
1793                            if let Some(filter) = self.pattern_filter {
1794                                // Normalize both for comparison (remove spaces)
1795                                let normalized_pattern = pattern_str.replace(" ", "");
1796                                let normalized_filter = filter.replace(" ", "");
1797
1798                                if !normalized_pattern.contains(&normalized_filter) {
1799                                    continue;
1800                                }
1801                            }
1802
1803                            // Format the match arm (pattern => body)
1804                            let snippet = self.editor.format_match_arm(arm);
1805                            let location = self.editor.span_to_location(arm.span());
1806
1807                            self.results.push(InspectResult {
1808                                file_path: String::new(), // Will be filled in by caller
1809                                node_type: "MatchArm".to_string(),
1810                                identifier: pattern_str.replace(" ", ""),
1811                                location,
1812                                snippet,
1813                            });
1814                        }
1815
1816                        // Continue visiting nested expressions
1817                        syn::visit::visit_expr_match(self, node);
1818                    }
1819                }
1820
1821                let mut visitor = MatchArmVisitor {
1822                    results: &mut results,
1823                    pattern_filter: name_filter,
1824                    editor: self,
1825                };
1826
1827                // Visit all items in the file
1828                for item in &self.syntax_tree.items {
1829                    syn::visit::visit_item(&mut visitor, item);
1830                }
1831            }
1832            "enum-usage" => {
1833                // Find all enum variant usages (paths like Operator::Error)
1834                struct EnumUsageVisitor<'a> {
1835                    results: &'a mut Vec<InspectResult>,
1836                    path_filter: Option<&'a str>,
1837                    editor: &'a RustEditor,
1838                }
1839
1840                impl<'ast, 'a> Visit<'ast> for EnumUsageVisitor<'a> {
1841                    fn visit_expr_path(&mut self, node: &'ast syn::ExprPath) {
1842                        // Convert path to string
1843                        let path = &node.path;
1844                        let path_str = quote::quote!(#path).to_string();
1845
1846                        // Apply path filter if specified
1847                        if let Some(filter) = self.path_filter {
1848                            // Normalize both for comparison (remove spaces)
1849                            let normalized_path = path_str.replace(" ", "");
1850                            let normalized_filter = filter.replace(" ", "");
1851
1852                            if !normalized_path.contains(&normalized_filter) {
1853                                syn::visit::visit_expr_path(self, node);
1854                                return;
1855                            }
1856                        }
1857
1858                        // Format the path expression
1859                        let snippet = self.editor.format_expr_path(node);
1860                        let location = self.editor.span_to_location(node.span());
1861
1862                        self.results.push(InspectResult {
1863                            file_path: String::new(), // Will be filled in by caller
1864                            node_type: "ExprPath".to_string(),
1865                            identifier: path_str.replace(" ", ""),
1866                            location,
1867                            snippet,
1868                        });
1869
1870                        // Continue visiting nested expressions
1871                        syn::visit::visit_expr_path(self, node);
1872                    }
1873                }
1874
1875                let mut visitor = EnumUsageVisitor {
1876                    results: &mut results,
1877                    path_filter: name_filter,
1878                    editor: self,
1879                };
1880
1881                // Visit all items in the file
1882                for item in &self.syntax_tree.items {
1883                    syn::visit::visit_item(&mut visitor, item);
1884                }
1885            }
1886            "function-call" => {
1887                // Find all function call expressions
1888                struct FunctionCallVisitor<'a> {
1889                    results: &'a mut Vec<InspectResult>,
1890                    name_filter: Option<&'a str>,
1891                    editor: &'a RustEditor,
1892                }
1893
1894                impl<'ast, 'a> Visit<'ast> for FunctionCallVisitor<'a> {
1895                    fn visit_expr_call(&mut self, node: &'ast syn::ExprCall) {
1896                        // Extract function name from the call expression
1897                        let func_name = if let syn::Expr::Path(expr_path) = &*node.func {
1898                            // Get the last segment of the path as the function name
1899                            expr_path.path.segments.last()
1900                                .map(|seg| seg.ident.to_string())
1901                                .unwrap_or_default()
1902                        } else {
1903                            // For other expression types, use quote to convert to string
1904                            quote::quote!(#node.func).to_string()
1905                        };
1906
1907                        // Apply name filter if specified
1908                        if let Some(filter) = self.name_filter {
1909                            if func_name != filter {
1910                                syn::visit::visit_expr_call(self, node);
1911                                return;
1912                            }
1913                        }
1914
1915                        // Format the function call
1916                        let snippet = self.editor.format_expr_call(node);
1917                        let location = self.editor.span_to_location(node.span());
1918
1919                        self.results.push(InspectResult {
1920                            file_path: String::new(), // Will be filled in by caller
1921                            node_type: "ExprCall".to_string(),
1922                            identifier: func_name,
1923                            location,
1924                            snippet,
1925                        });
1926
1927                        // Continue visiting nested expressions
1928                        syn::visit::visit_expr_call(self, node);
1929                    }
1930                }
1931
1932                let mut visitor = FunctionCallVisitor {
1933                    results: &mut results,
1934                    name_filter,
1935                    editor: self,
1936                };
1937
1938                // Visit all items in the file
1939                for item in &self.syntax_tree.items {
1940                    syn::visit::visit_item(&mut visitor, item);
1941                }
1942            }
1943            "method-call" => {
1944                // Find all method call expressions
1945                struct MethodCallVisitor<'a> {
1946                    results: &'a mut Vec<InspectResult>,
1947                    name_filter: Option<&'a str>,
1948                    editor: &'a RustEditor,
1949                }
1950
1951                impl<'ast, 'a> Visit<'ast> for MethodCallVisitor<'a> {
1952                    fn visit_expr_method_call(&mut self, node: &'ast syn::ExprMethodCall) {
1953                        // Extract method name
1954                        let method_name = node.method.to_string();
1955
1956                        // Apply name filter if specified
1957                        if let Some(filter) = self.name_filter {
1958                            if method_name != filter {
1959                                syn::visit::visit_expr_method_call(self, node);
1960                                return;
1961                            }
1962                        }
1963
1964                        // Format the method call
1965                        let snippet = self.editor.format_expr_method_call(node);
1966                        let location = self.editor.span_to_location(node.span());
1967
1968                        self.results.push(InspectResult {
1969                            file_path: String::new(), // Will be filled in by caller
1970                            node_type: "ExprMethodCall".to_string(),
1971                            identifier: method_name,
1972                            location,
1973                            snippet,
1974                        });
1975
1976                        // Continue visiting nested expressions
1977                        syn::visit::visit_expr_method_call(self, node);
1978                    }
1979                }
1980
1981                let mut visitor = MethodCallVisitor {
1982                    results: &mut results,
1983                    name_filter,
1984                    editor: self,
1985                };
1986
1987                // Visit all items in the file
1988                for item in &self.syntax_tree.items {
1989                    syn::visit::visit_item(&mut visitor, item);
1990                }
1991            }
1992            "identifier" => {
1993                // Find all identifier references
1994                struct IdentifierVisitor<'a> {
1995                    results: &'a mut Vec<InspectResult>,
1996                    name_filter: Option<&'a str>,
1997                    editor: &'a RustEditor,
1998                }
1999
2000                impl<'ast, 'a> Visit<'ast> for IdentifierVisitor<'a> {
2001                    fn visit_ident(&mut self, node: &'ast syn::Ident) {
2002                        // Extract identifier name
2003                        let ident_name = node.to_string();
2004
2005                        // Apply name filter if specified
2006                        if let Some(filter) = self.name_filter {
2007                            if ident_name != filter {
2008                                syn::visit::visit_ident(self, node);
2009                                return;
2010                            }
2011                        }
2012
2013                        // Format the identifier
2014                        let snippet = self.editor.format_ident(node);
2015                        let location = self.editor.span_to_location(node.span());
2016
2017                        self.results.push(InspectResult {
2018                            file_path: String::new(), // Will be filled in by caller
2019                            node_type: "Ident".to_string(),
2020                            identifier: ident_name,
2021                            location,
2022                            snippet,
2023                        });
2024
2025                        // Continue visiting
2026                        syn::visit::visit_ident(self, node);
2027                    }
2028                }
2029
2030                let mut visitor = IdentifierVisitor {
2031                    results: &mut results,
2032                    name_filter,
2033                    editor: self,
2034                };
2035
2036                // Visit all items in the file
2037                for item in &self.syntax_tree.items {
2038                    syn::visit::visit_item(&mut visitor, item);
2039                }
2040            }
2041            "type-ref" => {
2042                // Find all type path usages
2043                struct TypeRefVisitor<'a> {
2044                    results: &'a mut Vec<InspectResult>,
2045                    name_filter: Option<&'a str>,
2046                    editor: &'a RustEditor,
2047                }
2048
2049                impl<'ast, 'a> Visit<'ast> for TypeRefVisitor<'a> {
2050                    fn visit_type_path(&mut self, node: &'ast syn::TypePath) {
2051                        // Extract type name (last segment of path)
2052                        let type_name = node.path.segments.last()
2053                            .map(|seg| seg.ident.to_string())
2054                            .unwrap_or_default();
2055
2056                        // Apply name filter if specified
2057                        if let Some(filter) = self.name_filter {
2058                            if type_name != filter {
2059                                syn::visit::visit_type_path(self, node);
2060                                return;
2061                            }
2062                        }
2063
2064                        // Format the type path
2065                        let snippet = self.editor.format_type_path(node);
2066                        let location = self.editor.span_to_location(node.span());
2067
2068                        // Get full path for identifier
2069                        let path = &node.path;
2070                        let path_str = quote::quote!(#path).to_string();
2071
2072                        self.results.push(InspectResult {
2073                            file_path: String::new(), // Will be filled in by caller
2074                            node_type: "TypePath".to_string(),
2075                            identifier: path_str.replace(" ", ""),
2076                            location,
2077                            snippet,
2078                        });
2079
2080                        // Continue visiting
2081                        syn::visit::visit_type_path(self, node);
2082                    }
2083                }
2084
2085                let mut visitor = TypeRefVisitor {
2086                    results: &mut results,
2087                    name_filter,
2088                    editor: self,
2089                };
2090
2091                // Visit all items in the file
2092                for item in &self.syntax_tree.items {
2093                    syn::visit::visit_item(&mut visitor, item);
2094                }
2095            }
2096            "macro-call" => {
2097                // Find all macro call expressions
2098                struct MacroCallVisitor<'a> {
2099                    results: &'a mut Vec<InspectResult>,
2100                    name_filter: Option<&'a str>,
2101                    editor: &'a RustEditor,
2102                }
2103
2104                impl<'ast, 'a> Visit<'ast> for MacroCallVisitor<'a> {
2105                    fn visit_expr_macro(&mut self, node: &'ast syn::ExprMacro) {
2106                        // Extract macro name from the path
2107                        let macro_name = node.mac.path.segments.last()
2108                            .map(|seg| seg.ident.to_string())
2109                            .unwrap_or_default();
2110
2111                        // Apply name filter if specified
2112                        if let Some(filter) = self.name_filter {
2113                            if macro_name != filter {
2114                                syn::visit::visit_expr_macro(self, node);
2115                                return;
2116                            }
2117                        }
2118
2119                        // Format the macro call
2120                        let snippet = self.editor.format_expr_macro(node);
2121                        let location = self.editor.span_to_location(node.span());
2122
2123                        self.results.push(InspectResult {
2124                            file_path: String::new(), // Will be filled in by caller
2125                            node_type: "ExprMacro".to_string(),
2126                            identifier: macro_name,
2127                            location,
2128                            snippet,
2129                        });
2130
2131                        // Continue visiting nested expressions
2132                        syn::visit::visit_expr_macro(self, node);
2133                    }
2134
2135                    fn visit_stmt(&mut self, node: &'ast syn::Stmt) {
2136                        // Also catch macro calls at statement level (e.g., println! as statement)
2137                        if let syn::Stmt::Macro(macro_stmt) = node {
2138                            let macro_name = macro_stmt.mac.path.segments.last()
2139                                .map(|seg| seg.ident.to_string())
2140                                .unwrap_or_default();
2141
2142                            // Apply name filter if specified
2143                            if let Some(filter) = self.name_filter {
2144                                if macro_name != filter {
2145                                    syn::visit::visit_stmt(self, node);
2146                                    return;
2147                                }
2148                            }
2149
2150                            // Format the macro call
2151                            let snippet = self.editor.format_stmt_macro(macro_stmt);
2152                            let location = self.editor.span_to_location(macro_stmt.span());
2153
2154                            self.results.push(InspectResult {
2155                                file_path: String::new(), // Will be filled in by caller
2156                                node_type: "StmtMacro".to_string(),
2157                                identifier: macro_name,
2158                                location,
2159                                snippet,
2160                            });
2161                        }
2162
2163                        // Continue visiting
2164                        syn::visit::visit_stmt(self, node);
2165                    }
2166                }
2167
2168                let mut visitor = MacroCallVisitor {
2169                    results: &mut results,
2170                    name_filter,
2171                    editor: self,
2172                };
2173
2174                // Visit all items in the file
2175                for item in &self.syntax_tree.items {
2176                    syn::visit::visit_item(&mut visitor, item);
2177                }
2178            }
2179            _ => anyhow::bail!("Unsupported node type: {}", node_type),
2180        }
2181
2182        Ok(results)
2183    }
2184
2185    /// Format an ExprStruct node as a string - extracts original source
2186    fn format_expr_struct(&self, expr: &syn::ExprStruct) -> String {
2187        // Extract the original source code from the file content using the span
2188        let start = self.span_to_byte_offset(expr.span().start());
2189        let end = self.span_to_byte_offset(expr.span().end());
2190
2191        // Get the original text and collapse to single line
2192        let original = &self.content[start..end];
2193
2194        // Replace multiple whitespace/newlines with single space for single-line format
2195        original.split_whitespace().collect::<Vec<_>>().join(" ")
2196    }
2197
2198    /// Format a match arm as a string - extracts original source
2199    fn format_match_arm(&self, arm: &syn::Arm) -> String {
2200        // Extract the original source code from the file content using the span
2201        let start = self.span_to_byte_offset(arm.span().start());
2202        let end = self.span_to_byte_offset(arm.span().end());
2203
2204        // Get the original text and collapse to single line
2205        let original = &self.content[start..end];
2206
2207        // Replace multiple whitespace/newlines with single space for single-line format
2208        original.split_whitespace().collect::<Vec<_>>().join(" ")
2209    }
2210
2211    /// Format an ExprPath node as a string - extracts original source
2212    fn format_expr_path(&self, expr: &syn::ExprPath) -> String {
2213        // Extract the original source code from the file content using the span
2214        let start = self.span_to_byte_offset(expr.span().start());
2215        let end = self.span_to_byte_offset(expr.span().end());
2216
2217        // Get the original text and collapse to single line
2218        let original = &self.content[start..end];
2219
2220        // Replace multiple whitespace/newlines with single space for single-line format
2221        original.split_whitespace().collect::<Vec<_>>().join(" ")
2222    }
2223
2224    /// Format an ExprCall node as a string - extracts original source
2225    fn format_expr_call(&self, expr: &syn::ExprCall) -> 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 an ExprMethodCall node as a string - extracts original source
2238    fn format_expr_method_call(&self, expr: &syn::ExprMethodCall) -> String {
2239        // Extract the original source code from the file content using the span
2240        let start = self.span_to_byte_offset(expr.span().start());
2241        let end = self.span_to_byte_offset(expr.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 Ident node as a string - just return the identifier
2251    fn format_ident(&self, ident: &syn::Ident) -> String {
2252        ident.to_string()
2253    }
2254
2255    /// Format a TypePath node as a string - extracts original source
2256    fn format_type_path(&self, ty: &syn::TypePath) -> String {
2257        // Extract the original source code from the file content using the span
2258        let start = self.span_to_byte_offset(ty.span().start());
2259        let end = self.span_to_byte_offset(ty.span().end());
2260
2261        // Get the original text and collapse to single line
2262        let original = &self.content[start..end];
2263
2264        // Replace multiple whitespace/newlines with single space for single-line format
2265        original.split_whitespace().collect::<Vec<_>>().join(" ")
2266    }
2267
2268    /// Format an ExprMacro node as a string - extracts original source
2269    fn format_expr_macro(&self, expr: &syn::ExprMacro) -> String {
2270        // Extract the original source code from the file content using the span
2271        let start = self.span_to_byte_offset(expr.span().start());
2272        let end = self.span_to_byte_offset(expr.span().end());
2273
2274        // Get the original text and collapse to single line
2275        let original = &self.content[start..end];
2276
2277        // Replace multiple whitespace/newlines with single space for single-line format
2278        original.split_whitespace().collect::<Vec<_>>().join(" ")
2279    }
2280
2281    /// Format a StmtMacro node as a string - extracts original source
2282    fn format_stmt_macro(&self, stmt: &syn::StmtMacro) -> String {
2283        // Extract the original source code from the file content using the span
2284        let start = self.span_to_byte_offset(stmt.span().start());
2285        let end = self.span_to_byte_offset(stmt.span().end());
2286
2287        // Get the original text and collapse to single line
2288        let original = &self.content[start..end];
2289
2290        // Replace multiple whitespace/newlines with single space for single-line format
2291        original.split_whitespace().collect::<Vec<_>>().join(" ")
2292    }
2293
2294    /// Find the index of an item by type and name
2295    #[allow(dead_code)]
2296    pub(crate) fn find_item_index(&self, node_type: &str, name: &str) -> Result<usize> {
2297        for (index, item) in self.syntax_tree.items.iter().enumerate() {
2298            match (node_type, item) {
2299                ("struct", Item::Struct(s)) if s.ident == name => {
2300                    return Ok(index);
2301                }
2302                ("enum", Item::Enum(e)) if e.ident == name => {
2303                    return Ok(index);
2304                }
2305                ("fn", Item::Fn(f)) if f.sig.ident == name => {
2306                    return Ok(index);
2307                }
2308                ("impl", Item::Impl(impl_block)) => {
2309                    // For impl blocks, match on the self_ty
2310                    if let syn::Type::Path(type_path) = &*impl_block.self_ty {
2311                        if let Some(segment) = type_path.path.segments.last() {
2312                            if segment.ident == name {
2313                                return Ok(index);
2314                            }
2315                        }
2316                    }
2317                }
2318                _ => {}
2319            }
2320        }
2321
2322        anyhow::bail!("Item '{}' of type '{}' not found", name, node_type)
2323    }
2324
2325    /// Replace an item at a specific index with a new item
2326    #[allow(dead_code)]
2327    pub(crate) fn replace_item_at_index(&mut self, index: usize, new_item: Item) -> Result<()> {
2328        if index >= self.syntax_tree.items.len() {
2329            anyhow::bail!("Index {} out of bounds", index);
2330        }
2331
2332        // Replace the item in the syntax tree
2333        self.syntax_tree.items[index] = new_item;
2334
2335        // Reformat the entire file using prettyplease
2336        self.content = prettyplease::unparse(&self.syntax_tree);
2337
2338        // Recompute line offsets
2339        self.line_offsets = Self::compute_line_offsets(&self.content);
2340
2341        Ok(())
2342    }
2343
2344    pub fn find_node(&self, node_type: &str, name: &str) -> Result<Vec<NodeLocation>> {
2345        let mut locations = Vec::new();
2346        
2347        for item in &self.syntax_tree.items {
2348            match (node_type, item) {
2349                ("struct", Item::Struct(s)) if s.ident == name => {
2350                    locations.push(self.span_to_location(s.span()));
2351                }
2352                ("enum", Item::Enum(e)) if e.ident == name => {
2353                    locations.push(self.span_to_location(e.span()));
2354                }
2355                ("fn", Item::Fn(f)) if f.sig.ident == name => {
2356                    locations.push(self.span_to_location(f.span()));
2357                }
2358                _ => {}
2359            }
2360        }
2361        
2362        if locations.is_empty() {
2363            anyhow::bail!("Node '{}' of type '{}' not found", name, node_type);
2364        }
2365        
2366        Ok(locations)
2367    }
2368    
2369    fn span_to_location(&self, span: Span) -> NodeLocation {
2370        let start = span.start();
2371        let end = span.end();
2372
2373        NodeLocation {
2374            line: start.line,
2375            column: start.column,
2376            end_line: end.line,
2377            end_column: end.column,
2378        }
2379    }
2380
2381    /// Generic transform operation - find matching nodes and apply action
2382    pub(crate) fn transform(&mut self, op: &crate::operations::TransformOp) -> Result<ModificationResult> {
2383        use crate::operations::{InspectResult, TransformAction};
2384
2385        // First, use inspect to find all matching nodes
2386        let matches = self.inspect(&op.node_type, op.name_filter.as_deref())?;
2387
2388        // Apply content filter if specified
2389        let filtered_matches: Vec<InspectResult> = if let Some(ref content_filter) = op.content_filter {
2390            matches.into_iter()
2391                .filter(|m| m.snippet.contains(content_filter))
2392                .collect()
2393        } else {
2394            matches
2395        };
2396
2397        if filtered_matches.is_empty() {
2398            return Ok(ModificationResult {
2399                changed: false,
2400                modified_nodes: vec![],
2401            });
2402        }
2403
2404        // Now apply the transformation action to each match
2405        // We need to work backwards through the file to avoid offset issues
2406        let mut sorted_matches = filtered_matches;
2407        sorted_matches.sort_by(|a, b| {
2408            b.location.line.cmp(&a.location.line)
2409                .then(b.location.column.cmp(&a.location.column))
2410        });
2411
2412        let mut modified_nodes = Vec::new();
2413
2414        for match_result in &sorted_matches {
2415            // Create backup node
2416            let backup_node = BackupNode {
2417                node_type: match_result.node_type.clone(),
2418                identifier: match_result.identifier.clone(),
2419                original_content: match_result.snippet.clone(),
2420                location: match_result.location.clone(),
2421            };
2422
2423            // Find the byte offsets for this node
2424            let start_offset = self.line_column_to_byte_offset(
2425                match_result.location.line,
2426                match_result.location.column
2427            )?;
2428            let end_offset = self.line_column_to_byte_offset(
2429                match_result.location.end_line,
2430                match_result.location.end_column
2431            )?;
2432
2433            // Extract the original text
2434            let original_text = &self.content[start_offset..end_offset];
2435
2436            // Apply the action
2437            let replacement = match &op.action {
2438                TransformAction::Comment => {
2439                    // Comment out the code
2440                    format!("// {}", original_text.replace("\n", "\n// "))
2441                }
2442                TransformAction::Remove => {
2443                    // Remove the entire node
2444                    String::new()
2445                }
2446                TransformAction::Replace { with } => {
2447                    // Replace with provided code
2448                    with.clone()
2449                }
2450            };
2451
2452            // Replace in content
2453            self.content.replace_range(start_offset..end_offset, &replacement);
2454
2455            // Recompute line offsets after each change
2456            self.line_offsets = Self::compute_line_offsets(&self.content);
2457
2458            modified_nodes.push(backup_node);
2459        }
2460
2461        // Re-parse the content if we made changes
2462        if !modified_nodes.is_empty() {
2463            // Don't reparse for now - we're doing text-level operations
2464            // self.syntax_tree = syn::parse_str(&self.content)
2465            //     .context("Failed to re-parse content after transformation")?;
2466        }
2467
2468        Ok(ModificationResult {
2469            changed: !modified_nodes.is_empty(),
2470            modified_nodes,
2471        })
2472    }
2473
2474    /// Rename an enum variant across the entire file
2475    pub(crate) fn rename_enum_variant(&mut self, op: &crate::operations::RenameEnumVariantOp) -> Result<ModificationResult> {
2476        // Create a visitor that will rename the variant everywhere
2477        // Note: We don't require the enum to be defined in this file - it may just be used here
2478        let mut renamer = EnumVariantRenamer {
2479            enum_name: op.enum_name.clone(),
2480            old_variant: op.old_variant.clone(),
2481            new_variant: op.new_variant.clone(),
2482            modified: false,
2483        };
2484
2485        // Visit and mutate the syntax tree
2486        renamer.visit_file_mut(&mut self.syntax_tree);
2487
2488        if !renamer.modified {
2489            return Ok(ModificationResult {
2490                changed: false,
2491                modified_nodes: vec![],
2492            });
2493        }
2494
2495        // Reformat the entire file using prettyplease
2496        self.content = prettyplease::unparse(&self.syntax_tree);
2497
2498        // Recompute line offsets
2499        self.line_offsets = Self::compute_line_offsets(&self.content);
2500
2501        // Create a backup node for the entire file operation
2502        let backup_node = BackupNode {
2503            node_type: "EnumVariantRename".to_string(),
2504            identifier: format!("{}::{} -> {}", op.enum_name, op.old_variant, op.new_variant),
2505            original_content: format!("Renamed {} to {} in enum {}", op.old_variant, op.new_variant, op.enum_name),
2506            location: NodeLocation {
2507                line: 1,
2508                column: 0,
2509                end_line: 1,
2510                end_column: 0,
2511            },
2512        };
2513
2514        Ok(ModificationResult {
2515            changed: true,
2516            modified_nodes: vec![backup_node],
2517        })
2518    }
2519
2520    /// Convert line/column to byte offset
2521    fn line_column_to_byte_offset(&self, line: usize, column: usize) -> Result<usize> {
2522        if line == 0 || line > self.line_offsets.len() {
2523            anyhow::bail!("Line {} out of range", line);
2524        }
2525
2526        let line_start = self.line_offsets[line - 1];
2527        Ok(line_start + column)
2528    }
2529}
2530
2531// Visitor for adding match arms
2532struct MatchArmAdder {
2533    target_function: Option<String>,
2534    arm_to_add: Arm,
2535    modified: bool,
2536    current_function: Option<String>,
2537    modified_function: Option<String>,
2538}
2539
2540impl VisitMut for MatchArmAdder {
2541    fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
2542        let prev_fn = self.current_function.clone();
2543        self.current_function = Some(node.sig.ident.to_string());
2544
2545        // Continue visiting nested items
2546        syn::visit_mut::visit_item_fn_mut(self, node);
2547
2548        self.current_function = prev_fn;
2549    }
2550
2551    fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
2552        // Check if we're in the right function (if specified)
2553        if let Some(ref target) = self.target_function {
2554            if self.current_function.as_ref() != Some(target) {
2555                // Continue visiting nested expressions
2556                syn::visit_mut::visit_expr_match_mut(self, node);
2557                return;
2558            }
2559        }
2560
2561        // Check if the pattern already exists (idempotent)
2562        let pattern_str = self.arm_to_add.pat.to_token_stream().to_string();
2563        let already_exists = node.arms.iter().any(|arm| {
2564            arm.pat.to_token_stream().to_string() == pattern_str
2565        });
2566
2567        if !already_exists {
2568            // Add the arm to the end
2569            node.arms.push(self.arm_to_add.clone());
2570            self.modified = true;
2571            self.modified_function = self.current_function.clone();
2572        }
2573
2574        // Continue visiting nested expressions
2575        syn::visit_mut::visit_expr_match_mut(self, node);
2576    }
2577}
2578
2579// Visitor for updating match arms
2580struct MatchArmUpdater {
2581    target_function: Option<String>,
2582    pattern_to_match: String,
2583    new_body: syn::Expr,
2584    modified: bool,
2585    current_function: Option<String>,
2586    modified_function: Option<String>,
2587}
2588
2589impl VisitMut for MatchArmUpdater {
2590    fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
2591        let prev_fn = self.current_function.clone();
2592        self.current_function = Some(node.sig.ident.to_string());
2593
2594        syn::visit_mut::visit_item_fn_mut(self, node);
2595
2596        self.current_function = prev_fn;
2597    }
2598
2599    fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
2600        // Check if we're in the right function (if specified)
2601        if let Some(ref target) = self.target_function {
2602            if self.current_function.as_ref() != Some(target) {
2603                syn::visit_mut::visit_expr_match_mut(self, node);
2604                return;
2605            }
2606        }
2607
2608        // Find and update the matching arm
2609        for arm in &mut node.arms {
2610            let pattern_str = arm.pat.to_token_stream().to_string();
2611            // Normalize whitespace for comparison
2612            let pattern_normalized = pattern_str.replace(" ", "");
2613            let target_normalized = self.pattern_to_match.replace(" ", "");
2614
2615            if pattern_normalized == target_normalized {
2616                arm.body = Box::new(self.new_body.clone());
2617                self.modified = true;
2618                self.modified_function = self.current_function.clone();
2619                break;
2620            }
2621        }
2622
2623        syn::visit_mut::visit_expr_match_mut(self, node);
2624    }
2625}
2626
2627// Visitor for removing match arms
2628struct MatchArmRemover {
2629    target_function: Option<String>,
2630    pattern_to_remove: String,
2631    modified: bool,
2632    current_function: Option<String>,
2633    modified_function: Option<String>,
2634}
2635
2636impl VisitMut for MatchArmRemover {
2637    fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
2638        let prev_fn = self.current_function.clone();
2639        self.current_function = Some(node.sig.ident.to_string());
2640
2641        syn::visit_mut::visit_item_fn_mut(self, node);
2642
2643        self.current_function = prev_fn;
2644    }
2645
2646    fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
2647        // Check if we're in the right function (if specified)
2648        if let Some(ref target) = self.target_function {
2649            if self.current_function.as_ref() != Some(target) {
2650                syn::visit_mut::visit_expr_match_mut(self, node);
2651                return;
2652            }
2653        }
2654
2655        // Find and remove the matching arm
2656        let mut index_to_remove = None;
2657        for (i, arm) in node.arms.iter().enumerate() {
2658            let pattern_str = arm.pat.to_token_stream().to_string();
2659            // Normalize whitespace for comparison
2660            let pattern_normalized = pattern_str.replace(" ", "");
2661            let target_normalized = self.pattern_to_remove.replace(" ", "");
2662
2663            if pattern_normalized == target_normalized {
2664                index_to_remove = Some(i);
2665                break;
2666            }
2667        }
2668
2669        if let Some(index) = index_to_remove {
2670            node.arms.remove(index);
2671            self.modified = true;
2672            self.modified_function = self.current_function.clone();
2673        }
2674
2675        syn::visit_mut::visit_expr_match_mut(self, node);
2676    }
2677}
2678
2679// Visitor for adding multiple match arms at once (for auto-detect)
2680struct MultiMatchArmAdder {
2681    target_function: Option<String>,
2682    arms_to_add: Vec<(String, Arm)>,  // (pattern_string, arm)
2683    modified: bool,
2684    current_function: Option<String>,
2685    modified_function: Option<String>,
2686}
2687
2688impl VisitMut for MultiMatchArmAdder {
2689    fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
2690        let prev_fn = self.current_function.clone();
2691        self.current_function = Some(node.sig.ident.to_string());
2692
2693        syn::visit_mut::visit_item_fn_mut(self, node);
2694
2695        self.current_function = prev_fn;
2696    }
2697
2698    fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
2699        // Check if we're in the right function (if specified)
2700        if let Some(ref target) = self.target_function {
2701            if self.current_function.as_ref() != Some(target) {
2702                syn::visit_mut::visit_expr_match_mut(self, node);
2703                return;
2704            }
2705        }
2706
2707        // Add all missing arms
2708        for (pattern_str, arm) in &self.arms_to_add {
2709            // Check if the pattern already exists (idempotent)
2710            let already_exists = node.arms.iter().any(|existing_arm| {
2711                existing_arm.pat.to_token_stream().to_string() == *pattern_str
2712            });
2713
2714            if !already_exists {
2715                node.arms.push(arm.clone());
2716                self.modified = true;
2717                self.modified_function = self.current_function.clone();
2718            }
2719        }
2720
2721        syn::visit_mut::visit_expr_match_mut(self, node);
2722    }
2723}
2724
2725// Visitor for adding fields to struct literal expressions
2726struct StructLiteralFieldAdder {
2727    struct_name: String,
2728    field_def: String,
2729    field_name: String,
2730    position: InsertPosition,
2731    modified: bool,
2732}
2733
2734impl VisitMut for StructLiteralFieldAdder {
2735    fn visit_expr_mut(&mut self, node: &mut Expr) {
2736        // Check if this is a struct literal expression
2737        if let Expr::Struct(expr_struct) = node {
2738            // Match based on pattern:
2739            // - "Rectangle" → only Rectangle { ... } (no :: prefix)
2740            // - "*::Rectangle" → any path ending with Rectangle (View::Rectangle, etc.)
2741            // - "View::Rectangle" → exact match only View::Rectangle
2742
2743            let is_match = if self.struct_name.contains("::") {
2744                // Pattern contains :: - check for exact or wildcard match
2745                if self.struct_name.starts_with("*::") {
2746                    // Wildcard: *::Rectangle matches any path ending with Rectangle
2747                    let target_name = &self.struct_name[3..]; // Skip "*::"
2748                    expr_struct.path.segments.last()
2749                        .map(|seg| seg.ident.to_string() == target_name)
2750                        .unwrap_or(false)
2751                } else {
2752                    // Exact path match: View::Rectangle
2753                    let path_str = expr_struct.path.segments.iter()
2754                        .map(|seg| seg.ident.to_string())
2755                        .collect::<Vec<_>>()
2756                        .join("::");
2757                    path_str == self.struct_name
2758                }
2759            } else {
2760                // No :: in pattern - only match pure struct literals (no path qualifier)
2761                expr_struct.path.segments.len() == 1
2762                    && expr_struct.path.segments.last()
2763                        .map(|seg| seg.ident.to_string())
2764                        .as_ref() == Some(&self.struct_name)
2765            };
2766
2767            if is_match {
2768                // Check if field already exists (idempotent)
2769                let field_exists = expr_struct.fields.iter().any(|fv| {
2770                    fv.member.to_token_stream().to_string() == self.field_name
2771                });
2772
2773                if !field_exists {
2774                    // Parse the field value from field_def
2775                    // field_def is like "return_type: None"
2776                    let field_value_code = format!("{{ {} }}", self.field_def);
2777                    if let Ok(expr) = parse_str::<ExprStruct>(&format!("Dummy {}", field_value_code)) {
2778                        if let Some(new_fv) = expr.fields.first() {
2779                            // Determine where to insert
2780                            match &self.position {
2781                                InsertPosition::First => {
2782                                    expr_struct.fields.insert(0, new_fv.clone());
2783                                    self.modified = true;
2784                                }
2785                                InsertPosition::Last => {
2786                                    expr_struct.fields.push(new_fv.clone());
2787                                    self.modified = true;
2788                                }
2789                                InsertPosition::After(after_field) => {
2790                                    // Find the position of the field to insert after
2791                                    if let Some(pos) = expr_struct.fields.iter().position(|fv| {
2792                                        fv.member.to_token_stream().to_string() == *after_field
2793                                    }) {
2794                                        expr_struct.fields.insert(pos + 1, new_fv.clone());
2795                                        self.modified = true;
2796                                    }
2797                                }
2798                                InsertPosition::Before(before_field) => {
2799                                    // Find the position of the field to insert before
2800                                    if let Some(pos) = expr_struct.fields.iter().position(|fv| {
2801                                        fv.member.to_token_stream().to_string() == *before_field
2802                                    }) {
2803                                        expr_struct.fields.insert(pos, new_fv.clone());
2804                                        self.modified = true;
2805                                    }
2806                                }
2807                            }
2808                        }
2809                    }
2810                }
2811            }
2812        }
2813
2814        // IMPORTANT: Visit children AFTER processing this node
2815        // This ensures we traverse into nested expressions
2816        syn::visit_mut::visit_expr_mut(self, node);
2817    }
2818}
2819
2820// Visitor for renaming enum variants
2821struct EnumVariantRenamer {
2822    enum_name: String,
2823    old_variant: String,
2824    new_variant: String,
2825    modified: bool,
2826}
2827
2828impl EnumVariantRenamer {
2829    /// Rename a path segment if it matches
2830    fn rename_path(&mut self, path: &mut syn::Path) {
2831        if path.segments.len() == 2 {
2832            if path.segments[0].ident == self.enum_name
2833                && path.segments[1].ident == self.old_variant
2834            {
2835                path.segments[1].ident = syn::Ident::new(&self.new_variant, path.segments[1].ident.span());
2836                self.modified = true;
2837            }
2838        } else if path.segments.len() == 1 {
2839            if path.segments[0].ident == self.old_variant {
2840                path.segments[0].ident = syn::Ident::new(&self.new_variant, path.segments[0].ident.span());
2841                self.modified = true;
2842            }
2843        }
2844    }
2845}
2846
2847impl VisitMut for EnumVariantRenamer {
2848    /// Rename in enum definition
2849    fn visit_item_enum_mut(&mut self, node: &mut syn::ItemEnum) {
2850        if node.ident == self.enum_name {
2851            for variant in &mut node.variants {
2852                if variant.ident == self.old_variant {
2853                    variant.ident = syn::Ident::new(&self.new_variant, variant.ident.span());
2854                    self.modified = true;
2855                }
2856            }
2857        }
2858
2859        // Continue visiting nested items
2860        syn::visit_mut::visit_item_enum_mut(self, node);
2861    }
2862
2863    /// Rename in patterns (match arms, let bindings, function parameters, etc.)
2864    fn visit_pat_mut(&mut self, pat: &mut syn::Pat) {
2865        match pat {
2866            syn::Pat::TupleStruct(tuple_struct) => {
2867                self.rename_path(&mut tuple_struct.path);
2868            }
2869            syn::Pat::Struct(struct_pat) => {
2870                self.rename_path(&mut struct_pat.path);
2871            }
2872            syn::Pat::Path(path_pat) => {
2873                self.rename_path(&mut path_pat.path);
2874            }
2875            _ => {}
2876        }
2877
2878        // Continue visiting nested patterns
2879        syn::visit_mut::visit_pat_mut(self, pat);
2880    }
2881
2882    /// Rename in expressions (constructor calls, references, etc.)
2883    fn visit_expr_mut(&mut self, expr: &mut syn::Expr) {
2884        match expr {
2885            syn::Expr::Path(expr_path) => {
2886                self.rename_path(&mut expr_path.path);
2887            }
2888            syn::Expr::Call(call) => {
2889                if let syn::Expr::Path(path) = &mut *call.func {
2890                    self.rename_path(&mut path.path);
2891                }
2892            }
2893            syn::Expr::Struct(struct_expr) => {
2894                self.rename_path(&mut struct_expr.path);
2895            }
2896            _ => {}
2897        }
2898
2899        // Continue visiting nested expressions
2900        syn::visit_mut::visit_expr_mut(self, expr);
2901    }
2902}