Skip to main content

visi_core/core/engine/sheet/
mod.rs

1mod edit;
2mod functions;
3
4pub use edit::get_word_boundaries_from_str;
5
6use serde::{Deserialize, Serialize};
7use std::collections::{HashMap, HashSet, VecDeque};
8use web_time::Instant;
9
10use super::cell::{
11    CellRef, CellType, Dependency, EngineError, EvalError, TextCellRef, generate_unique_id,
12};
13use super::column::DataColumn;
14use super::result_data::ResultData;
15/// Context for evaluating expressions, containing references to other sheets
16#[derive(Default)]
17pub struct Context<'a> {
18    /// Map of sheet names to sheet references for cross-sheet lookups
19    pub sheets: HashMap<String, &'a Sheet>,
20    /// Every pivot table in the workbook, so `GETPIVOTDATA` can resolve a
21    /// rendered pivot's destination cell back to its definition. Pivot
22    /// tables are workbook-level (like `Context.sheets`' cross-sheet
23    /// lookups), not sheet-scoped, so this lives here rather than on
24    /// `Sheet` itself.
25    pub pivot_tables: &'a [crate::core::pivot::PivotTable],
26    /// Sheet names in true workbook order, so `SHEET()` can report a real
27    /// ordinal. `sheets` is an unordered `HashMap`, which is why this is
28    /// tracked separately rather than derived from it -- true order only
29    /// exists one layer up, in `visi`'s `WorkbookManager::sheets` (a
30    /// `Vec`), which populates this when building the context.
31    pub sheet_order: Vec<String>,
32}
33
34impl<'a> Context<'a> {
35    /// Create a new empty context
36    pub fn new() -> Self {
37        Self {
38            sheets: HashMap::new(),
39            pivot_tables: &[],
40            sheet_order: Vec::new(),
41        }
42    }
43
44    /// Add a sheet to the context for lookup during evaluation
45    pub fn add_table(&mut self, name: String, sheet: &'a Sheet) {
46        self.sheets.insert(name, sheet);
47    }
48}
49
50enum LetScope<'a> {
51    Empty,
52    Bound {
53        name: &'a str,
54        value: &'a ResultData,
55        parent: &'a LetScope<'a>,
56    },
57}
58
59impl<'a> LetScope<'a> {
60    fn get(&self, name: &str) -> Option<&ResultData> {
61        match self {
62            LetScope::Empty => None,
63            LetScope::Bound {
64                name: n,
65                value,
66                parent,
67            } => {
68                if n.eq_ignore_ascii_case(name) {
69                    Some(value)
70                } else {
71                    parent.get(name)
72                }
73            }
74        }
75    }
76}
77
78/// Which way a fill or selection extends from its anchor cell.
79#[derive(Debug, Clone, Copy, PartialEq)]
80pub enum Direction {
81    /// No direction; the operation is a no-op.
82    None,
83    /// Toward row 0.
84    Up,
85    /// Toward the last row.
86    Down,
87    /// Toward column 0.
88    Left,
89    /// Toward the last column.
90    Right,
91}
92
93/// One worksheet: a grid of cells, the formulas over them, and the dependency
94/// graph that keeps them up to date.
95///
96/// # Coordinates
97///
98/// Everything here is **0-based `(row, col)`**. A1 notation exists only at the
99/// parser and CLI boundaries -- see [`parse_a1_coordinates`] and
100/// [`col_idx_to_letters`] to convert.
101///
102/// # Naming trap
103///
104/// A `Sheet` is informally called a "table" in places (a new one is named
105/// `table_1`, and `Context::add_table` registers one). That is *not* an
106/// [`ExcelTable`], which is a ListObject -- a named rectangular range *on* a
107/// sheet -- and lives in [`Sheet::tables`].
108///
109/// # Storage
110///
111/// Storage is column-oriented: each [`DataColumn`] keeps the raw user text,
112/// the computed values and the compiled formulas in three parallel vectors
113/// that must stay the same length. The row and column insert/delete paths
114/// maintain that invariant by hand, so a new one has to do the same.
115///
116/// # Recalculation
117///
118/// [`Sheet::commit`] recomputes the dirty cells and propagates through
119/// [`Dependency::Local`] and [`Dependency::LocalColumn`] edges only.
120/// Cross-sheet edges are `WorkbookManager::evaluate`'s job, and evaluating a
121/// formula with a remote reference requires a [`Context`] -- without one it
122/// errors.
123///
124/// [`parse_a1_coordinates`]: crate::core::parse_a1_coordinates
125/// [`col_idx_to_letters`]: crate::core::col_idx_to_letters
126/// [`ExcelTable`]: crate::core::table::ExcelTable
127#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct Sheet {
129    /// Workbook-unique identifier. Formulas compile references against this
130    /// rather than the name, which is what makes a rename non-destructive.
131    #[serde(default = "generate_unique_id")]
132    pub id: u64,
133    /// Display name, as it appears in a cross-sheet reference.
134    pub name: String,
135    /// The cells, one entry per column. Row `r` of column `c` is
136    /// `columns[c]`'s entry `r`.
137    ///
138    /// Every column has the same number of rows -- [`Sheet::row_count`] reads
139    /// only the first and assumes the rest match -- so the `Vec` itself is
140    /// crate-private. Read them through [`Sheet::columns`].
141    pub(crate) columns: Vec<DataColumn>,
142    /// [AI-Agent] Excel/OpenXML row heights in point units, aligned with sheet rows.
143    #[serde(default)]
144    pub(crate) row_heights: Vec<Option<f64>>,
145    /// Excel Tables (ListObjects) defined on this sheet.
146    #[serde(default)]
147    pub tables: Vec<crate::core::table::ExcelTable>,
148    /// Forward edges: which cells must be recomputed when a dependency
149    /// changes. Rebuilt from the formulas, so not serialized.
150    #[serde(skip, default)]
151    pub dependencies: HashMap<Dependency, HashSet<CellRef>>,
152    /// Reverse edges: what each cell currently reads, so its old edges can be
153    /// dropped when its formula changes. Rebuilt, so not serialized.
154    #[serde(skip, default)]
155    pub dependencies_rev: HashMap<CellRef, HashSet<Dependency>>,
156    /// Edits made since the last commit, for callers that want to observe or
157    /// replay them.
158    #[serde(skip)]
159    pub uncommitted_actions: Vec<crate::core::SheetAction>,
160    /// Regional locale for date and number parsing.
161    #[serde(default)]
162    pub locale: crate::core::locale::Locale,
163}
164
165/// Arguments for [`Sheet::new`]. [`Default`] gives a 10x5 sheet with a
166/// generated id and the name `table_1`.
167#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct SheetInit {
169    /// Identifier to use; `None` generates a fresh one.
170    #[serde(default)]
171    pub id: Option<u64>,
172    /// Name to use; `None` means `table_1`.
173    pub name: Option<String>,
174    /// Rows to allocate.
175    pub rows: usize,
176    /// Columns to allocate.
177    pub cols: usize,
178}
179
180impl Default for SheetInit {
181    fn default() -> Self {
182        Self {
183            id: None,
184            name: None,
185            rows: 10,
186            cols: 5,
187        }
188    }
189}
190
191#[derive(Clone, Copy, PartialEq, Eq)]
192enum BlankPolicy {
193    /// Counts as 0 (MULTINOMIAL).
194    Zero,
195    /// Dropped entirely, shifting later elements (SERIESSUM).
196    Skip,
197    /// #VALUE!, like text (LINEST/TREND/GROWTH/LOGEST/MMULT).
198    Reject,
199}
200
201impl Sheet {
202    /// Creates a sheet of `args.rows` x `args.cols` empty cells, every one of
203    /// them queued as a pending edit so the first [`Sheet::commit`] sees them.
204    pub fn new(args: SheetInit) -> Sheet {
205        let SheetInit {
206            id,
207            name,
208            rows,
209            cols,
210        } = args;
211        let sheet_id = id.unwrap_or_else(generate_unique_id);
212        let sheet_name = name.unwrap_or_else(|| "table_1".to_string());
213
214        let mut columns = Vec::with_capacity(cols);
215        for _ in 0..cols {
216            columns.push(DataColumn::new(rows));
217        }
218
219        let mut uncommitted_actions = Vec::new();
220        for c in 0..cols {
221            for r in 0..rows {
222                uncommitted_actions.push(crate::core::SheetAction::SetCellSrc {
223                    sheet_name: sheet_name.clone(),
224                    col: c,
225                    row: r,
226                    src: String::new(),
227                });
228            }
229        }
230
231        Self {
232            id: sheet_id,
233            name: sheet_name,
234            columns,
235            row_heights: vec![None; rows],
236            tables: Vec::new(),
237            dependencies: HashMap::new(),
238            dependencies_rev: HashMap::new(),
239            uncommitted_actions,
240            locale: crate::core::locale::Locale::default(),
241        }
242    }
243
244    /// Rebuilds what serialization drops.
245    ///
246    /// Only the raw source text is persisted, so this resizes the value and
247    /// compiled-formula vectors back to match it -- restoring the
248    /// same-length invariant -- and marks everything dirty. Call it after
249    /// deserializing, before [`Sheet::commit`].
250    pub fn setup_after_deserialization(&mut self) {
251        for col in &mut self.columns {
252            col.rebuild_after_load();
253        }
254        let row_count = self.row_count();
255        self.row_heights.resize(row_count, None);
256        self.mark_all_dirty();
257    }
258
259    pub(crate) fn get_all_sheets_for_compilation(&self, context: Option<&Context>) -> Vec<Sheet> {
260        let mut list = vec![self.clone()];
261        let mut seen = std::collections::HashSet::new();
262        seen.insert(self.id);
263        if let Some(ctx) = context {
264            for sheet in ctx.sheets.values() {
265                if !seen.contains(&sheet.id) {
266                    seen.insert(sheet.id);
267                    list.push((*sheet).clone());
268                }
269            }
270        }
271        list
272    }
273
274    /// Queues every cell for recomputation on the next [`Sheet::commit`].
275    ///
276    /// This is how cross-sheet staleness is handled: `WorkbookManager` cannot
277    /// tell which cells a remote edit reached, so it marks whole sheets.
278    pub fn mark_all_dirty(&mut self) {
279        for col in &mut self.columns {
280            col.dirty_indices.clear();
281            col.dirty_indices.extend(0..col.src.len());
282        }
283    }
284
285    /// Commit all changed src items with a context for sheet lookups
286    pub fn commit(&mut self, context: Option<&Context>) -> Result<HashSet<CellRef>, EngineError> {
287        let mut queue: VecDeque<CellRef> = VecDeque::new();
288        let mut queue_set: HashSet<CellRef> = HashSet::new();
289        let mut updated_cells: HashSet<CellRef> = HashSet::new();
290
291        for (col_idx, col_data) in self.columns.iter_mut().enumerate() {
292            for row_idx in &col_data.dirty_indices {
293                let cell = CellRef::new(*row_idx, col_idx);
294                queue.push_back(cell);
295                queue_set.insert(cell);
296                updated_cells.insert(cell);
297            }
298            col_data.dirty_indices.clear();
299        }
300
301        let initial_queue_len = queue.len();
302        if initial_queue_len == 0 {
303            return Ok(updated_cells);
304        }
305
306        let start_commit = Instant::now();
307        log::info!(
308            "Sheet '{}' commit starting for {} dirty cells",
309            self.name,
310            initial_queue_len
311        );
312        let max_ops = 10000.max(initial_queue_len * 3);
313        let mut ops = 0;
314
315        let mut sheets_for_compilation = self.get_all_sheets_for_compilation(context);
316        let mut last_log_time = Instant::now();
317
318        while let Some(cell_ref) = queue.pop_front() {
319            queue_set.remove(&cell_ref);
320            ops += 1;
321            if ops > max_ops {
322                println!("Circular dependency or too many updates detected");
323                break;
324            }
325
326            if ops % 50000 == 0 {
327                log::info!(
328                    "Sheet '{}' commit progress: {}/{} cells processed ({:.2?})",
329                    self.name,
330                    ops,
331                    initial_queue_len,
332                    last_log_time.elapsed()
333                );
334                last_log_time = Instant::now();
335            }
336
337            let cell_type_hint = self
338                .columns
339                .get(cell_ref.col)
340                .and_then(|c| c.cell_types.get(cell_ref.row).copied())
341                .unwrap_or(CellType::Empty);
342
343            let mut detected_num_format: Option<String> = None;
344            let (result, new_deps, compiled_to_cache, mut final_cell_type) = {
345                let src = self.get_src_str_ref(&cell_ref).unwrap_or("");
346                if !src.starts_with('=') && cell_type_hint == CellType::String {
347                    let val = if src.starts_with('"') && src.ends_with('"') && src.len() >= 2 {
348                        src[1..src.len() - 1].to_string()
349                    } else {
350                        src.to_string()
351                    };
352                    (ResultData::String(val), vec![], None, CellType::String)
353                } else if !src.starts_with('=') {
354                    let (res, c_type) = if let Some(stripped) = src.strip_prefix('\'') {
355                        (ResultData::String(stripped.to_string()), CellType::String)
356                    } else if matches!(
357                        cell_type_hint,
358                        CellType::DateTimeIso | CellType::DurationIso
359                    ) {
360                        (ResultData::String(src.to_string()), cell_type_hint)
361                    } else if cell_type_hint == CellType::DateTime {
362                        if let Ok(f) = src.trim().parse::<f64>() {
363                            (ResultData::Float(f), CellType::DateTime)
364                        } else if let Some((date, format)) =
365                            crate::core::date::parse_date_with_locale(
366                                src.trim_matches(' '),
367                                &self.locale,
368                            )
369                        {
370                            detected_num_format = Some(format.to_format_code());
371                            (
372                                ResultData::Float(crate::core::date::date_to_excel_serial(date)),
373                                CellType::DateTime,
374                            )
375                        } else if let Some(f) = crate::core::date_fn::parse_time_fraction(src) {
376                            (ResultData::Float(f), CellType::DateTime)
377                        } else {
378                            (ResultData::String(src.to_string()), CellType::String)
379                        }
380                    } else if cell_type_hint == CellType::Float {
381                        if let Ok(f) = src.trim().parse::<f64>()
382                            && f.is_finite()
383                        {
384                            (ResultData::Float(f), CellType::Float)
385                        } else {
386                            (ResultData::String(src.to_string()), CellType::String)
387                        }
388                    } else if cell_type_hint == CellType::Int {
389                        if let Ok(i) = src.trim().parse::<i64>() {
390                            (ResultData::Integer(i), CellType::Int)
391                        } else {
392                            (ResultData::String(src.to_string()), CellType::String)
393                        }
394                    } else if cell_type_hint == CellType::Bool {
395                        if src == "1" || src.eq_ignore_ascii_case("true") {
396                            (ResultData::Boolean(true), CellType::Bool)
397                        } else if src == "0" || src.eq_ignore_ascii_case("false") {
398                            (ResultData::Boolean(false), CellType::Bool)
399                        } else {
400                            (ResultData::String(src.to_string()), CellType::String)
401                        }
402                    } else if cell_type_hint == CellType::Error {
403                        (ResultData::Error(src.to_uppercase()), CellType::Error)
404                    } else if src.is_empty() {
405                        (ResultData::None, CellType::Empty)
406                    } else if src.starts_with('"') && src.ends_with('"') && src.len() >= 2 {
407                        (
408                            ResultData::String(src[1..src.len() - 1].to_string()),
409                            CellType::String,
410                        )
411                    } else if let Ok(i) = src.trim().parse::<i64>() {
412                        (ResultData::Integer(i), CellType::Int)
413                    } else if let Ok(f) = src.trim().parse::<f64>()
414                        && f.is_finite()
415                    {
416                        (ResultData::Float(f), CellType::Float)
417                    } else if crate::core::engine::result_data::is_excel_error_code(src) {
418                        (ResultData::Error(src.to_uppercase()), CellType::Error)
419                    } else if src.eq_ignore_ascii_case("true") {
420                        (ResultData::Boolean(true), CellType::Bool)
421                    } else if src.eq_ignore_ascii_case("false") {
422                        (ResultData::Boolean(false), CellType::Bool)
423                    } else if let Some((date, format)) = crate::core::date::parse_date_with_locale(
424                        src.trim_matches(' '),
425                        &self.locale,
426                    ) {
427                        detected_num_format = Some(format.to_format_code());
428                        (
429                            ResultData::Float(crate::core::date::date_to_excel_serial(date)),
430                            CellType::DateTime,
431                        )
432                    } else if let Some(f) = crate::core::date_fn::parse_time_fraction(src) {
433                        (ResultData::Float(f), CellType::DateTime)
434                    } else {
435                        (ResultData::String(src.to_string()), CellType::String)
436                    };
437                    (res, vec![], None, c_type)
438                } else {
439                    let compiled =
440                        crate::core::parser::compile_formula(src, &sheets_for_compilation);
441                    let eval_src =
442                        crate::core::parser::serialize_formula(&compiled, &sheets_for_compilation);
443                    let (res, deps) = match self.eval_with_row(
444                        &eval_src,
445                        context,
446                        Some(cell_ref.row),
447                        Some(cell_ref.col),
448                    ) {
449                        Ok(r) => r,
450                        Err(e) => (ResultData::Error(e.to_string()), vec![]),
451                    };
452                    let final_res = if let ResultData::None = res {
453                        ResultData::Float(0.0)
454                    } else {
455                        res
456                    };
457                    let final_cell_type = match &final_res {
458                        ResultData::None => CellType::Empty,
459                        ResultData::Integer(_) => CellType::Int,
460                        ResultData::Float(_) => CellType::Float,
461                        ResultData::String(_) => CellType::String,
462                        ResultData::Boolean(_) => CellType::Bool,
463                        ResultData::Error(_) => CellType::Error,
464                        ResultData::List(_) | ResultData::Dict(_) => CellType::String,
465                    };
466                    (final_res, deps, Some(compiled), final_cell_type)
467                }
468            };
469
470            if let Some(src_str) = self.get_src_str_ref(&cell_ref)
471                && let Some(stripped) = src_str.strip_prefix('\'')
472            {
473                let stripped_str = stripped.to_string();
474                if let Some(col) = self.columns.get_mut(cell_ref.col)
475                    && cell_ref.row < col.src.len()
476                {
477                    col.src[cell_ref.row] = stripped_str;
478                }
479            }
480
481            if let Some(col) = self.columns.get_mut(cell_ref.col)
482                && cell_ref.row < col.compiled_src.len()
483            {
484                col.compiled_src[cell_ref.row] = compiled_to_cache.unwrap_or_default();
485            }
486
487            if let Some(old_deps) = self.dependencies_rev.remove(&cell_ref) {
488                for provider in old_deps {
489                    if let Some(dependents) = self.dependencies.get_mut(&provider) {
490                        dependents.remove(&cell_ref);
491                    }
492                }
493            }
494
495            if !new_deps.is_empty() {
496                let mut new_deps_set = HashSet::new();
497                for provider in new_deps {
498                    new_deps_set.insert(provider.clone());
499                    self.dependencies
500                        .entry(provider)
501                        .or_default()
502                        .insert(cell_ref);
503                }
504                self.dependencies_rev.insert(cell_ref, new_deps_set);
505            }
506
507            let inherited = if detected_num_format.is_some()
508                || !matches!(&result, ResultData::Float(_) | ResultData::Integer(_))
509            {
510                None
511            } else {
512                self.get_src_str_ref(&cell_ref)
513                    .and_then(|src| src.strip_prefix('='))
514                    .and_then(|body| crate::core::parser::parse_excel_formula(body).ok())
515                    .and_then(|ast| self.inherited_date_format(&ast))
516            };
517            if let Some(code) = detected_num_format.or(inherited) {
518                if matches!(&result, ResultData::Float(_) | ResultData::Integer(_)) {
519                    final_cell_type = CellType::DateTime;
520                }
521                let existing = self
522                    .get_cell_style(cell_ref.row, cell_ref.col)
523                    .and_then(|s| s.num_format.clone());
524                if existing.is_none() {
525                    self.update_cell_style(cell_ref.row, cell_ref.col, |style| {
526                        style.num_format = Some(code);
527                    });
528                }
529            }
530
531            if let Some(col) = self.columns.get_mut(cell_ref.col)
532                && cell_ref.row < col.data.len()
533            {
534                col.cell_types[cell_ref.row] = final_cell_type;
535                col.data.set(cell_ref.row, result.clone());
536                updated_cells.insert(cell_ref);
537            }
538            if let Some(comp_sheet) = sheets_for_compilation
539                .iter_mut()
540                .find(|s| s.name == self.name)
541                && let Some(col) = comp_sheet.columns.get_mut(cell_ref.col)
542                && cell_ref.row < col.data.len()
543            {
544                col.cell_types[cell_ref.row] = final_cell_type;
545                col.data.set(cell_ref.row, result);
546            }
547
548            let local_dep_key = Dependency::Local(cell_ref);
549            if let Some(dependents) = self.dependencies.get(&local_dep_key) {
550                for dependent in dependents {
551                    if !queue_set.contains(dependent) {
552                        queue.push_back(*dependent);
553                        queue_set.insert(*dependent);
554                    }
555                }
556            }
557
558            let local_col_dep_key = Dependency::LocalColumn(cell_ref.col);
559            if let Some(dependents) = self.dependencies.get(&local_col_dep_key) {
560                for dependent in dependents {
561                    if !queue_set.contains(dependent) {
562                        queue.push_back(*dependent);
563                        queue_set.insert(*dependent);
564                    }
565                }
566            }
567        }
568        if initial_queue_len > 0 {
569            log::info!(
570                "Sheet '{}' commit finished. Processed {} cell updates. Total time: {:.2?}",
571                self.name,
572                ops,
573                start_commit.elapsed()
574            );
575        }
576        Ok(updated_cells)
577    }
578
579    /// Evaluates cell source text without storing it, as
580    /// [`Sheet::eval`] does, but from the point of view of `(row, col)`.
581    ///
582    /// The position is what makes relative constructs work -- a structured
583    /// reference like `[@Amount]` means "this row", so it needs to know which
584    /// row is asking. Pass `None` for both when there is no anchor.
585    ///
586    /// # Errors
587    ///
588    /// Returns an [`EngineError`] if the formula cannot be parsed. An *Excel*
589    /// error is not a Rust error: `=1/0` succeeds, returning
590    /// `ResultData::Error("#DIV/0!")`.
591    pub fn eval_with_row(
592        &self,
593        input: &str,
594        context: Option<&Context>,
595        row: Option<usize>,
596        col: Option<usize>,
597    ) -> Result<(ResultData, Vec<Dependency>), EngineError> {
598        if input.is_empty() {
599            return Ok((ResultData::None, vec![]));
600        }
601        if let Some(formula) = input.strip_prefix('=') {
602            self.eval_excel(formula, context, row, col)
603        } else {
604            if let Ok(i) = input.parse::<i64>() {
605                Ok((ResultData::Integer(i), vec![]))
606            } else if let Ok(f) = input.parse::<f64>() {
607                Ok((ResultData::Float(f), vec![]))
608            } else if let Ok(b) = input.parse::<bool>() {
609                Ok((ResultData::Boolean(b), vec![]))
610            } else {
611                Ok((ResultData::String(input.to_string()), vec![]))
612            }
613        }
614    }
615
616    /// Evaluates cell source text against this sheet without storing it,
617    /// returning the value and the references it read.
618    ///
619    /// Text with a leading `=` is a formula; anything else is parsed as a
620    /// literal. `context` supplies the other sheets, and is required for a
621    /// cross-sheet reference to resolve.
622    ///
623    /// # Errors
624    ///
625    /// Returns an [`EngineError`] if the formula cannot be parsed. An *Excel*
626    /// error is not a Rust error: `=1/0` succeeds, returning
627    /// `ResultData::Error("#DIV/0!")`.
628    pub fn eval(
629        &self,
630        input: &str,
631        context: Option<&Context>,
632    ) -> Result<(ResultData, Vec<Dependency>), EngineError> {
633        self.eval_with_row(input, context, None, None)
634    }
635
636    fn eval_excel(
637        &self,
638        code: &str,
639        context: Option<&Context>,
640        row: Option<usize>,
641        col: Option<usize>,
642    ) -> Result<(ResultData, Vec<Dependency>), EngineError> {
643        let ast = crate::core::parser::parse_excel_formula(code)
644            .map_err(|e| EngineError::EvalError(EvalError::UnknownFunction(e)))?;
645
646        let mut deps = Vec::new();
647        let result = match self.evaluate_ast(&ast, context, row, col, &mut deps, &LetScope::Empty) {
648            Ok(r) => r,
649            Err(EngineError::EvalError(EvalError::UnknownFunction(err_str)))
650                if err_str.starts_with('#') =>
651            {
652                ResultData::Error(err_str)
653            }
654            Err(e) => return Err(e),
655        };
656        Ok((result, deps))
657    }
658
659    fn evaluate_ast(
660        &self,
661        ast: &crate::core::parser::Expr,
662        context: Option<&Context>,
663        row: Option<usize>,
664        col: Option<usize>,
665        deps: &mut Vec<Dependency>,
666        scope: &LetScope<'_>,
667    ) -> Result<ResultData, EngineError> {
668        use crate::core::SheetSection;
669        use crate::core::parser::Expr;
670        use crate::core::parser::Op;
671
672        match ast {
673            Expr::Number(n) => Ok(ResultData::Float(*n)),
674            Expr::String(s) => Ok(ResultData::String(s.clone())),
675            Expr::Boolean(b) => Ok(ResultData::Boolean(*b)),
676            Expr::Error(code) => Ok(ResultData::Error(code.to_string())),
677            Expr::Identifier(name) => match scope.get(name) {
678                Some(val) => Ok(val.clone()),
679                None => Ok(ResultData::Error("#NAME?".to_string())),
680            },
681            Expr::StructuredRef {
682                sheet,
683                column,
684                is_this_row,
685                section,
686            } => {
687                let ref_name = match sheet {
688                    Some(name) => name.clone(),
689                    None => self.name.clone(),
690                };
691
692                let mut found: Option<(&Sheet, &crate::core::table::ExcelTable)> =
693                    self.find_table(&ref_name).map(|t| (self, t));
694                if found.is_none()
695                    && let Some(ctx) = context
696                {
697                    for s in ctx.sheets.values() {
698                        if let Some(t) = s.find_table(&ref_name) {
699                            found = Some((s, t));
700                            break;
701                        }
702                    }
703                }
704
705                if let Some((table_sheet, excel_table)) = found {
706                    let is_self = table_sheet.name == self.name;
707                    let sheet_name = table_sheet.name.clone();
708
709                    let col_indices: Vec<(usize, usize)> = if let Some(col_name) = column {
710                        let local = excel_table.local_column_index(col_name).ok_or_else(|| {
711                            EngineError::EvalError(EvalError::UnknownFunction(format!(
712                                "Column not found: {}",
713                                col_name
714                            )))
715                        })?;
716                        vec![(local, excel_table.start_col + local)]
717                    } else {
718                        (0..excel_table.columns.len())
719                            .map(|local| (local, excel_table.start_col + local))
720                            .collect()
721                    };
722                    let is_whole_table = column.is_none();
723
724                    match section {
725                        SheetSection::Headers => {
726                            let names: Vec<ResultData> = col_indices
727                                .iter()
728                                .map(|&(local, _)| {
729                                    ResultData::String(
730                                        excel_table.columns.get(local).cloned().unwrap_or_default(),
731                                    )
732                                })
733                                .collect();
734                            if is_whole_table {
735                                Ok(ResultData::List(names))
736                            } else {
737                                Ok(names.into_iter().next().unwrap_or(ResultData::None))
738                            }
739                        }
740                        SheetSection::Totals => {
741                            if let Some(totals_row) = excel_table.totals_row() {
742                                let mut results = Vec::new();
743                                for &(_, col_idx) in &col_indices {
744                                    let cell_ref = CellRef::new(totals_row, col_idx);
745                                    if is_self {
746                                        deps.push(Dependency::Local(cell_ref));
747                                    } else {
748                                        deps.push(Dependency::Remote {
749                                            sheet: sheet_name.clone(),
750                                            cell: cell_ref,
751                                        });
752                                    }
753                                    results.push(table_sheet.get_result_data(&cell_ref));
754                                }
755                                if is_whole_table {
756                                    Ok(ResultData::List(results))
757                                } else {
758                                    Ok(results.into_iter().next().unwrap_or(ResultData::None))
759                                }
760                            } else {
761                                Ok(ResultData::None)
762                            }
763                        }
764                        SheetSection::Data | SheetSection::All => {
765                            if *is_this_row {
766                                let r = row.ok_or_else(|| {
767                                    EngineError::EvalError(EvalError::UnknownFunction(
768                                        "This row reference cannot be evaluated without row context"
769                                            .to_string(),
770                                    ))
771                                })?;
772                                let mut results = Vec::new();
773                                for &(_, col_idx) in &col_indices {
774                                    let cell_ref = CellRef::new(r, col_idx);
775                                    if is_self {
776                                        deps.push(Dependency::Local(cell_ref));
777                                    } else {
778                                        deps.push(Dependency::Remote {
779                                            sheet: sheet_name.clone(),
780                                            cell: cell_ref,
781                                        });
782                                    }
783                                    results.push(table_sheet.get_result_data(&cell_ref));
784                                }
785                                if is_whole_table {
786                                    Ok(ResultData::List(results))
787                                } else {
788                                    Ok(results.into_iter().next().unwrap_or(ResultData::None))
789                                }
790                            } else {
791                                let mut results = Vec::new();
792                                for &(_, col_idx) in &col_indices {
793                                    for r in
794                                        excel_table.data_start_row()..=excel_table.data_end_row()
795                                    {
796                                        let cell_ref = CellRef::new(r, col_idx);
797                                        if is_self {
798                                            deps.push(Dependency::Local(cell_ref));
799                                        } else {
800                                            deps.push(Dependency::Remote {
801                                                sheet: sheet_name.clone(),
802                                                cell: cell_ref,
803                                            });
804                                        }
805                                        results.push(table_sheet.get_result_data(&cell_ref));
806                                    }
807                                }
808                                Ok(ResultData::List(results))
809                            }
810                        }
811                    }
812                } else {
813                    let sheet_name = ref_name;
814                    let is_self = sheet_name == self.name;
815
816                    let target_sheet = if is_self {
817                        self
818                    } else if let Some(ctx) = context {
819                        if let Some(sheet) = ctx.sheets.get(&sheet_name) {
820                            sheet
821                        } else {
822                            return Err(EngineError::EvalError(EvalError::UnknownFunction(
823                                format!("Sheet not found: {}", sheet_name),
824                            )));
825                        }
826                    } else {
827                        return Err(EngineError::EvalError(EvalError::UnknownFunction(format!(
828                            "No context to resolve sheet reference: {}",
829                            sheet_name
830                        ))));
831                    };
832
833                    let col_indices: Vec<usize> = if let Some(col_name) = column {
834                        let pos = target_sheet
835                            .columns
836                            .iter()
837                            .position(|c| c.name == *col_name)
838                            .ok_or_else(|| {
839                                EngineError::EvalError(EvalError::UnknownFunction(format!(
840                                    "Column not found: {}",
841                                    col_name
842                                )))
843                            })?;
844                        vec![pos]
845                    } else {
846                        (0..target_sheet.columns.len()).collect()
847                    };
848                    let is_whole_table = column.is_none();
849
850                    match section {
851                        SheetSection::Headers => {
852                            let names: Vec<ResultData> = col_indices
853                                .iter()
854                                .map(|&idx| {
855                                    ResultData::String(
856                                        target_sheet
857                                            .columns
858                                            .get(idx)
859                                            .map(|c| c.name.clone())
860                                            .unwrap_or_default(),
861                                    )
862                                })
863                                .collect();
864                            if is_whole_table {
865                                Ok(ResultData::List(names))
866                            } else {
867                                Ok(names.into_iter().next().unwrap_or(ResultData::None))
868                            }
869                        }
870                        SheetSection::Totals => Ok(ResultData::None),
871                        SheetSection::Data | SheetSection::All => {
872                            if *is_this_row {
873                                let r = row.ok_or_else(|| {
874                                    EngineError::EvalError(EvalError::UnknownFunction(
875                                        "This row reference cannot be evaluated without row context"
876                                            .to_string(),
877                                    ))
878                                })?;
879                                let mut results = Vec::new();
880                                for &col_idx in &col_indices {
881                                    let cell_ref = CellRef::new(r, col_idx);
882                                    if is_self {
883                                        deps.push(Dependency::Local(cell_ref));
884                                    } else {
885                                        deps.push(Dependency::Remote {
886                                            sheet: sheet_name.clone(),
887                                            cell: cell_ref,
888                                        });
889                                    }
890                                    results.push(target_sheet.get_result_data(&cell_ref));
891                                }
892                                if is_whole_table {
893                                    Ok(ResultData::List(results))
894                                } else {
895                                    Ok(results.into_iter().next().unwrap_or(ResultData::None))
896                                }
897                            } else {
898                                let mut results = Vec::new();
899                                for &col_idx in &col_indices {
900                                    if is_self {
901                                        deps.push(Dependency::LocalColumn(col_idx));
902                                    } else {
903                                        deps.push(Dependency::RemoteColumn {
904                                            sheet: sheet_name.clone(),
905                                            col: col_idx,
906                                        });
907                                    }
908                                    for r in 0..target_sheet.row_count() {
909                                        let cell_ref = CellRef::new(r, col_idx);
910                                        results.push(target_sheet.get_result_data(&cell_ref));
911                                    }
912                                }
913                                Ok(ResultData::List(results))
914                            }
915                        }
916                    }
917                }
918            }
919            Expr::CellRef {
920                sheet,
921                row: r_val,
922                col,
923                ..
924            } => {
925                let cell_ref = CellRef::new(*r_val, *col);
926                let is_self = match sheet {
927                    Some(name) => name == &self.name,
928                    None => true,
929                };
930
931                if is_self {
932                    deps.push(Dependency::Local(cell_ref));
933                    Ok(self.get_result_data(&cell_ref))
934                } else {
935                    let name = sheet.as_ref().unwrap().clone();
936                    deps.push(Dependency::Remote {
937                        sheet: name.clone(),
938                        cell: cell_ref,
939                    });
940
941                    if let Some(ctx) = context {
942                        if let Some(t) = ctx.sheets.get(&name) {
943                            Ok(t.get_result_data(&cell_ref))
944                        } else {
945                            Err(EngineError::EvalError(EvalError::UnknownFunction(format!(
946                                "Sheet not found: {}",
947                                name
948                            ))))
949                        }
950                    } else {
951                        Err(EngineError::EvalError(EvalError::UnknownFunction(
952                            "No context to resolve sheet reference".to_string(),
953                        )))
954                    }
955                }
956            }
957            Expr::RangeRef {
958                sheet,
959                start_row,
960                start_col,
961                end_row,
962                end_col,
963                ..
964            } => {
965                let is_self = match sheet {
966                    Some(name) => name == &self.name,
967                    None => true,
968                };
969
970                let target_sheet = if is_self {
971                    Some(self)
972                } else {
973                    context.and_then(|ctx| ctx.sheets.get(sheet.as_ref().unwrap()).copied())
974                };
975
976                let actual_end_row = if *end_row == usize::MAX {
977                    target_sheet
978                        .map(|t| t.row_count().saturating_sub(1))
979                        .unwrap_or(0)
980                } else {
981                    *end_row
982                };
983                let actual_end_col = if *end_col == usize::MAX {
984                    target_sheet
985                        .map(|t| t.col_count().saturating_sub(1))
986                        .unwrap_or(0)
987                } else {
988                    *end_col
989                };
990
991                let is_col_range = *end_row == usize::MAX;
992
993                let mut seen_col_deps: HashSet<usize> = HashSet::new();
994
995                let mut results = Vec::new();
996                for r in *start_row..=actual_end_row {
997                    for c in *start_col..=actual_end_col {
998                        let cell_ref = CellRef::new(r, c);
999                        if is_self {
1000                            if is_col_range {
1001                                if seen_col_deps.insert(c) {
1002                                    let col_dep = Dependency::LocalColumn(c);
1003                                    if !deps.contains(&col_dep) {
1004                                        deps.push(col_dep);
1005                                    }
1006                                }
1007                            } else {
1008                                deps.push(Dependency::Local(cell_ref));
1009                            }
1010                            if row == Some(r) && col == Some(c) {
1011                                results.push(ResultData::None);
1012                            } else {
1013                                results.push(self.get_result_data(&cell_ref));
1014                            }
1015                        } else {
1016                            let name = sheet.as_ref().unwrap().clone();
1017                            if is_col_range {
1018                                if seen_col_deps.insert(c) {
1019                                    let col_dep = Dependency::RemoteColumn {
1020                                        sheet: name.clone(),
1021                                        col: c,
1022                                    };
1023                                    if !deps.contains(&col_dep) {
1024                                        deps.push(col_dep);
1025                                    }
1026                                }
1027                            } else {
1028                                deps.push(Dependency::Remote {
1029                                    sheet: name.clone(),
1030                                    cell: cell_ref,
1031                                });
1032                            }
1033                            if let Some(ctx) = context {
1034                                if let Some(t) = ctx.sheets.get(&name) {
1035                                    results.push(t.get_result_data(&cell_ref));
1036                                } else {
1037                                    return Err(EngineError::EvalError(
1038                                        EvalError::UnknownFunction(format!(
1039                                            "Sheet not found: {}",
1040                                            name
1041                                        )),
1042                                    ));
1043                                }
1044                            } else {
1045                                return Err(EngineError::EvalError(EvalError::UnknownFunction(
1046                                    "No context to resolve sheet reference".to_string(),
1047                                )));
1048                            }
1049                        }
1050                    }
1051                }
1052                Ok(ResultData::List(results))
1053            }
1054            Expr::List(list) => {
1055                let mut results = Vec::new();
1056                for item in list {
1057                    results.push(self.evaluate_ast(item, context, row, col, deps, scope)?);
1058                }
1059                Ok(ResultData::List(results))
1060            }
1061            Expr::Slice { expr, start, end } => {
1062                let target_val = self.evaluate_ast(expr, context, row, col, deps, scope)?;
1063                if let ResultData::Error(_) = &target_val {
1064                    return Ok(target_val);
1065                }
1066                if let ResultData::List(list) = target_val {
1067                    let len = list.len() as isize;
1068                    let start_idx = if let Some(start_expr) = start {
1069                        let s_val =
1070                            self.evaluate_ast(start_expr, context, row, col, deps, scope)?;
1071                        if let ResultData::Error(_) = &s_val {
1072                            return Ok(s_val);
1073                        }
1074                        let s = self.to_f64(&s_val).unwrap_or(0.0) as isize;
1075                        if s < 0 {
1076                            (len + s).max(0) as usize
1077                        } else {
1078                            s.min(len) as usize
1079                        }
1080                    } else {
1081                        0
1082                    };
1083
1084                    let end_idx = if let Some(end_expr) = end {
1085                        let e_val = self.evaluate_ast(end_expr, context, row, col, deps, scope)?;
1086                        if let ResultData::Error(_) = &e_val {
1087                            return Ok(e_val);
1088                        }
1089                        let e = self.to_f64(&e_val).unwrap_or(len as f64) as isize;
1090                        if e < 0 {
1091                            (len + e).max(0) as usize
1092                        } else {
1093                            e.min(len) as usize
1094                        }
1095                    } else {
1096                        len as usize
1097                    };
1098
1099                    let sliced = if start_idx < end_idx && start_idx < list.len() {
1100                        list[start_idx..end_idx.min(list.len())].to_vec()
1101                    } else {
1102                        Vec::new()
1103                    };
1104                    Ok(ResultData::List(sliced))
1105                } else {
1106                    Ok(ResultData::None)
1107                }
1108            }
1109            Expr::UnaryOp { op, expr } => {
1110                let val = self.evaluate_ast(expr, context, row, col, deps, scope)?;
1111                match op {
1112                    Op::Sub => match val {
1113                        ResultData::Float(f) => Ok(ResultData::Float(-f)),
1114                        ResultData::Integer(i) => Ok(ResultData::Integer(-i)),
1115                        _ => Err(EngineError::EvalError(EvalError::UnknownFunction(
1116                            "Unary minus expects number".to_string(),
1117                        ))),
1118                    },
1119                    _ => Ok(val),
1120                }
1121            }
1122            Expr::BinaryOp { op, left, right } => {
1123                let l_val = self.evaluate_ast(left, context, row, col, deps, scope)?;
1124
1125                match op {
1126                    Op::Eq | Op::Ne | Op::Lt | Op::Gt | Op::Le | Op::Ge => {
1127                        if let ResultData::Error(_) = &l_val {
1128                            return Ok(l_val);
1129                        }
1130                        let r_val = self.evaluate_ast(right, context, row, col, deps, scope)?;
1131                        if let ResultData::Error(_) = &r_val {
1132                            return Ok(r_val);
1133                        }
1134                        let ord = Self::compare_excel_values(&l_val, &r_val);
1135                        let b = match op {
1136                            Op::Eq => ord.is_eq(),
1137                            Op::Ne => !ord.is_eq(),
1138                            Op::Lt => ord.is_lt(),
1139                            Op::Gt => ord.is_gt(),
1140                            Op::Le => ord.is_le(),
1141                            Op::Ge => ord.is_ge(),
1142                            _ => unreachable!(),
1143                        };
1144                        Ok(ResultData::Boolean(b))
1145                    }
1146                    _ => {
1147                        if let ResultData::Error(_) = &l_val {
1148                            return Ok(l_val);
1149                        }
1150                        let lf = match self.to_f64(&l_val) {
1151                            Some(f) => f,
1152                            None => return Ok(ResultData::Error("#VALUE!".to_string())),
1153                        };
1154                        let r_val = self.evaluate_ast(right, context, row, col, deps, scope)?;
1155                        if let ResultData::Error(_) = &r_val {
1156                            return Ok(r_val);
1157                        }
1158                        let rf = match self.to_f64(&r_val) {
1159                            Some(f) => f,
1160                            None => return Ok(ResultData::Error("#VALUE!".to_string())),
1161                        };
1162                        match op {
1163                            Op::Add => Ok(ResultData::Float(lf + rf)),
1164                            Op::Sub => Ok(ResultData::Float(lf - rf)),
1165                            Op::Mul => Ok(ResultData::Float(lf * rf)),
1166                            Op::Div => {
1167                                if rf == 0.0 {
1168                                    return Ok(ResultData::Error("#DIV/0!".to_string()));
1169                                }
1170                                Ok(ResultData::Float(lf / rf))
1171                            }
1172                            Op::Exp => {
1173                                if lf == 0.0 && rf == 0.0 {
1174                                    return Ok(ResultData::Error("#NUM!".to_string()));
1175                                }
1176                                if lf == 0.0 && rf < 0.0 {
1177                                    return Ok(ResultData::Error("#DIV/0!".to_string()));
1178                                }
1179                                if lf < 0.0 {
1180                                    if rf.fract() != 0.0 || rf.abs() > 1e6 {
1181                                        return Ok(ResultData::Error("#NUM!".to_string()));
1182                                    }
1183                                    let res = lf.powi(rf as i32);
1184                                    if res.is_nan() || res.is_infinite() {
1185                                        return Ok(ResultData::Error("#NUM!".to_string()));
1186                                    }
1187                                    return Ok(ResultData::Float(res));
1188                                }
1189                                let res = lf.powf(rf);
1190                                if res.is_nan() || res.is_infinite() {
1191                                    return Ok(ResultData::Error("#NUM!".to_string()));
1192                                }
1193                                Ok(ResultData::Float(res))
1194                            }
1195                            _ => unreachable!(),
1196                        }
1197                    }
1198                }
1199            }
1200            Expr::FunctionCall { name, args } => {
1201                self.evaluate_function(name, args, context, row, col, deps, scope)
1202            }
1203        }
1204    }
1205
1206    fn excel_type_rank(val: &ResultData) -> u8 {
1207        match val {
1208            ResultData::None => 0,
1209            ResultData::Integer(_) | ResultData::Float(_) => 1,
1210            ResultData::String(_) => 2,
1211            ResultData::Boolean(_) => 3,
1212            _ => 4,
1213        }
1214    }
1215
1216    fn compare_excel_values(l: &ResultData, r: &ResultData) -> std::cmp::Ordering {
1217        match (l, r) {
1218            (ResultData::None, ResultData::None) => return std::cmp::Ordering::Equal,
1219            (ResultData::None, ResultData::Integer(b)) => {
1220                return 0.0
1221                    .partial_cmp(&(*b as f64))
1222                    .unwrap_or(std::cmp::Ordering::Equal);
1223            }
1224            (ResultData::None, ResultData::Float(b)) => {
1225                return 0.0.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal);
1226            }
1227            (ResultData::Integer(a), ResultData::None) => {
1228                return (*a as f64)
1229                    .partial_cmp(&0.0)
1230                    .unwrap_or(std::cmp::Ordering::Equal);
1231            }
1232            (ResultData::Float(a), ResultData::None) => {
1233                return a.partial_cmp(&0.0).unwrap_or(std::cmp::Ordering::Equal);
1234            }
1235            (ResultData::None, ResultData::String(b)) => {
1236                return "".cmp(b.to_lowercase().as_str());
1237            }
1238            (ResultData::String(a), ResultData::None) => {
1239                return a.to_lowercase().as_str().cmp("");
1240            }
1241            (ResultData::None, ResultData::Boolean(b)) => {
1242                return false.cmp(b);
1243            }
1244            (ResultData::Boolean(a), ResultData::None) => {
1245                return a.cmp(&false);
1246            }
1247            _ => {}
1248        }
1249
1250        let rank_l = Self::excel_type_rank(l);
1251        let rank_r = Self::excel_type_rank(r);
1252        if rank_l != rank_r {
1253            return rank_l.cmp(&rank_r);
1254        }
1255        match (l, r) {
1256            (ResultData::Integer(a), ResultData::Integer(b)) => a.cmp(b),
1257            (ResultData::Float(a), ResultData::Float(b)) => {
1258                a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
1259            }
1260            (ResultData::Integer(a), ResultData::Float(b)) => (*a as f64)
1261                .partial_cmp(b)
1262                .unwrap_or(std::cmp::Ordering::Equal),
1263            (ResultData::Float(a), ResultData::Integer(b)) => a
1264                .partial_cmp(&(*b as f64))
1265                .unwrap_or(std::cmp::Ordering::Equal),
1266            (ResultData::Boolean(a), ResultData::Boolean(b)) => a.cmp(b),
1267            (ResultData::String(a), ResultData::String(b)) => Self::compare_excel_strings(a, b),
1268            _ => std::cmp::Ordering::Equal,
1269        }
1270    }
1271
1272    fn sort_compare_blanks_last(
1273        l: &ResultData,
1274        r: &ResultData,
1275        sort_order: f64,
1276    ) -> std::cmp::Ordering {
1277        match (matches!(l, ResultData::None), matches!(r, ResultData::None)) {
1278            (true, true) => std::cmp::Ordering::Equal,
1279            (true, false) => std::cmp::Ordering::Greater,
1280            (false, true) => std::cmp::Ordering::Less,
1281            (false, false) => {
1282                let ord = Self::compare_excel_values(l, r);
1283                if sort_order < 0.0 { ord.reverse() } else { ord }
1284            }
1285        }
1286    }
1287
1288    fn is_excel_number_str(s: &str) -> bool {
1289        let s = s.trim();
1290        if s.is_empty() {
1291            return false;
1292        }
1293        let bytes = s.as_bytes();
1294        let first = bytes[0];
1295        if first == b'e' || first == b'E' {
1296            return false;
1297        }
1298        if (first == b'+' || first == b'-') && bytes.len() > 1 {
1299            let second = bytes[1];
1300            if second == b'e' || second == b'E' {
1301                return false;
1302            }
1303        }
1304        true
1305    }
1306
1307    fn compare_excel_strings(a: &str, b: &str) -> std::cmp::Ordering {
1308        let char_weight = |ch: char| -> u32 {
1309            match ch {
1310                ' ' => 0,
1311                '_' => 1,
1312                '-' => 2,
1313                ',' => 3,
1314                ';' => 4,
1315                ':' => 5,
1316                '!' => 6,
1317                '?' => 7,
1318                '.' => 8,
1319                '\'' => 9,
1320                '"' => 10,
1321                '(' => 11,
1322                ')' => 12,
1323                '[' => 13,
1324                ']' => 14,
1325                '{' => 15,
1326                '}' => 16,
1327                '@' => 17,
1328                '*' => 18,
1329                '/' => 19,
1330                '\\' => 20,
1331                '&' => 21,
1332                '#' => 22,
1333                '%' => 23,
1334                '`' => 24,
1335                '^' => 25,
1336                '+' => 26,
1337                '<' => 27,
1338                '=' => 28,
1339                '>' => 29,
1340                '|' => 30,
1341                '~' => 31,
1342                '$' => 32,
1343                '0'..='9' => 33 + (ch as u32 - '0' as u32),
1344                'A'..='Z' => 43 + (ch as u32 - 'A' as u32),
1345                'a'..='z' => 43 + (ch as u32 - 'a' as u32),
1346                _ => ch
1347                    .to_lowercase()
1348                    .next()
1349                    .map(|c| c as u32 + 200)
1350                    .unwrap_or(ch as u32 + 200),
1351            }
1352        };
1353
1354        for (ca, cb) in a.chars().zip(b.chars()) {
1355            let wa = char_weight(ca);
1356            let wb = char_weight(cb);
1357            if wa != wb {
1358                return wa.cmp(&wb);
1359            }
1360        }
1361        a.len().cmp(&b.len())
1362    }
1363
1364    pub(crate) fn clean_float(val: f64) -> f64 {
1365        if val == 0.0 || !val.is_finite() {
1366            return val;
1367        }
1368        let abs_val = val.abs();
1369        let exp = abs_val.log10().floor() as i32;
1370        let factor = 10.0f64.powi(15 - 1 - exp);
1371        if factor.is_finite() && factor != 0.0 {
1372            let rounded = (val * factor).round() / factor;
1373            if (val - rounded).abs() <= 1e-14 * abs_val {
1374                return rounded;
1375            }
1376        }
1377        val
1378    }
1379
1380    pub(crate) fn to_f64(&self, val: &ResultData) -> Option<f64> {
1381        match val {
1382            ResultData::None => Some(0.0),
1383            ResultData::Float(f) => Some(*f),
1384            ResultData::Integer(i) => Some(*i as f64),
1385            ResultData::Boolean(b) => Some(if *b { 1.0 } else { 0.0 }),
1386            ResultData::String(s) => {
1387                let s_trim = s.trim();
1388                if Self::is_excel_number_str(s_trim) {
1389                    if let Ok(f) = s_trim.parse::<f64>() {
1390                        return Some(f);
1391                    }
1392                    if let Some((date, _)) =
1393                        crate::core::date::parse_date_with_locale(s_trim, &self.locale)
1394                    {
1395                        return Some(crate::core::date::date_to_excel_serial(date));
1396                    }
1397                    None
1398                } else if let Some((date, _)) =
1399                    crate::core::date::parse_date_with_locale(s_trim, &self.locale)
1400                {
1401                    Some(crate::core::date::date_to_excel_serial(date))
1402                } else {
1403                    None
1404                }
1405            }
1406            _ => None,
1407        }
1408    }
1409
1410    fn to_f64_arg(&self, arg_opt: Option<&ResultData>, fn_name: &str) -> Result<f64, EngineError> {
1411        let val = arg_opt.ok_or_else(|| {
1412            EngineError::EvalError(EvalError::UnknownFunction(format!(
1413                "{} requires argument",
1414                fn_name
1415            )))
1416        })?;
1417        if let ResultData::Error(e) = val {
1418            return Err(EngineError::EvalError(EvalError::UnknownFunction(
1419                e.clone(),
1420            )));
1421        }
1422        self.to_f64(val).ok_or_else(|| {
1423            EngineError::EvalError(EvalError::UnknownFunction("#VALUE!".to_string()))
1424        })
1425    }
1426
1427    fn find_error_in_args(args: &[ResultData]) -> Option<ResultData> {
1428        for arg in args {
1429            match arg {
1430                ResultData::Error(_) => return Some(arg.clone()),
1431                ResultData::List(list) => {
1432                    if let Some(err) = Self::find_error_in_args(list) {
1433                        return Some(err);
1434                    }
1435                }
1436                _ => {}
1437            }
1438        }
1439        None
1440    }
1441
1442    fn check_arg_errors(&self, args: &[ResultData], is_direct: &[bool]) -> Option<ResultData> {
1443        for (i, arg) in args.iter().enumerate() {
1444            match arg {
1445                ResultData::Error(_) => return Some(arg.clone()),
1446                ResultData::List(list) => {
1447                    if let Some(err) = self.check_arg_errors(list, &[]) {
1448                        return Some(err);
1449                    }
1450                }
1451                ResultData::String(_)
1452                    if is_direct.get(i).copied().unwrap_or(false) && self.to_f64(arg).is_none() =>
1453                {
1454                    return Some(ResultData::Error("#VALUE!".to_string()));
1455                }
1456                _ => {}
1457            }
1458        }
1459        None
1460    }
1461
1462    fn sum_helper(&self, arg: &ResultData, is_direct: bool) -> f64 {
1463        match arg {
1464            ResultData::Float(f) => *f,
1465            ResultData::Integer(i) => *i as f64,
1466            ResultData::Boolean(b) => {
1467                if is_direct {
1468                    if *b { 1.0 } else { 0.0 }
1469                } else {
1470                    0.0
1471                }
1472            }
1473            ResultData::String(_) => {
1474                if is_direct {
1475                    self.to_f64(arg).unwrap_or(0.0)
1476                } else {
1477                    0.0
1478                }
1479            }
1480            ResultData::List(list) => {
1481                let mut sum = 0.0;
1482                for item in list {
1483                    sum += self.sum_helper(item, false);
1484                }
1485                sum
1486            }
1487            _ => 0.0,
1488        }
1489    }
1490
1491    fn flatten_finance_numbers(&self, arg: &ResultData, is_direct: bool) -> Vec<f64> {
1492        match arg {
1493            ResultData::Float(f) => vec![*f],
1494            ResultData::Integer(i) => vec![*i as f64],
1495            ResultData::Boolean(b) => {
1496                if is_direct {
1497                    vec![if *b { 1.0 } else { 0.0 }]
1498                } else {
1499                    vec![]
1500                }
1501            }
1502            ResultData::String(_) => {
1503                if is_direct {
1504                    self.to_f64(arg).into_iter().collect()
1505                } else {
1506                    vec![]
1507                }
1508            }
1509            ResultData::List(list) => list
1510                .iter()
1511                .flat_map(|v| self.flatten_finance_numbers(v, false))
1512                .collect(),
1513            _ => vec![],
1514        }
1515    }
1516
1517    fn flatten_stat_numbers(&self, arg: &ResultData, is_direct: bool) -> Vec<f64> {
1518        match arg {
1519            ResultData::Float(f) => vec![*f],
1520            ResultData::Integer(i) => vec![*i as f64],
1521            ResultData::Boolean(b) => {
1522                if is_direct {
1523                    vec![if *b { 1.0 } else { 0.0 }]
1524                } else {
1525                    vec![]
1526                }
1527            }
1528            ResultData::String(_) => {
1529                if is_direct {
1530                    self.to_f64(arg).into_iter().collect()
1531                } else {
1532                    vec![]
1533                }
1534            }
1535            ResultData::List(list) => list
1536                .iter()
1537                .flat_map(|v| self.flatten_stat_numbers(v, false))
1538                .collect(),
1539            _ => vec![],
1540        }
1541    }
1542
1543    fn flatten_positional(
1544        &self,
1545        arg: &ResultData,
1546        out: &mut Vec<Option<f64>>,
1547        first_err: &mut Option<String>,
1548    ) {
1549        match arg {
1550            ResultData::List(items) => {
1551                for item in items {
1552                    self.flatten_positional(item, out, first_err);
1553                }
1554            }
1555            ResultData::Float(f) => out.push(Some(*f)),
1556            ResultData::Integer(i) => out.push(Some(*i as f64)),
1557            ResultData::Error(e) => {
1558                if first_err.is_none() {
1559                    *first_err = Some(e.clone());
1560                }
1561                out.push(None);
1562            }
1563            _ => out.push(None),
1564        }
1565    }
1566
1567    fn positional_numbers(
1568        &self,
1569        arg: Option<&ResultData>,
1570        first_err: &mut Option<String>,
1571    ) -> Vec<Option<f64>> {
1572        let mut out = Vec::new();
1573        if let Some(a) = arg {
1574            self.flatten_positional(a, &mut out, first_err);
1575        }
1576        out
1577    }
1578
1579    fn pair_and_filter(
1580        xs_raw: Vec<Option<f64>>,
1581        ys_raw: Vec<Option<f64>>,
1582    ) -> Result<(Vec<f64>, Vec<f64>), String> {
1583        if xs_raw.len() != ys_raw.len() {
1584            return Err("#N/A".to_string());
1585        }
1586        let mut xs = Vec::with_capacity(xs_raw.len());
1587        let mut ys = Vec::with_capacity(ys_raw.len());
1588        for (x, y) in xs_raw.into_iter().zip(ys_raw) {
1589            if let (Some(x), Some(y)) = (x, y) {
1590                xs.push(x);
1591                ys.push(y);
1592            }
1593        }
1594        Ok((xs, ys))
1595    }
1596
1597    fn paired_args(
1598        &self,
1599        x_arg: Option<&ResultData>,
1600        y_arg: Option<&ResultData>,
1601    ) -> Result<(Vec<f64>, Vec<f64>), String> {
1602        for arg in [x_arg, y_arg].into_iter().flatten() {
1603            let scalar = match arg {
1604                ResultData::List(items) if items.len() == 1 => &items[0],
1605                other => other,
1606            };
1607            if let ResultData::Error(e) = scalar {
1608                return Err(e.clone());
1609            }
1610            if Self::is_empty_scalar_operand(arg) {
1611                return Err("#VALUE!".to_string());
1612            }
1613        }
1614        let mut first_err = None;
1615        let xs_raw = self.positional_numbers(x_arg, &mut first_err);
1616        let ys_raw = self.positional_numbers(y_arg, &mut first_err);
1617        if xs_raw.len() != ys_raw.len() {
1618            return Err("#N/A".to_string());
1619        }
1620        if let Some(e) = first_err {
1621            return Err(e);
1622        }
1623        Self::pair_and_filter(xs_raw, ys_raw)
1624    }
1625
1626    fn flatten_strict_inner(
1627        &self,
1628        arg: &ResultData,
1629        blanks: BlankPolicy,
1630        coerce_text: bool,
1631        out: &mut Vec<f64>,
1632    ) -> Result<(), String> {
1633        match arg {
1634            ResultData::List(items) => {
1635                for item in items {
1636                    self.flatten_strict_inner(item, blanks, coerce_text, out)?;
1637                }
1638                Ok(())
1639            }
1640            ResultData::Error(e) => Err(e.clone()),
1641            ResultData::Float(f) => {
1642                out.push(*f);
1643                Ok(())
1644            }
1645            ResultData::Integer(i) => {
1646                out.push(*i as f64);
1647                Ok(())
1648            }
1649            ResultData::None => match blanks {
1650                BlankPolicy::Zero => {
1651                    out.push(0.0);
1652                    Ok(())
1653                }
1654                BlankPolicy::Skip => Ok(()),
1655                BlankPolicy::Reject => Err("#VALUE!".to_string()),
1656            },
1657            ResultData::String(_) if coerce_text => match self.to_f64(arg) {
1658                Some(f) => {
1659                    out.push(f);
1660                    Ok(())
1661                }
1662                None => Err("#VALUE!".to_string()),
1663            },
1664            _ => Err("#VALUE!".to_string()),
1665        }
1666    }
1667
1668    fn flatten_strict_numbers(&self, arg: &ResultData) -> Result<Vec<f64>, String> {
1669        let mut out = Vec::new();
1670        self.flatten_strict_inner(arg, BlankPolicy::Zero, true, &mut out)?;
1671        Ok(out)
1672    }
1673
1674    fn flatten_skipping_blanks(&self, arg: Option<&ResultData>) -> Result<Vec<f64>, String> {
1675        let mut out = Vec::new();
1676        if let Some(a) = arg {
1677            self.flatten_strict_inner(a, BlankPolicy::Skip, true, &mut out)?;
1678        }
1679        Ok(out)
1680    }
1681
1682    fn flatten_skipping_blanks_no_text_coercion(
1683        &self,
1684        arg: Option<&ResultData>,
1685    ) -> Result<Vec<f64>, String> {
1686        let mut out = Vec::new();
1687        if let Some(a) = arg {
1688            self.flatten_strict_inner(a, BlankPolicy::Skip, false, &mut out)?;
1689        }
1690        Ok(out)
1691    }
1692
1693    fn flatten_numbers_only(&self, arg: &ResultData) -> Result<Vec<f64>, String> {
1694        let mut out = Vec::new();
1695        self.flatten_strict_inner(arg, BlankPolicy::Reject, false, &mut out)?;
1696        Ok(out)
1697    }
1698
1699    fn aggregate_range_number(val: &ResultData) -> Option<f64> {
1700        match val {
1701            ResultData::Float(f) => Some(*f),
1702            ResultData::Integer(i) => Some(*i as f64),
1703            _ => None,
1704        }
1705    }
1706
1707    fn flatten_numbers_only_arg(&self, arg: Option<&ResultData>) -> Result<Vec<f64>, String> {
1708        match arg {
1709            Some(a) => self.flatten_numbers_only(a),
1710            None => Ok(vec![]),
1711        }
1712    }
1713
1714    fn flatten_args_stat_numbers(
1715        &self,
1716        args: &[ResultData],
1717        is_direct: &[bool],
1718    ) -> Result<Vec<f64>, String> {
1719        let mut out = Vec::new();
1720        for (i, arg) in args.iter().enumerate() {
1721            let direct = is_direct.get(i).copied().unwrap_or(false);
1722            if direct && matches!(arg, ResultData::String(_)) && self.to_f64(arg).is_none() {
1723                return Err("#VALUE!".to_string());
1724            }
1725            out.extend(self.flatten_stat_numbers(arg, direct));
1726        }
1727        Ok(out)
1728    }
1729
1730    fn flatten_stat_numbers_a(
1731        &self,
1732        arg: &ResultData,
1733        is_direct: bool,
1734    ) -> Result<Vec<f64>, String> {
1735        Ok(match arg {
1736            ResultData::Float(f) => vec![*f],
1737            ResultData::Integer(i) => vec![*i as f64],
1738            ResultData::Boolean(b) => vec![if *b { 1.0 } else { 0.0 }],
1739            ResultData::String(_) => {
1740                if is_direct {
1741                    match self.to_f64(arg) {
1742                        Some(f) => vec![f],
1743                        None => return Err("#VALUE!".to_string()),
1744                    }
1745                } else {
1746                    vec![0.0]
1747                }
1748            }
1749            ResultData::Error(e) => return Err(e.clone()),
1750            ResultData::List(list) => {
1751                let mut out = Vec::new();
1752                for v in list {
1753                    out.extend(self.flatten_stat_numbers_a(v, false)?);
1754                }
1755                out
1756            }
1757            ResultData::None => vec![],
1758            _ => vec![0.0],
1759        })
1760    }
1761
1762    fn flatten_args_stat_numbers_a(
1763        &self,
1764        args: &[ResultData],
1765        is_direct: &[bool],
1766    ) -> Result<Vec<f64>, String> {
1767        let mut out = Vec::new();
1768        for (i, arg) in args.iter().enumerate() {
1769            out.extend(
1770                self.flatten_stat_numbers_a(arg, is_direct.get(i).copied().unwrap_or(false))?,
1771            );
1772        }
1773        Ok(out)
1774    }
1775
1776    fn extract_matrix(&self, arg: &ResultData) -> Vec<Vec<f64>> {
1777        match arg {
1778            ResultData::List(list) => {
1779                let mut rows = Vec::new();
1780                for item in list {
1781                    match item {
1782                        ResultData::List(sub_list) => {
1783                            let row: Vec<f64> =
1784                                sub_list.iter().flat_map(|v| self.to_f64(v)).collect();
1785                            if !row.is_empty() {
1786                                rows.push(row);
1787                            }
1788                        }
1789                        _ => {
1790                            if let Some(f) = self.to_f64(item) {
1791                                rows.push(vec![f]);
1792                            }
1793                        }
1794                    }
1795                }
1796                rows
1797            }
1798            _ => vec![],
1799        }
1800    }
1801
1802    fn matrix_from_arg(
1803        &self,
1804        expr: &crate::core::parser::Expr,
1805        value: &ResultData,
1806    ) -> Vec<Vec<f64>> {
1807        if let ResultData::List(items) = value
1808            && items.iter().any(|i| matches!(i, ResultData::List(_)))
1809        {
1810            return self.extract_matrix(value);
1811        }
1812        fn plain(v: &ResultData) -> Option<f64> {
1813            match v {
1814                ResultData::Float(f) => Some(*f),
1815                ResultData::Integer(i) => Some(*i as f64),
1816                _ => None,
1817            }
1818        }
1819        let items: Vec<&ResultData> = match value {
1820            ResultData::List(items) => items.iter().collect(),
1821            other => vec![other],
1822        };
1823        if items.iter().any(|v| plain(v).is_none()) {
1824            return Vec::new();
1825        }
1826        let flat: Vec<f64> = items.iter().filter_map(|v| plain(v)).collect();
1827        let cols = match Self::range_bounds(expr) {
1828            Some((_, _, start_col, _, end_col)) => end_col.saturating_sub(start_col) + 1,
1829            None => flat.len().max(1),
1830        };
1831        if cols == 0 || !flat.len().is_multiple_of(cols) {
1832            return self.extract_matrix(value);
1833        }
1834        flat.chunks(cols).map(|c| c.to_vec()).collect()
1835    }
1836
1837    fn paired_sum_has_no_numbers(&self, arg: Option<&ResultData>) -> bool {
1838        let mut ignored = None;
1839        let slots = self.positional_numbers(arg, &mut ignored);
1840        slots.iter().all(|v| v.is_none())
1841    }
1842
1843    fn is_empty_scalar_operand(arg: &ResultData) -> bool {
1844        let scalar = match arg {
1845            ResultData::List(items) if items.len() == 1 => &items[0],
1846            other => other,
1847        };
1848        matches!(scalar, ResultData::None)
1849    }
1850
1851    fn first_arg_is_boolean(args: &[ResultData]) -> bool {
1852        matches!(args.first(), Some(ResultData::Boolean(_)))
1853    }
1854
1855    fn opt_f64_arg(&self, args: &[ResultData], i: usize, default: f64) -> Result<f64, EngineError> {
1856        match args.get(i) {
1857            None => Ok(default),
1858            Some(ResultData::None) => Ok(0.0),
1859            Some(ResultData::Error(e)) => Err(EngineError::EvalError(EvalError::UnknownFunction(
1860                e.clone(),
1861            ))),
1862            Some(v) => self.to_f64(v).ok_or_else(|| {
1863                EngineError::EvalError(EvalError::UnknownFunction("#VALUE!".to_string()))
1864            }),
1865        }
1866    }
1867
1868    fn opt_f64(&self, args: &[ResultData], i: usize, default: f64) -> f64 {
1869        args.get(i).and_then(|v| self.to_f64(v)).unwrap_or(default)
1870    }
1871
1872    fn average_helper(&self, arg: &ResultData, is_direct: bool) -> (f64, usize) {
1873        match arg {
1874            ResultData::Float(f) => (*f, 1),
1875            ResultData::Integer(i) => (*i as f64, 1),
1876            ResultData::Boolean(b) => {
1877                if is_direct {
1878                    (if *b { 1.0 } else { 0.0 }, 1)
1879                } else {
1880                    (0.0, 0)
1881                }
1882            }
1883            ResultData::String(_) => {
1884                if is_direct {
1885                    if let Some(f) = self.to_f64(arg) {
1886                        (f, 1)
1887                    } else {
1888                        (0.0, 0)
1889                    }
1890                } else {
1891                    (0.0, 0)
1892                }
1893            }
1894            ResultData::List(list) => {
1895                let mut sum = 0.0;
1896                let mut count = 0;
1897                for item in list {
1898                    let (s, c) = self.average_helper(item, false);
1899                    sum += s;
1900                    count += c;
1901                }
1902                (sum, count)
1903            }
1904            _ => (0.0, 0),
1905        }
1906    }
1907
1908    fn count_helper(&self, arg: &ResultData) -> usize {
1909        match arg {
1910            ResultData::Float(_) | ResultData::Integer(_) => 1,
1911            ResultData::List(list) => {
1912                let mut count = 0;
1913                for item in list {
1914                    count += self.count_helper(item);
1915                }
1916                count
1917            }
1918            _ => 0,
1919        }
1920    }
1921
1922    fn min_helper(&self, arg: &ResultData, is_direct: bool) -> f64 {
1923        match arg {
1924            ResultData::Float(f) => *f,
1925            ResultData::Integer(i) => *i as f64,
1926            ResultData::Boolean(b) => {
1927                if is_direct {
1928                    if *b { 1.0 } else { 0.0 }
1929                } else {
1930                    f64::INFINITY
1931                }
1932            }
1933            ResultData::String(_) => {
1934                if is_direct {
1935                    self.to_f64(arg).unwrap_or(f64::INFINITY)
1936                } else {
1937                    f64::INFINITY
1938                }
1939            }
1940            ResultData::List(list) => {
1941                let mut min_val = f64::INFINITY;
1942                for item in list {
1943                    min_val = min_val.min(self.min_helper(item, false));
1944                }
1945                min_val
1946            }
1947            _ => f64::INFINITY,
1948        }
1949    }
1950
1951    fn max_helper(&self, arg: &ResultData, is_direct: bool) -> f64 {
1952        match arg {
1953            ResultData::Float(f) => *f,
1954            ResultData::Integer(i) => *i as f64,
1955            ResultData::Boolean(b) => {
1956                if is_direct {
1957                    if *b { 1.0 } else { 0.0 }
1958                } else {
1959                    f64::NEG_INFINITY
1960                }
1961            }
1962            ResultData::String(_) => {
1963                if is_direct {
1964                    self.to_f64(arg).unwrap_or(f64::NEG_INFINITY)
1965                } else {
1966                    f64::NEG_INFINITY
1967                }
1968            }
1969            ResultData::List(list) => {
1970                let mut max_val = f64::NEG_INFINITY;
1971                for item in list {
1972                    max_val = max_val.max(self.max_helper(item, false));
1973                }
1974                max_val
1975            }
1976            _ => f64::NEG_INFINITY,
1977        }
1978    }
1979
1980    fn concat_helper(&self, arg: &ResultData, out: &mut String) {
1981        match arg {
1982            ResultData::List(list) => {
1983                for item in list {
1984                    self.concat_helper(item, out);
1985                }
1986            }
1987            other => {
1988                out.push_str(&other.to_string());
1989            }
1990        }
1991    }
1992
1993    fn counta_helper(&self, arg: &ResultData) -> usize {
1994        match arg {
1995            ResultData::None => 0,
1996            ResultData::List(list) => {
1997                let mut count = 0;
1998                for item in list {
1999                    count += self.counta_helper(item);
2000                }
2001                count
2002            }
2003            _ => 1,
2004        }
2005    }
2006
2007    fn product_helper(&self, arg: &ResultData, is_direct: bool) -> (f64, bool) {
2008        match arg {
2009            ResultData::Float(f) => (*f, true),
2010            ResultData::Integer(i) => (*i as f64, true),
2011            ResultData::Boolean(b) => {
2012                if is_direct {
2013                    (if *b { 1.0 } else { 0.0 }, true)
2014                } else {
2015                    (1.0, false)
2016                }
2017            }
2018            ResultData::String(_) => {
2019                if is_direct {
2020                    if let Some(f) = self.to_f64(arg) {
2021                        (f, true)
2022                    } else {
2023                        (1.0, false)
2024                    }
2025                } else {
2026                    (1.0, false)
2027                }
2028            }
2029            ResultData::List(list) => {
2030                let mut prod = 1.0;
2031                let mut has_nums = false;
2032                for item in list {
2033                    let (p, h) = self.product_helper(item, false);
2034                    if h {
2035                        prod *= p;
2036                        has_nums = true;
2037                    }
2038                }
2039                (prod, has_nums)
2040            }
2041            _ => (1.0, false),
2042        }
2043    }
2044
2045    fn to_bool_opt(&self, val: &ResultData) -> Option<bool> {
2046        match val {
2047            ResultData::Boolean(b) => Some(*b),
2048            ResultData::Integer(i) => Some(*i != 0),
2049            ResultData::Float(f) => Some(*f != 0.0),
2050            ResultData::String(s) => {
2051                let s_trim = s.trim();
2052                if s_trim.eq_ignore_ascii_case("true") {
2053                    Some(true)
2054                } else if s_trim.eq_ignore_ascii_case("false") {
2055                    Some(false)
2056                } else if let Ok(f) = s_trim.parse::<f64>() {
2057                    Some(f != 0.0)
2058                } else {
2059                    None
2060                }
2061            }
2062            ResultData::None => Some(false),
2063            _ => None,
2064        }
2065    }
2066
2067    fn to_bool(&self, val: &ResultData) -> bool {
2068        self.to_bool_opt(val).unwrap_or(false)
2069    }
2070
2071    fn range_numeric(val: &ResultData) -> Option<f64> {
2072        match val {
2073            ResultData::Integer(i) => Some(*i as f64),
2074            ResultData::Float(f) => Some(*f),
2075            _ => None,
2076        }
2077    }
2078
2079    fn exact_lookup_matches(lookup: &ResultData, candidate: &ResultData) -> bool {
2080        if matches!(candidate, ResultData::None) {
2081            return false;
2082        }
2083        let lookup_key = match lookup {
2084            ResultData::None => "0".to_string(),
2085            other => other.to_string(),
2086        };
2087        candidate.to_string() == lookup_key
2088    }
2089
2090    fn wildcard_criteria_matches(pattern: &str, text: &str) -> bool {
2091        fn rec(pat: &[char], txt: &[char]) -> bool {
2092            if pat.is_empty() {
2093                return txt.is_empty();
2094            }
2095            match pat[0] {
2096                '*' => rec(&pat[1..], txt) || (!txt.is_empty() && rec(pat, &txt[1..])),
2097                '?' => !txt.is_empty() && rec(&pat[1..], &txt[1..]),
2098                '~' if pat.len() > 1 && matches!(pat[1], '*' | '?' | '~') => {
2099                    !txt.is_empty() && pat[1] == txt[0] && rec(&pat[2..], &txt[1..])
2100                }
2101                ch => !txt.is_empty() && ch == txt[0] && rec(&pat[1..], &txt[1..]),
2102            }
2103        }
2104
2105        let pat = pattern.to_lowercase().chars().collect::<Vec<_>>();
2106        let txt = text.to_lowercase().chars().collect::<Vec<_>>();
2107        rec(&pat, &txt)
2108    }
2109
2110    fn criteria_text_eq(val: &ResultData, pattern: &str) -> bool {
2111        let text = val.to_string();
2112        if pattern.contains('*') || pattern.contains('?') {
2113            matches!(val, ResultData::String(_)) && Self::wildcard_criteria_matches(pattern, &text)
2114        } else {
2115            text.to_lowercase() == pattern.to_lowercase()
2116        }
2117    }
2118
2119    fn match_criteria(&self, val: &ResultData, criteria: &ResultData) -> bool {
2120        let crit_str = criteria.to_string();
2121        if let Some(rest) = crit_str.strip_prefix(">=") {
2122            let val_f = match Self::range_numeric(val) {
2123                Some(f) => f,
2124                None => return false,
2125            };
2126            let crit_f = rest.trim().parse::<f64>().unwrap_or(0.0);
2127            val_f >= crit_f
2128        } else if let Some(rest) = crit_str.strip_prefix('>') {
2129            let val_f = match Self::range_numeric(val) {
2130                Some(f) => f,
2131                None => return false,
2132            };
2133            let crit_f = rest.trim().parse::<f64>().unwrap_or(0.0);
2134            val_f > crit_f
2135        } else if let Some(rest) = crit_str.strip_prefix("<>") {
2136            let remainder = rest.trim();
2137            !Self::criteria_text_eq(val, remainder)
2138        } else if let Some(rest) = crit_str.strip_prefix("<=") {
2139            let val_f = match Self::range_numeric(val) {
2140                Some(f) => f,
2141                None => return false,
2142            };
2143            let crit_f = rest.trim().parse::<f64>().unwrap_or(0.0);
2144            val_f <= crit_f
2145        } else if let Some(rest) = crit_str.strip_prefix('<') {
2146            let val_f = match Self::range_numeric(val) {
2147                Some(f) => f,
2148                None => return false,
2149            };
2150            let crit_f = rest.trim().parse::<f64>().unwrap_or(0.0);
2151            val_f < crit_f
2152        } else if let Some(rest) = crit_str.strip_prefix('=') {
2153            let remainder = rest.trim();
2154            Self::criteria_text_eq(val, remainder)
2155        } else {
2156            Self::criteria_text_eq(val, &crit_str)
2157        }
2158    }
2159
2160    fn range_bounds(
2161        expr: &crate::core::parser::Expr,
2162    ) -> Option<(Option<String>, usize, usize, usize, usize)> {
2163        use crate::core::parser::Expr;
2164        match expr {
2165            Expr::RangeRef {
2166                sheet,
2167                start_row,
2168                start_col,
2169                end_row,
2170                end_col,
2171                ..
2172            } => Some((sheet.clone(), *start_row, *start_col, *end_row, *end_col)),
2173            Expr::CellRef {
2174                sheet, row, col, ..
2175            } => Some((sheet.clone(), *row, *col, *row, *col)),
2176            _ => None,
2177        }
2178    }
2179
2180    fn materialize_range(
2181        &self,
2182        sheet_opt: &Option<String>,
2183        start_row: usize,
2184        start_col: usize,
2185        end_row: usize,
2186        end_col: usize,
2187        context: Option<&Context>,
2188    ) -> Option<Vec<Vec<ResultData>>> {
2189        let is_self = match sheet_opt {
2190            Some(name) => name == &self.name,
2191            None => true,
2192        };
2193        let source: &Sheet = if is_self {
2194            self
2195        } else {
2196            context?.sheets.get(sheet_opt.as_ref()?)?
2197        };
2198        let actual_end_row = if end_row == usize::MAX {
2199            source.row_count().saturating_sub(1)
2200        } else {
2201            end_row
2202        };
2203        let actual_end_col = if end_col == usize::MAX {
2204            source.col_count().saturating_sub(1)
2205        } else {
2206            end_col
2207        };
2208        if actual_end_row < start_row || actual_end_col < start_col {
2209            return Some(Vec::new());
2210        }
2211        let mut grid = Vec::with_capacity(actual_end_row - start_row + 1);
2212        for r in start_row..=actual_end_row {
2213            let mut row = Vec::with_capacity(actual_end_col - start_col + 1);
2214            for c in start_col..=actual_end_col {
2215                row.push(source.get_result_data(&CellRef::new(r, c)));
2216            }
2217            grid.push(row);
2218        }
2219        Some(grid)
2220    }
2221
2222    fn evaluate_database_function(
2223        &self,
2224        func_name: &str,
2225        args: &[crate::core::parser::Expr],
2226        evaluated_args: &[ResultData],
2227        context: Option<&Context>,
2228    ) -> Result<ResultData, EngineError> {
2229        if args.len() < 3 || evaluated_args.len() < 3 {
2230            return Ok(ResultData::Error("#VALUE!".to_string()));
2231        }
2232        let (db_sheet, db_sr, db_sc, db_er, db_ec) = match Self::range_bounds(&args[0]) {
2233            Some(v) => v,
2234            None => return Ok(ResultData::Error("#VALUE!".to_string())),
2235        };
2236        let (crit_sheet, crit_sr, crit_sc, crit_er, crit_ec) = match Self::range_bounds(&args[2]) {
2237            Some(v) => v,
2238            None => return Ok(ResultData::Error("#VALUE!".to_string())),
2239        };
2240        let db = match self.materialize_range(&db_sheet, db_sr, db_sc, db_er, db_ec, context) {
2241            Some(g) => g,
2242            None => return Ok(ResultData::Error("#REF!".to_string())),
2243        };
2244        let crit = match self.materialize_range(
2245            &crit_sheet,
2246            crit_sr,
2247            crit_sc,
2248            crit_er,
2249            crit_ec,
2250            context,
2251        ) {
2252            Some(g) => g,
2253            None => return Ok(ResultData::Error("#REF!".to_string())),
2254        };
2255        if db.len() < 2 || crit.len() < 2 {
2256            return Ok(ResultData::Error("#VALUE!".to_string()));
2257        }
2258
2259        let db_headers: Vec<String> = db[0].iter().map(|v| v.to_string()).collect();
2260        let field_idx: usize = match &evaluated_args[1] {
2261            ResultData::String(s) => {
2262                match db_headers.iter().position(|h| h.eq_ignore_ascii_case(s)) {
2263                    Some(idx) => idx,
2264                    None => return Ok(ResultData::Error("#VALUE!".to_string())),
2265                }
2266            }
2267            other => match self.to_f64(other) {
2268                Some(n) if n >= 1.0 && (n as usize) <= db_headers.len() => n as usize - 1,
2269                _ => return Ok(ResultData::Error("#VALUE!".to_string())),
2270            },
2271        };
2272
2273        let crit_headers: Vec<String> = crit[0].iter().map(|v| v.to_string()).collect();
2274        let crit_to_db: Vec<Option<usize>> = crit_headers
2275            .iter()
2276            .map(|h| db_headers.iter().position(|dh| dh.eq_ignore_ascii_case(h)))
2277            .collect();
2278
2279        let mut matched: Vec<ResultData> = Vec::new();
2280        for row in db.iter().skip(1) {
2281            let row_matches_any_criteria_row = crit.iter().skip(1).any(|crit_row| {
2282                crit_row.iter().enumerate().all(|(ci, cell)| {
2283                    if matches!(cell, ResultData::None) {
2284                        return true;
2285                    }
2286                    match crit_to_db.get(ci).copied().flatten() {
2287                        Some(db_col) => self.match_criteria(&row[db_col], cell),
2288                        None => false,
2289                    }
2290                })
2291            });
2292            if row_matches_any_criteria_row {
2293                matched.push(row[field_idx].clone());
2294            }
2295        }
2296
2297        match func_name {
2298            "DGET" => match matched.len() {
2299                0 => Ok(ResultData::Error("#VALUE!".to_string())),
2300                1 => Ok(matched.into_iter().next().unwrap()),
2301                _ => Ok(ResultData::Error("#NUM!".to_string())),
2302            },
2303            "DCOUNT" => Ok(ResultData::Float(
2304                matched
2305                    .iter()
2306                    .filter(|v| Self::range_numeric(v).is_some())
2307                    .count() as f64,
2308            )),
2309            "DCOUNTA" => Ok(ResultData::Float(
2310                matched.iter().map(|v| self.counta_helper(v)).sum::<usize>() as f64,
2311            )),
2312            _ => {
2313                let nums: Vec<f64> = matched.iter().filter_map(Self::range_numeric).collect();
2314                match func_name {
2315                    "DSUM" => Ok(ResultData::Float(nums.iter().sum())),
2316                    "DPRODUCT" => Ok(ResultData::Float(if nums.is_empty() {
2317                        0.0
2318                    } else {
2319                        nums.iter().product()
2320                    })),
2321                    "DMAX" => {
2322                        let m = nums.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
2323                        Ok(ResultData::Float(if m.is_finite() { m } else { 0.0 }))
2324                    }
2325                    "DMIN" => {
2326                        let m = nums.iter().cloned().fold(f64::INFINITY, f64::min);
2327                        Ok(ResultData::Float(if m.is_finite() { m } else { 0.0 }))
2328                    }
2329                    "DAVERAGE" => {
2330                        if nums.is_empty() {
2331                            Ok(ResultData::Error("#DIV/0!".to_string()))
2332                        } else {
2333                            Ok(ResultData::Float(
2334                                nums.iter().sum::<f64>() / nums.len() as f64,
2335                            ))
2336                        }
2337                    }
2338                    "DSTDEV" => match crate::core::stats::stdev_s(&nums) {
2339                        Ok(v) => Ok(ResultData::Float(v)),
2340                        Err(e) => Ok(ResultData::Error(e)),
2341                    },
2342                    "DSTDEVP" => match crate::core::stats::stdev_p(&nums) {
2343                        Ok(v) => Ok(ResultData::Float(v)),
2344                        Err(e) => Ok(ResultData::Error(e)),
2345                    },
2346                    "DVAR" => match crate::core::stats::var_s(&nums) {
2347                        Ok(v) => Ok(ResultData::Float(v)),
2348                        Err(e) => Ok(ResultData::Error(e)),
2349                    },
2350                    "DVARP" => match crate::core::stats::var_p(&nums) {
2351                        Ok(v) => Ok(ResultData::Float(v)),
2352                        Err(e) => Ok(ResultData::Error(e)),
2353                    },
2354                    _ => unreachable!(),
2355                }
2356            }
2357        }
2358    }
2359
2360    fn proper(&self, s: &str) -> String {
2361        let mut c_chars = Vec::new();
2362        let mut capitalize_next = true;
2363        for c in s.chars() {
2364            if c.is_alphabetic() {
2365                if capitalize_next {
2366                    c_chars.extend(c.to_uppercase());
2367                } else {
2368                    c_chars.extend(c.to_lowercase());
2369                }
2370                capitalize_next = false;
2371            } else {
2372                c_chars.push(c);
2373                capitalize_next = true;
2374            }
2375        }
2376        c_chars.into_iter().collect()
2377    }
2378
2379    fn get_ymd_hms(&self) -> ((i32, u32, u32), (u32, u32, u32)) {
2380        let now = web_time::SystemTime::now()
2381            .duration_since(web_time::SystemTime::UNIX_EPOCH)
2382            .unwrap_or_default()
2383            .as_secs();
2384        let secs_in_day = 86400;
2385        let days_since_epoch = (now / secs_in_day) as i32;
2386        let seconds_of_day = (now % secs_in_day) as u32;
2387
2388        let hour = seconds_of_day / 3600;
2389        let minute = (seconds_of_day % 3600) / 60;
2390        let second = seconds_of_day % 60;
2391
2392        let era = (if days_since_epoch >= -719468 {
2393            days_since_epoch + 719468
2394        } else {
2395            days_since_epoch + 719468 - 146096
2396        }) / 146097;
2397        let doe = (days_since_epoch + 719468 - era * 146097) as u32;
2398        let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
2399        let y = (yoe as i32) + era * 400;
2400        let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
2401        let mp = (5 * doy + 2) / 153;
2402        let d = doy - (153 * mp + 2) / 5 + 1;
2403        let m = if mp < 10 { mp + 3 } else { mp - 9 };
2404        let year = if m <= 2 { y + 1 } else { y };
2405
2406        ((year, m, d), (hour, minute, second))
2407    }
2408
2409    fn evaluate_let(
2410        &self,
2411        args: &[crate::core::parser::Expr],
2412        context: Option<&Context>,
2413        row: Option<usize>,
2414        col: Option<usize>,
2415        deps: &mut Vec<Dependency>,
2416        scope: &LetScope<'_>,
2417    ) -> Result<ResultData, EngineError> {
2418        use crate::core::parser::Expr;
2419
2420        if args.is_empty() || args.len().is_multiple_of(2) {
2421            return Ok(ResultData::Error("#VALUE!".to_string()));
2422        }
2423        if args.len() == 1 {
2424            return self.evaluate_ast(&args[0], context, row, col, deps, scope);
2425        }
2426
2427        let name = match &args[0] {
2428            Expr::Identifier(n) => n.as_str(),
2429            _ => return Ok(ResultData::Error("#VALUE!".to_string())),
2430        };
2431        let remaining_pairs = args.len() / 2 - 1;
2432        let is_duplicate = args[2..]
2433            .iter()
2434            .step_by(2)
2435            .take(remaining_pairs)
2436            .any(|a| matches!(a, Expr::Identifier(n2) if n2.eq_ignore_ascii_case(name)));
2437        if is_duplicate {
2438            return Ok(ResultData::Error("#VALUE!".to_string()));
2439        }
2440
2441        let value = self.evaluate_ast(&args[1], context, row, col, deps, scope)?;
2442        let inner_scope = LetScope::Bound {
2443            name,
2444            value: &value,
2445            parent: scope,
2446        };
2447        self.evaluate_let(&args[2..], context, row, col, deps, &inner_scope)
2448    }
2449
2450    fn extract_lambda(
2451        expr: &crate::core::parser::Expr,
2452    ) -> Option<(Vec<&str>, &crate::core::parser::Expr)> {
2453        use crate::core::parser::Expr;
2454        let Expr::FunctionCall { name, args } = expr else {
2455            return None;
2456        };
2457        if !name.eq_ignore_ascii_case("LAMBDA") || args.is_empty() {
2458            return None;
2459        }
2460        let (body, params) = args.split_last().unwrap();
2461        let param_names: Vec<&str> = params
2462            .iter()
2463            .filter_map(|p| match p {
2464                Expr::Identifier(n) => Some(n.as_str()),
2465                _ => None,
2466            })
2467            .collect();
2468        if param_names.len() != params.len() {
2469            return None;
2470        }
2471        Some((param_names, body))
2472    }
2473
2474    #[allow(clippy::too_many_arguments)]
2475    fn invoke_lambda<'v>(
2476        &self,
2477        params: &[&str],
2478        values: &'v [ResultData],
2479        body: &crate::core::parser::Expr,
2480        context: Option<&Context>,
2481        row: Option<usize>,
2482        col: Option<usize>,
2483        deps: &mut Vec<Dependency>,
2484        scope: &LetScope<'v>,
2485    ) -> Result<ResultData, EngineError> {
2486        match (params.split_first(), values.split_first()) {
2487            (Some((&pname, prest)), Some((vfirst, vrest))) => {
2488                let inner_scope = LetScope::Bound {
2489                    name: pname,
2490                    value: vfirst,
2491                    parent: scope,
2492                };
2493                self.invoke_lambda(prest, vrest, body, context, row, col, deps, &inner_scope)
2494            }
2495            _ => self.evaluate_ast(body, context, row, col, deps, scope),
2496        }
2497    }
2498
2499    fn eval_as_array(
2500        &self,
2501        expr: &crate::core::parser::Expr,
2502        context: Option<&Context>,
2503        row: Option<usize>,
2504        col: Option<usize>,
2505        deps: &mut Vec<Dependency>,
2506        scope: &LetScope<'_>,
2507    ) -> Result<Vec<ResultData>, EngineError> {
2508        Ok(
2509            match self.evaluate_ast(expr, context, row, col, deps, scope)? {
2510                ResultData::List(items) => Self::flatten_row_major(items).0,
2511                other => vec![other],
2512            },
2513        )
2514    }
2515
2516    fn flatten_row_major(items: Vec<ResultData>) -> (Vec<ResultData>, Option<usize>) {
2517        if !items.is_empty() && items.iter().all(|v| matches!(v, ResultData::List(_))) {
2518            let cols = match &items[0] {
2519                ResultData::List(inner) => inner.len().max(1),
2520                _ => 1,
2521            };
2522            let flat = items
2523                .into_iter()
2524                .flat_map(|v| match v {
2525                    ResultData::List(inner) => inner,
2526                    other => vec![other],
2527                })
2528                .collect();
2529            (flat, Some(cols))
2530        } else {
2531            (items, None)
2532        }
2533    }
2534
2535    fn array_shape(
2536        &self,
2537        expr: &crate::core::parser::Expr,
2538        context: Option<&Context>,
2539        row: Option<usize>,
2540        col: Option<usize>,
2541        deps: &mut Vec<Dependency>,
2542        scope: &LetScope<'_>,
2543    ) -> Result<(Vec<ResultData>, usize), EngineError> {
2544        use crate::core::parser::Expr;
2545        let items = match self.evaluate_ast(expr, context, row, col, deps, scope)? {
2546            ResultData::List(items) => items,
2547            other => vec![other],
2548        };
2549        let (flat, nested_cols) = Self::flatten_row_major(items);
2550        if let Some(cols) = nested_cols {
2551            return Ok((flat, cols));
2552        }
2553        let num_cols = match expr {
2554            Expr::RangeRef {
2555                start_col, end_col, ..
2556            } => (end_col - start_col + 1).max(1),
2557            Expr::CellRef { .. } => 1,
2558            Expr::FunctionCall { name, args } => self
2559                .function_call_cols(name, args, context, row, col, deps, scope)
2560                .unwrap_or_else(|| flat.len().max(1)),
2561            _ => flat.len().max(1),
2562        };
2563        Ok((flat, num_cols))
2564    }
2565
2566    #[allow(clippy::too_many_arguments)]
2567    fn function_call_cols(
2568        &self,
2569        name: &str,
2570        args: &[crate::core::parser::Expr],
2571        context: Option<&Context>,
2572        row: Option<usize>,
2573        col: Option<usize>,
2574        deps: &mut Vec<Dependency>,
2575        scope: &LetScope<'_>,
2576    ) -> Option<usize> {
2577        let mut upper = name.to_ascii_uppercase();
2578        if let Some(rest) = upper.strip_prefix("_XLFN.") {
2579            upper = rest.to_string();
2580        }
2581        if let Some(rest) = upper.strip_prefix("_XLWS.") {
2582            upper = rest.to_string();
2583        }
2584        match upper.as_str() {
2585            "TRANSPOSE" => {
2586                let (flat, cols) = self
2587                    .array_shape(args.first()?, context, row, col, deps, scope)
2588                    .ok()?;
2589                Some((flat.len().checked_div(cols).unwrap_or(0)).max(1))
2590            }
2591            "HSTACK" => {
2592                let mut total = 0usize;
2593                for a in args {
2594                    total += self.array_shape(a, context, row, col, deps, scope).ok()?.1;
2595                }
2596                Some(total)
2597            }
2598            "VSTACK" => {
2599                let mut max_cols = 0usize;
2600                for a in args {
2601                    max_cols =
2602                        max_cols.max(self.array_shape(a, context, row, col, deps, scope).ok()?.1);
2603                }
2604                Some(max_cols)
2605            }
2606            "CHOOSEROWS" => Some(
2607                self.array_shape(args.first()?, context, row, col, deps, scope)
2608                    .ok()?
2609                    .1,
2610            ),
2611            "CHOOSECOLS" => Some(args.len().saturating_sub(1).max(1)),
2612            "DROP" | "TAKE" => {
2613                let (_, cols) = self
2614                    .array_shape(args.first()?, context, row, col, deps, scope)
2615                    .ok()?;
2616                let is_take = upper == "TAKE";
2617                match args.get(2) {
2618                    Some(e) => {
2619                        let n = self
2620                            .to_f64(&self.evaluate_ast(e, context, row, col, deps, scope).ok()?)
2621                            .unwrap_or(0.0) as isize;
2622                        let (s, e2) = Self::drop_take_bounds(cols as isize, n, is_take);
2623                        Some((e2 - s).max(0) as usize)
2624                    }
2625                    None => Some(if is_take { cols } else { 0 }),
2626                }
2627            }
2628            "EXPAND" => {
2629                let (_, cols) = self
2630                    .array_shape(args.first()?, context, row, col, deps, scope)
2631                    .ok()?;
2632                match args.get(2) {
2633                    Some(e) => Some(
2634                        self.to_f64(&self.evaluate_ast(e, context, row, col, deps, scope).ok()?)
2635                            .unwrap_or(cols as f64) as usize,
2636                    ),
2637                    None => Some(cols),
2638                }
2639            }
2640            "TOCOL" => Some(1),
2641            "WRAPROWS" => {
2642                let n = self
2643                    .to_f64(
2644                        &self
2645                            .evaluate_ast(args.get(1)?, context, row, col, deps, scope)
2646                            .ok()?,
2647                    )
2648                    .unwrap_or(1.0)
2649                    .max(1.0) as usize;
2650                Some(n)
2651            }
2652            "WRAPCOLS" => {
2653                let (flat, _) = self
2654                    .array_shape(args.first()?, context, row, col, deps, scope)
2655                    .ok()?;
2656                let wrap = self
2657                    .to_f64(
2658                        &self
2659                            .evaluate_ast(args.get(1)?, context, row, col, deps, scope)
2660                            .ok()?,
2661                    )
2662                    .unwrap_or(1.0)
2663                    .max(1.0) as usize;
2664                Some(flat.len().div_ceil(wrap).max(1))
2665            }
2666            "UNIQUE" | "SORT" | "SORTBY" | "FILTER" | "TRIMRANGE" => Some(
2667                self.array_shape(args.first()?, context, row, col, deps, scope)
2668                    .ok()?
2669                    .1,
2670            ),
2671            "SEQUENCE" => match args.get(1) {
2672                Some(e) => Some(
2673                    self.to_f64(&self.evaluate_ast(e, context, row, col, deps, scope).ok()?)
2674                        .unwrap_or(1.0)
2675                        .max(1.0) as usize,
2676                ),
2677                None => Some(1),
2678            },
2679            "MUNIT" => {
2680                let n = self
2681                    .to_f64(
2682                        &self
2683                            .evaluate_ast(args.first()?, context, row, col, deps, scope)
2684                            .ok()?,
2685                    )
2686                    .unwrap_or(1.0)
2687                    .max(1.0) as usize;
2688                Some(n)
2689            }
2690            "MAKEARRAY" => match args.get(1) {
2691                Some(e) => Some(
2692                    self.to_f64(&self.evaluate_ast(e, context, row, col, deps, scope).ok()?)
2693                        .unwrap_or(1.0)
2694                        .max(1.0) as usize,
2695                ),
2696                None => Some(1),
2697            },
2698            _ => None,
2699        }
2700    }
2701
2702    fn drop_take_bounds(total: isize, n: isize, is_take: bool) -> (isize, isize) {
2703        let n = n.clamp(-total, total);
2704        if is_take {
2705            if n >= 0 { (0, n) } else { (total + n, total) }
2706        } else if n >= 0 {
2707            (n, total)
2708        } else {
2709            (0, total + n)
2710        }
2711    }
2712
2713    #[allow(clippy::too_many_arguments)]
2714    fn evaluate_lambda_function(
2715        &self,
2716        func_name: &str,
2717        args: &[crate::core::parser::Expr],
2718        context: Option<&Context>,
2719        row: Option<usize>,
2720        col: Option<usize>,
2721        deps: &mut Vec<Dependency>,
2722        scope: &LetScope<'_>,
2723    ) -> Result<ResultData, EngineError> {
2724        use crate::core::parser::Expr;
2725
2726        match func_name {
2727            "MAP" => {
2728                if args.len() < 2 {
2729                    return Ok(ResultData::Error("#VALUE!".to_string()));
2730                }
2731                let (lambda_expr, array_exprs) = args.split_last().unwrap();
2732                let Some((params, body)) = Self::extract_lambda(lambda_expr) else {
2733                    return Ok(ResultData::Error("#VALUE!".to_string()));
2734                };
2735                if params.len() != array_exprs.len() {
2736                    return Ok(ResultData::Error("#VALUE!".to_string()));
2737                }
2738                let arrays: Vec<Vec<ResultData>> = array_exprs
2739                    .iter()
2740                    .map(|e| self.eval_as_array(e, context, row, col, deps, scope))
2741                    .collect::<Result<_, _>>()?;
2742                let len = arrays.iter().map(|a| a.len()).max().unwrap_or(0);
2743                let mut results = Vec::with_capacity(len);
2744                for i in 0..len {
2745                    let values: Vec<ResultData> = arrays
2746                        .iter()
2747                        .map(|a| a.get(i).cloned().unwrap_or(ResultData::None))
2748                        .collect();
2749                    results.push(
2750                        self.invoke_lambda(&params, &values, body, context, row, col, deps, scope)?,
2751                    );
2752                }
2753                Ok(ResultData::List(results))
2754            }
2755            "BYROW" | "BYCOL" => {
2756                if args.len() != 2 {
2757                    return Ok(ResultData::Error("#VALUE!".to_string()));
2758                }
2759                let Some((params, body)) = Self::extract_lambda(&args[1]) else {
2760                    return Ok(ResultData::Error("#VALUE!".to_string()));
2761                };
2762                if params.len() != 1 {
2763                    return Ok(ResultData::Error("#VALUE!".to_string()));
2764                }
2765                let num_cols = match &args[0] {
2766                    Expr::RangeRef {
2767                        start_col, end_col, ..
2768                    } => (end_col - start_col + 1).max(1),
2769                    _ => 1,
2770                };
2771                let flat = self.eval_as_array(&args[0], context, row, col, deps, scope)?;
2772                let num_rows = if num_cols == 0 {
2773                    0
2774                } else {
2775                    flat.len().div_ceil(num_cols)
2776                };
2777                let mut results = Vec::new();
2778                if func_name == "BYROW" {
2779                    for r in 0..num_rows {
2780                        let row_vals: Vec<ResultData> = (0..num_cols)
2781                            .filter_map(|c| flat.get(r * num_cols + c).cloned())
2782                            .collect();
2783                        let arg = vec![ResultData::List(row_vals)];
2784                        results.push(
2785                            self.invoke_lambda(
2786                                &params, &arg, body, context, row, col, deps, scope,
2787                            )?,
2788                        );
2789                    }
2790                } else {
2791                    for c in 0..num_cols {
2792                        let col_vals: Vec<ResultData> = (0..num_rows)
2793                            .filter_map(|r| flat.get(r * num_cols + c).cloned())
2794                            .collect();
2795                        let arg = vec![ResultData::List(col_vals)];
2796                        results.push(
2797                            self.invoke_lambda(
2798                                &params, &arg, body, context, row, col, deps, scope,
2799                            )?,
2800                        );
2801                    }
2802                }
2803                Ok(ResultData::List(results))
2804            }
2805            "REDUCE" | "SCAN" => {
2806                if args.len() != 2 && args.len() != 3 {
2807                    return Ok(ResultData::Error("#VALUE!".to_string()));
2808                }
2809                let lambda_idx = args.len() - 1;
2810                let array_idx = args.len() - 2;
2811                let Some((params, body)) = Self::extract_lambda(&args[lambda_idx]) else {
2812                    return Ok(ResultData::Error("#VALUE!".to_string()));
2813                };
2814                if params.len() != 2 {
2815                    return Ok(ResultData::Error("#VALUE!".to_string()));
2816                }
2817                let array = self.eval_as_array(&args[array_idx], context, row, col, deps, scope)?;
2818                let (mut acc, rest, mut history): (ResultData, &[ResultData], Vec<ResultData>) =
2819                    if args.len() == 3 {
2820                        let init = self.evaluate_ast(&args[0], context, row, col, deps, scope)?;
2821                        (init, &array[..], Vec::new())
2822                    } else {
2823                        match array.split_first() {
2824                            Some((first, rest)) => (first.clone(), rest, vec![first.clone()]),
2825                            None => return Ok(ResultData::Error("#VALUE!".to_string())),
2826                        }
2827                    };
2828                for item in rest {
2829                    let call_args = [acc.clone(), item.clone()];
2830                    acc = self
2831                        .invoke_lambda(&params, &call_args, body, context, row, col, deps, scope)?;
2832                    history.push(acc.clone());
2833                }
2834                if func_name == "REDUCE" {
2835                    Ok(acc)
2836                } else {
2837                    Ok(ResultData::List(history))
2838                }
2839            }
2840            "MAKEARRAY" => {
2841                if args.len() != 3 {
2842                    return Ok(ResultData::Error("#VALUE!".to_string()));
2843                }
2844                let Some((params, body)) = Self::extract_lambda(&args[2]) else {
2845                    return Ok(ResultData::Error("#VALUE!".to_string()));
2846                };
2847                if params.len() != 2 {
2848                    return Ok(ResultData::Error("#VALUE!".to_string()));
2849                }
2850                let rows_val = self.evaluate_ast(&args[0], context, row, col, deps, scope)?;
2851                let cols_val = self.evaluate_ast(&args[1], context, row, col, deps, scope)?;
2852                let num_rows = self.to_f64(&rows_val).unwrap_or(0.0).max(0.0) as usize;
2853                let num_cols = self.to_f64(&cols_val).unwrap_or(0.0).max(0.0) as usize;
2854                let mut results = Vec::with_capacity(num_rows * num_cols);
2855                for r in 1..=num_rows {
2856                    for c in 1..=num_cols {
2857                        let call_args = [ResultData::Float(r as f64), ResultData::Float(c as f64)];
2858                        results.push(self.invoke_lambda(
2859                            &params, &call_args, body, context, row, col, deps, scope,
2860                        )?);
2861                    }
2862                }
2863                Ok(ResultData::List(results))
2864            }
2865            _ => unreachable!(),
2866        }
2867    }
2868
2869    fn parse_a1_reference(text: &str) -> Option<(Option<String>, usize, usize, usize, usize)> {
2870        let text = text.trim();
2871        let (sheet_part, ref_part) = match text.rfind('!') {
2872            Some(idx) => (Some(&text[..idx]), &text[idx + 1..]),
2873            None => (None, text),
2874        };
2875        let sheet = sheet_part.map(|s| s.trim().trim_matches('\'').to_string());
2876
2877        fn parse_cell(s: &str) -> Option<(usize, usize)> {
2878            let s = s.replace('$', "");
2879            let col_end = s.find(|c: char| c.is_ascii_digit())?;
2880            let (col_str, row_str) = s.split_at(col_end);
2881            if col_str.is_empty() || row_str.is_empty() {
2882                return None;
2883            }
2884            let mut col = 0usize;
2885            for ch in col_str.chars() {
2886                if !ch.is_ascii_alphabetic() {
2887                    return None;
2888                }
2889                col = col * 26 + (ch.to_ascii_uppercase() as usize - 'A' as usize + 1);
2890            }
2891            let row: usize = row_str.parse().ok()?;
2892            if row == 0 || col == 0 {
2893                return None;
2894            }
2895            Some((row - 1, col - 1))
2896        }
2897
2898        if let Some((start, end)) = ref_part.split_once(':') {
2899            let (r1, c1) = parse_cell(start)?;
2900            let (r2, c2) = parse_cell(end)?;
2901            Some((sheet, r1.min(r2), c1.min(c2), r1.max(r2), c1.max(c2)))
2902        } else {
2903            let (r, c) = parse_cell(ref_part)?;
2904            Some((sheet, r, c, r, c))
2905        }
2906    }
2907
2908    fn read_cell_with_deps(
2909        &self,
2910        sheet_opt: &Option<String>,
2911        r: usize,
2912        c: usize,
2913        context: Option<&Context>,
2914        deps: &mut Vec<Dependency>,
2915    ) -> ResultData {
2916        let is_self = sheet_opt.as_deref().is_none_or(|n| n == self.name);
2917        if is_self {
2918            deps.push(Dependency::Local(CellRef::new(r, c)));
2919            self.get_result_data(&CellRef::new(r, c))
2920        } else if let Some(ctx) = context {
2921            let name = sheet_opt.clone().unwrap();
2922            deps.push(Dependency::Remote {
2923                sheet: name.clone(),
2924                cell: CellRef::new(r, c),
2925            });
2926            ctx.sheets
2927                .get(&name)
2928                .map(|s| s.get_result_data(&CellRef::new(r, c)))
2929                .unwrap_or(ResultData::None)
2930        } else {
2931            ResultData::None
2932        }
2933    }
2934
2935    #[allow(clippy::too_many_arguments)]
2936    fn evaluate_range_info_function(
2937        &self,
2938        func_name: &str,
2939        args: &[crate::core::parser::Expr],
2940        context: Option<&Context>,
2941        row: Option<usize>,
2942        col: Option<usize>,
2943        deps: &mut Vec<Dependency>,
2944        scope: &LetScope<'_>,
2945    ) -> Result<ResultData, EngineError> {
2946        use crate::core::parser::Expr;
2947
2948        match func_name {
2949            "ROW" => match args.first() {
2950                Some(arg) => match Self::range_bounds(arg) {
2951                    Some((_, start_row, _, end_row, _)) if end_row > start_row => {
2952                        Ok(ResultData::List(
2953                            (start_row..=end_row)
2954                                .map(|r| ResultData::Float((r + 1) as f64))
2955                                .collect(),
2956                        ))
2957                    }
2958                    Some((_, start_row, _, _, _)) => Ok(ResultData::Float((start_row + 1) as f64)),
2959                    None => Ok(ResultData::Error("#VALUE!".to_string())),
2960                },
2961                None => match row {
2962                    Some(r) => Ok(ResultData::Float((r + 1) as f64)),
2963                    None => Ok(ResultData::Error("#VALUE!".to_string())),
2964                },
2965            },
2966            "COLUMN" => match args.first() {
2967                Some(arg) => match Self::range_bounds(arg) {
2968                    Some((_, _, start_col, _, end_col)) if end_col > start_col => {
2969                        Ok(ResultData::List(
2970                            (start_col..=end_col)
2971                                .map(|c| ResultData::Float((c + 1) as f64))
2972                                .collect(),
2973                        ))
2974                    }
2975                    Some((_, _, start_col, _, _)) => Ok(ResultData::Float((start_col + 1) as f64)),
2976                    None => Ok(ResultData::Error("#VALUE!".to_string())),
2977                },
2978                None => match col {
2979                    Some(c) => Ok(ResultData::Float((c + 1) as f64)),
2980                    None => Ok(ResultData::Error("#VALUE!".to_string())),
2981                },
2982            },
2983            "ROWS" => {
2984                let Some(arg) = args.first() else {
2985                    return Ok(ResultData::Error("#VALUE!".to_string()));
2986                };
2987                let Some((sheet_opt, start_row, _, end_row, _)) = Self::range_bounds(arg) else {
2988                    return Ok(ResultData::Error("#VALUE!".to_string()));
2989                };
2990                let is_self = sheet_opt.as_deref().is_none_or(|n| n == self.name);
2991                let actual_end_row = if end_row == usize::MAX {
2992                    if is_self {
2993                        self.row_count().saturating_sub(1)
2994                    } else {
2995                        context
2996                            .and_then(|ctx| sheet_opt.as_ref().and_then(|n| ctx.sheets.get(n)))
2997                            .map(|s| s.row_count().saturating_sub(1))
2998                            .unwrap_or(0)
2999                    }
3000                } else {
3001                    end_row
3002                };
3003                Ok(ResultData::Float(
3004                    (actual_end_row.saturating_sub(start_row) + 1) as f64,
3005                ))
3006            }
3007            "COLUMNS" => {
3008                let Some(arg) = args.first() else {
3009                    return Ok(ResultData::Error("#VALUE!".to_string()));
3010                };
3011                match Self::range_bounds(arg) {
3012                    Some((_, _, start_col, _, end_col)) => Ok(ResultData::Float(
3013                        (end_col.saturating_sub(start_col) + 1) as f64,
3014                    )),
3015                    None => Ok(ResultData::Error("#VALUE!".to_string())),
3016                }
3017            }
3018            "AREAS" => {
3019                if args.is_empty() {
3020                    Ok(ResultData::Error("#VALUE!".to_string()))
3021                } else {
3022                    Ok(ResultData::Float(1.0))
3023                }
3024            }
3025            "ISREF" => Ok(ResultData::Boolean(matches!(
3026                args.first(),
3027                Some(Expr::CellRef { .. } | Expr::RangeRef { .. } | Expr::StructuredRef { .. })
3028            ))),
3029            "FORMULATEXT" | "ISFORMULA" => {
3030                let Some(arg) = args.first() else {
3031                    return Ok(ResultData::Error("#VALUE!".to_string()));
3032                };
3033                let Some((sheet_opt, r, c, _, _)) = Self::range_bounds(arg) else {
3034                    return Ok(ResultData::Error("#VALUE!".to_string()));
3035                };
3036                let is_self = sheet_opt.as_deref().is_none_or(|n| n == self.name);
3037                let src = if is_self {
3038                    deps.push(Dependency::Local(CellRef::new(r, c)));
3039                    self.get_src_str(&CellRef::new(r, c))
3040                } else if let Some(ctx) = context {
3041                    let name = sheet_opt.unwrap();
3042                    deps.push(Dependency::Remote {
3043                        sheet: name.clone(),
3044                        cell: CellRef::new(r, c),
3045                    });
3046                    ctx.sheets
3047                        .get(&name)
3048                        .map(|s| s.get_src_str(&CellRef::new(r, c)))
3049                        .unwrap_or_default()
3050                } else {
3051                    String::new()
3052                };
3053                let is_formula = src.starts_with('=');
3054                if func_name == "ISFORMULA" {
3055                    Ok(ResultData::Boolean(is_formula))
3056                } else if is_formula {
3057                    Ok(ResultData::String(src))
3058                } else {
3059                    Ok(ResultData::Error("#N/A".to_string()))
3060                }
3061            }
3062            "SHEETS" => Ok(ResultData::Float(
3063                context.map(|c| c.sheets.len() + 1).unwrap_or(1) as f64,
3064            )),
3065            "SHEET" => {
3066                let sheet_name = match args.first() {
3067                    None => Some(self.name.clone()),
3068                    Some(arg) => match Self::range_bounds(arg) {
3069                        Some((sheet_opt, ..)) => {
3070                            Some(sheet_opt.unwrap_or_else(|| self.name.clone()))
3071                        }
3072                        None => self
3073                            .evaluate_ast(arg, context, row, col, deps, scope)
3074                            .ok()
3075                            .map(|v| v.to_string()),
3076                    },
3077                };
3078
3079                match sheet_name {
3080                    Some(name) => {
3081                        let ordinal = context
3082                            .and_then(|c| {
3083                                c.sheet_order
3084                                    .iter()
3085                                    .position(|n| n.eq_ignore_ascii_case(&name))
3086                            })
3087                            .map(|i| i + 1)
3088                            .unwrap_or(1);
3089                        Ok(ResultData::Float(ordinal as f64))
3090                    }
3091                    None => Ok(ResultData::Error("#N/A".to_string())),
3092                }
3093            }
3094            "CELL" => {
3095                if args.is_empty() {
3096                    return Ok(ResultData::Error("#VALUE!".to_string()));
3097                }
3098                let info_type = self
3099                    .evaluate_ast(&args[0], context, row, col, deps, scope)?
3100                    .to_string()
3101                    .to_lowercase();
3102                let bounds = args.get(1).and_then(Self::range_bounds);
3103                match info_type.as_str() {
3104                    "row" => match bounds.map(|b| b.1).or(row) {
3105                        Some(r) => Ok(ResultData::Float((r + 1) as f64)),
3106                        None => Ok(ResultData::Error("#VALUE!".to_string())),
3107                    },
3108                    "col" => match bounds {
3109                        Some((_, _, c, _, _)) => Ok(ResultData::Float((c + 1) as f64)),
3110                        None => Ok(ResultData::Error("#VALUE!".to_string())),
3111                    },
3112                    "address" => match bounds {
3113                        Some((_, r, c, _, _)) => Ok(ResultData::String(format!(
3114                            "${}${}",
3115                            crate::core::parser::col_idx_to_letters(c),
3116                            r + 1
3117                        ))),
3118                        None => Ok(ResultData::Error("#VALUE!".to_string())),
3119                    },
3120                    "contents" => match bounds {
3121                        Some((sheet_opt, r, c, _, _)) => {
3122                            Ok(self.read_cell_with_deps(&sheet_opt, r, c, context, deps))
3123                        }
3124                        None => Ok(ResultData::Error("#VALUE!".to_string())),
3125                    },
3126                    _ => Ok(ResultData::Error("#VALUE!".to_string())),
3127                }
3128            }
3129            "INFO" => {
3130                if args.is_empty() {
3131                    return Ok(ResultData::Error("#VALUE!".to_string()));
3132                }
3133                let info_type = self
3134                    .evaluate_ast(&args[0], context, row, col, deps, scope)?
3135                    .to_string()
3136                    .to_lowercase();
3137                match info_type.as_str() {
3138                    "numfile" => Ok(ResultData::Float(
3139                        context.map(|c| c.sheets.len() + 1).unwrap_or(1) as f64,
3140                    )),
3141                    "release" => Ok(ResultData::String("16.0".to_string())),
3142                    "system" => Ok(ResultData::String(
3143                        if cfg!(target_os = "macos") {
3144                            "mac"
3145                        } else {
3146                            "pcdos"
3147                        }
3148                        .to_string(),
3149                    )),
3150                    _ => Ok(ResultData::Error("#VALUE!".to_string())),
3151                }
3152            }
3153            "INDIRECT" => {
3154                if args.is_empty() {
3155                    return Ok(ResultData::Error("#VALUE!".to_string()));
3156                }
3157                let text = self
3158                    .evaluate_ast(&args[0], context, row, col, deps, scope)?
3159                    .to_string();
3160                let a1_style = match args.get(1) {
3161                    Some(a) => self.to_bool(&self.evaluate_ast(a, context, row, col, deps, scope)?),
3162                    None => true,
3163                };
3164                if !a1_style {
3165                    return Ok(ResultData::Error("#VALUE!".to_string()));
3166                }
3167                match Self::parse_a1_reference(&text) {
3168                    Some((sheet_opt, start_row, start_col, end_row, end_col)) => {
3169                        if start_row == end_row && start_col == end_col {
3170                            Ok(self.read_cell_with_deps(
3171                                &sheet_opt, start_row, start_col, context, deps,
3172                            ))
3173                        } else {
3174                            match self.materialize_range(
3175                                &sheet_opt, start_row, start_col, end_row, end_col, context,
3176                            ) {
3177                                Some(grid) => {
3178                                    Ok(ResultData::List(grid.into_iter().flatten().collect()))
3179                                }
3180                                None => Ok(ResultData::Error("#REF!".to_string())),
3181                            }
3182                        }
3183                    }
3184                    None => Ok(ResultData::Error("#REF!".to_string())),
3185                }
3186            }
3187            "OFFSET" => {
3188                if args.len() < 3 {
3189                    return Ok(ResultData::Error("#VALUE!".to_string()));
3190                }
3191                let Some((sheet_opt, base_row, base_col, base_end_row, base_end_col)) =
3192                    Self::range_bounds(&args[0])
3193                else {
3194                    return Ok(ResultData::Error("#VALUE!".to_string()));
3195                };
3196                let row_offset = self
3197                    .to_f64(&self.evaluate_ast(&args[1], context, row, col, deps, scope)?)
3198                    .unwrap_or(0.0) as isize;
3199                let col_offset = self
3200                    .to_f64(&self.evaluate_ast(&args[2], context, row, col, deps, scope)?)
3201                    .unwrap_or(0.0) as isize;
3202                let base_height = (base_end_row.saturating_sub(base_row) + 1) as isize;
3203                let base_width = (base_end_col.saturating_sub(base_col) + 1) as isize;
3204                let height = match args.get(3) {
3205                    Some(a) => self
3206                        .to_f64(&self.evaluate_ast(a, context, row, col, deps, scope)?)
3207                        .unwrap_or(base_height as f64) as isize,
3208                    None => base_height,
3209                };
3210                let width = match args.get(4) {
3211                    Some(a) => self
3212                        .to_f64(&self.evaluate_ast(a, context, row, col, deps, scope)?)
3213                        .unwrap_or(base_width as f64) as isize,
3214                    None => base_width,
3215                };
3216                let new_row = base_row as isize + row_offset;
3217                let new_col = base_col as isize + col_offset;
3218                if new_row < 0 || new_col < 0 || height <= 0 || width <= 0 {
3219                    return Ok(ResultData::Error("#REF!".to_string()));
3220                }
3221                let (start_row, start_col) = (new_row as usize, new_col as usize);
3222                let (end_row, end_col) = (
3223                    start_row + (height - 1) as usize,
3224                    start_col + (width - 1) as usize,
3225                );
3226                if start_row == end_row && start_col == end_col {
3227                    Ok(self.read_cell_with_deps(&sheet_opt, start_row, start_col, context, deps))
3228                } else {
3229                    match self.materialize_range(
3230                        &sheet_opt, start_row, start_col, end_row, end_col, context,
3231                    ) {
3232                        Some(grid) => Ok(ResultData::List(grid.into_iter().flatten().collect())),
3233                        None => Ok(ResultData::Error("#REF!".to_string())),
3234                    }
3235                }
3236            }
3237            _ => unreachable!(),
3238        }
3239    }
3240
3241    fn evaluate_getpivotdata(
3242        &self,
3243        args: &[crate::core::parser::Expr],
3244        context: Option<&Context>,
3245        row: Option<usize>,
3246        col: Option<usize>,
3247        deps: &mut Vec<Dependency>,
3248        scope: &LetScope<'_>,
3249    ) -> Result<ResultData, EngineError> {
3250        if args.len() < 2 || !(args.len() - 2).is_multiple_of(2) {
3251            return Ok(ResultData::Error("#VALUE!".to_string()));
3252        }
3253
3254        let data_field = self
3255            .evaluate_ast(&args[0], context, row, col, deps, scope)?
3256            .to_string();
3257
3258        let (sheet_opt, target_row, target_col, _, _) = match Self::range_bounds(&args[1]) {
3259            Some(bounds) => bounds,
3260            None => return Ok(ResultData::Error("#REF!".to_string())),
3261        };
3262        self.read_cell_with_deps(&sheet_opt, target_row, target_col, context, deps);
3263
3264        let sheet_id = match &sheet_opt {
3265            None => self.id,
3266            Some(name) if name == &self.name => self.id,
3267            Some(name) => match context.and_then(|c| c.sheets.get(name)) {
3268                Some(s) => s.id,
3269                None => return Ok(ResultData::Error("#REF!".to_string())),
3270            },
3271        };
3272
3273        let pivot_tables = context.map(|c| c.pivot_tables).unwrap_or(&[]);
3274        let pivot = match pivot_tables.iter().find(|p| {
3275            p.dest_sheet_id == sheet_id
3276                && p.last_output_end_row
3277                    .is_some_and(|end| target_row >= p.dest_row && target_row <= end)
3278                && p.last_output_end_col
3279                    .is_some_and(|end| target_col >= p.dest_col && target_col <= end)
3280        }) {
3281            Some(p) => p,
3282            None => return Ok(ResultData::Error("#REF!".to_string())),
3283        };
3284
3285        let mut criteria: Vec<(String, String)> = Vec::new();
3286        let mut i = 2;
3287        while i < args.len() {
3288            let field = self
3289                .evaluate_ast(&args[i], context, row, col, deps, scope)?
3290                .to_string();
3291            let item = self
3292                .evaluate_ast(&args[i + 1], context, row, col, deps, scope)?
3293                .to_string();
3294            criteria.push((field, item));
3295            i += 2;
3296        }
3297
3298        let mut sheet_refs: Vec<&Sheet> = context
3299            .map(|c| c.sheets.values().copied().collect())
3300            .unwrap_or_default();
3301        sheet_refs.push(self);
3302
3303        match crate::core::pivot::getpivotdata(&sheet_refs, pivot, &data_field, &criteria) {
3304            Ok(v) => Ok(v),
3305            Err(e) => Ok(ResultData::Error(e)),
3306        }
3307    }
3308
3309    #[allow(clippy::too_many_arguments)]
3310    fn evaluate_array_reshape_function(
3311        &self,
3312        func_name: &str,
3313        args: &[crate::core::parser::Expr],
3314        context: Option<&Context>,
3315        row: Option<usize>,
3316        col: Option<usize>,
3317        deps: &mut Vec<Dependency>,
3318        scope: &LetScope<'_>,
3319    ) -> Result<ResultData, EngineError> {
3320        match func_name {
3321            "TRANSPOSE" => {
3322                let Some(arg) = args.first() else {
3323                    return Ok(ResultData::Error("#VALUE!".to_string()));
3324                };
3325                let (flat, cols) = self.array_shape(arg, context, row, col, deps, scope)?;
3326                let rows = flat.len().checked_div(cols).unwrap_or(0);
3327                let mut result = Vec::with_capacity(flat.len());
3328                for c in 0..cols {
3329                    for r in 0..rows {
3330                        result.push(flat[r * cols + c].clone());
3331                    }
3332                }
3333                Ok(ResultData::List(result))
3334            }
3335            "HSTACK" | "VSTACK" => {
3336                if args.is_empty() {
3337                    return Ok(ResultData::Error("#VALUE!".to_string()));
3338                }
3339                let mut shapes = Vec::with_capacity(args.len());
3340                for a in args {
3341                    shapes.push(self.array_shape(a, context, row, col, deps, scope)?);
3342                }
3343                let mut result = Vec::new();
3344                if func_name == "HSTACK" {
3345                    let max_rows = shapes
3346                        .iter()
3347                        .map(|(f, c)| if *c == 0 { 0 } else { f.len() / c })
3348                        .max()
3349                        .unwrap_or(0);
3350                    for r in 0..max_rows {
3351                        for (flat, cols) in &shapes {
3352                            let rows = if *cols == 0 { 0 } else { flat.len() / cols };
3353                            for c in 0..*cols {
3354                                result.push(if r < rows {
3355                                    flat[r * cols + c].clone()
3356                                } else {
3357                                    ResultData::Error("#N/A".to_string())
3358                                });
3359                            }
3360                        }
3361                    }
3362                } else {
3363                    let max_cols = shapes.iter().map(|(_, c)| *c).max().unwrap_or(0);
3364                    for (flat, cols) in &shapes {
3365                        let rows = if *cols == 0 { 0 } else { flat.len() / cols };
3366                        for r in 0..rows {
3367                            for c in 0..max_cols {
3368                                result.push(if c < *cols {
3369                                    flat[r * cols + c].clone()
3370                                } else {
3371                                    ResultData::Error("#N/A".to_string())
3372                                });
3373                            }
3374                        }
3375                    }
3376                }
3377                Ok(ResultData::List(result))
3378            }
3379            "CHOOSEROWS" | "CHOOSECOLS" => {
3380                if args.len() < 2 {
3381                    return Ok(ResultData::Error("#VALUE!".to_string()));
3382                }
3383                let (flat, cols) = self.array_shape(&args[0], context, row, col, deps, scope)?;
3384                let rows = flat.len().checked_div(cols).unwrap_or(0);
3385                let total = if func_name == "CHOOSEROWS" {
3386                    rows
3387                } else {
3388                    cols
3389                } as isize;
3390                let mut indices = Vec::with_capacity(args.len() - 1);
3391                for idx_expr in &args[1..] {
3392                    let n = self
3393                        .to_f64(&self.evaluate_ast(idx_expr, context, row, col, deps, scope)?)
3394                        .unwrap_or(0.0) as isize;
3395                    let real_idx = if n < 0 { total + n } else { n - 1 };
3396                    if real_idx < 0 || real_idx >= total {
3397                        return Ok(ResultData::Error("#VALUE!".to_string()));
3398                    }
3399                    indices.push(real_idx as usize);
3400                }
3401                let mut result = Vec::new();
3402                if func_name == "CHOOSEROWS" {
3403                    for r in indices {
3404                        for c in 0..cols {
3405                            result.push(flat[r * cols + c].clone());
3406                        }
3407                    }
3408                } else {
3409                    for r in 0..rows {
3410                        for &c in &indices {
3411                            result.push(flat[r * cols + c].clone());
3412                        }
3413                    }
3414                }
3415                Ok(ResultData::List(result))
3416            }
3417            "DROP" | "TAKE" => {
3418                if args.len() < 2 {
3419                    return Ok(ResultData::Error("#VALUE!".to_string()));
3420                }
3421                let (flat, cols) = self.array_shape(&args[0], context, row, col, deps, scope)?;
3422                let num_rows = flat.len().checked_div(cols).unwrap_or(0) as isize;
3423                let is_take = func_name == "TAKE";
3424                let rows_n = self
3425                    .to_f64(&self.evaluate_ast(&args[1], context, row, col, deps, scope)?)
3426                    .unwrap_or(0.0) as isize;
3427                let cols_n = match args.get(2) {
3428                    Some(e) => self
3429                        .to_f64(&self.evaluate_ast(e, context, row, col, deps, scope)?)
3430                        .unwrap_or(0.0) as isize,
3431                    None => {
3432                        if is_take {
3433                            cols as isize
3434                        } else {
3435                            0
3436                        }
3437                    }
3438                };
3439                let (row_start, row_end) = Self::drop_take_bounds(num_rows, rows_n, is_take);
3440                let (col_start, col_end) = Self::drop_take_bounds(cols as isize, cols_n, is_take);
3441                if row_start >= row_end || col_start >= col_end {
3442                    return Ok(ResultData::Error("#CALC!".to_string()));
3443                }
3444                let mut result = Vec::new();
3445                for r in row_start..row_end {
3446                    for c in col_start..col_end {
3447                        result.push(flat[(r as usize) * cols + (c as usize)].clone());
3448                    }
3449                }
3450                Ok(ResultData::List(result))
3451            }
3452            "EXPAND" => {
3453                if args.len() < 2 {
3454                    return Ok(ResultData::Error("#VALUE!".to_string()));
3455                }
3456                let (flat, cols) = self.array_shape(&args[0], context, row, col, deps, scope)?;
3457                let orig_rows = flat.len().checked_div(cols).unwrap_or(0);
3458                let new_rows = self
3459                    .to_f64(&self.evaluate_ast(&args[1], context, row, col, deps, scope)?)
3460                    .unwrap_or(orig_rows as f64) as usize;
3461                let new_cols = match args.get(2) {
3462                    Some(e) => self
3463                        .to_f64(&self.evaluate_ast(e, context, row, col, deps, scope)?)
3464                        .unwrap_or(cols as f64) as usize,
3465                    None => cols,
3466                };
3467                let pad = match args.get(3) {
3468                    Some(e) => self.evaluate_ast(e, context, row, col, deps, scope)?,
3469                    None => ResultData::Error("#N/A".to_string()),
3470                };
3471                if new_rows < orig_rows || new_cols < cols {
3472                    return Ok(ResultData::Error("#VALUE!".to_string()));
3473                }
3474                let mut result = Vec::with_capacity(new_rows * new_cols);
3475                for r in 0..new_rows {
3476                    for c in 0..new_cols {
3477                        result.push(if r < orig_rows && c < cols {
3478                            flat[r * cols + c].clone()
3479                        } else {
3480                            pad.clone()
3481                        });
3482                    }
3483                }
3484                Ok(ResultData::List(result))
3485            }
3486            "TOCOL" | "TOROW" => {
3487                let Some(arg) = args.first() else {
3488                    return Ok(ResultData::Error("#VALUE!".to_string()));
3489                };
3490                let (flat, cols) = self.array_shape(arg, context, row, col, deps, scope)?;
3491                let rows = flat.len().checked_div(cols).unwrap_or(0);
3492                let ignore = match args.get(1) {
3493                    Some(e) => self
3494                        .to_f64(&self.evaluate_ast(e, context, row, col, deps, scope)?)
3495                        .unwrap_or(0.0) as i64,
3496                    None => 0,
3497                };
3498                let scan_by_col = match args.get(2) {
3499                    Some(e) => self.to_bool(&self.evaluate_ast(e, context, row, col, deps, scope)?),
3500                    None => false,
3501                };
3502                let ordered: Vec<ResultData> = if scan_by_col {
3503                    let mut v = Vec::with_capacity(flat.len());
3504                    for c in 0..cols {
3505                        for r in 0..rows {
3506                            v.push(flat[r * cols + c].clone());
3507                        }
3508                    }
3509                    v
3510                } else {
3511                    flat
3512                };
3513                let filtered: Vec<ResultData> = ordered
3514                    .into_iter()
3515                    .filter(|v| match ignore {
3516                        1 => !matches!(v, ResultData::None),
3517                        2 => !matches!(v, ResultData::Error(_)),
3518                        3 => !matches!(v, ResultData::None | ResultData::Error(_)),
3519                        _ => true,
3520                    })
3521                    .collect();
3522                Ok(ResultData::List(filtered))
3523            }
3524            "WRAPROWS" | "WRAPCOLS" => {
3525                if args.len() < 2 {
3526                    return Ok(ResultData::Error("#VALUE!".to_string()));
3527                }
3528                let (flat, _cols) = self.array_shape(&args[0], context, row, col, deps, scope)?;
3529                let wrap = self
3530                    .to_f64(&self.evaluate_ast(&args[1], context, row, col, deps, scope)?)
3531                    .unwrap_or(1.0)
3532                    .max(1.0) as usize;
3533                let pad = match args.get(2) {
3534                    Some(e) => self.evaluate_ast(e, context, row, col, deps, scope)?,
3535                    None => ResultData::Error("#N/A".to_string()),
3536                };
3537                if func_name == "WRAPROWS" {
3538                    let mut result = flat;
3539                    let rem = result.len() % wrap;
3540                    if rem != 0 {
3541                        result.extend(std::iter::repeat_n(pad, wrap - rem));
3542                    }
3543                    Ok(ResultData::List(result))
3544                } else {
3545                    let num_result_cols = flat.len().div_ceil(wrap).max(1);
3546                    let total = wrap * num_result_cols;
3547                    let mut result = Vec::with_capacity(total);
3548                    for i in 0..total {
3549                        let col = i / wrap;
3550                        let r = i % wrap;
3551                        let target = r * num_result_cols + col;
3552                        while result.len() <= target {
3553                            result.push(pad.clone());
3554                        }
3555                        if i < flat.len() {
3556                            result[target] = flat[i].clone();
3557                        }
3558                    }
3559                    Ok(ResultData::List(result))
3560                }
3561            }
3562            "UNIQUE" => {
3563                let Some(arg) = args.first() else {
3564                    return Ok(ResultData::Error("#VALUE!".to_string()));
3565                };
3566                let (flat, _cols) = self.array_shape(arg, context, row, col, deps, scope)?;
3567                let exactly_once = match args.get(2) {
3568                    Some(e) => self.to_bool(&self.evaluate_ast(e, context, row, col, deps, scope)?),
3569                    None => false,
3570                };
3571                let mut seen: Vec<(String, ResultData, usize)> = Vec::new();
3572                for v in &flat {
3573                    let key = match v {
3574                        ResultData::None => "blank:".to_string(),
3575                        ResultData::Boolean(b) => format!("bool:{b}"),
3576                        ResultData::Integer(i) => format!("num:{}", *i as f64),
3577                        ResultData::Float(f) => format!("num:{f}"),
3578                        ResultData::String(s) => format!("str:{s}"),
3579                        ResultData::Error(e) => format!("err:{e}"),
3580                        ResultData::List(_) | ResultData::Dict(_) => format!("other:{v}"),
3581                    };
3582                    match seen.iter_mut().find(|(k, ..)| k == &key) {
3583                        Some(entry) => entry.2 += 1,
3584                        None => seen.push((key, v.clone(), 1)),
3585                    }
3586                }
3587                let result: Vec<ResultData> = seen
3588                    .into_iter()
3589                    .filter(|(_, _, count)| !exactly_once || *count == 1)
3590                    .map(|(_, v, _)| v)
3591                    .collect();
3592                Ok(ResultData::List(result))
3593            }
3594            "SORT" => {
3595                let Some(arg) = args.first() else {
3596                    return Ok(ResultData::Error("#VALUE!".to_string()));
3597                };
3598                let (flat, cols) = self.array_shape(arg, context, row, col, deps, scope)?;
3599                let rows = flat.len().checked_div(cols).unwrap_or(0);
3600                let sort_index = match args.get(1) {
3601                    Some(e) => self
3602                        .to_f64(&self.evaluate_ast(e, context, row, col, deps, scope)?)
3603                        .unwrap_or(1.0) as usize,
3604                    None => 1,
3605                };
3606                let sort_order = match args.get(2) {
3607                    Some(e) => self
3608                        .to_f64(&self.evaluate_ast(e, context, row, col, deps, scope)?)
3609                        .unwrap_or(1.0),
3610                    None => 1.0,
3611                };
3612                let col_idx = sort_index.saturating_sub(1).min(cols.saturating_sub(1));
3613                let mut row_indices: Vec<usize> = (0..rows).collect();
3614                row_indices.sort_by(|&a, &b| {
3615                    Self::sort_compare_blanks_last(
3616                        &flat[a * cols + col_idx],
3617                        &flat[b * cols + col_idx],
3618                        sort_order,
3619                    )
3620                });
3621                let mut result = Vec::with_capacity(flat.len());
3622                for r in row_indices {
3623                    for c in 0..cols {
3624                        result.push(flat[r * cols + c].clone());
3625                    }
3626                }
3627                Ok(ResultData::List(result))
3628            }
3629            "SORTBY" => {
3630                if args.len() < 2 {
3631                    return Ok(ResultData::Error("#VALUE!".to_string()));
3632                }
3633                let (flat, cols) = self.array_shape(&args[0], context, row, col, deps, scope)?;
3634                let rows = flat.len().checked_div(cols).unwrap_or(0);
3635                let by = self.eval_as_array(&args[1], context, row, col, deps, scope)?;
3636                let order = match args.get(2) {
3637                    Some(e) => self
3638                        .to_f64(&self.evaluate_ast(e, context, row, col, deps, scope)?)
3639                        .unwrap_or(1.0),
3640                    None => 1.0,
3641                };
3642                let mut row_indices: Vec<usize> = (0..rows).collect();
3643                row_indices.sort_by(|&a, &b| {
3644                    let va = by.get(a).cloned().unwrap_or(ResultData::None);
3645                    let vb = by.get(b).cloned().unwrap_or(ResultData::None);
3646                    Self::sort_compare_blanks_last(&va, &vb, order)
3647                });
3648                let mut result = Vec::with_capacity(flat.len());
3649                for r in row_indices {
3650                    for c in 0..cols {
3651                        result.push(flat[r * cols + c].clone());
3652                    }
3653                }
3654                Ok(ResultData::List(result))
3655            }
3656            "FILTER" => {
3657                if args.len() < 2 {
3658                    return Ok(ResultData::Error("#VALUE!".to_string()));
3659                }
3660                let (flat, cols) = self.array_shape(&args[0], context, row, col, deps, scope)?;
3661                let rows = flat.len().checked_div(cols).unwrap_or(0);
3662                let include = self.eval_as_array(&args[1], context, row, col, deps, scope)?;
3663                let mut result = Vec::new();
3664                for r in 0..rows {
3665                    let keep = include.get(r).map(|v| self.to_bool(v)).unwrap_or(false);
3666                    if keep {
3667                        for c in 0..cols {
3668                            result.push(flat[r * cols + c].clone());
3669                        }
3670                    }
3671                }
3672                if result.is_empty() {
3673                    match args.get(2) {
3674                        Some(e) => Ok(self.evaluate_ast(e, context, row, col, deps, scope)?),
3675                        None => Ok(ResultData::Error("#CALC!".to_string())),
3676                    }
3677                } else {
3678                    Ok(ResultData::List(result))
3679                }
3680            }
3681            "TRIMRANGE" => {
3682                let Some(arg) = args.first() else {
3683                    return Ok(ResultData::Error("#VALUE!".to_string()));
3684                };
3685                let (flat, cols) = self.array_shape(arg, context, row, col, deps, scope)?;
3686                let rows = flat.len().checked_div(cols).unwrap_or(0);
3687                let is_blank = |v: &ResultData| {
3688                    matches!(v, ResultData::None)
3689                        || matches!(v, ResultData::String(s) if s.is_empty())
3690                };
3691                let row_blank = |r: usize| (0..cols).all(|c| is_blank(&flat[r * cols + c]));
3692                let col_blank = |c: usize| (0..rows).all(|r| is_blank(&flat[r * cols + c]));
3693                let mut r_start = 0;
3694                while r_start < rows && row_blank(r_start) {
3695                    r_start += 1;
3696                }
3697                let mut r_end = rows;
3698                while r_end > r_start && row_blank(r_end - 1) {
3699                    r_end -= 1;
3700                }
3701                let mut c_start = 0;
3702                while c_start < cols && col_blank(c_start) {
3703                    c_start += 1;
3704                }
3705                let mut c_end = cols;
3706                while c_end > c_start && col_blank(c_end - 1) {
3707                    c_end -= 1;
3708                }
3709                let mut result = Vec::new();
3710                for r in r_start..r_end {
3711                    for c in c_start..c_end {
3712                        result.push(flat[r * cols + c].clone());
3713                    }
3714                }
3715                Ok(ResultData::List(result))
3716            }
3717            _ => unreachable!(),
3718        }
3719    }
3720
3721    /// The raw text typed into a cell -- `"10"`, `"=SUM(A1:A2)"` -- or `None`
3722    /// if the cell is outside the sheet's allocated grid.
3723    ///
3724    /// This is the input, not the result; see [`Sheet::get_result_data`] for
3725    /// the computed value and `Sheet::get_display_string` for what a user
3726    /// should see.
3727    pub fn get_src(&self, cell: &CellRef) -> Option<&String> {
3728        let col = self.columns.get(cell.col);
3729        if let Some(col) = col {
3730            col.src.get(cell.row)
3731        } else {
3732            None
3733        }
3734    }
3735
3736    /// [`Sheet::get_src`] with an out-of-range cell flattened to an owned
3737    /// empty string.
3738    pub fn get_src_str(&self, cell: &CellRef) -> String {
3739        let col = self.columns.get(cell.col);
3740        if let Some(col) = col {
3741            col.src.get(cell.row).cloned().unwrap_or("".to_string())
3742        } else {
3743            "".to_string()
3744        }
3745    }
3746
3747    /// [`Sheet::get_src`] as a borrowed `&str`, for callers that only read.
3748    pub fn get_src_str_ref(&self, cell: &CellRef) -> Option<&str> {
3749        let col = self.columns.get(cell.col)?;
3750        col.src.get(cell.row).map(|s| s.as_str())
3751    }
3752
3753    /// The word surrounding `char_offset` in a cell's source text, as a
3754    /// half-open range of character (not byte) indices -- what an editor needs
3755    /// for word-wise selection. See [`get_word_boundaries_from_str`].
3756    pub fn get_word_boundaries(&self, cell: &CellRef, char_offset: usize) -> (usize, usize) {
3757        let text = self.get_src_str(cell);
3758        get_word_boundaries_from_str(&text, char_offset)
3759    }
3760}