Skip to main content

formualizer_eval/engine/graph/
sheets.rs

1use super::ast_utils::update_internal_sheet_references;
2use super::*;
3use formualizer_common::{ExcelError, ExcelErrorKind, LiteralValue};
4
5impl DependencyGraph {
6    /// Add a new sheet to the workbook.
7    ///
8    /// Creates a new sheet with the given name. If a sheet with this name
9    /// already exists, returns its ID without error (idempotent operation).
10    pub fn add_sheet(&mut self, name: &str) -> Result<SheetId, ExcelError> {
11        if let Some(id) = self.sheet_reg.get_id(name) {
12            return Ok(id);
13        }
14
15        let sheet_id = self.sheet_reg.id_for(name);
16        self.sheet_indexes.entry(sheet_id).or_default();
17
18        // Heal formulas that were waiting on this sheet name.
19        self.heal_orphaned_formulas(name);
20        Ok(sheet_id)
21    }
22
23    /// Remove a sheet from the workbook.
24    pub fn remove_sheet(&mut self, sheet_id: SheetId) -> Result<(), ExcelError> {
25        let old_name = self.sheet_reg.name(sheet_id).to_string();
26        if old_name.is_empty() {
27            return Err(ExcelError::new(ExcelErrorKind::Value).with_message("Sheet does not exist"));
28        }
29
30        let sheet_count = self.sheet_reg.all_sheets().len();
31        if sheet_count <= 1 {
32            return Err(
33                ExcelError::new(ExcelErrorKind::Value).with_message("Cannot remove the last sheet")
34            );
35        }
36
37        self.begin_batch();
38
39        let vertices_to_delete: Vec<VertexId> = self.vertices_in_sheet(sheet_id).collect();
40
41        // Formulas can reference this sheet either through explicit dependency edges
42        // (expanded refs) or compressed range deps. Track both.
43        let mut formulas_to_update: rustc_hash::FxHashSet<VertexId> =
44            rustc_hash::FxHashSet::default();
45
46        for &formula_id in self.vertex_formulas.keys() {
47            let deps = self.edges.out_edges(formula_id);
48            if deps
49                .iter()
50                .any(|&dep_id| self.store.sheet_id(dep_id) == sheet_id)
51            {
52                formulas_to_update.insert(formula_id);
53            }
54        }
55
56        for (&formula_id, ranges) in &self.formula_to_range_deps {
57            if ranges.iter().any(|r| match r.sheet {
58                SharedSheetLocator::Id(id) => id == sheet_id,
59                SharedSheetLocator::Name(ref n) => n.as_ref() == old_name,
60                SharedSheetLocator::Current => false,
61            }) {
62                formulas_to_update.insert(formula_id);
63            }
64        }
65
66        let formulas_to_update: Vec<VertexId> = formulas_to_update.into_iter().collect();
67
68        for &formula_id in &formulas_to_update {
69            self.tombstone_registry
70                .add_orphan(old_name.clone(), formula_id);
71            self.rewrite_formula_sheet_to_tombstone(formula_id, &old_name);
72        }
73
74        for formula_id in formulas_to_update {
75            self.mark_as_ref_error(formula_id);
76        }
77
78        // Invalidate defined names that reference the removed sheet.
79        //
80        // In canonical (Arrow-truth) mode, cell/formula vertices do not cache values in the graph,
81        // so we cannot rely on graph-stored ref errors. We must explicitly dirty name vertices and
82        // their dependents so that subsequent evaluation updates Arrow overlays.
83        let ref_err = LiteralValue::Error(ExcelError::new(ExcelErrorKind::Ref));
84        let mut name_vertices_to_update: Vec<VertexId> = Vec::new();
85        let mut dirty_vertices: Vec<VertexId> = Vec::new();
86
87        for nr in self.named_ranges.values_mut() {
88            match &nr.definition {
89                NamedDefinition::Cell(c) if c.sheet_id == sheet_id => {
90                    nr.definition = NamedDefinition::Literal(ref_err.clone());
91                    name_vertices_to_update.push(nr.vertex);
92                    dirty_vertices.push(nr.vertex);
93                    dirty_vertices.extend(nr.dependents.iter().copied());
94                }
95                NamedDefinition::Range(r)
96                    if r.start.sheet_id == sheet_id || r.end.sheet_id == sheet_id =>
97                {
98                    nr.definition = NamedDefinition::Literal(ref_err.clone());
99                    name_vertices_to_update.push(nr.vertex);
100                    dirty_vertices.push(nr.vertex);
101                    dirty_vertices.extend(nr.dependents.iter().copied());
102                }
103                _ => {}
104            }
105        }
106        for nr in self.sheet_named_ranges.values_mut() {
107            match &nr.definition {
108                NamedDefinition::Cell(c) if c.sheet_id == sheet_id => {
109                    nr.definition = NamedDefinition::Literal(ref_err.clone());
110                    name_vertices_to_update.push(nr.vertex);
111                    dirty_vertices.push(nr.vertex);
112                    dirty_vertices.extend(nr.dependents.iter().copied());
113                }
114                NamedDefinition::Range(r)
115                    if r.start.sheet_id == sheet_id || r.end.sheet_id == sheet_id =>
116                {
117                    nr.definition = NamedDefinition::Literal(ref_err.clone());
118                    name_vertices_to_update.push(nr.vertex);
119                    dirty_vertices.push(nr.vertex);
120                    dirty_vertices.extend(nr.dependents.iter().copied());
121                }
122                _ => {}
123            }
124        }
125
126        // Update cached values for name vertices after the map borrows end.
127        for vid in name_vertices_to_update {
128            self.update_vertex_value(vid, ref_err.clone());
129        }
130        for vid in dirty_vertices {
131            self.mark_vertex_dirty(vid);
132        }
133
134        for vertex_id in vertices_to_delete {
135            if let Some(cell_ref) = self.get_cell_ref_for_vertex(vertex_id) {
136                self.cell_to_vertex.remove(&cell_ref);
137            }
138
139            self.remove_all_edges(vertex_id);
140
141            let coord = self.store.coord(vertex_id);
142            if let Some(index) = self.sheet_indexes.get_mut(&sheet_id) {
143                index.remove_vertex(coord, vertex_id);
144            }
145
146            self.clear_pending_name_references(vertex_id);
147            self.vertex_formulas.remove(&vertex_id);
148            self.vertex_values.remove(&vertex_id);
149
150            self.mark_deleted(vertex_id, true);
151        }
152
153        let sheet_names_to_remove: Vec<(SheetId, String)> = self
154            .sheet_named_ranges
155            .keys()
156            .filter(|(sid, _)| *sid == sheet_id)
157            .cloned()
158            .collect();
159
160        for key in sheet_names_to_remove {
161            if let Some(named_range) = self.sheet_named_ranges.remove(&key) {
162                if !self.config.case_sensitive_names {
163                    let normalized = key.1.to_lowercase();
164                    self.sheet_named_ranges_lookup
165                        .remove(&(sheet_id, normalized));
166                } else {
167                    self.sheet_named_ranges_lookup.remove(&key);
168                }
169                self.mark_named_vertex_deleted(&named_range);
170            }
171        }
172
173        self.sheet_indexes.remove(&sheet_id);
174
175        if self.default_sheet_id == sheet_id
176            && let Some(&new_default) = self.sheet_indexes.keys().next()
177        {
178            self.default_sheet_id = new_default;
179        }
180
181        self.sheet_reg.remove(sheet_id)?;
182        self.end_batch();
183
184        Ok(())
185    }
186
187    fn tombstone_marker(sheet_name: &str) -> String {
188        format!("__FZ_MISSING_SHEET__{sheet_name}")
189    }
190
191    fn rewrite_formula_sheet_to_tombstone(&mut self, vertex_id: VertexId, sheet_name: &str) {
192        let Some(ast_id) = self.vertex_formulas.get(&vertex_id).copied() else {
193            return;
194        };
195        let Some(ast) = self.data_store.retrieve_ast(ast_id, &self.sheet_reg) else {
196            return;
197        };
198
199        let marker = Self::tombstone_marker(sheet_name);
200        let mut updated_ast = ast.clone();
201        updated_ast.update_sheet_references(Some(sheet_name), &marker);
202
203        if updated_ast != ast {
204            let updated_ast_id = self.data_store.store_ast(&updated_ast, &self.sheet_reg);
205            self.vertex_formulas.insert(vertex_id, updated_ast_id);
206        }
207    }
208
209    fn heal_orphaned_formulas(&mut self, sheet_name: &str) {
210        let orphans = self.tombstone_registry.take_orphans(sheet_name);
211        let marker = Self::tombstone_marker(sheet_name);
212
213        for vertex_id in orphans {
214            let Some(ast_id) = self.vertex_formulas.get(&vertex_id).copied() else {
215                continue;
216            };
217            let Some(ast) = self.data_store.retrieve_ast(ast_id, &self.sheet_reg) else {
218                continue;
219            };
220
221            // If the formula was edited while the sheet was missing, it may no longer
222            // be in #REF! state; skip stale orphan entries in that case.
223            if !self.is_ref_error(vertex_id) {
224                continue;
225            }
226
227            // Heal only references that were explicitly tombstoned for this sheet.
228            let mut updated_ast = ast.clone();
229            updated_ast.update_sheet_references(Some(&marker), sheet_name);
230
231            if updated_ast == ast {
232                // Stale orphan entry (formula changed while sheet was missing).
233                continue;
234            }
235
236            let updated_ast_id = self.data_store.store_ast(&updated_ast, &self.sheet_reg);
237            self.vertex_formulas.insert(vertex_id, updated_ast_id);
238            self.rebuild_formula_dependencies(vertex_id, &updated_ast);
239        }
240    }
241    /// Rename an existing sheet.
242    pub fn rename_sheet(&mut self, sheet_id: SheetId, new_name: &str) -> Result<(), ExcelError> {
243        if new_name.is_empty() || new_name.len() > 255 {
244            return Err(ExcelError::new(ExcelErrorKind::Value).with_message("Invalid sheet name"));
245        }
246
247        let old_name = self.sheet_reg.name(sheet_id).to_string();
248
249        if old_name.is_empty() {
250            return Err(ExcelError::new(ExcelErrorKind::Value).with_message("Sheet does not exist"));
251        }
252
253        if let Some(existing_id) = self.sheet_reg.get_id(new_name) {
254            if existing_id != sheet_id {
255                return Err(ExcelError::new(ExcelErrorKind::Value)
256                    .with_message(format!("Sheet '{new_name}' already exists")));
257            }
258            return Ok(());
259        }
260
261        self.sheet_reg.rename(sheet_id, new_name)?;
262
263        self.begin_batch();
264
265        // Rescue formulas that were waiting for this exact sheet name to reappear.
266        self.heal_orphaned_formulas(new_name);
267
268        // Update still-valid references that explicitly mentioned the renamed sheet.
269        let formulas_to_update: Vec<VertexId> = self.vertex_formulas.keys().copied().collect();
270        for formula_id in formulas_to_update {
271            if let Some(ast_id) = self.vertex_formulas.get(&formula_id)
272                && let Some(ast) = self.data_store.retrieve_ast(*ast_id, &self.sheet_reg)
273            {
274                let mut updated_ast = ast.clone();
275                updated_ast.update_sheet_references(Some(&old_name), new_name);
276
277                if ast != updated_ast {
278                    self.rebuild_formula_dependencies(formula_id, &updated_ast);
279                    let updated_ast_id = self.data_store.store_ast(&updated_ast, &self.sheet_reg);
280                    self.vertex_formulas.insert(formula_id, updated_ast_id);
281                }
282            }
283        }
284
285        self.end_batch();
286        Ok(())
287    }
288
289    /// Duplicate an existing sheet.
290    pub fn duplicate_sheet(
291        &mut self,
292        source_sheet_id: SheetId,
293        new_name: &str,
294    ) -> Result<SheetId, ExcelError> {
295        if new_name.is_empty() || new_name.len() > 255 {
296            return Err(ExcelError::new(ExcelErrorKind::Value).with_message("Invalid sheet name"));
297        }
298
299        let source_name = self.sheet_reg.name(source_sheet_id).to_string();
300        if source_name.is_empty() {
301            return Err(
302                ExcelError::new(ExcelErrorKind::Value).with_message("Source sheet does not exist")
303            );
304        }
305
306        if self.sheet_reg.get_id(new_name).is_some() {
307            return Err(ExcelError::new(ExcelErrorKind::Value)
308                .with_message(format!("Sheet '{new_name}' already exists")));
309        }
310
311        let new_sheet_id = self.add_sheet(new_name)?;
312
313        self.begin_batch();
314
315        let source_vertices: Vec<(VertexId, AbsCoord)> = self
316            .vertices_in_sheet(source_sheet_id)
317            .map(|id| (id, self.store.coord(id)))
318            .collect();
319
320        let mut vertex_mapping = FxHashMap::default();
321
322        for (old_id, coord) in &source_vertices {
323            let row = coord.row();
324            let col = coord.col();
325            let kind = self.store.kind(*old_id);
326
327            let new_id = self.store.allocate(*coord, new_sheet_id, 0x01);
328            self.edges.add_vertex(*coord, new_id.0);
329            self.sheet_index_mut(new_sheet_id)
330                .add_vertex(*coord, new_id);
331
332            self.store.set_kind(new_id, kind);
333
334            if let Some(&value_ref) = self.vertex_values.get(old_id) {
335                self.vertex_values.insert(new_id, value_ref);
336            }
337
338            vertex_mapping.insert(*old_id, new_id);
339
340            let cell_ref = CellRef::new(new_sheet_id, Coord::new(row, col, true, true));
341            self.cell_to_vertex.insert(cell_ref, new_id);
342        }
343
344        let sheet_names: Vec<(String, NamedRange)> = self
345            .sheet_named_ranges
346            .iter()
347            .filter(|((sid, _), _)| *sid == source_sheet_id)
348            .map(|((_, name), range)| (name.clone(), range.clone()))
349            .collect();
350
351        for (name, mut named_range) in sheet_names {
352            named_range.scope = NameScope::Sheet(new_sheet_id);
353
354            match &mut named_range.definition {
355                NamedDefinition::Cell(cell_ref) if cell_ref.sheet_id == source_sheet_id => {
356                    cell_ref.sheet_id = new_sheet_id;
357                }
358                NamedDefinition::Range(range_ref) => {
359                    if range_ref.start.sheet_id == source_sheet_id {
360                        range_ref.start.sheet_id = new_sheet_id;
361                        range_ref.end.sheet_id = new_sheet_id;
362                    }
363                }
364                _ => {}
365            }
366
367            named_range.dependents.clear();
368            let name_vertex = self.allocate_name_vertex(named_range.scope);
369            if matches!(named_range.definition, NamedDefinition::Range(_)) {
370                self.store.set_kind(name_vertex, VertexKind::NamedArray);
371            } else {
372                self.store.set_kind(name_vertex, VertexKind::NamedScalar);
373            }
374            named_range.vertex = name_vertex;
375
376            let referenced_names = self.rebuild_name_dependencies(
377                name_vertex,
378                &named_range.definition,
379                named_range.scope,
380            );
381            if !referenced_names.is_empty() {
382                self.attach_vertex_to_names(name_vertex, &referenced_names);
383            }
384
385            self.sheet_named_ranges
386                .insert((new_sheet_id, name.clone()), named_range);
387            self.sheet_named_ranges_lookup
388                .insert((new_sheet_id, self.name_lookup_key(&name)), name.clone());
389            self.name_vertex_lookup
390                .insert(name_vertex, (NameScope::Sheet(new_sheet_id), name));
391        }
392
393        for (old_id, _) in &source_vertices {
394            if let Some(&new_id) = vertex_mapping.get(old_id)
395                && let Some(&ast_id) = self.vertex_formulas.get(old_id)
396                && let Some(ast) = self.data_store.retrieve_ast(ast_id, &self.sheet_reg)
397            {
398                let updated_ast = update_internal_sheet_references(
399                    &ast,
400                    &source_name,
401                    new_name,
402                    source_sheet_id,
403                    new_sheet_id,
404                );
405
406                let new_ast_id = self.data_store.store_ast(&updated_ast, &self.sheet_reg);
407                self.vertex_formulas.insert(new_id, new_ast_id);
408
409                if let Ok((deps, range_deps, _, name_vertices)) =
410                    self.extract_dependencies(&updated_ast, new_sheet_id)
411                {
412                    let mapped_deps: Vec<VertexId> = deps
413                        .iter()
414                        .map(|&dep_id| vertex_mapping.get(&dep_id).copied().unwrap_or(dep_id))
415                        .collect();
416
417                    self.add_dependent_edges(new_id, &mapped_deps);
418                    self.add_range_dependent_edges(new_id, &range_deps, new_sheet_id);
419
420                    if !name_vertices.is_empty() {
421                        self.attach_vertex_to_names(new_id, &name_vertices);
422                    }
423                }
424            }
425        }
426
427        self.end_batch();
428
429        Ok(new_sheet_id)
430    }
431}