Skip to main content

pdforg_sheets/
functions.rs

1//! Built-in spreadsheet functions (300+ planned, 80+ implemented here).
2//!
3//! All functions take `&[CellValue]` and return `Result<CellValue, String>`.
4
5use pdf_core::{CellValue, CellError};
6use std::collections::HashMap;
7
8pub type FnImpl = fn(&[CellValue]) -> Result<CellValue, String>;
9
10/// Global function registry
11pub static FUNCTIONS: std::sync::LazyLock<HashMap<&'static str, FnImpl>> =
12    std::sync::LazyLock::new(|| {
13        let mut m: HashMap<&'static str, FnImpl> = HashMap::new();
14
15        // ── Math & Trig ──────────────────────────────────────────────────────
16        m.insert("SUM",      fn_sum);
17        m.insert("SUMIF",    fn_sumif);
18        m.insert("SUMIFS",   fn_sumifs);
19        m.insert("PRODUCT",  fn_product);
20        m.insert("ABS",      fn_abs);
21        m.insert("ROUND",    fn_round);
22        m.insert("ROUNDUP",  fn_roundup);
23        m.insert("ROUNDDOWN",fn_rounddown);
24        m.insert("CEILING",  fn_ceiling);
25        m.insert("FLOOR",    fn_floor);
26        m.insert("TRUNC",    fn_trunc);
27        m.insert("INT",      fn_int);
28        m.insert("MOD",      fn_mod);
29        m.insert("POWER",    fn_power);
30        m.insert("SQRT",     fn_sqrt);
31        m.insert("SQRTPI",   fn_sqrtpi);
32        m.insert("EXP",      fn_exp);
33        m.insert("LN",       fn_ln);
34        m.insert("LOG",      fn_log);
35        m.insert("LOG10",    fn_log10);
36        m.insert("PI",       fn_pi);
37        m.insert("SIN",      fn_sin);
38        m.insert("COS",      fn_cos);
39        m.insert("TAN",      fn_tan);
40        m.insert("ASIN",     fn_asin);
41        m.insert("ACOS",     fn_acos);
42        m.insert("ATAN",     fn_atan);
43        m.insert("ATAN2",    fn_atan2);
44        m.insert("DEGREES",  fn_degrees);
45        m.insert("RADIANS",  fn_radians);
46        m.insert("SIGN",     fn_sign);
47        m.insert("RAND",     fn_rand);
48        m.insert("RANDBETWEEN", fn_randbetween);
49        m.insert("FACT",     fn_fact);
50        m.insert("COMBIN",   fn_combin);
51        m.insert("PERMUT",   fn_permut);
52        m.insert("GCD",      fn_gcd);
53        m.insert("LCM",      fn_lcm);
54        m.insert("EVEN",     fn_even);
55        m.insert("ODD",      fn_odd);
56        m.insert("MROUND",   fn_mround);
57
58        // ── Statistical ──────────────────────────────────────────────────────
59        m.insert("AVERAGE",  fn_average);
60        m.insert("AVERAGEIF",fn_averageif);
61        m.insert("MIN",      fn_min);
62        m.insert("MAX",      fn_max);
63        m.insert("MEDIAN",   fn_median);
64        m.insert("MODE",     fn_mode);
65        m.insert("COUNT",    fn_count);
66        m.insert("COUNTA",   fn_counta);
67        m.insert("COUNTBLANK", fn_countblank);
68        m.insert("COUNTIF",  fn_countif);
69        m.insert("COUNTIFS", fn_countifs);
70        m.insert("STDEV",    fn_stdev);
71        m.insert("STDEVP",   fn_stdevp);
72        m.insert("VAR",      fn_var);
73        m.insert("VARP",     fn_varp);
74        m.insert("LARGE",    fn_large);
75        m.insert("SMALL",    fn_small);
76        m.insert("RANK",     fn_rank);
77        m.insert("PERCENTILE", fn_percentile);
78        m.insert("QUARTILE", fn_quartile);
79        m.insert("CORREL",   fn_correl);
80        m.insert("COVAR",    fn_covar);
81        m.insert("SLOPE",    fn_slope);
82        m.insert("INTERCEPT",fn_intercept);
83
84        // ── Text ─────────────────────────────────────────────────────────────
85        m.insert("LEN",      fn_len);
86        m.insert("LEFT",     fn_left);
87        m.insert("RIGHT",    fn_right);
88        m.insert("MID",      fn_mid);
89        m.insert("UPPER",    fn_upper);
90        m.insert("LOWER",    fn_lower);
91        m.insert("PROPER",   fn_proper);
92        m.insert("TRIM",     fn_trim);
93        m.insert("LTRIM",    fn_ltrim);
94        m.insert("RTRIM",    fn_rtrim);
95        m.insert("CONCATENATE", fn_concatenate);
96        m.insert("CONCAT",   fn_concat);
97        m.insert("TEXTJOIN", fn_textjoin);
98        m.insert("REPLACE",  fn_replace);
99        m.insert("SUBSTITUTE",fn_substitute);
100        m.insert("FIND",     fn_find);
101        m.insert("SEARCH",   fn_search);
102        m.insert("TEXT",     fn_text);
103        m.insert("VALUE",    fn_value);
104        m.insert("FIXED",    fn_fixed);
105        m.insert("REPT",     fn_rept);
106        m.insert("CODE",     fn_code);
107        m.insert("CHAR",     fn_char);
108        m.insert("EXACT",    fn_exact);
109        m.insert("T",        fn_t);
110
111        // ── Logical ──────────────────────────────────────────────────────────
112        m.insert("IF",       fn_if);
113        m.insert("IFS",      fn_ifs);
114        m.insert("AND",      fn_and);
115        m.insert("OR",       fn_or);
116        m.insert("NOT",      fn_not);
117        m.insert("XOR",      fn_xor);
118        m.insert("IFERROR",  fn_iferror);
119        m.insert("IFNA",     fn_ifna);
120        m.insert("SWITCH",   fn_switch);
121        m.insert("TRUE",     fn_true);
122        m.insert("FALSE",    fn_false);
123
124        // ── Lookup ───────────────────────────────────────────────────────────
125        m.insert("VLOOKUP",  fn_vlookup);
126        m.insert("HLOOKUP",  fn_hlookup);
127        m.insert("INDEX",    fn_index);
128        m.insert("MATCH",    fn_match);
129        m.insert("CHOOSE",   fn_choose);
130        m.insert("OFFSET",   fn_offset);
131        m.insert("ROW",      fn_row);
132        m.insert("COLUMN",   fn_column);
133        m.insert("ROWS",     fn_rows);
134        m.insert("COLUMNS",  fn_columns);
135        m.insert("TRANSPOSE",fn_transpose);
136
137        // ── Date & Time ──────────────────────────────────────────────────────
138        m.insert("NOW",      fn_now);
139        m.insert("TODAY",    fn_today);
140        m.insert("DATE",     fn_date);
141        m.insert("TIME",     fn_time);
142        m.insert("YEAR",     fn_year);
143        m.insert("MONTH",    fn_month);
144        m.insert("DAY",      fn_day);
145        m.insert("HOUR",     fn_hour);
146        m.insert("MINUTE",   fn_minute);
147        m.insert("SECOND",   fn_second);
148        m.insert("WEEKDAY",  fn_weekday);
149        m.insert("WEEKNUM",  fn_weeknum);
150        m.insert("EDATE",    fn_edate);
151        m.insert("EOMONTH",  fn_eomonth);
152        m.insert("NETWORKDAYS", fn_networkdays);
153        m.insert("WORKDAY",  fn_workday);
154        m.insert("DATEDIF",  fn_datedif);
155        m.insert("DATEVALUE",fn_datevalue);
156        m.insert("TIMEVALUE",fn_timevalue);
157
158        // ── Information ──────────────────────────────────────────────────────
159        m.insert("ISNUMBER", fn_isnumber);
160        m.insert("ISTEXT",   fn_istext);
161        m.insert("ISBLANK",  fn_isblank);
162        m.insert("ISERROR",  fn_iserror);
163        m.insert("ISERR",    fn_iserr);
164        m.insert("ISNA",     fn_isna);
165        m.insert("ISLOGICAL",fn_islogical);
166        m.insert("ISODD",    fn_isodd);
167        m.insert("ISEVEN",   fn_iseven);
168        m.insert("NA",       fn_na);
169        m.insert("ERROR.TYPE", fn_error_type);
170        m.insert("CELL",     fn_cell);
171        m.insert("TYPE",     fn_type);
172        m.insert("N",        fn_n);
173
174        // ── Financial ────────────────────────────────────────────────────────
175        m.insert("PMT",      fn_pmt);
176        m.insert("PV",       fn_pv);
177        m.insert("FV",       fn_fv);
178        m.insert("RATE",     fn_rate);
179        m.insert("NPER",     fn_nper);
180        m.insert("NPV",      fn_npv);
181        m.insert("IRR",      fn_irr);
182        m.insert("IPMT",     fn_ipmt);
183        m.insert("PPMT",     fn_ppmt);
184
185        m
186    });
187
188// ─── Helper macros ───────────────────────────────────────────────────────────
189
190macro_rules! require_num {
191    ($v:expr) => {
192        $v.as_number().ok_or_else(|| format!("Expected number, got {:?}", $v))?
193    };
194}
195
196macro_rules! require_text {
197    ($v:expr) => {
198        match $v {
199            CellValue::Text(s) => s.clone(),
200            other => other.as_text(),
201        }
202    };
203}
204
205fn nums_from_args(args: &[CellValue]) -> Vec<f64> {
206    args.iter().filter_map(|v| v.as_number()).collect()
207}
208
209fn to_bool(v: &CellValue) -> bool {
210    match v {
211        CellValue::Bool(b) => *b,
212        CellValue::Number(n) => *n != 0.0,
213        CellValue::Text(s) => !s.is_empty(),
214        CellValue::Empty => false,
215        _ => false,
216    }
217}
218
219// ─── Math & Trig ─────────────────────────────────────────────────────────────
220
221fn fn_sum(args: &[CellValue]) -> Result<CellValue, String> {
222    Ok(CellValue::Number(nums_from_args(args).iter().sum()))
223}
224
225fn fn_sumif(args: &[CellValue]) -> Result<CellValue, String> {
226    if args.len() < 2 { return Err("SUMIF requires at least 2 arguments".into()); }
227    // Simplified: sum if value matches criteria
228    let criteria = &args[1];
229    let sum: f64 = args[0..1].iter()
230        .zip(args.get(2..).unwrap_or(&args[0..1]))
231        .filter_map(|(check, sum_v)| {
232            if check == criteria { sum_v.as_number() } else { None }
233        })
234        .sum();
235    Ok(CellValue::Number(sum))
236}
237
238fn fn_sumifs(args: &[CellValue]) -> Result<CellValue, String> {
239    // Simplified stub
240    fn_sum(args)
241}
242
243fn fn_product(args: &[CellValue]) -> Result<CellValue, String> {
244    Ok(CellValue::Number(nums_from_args(args).iter().product()))
245}
246
247fn fn_abs(args: &[CellValue]) -> Result<CellValue, String> {
248    let n = require_num!(&args[0]);
249    Ok(CellValue::Number(n.abs()))
250}
251
252fn fn_round(args: &[CellValue]) -> Result<CellValue, String> {
253    let n = require_num!(&args[0]);
254    let places = if args.len() > 1 { require_num!(&args[1]) as i32 } else { 0 };
255    let factor = 10f64.powi(places);
256    Ok(CellValue::Number((n * factor).round() / factor))
257}
258
259fn fn_roundup(args: &[CellValue]) -> Result<CellValue, String> {
260    let n = require_num!(&args[0]);
261    let places = if args.len() > 1 { require_num!(&args[1]) as i32 } else { 0 };
262    let factor = 10f64.powi(places);
263    Ok(CellValue::Number((n * factor).ceil() / factor))
264}
265
266fn fn_rounddown(args: &[CellValue]) -> Result<CellValue, String> {
267    let n = require_num!(&args[0]);
268    let places = if args.len() > 1 { require_num!(&args[1]) as i32 } else { 0 };
269    let factor = 10f64.powi(places);
270    Ok(CellValue::Number((n * factor).floor() / factor))
271}
272
273fn fn_ceiling(args: &[CellValue]) -> Result<CellValue, String> {
274    let n = require_num!(&args[0]);
275    let sig = if args.len() > 1 { require_num!(&args[1]) } else { 1.0 };
276    if sig == 0.0 { return Ok(CellValue::Number(0.0)); }
277    Ok(CellValue::Number((n / sig).ceil() * sig))
278}
279
280fn fn_floor(args: &[CellValue]) -> Result<CellValue, String> {
281    let n = require_num!(&args[0]);
282    let sig = if args.len() > 1 { require_num!(&args[1]) } else { 1.0 };
283    if sig == 0.0 { return Ok(CellValue::Number(0.0)); }
284    Ok(CellValue::Number((n / sig).floor() * sig))
285}
286
287fn fn_trunc(args: &[CellValue]) -> Result<CellValue, String> {
288    let n = require_num!(&args[0]);
289    Ok(CellValue::Number(n.trunc()))
290}
291
292fn fn_int(args: &[CellValue]) -> Result<CellValue, String> {
293    let n = require_num!(&args[0]);
294    Ok(CellValue::Number(n.floor()))
295}
296
297fn fn_mod(args: &[CellValue]) -> Result<CellValue, String> {
298    let n = require_num!(&args[0]);
299    let d = require_num!(&args[1]);
300    if d == 0.0 { return Ok(CellValue::Error(CellError::Div0)); }
301    Ok(CellValue::Number(n - d * (n / d).floor()))
302}
303
304fn fn_power(args: &[CellValue]) -> Result<CellValue, String> {
305    let base = require_num!(&args[0]);
306    let exp = require_num!(&args[1]);
307    Ok(CellValue::Number(base.powf(exp)))
308}
309
310fn fn_sqrt(args: &[CellValue]) -> Result<CellValue, String> {
311    let n = require_num!(&args[0]);
312    if n < 0.0 { return Ok(CellValue::Error(CellError::Num)); }
313    Ok(CellValue::Number(n.sqrt()))
314}
315
316fn fn_sqrtpi(args: &[CellValue]) -> Result<CellValue, String> {
317    let n = require_num!(&args[0]);
318    Ok(CellValue::Number((n * std::f64::consts::PI).sqrt()))
319}
320
321fn fn_exp(args: &[CellValue]) -> Result<CellValue, String> {
322    Ok(CellValue::Number(require_num!(&args[0]).exp()))
323}
324
325fn fn_ln(args: &[CellValue]) -> Result<CellValue, String> {
326    let n = require_num!(&args[0]);
327    if n <= 0.0 { return Ok(CellValue::Error(CellError::Num)); }
328    Ok(CellValue::Number(n.ln()))
329}
330
331fn fn_log(args: &[CellValue]) -> Result<CellValue, String> {
332    let n = require_num!(&args[0]);
333    let base = if args.len() > 1 { require_num!(&args[1]) } else { 10.0 };
334    if n <= 0.0 || base <= 0.0 || base == 1.0 { return Ok(CellValue::Error(CellError::Num)); }
335    Ok(CellValue::Number(n.log(base)))
336}
337
338fn fn_log10(args: &[CellValue]) -> Result<CellValue, String> {
339    let n = require_num!(&args[0]);
340    if n <= 0.0 { return Ok(CellValue::Error(CellError::Num)); }
341    Ok(CellValue::Number(n.log10()))
342}
343
344fn fn_pi(_: &[CellValue]) -> Result<CellValue, String> {
345    Ok(CellValue::Number(std::f64::consts::PI))
346}
347
348fn fn_sin(args: &[CellValue]) -> Result<CellValue, String> {
349    Ok(CellValue::Number(require_num!(&args[0]).sin()))
350}
351fn fn_cos(args: &[CellValue]) -> Result<CellValue, String> {
352    Ok(CellValue::Number(require_num!(&args[0]).cos()))
353}
354fn fn_tan(args: &[CellValue]) -> Result<CellValue, String> {
355    Ok(CellValue::Number(require_num!(&args[0]).tan()))
356}
357fn fn_asin(args: &[CellValue]) -> Result<CellValue, String> {
358    Ok(CellValue::Number(require_num!(&args[0]).asin()))
359}
360fn fn_acos(args: &[CellValue]) -> Result<CellValue, String> {
361    Ok(CellValue::Number(require_num!(&args[0]).acos()))
362}
363fn fn_atan(args: &[CellValue]) -> Result<CellValue, String> {
364    Ok(CellValue::Number(require_num!(&args[0]).atan()))
365}
366fn fn_atan2(args: &[CellValue]) -> Result<CellValue, String> {
367    Ok(CellValue::Number(require_num!(&args[0]).atan2(require_num!(&args[1]))))
368}
369fn fn_degrees(args: &[CellValue]) -> Result<CellValue, String> {
370    Ok(CellValue::Number(require_num!(&args[0]).to_degrees()))
371}
372fn fn_radians(args: &[CellValue]) -> Result<CellValue, String> {
373    Ok(CellValue::Number(require_num!(&args[0]).to_radians()))
374}
375fn fn_sign(args: &[CellValue]) -> Result<CellValue, String> {
376    let n = require_num!(&args[0]);
377    Ok(CellValue::Number(if n > 0.0 { 1.0 } else if n < 0.0 { -1.0 } else { 0.0 }))
378}
379fn fn_rand(_: &[CellValue]) -> Result<CellValue, String> {
380    // Without a proper RNG dependency, use a hash-based pseudo-random
381    use std::time::{SystemTime, UNIX_EPOCH};
382    let ns = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().subsec_nanos();
383    Ok(CellValue::Number((ns as f64) / (u32::MAX as f64)))
384}
385fn fn_randbetween(args: &[CellValue]) -> Result<CellValue, String> {
386    let lo = require_num!(&args[0]);
387    let hi = require_num!(&args[1]);
388    let r = match fn_rand(&[])? { CellValue::Number(n) => n, _ => 0.5 };
389    Ok(CellValue::Number((lo + r * (hi - lo)).floor()))
390}
391fn fn_fact(args: &[CellValue]) -> Result<CellValue, String> {
392    let n = require_num!(&args[0]) as u64;
393    Ok(CellValue::Number((1..=n).product::<u64>() as f64))
394}
395fn fn_combin(args: &[CellValue]) -> Result<CellValue, String> {
396    let n = require_num!(&args[0]) as u64;
397    let k = require_num!(&args[1]) as u64;
398    if k > n { return Ok(CellValue::Number(0.0)); }
399    let num: u64 = ((n-k+1)..=n).product();
400    let den: u64 = (1..=k).product();
401    Ok(CellValue::Number((num / den) as f64))
402}
403fn fn_permut(args: &[CellValue]) -> Result<CellValue, String> {
404    let n = require_num!(&args[0]) as u64;
405    let k = require_num!(&args[1]) as u64;
406    Ok(CellValue::Number(((n-k+1)..=n).product::<u64>() as f64))
407}
408fn fn_gcd(args: &[CellValue]) -> Result<CellValue, String> {
409    fn gcd(a: u64, b: u64) -> u64 { if b == 0 { a } else { gcd(b, a % b) } }
410    let ns: Vec<u64> = args.iter().filter_map(|v| v.as_number().map(|n| n as u64)).collect();
411    Ok(CellValue::Number(ns.iter().copied().reduce(gcd).unwrap_or(0) as f64))
412}
413fn fn_lcm(args: &[CellValue]) -> Result<CellValue, String> {
414    fn gcd(a: u64, b: u64) -> u64 { if b == 0 { a } else { gcd(b, a % b) } }
415    fn lcm(a: u64, b: u64) -> u64 { a / gcd(a, b) * b }
416    let ns: Vec<u64> = args.iter().filter_map(|v| v.as_number().map(|n| n as u64)).collect();
417    Ok(CellValue::Number(ns.iter().copied().reduce(lcm).unwrap_or(0) as f64))
418}
419fn fn_even(args: &[CellValue]) -> Result<CellValue, String> {
420    let n = require_num!(&args[0]);
421    let r = if n >= 0.0 { (n / 2.0).ceil() * 2.0 } else { (n / 2.0).floor() * 2.0 };
422    Ok(CellValue::Number(r))
423}
424fn fn_odd(args: &[CellValue]) -> Result<CellValue, String> {
425    let n = require_num!(&args[0]);
426    let r = if n >= 0.0 { (n / 2.0).ceil() * 2.0 + 1.0 } else { (n / 2.0).floor() * 2.0 - 1.0 };
427    Ok(CellValue::Number(r))
428}
429fn fn_mround(args: &[CellValue]) -> Result<CellValue, String> {
430    let n = require_num!(&args[0]);
431    let m = require_num!(&args[1]);
432    if m == 0.0 { return Ok(CellValue::Number(0.0)); }
433    Ok(CellValue::Number((n / m).round() * m))
434}
435
436// ─── Statistical ─────────────────────────────────────────────────────────────
437
438fn fn_average(args: &[CellValue]) -> Result<CellValue, String> {
439    let ns = nums_from_args(args);
440    if ns.is_empty() { return Ok(CellValue::Error(CellError::Div0)); }
441    Ok(CellValue::Number(ns.iter().sum::<f64>() / ns.len() as f64))
442}
443
444fn fn_averageif(args: &[CellValue]) -> Result<CellValue, String> {
445    fn_average(args) // simplified
446}
447
448fn fn_min(args: &[CellValue]) -> Result<CellValue, String> {
449    let ns = nums_from_args(args);
450    Ok(CellValue::Number(ns.iter().copied().fold(f64::INFINITY, f64::min)))
451}
452
453fn fn_max(args: &[CellValue]) -> Result<CellValue, String> {
454    let ns = nums_from_args(args);
455    Ok(CellValue::Number(ns.iter().copied().fold(f64::NEG_INFINITY, f64::max)))
456}
457
458fn fn_median(args: &[CellValue]) -> Result<CellValue, String> {
459    let mut ns = nums_from_args(args);
460    if ns.is_empty() { return Ok(CellValue::Error(CellError::NA)); }
461    ns.sort_by(f64::total_cmp);
462    let mid = ns.len() / 2;
463    let m = if ns.len() % 2 == 0 { (ns[mid - 1] + ns[mid]) / 2.0 } else { ns[mid] };
464    Ok(CellValue::Number(m))
465}
466
467fn fn_mode(args: &[CellValue]) -> Result<CellValue, String> {
468    let ns = nums_from_args(args);
469    let mut freq: HashMap<u64, (f64, usize)> = HashMap::new();
470    for n in &ns {
471        let key = n.to_bits();
472        let entry = freq.entry(key).or_insert((*n, 0));
473        entry.1 += 1;
474    }
475    freq.into_values().max_by_key(|(_, c)| *c).map(|(v, _)| CellValue::Number(v))
476        .ok_or_else(|| "No data".into())
477}
478
479fn fn_count(args: &[CellValue]) -> Result<CellValue, String> {
480    Ok(CellValue::Number(args.iter().filter(|v| v.is_number()).count() as f64))
481}
482
483fn fn_counta(args: &[CellValue]) -> Result<CellValue, String> {
484    Ok(CellValue::Number(args.iter().filter(|v| !v.is_empty()).count() as f64))
485}
486
487fn fn_countblank(args: &[CellValue]) -> Result<CellValue, String> {
488    Ok(CellValue::Number(args.iter().filter(|v| v.is_empty()).count() as f64))
489}
490
491fn fn_countif(args: &[CellValue]) -> Result<CellValue, String> {
492    if args.len() < 2 { return Err("COUNTIF requires 2 args".into()); }
493    let criteria = &args[1];
494    let count = args[..args.len()-1].iter().filter(|v| *v == criteria).count();
495    Ok(CellValue::Number(count as f64))
496}
497
498fn fn_countifs(args: &[CellValue]) -> Result<CellValue, String> {
499    fn_countif(args)
500}
501
502fn fn_stdev(args: &[CellValue]) -> Result<CellValue, String> {
503    let ns = nums_from_args(args);
504    if ns.len() < 2 { return Ok(CellValue::Error(CellError::Div0)); }
505    let mean = ns.iter().sum::<f64>() / ns.len() as f64;
506    let var = ns.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / (ns.len() - 1) as f64;
507    Ok(CellValue::Number(var.sqrt()))
508}
509
510fn fn_stdevp(args: &[CellValue]) -> Result<CellValue, String> {
511    let ns = nums_from_args(args);
512    if ns.is_empty() { return Ok(CellValue::Error(CellError::Div0)); }
513    let mean = ns.iter().sum::<f64>() / ns.len() as f64;
514    let var = ns.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / ns.len() as f64;
515    Ok(CellValue::Number(var.sqrt()))
516}
517
518fn fn_var(args: &[CellValue]) -> Result<CellValue, String> {
519    let ns = nums_from_args(args);
520    if ns.len() < 2 { return Ok(CellValue::Error(CellError::Div0)); }
521    let mean = ns.iter().sum::<f64>() / ns.len() as f64;
522    Ok(CellValue::Number(ns.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / (ns.len() - 1) as f64))
523}
524
525fn fn_varp(args: &[CellValue]) -> Result<CellValue, String> {
526    let ns = nums_from_args(args);
527    if ns.is_empty() { return Ok(CellValue::Error(CellError::Div0)); }
528    let mean = ns.iter().sum::<f64>() / ns.len() as f64;
529    Ok(CellValue::Number(ns.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / ns.len() as f64))
530}
531
532fn fn_large(args: &[CellValue]) -> Result<CellValue, String> {
533    let mut ns = nums_from_args(&args[..args.len()-1]);
534    let k = require_num!(args.last().unwrap()) as usize;
535    ns.sort_by(|a, b| b.total_cmp(a));
536    ns.get(k - 1).map(|n| CellValue::Number(*n)).ok_or_else(|| "Index out of range".into())
537}
538
539fn fn_small(args: &[CellValue]) -> Result<CellValue, String> {
540    let mut ns = nums_from_args(&args[..args.len()-1]);
541    let k = require_num!(args.last().unwrap()) as usize;
542    ns.sort_by(f64::total_cmp);
543    ns.get(k - 1).map(|n| CellValue::Number(*n)).ok_or_else(|| "Index out of range".into())
544}
545
546fn fn_rank(args: &[CellValue]) -> Result<CellValue, String> {
547    let n = require_num!(&args[0]);
548    let ns = nums_from_args(&args[1..]);
549    let rank = ns.iter().filter(|&&x| x > n).count() + 1;
550    Ok(CellValue::Number(rank as f64))
551}
552
553fn fn_percentile(args: &[CellValue]) -> Result<CellValue, String> {
554    let mut ns = nums_from_args(&args[..args.len()-1]);
555    let k = require_num!(args.last().unwrap());
556    ns.sort_by(f64::total_cmp);
557    let idx = k * (ns.len() - 1) as f64;
558    let lo = idx.floor() as usize;
559    let hi = idx.ceil() as usize;
560    let frac = idx - lo as f64;
561    let val = ns[lo] + frac * (ns.get(hi).copied().unwrap_or(ns[lo]) - ns[lo]);
562    Ok(CellValue::Number(val))
563}
564
565fn fn_quartile(args: &[CellValue]) -> Result<CellValue, String> {
566    let q = require_num!(args.last().unwrap());
567    fn_percentile(&[args[..args.len()-1].to_vec(), vec![CellValue::Number(q / 4.0)]].concat())
568}
569
570fn fn_correl(args: &[CellValue]) -> Result<CellValue, String> {
571    let n = args.len() / 2;
572    let xs = nums_from_args(&args[..n]);
573    let ys = nums_from_args(&args[n..]);
574    let mx = xs.iter().sum::<f64>() / n as f64;
575    let my = ys.iter().sum::<f64>() / n as f64;
576    let cov: f64 = xs.iter().zip(ys.iter()).map(|(x, y)| (x - mx) * (y - my)).sum();
577    let sx: f64 = xs.iter().map(|x| (x - mx).powi(2)).sum::<f64>().sqrt();
578    let sy: f64 = ys.iter().map(|y| (y - my).powi(2)).sum::<f64>().sqrt();
579    Ok(CellValue::Number(cov / (sx * sy)))
580}
581
582fn fn_covar(args: &[CellValue]) -> Result<CellValue, String> {
583    let n = args.len() / 2;
584    let xs = nums_from_args(&args[..n]);
585    let ys = nums_from_args(&args[n..]);
586    let mx = xs.iter().sum::<f64>() / n as f64;
587    let my = ys.iter().sum::<f64>() / n as f64;
588    let cov: f64 = xs.iter().zip(ys.iter()).map(|(x, y)| (x - mx) * (y - my)).sum::<f64>() / n as f64;
589    Ok(CellValue::Number(cov))
590}
591
592fn fn_slope(args: &[CellValue]) -> Result<CellValue, String> {
593    let n = args.len() / 2;
594    let ys = nums_from_args(&args[..n]);
595    let xs = nums_from_args(&args[n..]);
596    let mx = xs.iter().sum::<f64>() / n as f64;
597    let my = ys.iter().sum::<f64>() / n as f64;
598    let num: f64 = xs.iter().zip(ys.iter()).map(|(x, y)| (x - mx) * (y - my)).sum();
599    let den: f64 = xs.iter().map(|x| (x - mx).powi(2)).sum();
600    Ok(CellValue::Number(num / den))
601}
602
603fn fn_intercept(args: &[CellValue]) -> Result<CellValue, String> {
604    let n = args.len() / 2;
605    let ys = nums_from_args(&args[..n]);
606    let xs = nums_from_args(&args[n..]);
607    let mx = xs.iter().sum::<f64>() / n as f64;
608    let my = ys.iter().sum::<f64>() / n as f64;
609    let slope = match fn_slope(args)? { CellValue::Number(n) => n, _ => return Ok(CellValue::Error(CellError::Value)) };
610    Ok(CellValue::Number(my - slope * mx))
611}
612
613// ─── Text ─────────────────────────────────────────────────────────────────────
614
615fn fn_len(args: &[CellValue]) -> Result<CellValue, String> {
616    Ok(CellValue::Number(require_text!(&args[0]).chars().count() as f64))
617}
618
619fn fn_left(args: &[CellValue]) -> Result<CellValue, String> {
620    let s = require_text!(&args[0]);
621    let n = if args.len() > 1 { require_num!(&args[1]) as usize } else { 1 };
622    Ok(CellValue::Text(s.chars().take(n).collect()))
623}
624
625fn fn_right(args: &[CellValue]) -> Result<CellValue, String> {
626    let s = require_text!(&args[0]);
627    let n = if args.len() > 1 { require_num!(&args[1]) as usize } else { 1 };
628    let chars: Vec<char> = s.chars().collect();
629    let start = chars.len().saturating_sub(n);
630    Ok(CellValue::Text(chars[start..].iter().collect()))
631}
632
633fn fn_mid(args: &[CellValue]) -> Result<CellValue, String> {
634    let s = require_text!(&args[0]);
635    let start = (require_num!(&args[1]) as usize).saturating_sub(1);
636    let len = require_num!(&args[2]) as usize;
637    Ok(CellValue::Text(s.chars().skip(start).take(len).collect()))
638}
639
640fn fn_upper(args: &[CellValue]) -> Result<CellValue, String> {
641    Ok(CellValue::Text(require_text!(&args[0]).to_uppercase()))
642}
643
644fn fn_lower(args: &[CellValue]) -> Result<CellValue, String> {
645    Ok(CellValue::Text(require_text!(&args[0]).to_lowercase()))
646}
647
648fn fn_proper(args: &[CellValue]) -> Result<CellValue, String> {
649    let s = require_text!(&args[0]);
650    let result = s.chars().enumerate().map(|(i, c)| {
651        if i == 0 || !s.chars().nth(i-1).map(|p| p.is_alphanumeric()).unwrap_or(false) {
652            c.to_uppercase().next().unwrap_or(c)
653        } else {
654            c.to_lowercase().next().unwrap_or(c)
655        }
656    }).collect();
657    Ok(CellValue::Text(result))
658}
659
660fn fn_trim(args: &[CellValue]) -> Result<CellValue, String> {
661    let s = require_text!(&args[0]);
662    // Trim leading/trailing whitespace and collapse internal spaces
663    let trimmed = s.split_whitespace().collect::<Vec<&str>>().join(" ");
664    Ok(CellValue::Text(trimmed))
665}
666
667fn fn_ltrim(args: &[CellValue]) -> Result<CellValue, String> {
668    Ok(CellValue::Text(require_text!(&args[0]).trim_start().to_string()))
669}
670
671fn fn_rtrim(args: &[CellValue]) -> Result<CellValue, String> {
672    Ok(CellValue::Text(require_text!(&args[0]).trim_end().to_string()))
673}
674
675fn fn_concatenate(args: &[CellValue]) -> Result<CellValue, String> {
676    Ok(CellValue::Text(args.iter().map(|v| v.as_text()).collect()))
677}
678
679fn fn_concat(args: &[CellValue]) -> Result<CellValue, String> {
680    fn_concatenate(args)
681}
682
683fn fn_textjoin(args: &[CellValue]) -> Result<CellValue, String> {
684    if args.len() < 3 { return Err("TEXTJOIN needs 3+ args".into()); }
685    let delim = require_text!(&args[0]);
686    let ignore_empty = to_bool(&args[1]);
687    let parts: Vec<String> = args[2..].iter()
688        .map(|v| v.as_text())
689        .filter(|s| !ignore_empty || !s.is_empty())
690        .collect();
691    Ok(CellValue::Text(parts.join(&delim)))
692}
693
694fn fn_replace(args: &[CellValue]) -> Result<CellValue, String> {
695    let s = require_text!(&args[0]);
696    let start = (require_num!(&args[1]) as usize).saturating_sub(1);
697    let num_chars = require_num!(&args[2]) as usize;
698    let new_text = require_text!(&args[3]);
699    let chars: Vec<char> = s.chars().collect();
700    let result: String = chars[..start.min(chars.len())].iter()
701        .chain(new_text.chars().collect::<Vec<_>>().iter())
702        .chain(chars[(start + num_chars).min(chars.len())..].iter())
703        .collect();
704    Ok(CellValue::Text(result))
705}
706
707fn fn_substitute(args: &[CellValue]) -> Result<CellValue, String> {
708    let text = require_text!(&args[0]);
709    let old = require_text!(&args[1]);
710    let new = require_text!(&args[2]);
711    let instance = if args.len() > 3 { Some(require_num!(&args[3]) as usize) } else { None };
712    match instance {
713        None => Ok(CellValue::Text(text.replace(old.as_str(), new.as_str()))),
714        Some(n) => {
715            let mut count = 0;
716            let result = text.split(old.as_str()).enumerate().map(|(i, part)| {
717                if i == 0 { part.to_string() }
718                else { count += 1; if count == n { format!("{}{}", new, part) } else { format!("{}{}", old, part) } }
719            }).collect();
720            Ok(CellValue::Text(result))
721        }
722    }
723}
724
725fn fn_find(args: &[CellValue]) -> Result<CellValue, String> {
726    let find = require_text!(&args[0]);
727    let within = require_text!(&args[1]);
728    let start = if args.len() > 2 { (require_num!(&args[2]) as usize).saturating_sub(1) } else { 0 };
729    within[start..].find(find.as_str())
730        .map(|pos| CellValue::Number((start + pos + 1) as f64))
731        .ok_or_else(|| "Not found".into())
732}
733
734fn fn_search(args: &[CellValue]) -> Result<CellValue, String> {
735    let find = require_text!(&args[0]).to_lowercase();
736    let within = require_text!(&args[1]).to_lowercase();
737    let start = if args.len() > 2 { (require_num!(&args[2]) as usize).saturating_sub(1) } else { 0 };
738    within[start..].find(find.as_str())
739        .map(|pos| CellValue::Number((start + pos + 1) as f64))
740        .ok_or_else(|| "Not found".into())
741}
742
743fn fn_text(args: &[CellValue]) -> Result<CellValue, String> {
744    let n = require_num!(&args[0]);
745    let fmt = require_text!(&args[1]);
746    // Basic number formatting
747    let formatted = if fmt.contains('%') {
748        format!("{:.0}%", n * 100.0)
749    } else if fmt.contains('.') {
750        let places = fmt.chars().rev().take_while(|c| *c == '0').count();
751        format!("{:.prec$}", n, prec = places)
752    } else {
753        format!("{:.0}", n)
754    };
755    Ok(CellValue::Text(formatted))
756}
757
758fn fn_value(args: &[CellValue]) -> Result<CellValue, String> {
759    let s = require_text!(&args[0]);
760    s.trim().replace(',', "").parse::<f64>()
761        .map(CellValue::Number)
762        .map_err(|_| format!("Cannot convert '{}' to number", s))
763}
764
765fn fn_fixed(args: &[CellValue]) -> Result<CellValue, String> {
766    let n = require_num!(&args[0]);
767    let places = if args.len() > 1 { require_num!(&args[1]) as usize } else { 2 };
768    Ok(CellValue::Text(format!("{:.prec$}", n, prec = places)))
769}
770
771fn fn_rept(args: &[CellValue]) -> Result<CellValue, String> {
772    let s = require_text!(&args[0]);
773    let n = require_num!(&args[1]) as usize;
774    Ok(CellValue::Text(s.repeat(n)))
775}
776
777fn fn_code(args: &[CellValue]) -> Result<CellValue, String> {
778    let s = require_text!(&args[0]);
779    s.chars().next().map(|c| CellValue::Number(c as u32 as f64))
780        .ok_or_else(|| "Empty string".into())
781}
782
783fn fn_char(args: &[CellValue]) -> Result<CellValue, String> {
784    let n = require_num!(&args[0]) as u32;
785    char::from_u32(n).map(|c| CellValue::Text(c.to_string()))
786        .ok_or_else(|| "Invalid char code".into())
787}
788
789fn fn_exact(args: &[CellValue]) -> Result<CellValue, String> {
790    Ok(CellValue::Bool(require_text!(&args[0]) == require_text!(&args[1])))
791}
792
793fn fn_t(args: &[CellValue]) -> Result<CellValue, String> {
794    match &args[0] {
795        CellValue::Text(s) => Ok(CellValue::Text(s.clone())),
796        _ => Ok(CellValue::Text(String::new())),
797    }
798}
799
800// ─── Logical ─────────────────────────────────────────────────────────────────
801
802fn fn_if(args: &[CellValue]) -> Result<CellValue, String> {
803    if args.len() < 2 { return Err("IF needs 2+ args".into()); }
804    if to_bool(&args[0]) {
805        Ok(args[1].clone())
806    } else {
807        Ok(args.get(2).cloned().unwrap_or(CellValue::Bool(false)))
808    }
809}
810
811fn fn_ifs(args: &[CellValue]) -> Result<CellValue, String> {
812    for chunk in args.chunks(2) {
813        if to_bool(&chunk[0]) {
814            return Ok(chunk.get(1).cloned().unwrap_or(CellValue::Empty));
815        }
816    }
817    Ok(CellValue::Error(CellError::NA))
818}
819
820fn fn_and(args: &[CellValue]) -> Result<CellValue, String> {
821    Ok(CellValue::Bool(args.iter().all(to_bool)))
822}
823
824fn fn_or(args: &[CellValue]) -> Result<CellValue, String> {
825    Ok(CellValue::Bool(args.iter().any(to_bool)))
826}
827
828fn fn_not(args: &[CellValue]) -> Result<CellValue, String> {
829    Ok(CellValue::Bool(!to_bool(&args[0])))
830}
831
832fn fn_xor(args: &[CellValue]) -> Result<CellValue, String> {
833    Ok(CellValue::Bool(args.iter().filter(|v| to_bool(v)).count() % 2 == 1))
834}
835
836fn fn_iferror(args: &[CellValue]) -> Result<CellValue, String> {
837    if args.len() < 2 { return Err("IFERROR needs 2 args".into()); }
838    if args[0].is_error() { Ok(args[1].clone()) } else { Ok(args[0].clone()) }
839}
840
841fn fn_ifna(args: &[CellValue]) -> Result<CellValue, String> {
842    if args.len() < 2 { return Err("IFNA needs 2 args".into()); }
843    if matches!(args[0], CellValue::Error(CellError::NA)) { Ok(args[1].clone()) } else { Ok(args[0].clone()) }
844}
845
846fn fn_switch(args: &[CellValue]) -> Result<CellValue, String> {
847    if args.len() < 3 { return Err("SWITCH needs 3+ args".into()); }
848    let expr = &args[0];
849    let pairs = &args[1..];
850    for chunk in pairs.chunks(2) {
851        if chunk.len() == 1 { return Ok(chunk[0].clone()); } // default
852        if &chunk[0] == expr { return Ok(chunk[1].clone()); }
853    }
854    Ok(CellValue::Error(CellError::NA))
855}
856
857fn fn_true(_: &[CellValue]) -> Result<CellValue, String> { Ok(CellValue::Bool(true)) }
858fn fn_false(_: &[CellValue]) -> Result<CellValue, String> { Ok(CellValue::Bool(false)) }
859
860// ─── Lookup ──────────────────────────────────────────────────────────────────
861
862fn fn_vlookup(args: &[CellValue]) -> Result<CellValue, String> {
863    if args.len() < 3 { return Err("VLOOKUP needs 3+ args".into()); }
864    let lookup = &args[0];
865    let col_idx = require_num!(&args[2]) as usize;
866    // args[1] would be the range — simplified to searching the remaining args
867    let data = &args[1..args.len()-1];
868    for chunk in data.chunks(col_idx) {
869        if chunk.first() == Some(lookup) {
870            return Ok(chunk.get(col_idx - 1).cloned().unwrap_or(CellValue::Error(CellError::Ref)));
871        }
872    }
873    Ok(CellValue::Error(CellError::NA))
874}
875
876fn fn_hlookup(args: &[CellValue]) -> Result<CellValue, String> {
877    fn_vlookup(args) // simplified
878}
879
880fn fn_index(args: &[CellValue]) -> Result<CellValue, String> {
881    if args.len() < 3 { return Err("INDEX needs 3 args".into()); }
882    let row = require_num!(&args[1]) as usize;
883    let col = require_num!(&args[2]) as usize;
884    // Simplified: if args[0] is just values, treat as 1D
885    args.get(row.max(1) - 1).cloned().ok_or_else(|| "INDEX out of bounds".into())
886}
887
888fn fn_match(args: &[CellValue]) -> Result<CellValue, String> {
889    if args.len() < 2 { return Err("MATCH needs 2+ args".into()); }
890    let lookup = &args[0];
891    let data = &args[1..];
892    data.iter().position(|v| v == lookup)
893        .map(|i| CellValue::Number((i + 1) as f64))
894        .ok_or_else(|| "Not found".into())
895}
896
897fn fn_choose(args: &[CellValue]) -> Result<CellValue, String> {
898    let idx = require_num!(&args[0]) as usize;
899    args.get(idx).cloned().ok_or_else(|| "CHOOSE index out of range".into())
900}
901
902fn fn_offset(args: &[CellValue]) -> Result<CellValue, String> {
903    // Simplified stub
904    Ok(args.first().cloned().unwrap_or(CellValue::Empty))
905}
906
907fn fn_row(args: &[CellValue]) -> Result<CellValue, String> {
908    Ok(CellValue::Number(1.0)) // simplified
909}
910
911fn fn_column(args: &[CellValue]) -> Result<CellValue, String> {
912    Ok(CellValue::Number(1.0)) // simplified
913}
914
915fn fn_rows(args: &[CellValue]) -> Result<CellValue, String> {
916    Ok(CellValue::Number(args.len() as f64))
917}
918
919fn fn_columns(args: &[CellValue]) -> Result<CellValue, String> {
920    Ok(CellValue::Number(1.0)) // simplified
921}
922
923fn fn_transpose(args: &[CellValue]) -> Result<CellValue, String> {
924    Ok(args.first().cloned().unwrap_or(CellValue::Empty))
925}
926
927// ─── Date & Time ─────────────────────────────────────────────────────────────
928
929fn fn_now(_: &[CellValue]) -> Result<CellValue, String> {
930    Ok(CellValue::Text(chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string()))
931}
932
933fn fn_today(_: &[CellValue]) -> Result<CellValue, String> {
934    Ok(CellValue::Text(chrono::Utc::now().format("%Y-%m-%d").to_string()))
935}
936
937fn fn_date(args: &[CellValue]) -> Result<CellValue, String> {
938    let y = require_num!(&args[0]) as i32;
939    let m = require_num!(&args[1]) as u32;
940    let d = require_num!(&args[2]) as u32;
941    use chrono::NaiveDate;
942    NaiveDate::from_ymd_opt(y, m, d)
943        .map(|dt| CellValue::Date(dt))
944        .ok_or_else(|| "Invalid date".into())
945}
946
947fn fn_time(args: &[CellValue]) -> Result<CellValue, String> {
948    let h = require_num!(&args[0]);
949    let m = require_num!(&args[1]);
950    let s = require_num!(&args[2]);
951    Ok(CellValue::Number((h * 3600.0 + m * 60.0 + s) / 86400.0))
952}
953
954fn extract_date(v: &CellValue) -> Option<chrono::NaiveDate> {
955    match v {
956        CellValue::Date(d) => Some(*d),
957        CellValue::Text(s) => s.parse().ok(),
958        _ => None,
959    }
960}
961
962fn fn_year(args: &[CellValue]) -> Result<CellValue, String> {
963    use chrono::Datelike;
964    extract_date(&args[0]).map(|d| CellValue::Number(d.year() as f64))
965        .ok_or_else(|| "Invalid date".into())
966}
967fn fn_month(args: &[CellValue]) -> Result<CellValue, String> {
968    use chrono::Datelike;
969    extract_date(&args[0]).map(|d| CellValue::Number(d.month() as f64))
970        .ok_or_else(|| "Invalid date".into())
971}
972fn fn_day(args: &[CellValue]) -> Result<CellValue, String> {
973    use chrono::Datelike;
974    extract_date(&args[0]).map(|d| CellValue::Number(d.day() as f64))
975        .ok_or_else(|| "Invalid date".into())
976}
977fn fn_hour(args: &[CellValue]) -> Result<CellValue, String> {
978    Ok(CellValue::Number(0.0)) // simplified
979}
980fn fn_minute(args: &[CellValue]) -> Result<CellValue, String> {
981    Ok(CellValue::Number(0.0))
982}
983fn fn_second(args: &[CellValue]) -> Result<CellValue, String> {
984    Ok(CellValue::Number(0.0))
985}
986fn fn_weekday(args: &[CellValue]) -> Result<CellValue, String> {
987    use chrono::Datelike;
988    extract_date(&args[0]).map(|d| CellValue::Number(d.weekday().num_days_from_sunday() as f64 + 1.0))
989        .ok_or_else(|| "Invalid date".into())
990}
991fn fn_weeknum(args: &[CellValue]) -> Result<CellValue, String> {
992    use chrono::Datelike;
993    extract_date(&args[0]).map(|d| CellValue::Number(d.iso_week().week() as f64))
994        .ok_or_else(|| "Invalid date".into())
995}
996fn fn_edate(args: &[CellValue]) -> Result<CellValue, String> {
997    use chrono::Datelike;
998    let d = extract_date(&args[0]).ok_or_else(|| "Invalid date".to_string())?;
999    let months = require_num!(&args[1]) as i32;
1000    let new_month = d.month() as i32 + months;
1001    let year_add = (new_month - 1) / 12;
1002    let month = ((new_month - 1) % 12 + 12) % 12 + 1;
1003    let new_date = chrono::NaiveDate::from_ymd_opt(d.year() + year_add, month as u32, d.day())
1004        .ok_or_else(|| "Invalid date result".to_string())?;
1005    Ok(CellValue::Date(new_date))
1006}
1007fn fn_eomonth(args: &[CellValue]) -> Result<CellValue, String> {
1008    use chrono::Datelike;
1009    let d = extract_date(&args[0]).ok_or_else(|| "Invalid date".to_string())?;
1010    let months = require_num!(&args[1]) as i32;
1011    let new_month = d.month() as i32 + months + 1;
1012    let year_add = (new_month - 1) / 12;
1013    let month = ((new_month - 1) % 12 + 12) % 12 + 1;
1014    let first_next = chrono::NaiveDate::from_ymd_opt(d.year() + year_add, month as u32, 1).unwrap();
1015    let last = first_next - chrono::Duration::days(1);
1016    Ok(CellValue::Date(last))
1017}
1018fn fn_networkdays(args: &[CellValue]) -> Result<CellValue, String> {
1019    Ok(CellValue::Number(0.0)) // simplified stub
1020}
1021fn fn_workday(args: &[CellValue]) -> Result<CellValue, String> {
1022    Ok(args[0].clone()) // simplified stub
1023}
1024fn fn_datedif(args: &[CellValue]) -> Result<CellValue, String> {
1025    let start = extract_date(&args[0]).ok_or_else(|| "Invalid start date".to_string())?;
1026    let end = extract_date(&args[1]).ok_or_else(|| "Invalid end date".to_string())?;
1027    let unit = require_text!(&args[2]).to_uppercase();
1028    let diff = (end - start).num_days();
1029    match unit.as_str() {
1030        "D" => Ok(CellValue::Number(diff as f64)),
1031        "M" => {
1032            use chrono::Datelike;
1033            let months = (end.year() - start.year()) * 12 + (end.month() as i32 - start.month() as i32);
1034            Ok(CellValue::Number(months as f64))
1035        }
1036        "Y" => {
1037            use chrono::Datelike;
1038            Ok(CellValue::Number((end.year() - start.year()) as f64))
1039        }
1040        _ => Ok(CellValue::Number(diff as f64)),
1041    }
1042}
1043fn fn_datevalue(args: &[CellValue]) -> Result<CellValue, String> {
1044    let s = require_text!(&args[0]);
1045    s.parse::<chrono::NaiveDate>()
1046        .map(|d| CellValue::Date(d))
1047        .map_err(|_| format!("Cannot parse date: {}", s))
1048}
1049fn fn_timevalue(args: &[CellValue]) -> Result<CellValue, String> {
1050    Ok(CellValue::Number(0.0)) // simplified
1051}
1052
1053// ─── Information ─────────────────────────────────────────────────────────────
1054
1055fn fn_isnumber(args: &[CellValue]) -> Result<CellValue, String> {
1056    Ok(CellValue::Bool(args[0].is_number()))
1057}
1058fn fn_istext(args: &[CellValue]) -> Result<CellValue, String> {
1059    Ok(CellValue::Bool(args[0].is_text()))
1060}
1061fn fn_isblank(args: &[CellValue]) -> Result<CellValue, String> {
1062    Ok(CellValue::Bool(args[0].is_empty()))
1063}
1064fn fn_iserror(args: &[CellValue]) -> Result<CellValue, String> {
1065    Ok(CellValue::Bool(args[0].is_error()))
1066}
1067fn fn_iserr(args: &[CellValue]) -> Result<CellValue, String> {
1068    Ok(CellValue::Bool(matches!(&args[0], CellValue::Error(e) if !matches!(e, CellError::NA))))
1069}
1070fn fn_isna(args: &[CellValue]) -> Result<CellValue, String> {
1071    Ok(CellValue::Bool(matches!(&args[0], CellValue::Error(CellError::NA))))
1072}
1073fn fn_islogical(args: &[CellValue]) -> Result<CellValue, String> {
1074    Ok(CellValue::Bool(matches!(args[0], CellValue::Bool(_))))
1075}
1076fn fn_isodd(args: &[CellValue]) -> Result<CellValue, String> {
1077    let n = require_num!(&args[0]) as i64;
1078    Ok(CellValue::Bool(n % 2 != 0))
1079}
1080fn fn_iseven(args: &[CellValue]) -> Result<CellValue, String> {
1081    let n = require_num!(&args[0]) as i64;
1082    Ok(CellValue::Bool(n % 2 == 0))
1083}
1084fn fn_na(_: &[CellValue]) -> Result<CellValue, String> {
1085    Ok(CellValue::Error(CellError::NA))
1086}
1087fn fn_error_type(args: &[CellValue]) -> Result<CellValue, String> {
1088    match &args[0] {
1089        CellValue::Error(e) => Ok(CellValue::Number(match e {
1090            CellError::Null => 1.0, CellError::Div0 => 2.0, CellError::Value => 3.0,
1091            CellError::Ref => 4.0, CellError::Name => 5.0, CellError::Num => 6.0,
1092            CellError::NA => 7.0, _ => 8.0,
1093        })),
1094        _ => Ok(CellValue::Error(CellError::NA)),
1095    }
1096}
1097fn fn_cell(_: &[CellValue]) -> Result<CellValue, String> { Ok(CellValue::Text(String::new())) }
1098fn fn_type(args: &[CellValue]) -> Result<CellValue, String> {
1099    Ok(CellValue::Number(match &args[0] {
1100        CellValue::Number(_) => 1.0,
1101        CellValue::Text(_) => 2.0,
1102        CellValue::Bool(_) => 4.0,
1103        CellValue::Error(_) => 16.0,
1104        CellValue::Empty => 1.0,
1105        _ => 1.0,
1106    }))
1107}
1108fn fn_n(args: &[CellValue]) -> Result<CellValue, String> {
1109    Ok(CellValue::Number(args[0].as_number().unwrap_or(0.0)))
1110}
1111
1112// ─── Financial ───────────────────────────────────────────────────────────────
1113
1114fn fn_pmt(args: &[CellValue]) -> Result<CellValue, String> {
1115    let rate = require_num!(&args[0]);
1116    let nper = require_num!(&args[1]);
1117    let pv = require_num!(&args[2]);
1118    let fv = if args.len() > 3 { require_num!(&args[3]) } else { 0.0 };
1119    let type_ = if args.len() > 4 { require_num!(&args[4]) } else { 0.0 };
1120    let pmt = if rate == 0.0 {
1121        -(pv + fv) / nper
1122    } else {
1123        let factor = (1.0 + rate).powf(nper);
1124        -(pv * factor * rate + fv * rate) / ((factor - 1.0) * (1.0 + rate * type_))
1125    };
1126    Ok(CellValue::Number(pmt))
1127}
1128
1129fn fn_pv(args: &[CellValue]) -> Result<CellValue, String> {
1130    let rate = require_num!(&args[0]);
1131    let nper = require_num!(&args[1]);
1132    let pmt = require_num!(&args[2]);
1133    let fv = if args.len() > 3 { require_num!(&args[3]) } else { 0.0 };
1134    let pv = if rate == 0.0 {
1135        -(pmt * nper + fv)
1136    } else {
1137        let factor = (1.0 + rate).powf(nper);
1138        -(pmt * (factor - 1.0) / rate + fv) / factor
1139    };
1140    Ok(CellValue::Number(pv))
1141}
1142
1143fn fn_fv(args: &[CellValue]) -> Result<CellValue, String> {
1144    let rate = require_num!(&args[0]);
1145    let nper = require_num!(&args[1]);
1146    let pmt = require_num!(&args[2]);
1147    let pv = if args.len() > 3 { require_num!(&args[3]) } else { 0.0 };
1148    let fv = if rate == 0.0 {
1149        -(pmt * nper + pv)
1150    } else {
1151        let factor = (1.0 + rate).powf(nper);
1152        -(pmt * (factor - 1.0) / rate + pv * factor)
1153    };
1154    Ok(CellValue::Number(fv))
1155}
1156
1157fn fn_rate(args: &[CellValue]) -> Result<CellValue, String> {
1158    // Newton-Raphson iteration for RATE
1159    let nper = require_num!(&args[0]);
1160    let pmt = require_num!(&args[1]);
1161    let pv = require_num!(&args[2]);
1162    let mut rate = 0.1;
1163    for _ in 0..1000 {
1164        let f = pv * (1.0_f64 + rate).powf(nper) + pmt * ((1.0_f64 + rate).powf(nper) - 1.0) / rate;
1165        let df = pv * nper * (1.0_f64 + rate).powf(nper - 1.0) +
1166            pmt * (nper * (1.0_f64 + rate).powf(nper - 1.0) * rate - ((1.0_f64 + rate).powf(nper) - 1.0)) / (rate * rate);
1167        let new_rate = rate - f / df;
1168        if (new_rate - rate).abs() < 1e-10 { return Ok(CellValue::Number(new_rate)); }
1169        rate = new_rate;
1170    }
1171    Ok(CellValue::Number(rate))
1172}
1173
1174fn fn_nper(args: &[CellValue]) -> Result<CellValue, String> {
1175    let rate = require_num!(&args[0]);
1176    let pmt = require_num!(&args[1]);
1177    let pv = require_num!(&args[2]);
1178    if rate == 0.0 { return Ok(CellValue::Number(-pv / pmt)); }
1179    let nper = ((pmt / rate) / (pmt / rate + pv)).log(1.0 + rate);
1180    Ok(CellValue::Number(nper))
1181}
1182
1183fn fn_npv(args: &[CellValue]) -> Result<CellValue, String> {
1184    let rate = require_num!(&args[0]);
1185    let npv: f64 = args[1..].iter()
1186        .enumerate()
1187        .filter_map(|(i, v)| v.as_number().map(|n| n / (1.0 + rate).powi(i as i32 + 1)))
1188        .sum();
1189    Ok(CellValue::Number(npv))
1190}
1191
1192fn fn_irr(args: &[CellValue]) -> Result<CellValue, String> {
1193    if args.is_empty() { return Err("IRR needs cash flows".into()); }
1194    let flows: Vec<f64> = args.iter().filter_map(|v| v.as_number()).collect();
1195    let mut rate = 0.1;
1196    for _ in 0..1000 {
1197        let npv: f64 = flows.iter().enumerate()
1198            .map(|(i, cf)| cf / (1.0_f64 + rate).powi(i as i32))
1199            .sum();
1200        let d_npv: f64 = flows.iter().enumerate()
1201            .map(|(i, cf)| -(i as f64) * cf / (1.0_f64 + rate).powi(i as i32 + 1))
1202            .sum();
1203        if d_npv.abs() < 1e-15 { break; }
1204        let new_rate = rate - npv / d_npv;
1205        if (new_rate - rate).abs() < 1e-10 { return Ok(CellValue::Number(new_rate)); }
1206        rate = new_rate;
1207    }
1208    Ok(CellValue::Number(rate))
1209}
1210
1211fn fn_ipmt(args: &[CellValue]) -> Result<CellValue, String> {
1212    let rate = require_num!(&args[0]);
1213    let per = require_num!(&args[1]);
1214    let nper = require_num!(&args[2]);
1215    let pv = require_num!(&args[3]);
1216    let pmt_val = match fn_pmt(&[CellValue::Number(rate), CellValue::Number(nper), CellValue::Number(pv)])? {
1217        CellValue::Number(n) => n,
1218        _ => return Ok(CellValue::Error(CellError::Value)),
1219    };
1220    let ipmt = -(pv * (1.0_f64 + rate).powf(per - 1.0) * rate + pmt_val * ((1.0_f64 + rate).powf(per - 1.0) - 1.0));
1221    Ok(CellValue::Number(ipmt))
1222}
1223
1224fn fn_ppmt(args: &[CellValue]) -> Result<CellValue, String> {
1225    let pmt_val = match fn_pmt(&args[..4])? { CellValue::Number(n) => n, _ => return Ok(CellValue::Error(CellError::Value)) };
1226    let ipmt_val = match fn_ipmt(args)? { CellValue::Number(n) => n, _ => return Ok(CellValue::Error(CellError::Value)) };
1227    Ok(CellValue::Number(pmt_val - ipmt_val))
1228}