formualizer_eval/engine/graph/editor/
vertex_editor.rs

1use crate::SheetId;
2use crate::engine::graph::DependencyGraph;
3use crate::engine::graph::editor::reference_adjuster::{ReferenceAdjuster, ShiftOperation};
4use crate::engine::named_range::{NameScope, NamedDefinition};
5use crate::engine::packed_coord::PackedCoord;
6use crate::engine::{ChangeEvent, ChangeLogger, VertexId, VertexKind};
7use crate::reference::{CellRef, Coord};
8use formualizer_common::{ExcelError, ExcelErrorKind, LiteralValue};
9use formualizer_parse::parser::ASTNode;
10use std::sync::atomic::{AtomicU64, Ordering};
11
12/// Metadata for creating a new vertex
13#[derive(Debug, Clone)]
14pub struct VertexMeta {
15    pub coord: PackedCoord,
16    pub sheet_id: SheetId,
17    pub kind: VertexKind,
18    pub flags: u8,
19}
20
21impl VertexMeta {
22    pub fn new(row: u32, col: u32, sheet_id: SheetId, kind: VertexKind) -> Self {
23        Self {
24            coord: PackedCoord::new(row, col),
25            sheet_id,
26            kind,
27            flags: 0,
28        }
29    }
30
31    pub fn with_flags(mut self, flags: u8) -> Self {
32        self.flags = flags;
33        self
34    }
35
36    pub fn dirty(mut self) -> Self {
37        self.flags |= 0x01;
38        self
39    }
40
41    pub fn volatile(mut self) -> Self {
42        self.flags |= 0x02;
43        self
44    }
45}
46
47/// Patch for updating vertex metadata
48#[derive(Debug, Clone)]
49pub struct VertexMetaPatch {
50    pub kind: Option<VertexKind>,
51    pub coord: Option<PackedCoord>,
52    pub dirty: Option<bool>,
53    pub volatile: Option<bool>,
54}
55
56/// Patch for updating vertex data
57#[derive(Debug, Clone)]
58pub struct VertexDataPatch {
59    pub value: Option<LiteralValue>,
60    pub formula: Option<ASTNode>,
61}
62
63/// Summary of metadata update
64#[derive(Debug, Clone, Default)]
65pub struct MetaUpdateSummary {
66    pub coord_changed: bool,
67    pub kind_changed: bool,
68    pub flags_changed: bool,
69}
70
71/// Summary of data update
72#[derive(Debug, Clone, Default)]
73pub struct DataUpdateSummary {
74    pub value_changed: bool,
75    pub formula_changed: bool,
76    pub dependents_marked_dirty: Vec<VertexId>,
77}
78
79/// Summary of shift operations (row/column insert/delete)
80#[derive(Debug, Clone, Default)]
81pub struct ShiftSummary {
82    pub vertices_moved: Vec<VertexId>,
83    pub vertices_deleted: Vec<VertexId>,
84    pub references_adjusted: usize,
85    pub formulas_updated: usize,
86}
87
88/// Summary of range operations
89#[derive(Debug, Clone, Default)]
90pub struct RangeSummary {
91    pub cells_affected: usize,
92    pub vertices_created: Vec<VertexId>,
93    pub vertices_updated: Vec<VertexId>,
94    pub cells_moved: usize,
95}
96
97/// Transaction ID for tracking active transactions
98#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99pub struct TransactionId(u64);
100
101impl TransactionId {
102    fn new() -> Self {
103        static COUNTER: AtomicU64 = AtomicU64::new(0);
104        TransactionId(COUNTER.fetch_add(1, Ordering::Relaxed))
105    }
106}
107
108/// Represents an active transaction
109#[derive(Debug)]
110struct Transaction {
111    id: TransactionId,
112    start_index: usize, // Index in change_log where transaction started
113}
114
115/// Custom error type for vertex editor operations
116#[derive(Debug, Clone)]
117pub enum EditorError {
118    TargetOccupied { cell: CellRef },
119    OutOfBounds { row: u32, col: u32 },
120    InvalidName { name: String, reason: String },
121    TransactionFailed { reason: String },
122    NoActiveTransaction,
123    VertexNotFound { id: VertexId },
124    Excel(ExcelError),
125}
126
127impl From<ExcelError> for EditorError {
128    fn from(e: ExcelError) -> Self {
129        EditorError::Excel(e)
130    }
131}
132
133impl std::fmt::Display for EditorError {
134    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135        match self {
136            EditorError::TargetOccupied { cell } => {
137                write!(
138                    f,
139                    "Target cell occupied at row {}, col {}",
140                    cell.coord.row, cell.coord.col
141                )
142            }
143            EditorError::OutOfBounds { row, col } => {
144                write!(f, "Cell position out of bounds: row {row}, col {col}")
145            }
146            EditorError::InvalidName { name, reason } => {
147                write!(f, "Invalid name '{name}': {reason}")
148            }
149            EditorError::TransactionFailed { reason } => {
150                write!(f, "Transaction failed: {reason}")
151            }
152            EditorError::NoActiveTransaction => {
153                write!(f, "No active transaction")
154            }
155            EditorError::VertexNotFound { id } => {
156                write!(f, "Vertex not found: {id:?}")
157            }
158            EditorError::Excel(e) => write!(f, "Excel error: {e:?}"),
159        }
160    }
161}
162
163impl std::error::Error for EditorError {}
164
165/// Builder/controller object that provides exclusive access to the dependency graph
166/// for all mutation operations. This ensures consistency and proper change tracking.
167/// # Example Usage
168///
169/// ```rust
170/// use formualizer_eval::engine::{DependencyGraph, VertexEditor, VertexMeta, VertexKind};
171/// use formualizer_common::LiteralValue;
172/// use formualizer_eval::reference::{CellRef, Coord};
173///
174/// let mut graph = DependencyGraph::new();
175/// let mut editor = VertexEditor::new(&mut graph);
176///
177/// // Batch operations for better performance
178/// editor.begin_batch();
179///
180/// // Create a new cell vertex
181/// let meta = VertexMeta::new(1, 1, 0, VertexKind::Cell).dirty();
182/// let vertex_id = editor.add_vertex(meta);
183///
184/// // Set cell values
185/// let cell_ref = CellRef {
186///     sheet_id: 0,
187///     coord: Coord::new(2, 3, true, true)
188/// };
189/// editor.set_cell_value(cell_ref, LiteralValue::Number(42.0));
190///
191/// // Commit batch operations
192/// editor.commit_batch();
193///
194/// ```
195pub struct VertexEditor<'g> {
196    graph: &'g mut DependencyGraph,
197    change_logger: Option<&'g mut dyn ChangeLogger>,
198    batch_mode: bool,
199}
200
201impl<'g> VertexEditor<'g> {
202    /// Create a new vertex editor without change logging
203    pub fn new(graph: &'g mut DependencyGraph) -> Self {
204        Self {
205            graph,
206            change_logger: None,
207            batch_mode: false,
208        }
209    }
210
211    /// Create a new vertex editor with change logging
212    pub fn with_logger<L: ChangeLogger + 'g>(
213        graph: &'g mut DependencyGraph,
214        logger: &'g mut L,
215    ) -> Self {
216        Self {
217            graph,
218            change_logger: Some(logger as &'g mut dyn ChangeLogger),
219            batch_mode: false,
220        }
221    }
222
223    /// Start batch mode to defer expensive operations until commit
224    pub fn begin_batch(&mut self) {
225        if !self.batch_mode {
226            self.graph.begin_batch();
227            self.batch_mode = true;
228        }
229    }
230
231    /// End batch mode and commit all deferred operations
232    pub fn commit_batch(&mut self) {
233        if self.batch_mode {
234            self.graph.end_batch();
235            self.batch_mode = false;
236        }
237    }
238
239    /// Helper method to log a change event
240    fn log_change(&mut self, event: ChangeEvent) {
241        if let Some(logger) = &mut self.change_logger {
242            logger.record(event);
243        }
244    }
245
246    /// Check if change logging is enabled
247    pub fn has_logger(&self) -> bool {
248        self.change_logger.is_some()
249    }
250
251    // Transaction support
252
253    // Transaction support has been moved to TransactionContext
254    // which coordinates ChangeLog, TransactionManager, and VertexEditor
255
256    /// Apply the inverse of a change event (used by TransactionContext for rollback)
257    pub fn apply_inverse(&mut self, change: ChangeEvent) -> Result<(), EditorError> {
258        match change {
259            ChangeEvent::SetValue { addr, old, new: _ } => {
260                // Restore old value
261                if let Some(old_value) = old {
262                    self.set_cell_value(addr, old_value);
263                } else {
264                    // Cell didn't exist before, remove it
265                    if let Some(&id) = self.graph.get_vertex_id_for_address(&addr) {
266                        self.remove_vertex(id)?;
267                    }
268                }
269            }
270            ChangeEvent::SetFormula { addr, old, new: _ } => {
271                // Restore old formula
272                if let Some(old_formula) = old {
273                    self.set_cell_formula(addr, old_formula);
274                } else {
275                    // Cell didn't have formula before, remove it or set to value
276                    if let Some(&id) = self.graph.get_vertex_id_for_address(&addr) {
277                        self.remove_vertex(id)?;
278                    }
279                }
280            }
281            ChangeEvent::AddVertex { id, .. } => {
282                // Inverse of AddVertex is removal
283                let _ = self.remove_vertex(id); // ignore errors for now
284            }
285            ChangeEvent::RemoveVertex {
286                id: _,
287                old_value,
288                old_formula,
289                old_dependencies,
290                old_dependents,
291                coord,
292                sheet_id,
293                kind,
294                ..
295            } => {
296                if let (Some(c), Some(sid)) = (coord, sheet_id) {
297                    let meta =
298                        VertexMeta::new(c.row(), c.col(), sid, kind.unwrap_or(VertexKind::Cell));
299                    let new_id = self.add_vertex(meta);
300                    if let Some(v) = old_value {
301                        let cell_ref = self.graph.make_cell_ref_internal(sid, c.row(), c.col());
302                        self.set_cell_value(cell_ref, v);
303                    }
304                    if let Some(f) = old_formula {
305                        let cell_ref = self.graph.make_cell_ref_internal(sid, c.row(), c.col());
306                        self.set_cell_formula(cell_ref, f);
307                    }
308                    for dep in old_dependencies {
309                        self.graph.add_dependency_edge(new_id, dep);
310                    }
311                    for parent in old_dependents {
312                        self.graph.add_dependency_edge(parent, new_id);
313                    }
314                }
315            }
316            ChangeEvent::DefineName { name, scope, .. } => {
317                // Inverse is delete name
318                self.graph.delete_name(&name, scope)?;
319            }
320            ChangeEvent::UpdateName {
321                name,
322                scope,
323                old_definition,
324                ..
325            } => {
326                // Restore old definition
327                self.graph.update_name(&name, old_definition, scope)?;
328            }
329            ChangeEvent::DeleteName {
330                name,
331                scope,
332                old_definition,
333            } => {
334                if let Some(def) = old_definition {
335                    self.graph.define_name(&name, def, scope)?;
336                } else {
337                    return Err(EditorError::TransactionFailed {
338                        reason: "Missing old definition for name deletion rollback".to_string(),
339                    });
340                }
341            }
342            // Granular events for compound operations
343            ChangeEvent::CompoundStart { .. } | ChangeEvent::CompoundEnd { .. } => {
344                // These are markers, no inverse needed
345            }
346            ChangeEvent::VertexMoved { id, old_coord, .. } => {
347                // Move back to old position
348                self.move_vertex(id, old_coord)?;
349            }
350            ChangeEvent::FormulaAdjusted { id, old_ast, .. } => {
351                // Restore old formula
352                // TODO: Need a method to update formula by vertex ID
353                return Err(EditorError::TransactionFailed {
354                    reason: "Cannot rollback formula adjustment yet".to_string(),
355                });
356            }
357            ChangeEvent::NamedRangeAdjusted {
358                name,
359                scope,
360                old_definition,
361                ..
362            } => {
363                // Restore old definition
364                self.graph.update_name(&name, old_definition, scope)?;
365            }
366            ChangeEvent::EdgeAdded { from, to } => {
367                // Remove the edge
368                // TODO: Need specific edge removal method
369                return Err(EditorError::TransactionFailed {
370                    reason: "Cannot rollback edge addition yet".to_string(),
371                });
372            }
373            ChangeEvent::EdgeRemoved { from, to } => {
374                // Re-add the edge
375                // TODO: Need specific edge addition method
376                return Err(EditorError::TransactionFailed {
377                    reason: "Cannot rollback edge removal yet".to_string(),
378                });
379            }
380        }
381        Ok(())
382    }
383
384    /// Add a vertex to the graph
385    pub fn add_vertex(&mut self, meta: VertexMeta) -> VertexId {
386        // For now, use the existing set_cell_value method to create vertices
387        // This is a simplified implementation that works with the current API
388        let sheet_name = self.graph.sheet_name(meta.sheet_id).to_string();
389
390        let id = match meta.kind {
391            VertexKind::Cell => {
392                // Create with empty value initially
393                match self.graph.set_cell_value(
394                    &sheet_name,
395                    meta.coord.row(),
396                    meta.coord.col(),
397                    LiteralValue::Empty,
398                ) {
399                    Ok(summary) => summary
400                        .affected_vertices
401                        .into_iter()
402                        .next()
403                        .unwrap_or(VertexId::new(0)),
404                    Err(_) => VertexId::new(0),
405                }
406            }
407            _ => {
408                // For now, treat other kinds as cells
409                // A full implementation would handle different vertex kinds properly
410                match self.graph.set_cell_value(
411                    &sheet_name,
412                    meta.coord.row(),
413                    meta.coord.col(),
414                    LiteralValue::Empty,
415                ) {
416                    Ok(summary) => summary
417                        .affected_vertices
418                        .into_iter()
419                        .next()
420                        .unwrap_or(VertexId::new(0)),
421                    Err(_) => VertexId::new(0),
422                }
423            }
424        };
425
426        if self.has_logger() && id.0 != 0 {
427            self.log_change(ChangeEvent::AddVertex {
428                id,
429                coord: meta.coord,
430                sheet_id: meta.sheet_id,
431                value: Some(LiteralValue::Empty),
432                formula: None,
433                kind: Some(meta.kind),
434                flags: Some(meta.flags),
435            });
436        }
437        id
438    }
439
440    /// Remove a vertex from the graph with proper cleanup
441    pub fn remove_vertex(&mut self, id: VertexId) -> Result<(), EditorError> {
442        // Check if vertex exists
443        if !self.graph.vertex_exists(id) {
444            return Err(EditorError::Excel(
445                ExcelError::new(ExcelErrorKind::Ref).with_message("Vertex does not exist"),
446            ));
447        }
448
449        // Get dependents before removing edges
450        // Note: get_dependents may require CSR rebuild if delta has changes
451        let dependents = self.graph.get_dependents(id);
452
453        // Capture old state (dependencies & dependents) BEFORE edge removal
454        let (
455            old_value,
456            old_formula,
457            old_dependencies,
458            old_dependents,
459            coord,
460            sheet_id_opt,
461            kind,
462            flags,
463        ) = if self.has_logger() {
464            let coord = self.graph.get_coord(id);
465            let sheet_id = self.graph.get_sheet_id(id);
466            let kind = self.graph.get_vertex_kind(id);
467            // flags not publicly exposed; set to 0 for now (future: expose getter)
468            let flags = 0u8;
469            (
470                self.graph.get_value(id),
471                self.graph.get_formula(id),
472                self.graph.get_dependencies(id), // outgoing deps
473                dependents.clone(),              // captured earlier
474                Some(coord),
475                Some(sheet_id),
476                Some(kind),
477                Some(flags),
478            )
479        } else {
480            (None, None, vec![], vec![], None, None, None, None)
481        };
482
483        // Remove from cell mapping if it exists
484        if let Some(cell_ref) = self.graph.get_cell_ref_for_vertex(id) {
485            self.graph.remove_cell_mapping(&cell_ref);
486        }
487
488        // Remove all edges
489        self.graph.remove_all_edges(id);
490
491        // Mark all dependents as having #REF! error
492        for dep_id in &dependents {
493            self.graph.mark_as_ref_error(*dep_id);
494        }
495
496        // Mark as deleted in store (tombstone)
497        self.graph.mark_deleted(id, true);
498
499        // Log change event
500        self.log_change(ChangeEvent::RemoveVertex {
501            id,
502            old_value,
503            old_formula,
504            old_dependencies,
505            old_dependents,
506            coord,
507            sheet_id: sheet_id_opt,
508            kind,
509            flags,
510        });
511
512        Ok(())
513    }
514
515    /// Convenience: remove vertex at a given cell ref if exists
516    pub fn remove_vertex_at(&mut self, cell: CellRef) -> Result<(), EditorError> {
517        if let Some(id) = self.graph.get_vertex_for_cell(&cell) {
518            self.remove_vertex(id)
519        } else {
520            Ok(())
521        }
522    }
523
524    /// Move a vertex to a new position
525    pub fn move_vertex(&mut self, id: VertexId, new_coord: PackedCoord) -> Result<(), EditorError> {
526        // Check if vertex exists
527        if !self.graph.vertex_exists(id) {
528            return Err(EditorError::Excel(
529                ExcelError::new(ExcelErrorKind::Ref).with_message("Vertex does not exist"),
530            ));
531        }
532
533        // Get old cell reference
534        let old_cell_ref = self.graph.get_cell_ref_for_vertex(id);
535
536        // Create new cell reference
537        let sheet_id = self.graph.get_sheet_id(id);
538        let new_cell_ref = CellRef::new(
539            sheet_id,
540            Coord::new(new_coord.row(), new_coord.col(), true, true),
541        );
542
543        // Update coordinate in store
544        self.graph.set_coord(id, new_coord);
545
546        // Update edge cache coordinate if needed
547        self.graph.update_edge_coord(id, new_coord);
548
549        // Update cell mapping
550        self.graph
551            .update_cell_mapping(id, old_cell_ref, new_cell_ref);
552
553        // Mark dependents as dirty
554        self.graph.mark_dependents_dirty(id);
555
556        Ok(())
557    }
558
559    /// Update vertex metadata
560    pub fn patch_vertex_meta(
561        &mut self,
562        id: VertexId,
563        patch: VertexMetaPatch,
564    ) -> Result<MetaUpdateSummary, EditorError> {
565        if !self.graph.vertex_exists(id) {
566            return Err(EditorError::Excel(
567                ExcelError::new(ExcelErrorKind::Ref).with_message("Vertex does not exist"),
568            ));
569        }
570
571        let mut summary = MetaUpdateSummary::default();
572
573        if let Some(coord) = patch.coord {
574            self.graph.set_coord(id, coord);
575            self.graph.update_edge_coord(id, coord);
576            summary.coord_changed = true;
577        }
578
579        if let Some(kind) = patch.kind {
580            self.graph.set_kind(id, kind);
581            summary.kind_changed = true;
582        }
583
584        if let Some(dirty) = patch.dirty {
585            self.graph.set_dirty(id, dirty);
586            summary.flags_changed = true;
587        }
588
589        if let Some(volatile) = patch.volatile {
590            self.graph.mark_volatile(id, volatile);
591            summary.flags_changed = true;
592        }
593
594        Ok(summary)
595    }
596
597    /// Update vertex data (value or formula)
598    pub fn patch_vertex_data(
599        &mut self,
600        id: VertexId,
601        patch: VertexDataPatch,
602    ) -> Result<DataUpdateSummary, EditorError> {
603        if !self.graph.vertex_exists(id) {
604            return Err(EditorError::Excel(
605                ExcelError::new(ExcelErrorKind::Ref).with_message("Vertex does not exist"),
606            ));
607        }
608
609        let mut summary = DataUpdateSummary::default();
610
611        if let Some(value) = patch.value {
612            self.graph.update_vertex_value(id, value);
613            summary.value_changed = true;
614
615            // Force edge rebuild if needed to get accurate dependents
616            // get_dependents may require rebuild when delta has changes
617            if self.graph.edges_delta_size() > 0 {
618                self.graph.rebuild_edges();
619            }
620
621            // Mark dependents as dirty
622            let dependents = self.graph.get_dependents(id);
623            for dep in &dependents {
624                self.graph.set_dirty(*dep, true);
625            }
626            summary.dependents_marked_dirty = dependents;
627        }
628
629        if let Some(_formula) = patch.formula {
630            // This would need proper formula update implementation
631            // For now, we'll mark as changed
632            summary.formula_changed = true;
633        }
634
635        Ok(summary)
636    }
637
638    /// Add an edge between two vertices
639    pub fn add_edge(&mut self, from: VertexId, to: VertexId) -> bool {
640        if from == to {
641            return false; // Prevent self-loops
642        }
643
644        // TODO: Add edge through proper API when available
645        // For now, return true to indicate intent
646        true
647    }
648
649    /// Remove an edge between two vertices
650    pub fn remove_edge(&mut self, _from: VertexId, _to: VertexId) -> bool {
651        // TODO: Remove edge through proper API when available
652        true
653    }
654
655    /// Insert rows at the specified position, shifting existing rows down
656    pub fn insert_rows(
657        &mut self,
658        sheet_id: SheetId,
659        before: u32,
660        count: u32,
661    ) -> Result<ShiftSummary, EditorError> {
662        if count == 0 {
663            return Ok(ShiftSummary::default());
664        }
665
666        let mut summary = ShiftSummary::default();
667
668        // Begin batch for efficiency
669        self.begin_batch();
670
671        // 1. Collect vertices to shift (those at or after the insert point)
672        let vertices_to_shift: Vec<(VertexId, PackedCoord)> = self
673            .graph
674            .vertices_in_sheet(sheet_id)
675            .filter_map(|id| {
676                let coord = self.graph.get_coord(id);
677                if coord.row() >= before {
678                    Some((id, coord))
679                } else {
680                    None
681                }
682            })
683            .collect();
684
685        if let Some(logger) = &mut self.change_logger {
686            logger.begin_compound(format!(
687                "InsertRows sheet={sheet_id} before={before} count={count}"
688            ));
689        }
690        // 2. Shift vertices down (emit VertexMoved)
691        for (id, old_coord) in vertices_to_shift {
692            let new_coord = PackedCoord::new(old_coord.row() + count, old_coord.col());
693            if self.has_logger() {
694                self.log_change(ChangeEvent::VertexMoved {
695                    id,
696                    old_coord,
697                    new_coord,
698                });
699            }
700            self.move_vertex(id, new_coord)?;
701            summary.vertices_moved.push(id);
702        }
703
704        // 3. Adjust formulas using ReferenceAdjuster
705        let op = ShiftOperation::InsertRows {
706            sheet_id,
707            before,
708            count,
709        };
710        let adjuster = ReferenceAdjuster::new();
711
712        // Get all formulas and adjust them
713        let formula_vertices: Vec<VertexId> = self.graph.vertices_with_formulas().collect();
714
715        for id in formula_vertices {
716            if let Some(ast) = self.graph.get_formula(id) {
717                let adjusted = adjuster.adjust_ast(&ast, &op);
718                // Only update if the formula actually changed
719                if format!("{ast:?}") != format!("{adjusted:?}") {
720                    self.graph.update_vertex_formula(id, adjusted)?;
721                    self.graph.mark_vertex_dirty(id);
722                    summary.formulas_updated += 1;
723                }
724            }
725        }
726
727        // 4. Adjust named ranges
728        self.graph.adjust_named_ranges(&op)?;
729
730        // 5. Log change event
731        if let Some(logger) = &mut self.change_logger {
732            logger.end_compound();
733        }
734
735        self.commit_batch();
736
737        Ok(summary)
738    }
739
740    /// Delete rows at the specified position, shifting remaining rows up
741    pub fn delete_rows(
742        &mut self,
743        sheet_id: SheetId,
744        start: u32,
745        count: u32,
746    ) -> Result<ShiftSummary, EditorError> {
747        if count == 0 {
748            return Ok(ShiftSummary::default());
749        }
750
751        let mut summary = ShiftSummary::default();
752
753        self.begin_batch();
754
755        // 1. Delete vertices in the range
756        let vertices_to_delete: Vec<VertexId> = self
757            .graph
758            .vertices_in_sheet(sheet_id)
759            .filter(|&id| {
760                let coord = self.graph.get_coord(id);
761                coord.row() >= start && coord.row() < start + count
762            })
763            .collect();
764
765        for id in vertices_to_delete {
766            self.remove_vertex(id)?;
767            summary.vertices_deleted.push(id);
768        }
769
770        if let Some(logger) = &mut self.change_logger {
771            logger.begin_compound(format!(
772                "DeleteRows sheet={sheet_id} start={start} count={count}"
773            ));
774        }
775        // 2. Shift remaining vertices up (emit VertexMoved)
776        let vertices_to_shift: Vec<(VertexId, PackedCoord)> = self
777            .graph
778            .vertices_in_sheet(sheet_id)
779            .filter_map(|id| {
780                let coord = self.graph.get_coord(id);
781                if coord.row() >= start + count {
782                    Some((id, coord))
783                } else {
784                    None
785                }
786            })
787            .collect();
788
789        for (id, old_coord) in vertices_to_shift {
790            let new_coord = PackedCoord::new(old_coord.row() - count, old_coord.col());
791            if self.has_logger() {
792                self.log_change(ChangeEvent::VertexMoved {
793                    id,
794                    old_coord,
795                    new_coord,
796                });
797            }
798            self.move_vertex(id, new_coord)?;
799            summary.vertices_moved.push(id);
800        }
801
802        // 3. Adjust formulas
803        let op = ShiftOperation::DeleteRows {
804            sheet_id,
805            start,
806            count,
807        };
808        let adjuster = ReferenceAdjuster::new();
809
810        let formula_vertices: Vec<VertexId> = self.graph.vertices_with_formulas().collect();
811
812        for id in formula_vertices {
813            if let Some(ast) = self.graph.get_formula(id) {
814                let adjusted = adjuster.adjust_ast(&ast, &op);
815                if format!("{ast:?}") != format!("{adjusted:?}") {
816                    self.graph.update_vertex_formula(id, adjusted)?;
817                    self.graph.mark_vertex_dirty(id);
818                    summary.formulas_updated += 1;
819                }
820            }
821        }
822
823        // 4. Adjust named ranges
824        self.graph.adjust_named_ranges(&op)?;
825
826        // 5. Log change event
827        if let Some(logger) = &mut self.change_logger {
828            logger.end_compound();
829        }
830
831        self.commit_batch();
832
833        Ok(summary)
834    }
835
836    /// Insert columns at the specified position, shifting existing columns right
837    pub fn insert_columns(
838        &mut self,
839        sheet_id: SheetId,
840        before: u32,
841        count: u32,
842    ) -> Result<ShiftSummary, EditorError> {
843        if count == 0 {
844            return Ok(ShiftSummary::default());
845        }
846
847        let mut summary = ShiftSummary::default();
848
849        // Begin batch for efficiency
850        self.begin_batch();
851
852        // 1. Collect vertices to shift (those at or after the insert point)
853        let vertices_to_shift: Vec<(VertexId, PackedCoord)> = self
854            .graph
855            .vertices_in_sheet(sheet_id)
856            .filter_map(|id| {
857                let coord = self.graph.get_coord(id);
858                if coord.col() >= before {
859                    Some((id, coord))
860                } else {
861                    None
862                }
863            })
864            .collect();
865
866        if let Some(logger) = &mut self.change_logger {
867            logger.begin_compound(format!(
868                "InsertColumns sheet={sheet_id} before={before} count={count}"
869            ));
870        }
871        // 2. Shift vertices right (emit VertexMoved)
872        for (id, old_coord) in vertices_to_shift {
873            let new_coord = PackedCoord::new(old_coord.row(), old_coord.col() + count);
874            if self.has_logger() {
875                self.log_change(ChangeEvent::VertexMoved {
876                    id,
877                    old_coord,
878                    new_coord,
879                });
880            }
881            self.move_vertex(id, new_coord)?;
882            summary.vertices_moved.push(id);
883        }
884
885        // 3. Adjust formulas using ReferenceAdjuster
886        let op = ShiftOperation::InsertColumns {
887            sheet_id,
888            before,
889            count,
890        };
891        let adjuster = ReferenceAdjuster::new();
892
893        // Get all formulas and adjust them
894        let formula_vertices: Vec<VertexId> = self.graph.vertices_with_formulas().collect();
895
896        for id in formula_vertices {
897            if let Some(ast) = self.graph.get_formula(id) {
898                let adjusted = adjuster.adjust_ast(&ast, &op);
899                // Only update if the formula actually changed
900                if format!("{ast:?}") != format!("{adjusted:?}") {
901                    self.graph.update_vertex_formula(id, adjusted)?;
902                    self.graph.mark_vertex_dirty(id);
903                    summary.formulas_updated += 1;
904                }
905            }
906        }
907
908        // 4. Adjust named ranges
909        self.graph.adjust_named_ranges(&op)?;
910
911        // 5. Log change event
912        if let Some(logger) = &mut self.change_logger {
913            logger.end_compound();
914        }
915
916        self.commit_batch();
917
918        Ok(summary)
919    }
920
921    /// Delete columns at the specified position, shifting remaining columns left
922    pub fn delete_columns(
923        &mut self,
924        sheet_id: SheetId,
925        start: u32,
926        count: u32,
927    ) -> Result<ShiftSummary, EditorError> {
928        if count == 0 {
929            return Ok(ShiftSummary::default());
930        }
931
932        let mut summary = ShiftSummary::default();
933
934        self.begin_batch();
935
936        // 1. Delete vertices in the range
937        let vertices_to_delete: Vec<VertexId> = self
938            .graph
939            .vertices_in_sheet(sheet_id)
940            .filter(|&id| {
941                let coord = self.graph.get_coord(id);
942                coord.col() >= start && coord.col() < start + count
943            })
944            .collect();
945
946        for id in vertices_to_delete {
947            self.remove_vertex(id)?;
948            summary.vertices_deleted.push(id);
949        }
950
951        if let Some(logger) = &mut self.change_logger {
952            logger.begin_compound(format!(
953                "DeleteColumns sheet={sheet_id} start={start} count={count}"
954            ));
955        }
956        // 2. Shift remaining vertices left (emit VertexMoved)
957        let vertices_to_shift: Vec<(VertexId, PackedCoord)> = self
958            .graph
959            .vertices_in_sheet(sheet_id)
960            .filter_map(|id| {
961                let coord = self.graph.get_coord(id);
962                if coord.col() >= start + count {
963                    Some((id, coord))
964                } else {
965                    None
966                }
967            })
968            .collect();
969
970        for (id, old_coord) in vertices_to_shift {
971            let new_coord = PackedCoord::new(old_coord.row(), old_coord.col() - count);
972            if self.has_logger() {
973                self.log_change(ChangeEvent::VertexMoved {
974                    id,
975                    old_coord,
976                    new_coord,
977                });
978            }
979            self.move_vertex(id, new_coord)?;
980            summary.vertices_moved.push(id);
981        }
982
983        // 3. Adjust formulas
984        let op = ShiftOperation::DeleteColumns {
985            sheet_id,
986            start,
987            count,
988        };
989        let adjuster = ReferenceAdjuster::new();
990
991        let formula_vertices: Vec<VertexId> = self.graph.vertices_with_formulas().collect();
992
993        for id in formula_vertices {
994            if let Some(ast) = self.graph.get_formula(id) {
995                let adjusted = adjuster.adjust_ast(&ast, &op);
996                if format!("{ast:?}") != format!("{adjusted:?}") {
997                    self.graph.update_vertex_formula(id, adjusted)?;
998                    self.graph.mark_vertex_dirty(id);
999                    summary.formulas_updated += 1;
1000                }
1001            }
1002        }
1003
1004        // 4. Adjust named ranges
1005        self.graph.adjust_named_ranges(&op)?;
1006
1007        // 5. Log change event
1008        if let Some(logger) = &mut self.change_logger {
1009            logger.end_compound();
1010        }
1011
1012        self.commit_batch();
1013
1014        Ok(summary)
1015    }
1016
1017    /// Shift rows down/up within a sheet (Excel's insert/delete rows)
1018    pub fn shift_rows(&mut self, sheet_id: SheetId, start_row: u32, delta: i32) {
1019        if delta == 0 {
1020            return;
1021        }
1022
1023        // Log change event for undo/redo
1024        let change_event = ChangeEvent::SetValue {
1025            addr: CellRef {
1026                sheet_id,
1027                coord: Coord::new(start_row, 0, true, true),
1028            },
1029            old: None,
1030            new: LiteralValue::Text(format!("Row shift: start={start_row}, delta={delta}")),
1031        };
1032        self.log_change(change_event);
1033
1034        // TODO: Implement actual row shifting logic
1035        // This would require coordination with the vertex store and dependency tracking
1036    }
1037
1038    /// Shift columns left/right within a sheet (Excel's insert/delete columns)
1039    pub fn shift_columns(&mut self, sheet_id: SheetId, start_col: u32, delta: i32) {
1040        if delta == 0 {
1041            return;
1042        }
1043
1044        // Log change event
1045        let change_event = ChangeEvent::SetValue {
1046            addr: CellRef {
1047                sheet_id,
1048                coord: Coord::new(0, start_col, true, true),
1049            },
1050            old: None,
1051            new: LiteralValue::Text(format!("Column shift: start={start_col}, delta={delta}")),
1052        };
1053        self.log_change(change_event);
1054
1055        // TODO: Implement actual column shifting logic
1056        // This would require coordination with the vertex store and dependency tracking
1057    }
1058
1059    /// Set a cell value, creating the vertex if it doesn't exist
1060    pub fn set_cell_value(&mut self, cell_ref: CellRef, value: LiteralValue) -> VertexId {
1061        let sheet_name = self.graph.sheet_name(cell_ref.sheet_id).to_string();
1062
1063        // Capture old value before modification
1064        let old_value = self
1065            .graph
1066            .get_vertex_id_for_address(&cell_ref)
1067            .and_then(|&id| self.graph.get_value(id));
1068
1069        // Use the existing DependencyGraph API
1070        match self.graph.set_cell_value(
1071            &sheet_name,
1072            cell_ref.coord.row,
1073            cell_ref.coord.col,
1074            value.clone(),
1075        ) {
1076            Ok(summary) => {
1077                // Log change event
1078                let change_event = ChangeEvent::SetValue {
1079                    addr: cell_ref,
1080                    old: old_value,
1081                    new: value,
1082                };
1083                self.log_change(change_event);
1084
1085                summary
1086                    .affected_vertices
1087                    .into_iter()
1088                    .next()
1089                    .unwrap_or(VertexId::new(0))
1090            }
1091            Err(_) => VertexId::new(0),
1092        }
1093    }
1094
1095    /// Set a cell formula, creating the vertex if it doesn't exist
1096    pub fn set_cell_formula(&mut self, cell_ref: CellRef, formula: ASTNode) -> VertexId {
1097        let sheet_name = self.graph.sheet_name(cell_ref.sheet_id).to_string();
1098
1099        // Capture old formula before modification
1100        let old_formula = self
1101            .graph
1102            .get_vertex_id_for_address(&cell_ref)
1103            .and_then(|&id| self.graph.get_formula(id));
1104
1105        // Use the existing DependencyGraph API
1106        match self.graph.set_cell_formula(
1107            &sheet_name,
1108            cell_ref.coord.row,
1109            cell_ref.coord.col,
1110            formula.clone(),
1111        ) {
1112            Ok(summary) => {
1113                // Log change event
1114                let change_event = ChangeEvent::SetFormula {
1115                    addr: cell_ref,
1116                    old: old_formula,
1117                    new: formula,
1118                };
1119                self.log_change(change_event);
1120
1121                summary
1122                    .affected_vertices
1123                    .into_iter()
1124                    .next()
1125                    .unwrap_or(VertexId::new(0))
1126            }
1127            Err(_) => VertexId::new(0),
1128        }
1129    }
1130
1131    // Range operations
1132
1133    /// Set values for a rectangular range of cells
1134    pub fn set_range_values(
1135        &mut self,
1136        sheet_id: SheetId,
1137        start_row: u32,
1138        start_col: u32,
1139        values: &[Vec<LiteralValue>],
1140    ) -> Result<RangeSummary, EditorError> {
1141        let mut summary = RangeSummary::default();
1142
1143        self.begin_batch();
1144
1145        for (row_offset, row_values) in values.iter().enumerate() {
1146            for (col_offset, value) in row_values.iter().enumerate() {
1147                let row = start_row + row_offset as u32;
1148                let col = start_col + col_offset as u32;
1149
1150                // Check if cell already exists
1151                let cell_ref = self.graph.make_cell_ref_internal(sheet_id, row, col);
1152
1153                if let Some(&existing_id) = self.graph.get_vertex_id_for_address(&cell_ref) {
1154                    // Update existing vertex
1155                    self.graph.update_vertex_value(existing_id, value.clone());
1156                    self.graph.mark_vertex_dirty(existing_id);
1157                    summary.vertices_updated.push(existing_id);
1158                } else {
1159                    // Create new vertex
1160                    let meta = VertexMeta::new(row, col, sheet_id, VertexKind::Cell);
1161                    let id = self.add_vertex(meta);
1162                    self.graph.update_vertex_value(id, value.clone());
1163                    summary.vertices_created.push(id);
1164                }
1165
1166                summary.cells_affected += 1;
1167            }
1168        }
1169
1170        self.commit_batch();
1171
1172        Ok(summary)
1173    }
1174
1175    /// Clear all cells in a rectangular range
1176    pub fn clear_range(
1177        &mut self,
1178        sheet_id: SheetId,
1179        start_row: u32,
1180        start_col: u32,
1181        end_row: u32,
1182        end_col: u32,
1183    ) -> Result<RangeSummary, EditorError> {
1184        let mut summary = RangeSummary::default();
1185
1186        self.begin_batch();
1187
1188        // Collect vertices in range
1189        let vertices_in_range: Vec<_> = self
1190            .graph
1191            .vertices_in_sheet(sheet_id)
1192            .filter(|&id| {
1193                let coord = self.graph.get_coord(id);
1194                let row = coord.row();
1195                let col = coord.col();
1196                row >= start_row && row <= end_row && col >= start_col && col <= end_col
1197            })
1198            .collect();
1199
1200        for id in vertices_in_range {
1201            self.remove_vertex(id)?;
1202            summary.cells_affected += 1;
1203        }
1204
1205        self.commit_batch();
1206
1207        Ok(summary)
1208    }
1209
1210    /// Copy a range to a new location
1211    pub fn copy_range(
1212        &mut self,
1213        sheet_id: SheetId,
1214        from_start_row: u32,
1215        from_start_col: u32,
1216        from_end_row: u32,
1217        from_end_col: u32,
1218        to_sheet_id: SheetId,
1219        to_row: u32,
1220        to_col: u32,
1221    ) -> Result<RangeSummary, EditorError> {
1222        let row_offset = to_row as i32 - from_start_row as i32;
1223        let col_offset = to_col as i32 - from_start_col as i32;
1224
1225        let mut summary = RangeSummary::default();
1226        let mut cell_data = Vec::new();
1227
1228        // Collect source data
1229        let vertices_in_range: Vec<_> = self
1230            .graph
1231            .vertices_in_sheet(sheet_id)
1232            .filter(|&id| {
1233                let coord = self.graph.get_coord(id);
1234                let row = coord.row();
1235                let col = coord.col();
1236                row >= from_start_row
1237                    && row <= from_end_row
1238                    && col >= from_start_col
1239                    && col <= from_end_col
1240            })
1241            .collect();
1242
1243        for id in vertices_in_range {
1244            let coord = self.graph.get_coord(id);
1245            let row = coord.row();
1246            let col = coord.col();
1247
1248            // Get value or formula
1249            if let Some(formula) = self.graph.get_formula(id) {
1250                cell_data.push((
1251                    row - from_start_row,
1252                    col - from_start_col,
1253                    CellData::Formula(formula),
1254                ));
1255            } else if let Some(value) = self.graph.get_value(id) {
1256                cell_data.push((
1257                    row - from_start_row,
1258                    col - from_start_col,
1259                    CellData::Value(value),
1260                ));
1261            }
1262        }
1263
1264        self.begin_batch();
1265
1266        // Apply to destination with relative adjustment
1267        for (row_idx, col_idx, data) in cell_data {
1268            let dest_row = (to_row as i32 + row_idx as i32) as u32;
1269            let dest_col = (to_col as i32 + col_idx as i32) as u32;
1270
1271            match data {
1272                CellData::Value(value) => {
1273                    let cell_ref =
1274                        self.graph
1275                            .make_cell_ref_internal(to_sheet_id, dest_row, dest_col);
1276
1277                    if let Some(&existing_id) = self.graph.get_vertex_id_for_address(&cell_ref) {
1278                        self.graph.update_vertex_value(existing_id, value);
1279                        self.graph.mark_vertex_dirty(existing_id);
1280                        summary.vertices_updated.push(existing_id);
1281                    } else {
1282                        let meta =
1283                            VertexMeta::new(dest_row, dest_col, to_sheet_id, VertexKind::Cell);
1284                        let id = self.add_vertex(meta);
1285                        self.graph.update_vertex_value(id, value);
1286                        summary.vertices_created.push(id);
1287                    }
1288                }
1289                CellData::Formula(formula) => {
1290                    // Adjust relative references in formula
1291                    let adjuster = RelativeReferenceAdjuster::new(row_offset, col_offset);
1292                    let adjusted = adjuster.adjust_formula(&formula, sheet_id, to_sheet_id);
1293
1294                    let cell_ref =
1295                        self.graph
1296                            .make_cell_ref_internal(to_sheet_id, dest_row, dest_col);
1297
1298                    if let Some(&existing_id) = self.graph.get_vertex_id_for_address(&cell_ref) {
1299                        self.graph.update_vertex_formula(existing_id, adjusted)?;
1300                        summary.vertices_updated.push(existing_id);
1301                    } else {
1302                        let meta = VertexMeta::new(
1303                            dest_row,
1304                            dest_col,
1305                            to_sheet_id,
1306                            VertexKind::FormulaScalar,
1307                        );
1308                        let id = self.add_vertex(meta);
1309                        self.graph.update_vertex_formula(id, adjusted)?;
1310                        summary.vertices_created.push(id);
1311                    }
1312                }
1313            }
1314
1315            summary.cells_affected += 1;
1316        }
1317
1318        self.commit_batch();
1319
1320        Ok(summary)
1321    }
1322
1323    /// Move a range to a new location (copy + clear source)
1324    pub fn move_range(
1325        &mut self,
1326        sheet_id: SheetId,
1327        from_start_row: u32,
1328        from_start_col: u32,
1329        from_end_row: u32,
1330        from_end_col: u32,
1331        to_sheet_id: SheetId,
1332        to_row: u32,
1333        to_col: u32,
1334    ) -> Result<RangeSummary, EditorError> {
1335        // First copy the range
1336        let mut summary = self.copy_range(
1337            sheet_id,
1338            from_start_row,
1339            from_start_col,
1340            from_end_row,
1341            from_end_col,
1342            to_sheet_id,
1343            to_row,
1344            to_col,
1345        )?;
1346
1347        // Then clear the source range
1348        let clear_summary = self.clear_range(
1349            sheet_id,
1350            from_start_row,
1351            from_start_col,
1352            from_end_row,
1353            from_end_col,
1354        )?;
1355
1356        summary.cells_moved = clear_summary.cells_affected;
1357
1358        // Update external references to moved cells
1359        let row_offset = to_row as i32 - from_start_row as i32;
1360        let col_offset = to_col as i32 - from_start_col as i32;
1361
1362        // Find all formulas that reference the moved range
1363        let all_formula_vertices: Vec<_> = self.graph.vertices_with_formulas().collect();
1364
1365        for formula_id in all_formula_vertices {
1366            if let Some(formula) = self.graph.get_formula(formula_id) {
1367                let adjuster = MoveReferenceAdjuster::new(
1368                    sheet_id,
1369                    from_start_row,
1370                    from_start_col,
1371                    from_end_row,
1372                    from_end_col,
1373                    to_sheet_id,
1374                    row_offset,
1375                    col_offset,
1376                );
1377
1378                if let Some(adjusted) = adjuster.adjust_if_references(&formula) {
1379                    self.graph.update_vertex_formula(formula_id, adjusted)?;
1380                }
1381            }
1382        }
1383
1384        Ok(summary)
1385    }
1386
1387    /// Define a named range
1388    pub fn define_name(
1389        &mut self,
1390        name: &str,
1391        definition: NamedDefinition,
1392        scope: NameScope,
1393    ) -> Result<(), EditorError> {
1394        self.graph.define_name(name, definition.clone(), scope)?;
1395
1396        self.log_change(ChangeEvent::DefineName {
1397            name: name.to_string(),
1398            scope,
1399            definition,
1400        });
1401
1402        Ok(())
1403    }
1404
1405    /// Helper to create definitions from coordinates for a single cell
1406    pub fn define_name_for_cell(
1407        &mut self,
1408        name: &str,
1409        sheet_name: &str,
1410        row: u32,
1411        col: u32,
1412        scope: NameScope,
1413    ) -> Result<(), EditorError> {
1414        let sheet_id = self
1415            .graph
1416            .sheet_id(sheet_name)
1417            .ok_or_else(|| EditorError::InvalidName {
1418                name: sheet_name.to_string(),
1419                reason: "Sheet not found".to_string(),
1420            })?;
1421        let cell_ref = CellRef::new(sheet_id, Coord::new(row, col, true, true));
1422        self.define_name(name, NamedDefinition::Cell(cell_ref), scope)
1423    }
1424
1425    /// Helper to create definitions from coordinates for a range
1426    pub fn define_name_for_range(
1427        &mut self,
1428        name: &str,
1429        sheet_name: &str,
1430        start_row: u32,
1431        start_col: u32,
1432        end_row: u32,
1433        end_col: u32,
1434        scope: NameScope,
1435    ) -> Result<(), EditorError> {
1436        let sheet_id = self
1437            .graph
1438            .sheet_id(sheet_name)
1439            .ok_or_else(|| EditorError::InvalidName {
1440                name: sheet_name.to_string(),
1441                reason: "Sheet not found".to_string(),
1442            })?;
1443        let start = CellRef::new(sheet_id, Coord::new(start_row, start_col, true, true));
1444        let end = CellRef::new(sheet_id, Coord::new(end_row, end_col, true, true));
1445        let range_ref = crate::reference::RangeRef::new(start, end);
1446        self.define_name(name, NamedDefinition::Range(range_ref), scope)
1447    }
1448
1449    /// Update an existing named range definition
1450    pub fn update_name(
1451        &mut self,
1452        name: &str,
1453        new_definition: NamedDefinition,
1454        scope: NameScope,
1455    ) -> Result<(), EditorError> {
1456        // Get the old definition for the change log
1457        let old_definition = self
1458            .graph
1459            .resolve_name(
1460                name,
1461                match scope {
1462                    NameScope::Sheet(id) => id,
1463                    NameScope::Workbook => 0,
1464                },
1465            )
1466            .cloned();
1467
1468        self.graph
1469            .update_name(name, new_definition.clone(), scope)?;
1470
1471        if let Some(old_def) = old_definition {
1472            self.log_change(ChangeEvent::UpdateName {
1473                name: name.to_string(),
1474                scope,
1475                old_definition: old_def,
1476                new_definition,
1477            });
1478        }
1479
1480        Ok(())
1481    }
1482
1483    /// Delete a named range
1484    pub fn delete_name(&mut self, name: &str, scope: NameScope) -> Result<(), EditorError> {
1485        self.graph.delete_name(name, scope)?;
1486
1487        let old_def = if self.has_logger() {
1488            self.graph
1489                .resolve_name(
1490                    name,
1491                    match scope {
1492                        NameScope::Sheet(id) => id,
1493                        NameScope::Workbook => 0,
1494                    },
1495                )
1496                .cloned()
1497        } else {
1498            None
1499        };
1500        self.log_change(ChangeEvent::DeleteName {
1501            name: name.to_string(),
1502            scope,
1503            old_definition: old_def,
1504        });
1505
1506        Ok(())
1507    }
1508}
1509
1510/// Helper enum for cell data
1511enum CellData {
1512    Value(LiteralValue),
1513    Formula(ASTNode),
1514}
1515
1516/// Helper for adjusting relative references when copying
1517struct RelativeReferenceAdjuster {
1518    row_offset: i32,
1519    col_offset: i32,
1520}
1521
1522impl RelativeReferenceAdjuster {
1523    fn new(row_offset: i32, col_offset: i32) -> Self {
1524        Self {
1525            row_offset,
1526            col_offset,
1527        }
1528    }
1529
1530    fn adjust_formula(
1531        &self,
1532        formula: &ASTNode,
1533        _from_sheet: SheetId,
1534        _to_sheet: SheetId,
1535    ) -> ASTNode {
1536        // This would recursively adjust relative references in the formula
1537        // For now, just clone the formula
1538        formula.clone()
1539    }
1540}
1541
1542/// Helper for adjusting references when moving ranges
1543struct MoveReferenceAdjuster {
1544    from_sheet_id: SheetId,
1545    from_start_row: u32,
1546    from_start_col: u32,
1547    from_end_row: u32,
1548    from_end_col: u32,
1549    to_sheet_id: SheetId,
1550    row_offset: i32,
1551    col_offset: i32,
1552}
1553
1554impl MoveReferenceAdjuster {
1555    fn new(
1556        from_sheet_id: SheetId,
1557        from_start_row: u32,
1558        from_start_col: u32,
1559        from_end_row: u32,
1560        from_end_col: u32,
1561        to_sheet_id: SheetId,
1562        row_offset: i32,
1563        col_offset: i32,
1564    ) -> Self {
1565        Self {
1566            from_sheet_id,
1567            from_start_row,
1568            from_start_col,
1569            from_end_row,
1570            from_end_col,
1571            to_sheet_id,
1572            row_offset,
1573            col_offset,
1574        }
1575    }
1576
1577    fn adjust_if_references(&self, formula: &ASTNode) -> Option<ASTNode> {
1578        // This would check if the formula references the moved range
1579        // and adjust those references accordingly
1580        // For now, return None (no adjustment needed)
1581        None
1582    }
1583}
1584
1585impl<'g> Drop for VertexEditor<'g> {
1586    fn drop(&mut self) {
1587        // Ensure batch operations are committed when the editor is dropped
1588        if self.batch_mode {
1589            self.commit_batch();
1590        }
1591    }
1592}
1593
1594#[cfg(test)]
1595mod tests {
1596    use super::*;
1597    use crate::engine::graph::editor::change_log::{ChangeEvent, ChangeLog};
1598    use crate::reference::Coord;
1599
1600    fn create_test_graph() -> DependencyGraph {
1601        DependencyGraph::new()
1602    }
1603
1604    #[test]
1605    fn test_vertex_editor_creation() {
1606        let mut graph = create_test_graph();
1607        let editor = VertexEditor::new(&mut graph);
1608        assert!(!editor.has_logger());
1609        assert!(!editor.batch_mode);
1610    }
1611
1612    #[test]
1613    fn test_vertex_editor_with_logger() {
1614        let mut graph = create_test_graph();
1615        let mut log = ChangeLog::new();
1616        let editor = VertexEditor::with_logger(&mut graph, &mut log);
1617        assert!(editor.has_logger());
1618        assert!(!editor.batch_mode);
1619    }
1620
1621    #[test]
1622    fn test_add_vertex() {
1623        let mut graph = create_test_graph();
1624        let mut editor = VertexEditor::new(&mut graph);
1625
1626        let meta = VertexMeta::new(5, 10, 0, VertexKind::Cell).dirty();
1627        let vertex_id = editor.add_vertex(meta);
1628
1629        // Verify vertex was created (simplified check)
1630        assert!(vertex_id.0 > 0);
1631    }
1632
1633    #[test]
1634    fn test_batch_operations() {
1635        let mut graph = create_test_graph();
1636        let mut editor = VertexEditor::new(&mut graph);
1637
1638        assert!(!editor.batch_mode);
1639        editor.begin_batch();
1640        assert!(editor.batch_mode);
1641
1642        // Add multiple vertices in batch mode
1643        let meta1 = VertexMeta::new(1, 1, 0, VertexKind::Cell);
1644        let meta2 = VertexMeta::new(2, 2, 0, VertexKind::Cell);
1645
1646        let id1 = editor.add_vertex(meta1);
1647        let id2 = editor.add_vertex(meta2);
1648
1649        // Add edge between them
1650        assert!(editor.add_edge(id1, id2));
1651
1652        editor.commit_batch();
1653        assert!(!editor.batch_mode);
1654    }
1655
1656    #[test]
1657    fn test_remove_vertex() {
1658        let mut graph = create_test_graph();
1659        let mut editor = VertexEditor::new(&mut graph);
1660
1661        let meta = VertexMeta::new(3, 4, 0, VertexKind::Cell).dirty();
1662        let vertex_id = editor.add_vertex(meta);
1663
1664        // Now removal returns Result
1665        assert!(editor.remove_vertex(vertex_id).is_ok());
1666    }
1667
1668    #[test]
1669    fn test_edge_operations() {
1670        let mut graph = create_test_graph();
1671        let mut editor = VertexEditor::new(&mut graph);
1672
1673        let meta1 = VertexMeta::new(1, 1, 0, VertexKind::Cell);
1674        let meta2 = VertexMeta::new(2, 2, 0, VertexKind::FormulaScalar);
1675
1676        let id1 = editor.add_vertex(meta1);
1677        let id2 = editor.add_vertex(meta2);
1678
1679        // Add edge
1680        assert!(editor.add_edge(id1, id2));
1681
1682        // Prevent self-loop
1683        assert!(!editor.add_edge(id1, id1));
1684
1685        // Remove edge
1686        assert!(editor.remove_edge(id1, id2));
1687    }
1688
1689    #[test]
1690    fn test_set_cell_value() {
1691        let mut graph = create_test_graph();
1692        let mut log = ChangeLog::new();
1693
1694        let cell_ref = CellRef {
1695            sheet_id: 0,
1696            coord: Coord::new(2, 3, true, true),
1697        };
1698        let value = LiteralValue::Number(42.0);
1699
1700        let vertex_id = {
1701            let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
1702            editor.set_cell_value(cell_ref, value.clone())
1703        };
1704
1705        // Verify vertex was created (simplified check)
1706        assert!(vertex_id.0 > 0);
1707
1708        // Verify change log
1709        assert_eq!(log.len(), 1);
1710        match &log.events()[0] {
1711            ChangeEvent::SetValue { addr, new, .. } => {
1712                assert_eq!(addr.sheet_id, cell_ref.sheet_id);
1713                assert_eq!(addr.coord.row, cell_ref.coord.row);
1714                assert_eq!(addr.coord.col, cell_ref.coord.col);
1715                assert_eq!(new, &value);
1716            }
1717            _ => panic!("Expected SetValue event"),
1718        }
1719    }
1720
1721    #[test]
1722    fn test_set_cell_formula() {
1723        let mut graph = create_test_graph();
1724        let mut log = ChangeLog::new();
1725
1726        let cell_ref = CellRef {
1727            sheet_id: 0,
1728            coord: Coord::new(1, 1, true, true),
1729        };
1730
1731        use formualizer_parse::parser::ASTNodeType;
1732        let formula = formualizer_parse::parser::ASTNode {
1733            node_type: ASTNodeType::Literal(LiteralValue::Number(100.0)),
1734            source_token: None,
1735            contains_volatile: false,
1736        };
1737
1738        let vertex_id = {
1739            let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
1740            editor.set_cell_formula(cell_ref, formula.clone())
1741        };
1742
1743        // Verify vertex was created (simplified check)
1744        assert!(vertex_id.0 > 0);
1745
1746        // Verify change log
1747        assert_eq!(log.len(), 1);
1748        match &log.events()[0] {
1749            ChangeEvent::SetFormula { addr, .. } => {
1750                assert_eq!(addr.sheet_id, cell_ref.sheet_id);
1751                assert_eq!(addr.coord.row, cell_ref.coord.row);
1752                assert_eq!(addr.coord.col, cell_ref.coord.col);
1753            }
1754            _ => panic!("Expected SetFormula event"),
1755        }
1756    }
1757
1758    #[test]
1759    fn test_shift_rows() {
1760        let mut graph = create_test_graph();
1761        let mut log = ChangeLog::new();
1762
1763        {
1764            let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
1765
1766            // Create vertices at different rows
1767            let cell1 = CellRef {
1768                sheet_id: 0,
1769                coord: Coord::new(5, 1, true, true),
1770            };
1771            let cell2 = CellRef {
1772                sheet_id: 0,
1773                coord: Coord::new(10, 1, true, true),
1774            };
1775            let cell3 = CellRef {
1776                sheet_id: 0,
1777                coord: Coord::new(15, 1, true, true),
1778            };
1779
1780            editor.set_cell_value(cell1, LiteralValue::Number(1.0));
1781            editor.set_cell_value(cell2, LiteralValue::Number(2.0));
1782            editor.set_cell_value(cell3, LiteralValue::Number(3.0));
1783        }
1784
1785        // Clear change log to focus on shift operation
1786        log.clear();
1787
1788        {
1789            let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
1790            // Shift rows starting at row 10, moving down by 2
1791            editor.shift_rows(0, 10, 2);
1792        }
1793
1794        // Verify change log contains the shift operation
1795        assert_eq!(log.len(), 1);
1796        match &log.events()[0] {
1797            ChangeEvent::SetValue { addr, new, .. } => {
1798                assert_eq!(addr.sheet_id, 0);
1799                assert_eq!(addr.coord.row, 10);
1800                if let LiteralValue::Text(msg) = new {
1801                    assert!(msg.contains("Row shift"));
1802                    assert!(msg.contains("start=10"));
1803                    assert!(msg.contains("delta=2"));
1804                }
1805            }
1806            _ => panic!("Expected SetValue event for row shift"),
1807        }
1808    }
1809
1810    #[test]
1811    fn test_shift_columns() {
1812        let mut graph = create_test_graph();
1813        let mut log = ChangeLog::new();
1814
1815        {
1816            let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
1817
1818            // Create vertices at different columns
1819            let cell1 = CellRef {
1820                sheet_id: 0,
1821                coord: Coord::new(1, 5, true, true),
1822            };
1823            let cell2 = CellRef {
1824                sheet_id: 0,
1825                coord: Coord::new(1, 10, true, true),
1826            };
1827
1828            editor.set_cell_value(cell1, LiteralValue::Number(1.0));
1829            editor.set_cell_value(cell2, LiteralValue::Number(2.0));
1830        }
1831
1832        // Clear change log
1833        log.clear();
1834
1835        {
1836            let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
1837            // Shift columns starting at col 8, moving right by 3
1838            editor.shift_columns(0, 8, 3);
1839        }
1840
1841        // Verify change log
1842        assert_eq!(log.len(), 1);
1843        match &log.events()[0] {
1844            ChangeEvent::SetValue { addr, new, .. } => {
1845                assert_eq!(addr.sheet_id, 0);
1846                assert_eq!(addr.coord.col, 8);
1847                if let LiteralValue::Text(msg) = new {
1848                    assert!(msg.contains("Column shift"));
1849                    assert!(msg.contains("start=8"));
1850                    assert!(msg.contains("delta=3"));
1851                }
1852            }
1853            _ => panic!("Expected SetValue event for column shift"),
1854        }
1855    }
1856
1857    #[test]
1858    fn test_move_vertex() {
1859        let mut graph = create_test_graph();
1860        let mut editor = VertexEditor::new(&mut graph);
1861
1862        let meta = VertexMeta::new(5, 10, 0, VertexKind::Cell);
1863        let vertex_id = editor.add_vertex(meta);
1864
1865        // Move vertex returns Result
1866        assert!(
1867            editor
1868                .move_vertex(vertex_id, PackedCoord::new(8, 12))
1869                .is_ok()
1870        );
1871
1872        // Moving to same position should work
1873        assert!(
1874            editor
1875                .move_vertex(vertex_id, PackedCoord::new(8, 12))
1876                .is_ok()
1877        );
1878    }
1879
1880    #[test]
1881    fn test_vertex_meta_builder() {
1882        let meta = VertexMeta::new(1, 2, 3, VertexKind::FormulaScalar)
1883            .dirty()
1884            .volatile()
1885            .with_flags(0x08);
1886
1887        assert_eq!(meta.coord.row(), 1);
1888        assert_eq!(meta.coord.col(), 2);
1889        assert_eq!(meta.sheet_id, 3);
1890        assert_eq!(meta.kind, VertexKind::FormulaScalar);
1891        assert_eq!(meta.flags, 0x08); // Last with_flags call overwrites previous flags
1892    }
1893
1894    #[test]
1895    fn test_change_log_management() {
1896        let mut graph = create_test_graph();
1897        let mut log = ChangeLog::new();
1898
1899        {
1900            let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
1901            let cell_ref = CellRef {
1902                sheet_id: 0,
1903                coord: Coord::new(0, 0, true, true),
1904            };
1905            editor.set_cell_value(cell_ref, LiteralValue::Number(1.0));
1906            editor.set_cell_value(cell_ref, LiteralValue::Number(2.0));
1907        }
1908
1909        assert_eq!(log.len(), 2);
1910
1911        log.clear();
1912        assert_eq!(log.len(), 0);
1913    }
1914
1915    #[test]
1916    fn test_editor_drop_commits_batch() {
1917        let mut graph = create_test_graph();
1918        {
1919            let mut editor = VertexEditor::new(&mut graph);
1920            editor.begin_batch();
1921
1922            let meta = VertexMeta::new(1, 1, 0, VertexKind::Cell);
1923            editor.add_vertex(meta);
1924
1925            // Editor will be dropped here, should commit batch
1926        }
1927
1928        // If we reach here without hanging, the batch was properly committed
1929    }
1930}