Skip to main content

truecalc_core/eval/functions/array/
mod.rs

1//! Array and matrix functions for Google Sheets compatibility.
2
3use crate::eval::{evaluate_expr, EvalCtx};
4use crate::parser::ast::Expr;
5use crate::types::{ErrorKind, Value};
6
7use super::{check_arity, check_arity_len, FunctionMeta, Registry};
8
9// ── 2D array helpers ──────────────────────────────────────────────────────────
10
11/// Convert a Value into a 2D grid (Vec<Vec<Value>>).
12/// - Nested Array (2D): outer = rows, inner = cols
13/// - Flat Array (1D): one row
14/// - Scalar: 1x1
15pub fn to_2d(v: &Value) -> Vec<Vec<Value>> {
16    match v {
17        Value::Array(outer) => {
18            if outer.iter().any(|e| matches!(e, Value::Array(_))) {
19                outer
20                    .iter()
21                    .map(|row| match row {
22                        Value::Array(cols) => cols.clone(),
23                        other => vec![other.clone()],
24                    })
25                    .collect()
26            } else {
27                vec![outer.clone()] // 1-D flat array → single row
28            }
29        }
30        other => vec![vec![other.clone()]], // scalar → 1×1
31    }
32}
33
34/// Convert a 2D grid back to a Value.
35/// - Empty grid → empty Array
36/// - Single row → flat Array
37/// - Multiple rows → nested Array of row Arrays
38pub fn from_2d(rows: Vec<Vec<Value>>) -> Value {
39    if rows.is_empty() {
40        return Value::Array(vec![]);
41    }
42    if rows.len() == 1 {
43        return Value::Array(rows.into_iter().next().unwrap());
44    }
45    Value::Array(rows.into_iter().map(Value::Array).collect())
46}
47
48/// Flatten a Value to a 1D Vec<Value> (row-major order).
49pub fn flatten_val(v: &Value) -> Vec<Value> {
50    match v {
51        Value::Array(outer) => {
52            if outer.iter().any(|e| matches!(e, Value::Array(_))) {
53                outer
54                    .iter()
55                    .flat_map(|row| match row {
56                        Value::Array(cols) => cols.clone(),
57                        other => vec![other.clone()],
58                    })
59                    .collect()
60            } else {
61                outer.clone()
62            }
63        }
64        other => vec![other.clone()],
65    }
66}
67
68/// Convert a Value to f64 for numeric computations.
69fn to_f64(v: &Value) -> Option<f64> {
70    match v {
71        Value::Number(n) => Some(*n),
72        Value::Bool(b) => Some(if *b { 1.0 } else { 0.0 }),
73        _ => None,
74    }
75}
76
77
78
79// ── ROWS ─────────────────────────────────────────────────────────────────────
80
81pub(crate) fn rows_fn(args: &[Value]) -> Value {
82    if let Some(e) = check_arity(args, 1, 1) {
83        return e;
84    }
85    let grid = to_2d(&args[0]);
86    Value::Number(grid.len() as f64)
87}
88
89// ── COLUMNS ───────────────────────────────────────────────────────────────────
90
91pub(crate) fn columns_fn(args: &[Value]) -> Value {
92    if let Some(e) = check_arity(args, 1, 1) {
93        return e;
94    }
95    let grid = to_2d(&args[0]);
96    let cols = grid.first().map(|r| r.len()).unwrap_or(0);
97    Value::Number(cols as f64)
98}
99
100// ── TRANSPOSE ─────────────────────────────────────────────────────────────────
101
102pub(crate) fn transpose_fn(args: &[Value]) -> Value {
103    if let Some(e) = check_arity(args, 1, 1) {
104        return e;
105    }
106    let grid = to_2d(&args[0]);
107    if grid.is_empty() {
108        return Value::Array(vec![]);
109    }
110    let nrows = grid.len();
111    let ncols = grid[0].len();
112    let transposed: Vec<Vec<Value>> = (0..ncols)
113        .map(|c| (0..nrows).map(|r| grid[r][c].clone()).collect())
114        .collect();
115    from_2d(transposed)
116}
117
118// ── ARRAY_CONSTRAIN ───────────────────────────────────────────────────────────
119
120pub(crate) fn array_constrain_fn(args: &[Value]) -> Value {
121    if let Some(e) = check_arity(args, 3, 3) {
122        return e;
123    }
124    let grid = to_2d(&args[0]);
125    let num_rows = match to_f64(&args[1]) {
126        Some(n) if n >= 1.0 => n as usize,
127        Some(n) if n < 0.0 => return Value::Error(ErrorKind::Num),
128        Some(_) => return Value::Error(ErrorKind::Ref),
129        None => return Value::Error(ErrorKind::Value),
130    };
131    let num_cols = match to_f64(&args[2]) {
132        Some(n) if n >= 1.0 => n as usize,
133        Some(n) if n < 0.0 => return Value::Error(ErrorKind::Num),
134        Some(_) => return Value::Error(ErrorKind::Ref),
135        None => return Value::Error(ErrorKind::Value),
136    };
137    let rows_to_take = num_rows.min(grid.len());
138    let result: Vec<Vec<Value>> = grid[..rows_to_take]
139        .iter()
140        .map(|row| {
141            let cols_to_take = num_cols.min(row.len());
142            row[..cols_to_take].to_vec()
143        })
144        .collect();
145    from_2d(result)
146}
147
148// ── CHOOSECOLS ────────────────────────────────────────────────────────────────
149
150fn choosecols_fn(args: &[Value]) -> Value {
151    if let Some(e) = check_arity(args, 2, usize::MAX) {
152        return e;
153    }
154    let grid = to_2d(&args[0]);
155    let ncols = grid.first().map(|r| r.len()).unwrap_or(0);
156    let mut selected_cols: Vec<usize> = Vec::new();
157    for col_arg in &args[1..] {
158        match to_f64(col_arg) {
159            Some(0.0) => return Value::Error(ErrorKind::Value),
160            Some(n) => {
161                let idx = if n < 0.0 {
162                    let i = (ncols as isize + n as isize) as usize;
163                    if n as isize + (ncols as isize) < 0 {
164                        return Value::Error(ErrorKind::Value);
165                    }
166                    i
167                } else {
168                    let i = n as usize - 1;
169                    if i >= ncols {
170                        return Value::Error(ErrorKind::Value);
171                    }
172                    i
173                };
174                selected_cols.push(idx);
175            }
176            None => return Value::Error(ErrorKind::Value),
177        }
178    }
179    let result: Vec<Vec<Value>> = grid
180        .iter()
181        .map(|row| {
182            selected_cols
183                .iter()
184                .map(|&c| row.get(c).cloned().unwrap_or(Value::Empty))
185                .collect()
186        })
187        .collect();
188    from_2d(result)
189}
190
191// ── CHOOSEROWS ────────────────────────────────────────────────────────────────
192
193fn chooserows_fn(args: &[Value]) -> Value {
194    if let Some(e) = check_arity(args, 2, usize::MAX) {
195        return e;
196    }
197    let grid = to_2d(&args[0]);
198    let nrows = grid.len();
199    let mut selected_rows: Vec<usize> = Vec::new();
200    for row_arg in &args[1..] {
201        match to_f64(row_arg) {
202            Some(0.0) => return Value::Error(ErrorKind::Value),
203            Some(n) => {
204                let idx = if n < 0.0 {
205                    let i = (nrows as isize + n as isize) as usize;
206                    if n as isize + (nrows as isize) < 0 {
207                        return Value::Error(ErrorKind::Value);
208                    }
209                    i
210                } else {
211                    let i = n as usize - 1;
212                    if i >= nrows {
213                        return Value::Error(ErrorKind::Value);
214                    }
215                    i
216                };
217                selected_rows.push(idx);
218            }
219            None => return Value::Error(ErrorKind::Value),
220        }
221    }
222    let result: Vec<Vec<Value>> = selected_rows
223        .iter()
224        .map(|&r| grid.get(r).cloned().unwrap_or_default())
225        .collect();
226    from_2d(result)
227}
228
229// ── FLATTEN ───────────────────────────────────────────────────────────────────
230// Returns a single-column (ROWS=n, COLS=1) array.
231// Google Sheets FLATTEN accepts multiple arguments and concatenates them.
232
233pub(crate) fn flatten_fn(args: &[Value]) -> Value {
234    if let Some(e) = check_arity(args, 1, usize::MAX) {
235        return e;
236    }
237    let mut flat: Vec<Value> = Vec::new();
238    for arg in args {
239        flat.extend(flatten_val(arg));
240    }
241    // Return as column vector (nested array of single-element rows)
242    let col: Vec<Vec<Value>> = flat.into_iter().map(|v| vec![v]).collect();
243    from_2d(col)
244}
245
246// ── HSTACK ────────────────────────────────────────────────────────────────────
247
248fn hstack_fn(args: &[Value]) -> Value {
249    if let Some(e) = check_arity(args, 1, usize::MAX) {
250        return e;
251    }
252    let grids: Vec<Vec<Vec<Value>>> = args.iter().map(to_2d).collect();
253    let nrows = grids.iter().map(|g| g.len()).max().unwrap_or(0);
254    let result: Vec<Vec<Value>> = (0..nrows)
255        .map(|r| {
256            grids
257                .iter()
258                .flat_map(|g| {
259                    g.get(r).cloned().unwrap_or_default()
260                })
261                .collect()
262        })
263        .collect();
264    from_2d(result)
265}
266
267// ── VSTACK ────────────────────────────────────────────────────────────────────
268
269fn vstack_fn(args: &[Value]) -> Value {
270    if let Some(e) = check_arity(args, 1, usize::MAX) {
271        return e;
272    }
273    let mut result: Vec<Vec<Value>> = Vec::new();
274    for arg in args {
275        let grid = to_2d(arg);
276        result.extend(grid);
277    }
278    from_2d(result)
279}
280
281// ── TOCOL ─────────────────────────────────────────────────────────────────────
282// Converts array to column vector (many rows, 1 col)
283// ignore: 0=keep all, 1=ignore blanks, 2=ignore errors, 3=ignore both
284// scan_by_col: if TRUE, scan column-major instead of row-major
285
286fn tocol_fn(args: &[Value]) -> Value {
287    if let Some(e) = check_arity(args, 1, 3) {
288        return e;
289    }
290    let ignore = if let Some(m) = args.get(1) {
291        match to_f64(m) {
292            Some(n) if (0.0..=3.0).contains(&n) => n as u8,
293            _ => return Value::Error(ErrorKind::Value),
294        }
295    } else {
296        0
297    };
298    let scan_by_col = args.get(2).map(|v| matches!(v, Value::Bool(true))).unwrap_or(false);
299
300    let flat = if scan_by_col {
301        // column-major order
302        let grid = to_2d(&args[0]);
303        let ncols = grid.first().map(|r| r.len()).unwrap_or(0);
304        let mut out = Vec::new();
305        for c in 0..ncols {
306            for row in &grid {
307                out.push(row[c].clone());
308            }
309        }
310        out
311    } else {
312        flatten_val(&args[0])
313    };
314
315    let filtered: Vec<Value> = flat.into_iter().filter(|v| {
316        let is_blank = matches!(v, Value::Empty) || matches!(v, Value::Text(s) if s.is_empty());
317        let is_error = matches!(v, Value::Error(_));
318        if ignore == 1 && is_blank { return false; }
319        if ignore == 2 && is_error { return false; }
320        if ignore == 3 && (is_blank || is_error) { return false; }
321        true
322    }).collect();
323
324    let col: Vec<Vec<Value>> = filtered.into_iter().map(|v| vec![v]).collect();
325    from_2d(col)
326}
327
328// ── TOROW ─────────────────────────────────────────────────────────────────────
329// Converts array to row vector (1 row, many cols)
330// ignore: 0=keep all, 1=ignore blanks, 2=ignore errors, 3=ignore both
331// scan_by_col: if TRUE, scan column-major instead of row-major
332
333fn torow_fn(args: &[Value]) -> Value {
334    if let Some(e) = check_arity(args, 1, 3) {
335        return e;
336    }
337    let ignore = if let Some(m) = args.get(1) {
338        match to_f64(m) {
339            Some(n) if (0.0..=3.0).contains(&n) => n as u8,
340            _ => return Value::Error(ErrorKind::Value),
341        }
342    } else {
343        0
344    };
345    let scan_by_col = args.get(2).map(|v| matches!(v, Value::Bool(true))).unwrap_or(false);
346
347    let flat = if scan_by_col {
348        // column-major order
349        let grid = to_2d(&args[0]);
350        let ncols = grid.first().map(|r| r.len()).unwrap_or(0);
351        let mut out = Vec::new();
352        for c in 0..ncols {
353            for row in &grid {
354                out.push(row[c].clone());
355            }
356        }
357        out
358    } else {
359        flatten_val(&args[0])
360    };
361
362    let filtered: Vec<Value> = flat.into_iter().filter(|v| {
363        let is_blank = matches!(v, Value::Empty) || matches!(v, Value::Text(s) if s.is_empty());
364        let is_error = matches!(v, Value::Error(_));
365        if ignore == 1 && is_blank { return false; }
366        if ignore == 2 && is_error { return false; }
367        if ignore == 3 && (is_blank || is_error) { return false; }
368        true
369    }).collect();
370
371    Value::Array(filtered)
372}
373
374// ── WRAPCOLS ──────────────────────────────────────────────────────────────────
375// WRAPCOLS(vector, wrap_count) — split into columns of wrap_count rows
376// Result: ceil(n/wrap_count) columns, wrap_count rows (pad last col with Empty)
377
378fn wrapcols_fn(args: &[Value]) -> Value {
379    if let Some(e) = check_arity(args, 2, 3) {
380        return e;
381    }
382    let flat = flatten_val(&args[0]);
383    let wrap_count = match to_f64(&args[1]) {
384        Some(n) if n >= 1.0 => n as usize,
385        Some(_) => return Value::Error(ErrorKind::Num),
386        None => return Value::Error(ErrorKind::Value),
387    };
388    let pad = args.get(2).cloned().unwrap_or(Value::Empty);
389
390    // Split into columns of wrap_count elements each
391    let ncols = flat.len().div_ceil(wrap_count);
392    let nrows = wrap_count;
393
394    // Build column-major layout, then transpose to row-major
395    let grid: Vec<Vec<Value>> = (0..nrows)
396        .map(|r| {
397            (0..ncols)
398                .map(|c| {
399                    let idx = c * wrap_count + r;
400                    flat.get(idx).cloned().unwrap_or_else(|| pad.clone())
401                })
402                .collect()
403        })
404        .collect();
405    from_2d(grid)
406}
407
408// ── WRAPROWS ──────────────────────────────────────────────────────────────────
409// WRAPROWS(vector, wrap_count) — split into rows of wrap_count cols
410
411fn wraprows_fn(args: &[Value]) -> Value {
412    if let Some(e) = check_arity(args, 2, 3) {
413        return e;
414    }
415    let flat = flatten_val(&args[0]);
416    let wrap_count = match to_f64(&args[1]) {
417        Some(n) if n >= 1.0 => n as usize,
418        Some(_) => return Value::Error(ErrorKind::Num),
419        None => return Value::Error(ErrorKind::Value),
420    };
421    let pad = args.get(2).cloned().unwrap_or(Value::Empty);
422
423    let nrows = flat.len().div_ceil(wrap_count);
424    let grid: Vec<Vec<Value>> = (0..nrows)
425        .map(|r| {
426            (0..wrap_count)
427                .map(|c| {
428                    let idx = r * wrap_count + c;
429                    flat.get(idx).cloned().unwrap_or_else(|| pad.clone())
430                })
431                .collect()
432        })
433        .collect();
434    from_2d(grid)
435}
436
437// ── SORT ──────────────────────────────────────────────────────────────────────
438
439pub(crate) fn sort_fn(args: &[Value]) -> Value {
440    if let Some(e) = check_arity(args, 1, 4) {
441        return e;
442    }
443    let is_1d = matches!(&args[0], Value::Array(outer) if !outer.iter().any(|e| matches!(e, Value::Array(_))));
444
445    // Google Sheets semantics: a flat 1-D row array is treated as a single row.
446    // SORT sorts *rows*; with only one row nothing changes regardless of parameters.
447    // Exception: if by_col=TRUE is requested on a 1D array, GS returns #N/A.
448    if is_1d {
449        let by_col = args.get(3).map(|v| matches!(v, Value::Bool(true))).unwrap_or(false);
450        if by_col {
451            return Value::Error(ErrorKind::NA);
452        }
453        return args[0].clone();
454    }
455
456    let mut grid = to_2d(&args[0]);
457    let sort_col = if args.len() >= 2 {
458        match to_f64(&args[1]) {
459            Some(n) if n >= 1.0 => n as usize - 1,
460            Some(_) => return Value::Error(ErrorKind::Value),
461            None => 0,
462        }
463    } else {
464        0
465    };
466    let ascending = if args.len() >= 3 {
467        match &args[2] {
468            Value::Number(n) => *n >= 0.0,
469            Value::Bool(b) => *b,
470            _ => true,
471        }
472    } else {
473        true
474    };
475
476    grid.sort_by(|a, b| {
477        let va = a.get(sort_col).unwrap_or(&Value::Empty);
478        let vb = b.get(sort_col).unwrap_or(&Value::Empty);
479        let cmp = compare_values_sort(va, vb);
480        if ascending { cmp } else { cmp.reverse() }
481    });
482    from_2d(grid)
483}
484
485fn compare_values_sort(a: &Value, b: &Value) -> std::cmp::Ordering {
486    match (a, b) {
487        (Value::Number(x), Value::Number(y)) => x.partial_cmp(y).unwrap_or(std::cmp::Ordering::Equal),
488        (Value::Text(x), Value::Text(y)) => x.cmp(y),
489        (Value::Bool(x), Value::Bool(y)) => x.cmp(y),
490        _ => std::cmp::Ordering::Equal,
491    }
492}
493
494// ── SORTBY ────────────────────────────────────────────────────────────────────
495
496fn sortby_fn(args: &[Value]) -> Value {
497    if let Some(e) = check_arity(args, 2, usize::MAX) {
498        return e;
499    }
500    let is_1d = matches!(&args[0], Value::Array(outer) if !outer.iter().any(|e| matches!(e, Value::Array(_))));
501
502    if is_1d {
503        // 1D: treat each element as a separate item to sort
504        let elems = flatten_val(&args[0]);
505        let n = elems.len();
506
507        let mut sort_keys: Vec<(Vec<Value>, bool)> = Vec::new();
508        let mut i = 1;
509        while i < args.len() {
510            let key_vals = flatten_val(&args[i]);
511            if key_vals.len() != n {
512                return Value::Error(ErrorKind::Value);
513            }
514            let ascending = if i + 1 < args.len() {
515                match to_f64(&args[i + 1]) {
516                    Some(v) => v >= 0.0,
517                    None => true,
518                }
519            } else {
520                true
521            };
522            sort_keys.push((key_vals, ascending));
523            i += 2;
524        }
525
526        let mut indices: Vec<usize> = (0..n).collect();
527        indices.sort_by(|&ra, &rb| {
528            for (keys, asc) in &sort_keys {
529                let va = keys.get(ra).unwrap_or(&Value::Empty);
530                let vb = keys.get(rb).unwrap_or(&Value::Empty);
531                let cmp = compare_values_sort(va, vb);
532                if cmp != std::cmp::Ordering::Equal {
533                    return if *asc { cmp } else { cmp.reverse() };
534                }
535            }
536            std::cmp::Ordering::Equal
537        });
538
539        return Value::Array(indices.iter().map(|&r| elems[r].clone()).collect());
540    }
541
542    let grid = to_2d(&args[0]);
543    let nrows = grid.len();
544
545    // Collect (sort_key_array, order) pairs
546    let mut sort_keys: Vec<(Vec<Value>, bool)> = Vec::new();
547    let mut i = 1;
548    while i < args.len() {
549        let key_vals = flatten_val(&args[i]);
550        if key_vals.len() != nrows && nrows > 1 {
551            return Value::Error(ErrorKind::Value);
552        }
553        let ascending = if i + 1 < args.len() {
554            match to_f64(&args[i + 1]) {
555                Some(n) => n >= 0.0,
556                None => true,
557            }
558        } else {
559            true
560        };
561        sort_keys.push((key_vals, ascending));
562        i += 2;
563    }
564
565    let mut indices: Vec<usize> = (0..nrows).collect();
566    indices.sort_by(|&ra, &rb| {
567        for (keys, asc) in &sort_keys {
568            let va = keys.get(ra).unwrap_or(&Value::Empty);
569            let vb = keys.get(rb).unwrap_or(&Value::Empty);
570            let cmp = compare_values_sort(va, vb);
571            if cmp != std::cmp::Ordering::Equal {
572                return if *asc { cmp } else { cmp.reverse() };
573            }
574        }
575        std::cmp::Ordering::Equal
576    });
577
578    let sorted: Vec<Vec<Value>> = indices.iter().map(|&r| grid[r].clone()).collect();
579    drop(grid);
580    from_2d(sorted)
581}
582
583// ── UNIQUE ────────────────────────────────────────────────────────────────────
584
585pub(crate) fn unique_fn(args: &[Value]) -> Value {
586    if let Some(e) = check_arity(args, 1, 3) {
587        return e;
588    }
589    let is_1d = matches!(&args[0], Value::Array(outer) if !outer.iter().any(|e| matches!(e, Value::Array(_))));
590    let grid = to_2d(&args[0]);
591    // by_col defaults to false (deduplicate rows)
592    let by_col = args.get(1).map(|v| matches!(v, Value::Bool(true))).unwrap_or(false);
593    let exactly_once = args.get(2).map(|v| matches!(v, Value::Bool(true))).unwrap_or(false);
594
595    // Google Sheets semantics: a flat 1-D row array is treated as a single row.
596    // UNIQUE with by_col=FALSE deduplicates rows; with only one row, it is
597    // always unique and is returned as-is (regardless of exactly_once).
598    if is_1d && !by_col {
599        return args[0].clone();
600    }
601
602    if by_col {
603        // Deduplicate columns
604        let nrows = grid.len();
605        if nrows == 0 {
606            return from_2d(vec![]);
607        }
608        let ncols = grid[0].len();
609        // Build column-major representation
610        let columns: Vec<Vec<Value>> = (0..ncols)
611            .map(|c| grid.iter().map(|row| row[c].clone()).collect())
612            .collect();
613        let mut seen_cols: Vec<Vec<Value>> = Vec::new();
614        let mut counts: Vec<usize> = Vec::new();
615        for col in columns {
616            if let Some(pos) = seen_cols.iter().position(|sc| sc == &col) {
617                counts[pos] += 1;
618            } else {
619                seen_cols.push(col);
620                counts.push(1);
621            }
622        }
623        let result_cols: Vec<Vec<Value>> = seen_cols
624            .into_iter()
625            .zip(counts)
626            .filter(|(_, cnt)| !exactly_once || *cnt == 1)
627            .map(|(col, _)| col)
628            .collect();
629        // Transpose back to row-major
630        let ncols2 = result_cols.len();
631        let result: Vec<Vec<Value>> = (0..nrows)
632            .map(|r| (0..ncols2).map(|c| result_cols[c][r].clone()).collect())
633            .collect();
634        return from_2d(result);
635    }
636
637    // Deduplicate rows
638    let mut seen_rows: Vec<Vec<Value>> = Vec::new();
639    let mut counts: Vec<usize> = Vec::new();
640    for row in &grid {
641        if let Some(pos) = seen_rows.iter().position(|sr| sr == row) {
642            counts[pos] += 1;
643        } else {
644            seen_rows.push(row.clone());
645            counts.push(1);
646        }
647    }
648    let result: Vec<Vec<Value>> = seen_rows
649        .into_iter()
650        .zip(counts)
651        .filter(|(_, cnt)| !exactly_once || *cnt == 1)
652        .map(|(row, _)| row)
653        .collect();
654    from_2d(result)
655}
656
657// ── SUMPRODUCT ────────────────────────────────────────────────────────────────
658
659pub(crate) fn sumproduct_fn(args: &[Value]) -> Value {
660    if let Some(e) = check_arity(args, 1, usize::MAX) {
661        return e;
662    }
663    let arrays: Vec<Vec<Value>> = args.iter().map(flatten_val).collect();
664    let len = arrays[0].len();
665    // All arrays must have the same length
666    for arr in &arrays[1..] {
667        if arr.len() != len {
668            return Value::Error(ErrorKind::Value);
669        }
670    }
671    let mut sum = 0.0;
672    for i in 0..len {
673        let mut prod = 1.0;
674        for arr in &arrays {
675            prod *= to_f64(&arr[i]).unwrap_or(0.0);
676        }
677        sum += prod;
678    }
679    Value::Number(sum)
680}
681
682// ── SUMXMY2 ───────────────────────────────────────────────────────────────────
683
684fn sumxmy2_fn(args: &[Value]) -> Value {
685    if let Some(e) = check_arity(args, 2, 2) {
686        return e;
687    }
688    let xs = flatten_val(&args[0]);
689    let ys = flatten_val(&args[1]);
690    if xs.len() != ys.len() {
691        return Value::Error(ErrorKind::NA);
692    }
693    let mut sum = 0.0;
694    for (x, y) in xs.iter().zip(ys.iter()) {
695        // Only numeric values contribute; text, booleans, errors, empty are skipped.
696        if let (Value::Number(xn), Value::Number(yn)) = (x, y) {
697            sum += (*xn - *yn).powi(2);
698        }
699    }
700    Value::Number(sum)
701}
702
703// ── SUMX2MY2 ──────────────────────────────────────────────────────────────────
704
705fn sumx2my2_fn(args: &[Value]) -> Value {
706    if let Some(e) = check_arity(args, 2, 2) {
707        return e;
708    }
709    let xs = flatten_val(&args[0]);
710    let ys = flatten_val(&args[1]);
711    if xs.len() != ys.len() {
712        return Value::Error(ErrorKind::NA);
713    }
714    let mut sum = 0.0;
715    for (x, y) in xs.iter().zip(ys.iter()) {
716        if let (Value::Number(xn), Value::Number(yn)) = (x, y) {
717            sum += *xn * *xn - *yn * *yn;
718        }
719    }
720    Value::Number(sum)
721}
722
723// ── SUMX2PY2 ──────────────────────────────────────────────────────────────────
724
725fn sumx2py2_fn(args: &[Value]) -> Value {
726    if let Some(e) = check_arity(args, 2, 2) {
727        return e;
728    }
729    let xs = flatten_val(&args[0]);
730    let ys = flatten_val(&args[1]);
731    if xs.len() != ys.len() {
732        return Value::Error(ErrorKind::NA);
733    }
734    let mut sum = 0.0;
735    for (x, y) in xs.iter().zip(ys.iter()) {
736        if let (Value::Number(xn), Value::Number(yn)) = (x, y) {
737            sum += *xn * *xn + *yn * *yn;
738        }
739    }
740    Value::Number(sum)
741}
742
743// ── MMULT ─────────────────────────────────────────────────────────────────────
744
745fn mmult_fn(args: &[Value]) -> Value {
746    if let Some(e) = check_arity(args, 2, 2) {
747        return e;
748    }
749    let a = to_2d(&args[0]);
750    let b = to_2d(&args[1]);
751    if a.iter().chain(b.iter()).any(|row| row.iter().any(|v| matches!(v, Value::Bool(_)))) {
752        return Value::Error(ErrorKind::Value);
753    }
754    let n = a.first().map(|r| r.len()).unwrap_or(0);
755    let p = b.first().map(|r| r.len()).unwrap_or(0);
756    if b.len() != n {
757        return Value::Error(ErrorKind::Value);
758    }
759    // Convert to f64 matrices for computation
760    let af: Vec<Vec<f64>> = a.iter().map(|row| {
761        row.iter().map(|v| to_f64(v).unwrap_or(f64::NAN)).collect()
762    }).collect();
763    let bf: Vec<Vec<f64>> = b.iter().map(|row| {
764        row.iter().map(|v| to_f64(v).unwrap_or(f64::NAN)).collect()
765    }).collect();
766    if af.iter().any(|r| r.iter().any(|v| v.is_nan())) || bf.iter().any(|r| r.iter().any(|v| v.is_nan())) {
767        return Value::Error(ErrorKind::Value);
768    }
769    let result: Vec<Vec<Value>> = af.iter().map(|row_a| {
770        (0..p).map(|j| {
771            let sum: f64 = row_a.iter().enumerate().map(|(k, &av)| av * bf[k][j]).sum();
772            Value::Number(sum)
773        }).collect()
774    }).collect();
775    from_2d(result)
776}
777
778// ── MDETERM ───────────────────────────────────────────────────────────────────
779
780fn mdeterm_fn(args: &[Value]) -> Value {
781    if let Some(e) = check_arity(args, 1, 1) {
782        return e;
783    }
784    let grid = to_2d(&args[0]);
785    let n = grid.len();
786    if n == 0 {
787        return Value::Error(ErrorKind::Value);
788    }
789    for row in &grid {
790        if row.len() != n {
791            return Value::Error(ErrorKind::Value);
792        }
793    }
794    if grid.iter().any(|row| row.iter().any(|v| matches!(v, Value::Bool(_)))) {
795        return Value::Error(ErrorKind::Value);
796    }
797    // Convert to f64 matrix
798    let mut mat: Vec<Vec<f64>> = Vec::with_capacity(n);
799    for row in &grid {
800        let mut r = Vec::with_capacity(n);
801        for v in row {
802            match to_f64(v) {
803                Some(x) => r.push(x),
804                None => return Value::Error(ErrorKind::Value),
805            }
806        }
807        mat.push(r);
808    }
809    Value::Number(determinant(&mat))
810}
811
812fn determinant(mat: &[Vec<f64>]) -> f64 {
813    let n = mat.len();
814    if n == 1 {
815        return mat[0][0];
816    }
817    if n == 2 {
818        return mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0];
819    }
820    let mut det = 0.0;
821    for c in 0..n {
822        let minor: Vec<Vec<f64>> = (1..n)
823            .map(|r| {
824                (0..n)
825                    .filter(|&cc| cc != c)
826                    .map(|cc| mat[r][cc])
827                    .collect()
828            })
829            .collect();
830        let sign = if c % 2 == 0 { 1.0 } else { -1.0 };
831        det += sign * mat[0][c] * determinant(&minor);
832    }
833    det
834}
835
836// ── MINVERSE ──────────────────────────────────────────────────────────────────
837
838fn minverse_fn(args: &[Value]) -> Value {
839    if let Some(e) = check_arity(args, 1, 1) {
840        return e;
841    }
842    let grid = to_2d(&args[0]);
843    let n = grid.len();
844    if n == 0 {
845        return Value::Error(ErrorKind::Value);
846    }
847    for row in &grid {
848        if row.len() != n {
849            return Value::Error(ErrorKind::Value);
850        }
851    }
852    if grid.iter().any(|row| row.iter().any(|v| matches!(v, Value::Bool(_)))) {
853        return Value::Error(ErrorKind::Value);
854    }
855    let mut mat: Vec<Vec<f64>> = Vec::with_capacity(n);
856    for row in &grid {
857        let mut r = Vec::with_capacity(n);
858        for v in row {
859            match to_f64(v) {
860                Some(x) => r.push(x),
861                None => return Value::Error(ErrorKind::Value),
862            }
863        }
864        mat.push(r);
865    }
866    match invert_matrix(mat) {
867        Some(inv) => from_2d(inv.into_iter().map(|r| r.into_iter().map(Value::Number).collect()).collect()),
868        None => Value::Error(ErrorKind::Num),
869    }
870}
871
872fn invert_matrix(mut mat: Vec<Vec<f64>>) -> Option<Vec<Vec<f64>>> {
873    let n = mat.len();
874    // Augment with identity
875    let mut inv: Vec<Vec<f64>> = (0..n)
876        .map(|i| (0..n).map(|j| if i == j { 1.0 } else { 0.0 }).collect())
877        .collect();
878    for col in 0..n {
879        // Find pivot
880        let pivot = (col..n).max_by(|&a, &b| mat[a][col].abs().partial_cmp(&mat[b][col].abs()).unwrap_or(std::cmp::Ordering::Equal))?;
881        if mat[pivot][col].abs() < 1e-12 {
882            return None; // singular
883        }
884        mat.swap(col, pivot);
885        inv.swap(col, pivot);
886        let div = mat[col][col];
887        for j in 0..n {
888            mat[col][j] /= div;
889            inv[col][j] /= div;
890        }
891        for r in 0..n {
892            if r != col {
893                let factor = mat[r][col];
894                for j in 0..n {
895                    mat[r][j] -= factor * mat[col][j];
896                    inv[r][j] -= factor * inv[col][j];
897                }
898            }
899        }
900    }
901    Some(inv)
902}
903
904// ── FREQUENCY ─────────────────────────────────────────────────────────────────
905// Array-spill function; Google Sheets returns #REF! in scalar (non-array-formula) context.
906
907fn frequency_fn(args: &[Value]) -> Value {
908    if let Some(e) = check_arity(args, 2, 2) {
909        return e;
910    }
911    // Only numeric values are counted; text, booleans and blanks are ignored.
912    let data: Vec<f64> = flatten_val(&args[0])
913        .iter()
914        .filter_map(|v| if let Value::Number(n) = v { Some(*n) } else { None })
915        .collect();
916    let bins_raw = flatten_val(&args[1]);
917    // Empty bins array → #REF! (Google Sheets behaviour)
918    if bins_raw.is_empty() || matches!(bins_raw.as_slice(), [Value::Empty]) {
919        return Value::Error(ErrorKind::Ref);
920    }
921    // Also treat an array whose only element is Empty as empty
922    let all_empty = bins_raw.iter().all(|v| matches!(v, Value::Empty));
923    if all_empty {
924        return Value::Error(ErrorKind::Ref);
925    }
926    let bins: Vec<f64> = bins_raw
927        .iter()
928        .filter_map(|v| if let Value::Number(n) = v { Some(*n) } else { None })
929        .collect();
930    if bins.is_empty() {
931        return Value::Error(ErrorKind::Ref);
932    }
933    // One bucket per bin, plus a final "greater than the last bin" bucket.
934    let mut counts = vec![0i64; bins.len() + 1];
935    for &x in &data {
936        let mut placed = false;
937        for (i, &b) in bins.iter().enumerate() {
938            if x <= b {
939                counts[i] += 1;
940                placed = true;
941                break;
942            }
943        }
944        if !placed {
945            counts[bins.len()] += 1;
946        }
947    }
948    // Sheets returns a vertical (column) array of length bins+1.
949    let col: Vec<Vec<Value>> = counts
950        .into_iter()
951        .map(|c| vec![Value::Number(c as f64)])
952        .collect();
953    from_2d(col)
954}
955
956// ── LINEST ────────────────────────────────────────────────────────────────────
957// LINEST(known_y, [known_x], [const], [stats]) → returns 1-row array [slope, intercept, ...]
958
959fn linest_fn(args: &[Value]) -> Value {
960    if let Some(e) = check_arity(args, 1, 4) {
961        return e;
962    }
963    let ys = flatten_val(&args[0]);
964    let n = ys.len();
965    // boolean or text y-values → #VALUE!
966    if ys.iter().any(|v| matches!(v, Value::Bool(_) | Value::Text(_))) {
967        return Value::Error(ErrorKind::Value);
968    }
969    if n < 2 {
970        return Value::Error(ErrorKind::NA);
971    }
972    let xs: Vec<f64> = if args.len() >= 2 {
973        let xv = flatten_val(&args[1]);
974        if xv.len() != n {
975            return Value::Error(ErrorKind::Ref);
976        }
977        xv.iter().filter_map(to_f64).collect()
978    } else {
979        (1..=n).map(|i| i as f64).collect()
980    };
981    if xs.len() != n {
982        return Value::Error(ErrorKind::Ref);
983    }
984    let y_vals: Vec<f64> = ys.iter().filter_map(to_f64).collect();
985    if y_vals.len() != n {
986        return Value::Error(ErrorKind::Value);
987    }
988    let (slope, intercept) = simple_linear_regression(&xs, &y_vals);
989    Value::Array(vec![Value::Number(slope), Value::Number(intercept)])
990}
991
992fn simple_linear_regression(xs: &[f64], ys: &[f64]) -> (f64, f64) {
993    let n = xs.len() as f64;
994    let sum_x: f64 = xs.iter().sum();
995    let sum_y: f64 = ys.iter().sum();
996    let sum_xy: f64 = xs.iter().zip(ys.iter()).map(|(x, y)| x * y).sum();
997    let sum_xx: f64 = xs.iter().map(|x| x * x).sum();
998    let denom = n * sum_xx - sum_x * sum_x;
999    if denom.abs() < 1e-15 {
1000        let intercept = sum_y / n;
1001        return (0.0, intercept);
1002    }
1003    let slope = (n * sum_xy - sum_x * sum_y) / denom;
1004    let intercept = (sum_y - slope * sum_x) / n;
1005    (slope, intercept)
1006}
1007
1008// ── LOGEST ────────────────────────────────────────────────────────────────────
1009// LOGEST(known_y, [known_x], [const], [stats]) → returns 1-row array [base, intercept, ...]
1010
1011fn logest_fn(args: &[Value]) -> Value {
1012    if let Some(e) = check_arity(args, 1, 4) {
1013        return e;
1014    }
1015    let ys = flatten_val(&args[0]);
1016    let n = ys.len();
1017    // boolean y-values → #VALUE! (TRUE would coerce to 1 but GS errors)
1018    if ys.iter().any(|v| matches!(v, Value::Bool(_))) {
1019        return Value::Error(ErrorKind::Value);
1020    }
1021    if n < 2 {
1022        return Value::Error(ErrorKind::NA);
1023    }
1024    let xs: Vec<f64> = if args.len() >= 2 {
1025        let xv = flatten_val(&args[1]);
1026        if xv.len() != n {
1027            return Value::Error(ErrorKind::Ref);
1028        }
1029        xv.iter().filter_map(to_f64).collect()
1030    } else {
1031        (1..=n).map(|i| i as f64).collect()
1032    };
1033    if xs.len() != n {
1034        return Value::Error(ErrorKind::Ref);
1035    }
1036    let y_vals: Vec<f64> = ys.iter().filter_map(to_f64).collect();
1037    if y_vals.len() != n {
1038        return Value::Error(ErrorKind::Value);
1039    }
1040    // Take log of y values
1041    let log_y: Vec<f64> = y_vals.iter().map(|&y| y.ln()).collect();
1042    if log_y.iter().any(|v| v.is_nan() || v.is_infinite()) {
1043        return Value::Error(ErrorKind::Num);
1044    }
1045    let (log_base, log_intercept) = simple_linear_regression(&xs, &log_y);
1046    let base = log_base.exp();
1047    let intercept = log_intercept.exp();
1048    Value::Array(vec![Value::Number(base), Value::Number(intercept)])
1049}
1050
1051// ── TREND ─────────────────────────────────────────────────────────────────────
1052// TREND(known_y, [known_x], [new_x], [const]) → array of fitted/predicted values
1053
1054fn trend_fn(args: &[Value]) -> Value {
1055    if let Some(e) = check_arity(args, 1, 4) {
1056        return e;
1057    }
1058    let ys = flatten_val(&args[0]);
1059    let n = ys.len();
1060    // boolean or text y-values → #VALUE!
1061    if ys.iter().any(|v| matches!(v, Value::Bool(_) | Value::Text(_))) {
1062        return Value::Error(ErrorKind::Value);
1063    }
1064    if n < 2 {
1065        return Value::Error(ErrorKind::NA);
1066    }
1067    let xs: Vec<f64> = if args.len() >= 2 {
1068        let xv = flatten_val(&args[1]);
1069        if xv.len() != n {
1070            return Value::Error(ErrorKind::Ref);
1071        }
1072        xv.iter().filter_map(to_f64).collect()
1073    } else {
1074        (1..=n).map(|i| i as f64).collect()
1075    };
1076    if xs.len() != n {
1077        return Value::Error(ErrorKind::Ref);
1078    }
1079    let y_vals: Vec<f64> = ys.iter().filter_map(to_f64).collect();
1080    if y_vals.len() != n {
1081        return Value::Error(ErrorKind::Value);
1082    }
1083    let new_xs: Vec<f64> = if args.len() >= 3 {
1084        flatten_val(&args[2]).iter().filter_map(to_f64).collect()
1085    } else {
1086        xs.clone()
1087    };
1088    let (slope, intercept) = simple_linear_regression(&xs, &y_vals);
1089    let result: Vec<Value> = new_xs.iter().map(|&x| Value::Number(slope * x + intercept)).collect();
1090    Value::Array(result)
1091}
1092
1093// ── GROWTH ────────────────────────────────────────────────────────────────────
1094// GROWTH(known_y, [known_x], [new_x], [const]) → exponential predictions
1095
1096fn growth_fn(args: &[Value]) -> Value {
1097    if let Some(e) = check_arity(args, 1, 4) {
1098        return e;
1099    }
1100    let ys = flatten_val(&args[0]);
1101    let n = ys.len();
1102    // boolean y-values → #VALUE! (GS errors on TRUE/FALSE in y)
1103    if ys.iter().any(|v| matches!(v, Value::Bool(_))) {
1104        return Value::Error(ErrorKind::Value);
1105    }
1106    if n < 2 {
1107        return Value::Error(ErrorKind::NA);
1108    }
1109    let xs: Vec<f64> = if args.len() >= 2 {
1110        let xv = flatten_val(&args[1]);
1111        if xv.len() != n {
1112            return Value::Error(ErrorKind::Ref);
1113        }
1114        xv.iter().filter_map(to_f64).collect()
1115    } else {
1116        (1..=n).map(|i| i as f64).collect()
1117    };
1118    if xs.len() != n {
1119        return Value::Error(ErrorKind::Ref);
1120    }
1121    let y_vals: Vec<f64> = ys.iter().filter_map(to_f64).collect();
1122    if y_vals.len() != n {
1123        return Value::Error(ErrorKind::Value);
1124    }
1125    let log_y: Vec<f64> = y_vals.iter().map(|&y| y.ln()).collect();
1126    if log_y.iter().any(|v| v.is_nan() || v.is_infinite()) {
1127        return Value::Error(ErrorKind::Num);
1128    }
1129    let new_xs: Vec<f64> = if args.len() >= 3 && !matches!(args[2], Value::Empty) {
1130        let vals: Vec<f64> = flatten_val(&args[2]).iter().filter_map(to_f64).collect();
1131        if vals.is_empty() { xs.clone() } else { vals }
1132    } else {
1133        xs.clone()
1134    };
1135    // b param: TRUE (default) = compute intercept normally;
1136    //          FALSE = force intercept through origin (ln(b)=0, so b=1)
1137    let use_intercept = if args.len() >= 4 {
1138        match &args[3] {
1139            Value::Bool(b) => *b,
1140            Value::Number(n) => *n != 0.0,
1141            _ => true,
1142        }
1143    } else {
1144        true
1145    };
1146    let (log_base, log_intercept) = if use_intercept {
1147        simple_linear_regression(&xs, &log_y)
1148    } else {
1149        // Force intercept = 0: slope = sum(x*lny)/sum(x^2)
1150        let sum_xy: f64 = xs.iter().zip(log_y.iter()).map(|(x, ly)| x * ly).sum();
1151        let sum_xx: f64 = xs.iter().map(|x| x * x).sum();
1152        let slope = if sum_xx.abs() < 1e-15 { 0.0 } else { sum_xy / sum_xx };
1153        (slope, 0.0)
1154    };
1155    let result: Vec<Value> = new_xs
1156        .iter()
1157        .map(|&x| Value::Number((log_base * x + log_intercept).exp()))
1158        .collect();
1159    Value::Array(result)
1160}
1161
1162// ── Higher-order functions (LazyFn) ───────────────────────────────────────────
1163
1164/// Apply a LAMBDA expression with bound parameter values.
1165/// `lambda_expr` should be `Expr::FunctionCall { name: "LAMBDA", args: [p1, ..., body] }`
1166/// `bound_args` are the Values to bind to p1, p2, ...
1167fn apply_lambda(lambda_expr: &Expr, bound_args: &[Value], ctx: &mut EvalCtx<'_>) -> Option<Value> {
1168    match lambda_expr {
1169        Expr::FunctionCall { name, args, .. } if name == "LAMBDA" => {
1170            if args.is_empty() {
1171                return None;
1172            }
1173            let body = &args[args.len() - 1];
1174            let params = &args[..args.len() - 1];
1175            if params.len() != bound_args.len() {
1176                return None;
1177            }
1178            // Bind each parameter in context
1179            let mut saved: Vec<(String, Value)> = Vec::new();
1180            for (param_expr, val) in params.iter().zip(bound_args.iter()) {
1181                if let Expr::Variable(name, _) = param_expr {
1182                    let old = ctx.ctx.get(name);
1183                    saved.push((name.clone(), old));
1184                    ctx.ctx.set(name.clone(), val.clone());
1185                } else {
1186                    return None;
1187                }
1188            }
1189            let result = evaluate_expr(body, ctx);
1190            // Restore context
1191            for (name, old_val) in saved {
1192                ctx.ctx.set(name, old_val);
1193            }
1194            Some(result)
1195        }
1196        _ => None,
1197    }
1198}
1199
1200// ── BYROW ─────────────────────────────────────────────────────────────────────
1201
1202pub fn byrow_lazy_fn(args: &[Expr], ctx: &mut EvalCtx<'_>) -> Value {
1203    if let Some(e) = check_arity_len(args.len(), 2, 2) {
1204        return e;
1205    }
1206    let arr_val = evaluate_expr(&args[0], ctx);
1207    if matches!(arr_val, Value::Error(_)) {
1208        return arr_val;
1209    }
1210    let grid = to_2d(&arr_val);
1211    let lambda_expr = &args[1];
1212    let mut results: Vec<Value> = Vec::with_capacity(grid.len());
1213    for row in &grid {
1214        let row_val = Value::Array(row.clone());
1215        match apply_lambda(lambda_expr, &[row_val], ctx) {
1216            Some(v) => results.push(v),
1217            None => return Value::Error(ErrorKind::NA),
1218        }
1219    }
1220    // Return as column vector (one result per row)
1221    let col: Vec<Vec<Value>> = results.into_iter().map(|v| vec![v]).collect();
1222    from_2d(col)
1223}
1224
1225// ── BYCOL ─────────────────────────────────────────────────────────────────────
1226
1227pub fn bycol_lazy_fn(args: &[Expr], ctx: &mut EvalCtx<'_>) -> Value {
1228    if let Some(e) = check_arity_len(args.len(), 2, 2) {
1229        return e;
1230    }
1231    let arr_val = evaluate_expr(&args[0], ctx);
1232    if matches!(arr_val, Value::Error(_)) {
1233        return arr_val;
1234    }
1235    let grid = to_2d(&arr_val);
1236    let ncols = grid.first().map(|r| r.len()).unwrap_or(0);
1237    // Build columns first to avoid range-loop indexing
1238    let columns: Vec<Vec<Value>> = (0..ncols)
1239        .map(|c| grid.iter().map(|row| row[c].clone()).collect())
1240        .collect();
1241    let lambda_expr = &args[1];
1242    let mut results: Vec<Value> = Vec::with_capacity(ncols);
1243    for col in columns {
1244        // Pass flat array so SUM/MAX/MIN etc can iterate over elements
1245        let col_val = Value::Array(col);
1246        match apply_lambda(lambda_expr, &[col_val], ctx) {
1247            Some(v) => results.push(v),
1248            None => return Value::Error(ErrorKind::NA),
1249        }
1250    }
1251    // Return as row vector (one result per col)
1252    Value::Array(results)
1253}
1254
1255// ── MAP ───────────────────────────────────────────────────────────────────────
1256
1257pub fn map_lazy_fn(args: &[Expr], ctx: &mut EvalCtx<'_>) -> Value {
1258    if let Some(e) = check_arity_len(args.len(), 2, usize::MAX) {
1259        return e;
1260    }
1261    // Last arg is LAMBDA, all prior are arrays
1262    let lambda_expr = &args[args.len() - 1];
1263    let arr_count = args.len() - 1;
1264    let arrays: Vec<Vec<Value>> = args[..arr_count]
1265        .iter()
1266        .map(|a| {
1267            let v = evaluate_expr(a, ctx);
1268            flatten_val(&v)
1269        })
1270        .collect();
1271    let len = arrays[0].len();
1272    for arr in &arrays[1..] {
1273        if arr.len() != len {
1274            return Value::Error(ErrorKind::Value);
1275        }
1276    }
1277    let mut results: Vec<Value> = Vec::with_capacity(len);
1278    for i in 0..len {
1279        let bound: Vec<Value> = arrays.iter().map(|a| a[i].clone()).collect();
1280        match apply_lambda(lambda_expr, &bound, ctx) {
1281            Some(v) => results.push(v),
1282            None => return Value::Error(ErrorKind::NA),
1283        }
1284    }
1285    // Preserve shape of first array
1286    let first_grid = to_2d(&evaluate_expr(&args[0], ctx));
1287    if first_grid.len() > 1 {
1288        // 2D → reshape results
1289        let ncols = first_grid[0].len();
1290        let nrows = first_grid.len();
1291        let grid: Vec<Vec<Value>> = (0..nrows)
1292            .map(|r| (0..ncols).map(|c| results[r * ncols + c].clone()).collect())
1293            .collect();
1294        from_2d(grid)
1295    } else {
1296        Value::Array(results)
1297    }
1298}
1299
1300// ── REDUCE ────────────────────────────────────────────────────────────────────
1301
1302pub fn reduce_lazy_fn(args: &[Expr], ctx: &mut EvalCtx<'_>) -> Value {
1303    if let Some(e) = check_arity_len(args.len(), 3, 3) {
1304        return e;
1305    }
1306    let initial = evaluate_expr(&args[0], ctx);
1307    if matches!(initial, Value::Error(_)) {
1308        return initial;
1309    }
1310    let arr_val = evaluate_expr(&args[1], ctx);
1311    if matches!(arr_val, Value::Error(_)) {
1312        return arr_val;
1313    }
1314    let items = flatten_val(&arr_val);
1315    if items.is_empty() {
1316        return Value::Error(ErrorKind::Ref);
1317    }
1318    let lambda_expr = &args[2];
1319    let mut acc = initial;
1320    for item in &items {
1321        match apply_lambda(lambda_expr, &[acc.clone(), item.clone()], ctx) {
1322            Some(v) => acc = v,
1323            None => return Value::Error(ErrorKind::NA),
1324        }
1325    }
1326    acc
1327}
1328
1329// ── SCAN ──────────────────────────────────────────────────────────────────────
1330
1331pub fn scan_lazy_fn(args: &[Expr], ctx: &mut EvalCtx<'_>) -> Value {
1332    if let Some(e) = check_arity_len(args.len(), 3, 3) {
1333        return e;
1334    }
1335    let initial = evaluate_expr(&args[0], ctx);
1336    if matches!(initial, Value::Error(_)) {
1337        return initial;
1338    }
1339    let arr_val = evaluate_expr(&args[1], ctx);
1340    if matches!(arr_val, Value::Error(_)) {
1341        return arr_val;
1342    }
1343    let grid = to_2d(&arr_val);
1344    let items = flatten_val(&arr_val);
1345    let lambda_expr = &args[2];
1346    let mut acc = initial;
1347    let mut results: Vec<Value> = Vec::with_capacity(items.len());
1348    for item in &items {
1349        match apply_lambda(lambda_expr, &[acc.clone(), item.clone()], ctx) {
1350            Some(v) => {
1351                acc = v.clone();
1352                results.push(v);
1353            }
1354            None => return Value::Error(ErrorKind::NA),
1355        }
1356    }
1357    // Preserve shape of input array
1358    if grid.len() > 1 {
1359        let ncols = grid[0].len();
1360        let nrows = grid.len();
1361        let result_grid: Vec<Vec<Value>> = (0..nrows)
1362            .map(|r| (0..ncols).map(|c| results[r * ncols + c].clone()).collect())
1363            .collect();
1364        from_2d(result_grid)
1365    } else {
1366        Value::Array(results)
1367    }
1368}
1369
1370// ── MAKEARRAY ─────────────────────────────────────────────────────────────────
1371
1372pub fn makearray_lazy_fn(args: &[Expr], ctx: &mut EvalCtx<'_>) -> Value {
1373    if let Some(e) = check_arity_len(args.len(), 3, 3) {
1374        return e;
1375    }
1376    let rows_val = evaluate_expr(&args[0], ctx);
1377    let cols_val = evaluate_expr(&args[1], ctx);
1378    if matches!(rows_val, Value::Error(_)) {
1379        return rows_val;
1380    }
1381    if matches!(cols_val, Value::Error(_)) {
1382        return cols_val;
1383    }
1384    let nrows = match to_f64(&rows_val) {
1385        Some(n) if n >= 1.0 => n as usize,
1386        _ => return Value::Error(ErrorKind::Value),
1387    };
1388    let ncols = match to_f64(&cols_val) {
1389        Some(n) if n >= 1.0 => n as usize,
1390        _ => return Value::Error(ErrorKind::Value),
1391    };
1392    let lambda_expr = &args[2];
1393    let mut grid: Vec<Vec<Value>> = Vec::with_capacity(nrows);
1394    for r in 1..=nrows {
1395        let mut row = Vec::with_capacity(ncols);
1396        for c in 1..=ncols {
1397            let rv = Value::Number(r as f64);
1398            let cv = Value::Number(c as f64);
1399            match apply_lambda(lambda_expr, &[rv, cv], ctx) {
1400                Some(Value::Array(_)) => return Value::Error(ErrorKind::Value),
1401                Some(v) => row.push(v),
1402                None => return Value::Error(ErrorKind::NA),
1403            }
1404        }
1405        grid.push(row);
1406    }
1407    from_2d(grid)
1408}
1409
1410// ── Registration ─────────────────────────────────────────────────────────────
1411
1412
1413/// `ARRAYFORMULA(array_formula)` — evaluate an array formula.
1414/// In the engine this is a pass-through: the argument is already evaluated in
1415/// array context.  The function exists so formulas that wrap an expression in
1416/// ARRAYFORMULA parse and evaluate without an #NAME? error.
1417pub fn arrayformula_lazy_fn(args: &[Expr], ctx: &mut EvalCtx<'_>) -> Value {
1418    if args.len() != 1 {
1419        return Value::Error(ErrorKind::NA);
1420    }
1421    evaluate_expr(&args[0], ctx)
1422}
1423
1424pub fn register_array(registry: &mut Registry) {
1425    registry.register_eager("ROWS", rows_fn, FunctionMeta {
1426        category: "array",
1427        signature: "ROWS(array)",
1428        description: "Returns the number of rows in an array or range",
1429    });
1430    registry.register_eager("COLUMNS", columns_fn, FunctionMeta {
1431        category: "array",
1432        signature: "COLUMNS(array)",
1433        description: "Returns the number of columns in an array or range",
1434    });
1435    registry.register_eager("TRANSPOSE", transpose_fn, FunctionMeta {
1436        category: "array",
1437        signature: "TRANSPOSE(array)",
1438        description: "Transposes the rows and columns of an array",
1439    });
1440    registry.register_eager("ARRAY_CONSTRAIN", array_constrain_fn, FunctionMeta {
1441        category: "array",
1442        signature: "ARRAY_CONSTRAIN(input, num_rows, num_cols)",
1443        description: "Constrains an array to a given number of rows and columns",
1444    });
1445    registry.register_eager("CHOOSECOLS", choosecols_fn, FunctionMeta {
1446        category: "array",
1447        signature: "CHOOSECOLS(array, col_num1, ...)",
1448        description: "Returns selected columns from an array",
1449    });
1450    registry.register_eager("CHOOSEROWS", chooserows_fn, FunctionMeta {
1451        category: "array",
1452        signature: "CHOOSEROWS(array, row_num1, ...)",
1453        description: "Returns selected rows from an array",
1454    });
1455    registry.register_eager("FLATTEN", flatten_fn, FunctionMeta {
1456        category: "array",
1457        signature: "FLATTEN(array)",
1458        description: "Flattens an array into a single column",
1459    });
1460    registry.register_eager("HSTACK", hstack_fn, FunctionMeta {
1461        category: "array",
1462        signature: "HSTACK(array1, ...)",
1463        description: "Horizontally stacks arrays",
1464    });
1465    registry.register_eager("VSTACK", vstack_fn, FunctionMeta {
1466        category: "array",
1467        signature: "VSTACK(array1, ...)",
1468        description: "Vertically stacks arrays",
1469    });
1470    registry.register_eager("TOCOL", tocol_fn, FunctionMeta {
1471        category: "array",
1472        signature: "TOCOL(array, [ignore], [scan_by_col])",
1473        description: "Converts an array to a single column",
1474    });
1475    registry.register_eager("TOROW", torow_fn, FunctionMeta {
1476        category: "array",
1477        signature: "TOROW(array, [ignore], [scan_by_col])",
1478        description: "Converts an array to a single row",
1479    });
1480    registry.register_eager("WRAPCOLS", wrapcols_fn, FunctionMeta {
1481        category: "array",
1482        signature: "WRAPCOLS(vector, wrap_count, [pad_with])",
1483        description: "Wraps a vector into columns of the given length",
1484    });
1485    registry.register_eager("WRAPROWS", wraprows_fn, FunctionMeta {
1486        category: "array",
1487        signature: "WRAPROWS(vector, wrap_count, [pad_with])",
1488        description: "Wraps a vector into rows of the given length",
1489    });
1490    registry.register_eager("SORT", sort_fn, FunctionMeta {
1491        category: "array",
1492        signature: "SORT(array, [sort_index], [sort_order], [by_col])",
1493        description: "Sorts an array",
1494    });
1495    registry.register_eager("SORTBY", sortby_fn, FunctionMeta {
1496        category: "array",
1497        signature: "SORTBY(array, by_array1, [sort_order1], ...)",
1498        description: "Sorts an array based on the values in corresponding arrays",
1499    });
1500    registry.register_eager("UNIQUE", unique_fn, FunctionMeta {
1501        category: "array",
1502        signature: "UNIQUE(array, [by_col], [exactly_once])",
1503        description: "Returns unique rows or columns from an array",
1504    });
1505    registry.register_eager("SUMPRODUCT", sumproduct_fn, FunctionMeta {
1506        category: "array",
1507        signature: "SUMPRODUCT(array1, [array2], ...)",
1508        description: "Returns the sum of products of corresponding elements",
1509    });
1510    registry.register_eager("SUMXMY2", sumxmy2_fn, FunctionMeta {
1511        category: "array",
1512        signature: "SUMXMY2(array_x, array_y)",
1513        description: "Returns sum of squares of differences",
1514    });
1515    registry.register_eager("SUMX2MY2", sumx2my2_fn, FunctionMeta {
1516        category: "array",
1517        signature: "SUMX2MY2(array_x, array_y)",
1518        description: "Returns sum of (x^2 - y^2)",
1519    });
1520    registry.register_eager("SUMX2PY2", sumx2py2_fn, FunctionMeta {
1521        category: "array",
1522        signature: "SUMX2PY2(array_x, array_y)",
1523        description: "Returns sum of (x^2 + y^2)",
1524    });
1525    registry.register_eager("MMULT", mmult_fn, FunctionMeta {
1526        category: "array",
1527        signature: "MMULT(array1, array2)",
1528        description: "Returns the matrix product of two arrays",
1529    });
1530    registry.register_eager("MDETERM", mdeterm_fn, FunctionMeta {
1531        category: "array",
1532        signature: "MDETERM(array)",
1533        description: "Returns the matrix determinant",
1534    });
1535    registry.register_eager("MINVERSE", minverse_fn, FunctionMeta {
1536        category: "array",
1537        signature: "MINVERSE(array)",
1538        description: "Returns the matrix inverse",
1539    });
1540    registry.register_eager("FREQUENCY", frequency_fn, FunctionMeta {
1541        category: "array",
1542        signature: "FREQUENCY(data, bins)",
1543        description: "Calculates the frequency distribution of values",
1544    });
1545    registry.register_eager("LINEST", linest_fn, FunctionMeta {
1546        category: "array",
1547        signature: "LINEST(known_y, [known_x], [const], [stats])",
1548        description: "Returns linear regression statistics",
1549    });
1550    registry.register_eager("LOGEST", logest_fn, FunctionMeta {
1551        category: "array",
1552        signature: "LOGEST(known_y, [known_x], [const], [stats])",
1553        description: "Returns exponential regression statistics",
1554    });
1555    registry.register_eager("TREND", trend_fn, FunctionMeta {
1556        category: "array",
1557        signature: "TREND(known_y, [known_x], [new_x], [const])",
1558        description: "Returns values along a linear trend",
1559    });
1560    registry.register_eager("GROWTH", growth_fn, FunctionMeta {
1561        category: "array",
1562        signature: "GROWTH(known_y, [known_x], [new_x], [const])",
1563        description: "Returns values along an exponential trend",
1564    });
1565    registry.register_lazy("BYROW", byrow_lazy_fn, FunctionMeta {
1566        category: "array",
1567        signature: "BYROW(array, lambda)",
1568        description: "Applies a LAMBDA to each row of an array",
1569    });
1570    registry.register_lazy("BYCOL", bycol_lazy_fn, FunctionMeta {
1571        category: "array",
1572        signature: "BYCOL(array, lambda)",
1573        description: "Applies a LAMBDA to each column of an array",
1574    });
1575    registry.register_lazy("MAP", map_lazy_fn, FunctionMeta {
1576        category: "array",
1577        signature: "MAP(array1, [array2, ...], lambda)",
1578        description: "Maps a LAMBDA over one or more arrays",
1579    });
1580    registry.register_lazy("REDUCE", reduce_lazy_fn, FunctionMeta {
1581        category: "array",
1582        signature: "REDUCE(initial_value, array, lambda)",
1583        description: "Reduces an array to a single value using a LAMBDA",
1584    });
1585    registry.register_lazy("SCAN", scan_lazy_fn, FunctionMeta {
1586        category: "array",
1587        signature: "SCAN(initial_value, array, lambda)",
1588        description: "Returns running accumulation using a LAMBDA",
1589    });
1590    registry.register_lazy("MAKEARRAY", makearray_lazy_fn, FunctionMeta {
1591        category: "array",
1592        signature: "MAKEARRAY(rows, cols, lambda)",
1593        description: "Creates an array using a LAMBDA for each cell value",
1594    });
1595    registry.register_lazy("ARRAYFORMULA", arrayformula_lazy_fn, FunctionMeta {
1596        category: "array",
1597        signature: "ARRAYFORMULA(array_formula)",
1598        description: "Evaluates a formula as an array formula",
1599    });
1600}
1601
1602#[cfg(test)]
1603mod tests;