//! Text array functions: TEXTSPLIT, VALUETOTEXT, ARRAYTOTEXT
//!
//! TEXTSPLIT: Splits text into a 2D array based on delimiters
//! VALUETOTEXT: Converts a value to text representation
//! ARRAYTOTEXT: Converts an array to text representation

use super::super::utils::collapse_if_scalar;
use crate::args::{ArgSchema, ShapeKind};
use crate::function::Function;
use crate::traits::{ArgumentHandle, CalcValue, FunctionContext};
use formualizer_common::{ArgKind, CoercionPolicy, ExcelError, ExcelErrorKind, LiteralValue};
use formualizer_macros::func_caps;

fn scalar_like_value(arg: &ArgumentHandle<'_, '_>) -> Result<LiteralValue, ExcelError> {
    Ok(match arg.value()? {
        CalcValue::Scalar(v) => v,
        CalcValue::Range(rv) => rv.get_cell(0, 0),
        CalcValue::Callable(_) => LiteralValue::Error(
            ExcelError::new(ExcelErrorKind::Calc).with_message("LAMBDA value must be invoked"),
        ),
    })
}

/// Coerce a LiteralValue to text
fn coerce_text(v: &LiteralValue) -> String {
    match v {
        LiteralValue::Text(s) => s.clone(),
        LiteralValue::Empty => String::new(),
        LiteralValue::Boolean(b) => if *b { "TRUE" } else { "FALSE" }.to_string(),
        LiteralValue::Int(i) => i.to_string(),
        LiteralValue::Number(f) => {
            let s = f.to_string();
            if s.ends_with(".0") {
                s[..s.len() - 2].to_string()
            } else {
                s
            }
        }
        other => other.to_string(),
    }
}

/// Get delimiters from an argument (can be single value or array)
fn get_delimiters(arg: &ArgumentHandle<'_, '_>) -> Result<Vec<String>, ExcelError> {
    let cv = arg.value()?;
    match cv {
        CalcValue::Scalar(v) => match v {
            LiteralValue::Error(e) => Err(e),
            LiteralValue::Array(arr) => {
                let mut delims = Vec::new();
                for row in arr {
                    for cell in row {
                        let s = coerce_text(&cell);
                        if !s.is_empty() {
                            delims.push(s);
                        }
                    }
                }
                Ok(delims)
            }
            other => {
                let s = coerce_text(&other);
                if s.is_empty() {
                    Ok(vec![])
                } else {
                    Ok(vec![s])
                }
            }
        },
        CalcValue::Range(rv) => {
            let mut delims = Vec::new();
            rv.for_each_cell(&mut |cell| {
                let s = coerce_text(cell);
                if !s.is_empty() {
                    delims.push(s);
                }
                Ok(())
            })?;
            Ok(delims)
        }
        CalcValue::Callable(_) => {
            Err(ExcelError::new(ExcelErrorKind::Calc).with_message("LAMBDA value must be invoked"))
        }
    }
}

// ============================================================================
// TEXTSPLIT - Split text into 2D array based on delimiters
// ============================================================================

fn arg_textsplit() -> Vec<ArgSchema> {
    vec![
        // text
        ArgSchema {
            kinds: smallvec::smallvec![ArgKind::Any],
            required: true,
            by_ref: false,
            shape: ShapeKind::Scalar,
            coercion: CoercionPolicy::None,
            max: None,
            repeating: None,
            default: None,
        },
        // col_delimiter
        ArgSchema {
            kinds: smallvec::smallvec![ArgKind::Any],
            required: true,
            by_ref: false,
            shape: ShapeKind::Scalar,
            coercion: CoercionPolicy::None,
            max: None,
            repeating: None,
            default: None,
        },
        // row_delimiter (optional)
        ArgSchema {
            kinds: smallvec::smallvec![ArgKind::Any],
            required: false,
            by_ref: false,
            shape: ShapeKind::Scalar,
            coercion: CoercionPolicy::None,
            max: None,
            repeating: None,
            default: None,
        },
        // ignore_empty (optional, default FALSE)
        ArgSchema {
            kinds: smallvec::smallvec![ArgKind::Logical],
            required: false,
            by_ref: false,
            shape: ShapeKind::Scalar,
            coercion: CoercionPolicy::Logical,
            max: None,
            repeating: None,
            default: Some(LiteralValue::Boolean(false)),
        },
        // match_mode (optional, default 0)
        ArgSchema {
            kinds: smallvec::smallvec![ArgKind::Number],
            required: false,
            by_ref: false,
            shape: ShapeKind::Scalar,
            coercion: CoercionPolicy::NumberLenientText,
            max: None,
            repeating: None,
            default: Some(LiteralValue::Number(0.0)),
        },
        // pad_with (optional, default #N/A)
        ArgSchema {
            kinds: smallvec::smallvec![ArgKind::Any],
            required: false,
            by_ref: false,
            shape: ShapeKind::Scalar,
            coercion: CoercionPolicy::None,
            max: None,
            repeating: None,
            default: Some(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Na))),
        },
    ]
}

/// Split text using any of the delimiters, with optional case-insensitive matching
fn split_by_delimiters(text: &str, delimiters: &[String], case_insensitive: bool) -> Vec<String> {
    if delimiters.is_empty() {
        return vec![text.to_string()];
    }

    let working_text = if case_insensitive {
        text.to_lowercase()
    } else {
        text.to_string()
    };

    let delims_working: Vec<String> = if case_insensitive {
        delimiters.iter().map(|d| d.to_lowercase()).collect()
    } else {
        delimiters.to_vec()
    };

    let mut result = Vec::new();
    let mut current_start = 0;

    while current_start < text.len() {
        let mut earliest_match: Option<(usize, usize)> = None; // (position, delimiter_len)

        for delim in &delims_working {
            if delim.is_empty() {
                continue;
            }
            if let Some(pos) = working_text[current_start..].find(delim.as_str()) {
                let abs_pos = current_start + pos;
                match earliest_match {
                    None => earliest_match = Some((abs_pos, delim.len())),
                    Some((ep, _)) if abs_pos < ep => earliest_match = Some((abs_pos, delim.len())),
                    _ => {}
                }
            }
        }

        match earliest_match {
            Some((pos, len)) => {
                result.push(text[current_start..pos].to_string());
                current_start = pos + len;
            }
            None => {
                result.push(text[current_start..].to_string());
                break;
            }
        }
    }

    // If we ended exactly at a delimiter, add empty string at end
    if current_start == text.len() && !text.is_empty() {
        let ends_with_delim = delims_working.iter().any(|d| {
            if d.is_empty() {
                return false;
            }
            working_text.ends_with(d.as_str())
        });
        if ends_with_delim {
            result.push(String::new());
        }
    }

    result
}

#[derive(Debug)]
pub struct TextSplitFn;
/// Splits text into a dynamic 2D array by column and optional row delimiters.
///
/// `TEXTSPLIT` supports multiple delimiters, optional case-insensitive matching, and output padding.
///
/// # Remarks
/// - Column delimiter is required; row delimiter is optional.
/// - `match_mode=0` is case-sensitive, `match_mode=1` is case-insensitive.
/// - `ignore_empty=TRUE` drops empty segments created by adjacent delimiters.
/// - Rows are padded to equal width using `pad_with` (default `#N/A`).
///
/// # Examples
///
/// ```yaml,sandbox
/// title: "Split CSV row"
/// formula: '=TEXTSPLIT("A,B,C", ",")'
/// expected: "{A,B,C}"
/// ```
///
/// ```yaml,sandbox
/// title: "Row and column split with padding"
/// formula: '=TEXTSPLIT("A,B;C", ",", ";")'
/// expected: "{A,B;C,#N/A}"
/// ```
///
/// ```yaml,docs
/// related:
///   - TEXTBEFORE
///   - TEXTAFTER
///   - TEXTJOIN
/// faq:
///   - q: "Is delimiter matching case-sensitive?"
///     a: "Yes by default; set match_mode to 1 for case-insensitive delimiter matching."
/// ```
/// [formualizer-docgen:schema:start]
/// Name: TEXTSPLIT
/// Type: TextSplitFn
/// Min args: 2
/// Max args: 6
/// Variadic: false
/// Signature: TEXTSPLIT(arg1: any@scalar, arg2: any@scalar, arg3?: any@scalar, arg4?: logical@scalar, arg5?: number@scalar, arg6?: any@scalar)
/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}; arg2{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}; arg3{kinds=any,required=false,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}; arg4{kinds=logical,required=false,shape=scalar,by_ref=false,coercion=Logical,max=None,repeating=None,default=true}; arg5{kinds=number,required=false,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=true}; arg6{kinds=any,required=false,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=true}
/// Caps: PURE
/// [formualizer-docgen:schema:end]
impl Function for TextSplitFn {
    func_caps!(PURE);

    fn name(&self) -> &'static str {
        "TEXTSPLIT"
    }

    fn min_args(&self) -> usize {
        2
    }

    fn arg_schema(&self) -> &'static [ArgSchema] {
        use once_cell::sync::Lazy;
        static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(arg_textsplit);
        &SCHEMA
    }

    fn eval<'a, 'b, 'c>(
        &self,
        args: &'c [ArgumentHandle<'a, 'b>],
        ctx: &dyn FunctionContext<'b>,
    ) -> Result<CalcValue<'b>, ExcelError> {
        // Get text to split
        let text_val = scalar_like_value(&args[0])?;
        let text = match text_val {
            LiteralValue::Error(e) => return Ok(CalcValue::Scalar(LiteralValue::Error(e))),
            other => coerce_text(&other),
        };

        // Get column delimiters
        let col_delimiters = get_delimiters(&args[1])?;

        // Get optional row delimiters
        let row_delimiters = if args.len() > 2 {
            // Check if row_delimiter argument is provided and not omitted
            let val = scalar_like_value(&args[2])?;
            match val {
                LiteralValue::Empty => vec![],
                LiteralValue::Error(e) => return Ok(CalcValue::Scalar(LiteralValue::Error(e))),
                _ => get_delimiters(&args[2])?,
            }
        } else {
            vec![]
        };

        // Get ignore_empty (default FALSE)
        let ignore_empty = if args.len() > 3 {
            match scalar_like_value(&args[3])? {
                LiteralValue::Error(e) => return Ok(CalcValue::Scalar(LiteralValue::Error(e))),
                LiteralValue::Boolean(b) => b,
                LiteralValue::Number(n) => n != 0.0,
                LiteralValue::Int(i) => i != 0,
                _ => false,
            }
        } else {
            false
        };

        // Get match_mode (default 0 = case-sensitive)
        let case_insensitive = if args.len() > 4 {
            match scalar_like_value(&args[4])? {
                LiteralValue::Error(e) => return Ok(CalcValue::Scalar(LiteralValue::Error(e))),
                LiteralValue::Number(n) => n.trunc() as i32 == 1,
                LiteralValue::Int(i) => i == 1,
                _ => false,
            }
        } else {
            false
        };

        // Get pad_with (default #N/A)
        let pad_with = if args.len() > 5 {
            scalar_like_value(&args[5])?
        } else {
            LiteralValue::Error(ExcelError::new(ExcelErrorKind::Na))
        };

        // First, split by row delimiters (if any)
        let row_parts = if row_delimiters.is_empty() {
            vec![text.clone()]
        } else {
            split_by_delimiters(&text, &row_delimiters, case_insensitive)
        };

        // Then split each row by column delimiters
        let mut rows: Vec<Vec<LiteralValue>> = Vec::new();
        let mut max_cols = 0;

        for row_text in row_parts {
            if ignore_empty && row_text.is_empty() {
                continue;
            }

            let col_parts = split_by_delimiters(&row_text, &col_delimiters, case_insensitive);

            let row: Vec<LiteralValue> = if ignore_empty {
                col_parts
                    .into_iter()
                    .filter(|s| !s.is_empty())
                    .map(LiteralValue::Text)
                    .collect()
            } else {
                col_parts.into_iter().map(LiteralValue::Text).collect()
            };

            if !row.is_empty() {
                max_cols = max_cols.max(row.len());
                rows.push(row);
            }
        }

        // Handle empty result
        if rows.is_empty() {
            return Ok(CalcValue::Scalar(LiteralValue::Text(String::new())));
        }

        // Pad rows to same width
        for row in &mut rows {
            while row.len() < max_cols {
                row.push(pad_with.clone());
            }
        }

        Ok(collapse_if_scalar(rows, ctx.date_system()))
    }
}

// ============================================================================
// VALUETOTEXT - Convert value to text representation
// ============================================================================

fn arg_valuetotext() -> Vec<ArgSchema> {
    vec![
        // value
        ArgSchema {
            kinds: smallvec::smallvec![ArgKind::Any],
            required: true,
            by_ref: false,
            shape: ShapeKind::Scalar,
            coercion: CoercionPolicy::None,
            max: None,
            repeating: None,
            default: None,
        },
        // format (optional, default 0=concise)
        ArgSchema {
            kinds: smallvec::smallvec![ArgKind::Number],
            required: false,
            by_ref: false,
            shape: ShapeKind::Scalar,
            coercion: CoercionPolicy::NumberLenientText,
            max: None,
            repeating: None,
            default: Some(LiteralValue::Number(0.0)),
        },
    ]
}

/// Convert a single value to its text representation
fn value_to_text_repr(v: &LiteralValue, strict: bool) -> String {
    match v {
        LiteralValue::Text(s) => {
            if strict {
                format!("\"{}\"", s)
            } else {
                s.clone()
            }
        }
        LiteralValue::Number(n) => {
            let s = n.to_string();
            if s.ends_with(".0") {
                s[..s.len() - 2].to_string()
            } else {
                s
            }
        }
        LiteralValue::Int(i) => i.to_string(),
        LiteralValue::Boolean(b) => if *b { "TRUE" } else { "FALSE" }.to_string(),
        LiteralValue::Empty => String::new(),
        LiteralValue::Error(e) => e.to_string(),
        LiteralValue::Array(arr) => {
            // For arrays, use array syntax
            let rows: Vec<String> = arr
                .iter()
                .map(|row| {
                    row.iter()
                        .map(|cell| value_to_text_repr(cell, strict))
                        .collect::<Vec<_>>()
                        .join(",")
                })
                .collect();
            format!("{{{}}}", rows.join(";"))
        }
        LiteralValue::Date(d) => d.format("%Y-%m-%d").to_string(),
        LiteralValue::DateTime(dt) => dt.format("%Y-%m-%d %H:%M:%S").to_string(),
        LiteralValue::Time(t) => t.format("%H:%M:%S").to_string(),
        LiteralValue::Duration(dur) => {
            let total_secs = dur.num_seconds();
            let hours = total_secs / 3600;
            let mins = (total_secs % 3600) / 60;
            let secs = total_secs % 60;
            format!("{}:{:02}:{:02}", hours, mins, secs)
        }
        LiteralValue::Pending => String::new(),
    }
}

#[derive(Debug)]
pub struct ValueToTextFn;
/// Converts a value to text representation.
///
/// `VALUETOTEXT(value, [format])` supports concise (`0`) and strict (`1`) modes.
///
/// # Remarks
/// - Concise mode (`0`) returns natural text for scalars.
/// - Strict mode (`1`) adds explicit quoting for text and serializes arrays with braces.
/// - In concise mode, error values are propagated as errors.
/// - In strict mode, error values are rendered as their error text.
///
/// # Examples
///
/// ```yaml,sandbox
/// title: "Concise text conversion"
/// formula: '=VALUETOTEXT(123)'
/// expected: "123"
/// ```
///
/// ```yaml,sandbox
/// title: "Strict quoting for text"
/// formula: '=VALUETOTEXT("hello", 1)'
/// expected: '"hello"'
/// ```
///
/// ```yaml,docs
/// related:
///   - ARRAYTOTEXT
///   - TEXT
///   - VALUE
/// faq:
///   - q: "How are errors handled in concise vs strict mode?"
///     a: "Concise mode returns the error, while strict mode converts the error to its text form."
/// ```
/// [formualizer-docgen:schema:start]
/// Name: VALUETOTEXT
/// Type: ValueToTextFn
/// Min args: 1
/// Max args: 2
/// Variadic: false
/// Signature: VALUETOTEXT(arg1: any@scalar, arg2?: number@scalar)
/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}; arg2{kinds=number,required=false,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=true}
/// Caps: PURE
/// [formualizer-docgen:schema:end]
impl Function for ValueToTextFn {
    func_caps!(PURE);

    fn name(&self) -> &'static str {
        "VALUETOTEXT"
    }

    fn min_args(&self) -> usize {
        1
    }

    fn arg_schema(&self) -> &'static [ArgSchema] {
        use once_cell::sync::Lazy;
        static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(arg_valuetotext);
        &SCHEMA
    }

    fn eval<'a, 'b, 'c>(
        &self,
        args: &'c [ArgumentHandle<'a, 'b>],
        _ctx: &dyn FunctionContext<'b>,
    ) -> Result<CalcValue<'b>, ExcelError> {
        // Get value
        let value = scalar_like_value(&args[0])?;

        // Get format (0=concise, 1=strict)
        let format = if args.len() > 1 {
            match scalar_like_value(&args[1])? {
                LiteralValue::Error(e) => return Ok(CalcValue::Scalar(LiteralValue::Error(e))),
                LiteralValue::Number(n) => n.trunc() as i32,
                LiteralValue::Int(i) => i as i32,
                _ => 0,
            }
        } else {
            0
        };

        let strict = format == 1;

        // Handle error propagation for the value itself
        if let LiteralValue::Error(e) = &value {
            // In strict mode, errors become their text representation
            // In concise mode, propagate the error
            if strict {
                return Ok(CalcValue::Scalar(LiteralValue::Text(e.to_string())));
            } else {
                return Ok(CalcValue::Scalar(LiteralValue::Error(e.clone())));
            }
        }

        let result = value_to_text_repr(&value, strict);
        Ok(CalcValue::Scalar(LiteralValue::Text(result)))
    }
}

// ============================================================================
// ARRAYTOTEXT - Convert array to text representation
// ============================================================================

fn arg_arraytotext() -> Vec<ArgSchema> {
    vec![
        // array
        ArgSchema {
            kinds: smallvec::smallvec![ArgKind::Any, ArgKind::Range],
            required: true,
            by_ref: false,
            shape: ShapeKind::Range,
            coercion: CoercionPolicy::None,
            max: None,
            repeating: None,
            default: None,
        },
        // format (optional, default 0=concise)
        ArgSchema {
            kinds: smallvec::smallvec![ArgKind::Number],
            required: false,
            by_ref: false,
            shape: ShapeKind::Scalar,
            coercion: CoercionPolicy::NumberLenientText,
            max: None,
            repeating: None,
            default: Some(LiteralValue::Number(0.0)),
        },
    ]
}

#[derive(Debug)]
pub struct ArrayToTextFn;
/// Converts an array or range into a text representation.
///
/// `ARRAYTOTEXT(array, [format])` supports concise (`0`) and strict (`1`) output styles.
///
/// # Remarks
/// - Strict mode returns brace-delimited array syntax with row/column separators.
/// - Concise mode flattens all values into a comma-space list.
/// - Cells are converted using the same scalar text rules used by `VALUETOTEXT`.
/// - Errors in scalar-only input propagate immediately.
///
/// # Examples
///
/// ```yaml,sandbox
/// title: "Concise flattened output"
/// formula: '=ARRAYTOTEXT({1,2,3})'
/// expected: "1, 2, 3"
/// ```
///
/// ```yaml,sandbox
/// title: "Strict 2D representation"
/// formula: '=ARRAYTOTEXT({1,2;3,4}, 1)'
/// expected: "{1,2;3,4}"
/// ```
///
/// ```yaml,docs
/// related:
///   - VALUETOTEXT
///   - TEXTJOIN
///   - TEXTSPLIT
/// faq:
///   - q: "What changes when format is 1?"
///     a: "Format 1 returns brace-delimited array syntax; format 0 flattens values into a comma-space list."
/// ```
/// [formualizer-docgen:schema:start]
/// Name: ARRAYTOTEXT
/// Type: ArrayToTextFn
/// Min args: 1
/// Max args: 2
/// Variadic: false
/// Signature: ARRAYTOTEXT(arg1: any|range@range, arg2?: number@scalar)
/// Arg schema: arg1{kinds=any|range,required=true,shape=range,by_ref=false,coercion=None,max=None,repeating=None,default=false}; arg2{kinds=number,required=false,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=true}
/// Caps: PURE
/// [formualizer-docgen:schema:end]
impl Function for ArrayToTextFn {
    func_caps!(PURE);

    fn name(&self) -> &'static str {
        "ARRAYTOTEXT"
    }

    fn min_args(&self) -> usize {
        1
    }

    fn arg_schema(&self) -> &'static [ArgSchema] {
        use once_cell::sync::Lazy;
        static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(arg_arraytotext);
        &SCHEMA
    }

    fn eval<'a, 'b, 'c>(
        &self,
        args: &'c [ArgumentHandle<'a, 'b>],
        _ctx: &dyn FunctionContext<'b>,
    ) -> Result<CalcValue<'b>, ExcelError> {
        // Get format (0=concise, 1=strict)
        let format = if args.len() > 1 {
            match scalar_like_value(&args[1])? {
                LiteralValue::Error(e) => return Ok(CalcValue::Scalar(LiteralValue::Error(e))),
                LiteralValue::Number(n) => n.trunc() as i32,
                LiteralValue::Int(i) => i as i32,
                _ => 0,
            }
        } else {
            0
        };

        let strict = format == 1;

        // Try to get array from argument
        let rows: Vec<Vec<LiteralValue>> = if let Ok(rv) = args[0].range_view() {
            let (num_rows, num_cols) = rv.dims();
            let mut result = Vec::with_capacity(num_rows);
            for r in 0..num_rows {
                let mut row = Vec::with_capacity(num_cols);
                for c in 0..num_cols {
                    row.push(rv.get_cell(r, c));
                }
                result.push(row);
            }
            result
        } else {
            let cv = args[0].value()?;
            match cv.into_literal() {
                LiteralValue::Array(arr) => arr,
                LiteralValue::Error(e) => return Ok(CalcValue::Scalar(LiteralValue::Error(e))),
                other => vec![vec![other]],
            }
        };

        let result = if strict {
            // Strict format: {value;value;...} with rows separated by semicolons
            // and columns by commas, with strings quoted
            let row_strs: Vec<String> = rows
                .iter()
                .map(|row| {
                    row.iter()
                        .map(|cell| value_to_text_repr(cell, true))
                        .collect::<Vec<_>>()
                        .join(",")
                })
                .collect();
            format!("{{{}}}", row_strs.join(";"))
        } else {
            // Concise format: comma-separated values (all cells flattened)
            let all_values: Vec<String> = rows
                .iter()
                .flat_map(|row| row.iter().map(|cell| value_to_text_repr(cell, false)))
                .collect();
            all_values.join(", ")
        };

        Ok(CalcValue::Scalar(LiteralValue::Text(result)))
    }
}

// ============================================================================
// Registration
// ============================================================================

pub fn register_builtins() {
    use crate::function_registry::register_function;
    use std::sync::Arc;

    register_function(Arc::new(TextSplitFn));
    register_function(Arc::new(ValueToTextFn));
    register_function(Arc::new(ArrayToTextFn));
}

// ============================================================================
// Tests
// ============================================================================

#[cfg(test)]
mod tests {
    use super::*;
    use crate::test_workbook::TestWorkbook;
    use crate::traits::ArgumentHandle;
    use formualizer_parse::parser::{ASTNode, ASTNodeType};
    use std::sync::Arc;

    fn lit(v: LiteralValue) -> ASTNode {
        ASTNode::new(ASTNodeType::Literal(v), None)
    }

    fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
        wb.interpreter()
    }

    #[test]
    fn test_valuetotext_concise() {
        let wb = TestWorkbook::new().with_function(Arc::new(ValueToTextFn));
        let ctx = interp(&wb);
        let f = ctx.context.get_function("", "VALUETOTEXT").unwrap();

        // Test number
        let num = lit(LiteralValue::Number(123.0));
        let args = vec![ArgumentHandle::new(&num, &ctx)];
        match f
            .dispatch(&args, &ctx.function_context(None))
            .unwrap()
            .into_literal()
        {
            LiteralValue::Text(s) => assert_eq!(s, "123"),
            v => panic!("unexpected {v:?}"),
        }

        // Test text (concise = no quotes)
        let text = lit(LiteralValue::Text("hello".to_string()));
        let args = vec![ArgumentHandle::new(&text, &ctx)];
        match f
            .dispatch(&args, &ctx.function_context(None))
            .unwrap()
            .into_literal()
        {
            LiteralValue::Text(s) => assert_eq!(s, "hello"),
            v => panic!("unexpected {v:?}"),
        }
    }

    #[test]
    fn test_valuetotext_strict() {
        let wb = TestWorkbook::new().with_function(Arc::new(ValueToTextFn));
        let ctx = interp(&wb);
        let f = ctx.context.get_function("", "VALUETOTEXT").unwrap();

        // Test text with strict format (quotes)
        let text = lit(LiteralValue::Text("hello".to_string()));
        let format = lit(LiteralValue::Number(1.0));
        let args = vec![
            ArgumentHandle::new(&text, &ctx),
            ArgumentHandle::new(&format, &ctx),
        ];
        match f
            .dispatch(&args, &ctx.function_context(None))
            .unwrap()
            .into_literal()
        {
            LiteralValue::Text(s) => assert_eq!(s, "\"hello\""),
            v => panic!("unexpected {v:?}"),
        }
    }

    #[test]
    fn test_arraytotext_concise() {
        let wb = TestWorkbook::new().with_function(Arc::new(ArrayToTextFn));
        let ctx = interp(&wb);
        let f = ctx.context.get_function("", "ARRAYTOTEXT").unwrap();

        // Test simple array
        let arr = lit(LiteralValue::Array(vec![vec![
            LiteralValue::Number(1.0),
            LiteralValue::Number(2.0),
            LiteralValue::Number(3.0),
        ]]));
        let args = vec![ArgumentHandle::new(&arr, &ctx)];
        match f
            .dispatch(&args, &ctx.function_context(None))
            .unwrap()
            .into_literal()
        {
            LiteralValue::Text(s) => assert_eq!(s, "1, 2, 3"),
            v => panic!("unexpected {v:?}"),
        }
    }

    #[test]
    fn test_arraytotext_strict() {
        let wb = TestWorkbook::new().with_function(Arc::new(ArrayToTextFn));
        let ctx = interp(&wb);
        let f = ctx.context.get_function("", "ARRAYTOTEXT").unwrap();

        // Test 2D array with strict format
        let arr = lit(LiteralValue::Array(vec![
            vec![LiteralValue::Number(1.0), LiteralValue::Number(2.0)],
            vec![LiteralValue::Number(3.0), LiteralValue::Number(4.0)],
        ]));
        let format = lit(LiteralValue::Number(1.0));
        let args = vec![
            ArgumentHandle::new(&arr, &ctx),
            ArgumentHandle::new(&format, &ctx),
        ];
        match f
            .dispatch(&args, &ctx.function_context(None))
            .unwrap()
            .into_literal()
        {
            LiteralValue::Text(s) => assert_eq!(s, "{1,2;3,4}"),
            v => panic!("unexpected {v:?}"),
        }
    }

    #[test]
    fn test_textsplit_basic() {
        let wb = TestWorkbook::new().with_function(Arc::new(TextSplitFn));
        let ctx = interp(&wb);
        let f = ctx.context.get_function("", "TEXTSPLIT").unwrap();

        // Test simple split
        let text = lit(LiteralValue::Text("a,b,c".to_string()));
        let delim = lit(LiteralValue::Text(",".to_string()));
        let args = vec![
            ArgumentHandle::new(&text, &ctx),
            ArgumentHandle::new(&delim, &ctx),
        ];
        match f
            .dispatch(&args, &ctx.function_context(None))
            .unwrap()
            .into_literal()
        {
            LiteralValue::Array(arr) => {
                assert_eq!(arr.len(), 1);
                assert_eq!(arr[0].len(), 3);
                assert_eq!(arr[0][0], LiteralValue::Text("a".to_string()));
                assert_eq!(arr[0][1], LiteralValue::Text("b".to_string()));
                assert_eq!(arr[0][2], LiteralValue::Text("c".to_string()));
            }
            v => panic!("unexpected {v:?}"),
        }
    }

    #[test]
    fn test_textsplit_2d() {
        let wb = TestWorkbook::new().with_function(Arc::new(TextSplitFn));
        let ctx = interp(&wb);
        let f = ctx.context.get_function("", "TEXTSPLIT").unwrap();

        // Test 2D split with row and column delimiters
        let text = lit(LiteralValue::Text("a,b;c,d".to_string()));
        let col_delim = lit(LiteralValue::Text(",".to_string()));
        let row_delim = lit(LiteralValue::Text(";".to_string()));
        let args = vec![
            ArgumentHandle::new(&text, &ctx),
            ArgumentHandle::new(&col_delim, &ctx),
            ArgumentHandle::new(&row_delim, &ctx),
        ];
        match f
            .dispatch(&args, &ctx.function_context(None))
            .unwrap()
            .into_literal()
        {
            LiteralValue::Array(arr) => {
                assert_eq!(arr.len(), 2);
                assert_eq!(arr[0].len(), 2);
                assert_eq!(arr[0][0], LiteralValue::Text("a".to_string()));
                assert_eq!(arr[0][1], LiteralValue::Text("b".to_string()));
                assert_eq!(arr[1][0], LiteralValue::Text("c".to_string()));
                assert_eq!(arr[1][1], LiteralValue::Text("d".to_string()));
            }
            v => panic!("unexpected {v:?}"),
        }
    }

    #[test]
    fn test_textsplit_ignore_empty() {
        let wb = TestWorkbook::new().with_function(Arc::new(TextSplitFn));
        let ctx = interp(&wb);
        let f = ctx.context.get_function("", "TEXTSPLIT").unwrap();

        // Test with consecutive delimiters and ignore_empty=TRUE
        let text = lit(LiteralValue::Text("a,,b".to_string()));
        let delim = lit(LiteralValue::Text(",".to_string()));
        let row_delim = lit(LiteralValue::Empty);
        let ignore_empty = lit(LiteralValue::Boolean(true));
        let args = vec![
            ArgumentHandle::new(&text, &ctx),
            ArgumentHandle::new(&delim, &ctx),
            ArgumentHandle::new(&row_delim, &ctx),
            ArgumentHandle::new(&ignore_empty, &ctx),
        ];
        match f
            .dispatch(&args, &ctx.function_context(None))
            .unwrap()
            .into_literal()
        {
            LiteralValue::Array(arr) => {
                assert_eq!(arr.len(), 1);
                assert_eq!(arr[0].len(), 2);
                assert_eq!(arr[0][0], LiteralValue::Text("a".to_string()));
                assert_eq!(arr[0][1], LiteralValue::Text("b".to_string()));
            }
            v => panic!("unexpected {v:?}"),
        }
    }

    #[test]
    fn test_textsplit_case_insensitive() {
        let wb = TestWorkbook::new().with_function(Arc::new(TextSplitFn));
        let ctx = interp(&wb);
        let f = ctx.context.get_function("", "TEXTSPLIT").unwrap();

        // Test case-insensitive matching
        let text = lit(LiteralValue::Text("aXbxc".to_string()));
        let delim = lit(LiteralValue::Text("X".to_string()));
        let row_delim = lit(LiteralValue::Empty);
        let ignore_empty = lit(LiteralValue::Boolean(false));
        let match_mode = lit(LiteralValue::Number(1.0)); // case-insensitive
        let args = vec![
            ArgumentHandle::new(&text, &ctx),
            ArgumentHandle::new(&delim, &ctx),
            ArgumentHandle::new(&row_delim, &ctx),
            ArgumentHandle::new(&ignore_empty, &ctx),
            ArgumentHandle::new(&match_mode, &ctx),
        ];
        match f
            .dispatch(&args, &ctx.function_context(None))
            .unwrap()
            .into_literal()
        {
            LiteralValue::Array(arr) => {
                assert_eq!(arr.len(), 1);
                assert_eq!(arr[0].len(), 3);
                assert_eq!(arr[0][0], LiteralValue::Text("a".to_string()));
                assert_eq!(arr[0][1], LiteralValue::Text("b".to_string()));
                assert_eq!(arr[0][2], LiteralValue::Text("c".to_string()));
            }
            v => panic!("unexpected {v:?}"),
        }
    }
}
