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