Skip to main content

formualizer_workbook/
workbook.rs

1use crate::error::IoError;
2use crate::traits::{LoadStrategy, SpreadsheetReader, SpreadsheetWriter};
3use chrono::Timelike;
4use formualizer_common::{
5    LiteralValue, RangeAddress,
6    error::{ExcelError, ExcelErrorKind},
7};
8use formualizer_eval::engine::eval::EvalPlan;
9use formualizer_eval::engine::named_range::{NameScope, NamedDefinition};
10use std::collections::BTreeMap;
11
12/// Minimal resolver for engine-backed workbook (cells/ranges via graph/arrow; functions via registry).
13#[derive(Default, Debug, Clone, Copy)]
14pub struct WBResolver;
15
16impl formualizer_eval::traits::ReferenceResolver for WBResolver {
17    fn resolve_cell_reference(
18        &self,
19        _sheet: Option<&str>,
20        _row: u32,
21        _col: u32,
22    ) -> Result<LiteralValue, formualizer_common::error::ExcelError> {
23        Err(formualizer_common::error::ExcelError::from(
24            formualizer_common::error::ExcelErrorKind::NImpl,
25        ))
26    }
27}
28impl formualizer_eval::traits::RangeResolver for WBResolver {
29    fn resolve_range_reference(
30        &self,
31        _sheet: Option<&str>,
32        _sr: Option<u32>,
33        _sc: Option<u32>,
34        _er: Option<u32>,
35        _ec: Option<u32>,
36    ) -> Result<Box<dyn formualizer_eval::traits::Range>, formualizer_common::error::ExcelError>
37    {
38        Err(formualizer_common::error::ExcelError::from(
39            formualizer_common::error::ExcelErrorKind::NImpl,
40        ))
41    }
42}
43impl formualizer_eval::traits::NamedRangeResolver for WBResolver {
44    fn resolve_named_range_reference(
45        &self,
46        _name: &str,
47    ) -> Result<Vec<Vec<LiteralValue>>, formualizer_common::error::ExcelError> {
48        Err(ExcelError::new(ExcelErrorKind::Name))
49    }
50}
51impl formualizer_eval::traits::TableResolver for WBResolver {
52    fn resolve_table_reference(
53        &self,
54        _tref: &formualizer_parse::parser::TableReference,
55    ) -> Result<Box<dyn formualizer_eval::traits::Table>, formualizer_common::error::ExcelError>
56    {
57        Err(formualizer_common::error::ExcelError::from(
58            formualizer_common::error::ExcelErrorKind::NImpl,
59        ))
60    }
61}
62impl formualizer_eval::traits::SourceResolver for WBResolver {}
63impl formualizer_eval::traits::FunctionProvider for WBResolver {
64    fn get_function(
65        &self,
66        ns: &str,
67        name: &str,
68    ) -> Option<std::sync::Arc<dyn formualizer_eval::function::Function>> {
69        formualizer_eval::function_registry::get(ns, name)
70    }
71}
72impl formualizer_eval::traits::Resolver for WBResolver {}
73impl formualizer_eval::traits::EvaluationContext for WBResolver {}
74
75/// Engine-backed workbook facade.
76pub struct Workbook {
77    engine: formualizer_eval::engine::Engine<WBResolver>,
78    enable_changelog: bool,
79    log: formualizer_eval::engine::ChangeLog,
80    undo: formualizer_eval::engine::graph::editor::undo_engine::UndoEngine,
81}
82
83#[derive(Clone, Copy, Debug, PartialEq, Eq)]
84pub enum WorkbookMode {
85    /// Fastpath parity with direct Engine usage.
86    Ephemeral,
87    /// Default workbook behavior (changelog + deferred graph build).
88    Interactive,
89}
90
91#[derive(Clone, Debug)]
92pub struct WorkbookConfig {
93    pub eval: formualizer_eval::engine::EvalConfig,
94    pub enable_changelog: bool,
95}
96
97impl WorkbookConfig {
98    pub fn ephemeral() -> Self {
99        Self {
100            eval: formualizer_eval::engine::EvalConfig::default(),
101            enable_changelog: false,
102        }
103    }
104
105    pub fn interactive() -> Self {
106        let mut eval = formualizer_eval::engine::EvalConfig::default();
107        eval.defer_graph_building = true;
108        Self {
109            eval,
110            enable_changelog: true,
111        }
112    }
113}
114
115impl Default for Workbook {
116    fn default() -> Self {
117        Self::new()
118    }
119}
120
121impl Workbook {
122    pub fn new_with_config(mut config: WorkbookConfig) -> Self {
123        config.eval.arrow_storage_enabled = true;
124        config.eval.delta_overlay_enabled = true;
125        config.eval.write_formula_overlay_enabled = true;
126        let engine = formualizer_eval::engine::Engine::new(WBResolver, config.eval);
127        let mut log = formualizer_eval::engine::ChangeLog::new();
128        log.set_enabled(config.enable_changelog);
129        Self {
130            engine,
131            enable_changelog: config.enable_changelog,
132            log,
133            undo: formualizer_eval::engine::graph::editor::undo_engine::UndoEngine::new(),
134        }
135    }
136    pub fn new_with_mode(mode: WorkbookMode) -> Self {
137        let config = match mode {
138            WorkbookMode::Ephemeral => WorkbookConfig::ephemeral(),
139            WorkbookMode::Interactive => WorkbookConfig::interactive(),
140        };
141        Self::new_with_config(config)
142    }
143    pub fn new() -> Self {
144        Self::new_with_mode(WorkbookMode::Interactive)
145    }
146
147    pub fn engine(&self) -> &formualizer_eval::engine::Engine<WBResolver> {
148        &self.engine
149    }
150    pub fn engine_mut(&mut self) -> &mut formualizer_eval::engine::Engine<WBResolver> {
151        &mut self.engine
152    }
153    pub fn eval_config(&self) -> &formualizer_eval::engine::EvalConfig {
154        &self.engine.config
155    }
156
157    // Changelog controls
158    pub fn set_changelog_enabled(&mut self, enabled: bool) {
159        self.enable_changelog = enabled;
160        self.log.set_enabled(enabled);
161    }
162    pub fn begin_action(&mut self, description: impl Into<String>) {
163        if self.enable_changelog {
164            self.log.begin_compound(description.into());
165        }
166    }
167    pub fn end_action(&mut self) {
168        if self.enable_changelog {
169            self.log.end_compound();
170        }
171    }
172    pub fn undo(&mut self) -> Result<(), IoError> {
173        if self.enable_changelog {
174            self.engine
175                .undo_logged(&mut self.undo, &mut self.log)
176                .map_err(|e| IoError::from_backend("editor", e))?;
177            self.resync_all_overlays();
178        }
179        Ok(())
180    }
181    pub fn redo(&mut self) -> Result<(), IoError> {
182        if self.enable_changelog {
183            self.engine
184                .redo_logged(&mut self.undo, &mut self.log)
185                .map_err(|e| IoError::from_backend("editor", e))?;
186            self.resync_all_overlays();
187        }
188        Ok(())
189    }
190
191    fn resync_all_overlays(&mut self) {
192        // Heavy but simple: walk all sheets and rebuild overlay values from graph
193        let sheet_names: Vec<String> = self
194            .engine
195            .sheet_store()
196            .sheets
197            .iter()
198            .map(|s| s.name.as_ref().to_string())
199            .collect();
200        for s in sheet_names {
201            self.resync_overlay_for_sheet(&s);
202        }
203    }
204    fn resync_overlay_for_sheet(&mut self, sheet: &str) {
205        if let Some(asheet) = self.engine.sheet_store().sheet(sheet) {
206            let rows = asheet.nrows as usize;
207            let cols = asheet.columns.len();
208            for r0 in 0..rows {
209                let r = (r0 as u32) + 1;
210                for c0 in 0..cols {
211                    let c = (c0 as u32) + 1;
212                    let v = self
213                        .engine
214                        .graph_cell_value(sheet, r, c)
215                        .unwrap_or(LiteralValue::Empty);
216                    self.mirror_value_to_overlay(sheet, r, c, &v);
217                }
218            }
219        }
220        // No Arrow sheet: nothing to sync
221    }
222
223    fn ensure_arrow_sheet_capacity(&mut self, sheet: &str, min_rows: usize, min_cols: usize) {
224        use formualizer_eval::arrow_store::ArrowSheet;
225
226        if self.engine.sheet_store().sheet(sheet).is_none() {
227            self.engine.sheet_store_mut().sheets.push(ArrowSheet {
228                name: std::sync::Arc::<str>::from(sheet),
229                columns: Vec::new(),
230                nrows: 0,
231                chunk_starts: Vec::new(),
232                chunk_rows: 32 * 1024,
233            });
234        }
235
236        let asheet = self
237            .engine
238            .sheet_store_mut()
239            .sheet_mut(sheet)
240            .expect("ArrowSheet must exist");
241
242        // Ensure rows first so nrows is set before inserting columns
243        if min_rows > asheet.nrows as usize {
244            asheet.ensure_row_capacity(min_rows);
245        }
246
247        // Then ensure columns - they will get properly sized chunks since nrows is set
248        let cur_cols = asheet.columns.len();
249        if min_cols > cur_cols {
250            asheet.insert_columns(cur_cols, min_cols - cur_cols);
251        }
252    }
253
254    fn mirror_value_to_overlay(&mut self, sheet: &str, row: u32, col: u32, value: &LiteralValue) {
255        use formualizer_eval::arrow_store::OverlayValue;
256        if !(self.engine.config.arrow_storage_enabled && self.engine.config.delta_overlay_enabled) {
257            return;
258        }
259        let date_system = self.engine.config.date_system;
260        let row0 = row.saturating_sub(1) as usize;
261        let col0 = col.saturating_sub(1) as usize;
262        self.ensure_arrow_sheet_capacity(sheet, row0 + 1, col0 + 1);
263        let asheet = self
264            .engine
265            .sheet_store_mut()
266            .sheet_mut(sheet)
267            .expect("ArrowSheet must exist");
268        if let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) {
269            let ov = match value {
270                LiteralValue::Empty => OverlayValue::Empty,
271                LiteralValue::Int(i) => OverlayValue::Number(*i as f64),
272                LiteralValue::Number(n) => OverlayValue::Number(*n),
273                LiteralValue::Boolean(b) => OverlayValue::Boolean(*b),
274                LiteralValue::Text(s) => OverlayValue::Text(std::sync::Arc::from(s.clone())),
275                LiteralValue::Error(e) => {
276                    OverlayValue::Error(formualizer_eval::arrow_store::map_error_code(e.kind))
277                }
278                LiteralValue::Date(d) => {
279                    let dt = d.and_hms_opt(0, 0, 0).unwrap();
280                    let serial = formualizer_eval::builtins::datetime::datetime_to_serial_for(
281                        date_system,
282                        &dt,
283                    );
284                    OverlayValue::Number(serial)
285                }
286                LiteralValue::DateTime(dt) => {
287                    let serial = formualizer_eval::builtins::datetime::datetime_to_serial_for(
288                        date_system,
289                        dt,
290                    );
291                    OverlayValue::Number(serial)
292                }
293                LiteralValue::Time(t) => {
294                    let serial = t.num_seconds_from_midnight() as f64 / 86_400.0;
295                    OverlayValue::Number(serial)
296                }
297                LiteralValue::Duration(d) => {
298                    let serial = d.num_seconds() as f64 / 86_400.0;
299                    OverlayValue::Number(serial)
300                }
301                LiteralValue::Pending => OverlayValue::Pending,
302                LiteralValue::Array(_) => {
303                    OverlayValue::Error(formualizer_eval::arrow_store::map_error_code(
304                        formualizer_common::ExcelErrorKind::Value,
305                    ))
306                }
307            };
308            // Use ensure_column_chunk_mut to lazily create chunk if needed
309            if let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) {
310                ch.overlay.set(in_off, ov);
311            }
312        }
313    }
314
315    // Sheets
316    pub fn sheet_names(&self) -> Vec<String> {
317        self.engine
318            .sheet_store()
319            .sheets
320            .iter()
321            .map(|s| s.name.as_ref().to_string())
322            .collect()
323    }
324    /// Return (rows, cols) for a sheet if present in the Arrow store
325    pub fn sheet_dimensions(&self, name: &str) -> Option<(u32, u32)> {
326        self.engine
327            .sheet_store()
328            .sheet(name)
329            .map(|s| (s.nrows, s.columns.len() as u32))
330    }
331    pub fn has_sheet(&self, name: &str) -> bool {
332        self.engine.sheet_id(name).is_some()
333    }
334    pub fn add_sheet(&mut self, name: &str) -> Result<(), ExcelError> {
335        self.engine.add_sheet(name)?;
336        self.ensure_arrow_sheet_capacity(name, 0, 0);
337        Ok(())
338    }
339    pub fn delete_sheet(&mut self, name: &str) -> Result<(), ExcelError> {
340        if let Some(id) = self.engine.sheet_id(name) {
341            self.engine.remove_sheet(id)?;
342        }
343        // Remove from Arrow store as well
344        self.engine
345            .sheet_store_mut()
346            .sheets
347            .retain(|s| s.name.as_ref() != name);
348        Ok(())
349    }
350    pub fn rename_sheet(&mut self, old: &str, new: &str) -> Result<(), ExcelError> {
351        if let Some(id) = self.engine.sheet_id(old) {
352            self.engine.rename_sheet(id, new)?;
353        }
354        if let Some(asheet) = self.engine.sheet_store_mut().sheet_mut(old) {
355            asheet.name = std::sync::Arc::<str>::from(new);
356        }
357        Ok(())
358    }
359
360    // Cells
361    pub fn set_value(
362        &mut self,
363        sheet: &str,
364        row: u32,
365        col: u32,
366        value: LiteralValue,
367    ) -> Result<(), IoError> {
368        self.ensure_arrow_sheet_capacity(sheet, row as usize, col as usize);
369        if self.enable_changelog {
370            // Use VertexEditor with logging for graph, then mirror overlay and mark edited
371            let sheet_id = self
372                .engine
373                .sheet_id(sheet)
374                .unwrap_or_else(|| self.engine.add_sheet(sheet).expect("add sheet"));
375            let cell = formualizer_eval::reference::CellRef::new(
376                sheet_id,
377                formualizer_eval::reference::Coord::from_excel(row, col, true, true),
378            );
379            self.engine.edit_with_logger(&mut self.log, |editor| {
380                editor.set_cell_value(cell, value.clone());
381            });
382            self.mirror_value_to_overlay(sheet, row, col, &value);
383            self.engine.mark_data_edited();
384            Ok(())
385        } else {
386            self.engine
387                .set_cell_value(sheet, row, col, value)
388                .map_err(IoError::Engine)
389        }
390    }
391
392    pub fn set_formula(
393        &mut self,
394        sheet: &str,
395        row: u32,
396        col: u32,
397        formula: &str,
398    ) -> Result<(), IoError> {
399        self.ensure_arrow_sheet_capacity(sheet, row as usize, col as usize);
400        if self.engine.config.defer_graph_building {
401            if self.engine.get_cell(sheet, row, col).is_some() {
402                let with_eq = if formula.starts_with('=') {
403                    formula.to_string()
404                } else {
405                    format!("={formula}")
406                };
407                let ast = formualizer_parse::parser::parse(&with_eq)
408                    .map_err(|e| IoError::from_backend("parser", e))?;
409                if self.enable_changelog {
410                    let sheet_id = self
411                        .engine
412                        .sheet_id(sheet)
413                        .unwrap_or_else(|| self.engine.add_sheet(sheet).expect("add sheet"));
414                    let cell = formualizer_eval::reference::CellRef::new(
415                        sheet_id,
416                        formualizer_eval::reference::Coord::from_excel(row, col, true, true),
417                    );
418                    self.engine.edit_with_logger(&mut self.log, |editor| {
419                        editor.set_cell_formula(cell, ast);
420                    });
421                    self.engine.mark_data_edited();
422                    Ok(())
423                } else {
424                    self.engine
425                        .set_cell_formula(sheet, row, col, ast)
426                        .map_err(IoError::Engine)
427                }
428            } else {
429                self.engine
430                    .stage_formula_text(sheet, row, col, formula.to_string());
431                Ok(())
432            }
433        } else {
434            let with_eq = if formula.starts_with('=') {
435                formula.to_string()
436            } else {
437                format!("={formula}")
438            };
439            let ast = formualizer_parse::parser::parse(&with_eq)
440                .map_err(|e| IoError::from_backend("parser", e))?;
441            if self.enable_changelog {
442                let sheet_id = self
443                    .engine
444                    .sheet_id(sheet)
445                    .unwrap_or_else(|| self.engine.add_sheet(sheet).expect("add sheet"));
446                let cell = formualizer_eval::reference::CellRef::new(
447                    sheet_id,
448                    formualizer_eval::reference::Coord::from_excel(row, col, true, true),
449                );
450                self.engine.edit_with_logger(&mut self.log, |editor| {
451                    editor.set_cell_formula(cell, ast);
452                });
453                self.engine.mark_data_edited();
454                Ok(())
455            } else {
456                self.engine
457                    .set_cell_formula(sheet, row, col, ast)
458                    .map_err(IoError::Engine)
459            }
460        }
461    }
462
463    pub fn get_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
464        self.engine.get_cell_value(sheet, row, col)
465    }
466    pub fn get_formula(&self, sheet: &str, row: u32, col: u32) -> Option<String> {
467        if let Some(s) = self.engine.get_staged_formula_text(sheet, row, col) {
468            return Some(s);
469        }
470        self.engine
471            .get_cell(sheet, row, col)
472            .and_then(|(ast, _)| ast.map(|a| formualizer_parse::pretty::canonical_formula(&a)))
473    }
474
475    // Ranges
476    pub fn read_range(&self, addr: &RangeAddress) -> Vec<Vec<LiteralValue>> {
477        let mut out = Vec::with_capacity(addr.height() as usize);
478        if let Some(asheet) = self.engine.sheet_store().sheet(&addr.sheet) {
479            let sr0 = addr.start_row.saturating_sub(1) as usize;
480            let sc0 = addr.start_col.saturating_sub(1) as usize;
481            let er0 = addr.end_row.saturating_sub(1) as usize;
482            let ec0 = addr.end_col.saturating_sub(1) as usize;
483            let view = asheet.range_view(sr0, sc0, er0, ec0);
484            let (h, w) = view.dims();
485            for rr in 0..h {
486                let mut row = Vec::with_capacity(w);
487                for cc in 0..w {
488                    row.push(view.get_cell(rr, cc));
489                }
490                out.push(row);
491            }
492        } else {
493            // Fallback: materialize via graph stored values
494            for r in addr.start_row..=addr.end_row {
495                let mut row = Vec::with_capacity(addr.width() as usize);
496                for c in addr.start_col..=addr.end_col {
497                    row.push(
498                        self.engine
499                            .get_cell_value(&addr.sheet, r, c)
500                            .unwrap_or(LiteralValue::Empty),
501                    );
502                }
503                out.push(row);
504            }
505        }
506        out
507    }
508    pub fn write_range(
509        &mut self,
510        sheet: &str,
511        _start: (u32, u32),
512        cells: BTreeMap<(u32, u32), crate::traits::CellData>,
513    ) -> Result<(), IoError> {
514        if self.enable_changelog {
515            let sheet_id = self
516                .engine
517                .sheet_id(sheet)
518                .unwrap_or_else(|| self.engine.add_sheet(sheet).expect("add sheet"));
519            let defer_graph_building = self.engine.config.defer_graph_building;
520
521            let mut overlay_ops: Vec<(u32, u32, LiteralValue)> = Vec::new();
522            let mut staged_forms: Vec<(u32, u32, String)> = Vec::new();
523
524            self.engine
525                .edit_with_logger(&mut self.log, |editor| -> Result<(), IoError> {
526                    for ((r, c), d) in cells.into_iter() {
527                        let cell = formualizer_eval::reference::CellRef::new(
528                            sheet_id,
529                            formualizer_eval::reference::Coord::from_excel(r, c, true, true),
530                        );
531                        if let Some(v) = d.value.clone() {
532                            editor.set_cell_value(cell, v.clone());
533                            overlay_ops.push((r, c, v));
534                        }
535                        if let Some(f) = d.formula.as_ref() {
536                            if defer_graph_building {
537                                staged_forms.push((r, c, f.clone()));
538                            } else {
539                                let with_eq = if f.starts_with('=') {
540                                    f.clone()
541                                } else {
542                                    format!("={f}")
543                                };
544                                let ast = formualizer_parse::parser::parse(&with_eq)
545                                    .map_err(|e| IoError::from_backend("parser", e))?;
546                                editor.set_cell_formula(cell, ast);
547                            }
548                        }
549                    }
550                    Ok(())
551                })?;
552
553            for (r, c, v) in overlay_ops {
554                self.mirror_value_to_overlay(sheet, r, c, &v);
555            }
556            for (r, c, f) in staged_forms {
557                self.engine.stage_formula_text(sheet, r, c, f);
558            }
559            self.engine.mark_data_edited();
560            Ok(())
561        } else {
562            for ((r, c), d) in cells.into_iter() {
563                if let Some(v) = d.value.clone() {
564                    self.engine
565                        .set_cell_value(sheet, r, c, v)
566                        .map_err(IoError::Engine)?;
567                }
568                if let Some(f) = d.formula.as_ref() {
569                    if self.engine.config.defer_graph_building {
570                        self.engine.stage_formula_text(sheet, r, c, f.clone());
571                    } else {
572                        let with_eq = if f.starts_with('=') {
573                            f.clone()
574                        } else {
575                            format!("={f}")
576                        };
577                        let ast = formualizer_parse::parser::parse(&with_eq)
578                            .map_err(|e| IoError::from_backend("parser", e))?;
579                        self.engine
580                            .set_cell_formula(sheet, r, c, ast)
581                            .map_err(IoError::Engine)?;
582                    }
583                }
584            }
585            Ok(())
586        }
587    }
588
589    // Batch set values in a rectangle starting at (start_row,start_col)
590    pub fn set_values(
591        &mut self,
592        sheet: &str,
593        start_row: u32,
594        start_col: u32,
595        rows: &[Vec<LiteralValue>],
596    ) -> Result<(), IoError> {
597        if self.enable_changelog {
598            let sheet_id = self
599                .engine
600                .sheet_id(sheet)
601                .unwrap_or_else(|| self.engine.add_sheet(sheet).expect("add sheet"));
602            let mut overlay_ops: Vec<(u32, u32, LiteralValue)> = Vec::new();
603
604            self.engine.edit_with_logger(&mut self.log, |editor| {
605                for (ri, rvals) in rows.iter().enumerate() {
606                    let r = start_row + ri as u32;
607                    for (ci, v) in rvals.iter().enumerate() {
608                        let c = start_col + ci as u32;
609                        let cell = formualizer_eval::reference::CellRef::new(
610                            sheet_id,
611                            formualizer_eval::reference::Coord::from_excel(r, c, true, true),
612                        );
613                        editor.set_cell_value(cell, v.clone());
614                        overlay_ops.push((r, c, v.clone()));
615                    }
616                }
617            });
618
619            for (r, c, v) in overlay_ops {
620                self.mirror_value_to_overlay(sheet, r, c, &v);
621            }
622            self.engine.mark_data_edited();
623            Ok(())
624        } else {
625            for (ri, rvals) in rows.iter().enumerate() {
626                let r = start_row + ri as u32;
627                for (ci, v) in rvals.iter().enumerate() {
628                    let c = start_col + ci as u32;
629                    self.engine
630                        .set_cell_value(sheet, r, c, v.clone())
631                        .map_err(IoError::Engine)?;
632                }
633            }
634            Ok(())
635        }
636    }
637
638    // Batch set formulas in a rectangle starting at (start_row,start_col)
639    pub fn set_formulas(
640        &mut self,
641        sheet: &str,
642        start_row: u32,
643        start_col: u32,
644        rows: &[Vec<String>],
645    ) -> Result<(), IoError> {
646        let height = rows.len();
647        let width = rows.iter().map(|r| r.len()).max().unwrap_or(0);
648        if height == 0 || width == 0 {
649            return Ok(());
650        }
651        let end_row = start_row.saturating_add((height - 1) as u32);
652        let end_col = start_col.saturating_add((width - 1) as u32);
653        self.ensure_arrow_sheet_capacity(sheet, end_row as usize, end_col as usize);
654
655        if self.engine.config.defer_graph_building {
656            for (ri, rforms) in rows.iter().enumerate() {
657                let r = start_row + ri as u32;
658                for (ci, f) in rforms.iter().enumerate() {
659                    let c = start_col + ci as u32;
660                    self.engine.stage_formula_text(sheet, r, c, f.clone());
661                }
662            }
663            Ok(())
664        } else if self.enable_changelog {
665            let sheet_id = self
666                .engine
667                .sheet_id(sheet)
668                .unwrap_or_else(|| self.engine.add_sheet(sheet).expect("add sheet"));
669
670            self.engine
671                .edit_with_logger(&mut self.log, |editor| -> Result<(), IoError> {
672                    for (ri, rforms) in rows.iter().enumerate() {
673                        let r = start_row + ri as u32;
674                        for (ci, f) in rforms.iter().enumerate() {
675                            let c = start_col + ci as u32;
676                            let cell = formualizer_eval::reference::CellRef::new(
677                                sheet_id,
678                                formualizer_eval::reference::Coord::from_excel(r, c, true, true),
679                            );
680                            let with_eq = if f.starts_with('=') {
681                                f.clone()
682                            } else {
683                                format!("={f}")
684                            };
685                            let ast = formualizer_parse::parser::parse(&with_eq)
686                                .map_err(|e| IoError::from_backend("parser", e))?;
687                            editor.set_cell_formula(cell, ast);
688                        }
689                    }
690                    Ok(())
691                })?;
692
693            self.engine.mark_data_edited();
694            Ok(())
695        } else {
696            for (ri, rforms) in rows.iter().enumerate() {
697                let r = start_row + ri as u32;
698                for (ci, f) in rforms.iter().enumerate() {
699                    let c = start_col + ci as u32;
700                    let with_eq = if f.starts_with('=') {
701                        f.clone()
702                    } else {
703                        format!("={f}")
704                    };
705                    let ast = formualizer_parse::parser::parse(&with_eq)
706                        .map_err(|e| IoError::from_backend("parser", e))?;
707                    self.engine
708                        .set_cell_formula(sheet, r, c, ast)
709                        .map_err(IoError::Engine)?;
710                }
711            }
712            Ok(())
713        }
714    }
715
716    // Evaluation
717    pub fn prepare_graph_all(&mut self) -> Result<(), IoError> {
718        self.engine
719            .build_graph_all()
720            .map_err(|e| IoError::from_backend("parser", e))
721    }
722    pub fn prepare_graph_for_sheets<'a, I: IntoIterator<Item = &'a str>>(
723        &mut self,
724        sheets: I,
725    ) -> Result<(), IoError> {
726        self.engine
727            .build_graph_for_sheets(sheets)
728            .map_err(|e| IoError::from_backend("parser", e))
729    }
730    pub fn evaluate_cell(
731        &mut self,
732        sheet: &str,
733        row: u32,
734        col: u32,
735    ) -> Result<LiteralValue, IoError> {
736        self.engine
737            .evaluate_cell(sheet, row, col)
738            .map_err(IoError::Engine)
739            .map(|value| value.unwrap_or(LiteralValue::Empty))
740    }
741    pub fn evaluate_cells(
742        &mut self,
743        targets: &[(&str, u32, u32)],
744    ) -> Result<Vec<LiteralValue>, IoError> {
745        self.engine
746            .evaluate_cells(targets)
747            .map_err(IoError::Engine)
748            .map(|values| {
749                values
750                    .into_iter()
751                    .map(|v| v.unwrap_or(LiteralValue::Empty))
752                    .collect()
753            })
754    }
755
756    pub fn evaluate_cells_cancellable(
757        &mut self,
758        targets: &[(&str, u32, u32)],
759        cancel_flag: std::sync::Arc<std::sync::atomic::AtomicBool>,
760    ) -> Result<Vec<LiteralValue>, IoError> {
761        self.engine
762            .evaluate_cells_cancellable(targets, cancel_flag)
763            .map_err(IoError::Engine)
764            .map(|values| {
765                values
766                    .into_iter()
767                    .map(|v| v.unwrap_or(LiteralValue::Empty))
768                    .collect()
769            })
770    }
771    pub fn evaluate_all(&mut self) -> Result<formualizer_eval::engine::EvalResult, IoError> {
772        self.engine.evaluate_all().map_err(IoError::Engine)
773    }
774
775    pub fn evaluate_all_cancellable(
776        &mut self,
777        cancel_flag: std::sync::Arc<std::sync::atomic::AtomicBool>,
778    ) -> Result<formualizer_eval::engine::EvalResult, IoError> {
779        self.engine
780            .evaluate_all_cancellable(cancel_flag)
781            .map_err(IoError::Engine)
782    }
783
784    pub fn evaluate_with_plan(
785        &mut self,
786        plan: &formualizer_eval::engine::RecalcPlan,
787    ) -> Result<formualizer_eval::engine::EvalResult, IoError> {
788        self.engine
789            .evaluate_recalc_plan(plan)
790            .map_err(IoError::Engine)
791    }
792
793    pub fn get_eval_plan(&self, targets: &[(&str, u32, u32)]) -> Result<EvalPlan, IoError> {
794        self.engine.get_eval_plan(targets).map_err(IoError::Engine)
795    }
796
797    // Named ranges
798    pub fn define_named_range(
799        &mut self,
800        name: &str,
801        address: &RangeAddress,
802        scope: crate::traits::NamedRangeScope,
803    ) -> Result<(), IoError> {
804        let (definition, scope) = self.named_definition_with_scope(address, scope)?;
805        if self.enable_changelog {
806            let result = self.engine.edit_with_logger(&mut self.log, |editor| {
807                editor.define_name(name, definition, scope)
808            });
809            result.map_err(|e| IoError::from_backend("editor", e))
810        } else {
811            self.engine
812                .define_name(name, definition, scope)
813                .map_err(IoError::Engine)
814        }
815    }
816
817    pub fn update_named_range(
818        &mut self,
819        name: &str,
820        address: &RangeAddress,
821        scope: crate::traits::NamedRangeScope,
822    ) -> Result<(), IoError> {
823        let (definition, scope) = self.named_definition_with_scope(address, scope)?;
824        if self.enable_changelog {
825            let result = self.engine.edit_with_logger(&mut self.log, |editor| {
826                editor.update_name(name, definition, scope)
827            });
828            result.map_err(|e| IoError::from_backend("editor", e))
829        } else {
830            self.engine
831                .update_name(name, definition, scope)
832                .map_err(IoError::Engine)
833        }
834    }
835
836    pub fn delete_named_range(
837        &mut self,
838        name: &str,
839        scope: crate::traits::NamedRangeScope,
840        sheet: Option<&str>,
841    ) -> Result<(), IoError> {
842        let scope = self.name_scope_from_hint(scope, sheet)?;
843        if self.enable_changelog {
844            let result = self
845                .engine
846                .edit_with_logger(&mut self.log, |editor| editor.delete_name(name, scope));
847            result.map_err(|e| IoError::from_backend("editor", e))
848        } else {
849            self.engine
850                .delete_name(name, scope)
851                .map_err(IoError::Engine)
852        }
853    }
854
855    /// Resolve a named range (workbook-scoped or unique sheet-scoped) to an absolute address.
856    pub fn named_range_address(&self, name: &str) -> Option<RangeAddress> {
857        if let Some((_, named)) = self
858            .engine
859            .named_ranges_iter()
860            .find(|(n, _)| n.as_str() == name)
861        {
862            return self.named_definition_to_address(&named.definition);
863        }
864
865        let mut resolved: Option<RangeAddress> = None;
866        for ((_sheet_id, candidate), named) in self.engine.sheet_named_ranges_iter() {
867            if candidate == name
868                && let Some(address) = self.named_definition_to_address(&named.definition)
869            {
870                if resolved.is_some() {
871                    return None; // ambiguous sheet-scoped name
872                }
873                resolved = Some(address);
874            }
875        }
876        resolved
877    }
878
879    fn named_definition_with_scope(
880        &mut self,
881        address: &RangeAddress,
882        scope: crate::traits::NamedRangeScope,
883    ) -> Result<(NamedDefinition, NameScope), IoError> {
884        let sheet_id = self.ensure_sheet_for_address(address)?;
885        let scope = match scope {
886            crate::traits::NamedRangeScope::Workbook => NameScope::Workbook,
887            crate::traits::NamedRangeScope::Sheet => NameScope::Sheet(sheet_id),
888        };
889        let sr0 = address.start_row.saturating_sub(1);
890        let sc0 = address.start_col.saturating_sub(1);
891        let er0 = address.end_row.saturating_sub(1);
892        let ec0 = address.end_col.saturating_sub(1);
893        let start_ref = formualizer_eval::reference::CellRef::new(
894            sheet_id,
895            formualizer_eval::reference::Coord::new(sr0, sc0, true, true),
896        );
897        if sr0 == er0 && sc0 == ec0 {
898            Ok((NamedDefinition::Cell(start_ref), scope))
899        } else {
900            let end_ref = formualizer_eval::reference::CellRef::new(
901                sheet_id,
902                formualizer_eval::reference::Coord::new(er0, ec0, true, true),
903            );
904            let range_ref = formualizer_eval::reference::RangeRef::new(start_ref, end_ref);
905            Ok((NamedDefinition::Range(range_ref), scope))
906        }
907    }
908
909    fn name_scope_from_hint(
910        &mut self,
911        scope: crate::traits::NamedRangeScope,
912        sheet: Option<&str>,
913    ) -> Result<NameScope, IoError> {
914        match scope {
915            crate::traits::NamedRangeScope::Workbook => Ok(NameScope::Workbook),
916            crate::traits::NamedRangeScope::Sheet => {
917                let sheet = sheet.ok_or_else(|| IoError::Backend {
918                    backend: "workbook".to_string(),
919                    message: "Sheet scope requires a sheet name".to_string(),
920                })?;
921                let sheet_id = self
922                    .engine
923                    .sheet_id(sheet)
924                    .ok_or_else(|| IoError::Backend {
925                        backend: "workbook".to_string(),
926                        message: "Sheet not found".to_string(),
927                    })?;
928                Ok(NameScope::Sheet(sheet_id))
929            }
930        }
931    }
932
933    fn ensure_sheet_for_address(
934        &mut self,
935        address: &RangeAddress,
936    ) -> Result<formualizer_eval::SheetId, IoError> {
937        let sheet_id = self
938            .engine
939            .sheet_id(&address.sheet)
940            .or_else(|| self.engine.add_sheet(&address.sheet).ok())
941            .ok_or_else(|| IoError::Backend {
942                backend: "workbook".to_string(),
943                message: "Sheet not found".to_string(),
944            })?;
945        self.ensure_arrow_sheet_capacity(
946            &address.sheet,
947            address.end_row as usize,
948            address.end_col as usize,
949        );
950        Ok(sheet_id)
951    }
952
953    fn named_definition_to_address(&self, definition: &NamedDefinition) -> Option<RangeAddress> {
954        match definition {
955            NamedDefinition::Cell(cell) => {
956                let sheet = self.engine.sheet_name(cell.sheet_id).to_string();
957                let row = cell.coord.row() + 1;
958                let col = cell.coord.col() + 1;
959                RangeAddress::new(sheet, row, col, row, col).ok()
960            }
961            NamedDefinition::Range(range) => {
962                if range.start.sheet_id != range.end.sheet_id {
963                    return None;
964                }
965                let sheet = self.engine.sheet_name(range.start.sheet_id).to_string();
966                let start_row = range.start.coord.row() + 1;
967                let start_col = range.start.coord.col() + 1;
968                let end_row = range.end.coord.row() + 1;
969                let end_col = range.end.coord.col() + 1;
970                RangeAddress::new(sheet, start_row, start_col, end_row, end_col).ok()
971            }
972            NamedDefinition::Formula { .. } => {
973                #[cfg(feature = "tracing")]
974                tracing::debug!("formula-backed named ranges are not yet supported");
975                None
976            }
977        }
978    }
979
980    // Persistence/transactions via SpreadsheetWriter (self implements writer)
981    pub fn begin_tx<'a, W: SpreadsheetWriter>(
982        &'a mut self,
983        writer: &'a mut W,
984    ) -> crate::transaction::WriteTransaction<'a, W> {
985        crate::transaction::WriteTransaction::new(writer)
986    }
987
988    // Loading via streaming ingest (Arrow base + graph formulas)
989    pub fn from_reader<B>(
990        mut backend: B,
991        _strategy: LoadStrategy,
992        config: WorkbookConfig,
993    ) -> Result<Self, IoError>
994    where
995        B: SpreadsheetReader + formualizer_eval::engine::ingest::EngineLoadStream<WBResolver>,
996        IoError: From<<B as formualizer_eval::engine::ingest::EngineLoadStream<WBResolver>>::Error>,
997    {
998        let mut wb = Self::new_with_config(config);
999        backend
1000            .stream_into_engine(&mut wb.engine)
1001            .map_err(IoError::from)?;
1002        Ok(wb)
1003    }
1004
1005    pub fn from_reader_with_config<B>(
1006        backend: B,
1007        strategy: LoadStrategy,
1008        config: WorkbookConfig,
1009    ) -> Result<Self, IoError>
1010    where
1011        B: SpreadsheetReader + formualizer_eval::engine::ingest::EngineLoadStream<WBResolver>,
1012        IoError: From<<B as formualizer_eval::engine::ingest::EngineLoadStream<WBResolver>>::Error>,
1013    {
1014        Self::from_reader(backend, strategy, config)
1015    }
1016
1017    pub fn from_reader_with_mode<B>(
1018        backend: B,
1019        strategy: LoadStrategy,
1020        mode: WorkbookMode,
1021    ) -> Result<Self, IoError>
1022    where
1023        B: SpreadsheetReader + formualizer_eval::engine::ingest::EngineLoadStream<WBResolver>,
1024        IoError: From<<B as formualizer_eval::engine::ingest::EngineLoadStream<WBResolver>>::Error>,
1025    {
1026        let config = match mode {
1027            WorkbookMode::Ephemeral => WorkbookConfig::ephemeral(),
1028            WorkbookMode::Interactive => WorkbookConfig::interactive(),
1029        };
1030        Self::from_reader(backend, strategy, config)
1031    }
1032}
1033
1034// Implement SpreadsheetWriter so external transactions can target Workbook
1035impl SpreadsheetWriter for Workbook {
1036    type Error = IoError;
1037
1038    fn write_cell(
1039        &mut self,
1040        sheet: &str,
1041        row: u32,
1042        col: u32,
1043        data: crate::traits::CellData,
1044    ) -> Result<(), Self::Error> {
1045        if let Some(v) = data.value {
1046            self.set_value(sheet, row, col, v)?;
1047        }
1048        if let Some(f) = data.formula {
1049            self.set_formula(sheet, row, col, &f)?;
1050        }
1051        Ok(())
1052    }
1053    fn write_range(
1054        &mut self,
1055        sheet: &str,
1056        cells: BTreeMap<(u32, u32), crate::traits::CellData>,
1057    ) -> Result<(), Self::Error> {
1058        for ((r, c), d) in cells {
1059            self.write_cell(sheet, r, c, d)?;
1060        }
1061        Ok(())
1062    }
1063    fn clear_range(
1064        &mut self,
1065        sheet: &str,
1066        start: (u32, u32),
1067        end: (u32, u32),
1068    ) -> Result<(), Self::Error> {
1069        for r in start.0..=end.0 {
1070            for c in start.1..=end.1 {
1071                self.set_value(sheet, r, c, LiteralValue::Empty)?;
1072            }
1073        }
1074        Ok(())
1075    }
1076    fn create_sheet(&mut self, name: &str) -> Result<(), Self::Error> {
1077        self.add_sheet(name).map_err(IoError::Engine)
1078    }
1079    fn delete_sheet(&mut self, name: &str) -> Result<(), Self::Error> {
1080        self.delete_sheet(name).map_err(IoError::Engine)
1081    }
1082    fn rename_sheet(&mut self, old: &str, new: &str) -> Result<(), Self::Error> {
1083        self.rename_sheet(old, new).map_err(IoError::Engine)
1084    }
1085    fn flush(&mut self) -> Result<(), Self::Error> {
1086        Ok(())
1087    }
1088}