formualizer_workbook/
workbook.rs

1use crate::error::IoError;
2use crate::loader::WorkbookLoader;
3use crate::traits::{LoadStrategy, SpreadsheetReader, SpreadsheetWriter};
4use chrono::Timelike;
5use formualizer_common::{
6    LiteralValue, RangeAddress,
7    error::{ExcelError, ExcelErrorKind},
8};
9use formualizer_eval::engine::named_range::NamedDefinition;
10use std::cell::Cell;
11use std::collections::BTreeMap;
12use std::ptr;
13
14thread_local! {
15    static ACTIVE_WORKBOOK: Cell<*const Workbook> = const { Cell::new(ptr::null()) };
16}
17
18/// Minimal resolver for engine-backed workbook (cells/ranges via graph/arrow; functions via registry).
19#[derive(Default, Debug, Clone, Copy)]
20pub struct WBResolver;
21
22impl formualizer_eval::traits::ReferenceResolver for WBResolver {
23    fn resolve_cell_reference(
24        &self,
25        _sheet: Option<&str>,
26        _row: u32,
27        _col: u32,
28    ) -> Result<LiteralValue, formualizer_common::error::ExcelError> {
29        Err(formualizer_common::error::ExcelError::from(
30            formualizer_common::error::ExcelErrorKind::NImpl,
31        ))
32    }
33}
34impl formualizer_eval::traits::RangeResolver for WBResolver {
35    fn resolve_range_reference(
36        &self,
37        _sheet: Option<&str>,
38        _sr: Option<u32>,
39        _sc: Option<u32>,
40        _er: Option<u32>,
41        _ec: Option<u32>,
42    ) -> Result<Box<dyn formualizer_eval::traits::Range>, formualizer_common::error::ExcelError>
43    {
44        Err(formualizer_common::error::ExcelError::from(
45            formualizer_common::error::ExcelErrorKind::NImpl,
46        ))
47    }
48}
49impl formualizer_eval::traits::NamedRangeResolver for WBResolver {
50    fn resolve_named_range_reference(
51        &self,
52        _name: &str,
53    ) -> Result<Vec<Vec<LiteralValue>>, formualizer_common::error::ExcelError> {
54        ACTIVE_WORKBOOK.with(|cell| {
55            let ptr = cell.get();
56            if ptr.is_null() {
57                return Err(ExcelError::new(ExcelErrorKind::Name));
58            }
59            let workbook = unsafe { &*ptr };
60            if let Some(addr) = workbook.named_range_address(_name) {
61                Ok(workbook.read_range(&addr))
62            } else {
63                Err(ExcelError::new(ExcelErrorKind::Name))
64            }
65        })
66    }
67}
68impl formualizer_eval::traits::TableResolver for WBResolver {
69    fn resolve_table_reference(
70        &self,
71        _tref: &formualizer_parse::parser::TableReference,
72    ) -> Result<Box<dyn formualizer_eval::traits::Table>, formualizer_common::error::ExcelError>
73    {
74        Err(formualizer_common::error::ExcelError::from(
75            formualizer_common::error::ExcelErrorKind::NImpl,
76        ))
77    }
78}
79impl formualizer_eval::traits::FunctionProvider for WBResolver {
80    fn get_function(
81        &self,
82        ns: &str,
83        name: &str,
84    ) -> Option<std::sync::Arc<dyn formualizer_eval::function::Function>> {
85        formualizer_eval::function_registry::get(ns, name)
86    }
87}
88impl formualizer_eval::traits::Resolver for WBResolver {}
89impl formualizer_eval::traits::EvaluationContext for WBResolver {}
90
91/// Engine-backed workbook facade.
92pub struct Workbook {
93    engine: formualizer_eval::engine::Engine<WBResolver>,
94    enable_changelog: bool,
95    log: formualizer_eval::engine::ChangeLog,
96    undo: formualizer_eval::engine::graph::editor::undo_engine::UndoEngine,
97}
98
99impl Default for Workbook {
100    fn default() -> Self {
101        Self::new()
102    }
103}
104
105impl Workbook {
106    pub fn new_with_config(config: formualizer_eval::engine::EvalConfig) -> Self {
107        let engine = formualizer_eval::engine::Engine::new(WBResolver, config);
108        Self {
109            engine,
110            enable_changelog: false,
111            log: formualizer_eval::engine::ChangeLog::new(),
112            undo: formualizer_eval::engine::graph::editor::undo_engine::UndoEngine::new(),
113        }
114    }
115    pub fn new() -> Self {
116        let cfg = formualizer_eval::engine::EvalConfig {
117            defer_graph_building: true,
118            ..Default::default()
119        };
120        Self::new_with_config(cfg)
121    }
122
123    pub fn engine(&self) -> &formualizer_eval::engine::Engine<WBResolver> {
124        &self.engine
125    }
126    pub fn engine_mut(&mut self) -> &mut formualizer_eval::engine::Engine<WBResolver> {
127        &mut self.engine
128    }
129
130    // Changelog controls
131    pub fn set_changelog_enabled(&mut self, enabled: bool) {
132        self.enable_changelog = enabled;
133        self.log.set_enabled(enabled);
134    }
135    pub fn begin_action(&mut self, description: impl Into<String>) {
136        if self.enable_changelog {
137            self.log.begin_compound(description.into());
138        }
139    }
140    pub fn end_action(&mut self) {
141        if self.enable_changelog {
142            self.log.end_compound();
143        }
144    }
145    pub fn undo(&mut self) -> Result<(), IoError> {
146        if self.enable_changelog {
147            self.undo
148                .undo(&mut self.engine.graph, &mut self.log)
149                .map_err(|e| IoError::from_backend("editor", e))?;
150            self.resync_all_overlays();
151        }
152        Ok(())
153    }
154    pub fn redo(&mut self) -> Result<(), IoError> {
155        if self.enable_changelog {
156            self.undo
157                .redo(&mut self.engine.graph, &mut self.log)
158                .map_err(|e| IoError::from_backend("editor", e))?;
159            self.resync_all_overlays();
160        }
161        Ok(())
162    }
163
164    fn resync_all_overlays(&mut self) {
165        // Heavy but simple: walk all sheets and rebuild overlay values from graph
166        let sheet_names: Vec<String> = self
167            .engine
168            .sheet_store()
169            .sheets
170            .iter()
171            .map(|s| s.name.as_ref().to_string())
172            .collect();
173        for s in sheet_names {
174            self.resync_overlay_for_sheet(&s);
175        }
176    }
177    fn resync_overlay_for_sheet(&mut self, sheet: &str) {
178        if let Some(asheet) = self.engine.sheet_store().sheet(sheet) {
179            let rows = asheet.nrows as usize;
180            let cols = asheet.columns.len();
181            for r0 in 0..rows {
182                let r = (r0 as u32) + 1;
183                for c0 in 0..cols {
184                    let c = (c0 as u32) + 1;
185                    let v = self
186                        .engine
187                        .graph
188                        .get_cell_value(sheet, r, c)
189                        .unwrap_or(LiteralValue::Empty);
190                    self.mirror_value_to_overlay(sheet, r, c, &v);
191                }
192            }
193        }
194        // No Arrow sheet: nothing to sync
195    }
196    fn mirror_value_to_overlay(&mut self, sheet: &str, row: u32, col: u32, value: &LiteralValue) {
197        use formualizer_eval::arrow_store::OverlayValue;
198        if !(self.engine.config.arrow_storage_enabled && self.engine.config.delta_overlay_enabled) {
199            return;
200        }
201        let date_system = self.engine.config.date_system;
202        let asheet = match self.engine.sheet_store_mut().sheet_mut(sheet) {
203            Some(s) => s,
204            None => return,
205        };
206        let row0 = row.saturating_sub(1) as usize;
207        let col0 = col.saturating_sub(1) as usize;
208        if col0 >= asheet.columns.len() {
209            return;
210        }
211        if row0 >= asheet.nrows as usize {
212            asheet.ensure_row_capacity(row0 + 1);
213        }
214        if let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) {
215            let ov = match value {
216                LiteralValue::Empty => OverlayValue::Empty,
217                LiteralValue::Int(i) => OverlayValue::Number(*i as f64),
218                LiteralValue::Number(n) => OverlayValue::Number(*n),
219                LiteralValue::Boolean(b) => OverlayValue::Boolean(*b),
220                LiteralValue::Text(s) => OverlayValue::Text(std::sync::Arc::from(s.clone())),
221                LiteralValue::Error(e) => {
222                    OverlayValue::Error(formualizer_eval::arrow_store::map_error_code(e.kind))
223                }
224                LiteralValue::Date(d) => {
225                    let dt = d.and_hms_opt(0, 0, 0).unwrap();
226                    let serial = formualizer_eval::builtins::datetime::datetime_to_serial_for(
227                        date_system,
228                        &dt,
229                    );
230                    OverlayValue::Number(serial)
231                }
232                LiteralValue::DateTime(dt) => {
233                    let serial = formualizer_eval::builtins::datetime::datetime_to_serial_for(
234                        date_system,
235                        dt,
236                    );
237                    OverlayValue::Number(serial)
238                }
239                LiteralValue::Time(t) => {
240                    let serial = t.num_seconds_from_midnight() as f64 / 86_400.0;
241                    OverlayValue::Number(serial)
242                }
243                LiteralValue::Duration(d) => {
244                    let serial = d.num_seconds() as f64 / 86_400.0;
245                    OverlayValue::Number(serial)
246                }
247                LiteralValue::Pending => OverlayValue::Pending,
248                LiteralValue::Array(_) => {
249                    OverlayValue::Error(formualizer_eval::arrow_store::map_error_code(
250                        formualizer_common::ExcelErrorKind::Value,
251                    ))
252                }
253            };
254            let colref = &mut asheet.columns[col0];
255            let ch = &mut colref.chunks[ch_idx];
256            ch.overlay.set(in_off, ov);
257            // skip compaction here (optional)
258        }
259    }
260
261    // Sheets
262    pub fn sheet_names(&self) -> Vec<String> {
263        self.engine
264            .sheet_store()
265            .sheets
266            .iter()
267            .map(|s| s.name.as_ref().to_string())
268            .collect()
269    }
270    /// Return (rows, cols) for a sheet if present in the Arrow store
271    pub fn sheet_dimensions(&self, name: &str) -> Option<(u32, u32)> {
272        self.engine
273            .sheet_store()
274            .sheet(name)
275            .map(|s| (s.nrows, s.columns.len() as u32))
276    }
277    pub fn has_sheet(&self, name: &str) -> bool {
278        self.engine.graph.sheet_id(name).is_some()
279    }
280    pub fn add_sheet(&mut self, name: &str) {
281        let _ = self.engine.graph.add_sheet(name);
282    }
283    pub fn delete_sheet(&mut self, name: &str) {
284        if let Some(id) = self.engine.graph.sheet_id(name) {
285            let _ = self.engine.graph.remove_sheet(id);
286        }
287        // Remove from Arrow store as well
288        self.engine
289            .sheet_store_mut()
290            .sheets
291            .retain(|s| s.name.as_ref() != name);
292    }
293    pub fn rename_sheet(&mut self, old: &str, new: &str) {
294        if let Some(id) = self.engine.graph.sheet_id(old) {
295            let _ = self.engine.graph.rename_sheet(id, new);
296        }
297        if let Some(asheet) = self.engine.sheet_store_mut().sheet_mut(old) {
298            asheet.name = std::sync::Arc::<str>::from(new);
299        }
300    }
301
302    // Cells
303    pub fn set_value(
304        &mut self,
305        sheet: &str,
306        row: u32,
307        col: u32,
308        value: LiteralValue,
309    ) -> Result<(), IoError> {
310        if self.enable_changelog {
311            // Use VertexEditor with logging for graph, then mirror overlay and mark edited
312            let sheet_id = self
313                .engine
314                .graph
315                .sheet_id(sheet)
316                .unwrap_or_else(|| self.engine.graph.add_sheet(sheet).unwrap());
317            let cell = formualizer_eval::reference::CellRef::new(
318                sheet_id,
319                formualizer_eval::reference::Coord::from_excel(row, col, true, true),
320            );
321            {
322                let mut editor = formualizer_eval::engine::VertexEditor::with_logger(
323                    &mut self.engine.graph,
324                    &mut self.log,
325                );
326                editor.set_cell_value(cell, value.clone());
327            }
328            self.mirror_value_to_overlay(sheet, row, col, &value);
329            self.engine.mark_data_edited();
330            Ok(())
331        } else {
332            self.engine
333                .set_cell_value(sheet, row, col, value)
334                .map_err(IoError::Engine)
335        }
336    }
337    pub fn set_formula(
338        &mut self,
339        sheet: &str,
340        row: u32,
341        col: u32,
342        formula: &str,
343    ) -> Result<(), IoError> {
344        if self.engine.config.defer_graph_building {
345            self.engine
346                .stage_formula_text(sheet, row, col, formula.to_string());
347            Ok(())
348        } else {
349            let with_eq = if formula.starts_with('=') {
350                formula.to_string()
351            } else {
352                format!("={formula}")
353            };
354            let ast = formualizer_parse::parser::parse(&with_eq)
355                .map_err(|e| IoError::from_backend("parser", e))?;
356            if self.enable_changelog {
357                let sheet_id = self
358                    .engine
359                    .graph
360                    .sheet_id(sheet)
361                    .unwrap_or_else(|| self.engine.graph.add_sheet(sheet).unwrap());
362                let cell = formualizer_eval::reference::CellRef::new(
363                    sheet_id,
364                    formualizer_eval::reference::Coord::from_excel(row, col, true, true),
365                );
366                {
367                    let mut editor = formualizer_eval::engine::VertexEditor::with_logger(
368                        &mut self.engine.graph,
369                        &mut self.log,
370                    );
371                    editor.set_cell_formula(cell, ast);
372                }
373                self.engine.mark_data_edited();
374                Ok(())
375            } else {
376                self.engine
377                    .set_cell_formula(sheet, row, col, ast)
378                    .map_err(IoError::Engine)
379            }
380        }
381    }
382    pub fn get_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
383        self.engine.get_cell_value(sheet, row, col)
384    }
385    pub fn get_formula(&self, sheet: &str, row: u32, col: u32) -> Option<String> {
386        if let Some(s) = self.engine.get_staged_formula_text(sheet, row, col) {
387            return Some(s);
388        }
389        self.engine
390            .get_cell(sheet, row, col)
391            .and_then(|(ast, _)| ast.map(|a| formualizer_parse::pretty::canonical_formula(&a)))
392    }
393
394    // Ranges
395    pub fn read_range(&self, addr: &RangeAddress) -> Vec<Vec<LiteralValue>> {
396        let mut out = Vec::with_capacity(addr.height() as usize);
397        if let Some(asheet) = self.engine.sheet_store().sheet(&addr.sheet) {
398            let sr0 = addr.start_row.saturating_sub(1) as usize;
399            let sc0 = addr.start_col.saturating_sub(1) as usize;
400            let er0 = addr.end_row.saturating_sub(1) as usize;
401            let ec0 = addr.end_col.saturating_sub(1) as usize;
402            let view = asheet.range_view(sr0, sc0, er0, ec0);
403            let (h, w) = view.dims();
404            for rr in 0..h {
405                let mut row = Vec::with_capacity(w);
406                for cc in 0..w {
407                    row.push(view.get_cell(rr, cc));
408                }
409                out.push(row);
410            }
411        } else {
412            // Fallback: materialize via graph stored values
413            for r in addr.start_row..=addr.end_row {
414                let mut row = Vec::with_capacity(addr.width() as usize);
415                for c in addr.start_col..=addr.end_col {
416                    row.push(
417                        self.engine
418                            .graph
419                            .get_cell_value(&addr.sheet, r, c)
420                            .unwrap_or(LiteralValue::Empty),
421                    );
422                }
423                out.push(row);
424            }
425        }
426        out
427    }
428    pub fn write_range(
429        &mut self,
430        sheet: &str,
431        _start: (u32, u32),
432        cells: BTreeMap<(u32, u32), crate::traits::CellData>,
433    ) -> Result<(), IoError> {
434        if self.enable_changelog {
435            let sheet_id = self
436                .engine
437                .graph
438                .sheet_id(sheet)
439                .unwrap_or_else(|| self.engine.graph.add_sheet(sheet).unwrap());
440            let mut overlay_ops: Vec<(u32, u32, LiteralValue)> = Vec::new();
441            let mut staged_forms: Vec<(u32, u32, String)> = Vec::new();
442            {
443                let mut editor = formualizer_eval::engine::VertexEditor::with_logger(
444                    &mut self.engine.graph,
445                    &mut self.log,
446                );
447                for ((r, c), d) in cells.into_iter() {
448                    let cell = formualizer_eval::reference::CellRef::new(
449                        sheet_id,
450                        formualizer_eval::reference::Coord::from_excel(r, c, true, true),
451                    );
452                    if let Some(v) = d.value.clone() {
453                        editor.set_cell_value(cell, v.clone());
454                        overlay_ops.push((r, c, v));
455                    }
456                    if let Some(f) = d.formula.as_ref() {
457                        if self.engine.config.defer_graph_building {
458                            staged_forms.push((r, c, f.clone()));
459                        } else {
460                            let with_eq = if f.starts_with('=') {
461                                f.clone()
462                            } else {
463                                format!("={f}")
464                            };
465                            let ast = formualizer_parse::parser::parse(&with_eq)
466                                .map_err(|e| IoError::from_backend("parser", e))?;
467                            editor.set_cell_formula(cell, ast);
468                        }
469                    }
470                }
471            }
472            for (r, c, v) in overlay_ops {
473                self.mirror_value_to_overlay(sheet, r, c, &v);
474            }
475            for (r, c, f) in staged_forms {
476                self.engine.stage_formula_text(sheet, r, c, f);
477            }
478            self.engine.mark_data_edited();
479            Ok(())
480        } else {
481            for ((r, c), d) in cells.into_iter() {
482                if let Some(v) = d.value.clone() {
483                    self.engine
484                        .set_cell_value(sheet, r, c, v)
485                        .map_err(IoError::Engine)?;
486                }
487                if let Some(f) = d.formula.as_ref() {
488                    if self.engine.config.defer_graph_building {
489                        self.engine.stage_formula_text(sheet, r, c, f.clone());
490                    } else {
491                        let with_eq = if f.starts_with('=') {
492                            f.clone()
493                        } else {
494                            format!("={f}")
495                        };
496                        let ast = formualizer_parse::parser::parse(&with_eq)
497                            .map_err(|e| IoError::from_backend("parser", e))?;
498                        self.engine
499                            .set_cell_formula(sheet, r, c, ast)
500                            .map_err(IoError::Engine)?;
501                    }
502                }
503            }
504            Ok(())
505        }
506    }
507
508    // Batch set values in a rectangle starting at (start_row,start_col)
509    pub fn set_values(
510        &mut self,
511        sheet: &str,
512        start_row: u32,
513        start_col: u32,
514        rows: &[Vec<LiteralValue>],
515    ) -> Result<(), IoError> {
516        if self.enable_changelog {
517            let sheet_id = self
518                .engine
519                .graph
520                .sheet_id(sheet)
521                .unwrap_or_else(|| self.engine.graph.add_sheet(sheet).unwrap());
522            let mut overlay_ops: Vec<(u32, u32, LiteralValue)> = Vec::new();
523            {
524                let mut editor = formualizer_eval::engine::VertexEditor::with_logger(
525                    &mut self.engine.graph,
526                    &mut self.log,
527                );
528                for (ri, rvals) in rows.iter().enumerate() {
529                    let r = start_row + ri as u32;
530                    for (ci, v) in rvals.iter().enumerate() {
531                        let c = start_col + ci as u32;
532                        let cell = formualizer_eval::reference::CellRef::new(
533                            sheet_id,
534                            formualizer_eval::reference::Coord::from_excel(r, c, true, true),
535                        );
536                        editor.set_cell_value(cell, v.clone());
537                        overlay_ops.push((r, c, v.clone()));
538                    }
539                }
540            }
541            for (r, c, v) in overlay_ops {
542                self.mirror_value_to_overlay(sheet, r, c, &v);
543            }
544            self.engine.mark_data_edited();
545            Ok(())
546        } else {
547            for (ri, rvals) in rows.iter().enumerate() {
548                let r = start_row + ri as u32;
549                for (ci, v) in rvals.iter().enumerate() {
550                    let c = start_col + ci as u32;
551                    self.engine
552                        .set_cell_value(sheet, r, c, v.clone())
553                        .map_err(IoError::Engine)?;
554                }
555            }
556            Ok(())
557        }
558    }
559
560    // Batch set formulas in a rectangle starting at (start_row,start_col)
561    pub fn set_formulas(
562        &mut self,
563        sheet: &str,
564        start_row: u32,
565        start_col: u32,
566        rows: &[Vec<String>],
567    ) -> Result<(), IoError> {
568        if self.engine.config.defer_graph_building {
569            for (ri, rforms) in rows.iter().enumerate() {
570                let r = start_row + ri as u32;
571                for (ci, f) in rforms.iter().enumerate() {
572                    let c = start_col + ci as u32;
573                    self.engine.stage_formula_text(sheet, r, c, f.clone());
574                }
575            }
576            Ok(())
577        } else if self.enable_changelog {
578            let sheet_id = self
579                .engine
580                .graph
581                .sheet_id(sheet)
582                .unwrap_or_else(|| self.engine.graph.add_sheet(sheet).unwrap());
583            {
584                let mut editor = formualizer_eval::engine::VertexEditor::with_logger(
585                    &mut self.engine.graph,
586                    &mut self.log,
587                );
588                for (ri, rforms) in rows.iter().enumerate() {
589                    let r = start_row + ri as u32;
590                    for (ci, f) in rforms.iter().enumerate() {
591                        let c = start_col + ci as u32;
592                        let cell = formualizer_eval::reference::CellRef::new(
593                            sheet_id,
594                            formualizer_eval::reference::Coord::from_excel(r, c, true, true),
595                        );
596                        let with_eq = if f.starts_with('=') {
597                            f.clone()
598                        } else {
599                            format!("={f}")
600                        };
601                        let ast = formualizer_parse::parser::parse(&with_eq)
602                            .map_err(|e| IoError::from_backend("parser", e))?;
603                        editor.set_cell_formula(cell, ast);
604                    }
605                }
606            }
607            self.engine.mark_data_edited();
608            Ok(())
609        } else {
610            for (ri, rforms) in rows.iter().enumerate() {
611                let r = start_row + ri as u32;
612                for (ci, f) in rforms.iter().enumerate() {
613                    let c = start_col + ci as u32;
614                    let with_eq = if f.starts_with('=') {
615                        f.clone()
616                    } else {
617                        format!("={f}")
618                    };
619                    let ast = formualizer_parse::parser::parse(&with_eq)
620                        .map_err(|e| IoError::from_backend("parser", e))?;
621                    self.engine
622                        .set_cell_formula(sheet, r, c, ast)
623                        .map_err(IoError::Engine)?;
624                }
625            }
626            Ok(())
627        }
628    }
629
630    // Evaluation
631    pub fn prepare_graph_all(&mut self) -> Result<(), IoError> {
632        self.engine
633            .build_graph_all()
634            .map_err(|e| IoError::from_backend("parser", e))
635    }
636    pub fn prepare_graph_for_sheets<'a, I: IntoIterator<Item = &'a str>>(
637        &mut self,
638        sheets: I,
639    ) -> Result<(), IoError> {
640        self.engine
641            .build_graph_for_sheets(sheets)
642            .map_err(|e| IoError::from_backend("parser", e))
643    }
644    pub fn evaluate_cell(
645        &mut self,
646        sheet: &str,
647        row: u32,
648        col: u32,
649    ) -> Result<LiteralValue, IoError> {
650        ACTIVE_WORKBOOK.with(|cell| {
651            let previous = cell.replace(self as *const _);
652            let result = self
653                .engine
654                .evaluate_cell(sheet, row, col)
655                .map_err(IoError::Engine)
656                .map(|value| value.unwrap_or(LiteralValue::Empty));
657            cell.set(previous);
658            result
659        })
660    }
661    pub fn evaluate_cells(
662        &mut self,
663        targets: &[(&str, u32, u32)],
664    ) -> Result<Vec<LiteralValue>, IoError> {
665        ACTIVE_WORKBOOK.with(|cell| {
666            let previous = cell.replace(self as *const _);
667            let result = self
668                .engine
669                .evaluate_cells(targets)
670                .map_err(IoError::Engine)
671                .map(|values| {
672                    values
673                        .into_iter()
674                        .map(|v| v.unwrap_or(LiteralValue::Empty))
675                        .collect()
676                });
677            cell.set(previous);
678            result
679        })
680    }
681    pub fn evaluate_all(&mut self) -> Result<formualizer_eval::engine::EvalResult, IoError> {
682        ACTIVE_WORKBOOK.with(|cell| {
683            let previous = cell.replace(self as *const _);
684            let result = self.engine.evaluate_all().map_err(IoError::Engine);
685            cell.set(previous);
686            result
687        })
688    }
689
690    pub fn evaluate_with_plan(
691        &mut self,
692        plan: &formualizer_eval::engine::RecalcPlan,
693    ) -> Result<formualizer_eval::engine::EvalResult, IoError> {
694        ACTIVE_WORKBOOK.with(|cell| {
695            let previous = cell.replace(self as *const _);
696            let result = self
697                .engine
698                .evaluate_recalc_plan(plan)
699                .map_err(IoError::Engine);
700            cell.set(previous);
701            result
702        })
703    }
704
705    /// Resolve a named range (workbook-scoped or unique sheet-scoped) to an absolute address.
706    pub fn named_range_address(&self, name: &str) -> Option<RangeAddress> {
707        if let Some((_, named)) = self
708            .engine
709            .graph
710            .named_ranges_iter()
711            .find(|(n, _)| n.as_str() == name)
712        {
713            return self.named_definition_to_address(&named.definition);
714        }
715
716        let mut resolved: Option<RangeAddress> = None;
717        for ((_sheet_id, candidate), named) in self.engine.graph.sheet_named_ranges_iter() {
718            if candidate == name
719                && let Some(address) = self.named_definition_to_address(&named.definition)
720            {
721                if resolved.is_some() {
722                    return None; // ambiguous sheet-scoped name
723                }
724                resolved = Some(address);
725            }
726        }
727        resolved
728    }
729
730    fn named_definition_to_address(&self, definition: &NamedDefinition) -> Option<RangeAddress> {
731        match definition {
732            NamedDefinition::Cell(cell) => {
733                let sheet = self.engine.graph.sheet_name(cell.sheet_id).to_string();
734                let row = cell.coord.row() + 1;
735                let col = cell.coord.col() + 1;
736                RangeAddress::new(sheet, row, col, row, col).ok()
737            }
738            NamedDefinition::Range(range) => {
739                if range.start.sheet_id != range.end.sheet_id {
740                    return None;
741                }
742                let sheet = self
743                    .engine
744                    .graph
745                    .sheet_name(range.start.sheet_id)
746                    .to_string();
747                let start_row = range.start.coord.row() + 1;
748                let start_col = range.start.coord.col() + 1;
749                let end_row = range.end.coord.row() + 1;
750                let end_col = range.end.coord.col() + 1;
751                RangeAddress::new(sheet, start_row, start_col, end_row, end_col).ok()
752            }
753            NamedDefinition::Formula { .. } => {
754                #[cfg(feature = "tracing")]
755                tracing::debug!("formula-backed named ranges are not yet supported");
756                None
757            }
758        }
759    }
760
761    // Persistence/transactions via SpreadsheetWriter (self implements writer)
762    pub fn begin_tx<'a, W: SpreadsheetWriter>(
763        &'a mut self,
764        writer: &'a mut W,
765    ) -> crate::transaction::WriteTransaction<'a, W> {
766        crate::transaction::WriteTransaction::new(writer)
767    }
768
769    // Loading via SpreadsheetReader
770    pub fn from_reader<B: SpreadsheetReader>(
771        backend: B,
772        strategy: LoadStrategy,
773        mut config: formualizer_eval::engine::EvalConfig,
774    ) -> Result<Self, IoError> {
775        config.defer_graph_building = true;
776        let mut wb = Self::new_with_config(config);
777        let mut loader = WorkbookLoader::new(backend, strategy);
778        loader.load_into_engine(&mut wb.engine)?;
779        Ok(wb)
780    }
781}
782
783// Implement SpreadsheetWriter so external transactions can target Workbook
784impl SpreadsheetWriter for Workbook {
785    type Error = IoError;
786
787    fn write_cell(
788        &mut self,
789        sheet: &str,
790        row: u32,
791        col: u32,
792        data: crate::traits::CellData,
793    ) -> Result<(), Self::Error> {
794        if let Some(v) = data.value {
795            self.set_value(sheet, row, col, v)?;
796        }
797        if let Some(f) = data.formula {
798            self.set_formula(sheet, row, col, &f)?;
799        }
800        Ok(())
801    }
802    fn write_range(
803        &mut self,
804        sheet: &str,
805        cells: BTreeMap<(u32, u32), crate::traits::CellData>,
806    ) -> Result<(), Self::Error> {
807        for ((r, c), d) in cells {
808            self.write_cell(sheet, r, c, d)?;
809        }
810        Ok(())
811    }
812    fn clear_range(
813        &mut self,
814        sheet: &str,
815        start: (u32, u32),
816        end: (u32, u32),
817    ) -> Result<(), Self::Error> {
818        for r in start.0..=end.0 {
819            for c in start.1..=end.1 {
820                self.set_value(sheet, r, c, LiteralValue::Empty)?;
821            }
822        }
823        Ok(())
824    }
825    fn create_sheet(&mut self, name: &str) -> Result<(), Self::Error> {
826        self.add_sheet(name);
827        Ok(())
828    }
829    fn delete_sheet(&mut self, name: &str) -> Result<(), Self::Error> {
830        self.delete_sheet(name);
831        Ok(())
832    }
833    fn rename_sheet(&mut self, old: &str, new: &str) -> Result<(), Self::Error> {
834        self.rename_sheet(old, new);
835        Ok(())
836    }
837    fn flush(&mut self) -> Result<(), Self::Error> {
838        Ok(())
839    }
840}