Skip to main content

ironcalc_base/
model.rs

1#![deny(missing_docs)]
2
3use std::collections::HashMap;
4use std::vec::Vec;
5
6use crate::expressions::parser::static_analysis::run_static_analysis_on_node;
7use crate::{
8    calc_result::{CalcResult, Range},
9    cell::CellValue,
10    constants::{self, LAST_COLUMN, LAST_ROW},
11    expressions::{
12        lexer::LexerMode,
13        parser::{
14            move_formula::{move_formula, MoveContext},
15            new_parser_english,
16            static_analysis::StaticResult,
17            stringify::{
18                rename_defined_name_in_node, to_english_string, to_localized_string, to_rc_format,
19            },
20            ArrayNode, CompletionContext, NamedVariable, Node, Parser,
21        },
22        token::{get_error_by_name, Error, OpProduct, OpSum, OpUnary},
23        types::*,
24        utils::{self, is_valid_column_number, is_valid_identifier, is_valid_row},
25    },
26    formatter::{
27        format::{format_number, parse_formatted_number},
28        lexer::is_likely_date_number_format,
29    },
30    implicit_intersection::implicit_intersection,
31    language::{get_default_language, get_language, Language},
32    locale::{get_default_locale, get_locale, Locale},
33    types::*,
34    utils as common,
35};
36
37use crate::{cf_types::CfCellResult, tz::Tz};
38
39#[cfg(any(test, feature = "mock_time"))]
40pub use crate::mock_time::get_milliseconds_since_epoch;
41
42/// Number of milliseconds since January 1, 1970
43/// Used by time and date functions. It takes the value from the environment:
44/// * The Operative System
45/// * The JavaScript environment
46/// * Or mocked for tests
47#[cfg(not(any(test, feature = "mock_time")))]
48#[cfg(not(target_arch = "wasm32"))]
49#[allow(clippy::expect_used)]
50pub fn get_milliseconds_since_epoch() -> i64 {
51    use std::time::{SystemTime, UNIX_EPOCH};
52    SystemTime::now()
53        .duration_since(UNIX_EPOCH)
54        .expect("problem with system time")
55        .as_millis() as i64
56}
57
58/// Number of milliseconds since January 1, 1970
59/// Used by time and date functions. It takes the value from the environment:
60/// * The Operative System
61/// * The JavaScript environment
62/// * Or mocked for tests
63#[cfg(not(any(test, feature = "mock_time")))]
64#[cfg(target_arch = "wasm32")]
65pub fn get_milliseconds_since_epoch() -> i64 {
66    use js_sys::Date;
67    Date::now() as i64
68}
69
70// The structure of a cell.
71// It can be:
72// * A single cell
73// * The anchor of an array formula
74// * The anchor of a dynamic formula
75// * A part of an array formula spill
76// * A part of a dynamic formula spill
77pub(crate) enum CellStructure {
78    SingleCell,
79    ArrayFormula {
80        range: (i32, i32),
81    },
82    DynamicFormula {
83        range: (i32, i32),
84    },
85    SpillArray {
86        anchor: (i32, i32),
87        range: (i32, i32),
88    },
89    SpillDynamic {
90        anchor: (i32, i32),
91        range: (i32, i32),
92    },
93}
94
95/// A cell might be evaluated or being evaluated
96#[derive(Clone)]
97pub(crate) enum CellState {
98    /// The cell has already been evaluated
99    Evaluated,
100    /// The cell is being evaluated
101    Evaluating,
102}
103
104/// A parsed formula for a defined name
105#[derive(Clone)]
106pub(crate) enum ParsedDefinedName {
107    /// CellReference (`=C4`)
108    CellReference(CellReferenceIndex),
109    /// A Range (`=C4:D6`)
110    RangeReference(Range),
111    /// `=LAMBDA(params..., body)`
112    LambdaDefinition(Vec<NamedVariable>, Node),
113    /// `=SomethingElse`
114    InvalidDefinedNameFormula,
115}
116
117/// Formatting settings for a locale
118pub struct FmtSettings {
119    /// Currency format
120    pub currency: String,
121    /// Currency format with symbol
122    pub currency_format: String,
123    /// Short date format
124    pub short_date: String,
125    /// Example of short date format
126    pub short_date_example: String,
127    /// Long date format
128    pub long_date: String,
129    /// Example of long date format
130    pub long_date_example: String,
131    /// Number format
132    pub number_fmt: String,
133    /// Example of number format
134    pub number_example: String,
135}
136
137fn array_node_to_formula_value(node: ArrayNode) -> FormulaValue {
138    match node {
139        ArrayNode::Boolean(b) => FormulaValue::Boolean(b),
140        ArrayNode::Number(n) => FormulaValue::Number(n),
141        ArrayNode::String(s) => FormulaValue::Text(s),
142        ArrayNode::Error(ei) => FormulaValue::Error {
143            ei,
144            o: String::new(),
145            m: String::new(),
146        },
147        ArrayNode::Empty => FormulaValue::Number(0.0),
148    }
149}
150
151fn array_node_to_spill_value(node: ArrayNode) -> SpillValue {
152    match node {
153        ArrayNode::Boolean(b) => SpillValue::Boolean(b),
154        ArrayNode::Number(n) => SpillValue::Number(n),
155        ArrayNode::String(s) => SpillValue::Text(s),
156        ArrayNode::Error(ei) => SpillValue::Error(ei),
157        ArrayNode::Empty => SpillValue::Number(0.0),
158    }
159}
160
161fn formula_value_to_spill_value(v: &FormulaValue) -> SpillValue {
162    match v {
163        FormulaValue::Unevaluated => SpillValue::Error(Error::ERROR),
164        FormulaValue::Boolean(b) => SpillValue::Boolean(*b),
165        FormulaValue::Number(n) => SpillValue::Number(*n),
166        FormulaValue::Text(s) => SpillValue::Text(s.clone()),
167        FormulaValue::Error { ei, .. } => SpillValue::Error(ei.clone()),
168    }
169}
170
171pub(crate) enum CellOrRange {
172    // (sheet, row, column)
173    Cell((u32, i32, i32)),
174    // (sheet, start_row, start_column, end_row, end_column)
175    Range((u32, i32, i32, i32, i32)),
176}
177
178/// A dynamical IronCalc model.
179///
180/// Its is composed of a `Workbook`. Everything else are dynamical quantities:
181///
182/// * The Locale: a parsed version of the Workbook's locale
183/// * The Timezone: an object representing the Workbook's timezone
184/// * The language. Note that the timezone and the locale belong to the workbook while
185///   the language can be different for different users looking _at the same_ workbook.
186/// * Parsed Formulas: All the formulas in the workbook are parsed here (runtime only)
187/// * A list of cells with its status (evaluating, evaluated, not evaluated)
188/// * A dictionary with the shared strings and their indices.
189///   This is an optimization for large files (~1 million rows)
190pub struct Model<'a> {
191    /// A Rust internal representation of an Excel workbook
192    pub workbook: Workbook,
193    /// A list of parsed formulas
194    pub parsed_formulas: Vec<Vec<(Node, StaticResult)>>,
195    /// A list of parsed defined names
196    pub(crate) parsed_defined_names: HashMap<(Option<u32>, String), ParsedDefinedName>,
197    /// An optimization to lookup strings faster
198    pub(crate) shared_strings: HashMap<String, usize>,
199    /// An instance of the parser
200    pub(crate) parser: Parser<'a>,
201    /// The list of cells with formulas that are evaluated or being evaluated
202    pub(crate) cells: HashMap<(u32, i32, i32), CellState>,
203    /// The locale of the model
204    pub(crate) locale: &'a Locale,
205    /// The language used
206    pub(crate) language: &'a Language,
207    /// The timezone used to evaluate the model
208    pub(crate) tz: Tz,
209    /// The view id. A view consists of a selected sheet and ranges.
210    pub(crate) view_id: u32,
211    /// A stack of variables used for LET function evaluation. The key is the variable id, and the value is the variable value.
212    pub(crate) variable_stack: HashMap<usize, CalcResult>,
213    /// Last variable id used. It is incremented every time a new variable is created (for example, when evaluating a LET function).
214    pub(crate) last_variable_id: usize,
215    /// Lambdas
216    pub(crate) lambdas: HashMap<usize, (Vec<NamedVariable>, Node)>,
217    /// Last lambda id used. It is incremented every time a new lambda is created.
218    pub(crate) last_lambda_id: usize,
219    /// The list of cells that might spill
220    pub(crate) spill_cells: Vec<CellReferenceIndex>,
221    /// A dictionary to keep track of which cells or ranges support a given cell.
222    pub(crate) support: HashMap<CellReferenceIndex, Vec<CellOrRange>>,
223    /// Evaluated CF results per cell, keyed by (sheet_index, row, column).
224    /// Rebuilt from scratch on every call to evaluate_conditional_formatting().
225    pub(crate) cf_cache: HashMap<(u32, i32, i32), Vec<CfCellResult>>,
226    /// Dynamic links: links created by formulas like HYPERLINK
227    pub(crate) links: HashMap<(u32, i32, i32), Link>,
228}
229
230// FIXME: Maybe this should be the same as CellReference
231/// A struct pointing to a cell
232pub struct CellIndex {
233    /// Sheet index (0-indexed)
234    pub index: u32,
235    /// Row index
236    pub row: i32,
237    /// Column index
238    pub column: i32,
239}
240
241impl<'a> Model<'a> {
242    pub(crate) fn get_next_variable_id(&mut self) -> usize {
243        let id = self.last_variable_id;
244        self.last_variable_id += 1;
245        id
246    }
247    fn clear_variable_stack(&mut self) {
248        self.variable_stack.clear();
249        self.last_variable_id = 0;
250    }
251    pub(crate) fn get_next_lambda_id(&mut self) -> usize {
252        let id = self.last_lambda_id;
253        self.last_lambda_id += 1;
254        id
255    }
256    fn clear_lambdas(&mut self) {
257        self.lambdas.clear();
258        self.last_lambda_id = 0;
259    }
260    pub(crate) fn evaluate_node_with_reference(
261        &mut self,
262        node: &Node,
263        cell: CellReferenceIndex,
264    ) -> CalcResult {
265        match node {
266            Node::ReferenceKind {
267                sheet_name: _,
268                sheet_index,
269                absolute_row,
270                absolute_column,
271                row,
272                column,
273            } => {
274                let mut row1 = *row;
275                let mut column1 = *column;
276                if !absolute_row {
277                    row1 += cell.row;
278                }
279                if !absolute_column {
280                    column1 += cell.column;
281                }
282                CalcResult::Range {
283                    left: CellReferenceIndex {
284                        sheet: *sheet_index,
285                        row: row1,
286                        column: column1,
287                    },
288                    right: CellReferenceIndex {
289                        sheet: *sheet_index,
290                        row: row1,
291                        column: column1,
292                    },
293                }
294            }
295            Node::RangeKind {
296                sheet_name: _,
297                sheet_index,
298                absolute_row1,
299                absolute_column1,
300                row1,
301                column1,
302                absolute_row2,
303                absolute_column2,
304                row2,
305                column2,
306            } => {
307                let mut row_left = *row1;
308                let mut column_left = *column1;
309                if !absolute_row1 {
310                    row_left += cell.row;
311                }
312                if !absolute_column1 {
313                    column_left += cell.column;
314                }
315                let mut row_right = *row2;
316                let mut column_right = *column2;
317                if !absolute_row2 {
318                    row_right += cell.row;
319                }
320                if !absolute_column2 {
321                    column_right += cell.column;
322                }
323                // FIXME: HACK. The parser is currently parsing Sheet3!A1:A10 as Sheet3!A1:(present sheet)!A10
324                CalcResult::Range {
325                    left: CellReferenceIndex {
326                        sheet: *sheet_index,
327                        row: row_left,
328                        column: column_left,
329                    },
330                    right: CellReferenceIndex {
331                        sheet: *sheet_index,
332                        row: row_right,
333                        column: column_right,
334                    },
335                }
336            }
337            Node::ImplicitIntersection {
338                automatic: _,
339                child,
340            } => match self.evaluate_node_with_reference(child, cell) {
341                CalcResult::Range { left, right } => CalcResult::Range { left, right },
342                _ => CalcResult::new_error(
343                    Error::ERROR,
344                    cell,
345                    format!("Error with Implicit Intersection in cell {cell:?}"),
346                ),
347            },
348            _ => self.evaluate_node_in_context(node, cell),
349        }
350    }
351
352    fn get_range(&mut self, left: &Node, right: &Node, cell: CellReferenceIndex) -> CalcResult {
353        let left_result = self.evaluate_node_with_reference(left, cell);
354        let right_result = self.evaluate_node_with_reference(right, cell);
355        match (left_result, right_result) {
356            (
357                CalcResult::Range {
358                    left: left1,
359                    right: right1,
360                },
361                CalcResult::Range {
362                    left: left2,
363                    right: right2,
364                },
365            ) => {
366                if left1.row == right1.row
367                    && left1.column == right1.column
368                    && left2.row == right2.row
369                    && left2.column == right2.column
370                {
371                    return CalcResult::Range {
372                        left: left1,
373                        right: right2,
374                    };
375                }
376                CalcResult::Error {
377                    error: Error::VALUE,
378                    origin: cell,
379                    message: "Invalid range".to_string(),
380                }
381            }
382            _ => CalcResult::Error {
383                error: Error::VALUE,
384                origin: cell,
385                message: "Invalid range".to_string(),
386            },
387        }
388    }
389
390    pub(crate) fn formula_without_prefix<'b>(&self, value: &'b str) -> Option<&'b str> {
391        if let Some(stripped) = value.strip_prefix('=') {
392            if stripped.is_empty() {
393                None
394            } else {
395                Some(stripped)
396            }
397        } else if let Some(stripped) = value.strip_prefix(['+', '-']) {
398            if stripped.is_empty() || self.cast_number(stripped).is_some() {
399                None
400            } else {
401                Some(value)
402            }
403        } else {
404            None
405        }
406    }
407
408    /// Parses a formula that is stored internally (always in English) and
409    /// returns the resulting node.
410    ///
411    /// Formula strings kept outside of cells (defined names and conditional
412    /// formatting rules) are always stored in English — see
413    /// [Model::user_formula_to_internal]. They must therefore be parsed with
414    /// the English language and locale regardless of the user's active
415    /// language. This temporarily switches the parser, parses, and restores it.
416    pub(crate) fn parse_internal_formula(&mut self, body: &str, context: &CellReferenceRC) -> Node {
417        let locale = self.locale;
418        let language = self.language;
419        self.parser.set_locale(get_default_locale());
420        self.parser.set_language(get_default_language());
421        let node = self.parser.parse(body, context);
422        self.parser.set_locale(locale);
423        self.parser.set_language(language);
424        node
425    }
426
427    /// Translates a formula the user typed (in the active language and locale)
428    /// into the canonical English representation that is stored internally.
429    ///
430    /// The formula is first parsed in the active language/locale. If that fails
431    /// it is parsed as English — this lets internally generated formulas (which
432    /// are already English, e.g. produced by undo/redo or cut & paste) round
433    /// trip unchanged regardless of the active language. Returns an error if the
434    /// formula parses in neither. Any leading `=` is preserved.
435    pub(crate) fn user_formula_to_internal(
436        &mut self,
437        formula: &str,
438        context: &CellReferenceRC,
439    ) -> Result<String, String> {
440        let trimmed = formula.trim();
441        let had_equals = trimmed.starts_with('=');
442        let body = trimmed.strip_prefix('=').unwrap_or(trimmed);
443        let mut node = self.parser.parse(body, context);
444        if let Node::ParseErrorKind { .. } = node {
445            // The user's language could not parse it: it might already be in the
446            // internal English form.
447            node = self.parse_internal_formula(body, context);
448        }
449        if let Node::ParseErrorKind { .. } = node {
450            return Err(format!("Invalid formula: '{formula}'"));
451        }
452        let english = to_english_string(&node, context);
453        Ok(if had_equals {
454            format!("={english}")
455        } else {
456            english
457        })
458    }
459
460    /// Returns completion information for a formula being edited in a cell.
461    ///
462    /// `formula` is the raw cell input (it may start with `=`) and `cursor` is a
463    /// char offset into it. The references in the formula are resolved relative
464    /// to the cell at (`sheet`, `row`, `column`). See
465    /// [`CompletionContext`](crate::expressions::parser::CompletionContext).
466    pub fn formula_completion(
467        &mut self,
468        sheet: u32,
469        row: i32,
470        column: i32,
471        formula: &str,
472        cursor: usize,
473    ) -> Result<CompletionContext, String> {
474        let sheet_name = self.workbook.worksheet(sheet)?.get_name();
475        let cell_reference = CellReferenceRC {
476            sheet: sheet_name,
477            row,
478            column,
479        };
480        // The parser works on the formula body, without the leading `=`. Drop it
481        // and shift the cursor so it keeps pointing at the same character.
482        let (body, cursor) = match formula.strip_prefix('=') {
483            Some(rest) => (rest, cursor.saturating_sub(1)),
484            None => (formula, cursor),
485        };
486        Ok(self.parser.parse_at_cursor(body, cursor, &cell_reference))
487    }
488
489    /// Translates an internally-stored (English) formula into the active
490    /// language and locale for display to the user. Any leading `=` is
491    /// preserved. If the formula fails to parse it is returned unchanged.
492    pub(crate) fn internal_formula_to_display(
493        &self,
494        formula: &str,
495        context: &CellReferenceRC,
496    ) -> String {
497        let trimmed = formula.trim();
498        let had_equals = trimmed.starts_with('=');
499        let body = trimmed.strip_prefix('=').unwrap_or(trimmed);
500        if body.is_empty() {
501            return formula.to_string();
502        }
503        // Stored formulas are in English, so parse with an English parser.
504        let worksheet_names = self
505            .workbook
506            .worksheets
507            .iter()
508            .map(|s| s.get_name())
509            .collect();
510        let defined_names = self.workbook.get_defined_names_with_scope();
511        let mut parser =
512            new_parser_english(worksheet_names, defined_names, self.workbook.tables.clone());
513        let node = parser.parse(body, context);
514        if let Node::ParseErrorKind { .. } = node {
515            return formula.to_string();
516        }
517        let local = to_localized_string(&node, context, self.locale, self.language);
518        if had_equals {
519            format!("={local}")
520        } else {
521            local
522        }
523    }
524
525    /// Evaluates a formula string on a sheet, returning the numeric result.
526    /// Assumes the workbook has already been evaluated (cell values are up-to-date).
527    /// Returns `None` if the formula is invalid or does not produce a number.
528    pub(crate) fn evaluate_formula(&mut self, formula: &str, sheet: u32) -> Option<f64> {
529        let body = formula.trim().strip_prefix('=').unwrap_or(formula.trim());
530        if body.is_empty() {
531            return None;
532        }
533        let sheet_name = self.workbook.worksheets.get(sheet as usize)?.get_name();
534        let context_rc = CellReferenceRC {
535            sheet: sheet_name,
536            row: 1,
537            column: 1,
538        };
539        let node = self.parse_internal_formula(body, &context_rc);
540        let context_index = CellReferenceIndex {
541            sheet,
542            row: 1,
543            column: 1,
544        };
545        match self.evaluate_node_in_context(&node, context_index) {
546            CalcResult::Number(n) => Some(n),
547            _ => None,
548        }
549    }
550
551    pub(crate) fn evaluate_node_in_context(
552        &mut self,
553        node: &Node,
554        cell: CellReferenceIndex,
555    ) -> CalcResult {
556        use Node::*;
557        match node {
558            OpSumKind { kind, left, right } => match kind {
559                OpSum::Add => self.handle_arithmetic(left, right, cell, &|f1, f2| Ok(f1 + f2)),
560                OpSum::Minus => self.handle_arithmetic(left, right, cell, &|f1, f2| Ok(f1 - f2)),
561            },
562            NumberKind(value) => CalcResult::Number(*value),
563            StringKind(value) => CalcResult::String(value.replace(r#""""#, r#"""#)),
564            BooleanKind(value) => CalcResult::Boolean(*value),
565            ReferenceKind {
566                sheet_name: _,
567                sheet_index,
568                absolute_row,
569                absolute_column,
570                row,
571                column,
572            } => {
573                let mut row1 = *row;
574                let mut column1 = *column;
575                if !absolute_row {
576                    row1 += cell.row;
577                }
578                if !absolute_column {
579                    column1 += cell.column;
580                }
581                self.support
582                    .entry(cell)
583                    .or_default()
584                    .push(CellOrRange::Cell((*sheet_index, row1, column1)));
585                self.evaluate_cell(CellReferenceIndex {
586                    sheet: *sheet_index,
587                    row: row1,
588                    column: column1,
589                })
590            }
591            WrongReferenceKind { .. } => {
592                CalcResult::new_error(Error::REF, cell, "Wrong reference".to_string())
593            }
594            OpRangeKind { left, right } => self.get_range(left, right, cell),
595            WrongRangeKind { .. } => {
596                CalcResult::new_error(Error::REF, cell, "Wrong range".to_string())
597            }
598            RangeKind {
599                sheet_index,
600                row1,
601                column1,
602                row2,
603                column2,
604                absolute_column1,
605                absolute_row2,
606                absolute_row1,
607                absolute_column2,
608                sheet_name: _,
609            } => {
610                let r1 = if *absolute_row1 {
611                    *row1
612                } else {
613                    *row1 + cell.row
614                };
615                let r2 = if *absolute_row2 {
616                    *row2
617                } else {
618                    *row2 + cell.row
619                };
620                let c1 = if *absolute_column1 {
621                    *column1
622                } else {
623                    *column1 + cell.column
624                };
625                let c2 = if *absolute_column2 {
626                    *column2
627                } else {
628                    *column2 + cell.column
629                };
630                self.support
631                    .entry(cell)
632                    .or_default()
633                    .push(CellOrRange::Range((
634                        *sheet_index,
635                        r1.min(r2),
636                        c1.min(c2),
637                        r1.max(r2),
638                        c1.max(c2),
639                    )));
640                CalcResult::Range {
641                    left: CellReferenceIndex {
642                        sheet: *sheet_index,
643                        row: r1.min(r2),
644                        column: c1.min(c2),
645                    },
646                    right: CellReferenceIndex {
647                        sheet: *sheet_index,
648                        row: r1.max(r2),
649                        column: c1.max(c2),
650                    },
651                }
652            }
653            OpConcatenateKind { left, right } => self.handle_concatenate(left, right, cell),
654            OpProductKind { kind, left, right } => match kind {
655                OpProduct::Times => {
656                    self.handle_arithmetic(left, right, cell, &|f1, f2| Ok(f1 * f2))
657                }
658                OpProduct::Divide => self.handle_arithmetic(left, right, cell, &|f1, f2| {
659                    if f2 == 0.0 {
660                        Err(Error::DIV)
661                    } else {
662                        Ok(f1 / f2)
663                    }
664                }),
665            },
666            OpPowerKind { left, right } => {
667                self.handle_arithmetic(left, right, cell, &|f1, f2| Ok(f1.powf(f2)))
668            }
669            FunctionKind { kind, args } => self.evaluate_function(kind, args, cell),
670            NamedFunctionKind { name, args, id } => {
671                let lambda_result = if let Some(var_id) = id {
672                    // Bound by LET — look up the variable, which should be a Lambda.
673                    match self.variable_stack.get(&(*var_id as usize)) {
674                        Some(v) => v.clone(),
675                        None => {
676                            return CalcResult::new_error(
677                                Error::NAME,
678                                cell,
679                                format!("Variable \"{name}\" not found in scope."),
680                            )
681                        }
682                    }
683                } else {
684                    // Not bound by LET — look up as a defined-name Lambda.
685                    // Prefer sheet-local (current sheet) over global (scope = None),
686                    // matching Excel's name resolution order.
687                    let name_lower = name.to_lowercase();
688                    let found = self
689                        .parsed_defined_names
690                        .get(&(Some(cell.sheet), name_lower.clone()))
691                        .or_else(|| self.parsed_defined_names.get(&(None, name_lower)))
692                        .cloned();
693                    match found {
694                        Some(ParsedDefinedName::LambdaDefinition(param_names, body)) => {
695                            let lambda_id = self.get_next_lambda_id();
696                            self.lambdas.insert(lambda_id, (param_names, body));
697                            CalcResult::Lambda(lambda_id)
698                        }
699                        _ => {
700                            return CalcResult::new_error(
701                                Error::NAME,
702                                cell,
703                                format!("Invalid function: {name}"),
704                            )
705                        }
706                    }
707                };
708                self.call_lambda(lambda_result, args, cell)
709            }
710            ArrayKind(s) => CalcResult::Array(s.to_owned()),
711            DefinedNameKind((name, scope, _)) => {
712                if let Ok(Some(parsed_defined_name)) = self.get_parsed_defined_name(name, *scope) {
713                    match parsed_defined_name {
714                        ParsedDefinedName::CellReference(reference) => {
715                            self.evaluate_cell(reference)
716                        }
717                        ParsedDefinedName::RangeReference(range) => CalcResult::Range {
718                            left: range.left,
719                            right: range.right,
720                        },
721                        ParsedDefinedName::LambdaDefinition(param_names, body) => {
722                            let lambda_id = self.get_next_lambda_id();
723                            self.lambdas.insert(lambda_id, (param_names, body));
724                            CalcResult::Lambda(lambda_id)
725                        }
726                        ParsedDefinedName::InvalidDefinedNameFormula => CalcResult::new_error(
727                            Error::NAME,
728                            cell,
729                            format!("Defined name \"{name}\" is not a reference."),
730                        ),
731                    }
732                } else {
733                    CalcResult::new_error(
734                        Error::NAME,
735                        cell,
736                        format!("Defined name \"{name}\" not found."),
737                    )
738                }
739            }
740            TableNameKind(s) => CalcResult::new_error(
741                Error::NAME,
742                cell,
743                format!("table name \"{s}\" not supported."),
744            ),
745            NamedVariableKind { name, id: Some(id) } => {
746                match self.variable_stack.get(&(*id as usize)) {
747                    Some(v) => v.clone(),
748                    None => CalcResult::new_error(
749                        Error::NAME,
750                        cell,
751                        format!("Variable \"{name}\" not found in scope."),
752                    ),
753                }
754            }
755            NamedVariableKind { name, id: None } => CalcResult::new_error(
756                Error::NAME,
757                cell,
758                format!("Variable name \"{name}\" not found."),
759            ),
760            CompareKind { kind, left, right } => self.handle_comparison(left, right, cell, kind),
761            UnaryKind { kind, right } => {
762                let r = match self.get_number(right, cell) {
763                    Ok(f) => f,
764                    Err(s) => {
765                        return s;
766                    }
767                };
768                match kind {
769                    OpUnary::Minus => CalcResult::Number(-r),
770                    OpUnary::Percentage => CalcResult::Number(r / 100.0),
771                }
772            }
773            ErrorKind(kind) => CalcResult::new_error(kind.clone(), cell, "".to_string()),
774            ParseErrorKind {
775                formula, message, ..
776            } => CalcResult::new_error(
777                Error::ERROR,
778                cell,
779                format!("Error parsing {formula}: {message}"),
780            ),
781            EmptyArgKind => CalcResult::EmptyArg,
782            SpillRangeOperator { child } => match self.evaluate_node_with_reference(child, cell) {
783                CalcResult::Range { left, right } => {
784                    if left != right {
785                        return CalcResult::new_error(
786                            Error::ERROR,
787                            cell,
788                            format!("Error with Spill Range Operator in cell {cell:?}"),
789                        );
790                    }
791                    //
792                    let sheet = left.sheet;
793                    let row = left.row;
794                    let column = left.column;
795                    let worksheet = match self.workbook.worksheet(sheet) {
796                        Ok(s) => s,
797                        Err(e) => {
798                            return CalcResult::new_error(
799                                Error::REF,
800                                cell,
801                                format!("Sheet index {sheet} not found: {e}"),
802                            );
803                        }
804                    };
805                    match worksheet.get_cell_spill(row, column) {
806                        Ok((width, height)) => CalcResult::Range {
807                            left: CellReferenceIndex { sheet, row, column },
808                            right: CellReferenceIndex {
809                                sheet,
810                                row: row + height - 1,
811                                column: column + width - 1,
812                            },
813                        },
814                        Err(e) => CalcResult::new_error(
815                            Error::REF,
816                            cell,
817                            format!("Cell {sheet}!{row},{column} not found: {e}"),
818                        ),
819                    }
820                }
821                _ => CalcResult::new_error(
822                    Error::ERROR,
823                    cell,
824                    format!("Error with Spill Range Operator in cell {cell:?}"),
825                ),
826            },
827            ImplicitIntersection {
828                automatic: _,
829                child,
830            } => match self.evaluate_node_with_reference(child, cell) {
831                CalcResult::Range { left, right } => {
832                    match implicit_intersection(&cell, &Range { left, right }) {
833                        Some(cell_reference) => self.evaluate_cell(cell_reference),
834                        None => CalcResult::new_error(
835                            Error::VALUE,
836                            cell,
837                            format!("Error with Implicit Intersection in cell {cell:?}"),
838                        ),
839                    }
840                }
841                _ => self.evaluate_node_in_context(child, cell),
842            },
843            LambdaDefKind { parameters, body } => {
844                let id = self.get_next_lambda_id();
845                self.lambdas.insert(id, (parameters.clone(), *body.clone()));
846                CalcResult::Lambda(id)
847            }
848            LambdaCallKind { lambda, args } => {
849                let lambda_result = self.evaluate_node_in_context(lambda, cell);
850                self.call_lambda(lambda_result, args, cell)
851            }
852        }
853    }
854
855    fn cell_reference_to_string(
856        &self,
857        cell_reference: &CellReferenceIndex,
858    ) -> Result<String, String> {
859        let sheet = self.workbook.worksheet(cell_reference.sheet)?;
860        let column = utils::number_to_column(cell_reference.column)
861            .ok_or_else(|| "Invalid column".to_string())?;
862        if !is_valid_row(cell_reference.row) {
863            return Err("Invalid row".to_string());
864        }
865        Ok(format!("{}!{}{}", sheet.name, column, cell_reference.row))
866    }
867
868    fn get_value_from_array(
869        &self,
870        array: &[Vec<ArrayNode>],
871        row: i32,
872        column: i32,
873    ) -> Option<ArrayNode> {
874        let width = array[0].len() as i32;
875        let height = array.len() as i32;
876        if row < 1 || row > height || column < 1 || column > width {
877            return None;
878        }
879        let value = &array[(row - 1) as usize][(column - 1) as usize];
880        Some(value.clone())
881    }
882
883    /// Sets `result` in the cell given by `sheet` sheet index, row and column
884    /// Note that will panic if the cell does not exist
885    /// It will do nothing if the cell does not have a formula
886    /// If the result is an array it will spill over other cells
887    /// If the formula is an array formula it will update the spill area.
888    ///    If the array is smaller than the spill area it will fill the remaining cells with #N/A error
889    ///    If the array is just one element it will fill the original range with that element
890    fn set_cells_with_result(
891        &mut self,
892        cell_reference: CellReferenceIndex,
893        cell: &Cell,
894        result: &CalcResult,
895    ) -> Result<(), String> {
896        let CellReferenceIndex { sheet, column, row } = cell_reference;
897        let original_range = match cell {
898            Cell::ArrayFormula {
899                r,
900                kind: ArrayKind::Cse,
901                ..
902            } => Some((false, (r.0, r.1))),
903            Cell::ArrayFormula {
904                r,
905                kind: ArrayKind::Dynamic,
906                ..
907            } => Some((true, (r.0, r.1))),
908            _ => None,
909        };
910        let s = cell.get_style();
911        let formula = match cell.get_formula() {
912            Some(f) => f,
913            None => return Ok(()),
914        };
915        // Handle array results separately: they always return early, writing all cells
916        // themselves. By dispatching here we avoid needing an unreachable arm in the
917        // `new_cell` match below.
918        if let CalcResult::Array(array) = result {
919            if array.is_empty() || array[0].is_empty() {
920                return self.set_cells_with_result(
921                    cell_reference,
922                    cell,
923                    &CalcResult::new_error(
924                        Error::CALC,
925                        cell_reference,
926                        "Formula produced a zero-size array".to_string(),
927                    ),
928                );
929            }
930            let array_width = array[0].len() as i32;
931            let array_height = array.len() as i32;
932
933            match original_range {
934                Some((true, _)) => {
935                    if row + array_height - 1 > LAST_ROW || column + array_width - 1 > LAST_COLUMN {
936                        return self.set_cells_with_result(
937                            cell_reference,
938                            cell,
939                            &CalcResult::new_error(
940                                Error::SPILL,
941                                cell_reference,
942                                "Spill would exceed worksheet bounds".to_string(),
943                            ),
944                        );
945                    }
946                    // Check that the full spill area (based on actual result dimensions) is clear.
947                    // The stored range may be (1,1) on first evaluation, so we must re-check here.
948                    let sheet_data = &self.workbook.worksheets[sheet as usize].sheet_data;
949                    for r in row..row + array_height {
950                        let row_data = sheet_data.get(&r);
951                        for c in column..column + array_width {
952                            if r == row && c == column {
953                                continue;
954                            }
955                            // A cell blocks spilling only if it is occupied by something
956                            // other than an empty cell or a spill cell that already belongs
957                            // to this formula.  Own spill cells are about to be overwritten
958                            // and must never prevent the formula from re-spilling (this
959                            // matters after undo restores a SpillCell while the anchor's
960                            // stored `r` is still (1,1) from a prior #SPILL! evaluation).
961                            let blocking = row_data
962                                .and_then(|row_map| row_map.get(&c))
963                                .map(|cell| match cell {
964                                    Cell::EmptyCell { .. } => false,
965                                    Cell::SpillCell { a, .. } if *a == (row, column) => false,
966                                    _ => true,
967                                })
968                                .unwrap_or(false);
969                            if blocking {
970                                return self.set_cells_with_result(
971                                    cell_reference,
972                                    cell,
973                                    &CalcResult::new_error(
974                                        Error::SPILL,
975                                        cell_reference,
976                                        "Cannot spill array result".to_string(),
977                                    ),
978                                );
979                            }
980                        }
981                    }
982                    let worksheet = &mut self.workbook.worksheets[sheet as usize];
983                    // Dynamic formula: spill the array into adjacent cells.
984                    // Cells are created on demand via update_cell since they may not exist yet.
985                    for r in row..row + array_height {
986                        for c in column..column + array_width {
987                            let value = array[(r - row) as usize][(c - column) as usize].clone();
988                            let cell = if r == row && c == column {
989                                Cell::ArrayFormula {
990                                    f: formula,
991                                    s,
992                                    r: (array_width, array_height),
993                                    kind: ArrayKind::Dynamic,
994                                    v: array_node_to_formula_value(value),
995                                }
996                            } else {
997                                let existing_style = worksheet.get_style(r, c);
998                                Cell::SpillCell {
999                                    a: (row, column),
1000                                    s: existing_style,
1001                                    v: array_node_to_spill_value(value),
1002                                }
1003                            };
1004                            worksheet.update_cell(r, c, cell)?;
1005                        }
1006                    }
1007                    return Ok(());
1008                }
1009                Some((false, (original_width, original_height))) => {
1010                    // CSE array formula: fill the declared range with the array values.
1011                    // Use relative indices for get_value_from_array (1-based).
1012                    for r in row..row + original_height {
1013                        for c in column..column + original_width {
1014                            let rel_row = r - row + 1;
1015                            let rel_col = c - column + 1;
1016                            let value = self.get_value_from_array(array, rel_row, rel_col);
1017                            let new_cell = if r == row && c == column {
1018                                let fv = match value {
1019                                    Some(node) => array_node_to_formula_value(node),
1020                                    None => FormulaValue::Error {
1021                                        ei: Error::NIMPL,
1022                                        o: "".to_string(),
1023                                        m: "Unexpected array result".to_string(),
1024                                    },
1025                                };
1026                                Cell::ArrayFormula {
1027                                    f: formula,
1028                                    s,
1029                                    r: (original_width, original_height),
1030                                    kind: ArrayKind::Cse,
1031                                    v: fv,
1032                                }
1033                            } else {
1034                                let sv = match value {
1035                                    Some(node) => array_node_to_spill_value(node),
1036                                    None => SpillValue::Error(Error::VALUE),
1037                                };
1038                                let existing_style =
1039                                    self.workbook.worksheets[sheet as usize].get_style(r, c);
1040                                Cell::SpillCell {
1041                                    s: existing_style,
1042                                    a: (row, column),
1043                                    v: sv,
1044                                }
1045                            };
1046                            *self.workbook.worksheets[sheet as usize]
1047                                .sheet_data
1048                                .get_mut(&r)
1049                                .ok_or("expected a row")?
1050                                .get_mut(&c)
1051                                .ok_or("expected a column")? = new_cell;
1052                        }
1053                    }
1054                    // All cells (anchor + spills) have been written above.
1055                    return Ok(());
1056                }
1057                None => {
1058                    // Scalar formula produced an array at runtime. We only coerce safely
1059                    // when the array is 1x1 (the result is genuinely a single value just
1060                    // wrapped in an array). For larger arrays, Excel would apply implicit
1061                    // intersection (legacy) or, for formulas identified as dynamic/array
1062                    // at parse time, auto-spill. In this `original_range == None` path we
1063                    // do not have that array/dynamic context, so neither behavior is
1064                    // available here; picking [0][0] could silently produce wrong results.
1065                    // Emit #VALUE! instead so the divergence is visible.
1066                    let coerced = if array_width == 1 && array_height == 1 {
1067                        match self.get_value_from_array(array, 1, 1) {
1068                            Some(node) => array_node_to_formula_value(node),
1069                            None => FormulaValue::Error {
1070                                ei: Error::VALUE,
1071                                o: "".to_string(),
1072                                m: "Unexpected array result".to_string(),
1073                            },
1074                        }
1075                    } else {
1076                        // Currently unreachable from normal user formulas: static
1077                        // analysis wraps array-returning subexpressions in scalar
1078                        // contexts in implicit intersection (`@`), which collapses
1079                        // them to a single value before they reach the cell. If we
1080                        // ever get here, static analysis or implicit-intersection
1081                        // insertion has regressed.
1082                        debug_assert!(
1083                            false,
1084                            "Larger-than-1x1 array reached scalar-context cell \
1085                             (sheet={sheet}, row={row}, column={column}, \
1086                             {array_width}x{array_height}); implicit intersection \
1087                             was expected to collapse it.",
1088                        );
1089                        FormulaValue::Error {
1090                            ei: Error::VALUE,
1091                            o: "".to_string(),
1092                            m: "Array result in scalar context".to_string(),
1093                        }
1094                    };
1095                    *self.workbook.worksheets[sheet as usize]
1096                        .sheet_data
1097                        .get_mut(&row)
1098                        .ok_or("expected a row")?
1099                        .get_mut(&column)
1100                        .ok_or("expected a column")? = Cell::CellFormula {
1101                        f: formula,
1102                        s,
1103                        v: coerced,
1104                    };
1105                    return Ok(());
1106                }
1107            }
1108        }
1109
1110        let formula_value = match result {
1111            CalcResult::Number(value) => {
1112                // safety belt
1113                if value.is_nan() || value.is_infinite() {
1114                    // This should never happen, is there a way we can log this events?
1115                    return self.set_cells_with_result(
1116                        cell_reference,
1117                        cell,
1118                        &CalcResult::Error {
1119                            error: Error::NUM,
1120                            origin: cell_reference,
1121                            message: "".to_string(),
1122                        },
1123                    );
1124                }
1125                FormulaValue::Number(*value)
1126            }
1127            CalcResult::String(value) => FormulaValue::Text(value.clone()),
1128            CalcResult::Boolean(value) => FormulaValue::Boolean(*value),
1129            CalcResult::Error {
1130                error,
1131                origin,
1132                message,
1133            } => {
1134                let o = match self.cell_reference_to_string(origin) {
1135                    Ok(s) => s,
1136                    Err(_) => "".to_string(),
1137                };
1138                FormulaValue::Error {
1139                    ei: error.clone(),
1140                    o,
1141                    m: message.to_string(),
1142                }
1143            }
1144            CalcResult::Range { .. } => {
1145                // This should never happen
1146                debug_assert!(false, "Unexpected range result in non-array formula");
1147                return Err("Cannot set a range as cell value".to_string());
1148            }
1149            CalcResult::EmptyCell | CalcResult::EmptyArg => {
1150                // We treat empty cells as number 0.
1151                return self.set_cells_with_result(cell_reference, cell, &CalcResult::Number(0.0));
1152            }
1153            // CalcResult::Array is handled before this match (see above); it always returns early.
1154            CalcResult::Array(_) | CalcResult::Lambda(_) => {
1155                debug_assert!(false, "Unexpected array result in non-array formula");
1156                return Err("Unexpected array result in non-array formula".to_string());
1157            }
1158        };
1159
1160        let new_cell = match original_range {
1161            Some((is_dynamic, (width, height))) => {
1162                let (kind, r) = if is_dynamic {
1163                    (ArrayKind::Dynamic, (1, 1))
1164                } else {
1165                    (ArrayKind::Cse, (width, height))
1166                };
1167                Cell::ArrayFormula {
1168                    f: formula,
1169                    s,
1170                    r,
1171                    kind,
1172                    v: formula_value.clone(),
1173                }
1174            }
1175            None => Cell::CellFormula {
1176                f: formula,
1177                s,
1178                v: formula_value.clone(),
1179            },
1180        };
1181
1182        // If the cell is the anchor of a CSE array formula, fill all spill cells
1183        if let Some((false, (width, height))) = original_range {
1184            let spill_value = formula_value_to_spill_value(&formula_value);
1185            let ws = &mut self.workbook.worksheets[sheet as usize];
1186            for r in row..row + height {
1187                for c in column..column + width {
1188                    if r == row && c == column {
1189                        continue;
1190                    }
1191                    let existing_style = ws.get_style(r, c);
1192                    ws.update_cell(
1193                        r,
1194                        c,
1195                        Cell::SpillCell {
1196                            a: (row, column),
1197                            s: existing_style,
1198                            v: spill_value.clone(),
1199                        },
1200                    )?;
1201                }
1202            }
1203        }
1204
1205        self.workbook.worksheets[sheet as usize].update_cell(row, column, new_cell)?;
1206        Ok(())
1207    }
1208
1209    /// Sets the color of the sheet tab.
1210    ///
1211    /// # Examples
1212    ///
1213    /// ```rust
1214    /// # use ironcalc_base::{Model, types::Color};
1215    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1216    /// let mut model = Model::new_empty("model", "en", "UTC", "en")?;
1217    /// assert_eq!(model.workbook.worksheet(0)?.color, Color::None);
1218    /// model.set_sheet_color(0, &Color::Rgb("#DBBE29".to_string()))?;
1219    /// assert_eq!(model.workbook.worksheet(0)?.color, Color::Rgb("#DBBE29".to_string()));
1220    /// # Ok(())
1221    /// # }
1222    /// ```
1223    pub fn set_sheet_color(&mut self, sheet: u32, color: &Color) -> Result<(), String> {
1224        let worksheet = self.workbook.worksheet_mut(sheet)?;
1225        worksheet.color = color.clone();
1226        Ok(())
1227    }
1228
1229    /// Changes the visibility of a sheet
1230    pub fn set_sheet_state(&mut self, sheet: u32, state: SheetState) -> Result<(), String> {
1231        let worksheet = self.workbook.worksheet_mut(sheet)?;
1232        worksheet.state = state;
1233        Ok(())
1234    }
1235
1236    /// Sets the workbook theme.
1237    pub fn set_theme(&mut self, theme: crate::types::Theme) {
1238        self.workbook.theme = theme;
1239        self.evaluate_conditional_formatting();
1240    }
1241
1242    /// Returns the Theme
1243    pub fn get_theme(&self) -> Theme {
1244        self.workbook.theme.clone()
1245    }
1246
1247    /// Makes the grid lines in the sheet visible (`true`) or hidden (`false`)
1248    pub fn set_show_grid_lines(&mut self, sheet: u32, show_grid_lines: bool) -> Result<(), String> {
1249        let worksheet = self.workbook.worksheet_mut(sheet)?;
1250        worksheet.show_grid_lines = show_grid_lines;
1251        Ok(())
1252    }
1253
1254    // Returns the 'single' value of a cell. Not arrays or ranges.
1255    fn get_cell_value(&self, cell: &Cell, cell_reference: CellReferenceIndex) -> CalcResult {
1256        use Cell::*;
1257        match cell {
1258            EmptyCell { .. } => CalcResult::EmptyCell,
1259            BooleanCell { v, .. } => CalcResult::Boolean(*v),
1260            NumberCell { v, .. } => CalcResult::Number(*v),
1261            ErrorCell { ei, .. } => {
1262                let message = ei.to_localized_error_string(self.language);
1263                CalcResult::new_error(ei.clone(), cell_reference, message)
1264            }
1265            SharedString { si, .. } => {
1266                if let Some(s) = self.workbook.shared_strings.get(*si as usize) {
1267                    CalcResult::String(s.clone())
1268                } else {
1269                    let message = "Invalid shared string".to_string();
1270                    CalcResult::new_error(Error::ERROR, cell_reference, message)
1271                }
1272            }
1273            CellFormula {
1274                v: FormulaValue::Unevaluated,
1275                ..
1276            }
1277            | ArrayFormula {
1278                v: FormulaValue::Unevaluated,
1279                ..
1280            } => CalcResult::Error {
1281                error: Error::ERROR,
1282                origin: cell_reference,
1283                message: "Unevaluated formula".to_string(),
1284            },
1285            CellFormula {
1286                v: FormulaValue::Boolean(v),
1287                ..
1288            }
1289            | ArrayFormula {
1290                v: FormulaValue::Boolean(v),
1291                ..
1292            } => CalcResult::Boolean(*v),
1293            CellFormula {
1294                v: FormulaValue::Number(v),
1295                ..
1296            }
1297            | ArrayFormula {
1298                v: FormulaValue::Number(v),
1299                ..
1300            } => CalcResult::Number(*v),
1301            CellFormula {
1302                v: FormulaValue::Text(v),
1303                ..
1304            }
1305            | ArrayFormula {
1306                v: FormulaValue::Text(v),
1307                ..
1308            } => CalcResult::String(v.clone()),
1309            CellFormula {
1310                v: FormulaValue::Error { ei, o, m },
1311                ..
1312            }
1313            | ArrayFormula {
1314                v: FormulaValue::Error { ei, o, m },
1315                ..
1316            } => {
1317                if let Some(cell_reference) = self.parse_reference(o) {
1318                    CalcResult::new_error(ei.clone(), cell_reference, m.clone())
1319                } else {
1320                    CalcResult::Error {
1321                        error: ei.clone(),
1322                        origin: cell_reference,
1323                        message: ei.to_localized_error_string(self.language),
1324                    }
1325                }
1326            }
1327            SpillCell {
1328                v: SpillValue::Number(v),
1329                ..
1330            } => CalcResult::Number(*v),
1331            SpillCell {
1332                v: SpillValue::Boolean(v),
1333                ..
1334            } => CalcResult::Boolean(*v),
1335            SpillCell {
1336                v: SpillValue::Text(v),
1337                ..
1338            } => CalcResult::String(v.clone()),
1339            SpillCell {
1340                v: SpillValue::Error(ei),
1341                ..
1342            } => {
1343                let message = ei.to_localized_error_string(self.language);
1344                CalcResult::new_error(ei.clone(), cell_reference, message)
1345            }
1346        }
1347    }
1348
1349    /// Returns `true` if the cell is completely empty.
1350    ///
1351    /// # Examples
1352    ///
1353    /// ```rust
1354    /// # use ironcalc_base::Model;
1355    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1356    /// let mut model = Model::new_empty("model", "en", "UTC", "en")?;
1357    /// assert_eq!(model.is_empty_cell(0, 1, 1)?, true);
1358    /// model.set_user_input(0, 1, 1, "Attention is all you need".to_string());
1359    /// assert_eq!(model.is_empty_cell(0, 1, 1)?, false);
1360    /// # Ok(())
1361    /// # }
1362    /// ```
1363    pub fn is_empty_cell(&self, sheet: u32, row: i32, column: i32) -> Result<bool, String> {
1364        self.workbook.worksheet(sheet)?.is_empty_cell(row, column)
1365    }
1366
1367    /// Evaluates all cells in a given range and returns the results in a 2D vector.
1368    pub(crate) fn evaluate_range(
1369        &mut self,
1370        left: CellReferenceIndex,
1371        right: CellReferenceIndex,
1372    ) -> Vec<Vec<ArrayNode>> {
1373        let mut result = Vec::new();
1374        for r in left.row..=right.row {
1375            let mut row_result = Vec::new();
1376            for c in left.column..=right.column {
1377                let cell_reference = CellReferenceIndex {
1378                    sheet: left.sheet,
1379                    row: r,
1380                    column: c,
1381                };
1382                let value = match self.evaluate_cell(cell_reference) {
1383                    CalcResult::Number(n) => ArrayNode::Number(n),
1384                    CalcResult::Boolean(b) => ArrayNode::Boolean(b),
1385                    CalcResult::String(s) => ArrayNode::String(s),
1386                    CalcResult::Error { error, .. } => ArrayNode::Error(error),
1387                    CalcResult::EmptyCell | CalcResult::EmptyArg => ArrayNode::Empty,
1388                    CalcResult::Range { .. } | CalcResult::Array(_) | CalcResult::Lambda(_) => {
1389                        // This should never happen, but we need to handle it anyway
1390                        debug_assert!(false, "Unexpected array result in non-array formula");
1391                        ArrayNode::Error(Error::NIMPL)
1392                    }
1393                };
1394                row_result.push(value);
1395            }
1396            result.push(row_result);
1397        }
1398        result
1399    }
1400
1401    #[inline(always)]
1402    fn fetch_cell(&self, cell_reference: CellReferenceIndex) -> Option<&Cell> {
1403        self.workbook.worksheets[cell_reference.sheet as usize]
1404            .sheet_data
1405            .get(&cell_reference.row)?
1406            .get(&cell_reference.column)
1407    }
1408
1409    // Evaluates a cell and returns the value in the cell
1410    // FIXME: CalcResult cannot be Array or Range, should we have a different type?
1411    pub(crate) fn evaluate_cell(&mut self, cell_reference: CellReferenceIndex) -> CalcResult {
1412        let original_cell = match self.fetch_cell(cell_reference) {
1413            Some(c) => c.clone(),
1414            None => return CalcResult::EmptyCell,
1415        };
1416
1417        if let Cell::SpillCell { a, .. } = original_cell {
1418            // If it is part of an array or dynamic formula we need to evaluate the anchor cell
1419            // strictly speaking we don't need to evaluate the anchor cell of a dynamic array formula
1420            // but it is most likely a good guess anyway
1421            let anchor_cell_reference = CellReferenceIndex {
1422                sheet: cell_reference.sheet,
1423                column: a.1,
1424                row: a.0,
1425            };
1426            // evaluate the anchor and discard the result
1427            let _ = self.evaluate_cell(anchor_cell_reference);
1428            // refetch the cell after evaluating the spill reference
1429            let cell = match self.fetch_cell(cell_reference) {
1430                Some(c) => c,
1431                None => return CalcResult::EmptyCell,
1432            };
1433            // and return its value
1434            return self.get_cell_value(cell, cell_reference);
1435        };
1436
1437        match original_cell.get_formula() {
1438            Some(f) => {
1439                let key = (
1440                    cell_reference.sheet,
1441                    cell_reference.row,
1442                    cell_reference.column,
1443                );
1444                if let Some(state) = self.cells.get(&key) {
1445                    match state {
1446                        CellState::Evaluating => {
1447                            return CalcResult::new_error(
1448                                Error::CIRC,
1449                                cell_reference,
1450                                "Circular reference detected".to_string(),
1451                            );
1452                        }
1453                        CellState::Evaluated => {
1454                            return self.get_cell_value(&original_cell, cell_reference);
1455                        }
1456                    }
1457                }
1458                // Clear the pre-existing spill area of a dynamic formula before re-evaluating.
1459                // This must happen after the CellState check so that a recursive call from a
1460                // spill cell does not wipe out spill cells that were just written.
1461                if let Cell::ArrayFormula {
1462                    r,
1463                    kind: ArrayKind::Dynamic,
1464                    ..
1465                } = &original_cell
1466                {
1467                    let (width, height) = *r;
1468                    let ws = match self.workbook.worksheet_mut(cell_reference.sheet) {
1469                        Ok(ws) => ws,
1470                        Err(_) => {
1471                            return CalcResult::new_error(
1472                                Error::ERROR,
1473                                cell_reference,
1474                                "Invalid sheet".to_string(),
1475                            )
1476                        }
1477                    };
1478                    for r in cell_reference.row..cell_reference.row + height {
1479                        for c in cell_reference.column..cell_reference.column + width {
1480                            if r == cell_reference.row && c == cell_reference.column {
1481                                continue;
1482                            }
1483                            // Only clear cells that are spill cells belonging to this anchor.
1484                            // Non-SpillCell content must remain
1485                            // so they can block the spill on re-evaluation.
1486                            let is_own_spill = ws
1487                                .sheet_data
1488                                .get(&r)
1489                                .and_then(|row_data| row_data.get(&c))
1490                                .map(|cell| {
1491                                    matches!(cell, Cell::SpillCell { a, .. }
1492                                        if *a == (cell_reference.row, cell_reference.column))
1493                                })
1494                                .unwrap_or(false);
1495                            if is_own_spill {
1496                                let _ = ws.cell_clear_contents(r, c);
1497                            }
1498                        }
1499                    }
1500                }
1501                // mark cell as being evaluated
1502                self.cells.insert(key, CellState::Evaluating);
1503                let (node, _static_result) =
1504                    &self.parsed_formulas[cell_reference.sheet as usize][f as usize];
1505                let result = self.evaluate_node_in_context(&node.clone(), cell_reference);
1506
1507                // At this point a range needs to be transformed into an array
1508                let result = if let CalcResult::Range { left, right } = result {
1509                    if left.sheet == right.sheet
1510                        && left.row == right.row
1511                        && left.column == right.column
1512                    {
1513                        // it is a single cell range, we can just return the value of the cell
1514                        self.evaluate_cell(left)
1515                    } else {
1516                        let array_height = right.row - left.row + 1;
1517                        let array_width = right.column - left.column + 1;
1518                        let last_row = cell_reference.row + array_height - 1;
1519                        let last_col = cell_reference.column + array_width - 1;
1520                        if last_row > LAST_ROW || last_col > LAST_COLUMN {
1521                            CalcResult::new_error(
1522                                Error::SPILL,
1523                                cell_reference,
1524                                "Spill would exceed worksheet bounds".to_string(),
1525                            )
1526                        } else {
1527                            let array = self.evaluate_range(left, right);
1528                            CalcResult::Array(array)
1529                        }
1530                    }
1531                } else if matches!(result, CalcResult::Lambda(_)) {
1532                    CalcResult::new_error(
1533                        Error::CALC,
1534                        cell_reference,
1535                        "A LAMBDA was returned but not called".to_string(),
1536                    )
1537                } else {
1538                    result
1539                };
1540
1541                if let Err(e) = self.set_cells_with_result(cell_reference, &original_cell, &result)
1542                {
1543                    self.cells.insert(key, CellState::Evaluated);
1544                    // TODO: I _think_ this can never happen. Maybe we should  refactor things in a way that this is apparent
1545                    return CalcResult::new_error(Error::ERROR, cell_reference, e);
1546                };
1547
1548                // mark cell as evaluated
1549                self.cells.insert(key, CellState::Evaluated);
1550
1551                // return the result of the evaluation.
1552                match result {
1553                    CalcResult::Array(a) => {
1554                        // The cell ended up holding an array. Coerce it to a scalar so
1555                        // that dependents observe the same value `set_cells_with_result`
1556                        // wrote into the cell:
1557                        //   * Array formula anchor (CSE/Dynamic): return a[0][0] (the
1558                        //     anchor's "first cell" value, matching the existing model).
1559                        //   * Plain scalar formula: 1x1 -> unwrap to the single value;
1560                        //     larger -> `#VALUE!`. This must mirror the coercion in
1561                        //     `set_cells_with_result` so that dependents evaluated via
1562                        //     `ReferenceKind -> evaluate_cell` in the same recalculation
1563                        //     pass do not observe a different value than what is stored.
1564                        let is_array_formula = matches!(original_cell, Cell::ArrayFormula { .. });
1565                        let array_height = a.len();
1566                        let array_width = if array_height > 0 { a[0].len() } else { 0 };
1567                        if !is_array_formula && (array_width != 1 || array_height != 1) {
1568                            // Currently unreachable from normal user formulas: static
1569                            // analysis wraps array-returning subexpressions in scalar
1570                            // contexts in implicit intersection (`@`), which collapses
1571                            // them to a single value before they reach the cell. If we
1572                            // ever get here, static analysis or implicit-intersection
1573                            // insertion has regressed. Mirrors the assertion in
1574                            // `set_cells_with_result` so that the cell value and the
1575                            // value observed by in-pass dependents stay consistent.
1576                            debug_assert!(
1577                                false,
1578                                "Larger-than-1x1 array reached scalar-context cell \
1579                                 ({cell_reference:?}, {array_width}x{array_height}); \
1580                                 implicit intersection was expected to collapse it.",
1581                            );
1582                            CalcResult::new_error(
1583                                Error::VALUE,
1584                                cell_reference,
1585                                "Array result in scalar context".to_string(),
1586                            )
1587                        } else if array_height == 0 || array_width == 0 {
1588                            CalcResult::new_error(
1589                                Error::CALC,
1590                                cell_reference,
1591                                "Formula produced a zero-size array".to_string(),
1592                            )
1593                        } else {
1594                            match a[0][0] {
1595                                ArrayNode::Number(n) => CalcResult::Number(n),
1596                                ArrayNode::Boolean(b) => CalcResult::Boolean(b),
1597                                ArrayNode::String(ref s) => CalcResult::String(s.clone()),
1598                                ArrayNode::Error(ref error) => {
1599                                    let message = error.to_localized_error_string(self.language);
1600                                    CalcResult::new_error(error.clone(), cell_reference, message)
1601                                }
1602                                ArrayNode::Empty => CalcResult::EmptyCell,
1603                            }
1604                        }
1605                    }
1606                    _ => result,
1607                }
1608            }
1609            None => self.get_cell_value(&original_cell, cell_reference),
1610        }
1611    }
1612
1613    pub(crate) fn get_sheet_index_by_name(&self, name: &str) -> Option<u32> {
1614        let worksheets = &self.workbook.worksheets;
1615        for (index, worksheet) in worksheets.iter().enumerate() {
1616            if worksheet.get_name().to_uppercase() == name.to_uppercase() {
1617                return Some(index as u32);
1618            }
1619        }
1620        None
1621    }
1622
1623    /// Returns a model from an internal binary representation of a workbook
1624    ///
1625    /// # Examples
1626    ///
1627    /// ```rust
1628    /// # use ironcalc_base::Model;
1629    /// # use ironcalc_base::cell::CellValue;
1630    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1631    /// let mut model = Model::new_empty("model", "en", "UTC", "en")?;
1632    /// model.set_user_input(0, 1, 1, "Stella!".to_string());
1633    /// let model2 = Model::from_bytes(&model.to_bytes(), "en")?;
1634    /// assert_eq!(
1635    ///     model2.get_cell_value_by_index(0, 1, 1),
1636    ///     Ok(CellValue::String("Stella!".to_string()))
1637    /// );
1638    /// # Ok(())
1639    /// # }
1640    /// ```
1641    ///
1642    /// See also:
1643    /// * [Model::to_bytes]
1644    pub fn from_bytes(s: &[u8], language_id: &'a str) -> Result<Model<'a>, String> {
1645        let workbook: Workbook =
1646            bitcode::decode(s).map_err(|e| format!("Error parsing workbook: {e}"))?;
1647        Model::from_workbook(workbook, language_id)
1648    }
1649
1650    /// Returns a model from a Workbook object
1651    ///
1652    /// # Examples
1653    ///
1654    /// ```rust
1655    /// # use ironcalc_base::Model;
1656    /// # use ironcalc_base::cell::CellValue;
1657    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1658    /// let mut model = Model::new_empty("model", "en", "UTC", "en")?;
1659    /// model.set_user_input(0, 1, 1, "Stella!".to_string());
1660    /// let model2 = Model::from_workbook(model.workbook, "en")?;
1661    /// assert_eq!(
1662    ///     model2.get_cell_value_by_index(0, 1, 1),
1663    ///     Ok(CellValue::String("Stella!".to_string()))
1664    /// );
1665    /// # Ok(())
1666    /// # }
1667    /// ```
1668    pub fn from_workbook(workbook: Workbook, language_id: &str) -> Result<Model<'_>, String> {
1669        let parsed_formulas = Vec::new();
1670        let worksheets = &workbook.worksheets;
1671
1672        let worksheet_names = worksheets.iter().map(|s| s.get_name()).collect();
1673
1674        let defined_names = workbook.get_defined_names_with_scope();
1675        // add all tables
1676        // let mut tables = Vec::new();
1677        // for worksheet in worksheets {
1678        //     let mut tables_in_sheet = HashMap::new();
1679        //     for table in &worksheet.tables {
1680        //         tables_in_sheet.insert(table.name.clone(), table.clone());
1681        //     }
1682        //     tables.push(tables_in_sheet);
1683        // }
1684
1685        let cells = HashMap::new();
1686        let locale =
1687            get_locale(&workbook.settings.locale).map_err(|_| "Invalid locale".to_string())?;
1688        let tz = Tz::parse(&workbook.settings.tz)?;
1689
1690        let language = match get_language(language_id) {
1691            Ok(lang) => lang,
1692            Err(_) => return Err("Invalid language".to_string()),
1693        };
1694        let parser = Parser::new(
1695            worksheet_names,
1696            defined_names,
1697            workbook.tables.clone(),
1698            locale,
1699            language,
1700        );
1701        let mut shared_strings = HashMap::new();
1702        for (index, s) in workbook.shared_strings.iter().enumerate() {
1703            shared_strings.insert(s.to_string(), index);
1704        }
1705
1706        let mut model = Model {
1707            workbook,
1708            parsed_formulas,
1709            shared_strings,
1710            parsed_defined_names: HashMap::new(),
1711            parser,
1712            cells,
1713            language,
1714            locale,
1715            tz,
1716            view_id: 0,
1717            variable_stack: HashMap::new(),
1718            last_variable_id: 0,
1719            lambdas: HashMap::new(),
1720            last_lambda_id: 0,
1721            spill_cells: Vec::new(),
1722            support: HashMap::new(),
1723            cf_cache: HashMap::new(),
1724            links: HashMap::new(),
1725        };
1726
1727        model.parse_formulas();
1728        model.parse_defined_names();
1729        model.evaluate_conditional_formatting();
1730
1731        Ok(model)
1732    }
1733
1734    /// Parses a reference like "Sheet1!B4" into {0, 2, 4}
1735    ///
1736    /// # Examples
1737    ///
1738    /// ```rust
1739    /// # use ironcalc_base::Model;
1740    /// # use ironcalc_base::expressions::types::CellReferenceIndex;
1741    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1742    /// let mut model = Model::new_empty("model", "en", "UTC", "en")?;
1743    /// model.set_user_input(0, 1, 1, "Stella!".to_string());
1744    /// let reference = model.parse_reference("Sheet1!D40");
1745    /// assert_eq!(reference, Some(CellReferenceIndex {sheet: 0, row: 40, column: 4}));
1746    /// # Ok(())
1747    /// # }
1748    /// ```
1749    pub fn parse_reference(&self, s: &str) -> Option<CellReferenceIndex> {
1750        let bytes = s.as_bytes();
1751        let mut sheet_name = "".to_string();
1752        let mut column = "".to_string();
1753        let mut row = "".to_string();
1754        let mut state = "sheet"; // "sheet", "col", "row"
1755        for &byte in bytes {
1756            match state {
1757                "sheet" => {
1758                    if byte == b'!' {
1759                        state = "col"
1760                    } else {
1761                        sheet_name.push(byte as char);
1762                    }
1763                }
1764                "col" => {
1765                    if byte.is_ascii_alphabetic() {
1766                        column.push(byte as char);
1767                    } else {
1768                        state = "row";
1769                        row.push(byte as char);
1770                    }
1771                }
1772                _ => {
1773                    row.push(byte as char);
1774                }
1775            }
1776        }
1777        let sheet = self.get_sheet_index_by_name(&sheet_name)?;
1778        let row = match row.parse::<i32>() {
1779            Ok(r) => r,
1780            Err(_) => return None,
1781        };
1782        if !(1..=constants::LAST_ROW).contains(&row) {
1783            return None;
1784        }
1785
1786        let column = match utils::column_to_number(&column) {
1787            Ok(column) => {
1788                if is_valid_column_number(column) {
1789                    column
1790                } else {
1791                    return None;
1792                }
1793            }
1794            Err(_) => return None,
1795        };
1796
1797        Some(CellReferenceIndex { sheet, row, column })
1798    }
1799
1800    /// Moves the formula `value` from `source` (in `area`) to `target`.
1801    ///
1802    /// # Examples
1803    ///
1804    /// ```rust
1805    /// # use ironcalc_base::Model;
1806    /// # use ironcalc_base::expressions::types::{Area, CellReferenceIndex};
1807    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1808    /// let mut model = Model::new_empty("model", "en", "UTC", "en")?;
1809    /// let source = CellReferenceIndex { sheet: 0, row: 3, column: 1};
1810    /// let target = CellReferenceIndex { sheet: 0, row: 50, column: 1};
1811    /// let area = Area { sheet: 0, row: 1, column: 1, width: 5, height: 4};
1812    /// let result = model.move_cell_value_to_area("=B1", &source, &target, &area)?;
1813    /// assert_eq!(&result, "=B48");
1814    /// # Ok(())
1815    /// # }
1816    /// ```
1817    ///
1818    /// See also:
1819    /// * [Model::extend_to()]
1820    /// * [Model::extend_copied_value()]
1821    pub fn move_cell_value_to_area(
1822        &mut self,
1823        value: &str,
1824        source: &CellReferenceIndex,
1825        target: &CellReferenceIndex,
1826        area: &Area,
1827    ) -> Result<String, String> {
1828        let source_sheet_name = self
1829            .workbook
1830            .worksheet(source.sheet)
1831            .map_err(|e| format!("Could not find source worksheet: {e}"))?
1832            .get_name();
1833        if source.sheet != area.sheet {
1834            return Err("Source and area are in different sheets".to_string());
1835        }
1836        if source.row < area.row || source.row >= area.row + area.height {
1837            return Err("Source is outside the area".to_string());
1838        }
1839        if source.column < area.column || source.column >= area.column + area.width {
1840            return Err("Source is outside the area".to_string());
1841        }
1842        let target_sheet_name = self
1843            .workbook
1844            .worksheet(target.sheet)
1845            .map_err(|e| format!("Could not find target worksheet: {e}"))?
1846            .get_name();
1847        if let Some(formula) = self.formula_without_prefix(value) {
1848            let cell_reference = CellReferenceRC {
1849                sheet: source_sheet_name.to_owned(),
1850                row: source.row,
1851                column: source.column,
1852            };
1853            let formula_str = move_formula(
1854                &self.parser.parse(formula, &cell_reference),
1855                &MoveContext {
1856                    source_sheet_name: &source_sheet_name,
1857                    row: source.row,
1858                    column: source.column,
1859                    area,
1860                    target_sheet_name: &target_sheet_name,
1861                    row_delta: target.row - source.row,
1862                    column_delta: target.column - source.column,
1863                },
1864                self.locale,
1865                self.language,
1866            );
1867            Ok(format!("={formula_str}"))
1868        } else {
1869            Ok(value.to_string())
1870        }
1871    }
1872
1873    /// 'Extends' the value from cell (`sheet`, `row`, `column`) to (`target_row`, `target_column`) in the same sheet
1874    ///
1875    /// # Examples
1876    ///
1877    /// ```rust
1878    /// # use ironcalc_base::Model;
1879    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1880    /// let mut model = Model::new_empty("model", "en", "UTC", "en")?;
1881    /// let (sheet, row, column) = (0, 1, 1);
1882    /// model.set_user_input(sheet, row, column, "=B1*D4".to_string());
1883    /// let (target_row, target_column) = (30, 1);
1884    /// let result = model.extend_to(sheet, row, column, target_row, target_column)?;
1885    /// assert_eq!(&result, "=B30*D33");
1886    /// # Ok(())
1887    /// # }
1888    /// ```
1889    ///
1890    /// See also:
1891    /// * [Model::extend_copied_value()]
1892    /// * [Model::move_cell_value_to_area()]
1893    pub fn extend_to(
1894        &self,
1895        sheet: u32,
1896        row: i32,
1897        column: i32,
1898        target_row: i32,
1899        target_column: i32,
1900    ) -> Result<String, String> {
1901        let cell = self.workbook.worksheet(sheet)?.cell(row, column);
1902        let result = match cell {
1903            Some(cell) => match cell.get_formula() {
1904                None => cell.get_localized_text(
1905                    &self.workbook.shared_strings,
1906                    self.locale,
1907                    self.language,
1908                ),
1909                Some(i) => {
1910                    let (formula, _static_result) =
1911                        &self.parsed_formulas[sheet as usize][i as usize];
1912                    let cell_ref = CellReferenceRC {
1913                        sheet: self.workbook.worksheets[sheet as usize].get_name(),
1914                        row: target_row,
1915                        column: target_column,
1916                    };
1917                    format!(
1918                        "={}",
1919                        to_localized_string(formula, &cell_ref, self.locale, self.language)
1920                    )
1921                }
1922            },
1923            None => "".to_string(),
1924        };
1925        Ok(result)
1926    }
1927
1928    /// 'Extends' the formula `value` from `source` to `target`
1929    ///
1930    /// # Examples
1931    ///
1932    /// ```rust
1933    /// # use ironcalc_base::Model;
1934    /// # use ironcalc_base::expressions::types::CellReferenceIndex;
1935    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1936    /// let mut model = Model::new_empty("model", "en", "UTC", "en")?;
1937    /// let source = CellReferenceIndex {sheet: 0, row: 1, column: 1};
1938    /// let target = CellReferenceIndex {sheet: 0, row: 30, column: 1};
1939    /// let result = model.extend_copied_value("=B1*D4", &source, &target)?;
1940    /// assert_eq!(&result, "=B30*D33");
1941    /// # Ok(())
1942    /// # }
1943    /// ```
1944    ///
1945    /// See also:
1946    /// * [Model::extend_to()]
1947    /// * [Model::move_cell_value_to_area()]
1948    pub fn extend_copied_value(
1949        &mut self,
1950        value: &str,
1951        source: &CellReferenceIndex,
1952        target: &CellReferenceIndex,
1953    ) -> Result<String, String> {
1954        let source_sheet_name = match self.workbook.worksheets.get(source.sheet as usize) {
1955            Some(ws) => ws.get_name(),
1956            None => {
1957                return Err("Invalid worksheet index".to_owned());
1958            }
1959        };
1960        let target_sheet_name = match self.workbook.worksheets.get(target.sheet as usize) {
1961            Some(ws) => ws.get_name(),
1962            None => {
1963                return Err("Invalid worksheet index".to_owned());
1964            }
1965        };
1966
1967        if let Some(formula_str) = self.formula_without_prefix(value) {
1968            let cell_reference = CellReferenceRC {
1969                sheet: source_sheet_name.to_string(),
1970                row: source.row,
1971                column: source.column,
1972            };
1973            let formula = &self.parser.parse(formula_str, &cell_reference);
1974            let cell_reference = CellReferenceRC {
1975                sheet: target_sheet_name,
1976                row: target.row,
1977                column: target.column,
1978            };
1979            return Ok(format!(
1980                "={}",
1981                to_localized_string(formula, &cell_reference, self.locale, self.language)
1982            ));
1983        }
1984        Ok(value.to_string())
1985    }
1986
1987    /// Returns the formula in (`sheet`, `row`, `column`) if any
1988    ///
1989    /// # Examples
1990    ///
1991    /// ```rust
1992    /// # use ironcalc_base::Model;
1993    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1994    /// let mut model = Model::new_empty("model", "en", "UTC", "en")?;
1995    /// let (sheet, row, column) = (0, 1, 1);
1996    /// model.set_user_input(sheet, row, column, "=SIN(B1*C3)+1".to_string());
1997    /// model.evaluate();
1998    /// let result = model.get_cell_formula(sheet, row, column)?;
1999    /// assert_eq!(result, Some("=SIN(B1*C3)+1".to_string()));
2000    /// # Ok(())
2001    /// # }
2002    /// ```
2003    ///
2004    /// See also:
2005    /// * [Model::get_localized_cell_content()]
2006    pub fn get_cell_formula(
2007        &self,
2008        sheet: u32,
2009        row: i32,
2010        column: i32,
2011    ) -> Result<Option<String>, String> {
2012        let worksheet = self.workbook.worksheet(sheet)?;
2013        match worksheet.cell(row, column) {
2014            Some(cell) => match cell.get_formula() {
2015                Some(formula_index) => {
2016                    let (formula, _static_result) = &self
2017                        .parsed_formulas
2018                        .get(sheet as usize)
2019                        .ok_or("missing sheet")?
2020                        .get(formula_index as usize)
2021                        .ok_or("missing formula")?;
2022                    let cell_ref = CellReferenceRC {
2023                        sheet: worksheet.get_name(),
2024                        row,
2025                        column,
2026                    };
2027                    Ok(Some(format!(
2028                        "={}",
2029                        to_localized_string(formula, &cell_ref, self.locale, self.language)
2030                    )))
2031                }
2032                None => Ok(None),
2033            },
2034            None => Ok(None),
2035        }
2036    }
2037
2038    /// Returns the text for the formula in (`sheet`, `row`, `column`) in English if any
2039    ///
2040    /// See also:
2041    /// * [Model::get_localized_cell_content()]
2042    pub(crate) fn get_english_cell_formula(
2043        &self,
2044        sheet: u32,
2045        row: i32,
2046        column: i32,
2047    ) -> Result<Option<String>, String> {
2048        let worksheet = self.workbook.worksheet(sheet)?;
2049        match worksheet.cell(row, column) {
2050            Some(cell) => match cell.get_formula() {
2051                Some(formula_index) => {
2052                    let (formula, _static_result) = &self
2053                        .parsed_formulas
2054                        .get(sheet as usize)
2055                        .ok_or("missing sheet")?
2056                        .get(formula_index as usize)
2057                        .ok_or("missing formula")?;
2058                    let cell_ref = CellReferenceRC {
2059                        sheet: worksheet.get_name(),
2060                        row,
2061                        column,
2062                    };
2063                    let language_en = get_default_language();
2064                    Ok(Some(format!(
2065                        "={}",
2066                        to_localized_string(formula, &cell_ref, self.locale, language_en)
2067                    )))
2068                }
2069                None => Ok(None),
2070            },
2071            None => Ok(None),
2072        }
2073    }
2074
2075    /// Updates the value of a cell with some text
2076    /// It does not change the style unless needs to add "quoting"
2077    ///
2078    /// # Examples
2079    ///
2080    /// ```rust
2081    /// # use ironcalc_base::Model;
2082    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
2083    /// let mut model = Model::new_empty("model", "en", "UTC", "en")?;
2084    /// let (sheet, row, column) = (0, 1, 1);
2085    /// model.set_user_input(sheet, row, column, "Hello!".to_string())?;
2086    /// assert_eq!(model.get_localized_cell_content(sheet, row, column)?, "Hello!".to_string());
2087    ///
2088    /// model.update_cell_with_text(sheet, row, column, "Goodbye!")?;
2089    /// assert_eq!(model.get_localized_cell_content(sheet, row, column)?, "Goodbye!".to_string());
2090    /// # Ok(())
2091    /// # }
2092    /// ```
2093    ///
2094    /// See also:
2095    /// * [Model::set_user_input()]
2096    /// * [Model::update_cell_with_number()]
2097    /// * [Model::update_cell_with_bool()]
2098    /// * [Model::update_cell_with_formula()]
2099    pub fn update_cell_with_text(
2100        &mut self,
2101        sheet: u32,
2102        row: i32,
2103        column: i32,
2104        value: &str,
2105    ) -> Result<(), String> {
2106        let style_index = self.get_cell_style_index(sheet, row, column)?;
2107        let new_style_index;
2108        if common::value_needs_quoting(value, self.language) {
2109            new_style_index = self
2110                .workbook
2111                .styles
2112                .get_style_with_quote_prefix(style_index)?;
2113        } else if self.workbook.styles.style_is_quote_prefix(style_index) {
2114            new_style_index = self
2115                .workbook
2116                .styles
2117                .get_style_without_quote_prefix(style_index)?;
2118        } else {
2119            new_style_index = style_index;
2120        }
2121
2122        self.set_cell_with_string(sheet, row, column, value, new_style_index)
2123    }
2124
2125    /// Updates the value of a cell with a boolean value
2126    /// It does not change the style
2127    ///
2128    /// # Examples
2129    ///
2130    /// ```rust
2131    /// # use ironcalc_base::Model;
2132    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
2133    /// let mut model = Model::new_empty("model", "en", "UTC", "en")?;
2134    /// let (sheet, row, column) = (0, 1, 1);
2135    /// model.set_user_input(sheet, row, column, "TRUE".to_string())?;
2136    /// assert_eq!(model.get_localized_cell_content(sheet, row, column)?, "TRUE".to_string());
2137    ///
2138    /// model.update_cell_with_bool(sheet, row, column, false)?;
2139    /// assert_eq!(model.get_localized_cell_content(sheet, row, column)?, "FALSE".to_string());
2140    /// # Ok(())
2141    /// # }
2142    /// ```
2143    ///
2144    /// See also:
2145    /// * [Model::set_user_input()]
2146    /// * [Model::update_cell_with_number()]
2147    /// * [Model::update_cell_with_text()]
2148    /// * [Model::update_cell_with_formula()]
2149    pub fn update_cell_with_bool(
2150        &mut self,
2151        sheet: u32,
2152        row: i32,
2153        column: i32,
2154        value: bool,
2155    ) -> Result<(), String> {
2156        let style_index = self.get_cell_style_index(sheet, row, column)?;
2157        let new_style_index = if self.workbook.styles.style_is_quote_prefix(style_index) {
2158            self.workbook
2159                .styles
2160                .get_style_without_quote_prefix(style_index)?
2161        } else {
2162            style_index
2163        };
2164        self.set_cell_with_boolean(sheet, row, column, value, new_style_index)
2165    }
2166
2167    /// Updates the value of a cell with a number
2168    /// It does not change the style
2169    ///
2170    /// # Examples
2171    ///
2172    /// ```rust
2173    /// # use ironcalc_base::Model;
2174    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
2175    /// let mut model = Model::new_empty("model", "en", "UTC", "en")?;
2176    /// let (sheet, row, column) = (0, 1, 1);
2177    /// model.set_user_input(sheet, row, column, "42".to_string())?;
2178    /// assert_eq!(model.get_localized_cell_content(sheet, row, column)?, "42".to_string());
2179    ///
2180    /// model.update_cell_with_number(sheet, row, column, 23.0)?;
2181    /// assert_eq!(model.get_localized_cell_content(sheet, row, column)?, "23".to_string());
2182    /// # Ok(())
2183    /// # }
2184    /// ```
2185    ///
2186    /// See also:
2187    /// * [Model::set_user_input()]
2188    /// * [Model::update_cell_with_text()]
2189    /// * [Model::update_cell_with_bool()]
2190    /// * [Model::update_cell_with_formula()]
2191    pub fn update_cell_with_number(
2192        &mut self,
2193        sheet: u32,
2194        row: i32,
2195        column: i32,
2196        value: f64,
2197    ) -> Result<(), String> {
2198        let style_index = self.get_cell_style_index(sheet, row, column)?;
2199        let new_style_index = if self.workbook.styles.style_is_quote_prefix(style_index) {
2200            self.workbook
2201                .styles
2202                .get_style_without_quote_prefix(style_index)?
2203        } else {
2204            style_index
2205        };
2206        self.set_cell_with_number(sheet, row, column, value, new_style_index)
2207    }
2208
2209    /// Updates the formula of given cell
2210    /// It does not change the style unless needs to add "quoting"
2211    /// Expects the formula to start with "="
2212    ///
2213    /// # Examples
2214    ///
2215    /// ```rust
2216    /// # use ironcalc_base::Model;
2217    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
2218    /// let mut model = Model::new_empty("model", "en", "UTC", "en")?;
2219    /// let (sheet, row, column) = (0, 1, 1);
2220    /// model.set_user_input(sheet, row, column, "=A2*2".to_string())?;
2221    /// model.evaluate();
2222    /// assert_eq!(model.get_localized_cell_content(sheet, row, column)?, "=A2*2".to_string());
2223    ///
2224    /// model.update_cell_with_formula(sheet, row, column, "=A3*2".to_string())?;
2225    /// model.evaluate();
2226    /// assert_eq!(model.get_localized_cell_content(sheet, row, column)?, "=A3*2".to_string());
2227    /// # Ok(())
2228    /// # }
2229    /// ```
2230    ///
2231    /// See also:
2232    /// * [Model::set_user_input()]
2233    /// * [Model::update_cell_with_number()]
2234    /// * [Model::update_cell_with_bool()]
2235    /// * [Model::update_cell_with_text()]
2236    pub fn update_cell_with_formula(
2237        &mut self,
2238        sheet: u32,
2239        row: i32,
2240        column: i32,
2241        formula: String,
2242    ) -> Result<(), String> {
2243        let mut style_index = self.get_cell_style_index(sheet, row, column)?;
2244        if self.workbook.styles.style_is_quote_prefix(style_index) {
2245            style_index = self
2246                .workbook
2247                .styles
2248                .get_style_without_quote_prefix(style_index)?;
2249        }
2250
2251        if let Some(new_formula) = self.formula_without_prefix(&formula) {
2252            self.set_cell_with_formula(sheet, row, column, new_formula, style_index)?;
2253            Ok(())
2254        } else {
2255            Err(format!("\"{formula}\" is not a valid formula"))
2256        }
2257    }
2258
2259    // If we are writing in (sheet, row, column). If it is:
2260    // - A single cell => do nothing
2261    // - Part of an array formula => we bail
2262    // - Anchor of an array formula => we delete the formula and we clear the spill
2263    // - Part of a dynamic array formula => we delete the formula and we clear the spill
2264    // - Anchor of a dynamic array formula
2265    //     => we clear the spill and we set an unevaluated dynamic formula.
2266    fn prepare_cell_for_user_input(
2267        &mut self,
2268        sheet: u32,
2269        row: i32,
2270        column: i32,
2271    ) -> Result<(), String> {
2272        match self.get_cell_structure(sheet, row, column)? {
2273            CellStructure::SingleCell => {
2274                // noop
2275            }
2276            CellStructure::ArrayFormula { range } => {
2277                // We cannot write in a cell that is part of an array formula
2278                let (width, height) = range;
2279                if width > 1 || height > 1 {
2280                    return Err(
2281                        "Cannot write in a cell that is part of an array formula".to_string()
2282                    );
2283                }
2284            }
2285            CellStructure::DynamicFormula { range } => {
2286                // clear the spill of the dynamic formula
2287                let (width, height) = range;
2288                let ws = self.workbook.worksheet_mut(sheet)?;
2289                for r in row..row + height {
2290                    for c in column..column + width {
2291                        // We ignore errors here
2292                        let _ = ws.cell_clear_contents(r, c);
2293                    }
2294                }
2295            }
2296            CellStructure::SpillArray { .. } => {
2297                return Err("Cannot write in a cell that is part of an array formula".to_string());
2298            }
2299            CellStructure::SpillDynamic { anchor, range } => {
2300                // It is part of a dynamic array formula, but it is not the anchor.
2301                // We can write in it but we need to clear the spill and reset the anchor
2302                // to an unevaluated dynamic formula so it will re-spill on next evaluate().
2303                let (anchor_row, anchor_column) = anchor;
2304                let (width, height) = range;
2305                let ws = self.workbook.worksheet_mut(sheet)?;
2306                // Extract formula index and style from the anchor before mutating
2307                let (formula_index, anchor_style) = {
2308                    let anchor_cell = ws
2309                        .cell(anchor_row, anchor_column)
2310                        .ok_or_else(|| "Dynamic formula anchor not found".to_string())?;
2311                    let fi = anchor_cell
2312                        .get_formula()
2313                        .ok_or_else(|| "Dynamic formula anchor has no formula".to_string())?;
2314                    let s = anchor_cell.get_style();
2315                    (fi, s)
2316                };
2317                ws.set_cell_with_dynamic_formula(
2318                    anchor_row,
2319                    anchor_column,
2320                    formula_index,
2321                    anchor_style,
2322                    1,
2323                    1,
2324                )?;
2325                for r in anchor_row..anchor_row + height {
2326                    for c in anchor_column..anchor_column + width {
2327                        if r == anchor_row && c == anchor_column {
2328                            continue;
2329                        }
2330                        // We ignore errors here
2331                        let _ = ws.cell_clear_contents(r, c);
2332                    }
2333                }
2334            }
2335        };
2336        Ok(())
2337    }
2338
2339    /// Sets a cell parametrized by (`sheet`, `row`, `column`) with `value`.
2340    ///
2341    /// This mimics a user entering a value on a cell.
2342    ///
2343    /// If you enter a currency `$100` it will set as a number and update the style
2344    ///  Note that for currencies/percentage there is only one possible style
2345    ///  The value is always a string, so we need to try to cast it into numbers/booleans/errors
2346    ///
2347    /// # Examples
2348    ///
2349    /// ```rust
2350    /// # use ironcalc_base::Model;
2351    /// # use ironcalc_base::cell::CellValue;
2352    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
2353    /// let mut model = Model::new_empty("model", "en", "UTC", "en")?;
2354    /// model.set_user_input(0, 1, 1, "100$".to_string());
2355    /// model.set_user_input(0, 2, 1, "125$".to_string());
2356    /// model.set_user_input(0, 3, 1, "-10$".to_string());
2357    /// model.set_user_input(0, 1, 2, "=SUM(A:A)".to_string());
2358    /// model.evaluate();
2359    /// assert_eq!(model.get_cell_value_by_index(0, 1, 2), Ok(CellValue::Number(215.0)));
2360    /// assert_eq!(model.get_formatted_cell_value(0, 1, 2), Ok("215$".to_string()));
2361    /// # Ok(())
2362    /// # }
2363    /// ```
2364    ///
2365    /// See also:
2366    /// * [Model::update_cell_with_formula()]
2367    /// * [Model::update_cell_with_number()]
2368    /// * [Model::update_cell_with_bool()]
2369    /// * [Model::update_cell_with_text()]
2370    pub fn set_user_input(
2371        &mut self,
2372        sheet: u32,
2373        row: i32,
2374        column: i32,
2375        value: String,
2376    ) -> Result<(), String> {
2377        // first we make sure we can write in the cell and clear the spills.
2378        self.prepare_cell_for_user_input(sheet, row, column)?;
2379        if value.is_empty() {
2380            // If the value is empty we just clear the cell.
2381            // Deleting the contents of a cell also removes its link.
2382            let ws = self.workbook.worksheet_mut(sheet)?;
2383            ws.cell_clear_contents(row, column)?;
2384            ws.links.remove(&(row, column));
2385            return Ok(());
2386        }
2387
2388        // If value starts with "'" then we force the style to be quote_prefix
2389        let style_index = self.get_cell_style_index(sheet, row, column)?;
2390        if let Some(new_value) = value.strip_prefix('\'') {
2391            let new_style = self
2392                .workbook
2393                .styles
2394                .get_style_with_quote_prefix(style_index)?;
2395            self.set_cell_with_string(sheet, row, column, new_value, new_style)?;
2396        } else {
2397            let mut new_style_index = style_index;
2398            if self.workbook.styles.style_is_quote_prefix(style_index) {
2399                new_style_index = self
2400                    .workbook
2401                    .styles
2402                    .get_style_without_quote_prefix(style_index)?;
2403            }
2404            if let Some(formula) = self.formula_without_prefix(&value) {
2405                let formula_index =
2406                    self.set_cell_with_formula(sheet, row, column, formula, new_style_index)?;
2407                // Update the style if needed
2408                let cell = CellReferenceIndex { sheet, row, column };
2409                let (parsed_formula, _static_result) =
2410                    &self.parsed_formulas[sheet as usize][formula_index as usize];
2411                if let Some(units) = self.compute_node_units(parsed_formula, &cell) {
2412                    let new_style_index = self
2413                        .workbook
2414                        .styles
2415                        .get_style_with_format(new_style_index, &units.get_num_fmt())?;
2416                    let style = self.workbook.styles.get_style(new_style_index)?;
2417                    self.set_cell_style(sheet, row, column, &style)?;
2418                }
2419            } else {
2420                // The list of currencies is '$', '€' and the local currency
2421                let mut currencies = vec!["$", "€"];
2422                let currency = &self.locale.currency.symbol;
2423                if !currencies.iter().any(|e| e == currency) {
2424                    currencies.push(currency);
2425                }
2426
2427                //  We try to parse as number
2428                if let Ok((v, number_format)) =
2429                    parse_formatted_number(&value, &currencies, self.locale)
2430                {
2431                    if let Some(num_fmt) = number_format {
2432                        // Should not apply the format in the following cases:
2433                        // - we assign a date to already date-formatted cell
2434                        let should_apply_format = !(is_likely_date_number_format(
2435                            &self.workbook.styles.get_style(new_style_index)?.num_fmt,
2436                        ) && is_likely_date_number_format(&num_fmt));
2437                        if should_apply_format {
2438                            new_style_index = self
2439                                .workbook
2440                                .styles
2441                                .get_style_with_format(new_style_index, &num_fmt)?;
2442                        }
2443                    }
2444                    let worksheet = self.workbook.worksheet_mut(sheet)?;
2445                    worksheet.set_cell_with_number(row, column, v, new_style_index)?;
2446                    return Ok(());
2447                }
2448                // We try to parse as boolean
2449                if let Ok(v) = value.to_lowercase().parse::<bool>() {
2450                    let worksheet = self.workbook.worksheet_mut(sheet)?;
2451                    worksheet.set_cell_with_boolean(row, column, v, new_style_index)?;
2452                    return Ok(());
2453                }
2454                // Check is it is error value
2455                let upper = value.to_uppercase();
2456                let worksheet = self.workbook.worksheet_mut(sheet)?;
2457                match get_error_by_name(&upper, self.language) {
2458                    Some(error) => {
2459                        worksheet.set_cell_with_error(row, column, error, new_style_index)?;
2460                    }
2461                    None => {
2462                        self.set_cell_with_string(sheet, row, column, &value, new_style_index)?;
2463                        // If the input looks like an URL or an email address a link is
2464                        // attached to the cell, the same way other inputs change the
2465                        // number format. Note that a quote prefix prevents this.
2466                        self.auto_link_cell(sheet, row, column, &value)?;
2467                    }
2468                }
2469            }
2470        }
2471        Ok(())
2472    }
2473
2474    /// Sets an array formula in an area (CSE formula)
2475    pub fn set_user_array_formula(
2476        &mut self,
2477        sheet: u32,
2478        row: i32,
2479        column: i32,
2480        width: i32,
2481        height: i32,
2482        value: &str,
2483    ) -> Result<(), String> {
2484        self.prepare_cell_for_user_input(sheet, row, column)?;
2485        // If value starts with "'" then we force the style to be quote_prefix
2486        let style_index = self.get_cell_style_index(sheet, row, column)?;
2487        if value.strip_prefix('\'').is_none() {
2488            let mut new_style_index = style_index;
2489            if self.workbook.styles.style_is_quote_prefix(style_index) {
2490                new_style_index = self
2491                    .workbook
2492                    .styles
2493                    .get_style_without_quote_prefix(style_index)?;
2494            }
2495            if let Some(formula) = value.strip_prefix('=') {
2496                // It is a formula, we mark it as an array formulas and fill the "spills" with placeholders
2497                let formula_index = self.set_cell_with_array_formula(
2498                    sheet,
2499                    row,
2500                    column,
2501                    formula,
2502                    new_style_index,
2503                    width,
2504                    height,
2505                )?;
2506
2507                // Update the style if needed
2508                let cell = CellReferenceIndex { sheet, row, column };
2509                let (parsed_formula, _static_result) =
2510                    &self.parsed_formulas[sheet as usize][formula_index as usize];
2511
2512                if let Some(units) = self.compute_node_units(parsed_formula, &cell) {
2513                    let new_style_index = self
2514                        .workbook
2515                        .styles
2516                        .get_style_with_format(new_style_index, &units.get_num_fmt())?;
2517                    let style = self.workbook.styles.get_style(new_style_index)?;
2518                    self.set_cell_style(sheet, row, column, &style)?;
2519                }
2520                // Update the "spill" area with placeholders
2521                for r in row..row + height {
2522                    for c in column..column + width {
2523                        if r == row && c == column {
2524                            continue;
2525                        }
2526                        let mut new_style_index_spill = self.get_cell_style_index(sheet, r, c)?;
2527                        if self
2528                            .workbook
2529                            .styles
2530                            .style_is_quote_prefix(new_style_index_spill)
2531                        {
2532                            new_style_index_spill = self
2533                                .workbook
2534                                .styles
2535                                .get_style_without_quote_prefix(new_style_index_spill)?;
2536                        }
2537
2538                        self.set_cell_with_string(sheet, r, c, "", new_style_index_spill)?;
2539                    }
2540                }
2541                return Ok(());
2542            }
2543        }
2544        // just use set user input on every cell
2545        for r in row..row + height {
2546            for c in column..column + width {
2547                self.set_user_input(sheet, r, c, value.to_string())?;
2548            }
2549        }
2550
2551        Ok(())
2552    }
2553
2554    pub(crate) fn get_cell_structure(
2555        &self,
2556        sheet: u32,
2557        row: i32,
2558        column: i32,
2559    ) -> Result<CellStructure, String> {
2560        let worksheet = self.workbook.worksheet(sheet)?;
2561        worksheet.get_cell_structure(row, column)
2562    }
2563
2564    fn set_cell_with_formula(
2565        &mut self,
2566        sheet: u32,
2567        row: i32,
2568        column: i32,
2569        formula: &str,
2570        style: i32,
2571    ) -> Result<i32, String> {
2572        let worksheet = self.workbook.worksheet_mut(sheet)?;
2573        let cell_reference = CellReferenceRC {
2574            sheet: worksheet.get_name(),
2575            row,
2576            column,
2577        };
2578        let shared_formulas = &mut worksheet.shared_formulas;
2579        let mut parsed_formula = self.parser.parse(formula, &cell_reference);
2580        // If the formula fails to parse try adding a parenthesis
2581        // SUM(A1:A3  => SUM(A1:A3)
2582        if let Node::ParseErrorKind { .. } = parsed_formula {
2583            let new_parsed_formula = self.parser.parse(&format!("{formula})"), &cell_reference);
2584            match new_parsed_formula {
2585                Node::ParseErrorKind { .. } => {}
2586                _ => parsed_formula = new_parsed_formula,
2587            }
2588        }
2589        let static_result = run_static_analysis_on_node(&parsed_formula);
2590        let is_dynamic = !matches!(static_result, StaticResult::Scalar);
2591
2592        let s = to_rc_format(&parsed_formula);
2593        let mut formula_index: i32 = -1;
2594        if let Some(index) = shared_formulas.iter().position(|x| x == &s) {
2595            formula_index = index as i32;
2596        }
2597        if formula_index == -1 {
2598            shared_formulas.push(s);
2599            self.parsed_formulas[sheet as usize].push((parsed_formula, static_result));
2600            formula_index = (shared_formulas.len() as i32) - 1;
2601        }
2602        if is_dynamic {
2603            worksheet.set_cell_with_dynamic_formula(row, column, formula_index, style, 1, 1)?;
2604        } else {
2605            worksheet.set_cell_with_formula(row, column, formula_index, style)?;
2606        }
2607        Ok(formula_index)
2608    }
2609
2610    // FIXME
2611    #[allow(clippy::too_many_arguments)]
2612    pub(crate) fn set_cell_with_array_formula(
2613        &mut self,
2614        sheet: u32,
2615        row: i32,
2616        column: i32,
2617        formula: &str,
2618        style: i32,
2619        width: i32,
2620        height: i32,
2621    ) -> Result<i32, String> {
2622        let worksheet = self.workbook.worksheet_mut(sheet)?;
2623        let cell_reference = CellReferenceRC {
2624            sheet: worksheet.get_name(),
2625            row,
2626            column,
2627        };
2628        let shared_formulas = &mut worksheet.shared_formulas;
2629        let mut parsed_formula = self.parser.parse(formula, &cell_reference);
2630        // If the formula fails to parse try adding a parenthesis
2631        // SUM(A1:A3  => SUM(A1:A3)
2632        if let Node::ParseErrorKind { .. } = parsed_formula {
2633            let new_parsed_formula = self.parser.parse(&format!("{formula})"), &cell_reference);
2634            match new_parsed_formula {
2635                Node::ParseErrorKind { .. } => {}
2636                _ => parsed_formula = new_parsed_formula,
2637            }
2638        }
2639        let static_result = run_static_analysis_on_node(&parsed_formula);
2640
2641        let s = to_rc_format(&parsed_formula);
2642        let mut formula_index: i32 = -1;
2643        if let Some(index) = shared_formulas.iter().position(|x| x == &s) {
2644            formula_index = index as i32;
2645        }
2646        if formula_index == -1 {
2647            shared_formulas.push(s);
2648            self.parsed_formulas[sheet as usize].push((parsed_formula, static_result));
2649            formula_index = (shared_formulas.len() as i32) - 1;
2650        }
2651        worksheet.set_cell_with_array_formula(row, column, formula_index, style, width, height)?;
2652        Ok(formula_index)
2653    }
2654
2655    pub(crate) fn set_cell_with_string(
2656        &mut self,
2657        sheet: u32,
2658        row: i32,
2659        column: i32,
2660        value: &str,
2661        style: i32,
2662    ) -> Result<(), String> {
2663        match self.shared_strings.get(value) {
2664            Some(string_index) => {
2665                self.workbook.worksheet_mut(sheet)?.set_cell_with_string(
2666                    row,
2667                    column,
2668                    *string_index as i32,
2669                    style,
2670                )?;
2671            }
2672            None => {
2673                let string_index = self.workbook.shared_strings.len();
2674                self.workbook.shared_strings.push(value.to_string());
2675                self.shared_strings.insert(value.to_string(), string_index);
2676                self.workbook.worksheet_mut(sheet)?.set_cell_with_string(
2677                    row,
2678                    column,
2679                    string_index as i32,
2680                    style,
2681                )?;
2682            }
2683        }
2684        Ok(())
2685    }
2686
2687    fn set_cell_with_boolean(
2688        &mut self,
2689        sheet: u32,
2690        row: i32,
2691        column: i32,
2692        value: bool,
2693        style: i32,
2694    ) -> Result<(), String> {
2695        self.workbook
2696            .worksheet_mut(sheet)?
2697            .set_cell_with_boolean(row, column, value, style)
2698    }
2699
2700    fn set_cell_with_number(
2701        &mut self,
2702        sheet: u32,
2703        row: i32,
2704        column: i32,
2705        value: f64,
2706        style: i32,
2707    ) -> Result<(), String> {
2708        self.workbook
2709            .worksheet_mut(sheet)?
2710            .set_cell_with_number(row, column, value, style)
2711    }
2712
2713    // Helper function that returns a defined name given the name and scope
2714    fn get_parsed_defined_name(
2715        &self,
2716        name: &str,
2717        scope: Option<u32>,
2718    ) -> Result<Option<ParsedDefinedName>, String> {
2719        let name_upper = name.to_uppercase();
2720
2721        for (key, df) in &self.parsed_defined_names {
2722            if key.1.to_uppercase() == name_upper && key.0 == scope {
2723                return Ok(Some(df.clone()));
2724            }
2725        }
2726        Ok(None)
2727    }
2728
2729    // Returns the formula for a defined name
2730    pub(crate) fn get_defined_name_formula(
2731        &self,
2732        name: &str,
2733        scope: Option<u32>,
2734    ) -> Result<String, String> {
2735        let name_upper = name.to_uppercase();
2736        let defined_names = &self.workbook.defined_names;
2737        let sheet_id = match scope {
2738            Some(index) => Some(self.workbook.worksheet(index)?.sheet_id),
2739            None => None,
2740        };
2741        for df in defined_names {
2742            if df.name.to_uppercase() == name_upper && df.sheet_id == sheet_id {
2743                return Ok(df.formula.clone());
2744            }
2745        }
2746        Err("Defined name not found".to_string())
2747    }
2748
2749    /// Returns the list of defined names as `(name, scope, formula)`.
2750    ///
2751    /// Formulas are stored internally in English; they are translated into the
2752    /// active language/locale for display.
2753    pub fn get_defined_name_list(&self) -> Vec<(String, Option<u32>, String)> {
2754        let context = self.defined_name_context();
2755        self.workbook
2756            .get_defined_names_with_scope()
2757            .into_iter()
2758            .map(|(name, scope, formula)| {
2759                let formula = self.internal_formula_to_display(&formula, &context);
2760                (name, scope, formula)
2761            })
2762            .collect()
2763    }
2764
2765    /// Gets the Excel Value (Bool, Number, String) of a cell
2766    ///
2767    /// See also:
2768    /// * [Model::get_cell_value_by_index()]
2769    pub fn get_cell_value_by_ref(&self, cell_ref: &str) -> Result<CellValue, String> {
2770        let cell_reference = match self.parse_reference(cell_ref) {
2771            Some(c) => c,
2772            None => return Err(format!("Error parsing reference: '{cell_ref}'")),
2773        };
2774        let sheet_index = cell_reference.sheet;
2775        let column = cell_reference.column;
2776        let row = cell_reference.row;
2777
2778        self.get_cell_value_by_index(sheet_index, row, column)
2779    }
2780
2781    /// Returns the cell value for (`sheet`, `row`, `column`)
2782    ///
2783    /// See also:
2784    /// * [Model::get_formatted_cell_value()]
2785    pub fn get_cell_value_by_index(
2786        &self,
2787        sheet_index: u32,
2788        row: i32,
2789        column: i32,
2790    ) -> Result<CellValue, String> {
2791        let cell = self
2792            .workbook
2793            .worksheet(sheet_index)?
2794            .cell(row, column)
2795            .cloned()
2796            .unwrap_or_default();
2797        let cell_value = cell.value(&self.workbook.shared_strings, self.language);
2798        Ok(cell_value)
2799    }
2800
2801    /// Returns the formatted cell value for (`sheet`, `row`, `column`)
2802    ///
2803    /// See also:
2804    /// * [Model::get_cell_value_by_index()]
2805    /// * [Model::get_cell_value_by_ref]
2806    ///
2807    /// # Examples
2808    ///
2809    /// ```rust
2810    /// # use ironcalc_base::Model;
2811    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
2812    /// let mut model = Model::new_empty("model", "en", "UTC", "en")?;
2813    /// let (sheet, row, column) = (0, 1, 1);
2814    /// model.set_user_input(sheet, row, column, "=1/3".to_string());
2815    /// model.evaluate();
2816    /// let result = model.get_formatted_cell_value(sheet, row, column)?;
2817    /// assert_eq!(result, "0.333333333".to_string());
2818    /// # Ok(())
2819    /// # }
2820    /// ```
2821    pub fn get_formatted_cell_value(
2822        &self,
2823        sheet_index: u32,
2824        row: i32,
2825        column: i32,
2826    ) -> Result<String, String> {
2827        match self.workbook.worksheet(sheet_index)?.cell(row, column) {
2828            Some(cell) => {
2829                let format = self.get_style_for_cell(sheet_index, row, column)?.num_fmt;
2830                let formatted_value =
2831                    cell.formatted_value(&self.workbook.shared_strings, self.language, |value| {
2832                        format_number(value, &format, self.locale).text
2833                    });
2834                Ok(formatted_value)
2835            }
2836            None => Ok("".to_string()),
2837        }
2838    }
2839
2840    /// Return the typeof a cell
2841    pub fn get_cell_type(&self, sheet: u32, row: i32, column: i32) -> Result<CellType, String> {
2842        Ok(match self.workbook.worksheet(sheet)?.cell(row, column) {
2843            Some(c) => c.get_type(),
2844            None => CellType::Number,
2845        })
2846    }
2847
2848    /// Returns a string with the cell content in the given language and locale.
2849    /// If there is a formula returns the formula
2850    /// If the cell is empty returns the empty string
2851    /// Returns an error if there is no worksheet
2852    /// If the cell has quote prefix style it adds a ' at the beginning of the value
2853    /// If the cell is date formatted it tries to format it as date
2854    pub fn get_localized_cell_content(
2855        &self,
2856        sheet: u32,
2857        row: i32,
2858        column: i32,
2859    ) -> Result<String, String> {
2860        let worksheet = self.workbook.worksheet(sheet)?;
2861        let cell = match worksheet.cell(row, column) {
2862            Some(c) => c,
2863            None => return Ok("".to_string()),
2864        };
2865        match cell.get_formula() {
2866            Some(formula_index) => {
2867                let formula = &self.parsed_formulas[sheet as usize][formula_index as usize].0;
2868                let cell_ref = CellReferenceRC {
2869                    sheet: worksheet.get_name(),
2870                    row,
2871                    column,
2872                };
2873                Ok(format!(
2874                    "={}",
2875                    to_localized_string(formula, &cell_ref, self.locale, self.language)
2876                ))
2877            }
2878            None => {
2879                let style_index = cell.get_style();
2880                let style = self.workbook.styles.get_style(style_index)?;
2881                if style.quote_prefix {
2882                    Ok(format!(
2883                        "'{}",
2884                        cell.get_localized_text(
2885                            &self.workbook.shared_strings,
2886                            self.locale,
2887                            self.language,
2888                        )
2889                    ))
2890                } else {
2891                    // If it is a date formatted cell we try to format it as date, if it fails we return the raw value
2892                    if is_likely_date_number_format(&style.num_fmt) {
2893                        let value = cell.value(&self.workbook.shared_strings, self.language);
2894                        if let CellValue::Number(n) = value {
2895                            let formatted = format_number(n, &style.num_fmt, self.locale);
2896                            if formatted.error.is_none() {
2897                                return Ok(formatted.text);
2898                            }
2899                        }
2900                    }
2901                    Ok(cell.get_localized_text(
2902                        &self.workbook.shared_strings,
2903                        self.locale,
2904                        self.language,
2905                    ))
2906                }
2907            }
2908        }
2909    }
2910
2911    /// Returns a list of all cells
2912    pub fn get_all_cells(&self) -> Vec<CellIndex> {
2913        let mut cells = Vec::new();
2914        for (index, sheet) in self.workbook.worksheets.iter().enumerate() {
2915            let mut sorted_rows: Vec<_> = sheet.sheet_data.keys().collect();
2916            sorted_rows.sort_unstable();
2917            for row in sorted_rows {
2918                let row_data = &sheet.sheet_data[row];
2919                let mut sorted_columns: Vec<_> = row_data.keys().collect();
2920                sorted_columns.sort_unstable();
2921                for column in sorted_columns {
2922                    cells.push(CellIndex {
2923                        index: index as u32,
2924                        row: *row,
2925                        column: *column,
2926                    });
2927                }
2928            }
2929        }
2930        cells
2931    }
2932
2933    /// Collects all dynamic-formula anchor cells in natural (sheet, row, column) order
2934    /// and stores them in `self.spill_cells`.
2935    fn collect_spill_cells(&mut self) {
2936        let mut spill_cells = Vec::new();
2937        for (sheet_index, worksheet) in self.workbook.worksheets.iter().enumerate() {
2938            let mut sorted_rows: Vec<i32> = worksheet.sheet_data.keys().copied().collect();
2939            sorted_rows.sort_unstable();
2940            for row in &sorted_rows {
2941                let row_data = &worksheet.sheet_data[row];
2942                let mut sorted_cols: Vec<i32> = row_data.keys().copied().collect();
2943                sorted_cols.sort_unstable();
2944                for col in &sorted_cols {
2945                    if matches!(
2946                        &row_data[col],
2947                        Cell::ArrayFormula {
2948                            kind: ArrayKind::Dynamic,
2949                            ..
2950                        }
2951                    ) {
2952                        spill_cells.push(CellReferenceIndex {
2953                            sheet: sheet_index as u32,
2954                            row: *row,
2955                            column: *col,
2956                        });
2957                    }
2958                }
2959            }
2960        }
2961        self.spill_cells = spill_cells;
2962    }
2963
2964    /// Returns all cells in the current spill area of a dynamic-formula anchor,
2965    /// including the anchor itself.
2966    fn get_spill_area(&self, cell_ref: CellReferenceIndex) -> Vec<CellReferenceIndex> {
2967        let ws = match self.workbook.worksheet(cell_ref.sheet) {
2968            Ok(ws) => ws,
2969            Err(_) => return Vec::new(),
2970        };
2971        let (width, height) = match ws.cell(cell_ref.row, cell_ref.column) {
2972            Some(Cell::ArrayFormula {
2973                r,
2974                kind: ArrayKind::Dynamic,
2975                ..
2976            }) => *r,
2977            _ => return Vec::new(),
2978        };
2979        (cell_ref.row..cell_ref.row + height)
2980            .flat_map(|r| {
2981                (cell_ref.column..cell_ref.column + width).map(move |c| CellReferenceIndex {
2982                    sheet: cell_ref.sheet,
2983                    row: r,
2984                    column: c,
2985                })
2986            })
2987            .collect()
2988    }
2989
2990    /// Returns true if any position in `positions` falls within a dependency of `cell`.
2991    fn position_in_support(
2992        &self,
2993        cell: CellReferenceIndex,
2994        positions: &[CellReferenceIndex],
2995    ) -> bool {
2996        let deps = match self.support.get(&cell) {
2997            Some(d) => d,
2998            None => return false,
2999        };
3000        for dep in deps {
3001            match *dep {
3002                CellOrRange::Cell((sheet, row, col)) => {
3003                    if positions
3004                        .iter()
3005                        .any(|p| p.sheet == sheet && p.row == row && p.column == col)
3006                    {
3007                        return true;
3008                    }
3009                }
3010                CellOrRange::Range((sheet, r1, c1, r2, c2)) => {
3011                    if positions.iter().any(|p| {
3012                        p.sheet == sheet
3013                            && p.row >= r1
3014                            && p.row <= r2
3015                            && p.column >= c1
3016                            && p.column <= c2
3017                    }) {
3018                        return true;
3019                    }
3020                }
3021            }
3022        }
3023        false
3024    }
3025
3026    /// Evaluates the model using a two-phase algorithm that correctly handles dynamic arrays.
3027    ///
3028    /// Phase 1 evaluates all spill-capable cells first (in dependency order), so their spill
3029    /// areas are populated before any other cell reads from them.  When a spill cell writes
3030    /// into a position that an earlier spill cell depends on, the two cells are reordered and
3031    /// the phase restarts.  A restart bound of N*N prevents infinite loops caused by circular
3032    /// dependencies between spill cells.
3033    ///
3034    /// Phase 2 evaluates every remaining cell in natural order.  Because all spill areas have
3035    /// already been written, regular cells always read the correct spill values.
3036    pub fn evaluate(&mut self) {
3037        self.collect_spill_cells();
3038
3039        let n = self.spill_cells.len();
3040        // Each restart fixes at least one pair; O(N*N) restarts suffice.
3041        let max_restarts = n * n + 1;
3042        let mut retry = true;
3043        let mut restart_count = 0;
3044
3045        while retry && restart_count < max_restarts {
3046            retry = false;
3047            self.cells.clear();
3048            self.support.clear();
3049            // dynamic links (HYPERLINK) are rebuilt on every evaluation
3050            self.links.clear();
3051            self.clear_variable_stack();
3052            self.clear_lambdas();
3053
3054            // Phase 1: evaluate spill cells, correcting their order when needed.
3055            for i in 0..self.spill_cells.len() {
3056                let spill_cell = self.spill_cells[i];
3057                self.evaluate_cell(spill_cell);
3058
3059                // Find every cell position written by this spill (anchor + spill cells).
3060                let spill_area = self.get_spill_area(spill_cell);
3061
3062                // If any of those positions is a dependency of a spill cell that was
3063                // evaluated earlier (index j < i), the current cell must come first.
3064                for j in 0..i {
3065                    let prev = self.spill_cells[j];
3066                    if self.position_in_support(prev, &spill_area) {
3067                        let moved = self.spill_cells.remove(i);
3068                        self.spill_cells.insert(j, moved);
3069                        retry = true;
3070                        restart_count += 1;
3071                        break;
3072                    }
3073                }
3074                if retry {
3075                    break;
3076                }
3077            }
3078        }
3079
3080        // Phase 2: evaluate everything else; spill cells are already Evaluated and skipped.
3081        // Fallback when max restarts is exceeded (circular spill dependency).
3082        let all_cells = self.get_all_cells();
3083        for cell in all_cells {
3084            self.evaluate_cell(CellReferenceIndex {
3085                sheet: cell.index,
3086                row: cell.row,
3087                column: cell.column,
3088            });
3089        }
3090        self.evaluate_conditional_formatting();
3091    }
3092
3093    /// Removes the content of every cell in the range but leaves the style.
3094    ///
3095    /// See also:
3096    /// * [Model::range_clear_all()]
3097    ///
3098    /// # Examples
3099    ///
3100    /// ```rust
3101    /// # use ironcalc_base::Model;
3102    /// # use ironcalc_base::expressions::types::Area;
3103    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
3104    /// let mut model = Model::new_empty("model", "en", "UTC", "en")?;
3105    /// let (sheet, row, column) = (0, 1, 1);
3106    /// model.set_user_input(sheet, row, column, "100$".to_string());
3107    /// let area = Area {
3108    ///     sheet,
3109    ///     row,
3110    ///     column,
3111    ///     width: 1,
3112    ///     height: 1,
3113    /// };
3114    /// model.range_clear_contents(&area)?;
3115    /// model.set_user_input(sheet, row, column, "10".to_string());
3116    /// let result = model.get_formatted_cell_value(sheet, row, column)?;
3117    /// assert_eq!(result, "10$".to_string());
3118    /// # Ok(())
3119    /// # }
3120    /// ```
3121    pub fn range_clear_contents(&mut self, range: &Area) -> Result<(), String> {
3122        if !self.can_clear_range(range)? {
3123            return Err("Cannot clear the range because it contains array formulas".to_string());
3124        }
3125        let sheet = range.sheet;
3126        let ws = self.workbook.worksheet_mut(sheet)?;
3127        for row in range.row..range.row + range.height {
3128            for column in range.column..range.column + range.width {
3129                let structure = ws.get_cell_structure(row, column)?;
3130                match structure {
3131                    CellStructure::DynamicFormula { range }
3132                    | CellStructure::ArrayFormula { range, .. } => {
3133                        let (width, height) = range;
3134                        for r in row..row + height {
3135                            for c in column..column + width {
3136                                let _ = ws.cell_clear_contents(r, c);
3137                            }
3138                        }
3139                    }
3140                    _ => {
3141                        let _ = ws.cell_clear_contents(row, column);
3142                    }
3143                }
3144            }
3145        }
3146        // Deleting the contents of a cell also removes its link
3147        ws.links.retain(|&(row, column), _| {
3148            row < range.row
3149                || row >= range.row + range.height
3150                || column < range.column
3151                || column >= range.column + range.width
3152        });
3153        Ok(())
3154    }
3155
3156    // Returns true if for every array formula in the range, the whole spill is included in the range,
3157    // false otherwise.
3158    pub(crate) fn can_clear_range(&self, range: &Area) -> Result<bool, String> {
3159        let sheet = range.sheet;
3160        for row in range.row..range.row + range.height {
3161            for column in range.column..range.column + range.width {
3162                match self.get_cell_structure(sheet, row, column)? {
3163                    CellStructure::ArrayFormula { range: r } => {
3164                        let (width, height) = r;
3165                        if column + width > range.column + range.width
3166                            || row + height > range.row + range.height
3167                        {
3168                            return Ok(false);
3169                        }
3170                    }
3171                    CellStructure::SpillArray {
3172                        anchor: a,
3173                        range: r,
3174                    } => {
3175                        let (anchor_row, anchor_column) = a;
3176                        let (width, height) = r;
3177                        if anchor_column < range.column
3178                            || anchor_row < range.row
3179                            || anchor_column + width > range.column + range.width
3180                            || anchor_row + height > range.row + range.height
3181                        {
3182                            return Ok(false);
3183                        }
3184                    }
3185                    _ => {
3186                        // noop
3187                    }
3188                }
3189            }
3190        }
3191        Ok(true)
3192    }
3193
3194    /// Deletes a range by removing it from worksheet data. All content and style is removed.
3195    /// It fails if it deletes part of an array formula.
3196    /// Deletes the whole spill if it is part of a dynamic array formula.
3197    ///
3198    /// See also:
3199    /// * [Model::range_clear_contents()]
3200    ///
3201    /// # Examples
3202    ///
3203    /// ```rust
3204    /// # use ironcalc_base::Model;
3205    /// # use ironcalc_base::expressions::types::Area;
3206    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
3207    /// let mut model = Model::new_empty("model", "en", "UTC", "en")?;
3208    /// let (sheet, row, column) = (0, 1, 1);
3209    /// model.set_user_input(sheet, row, column, "100$".to_string());
3210    /// let area = Area {
3211    ///     sheet,
3212    ///     row,
3213    ///     column,
3214    ///     width: 1,
3215    ///     height: 1,
3216    /// };
3217    /// model.range_clear_all(&area)?;
3218    /// model.set_user_input(sheet, row, column, "10".to_string());
3219    /// let result = model.get_formatted_cell_value(sheet, row, column)?;
3220    /// assert_eq!(result, "10".to_string());
3221    /// # Ok(())
3222    /// # }
3223    pub fn range_clear_all(&mut self, area: &Area) -> Result<(), String> {
3224        if !self.can_clear_range(area)? {
3225            return Err("Cannot clear the range because it contains array formulas".to_string());
3226        }
3227        let worksheet = self.workbook.worksheet_mut(area.sheet)?;
3228
3229        let sheet_data = &mut worksheet.sheet_data;
3230        let mut cells_to_clear = Vec::new();
3231        for row in area.row..area.row + area.height {
3232            if let Some(row_data) = sheet_data.get_mut(&row) {
3233                for column in area.column..area.column + area.width {
3234                    // If it is part of a dynamic array we need to clear the spill
3235                    if let Some(Cell::ArrayFormula {
3236                        r,
3237                        kind: ArrayKind::Dynamic,
3238                        ..
3239                    }) = row_data.get(&column)
3240                    {
3241                        // clear the spill of the dynamic formula
3242                        let (width, height) = r;
3243                        for r in row..row + height {
3244                            for c in column..column + width {
3245                                cells_to_clear.push((r, c));
3246                            }
3247                        }
3248                    }
3249                    row_data.remove(&column);
3250                }
3251                if row_data.is_empty() {
3252                    sheet_data.remove(&row);
3253                };
3254            }
3255        }
3256        for (row, column) in cells_to_clear {
3257            // we ignore errors here because the cell might have already been cleared as part of an array formula
3258            let _ = worksheet.cell_clear_contents(row, column);
3259        }
3260        // Deleting the cells also removes their links
3261        worksheet.links.retain(|&(row, column), _| {
3262            row < area.row
3263                || row >= area.row + area.height
3264                || column < area.column
3265                || column >= area.column + area.width
3266        });
3267        Ok(())
3268    }
3269
3270    // Finds all the dynamic array formulas that spills:
3271    // * Delete the spilled cells
3272    // * Update the formula cell to be DynamicFormula with r = (1,1)
3273    pub(crate) fn reset_dynamic_array_spills(&mut self, sheet: u32) -> Result<(), String> {
3274        // Collect anchor info first — can't mutate sheet_data while iterating over it.
3275        let anchors: Vec<(i32, i32, i32, i32, i32, i32)> = {
3276            let ws = self.workbook.worksheet(sheet)?;
3277            let mut result = Vec::new();
3278            for (row, row_data) in &ws.sheet_data {
3279                for (column, cell) in row_data {
3280                    if let Cell::ArrayFormula {
3281                        r,
3282                        f,
3283                        s,
3284                        kind: ArrayKind::Dynamic,
3285                        ..
3286                    } = cell
3287                    {
3288                        let (width, height) = *r;
3289                        result.push((*row, *column, *f, *s, width, height));
3290                    }
3291                }
3292            }
3293            result
3294        };
3295
3296        for (row, column, f, s, width, height) in anchors {
3297            let ws = self.workbook.worksheet_mut(sheet)?;
3298            // Reset the anchor cell to DynamicFormula with r = (1, 1)
3299            if let Some(row_data) = ws.sheet_data.get_mut(&row) {
3300                row_data.insert(
3301                    column,
3302                    Cell::ArrayFormula {
3303                        f,
3304                        s,
3305                        r: (1, 1),
3306                        kind: ArrayKind::Dynamic,
3307                        v: FormulaValue::Unevaluated,
3308                    },
3309                );
3310            }
3311            // Delete all spill cells
3312            for r in row..row + height {
3313                for c in column..column + width {
3314                    if r == row && c == column {
3315                        continue;
3316                    }
3317                    let _ = ws.cell_clear_contents(r, c);
3318                }
3319            }
3320        }
3321        Ok(())
3322    }
3323
3324    /// Returns the style index for cell (`sheet`, `row`, `column`)
3325    pub fn get_cell_style_index(&self, sheet: u32, row: i32, column: i32) -> Result<i32, String> {
3326        // First check the cell, then row, the column
3327        let cell = self.workbook.worksheet(sheet)?.cell(row, column);
3328
3329        match cell {
3330            Some(cell) => Ok(cell.get_style()),
3331            None => {
3332                let rows = &self.workbook.worksheet(sheet)?.rows;
3333                for r in rows {
3334                    if r.r == row {
3335                        if r.custom_format {
3336                            return Ok(r.s);
3337                        }
3338                        break;
3339                    }
3340                }
3341                let cols = &self.workbook.worksheet(sheet)?.cols;
3342                for c in cols.iter() {
3343                    let min = c.min;
3344                    let max = c.max;
3345                    if column >= min && column <= max {
3346                        return Ok(c.style.unwrap_or(0));
3347                    }
3348                }
3349                Ok(0)
3350            }
3351        }
3352    }
3353
3354    /// Returns the style for cell (`sheet`, `row`, `column`)
3355    /// If the cell does not have a style defined we check the row, otherwise the column and finally a default
3356    pub fn get_style_for_cell(&self, sheet: u32, row: i32, column: i32) -> Result<Style, String> {
3357        let style_index = self.get_cell_style_index(sheet, row, column)?;
3358        let style = self.workbook.styles.get_style(style_index)?;
3359        Ok(style)
3360    }
3361
3362    /// Returns the style defined in a cell if any.
3363    pub fn get_cell_style_or_none(
3364        &self,
3365        sheet: u32,
3366        row: i32,
3367        column: i32,
3368    ) -> Result<Option<Style>, String> {
3369        let style = self
3370            .workbook
3371            .worksheet(sheet)?
3372            .cell(row, column)
3373            .map(|c| self.workbook.styles.get_style(c.get_style()))
3374            .transpose();
3375        style
3376    }
3377
3378    /// Returns an internal binary representation of the workbook
3379    ///
3380    /// See also:
3381    /// * [Model::from_bytes]
3382    pub fn to_bytes(&self) -> Vec<u8> {
3383        bitcode::encode(&self.workbook)
3384    }
3385
3386    /// Returns data about the worksheets
3387    pub fn get_worksheets_properties(&self) -> Vec<SheetProperties> {
3388        self.workbook
3389            .worksheets
3390            .iter()
3391            .map(|worksheet| SheetProperties {
3392                name: worksheet.get_name(),
3393                state: worksheet.state.to_string(),
3394                color: worksheet.color.clone(),
3395                sheet_id: worksheet.sheet_id,
3396            })
3397            .collect()
3398    }
3399
3400    /// Returns markup representation of the given `sheet`.
3401    pub fn get_sheet_markup(&self, sheet: u32) -> Result<String, String> {
3402        let worksheet = self.workbook.worksheet(sheet)?;
3403        let dimension = worksheet.dimension();
3404
3405        let mut rows = Vec::new();
3406
3407        for row in 1..(dimension.max_row + 1) {
3408            let mut row_markup: Vec<String> = Vec::new();
3409
3410            for column in 1..(dimension.max_column + 1) {
3411                let mut cell_markup = match self.get_cell_formula(sheet, row, column)? {
3412                    Some(formula) => formula,
3413                    None => self.get_formatted_cell_value(sheet, row, column)?,
3414                };
3415                let style = self.get_style_for_cell(sheet, row, column)?;
3416                if style.font.b {
3417                    cell_markup = format!("**{cell_markup}**")
3418                }
3419                row_markup.push(cell_markup);
3420            }
3421
3422            rows.push(row_markup.join("|"));
3423        }
3424
3425        Ok(rows.join("\n"))
3426    }
3427
3428    /// Returns the number of frozen rows in `sheet`
3429    pub fn get_frozen_rows_count(&self, sheet: u32) -> Result<i32, String> {
3430        if let Some(worksheet) = self.workbook.worksheets.get(sheet as usize) {
3431            Ok(worksheet.frozen_rows)
3432        } else {
3433            Err("Invalid sheet".to_string())
3434        }
3435    }
3436
3437    /// Return the number of frozen columns in `sheet`
3438    pub fn get_frozen_columns_count(&self, sheet: u32) -> Result<i32, String> {
3439        if let Some(worksheet) = self.workbook.worksheets.get(sheet as usize) {
3440            Ok(worksheet.frozen_columns)
3441        } else {
3442            Err("Invalid sheet".to_string())
3443        }
3444    }
3445
3446    /// Sets the number of frozen rows to `frozen_rows` in the workbook.
3447    /// Fails if `frozen`_rows` is either too small (<0) or too large (>LAST_ROW)`
3448    pub fn set_frozen_rows(&mut self, sheet: u32, frozen_rows: i32) -> Result<(), String> {
3449        if let Some(worksheet) = self.workbook.worksheets.get_mut(sheet as usize) {
3450            if frozen_rows < 0 {
3451                return Err("Frozen rows cannot be negative".to_string());
3452            }
3453            if frozen_rows >= LAST_ROW {
3454                return Err("Too many rows".to_string());
3455            }
3456            worksheet.frozen_rows = frozen_rows;
3457            Ok(())
3458        } else {
3459            Err("Invalid sheet".to_string())
3460        }
3461    }
3462
3463    /// Sets the number of frozen columns to `frozen_column` in the workbook.
3464    /// Fails if `frozen`_columns` is either too small (<0) or too large (>LAST_COLUMN)`
3465    pub fn set_frozen_columns(&mut self, sheet: u32, frozen_columns: i32) -> Result<(), String> {
3466        if let Some(worksheet) = self.workbook.worksheets.get_mut(sheet as usize) {
3467            if frozen_columns < 0 {
3468                return Err("Frozen columns cannot be negative".to_string());
3469            }
3470            if frozen_columns >= LAST_COLUMN {
3471                return Err("Too many columns".to_string());
3472            }
3473            worksheet.frozen_columns = frozen_columns;
3474            Ok(())
3475        } else {
3476            Err("Invalid sheet".to_string())
3477        }
3478    }
3479
3480    /// Returns the width of a column
3481    #[inline]
3482    pub fn get_column_width(&self, sheet: u32, column: i32) -> Result<f64, String> {
3483        self.workbook.worksheet(sheet)?.get_column_width(column)
3484    }
3485
3486    /// Sets the width of a column
3487    #[inline]
3488    pub fn set_column_width(&mut self, sheet: u32, column: i32, width: f64) -> Result<(), String> {
3489        self.workbook
3490            .worksheet_mut(sheet)?
3491            .set_column_width(column, width)
3492    }
3493
3494    /// Sets whether a column is hidden
3495    #[inline]
3496    pub fn set_column_hidden(
3497        &mut self,
3498        sheet: u32,
3499        column: i32,
3500        hidden: bool,
3501    ) -> Result<(), String> {
3502        self.workbook
3503            .worksheet_mut(sheet)?
3504            .set_column_hidden(column, hidden)
3505    }
3506
3507    /// Sets whether a row is hidden
3508    #[inline]
3509    pub fn set_row_hidden(&mut self, sheet: u32, row: i32, hidden: bool) -> Result<(), String> {
3510        self.workbook
3511            .worksheet_mut(sheet)?
3512            .set_row_hidden(row, hidden)
3513    }
3514
3515    /// Returns whether a column is hidden
3516    #[inline]
3517    pub fn is_column_hidden(&self, sheet: u32, column: i32) -> Result<bool, String> {
3518        self.workbook.worksheet(sheet)?.is_column_hidden(column)
3519    }
3520
3521    /// Returns whether a row is hidden
3522    #[inline]
3523    pub fn is_row_hidden(&self, sheet: u32, row: i32) -> Result<bool, String> {
3524        self.workbook.worksheet(sheet)?.is_row_hidden(row)
3525    }
3526
3527    /// Returns the height of a row
3528    #[inline]
3529    pub fn get_row_height(&self, sheet: u32, row: i32) -> Result<f64, String> {
3530        self.workbook.worksheet(sheet)?.row_height(row)
3531    }
3532
3533    /// Sets the height of a row
3534    #[inline]
3535    pub fn set_row_height(&mut self, sheet: u32, column: i32, height: f64) -> Result<(), String> {
3536        self.workbook
3537            .worksheet_mut(sheet)?
3538            .set_row_height(column, height)
3539    }
3540
3541    /// Adds a new defined name.
3542    /// If scope is None it is a global defined name, otherwise it is local to the sheet with index scope.
3543    pub fn new_defined_name(
3544        &mut self,
3545        name: &str,
3546        scope: Option<u32>,
3547        formula: &str,
3548    ) -> Result<(), String> {
3549        let sheet_id = self.is_valid_defined_name(name, scope, formula)?;
3550        // Defined-name formulas are stored internally in English so they keep
3551        // working when the user switches language/locale.
3552        let context = self.defined_name_context();
3553        let internal_formula = self.user_formula_to_internal(formula, &context)?;
3554        self.workbook.defined_names.push(DefinedName {
3555            name: name.to_string(),
3556            formula: internal_formula,
3557            sheet_id,
3558        });
3559        self.reset_parsed_structures();
3560
3561        Ok(())
3562    }
3563
3564    /// The context used to parse/stringify defined-name formulas. Defined names
3565    /// have no natural anchor cell, so we use the first worksheet's A1.
3566    pub(crate) fn defined_name_context(&self) -> CellReferenceRC {
3567        CellReferenceRC {
3568            sheet: self
3569                .workbook
3570                .worksheets
3571                .first()
3572                .map(|ws| ws.get_name())
3573                .unwrap_or_else(|| "Sheet1".to_string()),
3574            row: 1,
3575            column: 1,
3576        }
3577    }
3578
3579    /// Validates if a defined name can be created
3580    pub fn is_valid_defined_name(
3581        &mut self,
3582        name: &str,
3583        scope: Option<u32>,
3584        formula: &str,
3585    ) -> Result<Option<u32>, String> {
3586        if !is_valid_identifier(name) {
3587            return Err("Name: Invalid defined name".to_string());
3588        }
3589        let name_upper = name.to_uppercase();
3590        let defined_names = &self.workbook.defined_names;
3591        let sheet_id = match scope {
3592            Some(index) => match self.workbook.worksheet(index) {
3593                Ok(ws) => Some(ws.sheet_id),
3594                Err(_) => return Err("Scope: Invalid sheet index".to_string()),
3595            },
3596            None => None,
3597        };
3598        // if the defined name already exist return error
3599        for df in defined_names {
3600            if df.name.to_uppercase() == name_upper && df.sheet_id == sheet_id {
3601                return Err("Name: Defined name already exists".to_string());
3602            }
3603        }
3604
3605        // Make sure the formula is valid — accept cell/range references OR a LAMBDA definition.
3606        let is_reference =
3607            common::ParsedReference::parse_reference_formula(None, formula, self.locale, |name| {
3608                self.get_sheet_index_by_name(name)
3609            })
3610            .is_ok();
3611
3612        if !is_reference {
3613            // Try the full parser to see if it is a LAMBDA definition.
3614            // Defined-name formulas may carry a leading '='; strip it before parsing.
3615            use crate::expressions::types::CellReferenceRC;
3616            let formula_body = formula.strip_prefix('=').unwrap_or(formula);
3617            let dummy_ref = CellReferenceRC {
3618                sheet: self
3619                    .workbook
3620                    .worksheets
3621                    .first()
3622                    .map(|ws| ws.get_name())
3623                    .unwrap_or_else(|| "Sheet1".to_string()),
3624                row: 1,
3625                column: 1,
3626            };
3627            // Accept the formula whether it is written in the active language
3628            // or already in the internal English form (e.g. generated by
3629            // undo/redo or cut & paste).
3630            let mut node = self.parser.parse(formula_body, &dummy_ref);
3631            if let Node::ParseErrorKind { .. } = node {
3632                node = self.parse_internal_formula(formula_body, &dummy_ref);
3633            }
3634            if !matches!(node, Node::LambdaDefKind { .. }) {
3635                return Err("Formula: Invalid defined name formula".to_string());
3636            }
3637        }
3638
3639        Ok(sheet_id)
3640    }
3641
3642    /// Delete defined name of name and scope
3643    pub fn delete_defined_name(&mut self, name: &str, scope: Option<u32>) -> Result<(), String> {
3644        let name_upper = name.to_uppercase();
3645        let defined_names = &self.workbook.defined_names;
3646        let sheet_id = match scope {
3647            Some(index) => Some(self.workbook.worksheet(index)?.sheet_id),
3648            None => None,
3649        };
3650        let mut index = None;
3651        for (i, df) in defined_names.iter().enumerate() {
3652            if df.name.to_uppercase() == name_upper && df.sheet_id == sheet_id {
3653                index = Some(i);
3654            }
3655        }
3656        if let Some(i) = index {
3657            self.workbook.defined_names.remove(i);
3658            self.reset_parsed_structures();
3659            Ok(())
3660        } else {
3661            Err("Defined name not found".to_string())
3662        }
3663    }
3664
3665    /// Update defined name
3666    pub fn update_defined_name(
3667        &mut self,
3668        name: &str,
3669        scope: Option<u32>,
3670        new_name: &str,
3671        new_scope: Option<u32>,
3672        new_formula: &str,
3673    ) -> Result<(), String> {
3674        if !is_valid_identifier(new_name) {
3675            return Err("Name: Invalid defined name".to_string());
3676        };
3677        let name_upper = name.to_uppercase();
3678        let new_name_upper = new_name.to_uppercase();
3679
3680        if name_upper != new_name_upper || scope != new_scope {
3681            for key in self.parsed_defined_names.keys() {
3682                if key.1.to_uppercase() == new_name_upper && key.0 == new_scope {
3683                    return Err("Name: Defined name already exists".to_string());
3684                }
3685            }
3686        }
3687        let defined_names = &self.workbook.defined_names;
3688        let sheet_id = match scope {
3689            Some(index) => Some(
3690                self.workbook
3691                    .worksheet(index)
3692                    .map_err(|_| "Scope: Invalid sheet index")?
3693                    .sheet_id,
3694            ),
3695            None => None,
3696        };
3697
3698        let new_sheet_id = match new_scope {
3699            Some(index) => Some(
3700                self.workbook
3701                    .worksheet(index)
3702                    .map_err(|_| "Scope: Invalid sheet index")?
3703                    .sheet_id,
3704            ),
3705            None => None,
3706        };
3707
3708        let mut index = None;
3709        for (i, df) in defined_names.iter().enumerate() {
3710            if df.name.to_uppercase() == name_upper && df.sheet_id == sheet_id {
3711                index = Some(i);
3712            }
3713        }
3714        // Defined-name formulas are stored internally in English.
3715        let context = self.defined_name_context();
3716        let internal_formula = self.user_formula_to_internal(new_formula, &context)?;
3717        if let Some(i) = index {
3718            if let Some(df) = self.workbook.defined_names.get_mut(i) {
3719                if new_name != df.name {
3720                    // We need to rename the name in every formula:
3721
3722                    // Parse all formulas with the old name
3723                    // All internal formulas are R1C1
3724                    self.parser.set_lexer_mode(LexerMode::R1C1);
3725                    let worksheets = &mut self.workbook.worksheets;
3726                    for worksheet in worksheets {
3727                        let cell_reference = CellReferenceRC {
3728                            sheet: worksheet.get_name(),
3729                            row: 1,
3730                            column: 1,
3731                        };
3732                        let mut formulas = Vec::new();
3733                        for formula in &worksheet.shared_formulas {
3734                            let mut t = self.parser.parse(formula, &cell_reference);
3735                            rename_defined_name_in_node(&mut t, name, scope, new_name);
3736                            formulas.push(to_rc_format(&t));
3737                        }
3738                        worksheet.shared_formulas = formulas;
3739                    }
3740                    // Se the mode back to A1
3741                    self.parser.set_lexer_mode(LexerMode::A1);
3742                }
3743                df.name = new_name.to_string();
3744                df.sheet_id = new_sheet_id;
3745                df.formula = internal_formula;
3746                self.reset_parsed_structures();
3747            }
3748            Ok(())
3749        } else {
3750            Err("Defined name not found".to_string())
3751        }
3752    }
3753    /// Returns the style object of a column, if any
3754    pub fn get_column_style(&self, sheet: u32, column: i32) -> Result<Option<Style>, String> {
3755        if let Some(worksheet) = self.workbook.worksheets.get(sheet as usize) {
3756            let cols = &worksheet.cols;
3757            for col in cols {
3758                if column >= col.min && column <= col.max {
3759                    if let Some(style_index) = col.style {
3760                        let style = self.workbook.styles.get_style(style_index)?;
3761                        return Ok(Some(style));
3762                    }
3763                    return Ok(None);
3764                }
3765            }
3766            Ok(None)
3767        } else {
3768            Err("Invalid sheet".to_string())
3769        }
3770    }
3771
3772    /// Returns the style object of a row, if any
3773    pub fn get_row_style(&self, sheet: u32, row: i32) -> Result<Option<Style>, String> {
3774        if let Some(worksheet) = self.workbook.worksheets.get(sheet as usize) {
3775            let rows = &worksheet.rows;
3776            for r in rows {
3777                if row == r.r {
3778                    let style = self.workbook.styles.get_style(r.s)?;
3779                    return Ok(Some(style));
3780                }
3781            }
3782            Ok(None)
3783        } else {
3784            Err("Invalid sheet".to_string())
3785        }
3786    }
3787
3788    /// Sets a column with style
3789    pub fn set_column_style(
3790        &mut self,
3791        sheet: u32,
3792        column: i32,
3793        style: &Style,
3794    ) -> Result<(), String> {
3795        let style_index = self.workbook.styles.get_style_index_or_create(style);
3796        self.workbook
3797            .worksheet_mut(sheet)?
3798            .set_column_style(column, style_index)
3799    }
3800
3801    /// Sets a row with style
3802    pub fn set_row_style(&mut self, sheet: u32, row: i32, style: &Style) -> Result<(), String> {
3803        let style_index = self.workbook.styles.get_style_index_or_create(style);
3804        self.workbook
3805            .worksheet_mut(sheet)?
3806            .set_row_style(row, style_index)
3807    }
3808
3809    /// Deletes the style of a column if the is any
3810    pub fn delete_column_style(&mut self, sheet: u32, column: i32) -> Result<(), String> {
3811        self.workbook
3812            .worksheet_mut(sheet)?
3813            .delete_column_style(column)
3814    }
3815
3816    /// Deletes the style of a row if there is any
3817    pub fn delete_row_style(&mut self, sheet: u32, row: i32) -> Result<(), String> {
3818        self.workbook.worksheet_mut(sheet)?.delete_row_style(row)
3819    }
3820
3821    /// Sets the locale of the model
3822    pub fn set_locale(&mut self, locale_id: &str) -> Result<(), String> {
3823        let locale = match get_locale(locale_id) {
3824            Ok(l) => l,
3825            Err(_) => return Err(format!("Invalid locale: {locale_id}")),
3826        };
3827        self.parser.set_locale(locale);
3828        self.locale = locale;
3829        self.workbook.settings.locale = locale_id.to_string();
3830        self.evaluate();
3831        Ok(())
3832    }
3833
3834    /// Sets the timezone of the model
3835    pub fn set_timezone(&mut self, timezone: &str) -> Result<(), String> {
3836        let tz = match Tz::parse(timezone) {
3837            Ok(tz) => tz,
3838            Err(_) => return Err(format!("Invalid timezone: {}", timezone)),
3839        };
3840        self.tz = tz;
3841        self.workbook.settings.tz = timezone.to_string();
3842        self.evaluate();
3843        Ok(())
3844    }
3845
3846    /// Sets the language
3847    pub fn set_language(&mut self, language_id: &str) -> Result<(), String> {
3848        let language = match get_language(language_id) {
3849            Ok(l) => l,
3850            Err(_) => return Err(format!("Invalid language: {language_id}")),
3851        };
3852        self.parser.set_language(language);
3853        self.language = language;
3854        Ok(())
3855    }
3856
3857    /// Gets the current language
3858    pub fn get_language(&self) -> String {
3859        self.language.code.clone()
3860    }
3861
3862    /// Gets the timezone of the model
3863    pub fn get_timezone(&self) -> String {
3864        self.workbook.settings.tz.clone()
3865    }
3866
3867    /// Gets the locale of the model
3868    pub fn get_locale(&self) -> String {
3869        self.workbook.settings.locale.clone()
3870    }
3871
3872    /// Gets the formatting settings based on the locale
3873    pub fn get_fmt_settings(&self) -> FmtSettings {
3874        let day_example = 46006.0; // December 15, 2025
3875        let currency = self.locale.currency.iso.clone();
3876        let currency_symbol = &self.locale.currency.symbol;
3877        // "M/d/yy"
3878        let short_date = &self.locale.dates.date_formats.short;
3879        // "M/d/yyyy"
3880        let long_date = &self.locale.dates.date_formats.long;
3881        let short_date_example = format_number(day_example, short_date, self.locale).text;
3882        let long_date_example = format_number(day_example, long_date, self.locale).text;
3883        // Number format ("#,##0.###")
3884        // The CLDR formats are a bit different than Excel's
3885        // let number_fmt = self.locale.numbers.decimal_formats.standard.clone();
3886        // "#,##0.00 ¤" Currency format might have weird spaces
3887        let currency_format_template = &self.locale.numbers.currency_formats.standard;
3888        let currency_format = currency_format_template
3889            .replace("¤", &format!("\"{}\"", currency_symbol))
3890            .replace(" ", " ");
3891
3892        let number_fmt = "#,##0.00".to_string();
3893        let number_example = format_number(1234.567, &number_fmt, self.locale).text;
3894        FmtSettings {
3895            currency,
3896            currency_format,
3897            short_date: short_date.clone(),
3898            long_date: long_date.clone(),
3899            short_date_example,
3900            long_date_example,
3901            number_fmt,
3902            number_example,
3903        }
3904    }
3905
3906    /// Cycles the references touched by the cursor through the four
3907    /// absolute/relative states, Excel F4 style: A1 -> $A$1 -> A$1 -> $A1 -> A1.
3908    /// Returns the new text together with the new cursor start and end.
3909    ///
3910    /// Given cycle_reference("=A1", 3, 3) returns ("=$A$1", 5, 5)
3911    pub fn cycle_reference(
3912        &self,
3913        value: &str,
3914        start: usize,
3915        end: usize,
3916    ) -> Result<(String, i32, i32), String> {
3917        crate::expressions::lexer::util::cycle_reference(
3918            value,
3919            start,
3920            end,
3921            self.locale,
3922            self.language,
3923        )
3924    }
3925}
3926
3927#[cfg(test)]
3928mod tests {
3929    #![allow(clippy::expect_used)]
3930    use super::CellReferenceIndex as CellReference;
3931    use crate::{test::util::new_empty_model, types::Cell};
3932
3933    #[test]
3934    fn test_cell_reference_to_string() {
3935        let model = new_empty_model();
3936        let reference = CellReference {
3937            sheet: 0,
3938            row: 32,
3939            column: 16,
3940        };
3941        assert_eq!(
3942            model.cell_reference_to_string(&reference),
3943            Ok("Sheet1!P32".to_string())
3944        )
3945    }
3946
3947    #[test]
3948    fn test_cell_reference_to_string_invalid_worksheet() {
3949        let model = new_empty_model();
3950        let reference = CellReference {
3951            sheet: 10,
3952            row: 1,
3953            column: 1,
3954        };
3955        assert_eq!(
3956            model.cell_reference_to_string(&reference),
3957            Err("Invalid sheet index".to_string())
3958        )
3959    }
3960
3961    #[test]
3962    fn test_cell_reference_to_string_invalid_column() {
3963        let model = new_empty_model();
3964        let reference = CellReference {
3965            sheet: 0,
3966            row: 1,
3967            column: 20_000,
3968        };
3969        assert_eq!(
3970            model.cell_reference_to_string(&reference),
3971            Err("Invalid column".to_string())
3972        )
3973    }
3974
3975    #[test]
3976    fn test_cell_reference_to_string_invalid_row() {
3977        let model = new_empty_model();
3978        let reference = CellReference {
3979            sheet: 0,
3980            row: 2_000_000,
3981            column: 1,
3982        };
3983        assert_eq!(
3984            model.cell_reference_to_string(&reference),
3985            Err("Invalid row".to_string())
3986        )
3987    }
3988
3989    #[test]
3990    fn test_get_cell() {
3991        let mut model = new_empty_model();
3992        model._set("A1", "35");
3993        model._set("A2", "");
3994        let worksheet = model.workbook.worksheet(0).expect("Invalid sheet");
3995
3996        assert_eq!(
3997            worksheet.cell(1, 1),
3998            Some(&Cell::NumberCell { v: 35.0, s: 0 })
3999        );
4000
4001        // Clears the content of A2 but not the style
4002        assert_eq!(worksheet.cell(2, 1), Some(&Cell::EmptyCell { s: 0 }));
4003        assert_eq!(worksheet.cell(3, 1), None)
4004    }
4005
4006    #[test]
4007    fn test_get_cell_invalid_sheet() {
4008        let model = new_empty_model();
4009        assert_eq!(
4010            model.workbook.worksheet(5),
4011            Err("Invalid sheet index".to_string()),
4012        )
4013    }
4014
4015    #[test]
4016    fn test_update_cell_with_sign_prefixed_formulas() {
4017        let mut model = new_empty_model();
4018
4019        let update_result = model.update_cell_with_formula(0, 1, 1, "-A2*2".to_string());
4020        model.evaluate();
4021        assert_eq!(update_result, Ok(()));
4022        assert_eq!(model._get_formula("A1"), *"=-A2*2");
4023    }
4024}