//! Temporal functions for creating dates, times, and durations.

use crate::error::{ExecutionError, ExecutionResult};
use crate::result::{
    CypherValue, DateTimeValue, DateValue, DurationValue, ExtendedDateValue,
    LocalDateTimeValue, LocalTimeValue, TimeValue,
};
use chrono::{DateTime, Datelike, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, Offset, TimeZone, Timelike, Utc};
use chrono_tz::Tz;
use indexmap::IndexMap;
use std::cell::RefCell;

// Thread-local cache for temporal "now" values.
// Within a single query execution, temporal functions like datetime(), localtime(), etc.
// should return the same value when called with no arguments. This cache holds the
// current "now" value that's used throughout the query.
thread_local! {
    static TEMPORAL_NOW_CACHE: RefCell<Option<DateTime<Utc>>> = const { RefCell::new(None) };
}

/// Clear the temporal now cache. Call this at the start of each query execution.
pub fn clear_temporal_cache() {
    TEMPORAL_NOW_CACHE.with(|cache| {
        *cache.borrow_mut() = None;
    });
}

/// Get or initialize the cached "now" time for this query.
fn get_cached_now() -> DateTime<Utc> {
    TEMPORAL_NOW_CACHE.with(|cache| {
        let mut cached = cache.borrow_mut();
        if cached.is_none() {
            *cached = Some(Utc::now());
        }
        cached.unwrap()
    })
}

/// Helper to extract an optional integer component from a map
fn get_optional_int_component(
    map: &IndexMap<String, CypherValue>,
    key: &str,
    default: i64,
) -> i64 {
    map.get(key)
        .and_then(|v| v.as_integer())
        .unwrap_or(default)
}

/// Helper to extract an optional float component from a map (handles both float and integer)
fn get_optional_float_component(
    map: &IndexMap<String, CypherValue>,
    key: &str,
    default: f64,
) -> f64 {
    map.get(key)
        .map(|v| match v {
            CypherValue::Float(f) => *f,
            CypherValue::Integer(i) => *i as f64,
            _ => default,
        })
        .unwrap_or(default)
}

/// Helper to compute total nanoseconds from millisecond, microsecond, and nanosecond components
/// When constructing time from components, these are combined: total_ns = ms*1e6 + us*1e3 + ns
fn compute_total_nanoseconds(
    map: &IndexMap<String, CypherValue>,
    default_ns: i64,
) -> i64 {
    let millisecond = get_optional_int_component(map, "millisecond", 0);
    let microsecond = get_optional_int_component(map, "microsecond", 0);
    let nanosecond = get_optional_int_component(map, "nanosecond", 0);

    // If any sub-second component is specified, compute total from components
    if map.contains_key("millisecond") || map.contains_key("microsecond") || map.contains_key("nanosecond") {
        millisecond * 1_000_000 + microsecond * 1_000 + nanosecond
    } else {
        // No sub-second components specified, use default
        default_ns
    }
}

/// Chrono's year range limits
/// NaiveDate range is -262143-01-01 to +262142-12-31
const CHRONO_MIN_YEAR: i64 = -262_143;
const CHRONO_MAX_YEAR: i64 = 262_142;

/// Helper: Create a date value supporting extended year range (±999M years)
/// Returns Date for years in chrono range, ExtendedDate for years outside
fn create_date_value(year: i64, month: u32, day: u32) -> ExecutionResult<CypherValue> {
    if (CHRONO_MIN_YEAR..=CHRONO_MAX_YEAR).contains(&year) {
        // Use chrono for standard range
        NaiveDate::from_ymd_opt(year as i32, month, day)
            .map(|d| CypherValue::Date(DateValue::new(d)))
            .ok_or_else(|| ExecutionError::InvalidArgument(format!(
                "Invalid date: year={}, month={}, day={}",
                year, month, day
            )))
    } else {
        // Use extended date for astronomical years
        // Validate month and day
        if !(1..=12).contains(&month) {
            return Err(ExecutionError::InvalidArgument(format!(
                "Invalid month: {}", month
            )));
        }
        let max_day = crate::result::extended_temporal::ExtendedDate::days_in_month(year, month);
        if day < 1 || day > max_day {
            return Err(ExecutionError::InvalidArgument(format!(
                "Invalid day {} for year {} month {}", day, year, month
            )));
        }
        Ok(CypherValue::ExtendedDate(ExtendedDateValue::new(year, month as u8, day as u8)))
    }
}

/// Helper: Parse ISO 8601 localdatetime string (supports basic and extended formats)
fn parse_localdatetime_string(s: &str) -> ExecutionResult<CypherValue> {
    // Try extended formats first
    if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.f") {
        return Ok(CypherValue::LocalDateTime(LocalDateTimeValue::new(dt)));
    }
    if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S") {
        return Ok(CypherValue::LocalDateTime(LocalDateTimeValue::new(dt)));
    }
    if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M") {
        return Ok(CypherValue::LocalDateTime(LocalDateTimeValue::new(dt)));
    }

    // Split by 'T' to separate date and time parts
    if let Some(t_pos) = s.find('T') {
        let date_part = &s[..t_pos];
        let time_part = &s[t_pos + 1..];

        // Parse date part (handles all formats including astronomical dates)
        let date_value = parse_date_string(date_part)?;

        // Parse time part (handles basic and extended formats)
        let time = match parse_localtime_string(time_part)? {
            CypherValue::LocalTime(lt) => lt.time,
            _ => unreachable!(),
        };

        // Handle both regular and extended dates
        match date_value {
            CypherValue::Date(d) => {
                let dt = NaiveDateTime::new(d.date, time);
                return Ok(CypherValue::LocalDateTime(LocalDateTimeValue::new(dt)));
            }
            CypherValue::ExtendedDate(ed) => {
                // Create ExtendedLocalDateTime for astronomical dates
                return Ok(CypherValue::ExtendedLocalDateTime(
                    crate::result::ExtendedLocalDateTimeValue::new(
                        ed,
                        time.hour() as u8,
                        time.minute() as u8,
                        time.second() as u8,
                        time.nanosecond(),
                    )
                ));
            }
            _ => unreachable!(),
        }
    }

    // No 'T' separator - try to parse as date-only string (defaults to midnight)
    // This handles cases like localdatetime('-999999999-01-01') which are date-only
    let date_value = parse_date_string(s)?;
    match date_value {
        CypherValue::Date(d) => {
            let dt = NaiveDateTime::new(d.date, NaiveTime::from_hms_opt(0, 0, 0).unwrap());
            return Ok(CypherValue::LocalDateTime(LocalDateTimeValue::new(dt)));
        }
        CypherValue::ExtendedDate(ed) => {
            // Create ExtendedLocalDateTime for astronomical dates with midnight time
            return Ok(CypherValue::ExtendedLocalDateTime(
                crate::result::ExtendedLocalDateTimeValue::new(ed, 0, 0, 0, 0)
            ));
        }
        _ => {}
    }

    Err(ExecutionError::InvalidArgument(format!(
        "Invalid localdatetime string '{}'",
        s
    )))
}

/// Helper: Parse ISO 8601 localtime string (supports basic and extended formats)
fn parse_localtime_string(s: &str) -> ExecutionResult<CypherValue> {
    // Try extended formats first
    if let Ok(time) = NaiveTime::parse_from_str(s, "%H:%M:%S%.f") {
        return Ok(CypherValue::LocalTime(LocalTimeValue::new(time)));
    }
    if let Ok(time) = NaiveTime::parse_from_str(s, "%H:%M:%S") {
        return Ok(CypherValue::LocalTime(LocalTimeValue::new(time)));
    }
    if let Ok(time) = NaiveTime::parse_from_str(s, "%H:%M") {
        return Ok(CypherValue::LocalTime(LocalTimeValue::new(time)));
    }

    // Try basic formats (no separators)
    // Handle HHMM, HHMMSS, HHMMSS.fff, or HH
    let all_digits_before_dot = s.chars()
        .take_while(|c| *c != '.')
        .all(|c| c.is_ascii_digit());

    if all_digits_before_dot && (s.len() == 2 || s.len() == 4 || s.len() >= 6) {
        let formatted = if s.len() > 6 && s.chars().nth(6) == Some('.') {
            // HHMMSS.fff - Has fractional seconds
            format!("{}:{}:{}", &s[0..2], &s[2..4], &s[4..])
        } else if s.len() == 6 {
            // HHMMSS
            format!("{}:{}:{}", &s[0..2], &s[2..4], &s[4..6])
        } else if s.len() == 4 {
            // HHMM
            format!("{}:{}", &s[0..2], &s[2..4])
        } else if s.len() == 2 {
            // HH
            format!("{}:00", s)
        } else {
            s.to_string()
        };

        if let Ok(time) = NaiveTime::parse_from_str(&formatted, "%H:%M:%S%.f")
            .or_else(|_| NaiveTime::parse_from_str(&formatted, "%H:%M:%S"))
            .or_else(|_| NaiveTime::parse_from_str(&formatted, "%H:%M")) {
            return Ok(CypherValue::LocalTime(LocalTimeValue::new(time)));
        }
    }

    Err(ExecutionError::InvalidArgument(format!("Invalid time string '{}'", s)))
}

/// Helper: Parse ISO 8601 localtime string (supports basic and extended formats)
/// Helper: Parse ISO 8601 date string (supports basic and extended formats)
fn parse_date_string(s: &str) -> ExecutionResult<CypherValue> {
    // Handle astronomical dates with explicit sign prefix (e.g., -999999999-01-01, +999999999-12-31)
    // These dates are outside chrono's range (-262143 to +262142 years)
    if (s.starts_with('-') || s.starts_with('+')) && s.len() > 5 {
        // Parse format: [+-]YYYY...-MM-DD
        let sign = if s.starts_with('-') { -1i64 } else { 1i64 };
        let rest = &s[1..]; // Skip the sign

        // Find the first dash after the year digits
        if let Some(first_dash) = rest.find('-') {
            if first_dash > 0 {
                let year_str = &rest[..first_dash];
                let remainder = &rest[first_dash + 1..];

                if let Ok(year_abs) = year_str.parse::<i64>() {
                    let year = sign * year_abs;

                    // Parse MM-DD
                    if let Some(second_dash) = remainder.find('-') {
                        let month_str = &remainder[..second_dash];
                        let day_str = &remainder[second_dash + 1..];

                        if let (Ok(month), Ok(day)) = (month_str.parse::<u8>(), day_str.parse::<u8>()) {
                            if (1..=12).contains(&month) && (1..=31).contains(&day) {
                                // Check if it's within chrono's range
                                if (-262143..=262142).contains(&year) {
                                    // Use chrono for validation and creation
                                    if let Some(date) = NaiveDate::from_ymd_opt(year as i32, month as u32, day as u32) {
                                        return Ok(CypherValue::Date(DateValue::new(date)));
                                    }
                                } else {
                                    // Return ExtendedDate for astronomical dates
                                    return Ok(CypherValue::ExtendedDate(
                                        crate::result::ExtendedDateValue::new(year, month, day)
                                    ));
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    // Try extended format: YYYY-MM-DD
    if let Ok(date) = NaiveDate::parse_from_str(s, "%Y-%m-%d") {
        return Ok(CypherValue::Date(DateValue::new(date)));
    }

    // Try basic format: YYYYMMDD
    if s.len() == 8 && s.chars().all(|c| c.is_ascii_digit()) {
        if let Ok(date) = NaiveDate::parse_from_str(s, "%Y%m%d") {
            return Ok(CypherValue::Date(DateValue::new(date)));
        }
    }

    // Try year-month: YYYY-MM or YYYYMM
    if (s.len() == 7 && s.contains('-')) || (s.len() == 6 && s.chars().all(|c| c.is_ascii_digit())) {
        let (year, month) = if s.contains('-') {
            let parts: Vec<&str> = s.split('-').collect();
            (parts[0].parse::<i32>().ok(), parts.get(1).and_then(|m| m.parse::<u32>().ok()))
        } else {
            (s[0..4].parse::<i32>().ok(), s[4..6].parse::<u32>().ok())
        };
        if let (Some(y), Some(m)) = (year, month) {
            if let Some(date) = NaiveDate::from_ymd_opt(y, m, 1) {
                return Ok(CypherValue::Date(DateValue::new(date)));
            }
        }
    }

    // Try week dates: YYYY-Www-D, YYYY-Www, YYYYWwwD, YYYYWww
    if s.contains('W') || s.contains('w') {
        let s_upper = s.to_uppercase();

        // Extended: YYYY-Www-D
        if s_upper.len() == 10 && s_upper.chars().nth(4) == Some('-') && s_upper.chars().nth(8) == Some('-') {
            if let Ok((y, w, d)) = parse_week_date_components(&s_upper[0..4], &s_upper[6..8], Some(&s_upper[9..10])) {
                if let Some(date) = NaiveDate::from_isoywd_opt(y, w, d) {
                    return Ok(CypherValue::Date(DateValue::new(date)));
                }
            }
        }
        // Extended: YYYY-Www
        else if s_upper.len() == 8 && s_upper.chars().nth(4) == Some('-') {
            if let Ok((y, w, d)) = parse_week_date_components(&s_upper[0..4], &s_upper[6..8], None) {
                if let Some(date) = NaiveDate::from_isoywd_opt(y, w, d) {
                    return Ok(CypherValue::Date(DateValue::new(date)));
                }
            }
        }
        // Basic: YYYYWwwD
        else if s_upper.len() == 8 {
            if let Ok((y, w, d)) = parse_week_date_components(&s_upper[0..4], &s_upper[5..7], Some(&s_upper[7..8])) {
                if let Some(date) = NaiveDate::from_isoywd_opt(y, w, d) {
                    return Ok(CypherValue::Date(DateValue::new(date)));
                }
            }
        }
        // Basic: YYYYWww
        else if s_upper.len() == 7 {
            if let Ok((y, w, d)) = parse_week_date_components(&s_upper[0..4], &s_upper[5..7], None) {
                if let Some(date) = NaiveDate::from_isoywd_opt(y, w, d) {
                    return Ok(CypherValue::Date(DateValue::new(date)));
                }
            }
        }
    }

    // Try ordinal dates: YYYY-DDD or YYYYDDD
    if (s.len() == 8 && s.chars().nth(4) == Some('-')) || (s.len() == 7 && s.chars().all(|c| c.is_ascii_digit())) {
        let (year_str, ord_str) = if s.len() == 8 && s.chars().nth(4) == Some('-') {
            (&s[0..4], &s[5..8])
        } else if s.len() == 7 {
            (&s[0..4], &s[4..7])
        } else {
            ("", "")
        };

        if !year_str.is_empty() {
            if let (Ok(year), Ok(ordinal)) = (year_str.parse::<i32>(), ord_str.parse::<u32>()) {
                if let Some(date) = NaiveDate::from_yo_opt(year, ordinal) {
                    return Ok(CypherValue::Date(DateValue::new(date)));
                }
            }
        }
    }

    // Try year only: YYYY
    if s.len() == 4 && s.chars().all(|c| c.is_ascii_digit()) {
        if let Ok(year) = s.parse::<i32>() {
            if let Some(date) = NaiveDate::from_ymd_opt(year, 1, 1) {
                return Ok(CypherValue::Date(DateValue::new(date)));
            }
        }
    }

    Err(ExecutionError::InvalidArgument(format!("Invalid date string '{}'", s)))
}

/// Helper: Parse week date components
fn parse_week_date_components(year_str: &str, week_str: &str, day_str: Option<&str>) -> Result<(i32, u32, chrono::Weekday), ()> {
    let year = year_str.parse::<i32>().map_err(|_| ())?;
    let week = week_str.parse::<u32>().map_err(|_| ())?;
    let day_num = day_str.map(|s| s.parse::<u32>()).transpose().map_err(|_| ())?.unwrap_or(1);

    let weekday = match day_num {
        1 => chrono::Weekday::Mon,
        2 => chrono::Weekday::Tue,
        3 => chrono::Weekday::Wed,
        4 => chrono::Weekday::Thu,
        5 => chrono::Weekday::Fri,
        6 => chrono::Weekday::Sat,
        7 => chrono::Weekday::Sun,
        _ => return Err(()),
    };

    Ok((year, week, weekday))
}

/// Helper: Create date from ISO week date components
fn create_date_from_week(components: &IndexMap<String, CypherValue>) -> ExecutionResult<CypherValue> {
    // Get year (required for week dates)
    let year = components.get("year")
        .and_then(|v| v.as_integer())
        .ok_or_else(|| ExecutionError::InvalidArgument("week date requires 'year'".to_string()))? as i32;

    let week = components.get("week")
        .and_then(|v| v.as_integer())
        .ok_or_else(|| ExecutionError::InvalidArgument("week date requires 'week'".to_string()))? as u32;

    // dayOfWeek defaults to 1 (Monday) per ISO 8601
    let day_of_week = get_optional_int_component(components, "dayOfWeek", 1) as u32;

    // Validate ranges
    if !(1..=53).contains(&week) {
        return Err(ExecutionError::InvalidArgument(format!("week must be 1-53, got {}", week)));
    }
    if !(1..=7).contains(&day_of_week) {
        return Err(ExecutionError::InvalidArgument(format!("dayOfWeek must be 1-7, got {}", day_of_week)));
    }

    // Use chrono's from_isoywd_opt (year, week, weekday)
    // chrono uses Weekday enum where Monday=1, so convert our 1-7 to Weekday
    let weekday = match day_of_week {
        1 => chrono::Weekday::Mon,
        2 => chrono::Weekday::Tue,
        3 => chrono::Weekday::Wed,
        4 => chrono::Weekday::Thu,
        5 => chrono::Weekday::Fri,
        6 => chrono::Weekday::Sat,
        7 => chrono::Weekday::Sun,
        _ => unreachable!(),
    };

    let date = NaiveDate::from_isoywd_opt(year, week, weekday)
        .ok_or_else(|| ExecutionError::InvalidArgument(format!(
            "Invalid ISO week date: year={}, week={}, dayOfWeek={}",
            year, week, day_of_week
        )))?;

    Ok(CypherValue::Date(DateValue::new(date)))
}

/// Helper: Create date from ordinal date components (year + day of year)
fn create_date_from_ordinal(components: &IndexMap<String, CypherValue>) -> ExecutionResult<CypherValue> {
    let year = components.get("year")
        .and_then(|v| v.as_integer())
        .ok_or_else(|| ExecutionError::InvalidArgument("ordinal date requires 'year'".to_string()))? as i32;

    let ordinal_day = components.get("ordinalDay")
        .and_then(|v| v.as_integer())
        .ok_or_else(|| ExecutionError::InvalidArgument("ordinal date requires 'ordinalDay'".to_string()))? as u32;

    let date = NaiveDate::from_yo_opt(year, ordinal_day)
        .ok_or_else(|| ExecutionError::InvalidArgument(format!(
            "Invalid ordinal date: year={}, ordinalDay={}",
            year, ordinal_day
        )))?;

    Ok(CypherValue::Date(DateValue::new(date)))
}

/// Helper: Create date from quarter components
fn create_date_from_quarter(components: &IndexMap<String, CypherValue>) -> ExecutionResult<CypherValue> {
    let year = components.get("year")
        .and_then(|v| v.as_integer())
        .ok_or_else(|| ExecutionError::InvalidArgument("quarter date requires 'year'".to_string()))? as i32;

    let quarter = components.get("quarter")
        .and_then(|v| v.as_integer())
        .ok_or_else(|| ExecutionError::InvalidArgument("quarter date requires 'quarter'".to_string()))? as u32;

    if !(1..=4).contains(&quarter) {
        return Err(ExecutionError::InvalidArgument(format!("quarter must be 1-4, got {}", quarter)));
    }

    // dayOfQuarter defaults to 1
    let day_of_quarter = get_optional_int_component(components, "dayOfQuarter", 1) as u32;

    // Calculate the month (Q1=Jan, Q2=Apr, Q3=Jul, Q4=Oct)
    let quarter_start_month = (quarter - 1) * 3 + 1;

    // Calculate the date by adding days to the quarter start
    let quarter_start = NaiveDate::from_ymd_opt(year, quarter_start_month, 1)
        .ok_or_else(|| ExecutionError::Internal("Invalid quarter start date".to_string()))?;

    let date = quarter_start + chrono::Duration::days((day_of_quarter - 1) as i64);

    // Validate it's still in the same quarter
    let result_quarter = (date.month() - 1) / 3 + 1;
    if result_quarter != quarter {
        return Err(ExecutionError::InvalidArgument(format!(
            "dayOfQuarter {} is out of range for quarter {}",
            day_of_quarter, quarter
        )));
    }

    Ok(CypherValue::Date(DateValue::new(date)))
}

/// Create a Date value
/// Supports: date(), date("2020-01-15"), date({year: 2020, month: 1, day: 15})
pub fn date(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    match args.len() {
        0 => {
            // No arguments: return today's date (cached for query consistency)
            let today = get_cached_now().date_naive();
            Ok(CypherValue::Date(DateValue::new(today)))
        }
        1 => match &args[0] {
            CypherValue::Null => Ok(CypherValue::Null),
            CypherValue::String(s) => {
                parse_date_string(s)
            }
            CypherValue::Map(components) => {
                // Check if there's a base temporal value to project from
                if let Some(base) = components.get("date") {
                    // Extract base date from temporal value
                    let base_date = match base {
                        CypherValue::Date(d) => d.date,
                        CypherValue::DateTime(dt) => dt.datetime.date_naive(),
                        CypherValue::LocalDateTime(ldt) => ldt.datetime.date(),
                        _ => return Err(ExecutionError::InvalidArgument(
                            "date parameter must be a Date, DateTime, or LocalDateTime".to_string()
                        )),
                    };

                    // Check if we need to apply week-based projection
                    if components.contains_key("week") {
                        let week = components.get("week").and_then(|v| v.as_integer()).ok_or_else(||
                            ExecutionError::InvalidArgument("week must be an integer".to_string()))? as u32;
                        // Use ISO week year from base date (can differ from calendar year at year boundaries)
                        let base_iso_year = base_date.iso_week().year() as i64;
                        let year = get_optional_int_component(components, "year", base_iso_year) as i32;
                        // Preserve dayOfWeek from base date if not specified (ISO: 1=Mon, 7=Sun)
                        let base_day_of_week = base_date.weekday().num_days_from_monday() as i64 + 1;
                        let day_of_week = get_optional_int_component(components, "dayOfWeek", base_day_of_week) as u32;

                        let weekday = match day_of_week {
                            1 => chrono::Weekday::Mon,
                            2 => chrono::Weekday::Tue,
                            3 => chrono::Weekday::Wed,
                            4 => chrono::Weekday::Thu,
                            5 => chrono::Weekday::Fri,
                            6 => chrono::Weekday::Sat,
                            7 => chrono::Weekday::Sun,
                            _ => return Err(ExecutionError::InvalidArgument(format!("dayOfWeek must be 1-7, got {}", day_of_week))),
                        };

                        let result_date = NaiveDate::from_isoywd_opt(year, week, weekday)
                            .ok_or_else(|| ExecutionError::InvalidArgument(format!(
                                "Invalid ISO week date: year={}, week={}, dayOfWeek={}",
                                year, week, day_of_week
                            )))?;
                        return Ok(CypherValue::Date(DateValue::new(result_date)));
                    }

                    // Check for ordinalDay projection
                    if components.contains_key("ordinalDay") {
                        let ordinal = components.get("ordinalDay").and_then(|v| v.as_integer()).ok_or_else(||
                            ExecutionError::InvalidArgument("ordinalDay must be an integer".to_string()))? as u32;
                        let year = get_optional_int_component(components, "year", base_date.year() as i64) as i32;

                        let result_date = NaiveDate::from_yo_opt(year, ordinal)
                            .ok_or_else(|| ExecutionError::InvalidArgument(format!(
                                "Invalid ordinal date: year={}, ordinalDay={}",
                                year, ordinal
                            )))?;
                        return Ok(CypherValue::Date(DateValue::new(result_date)));
                    }

                    // Check for quarter projection
                    if components.contains_key("quarter") {
                        let quarter = components.get("quarter").and_then(|v| v.as_integer()).ok_or_else(||
                            ExecutionError::InvalidArgument("quarter must be an integer".to_string()))? as u32;
                        let year = get_optional_int_component(components, "year", base_date.year() as i64) as i32;
                        let day = get_optional_int_component(components, "day", base_date.day() as i64) as u32;

                        // Preserve month position within quarter (0, 1, or 2)
                        let month_in_quarter = (base_date.month() - 1) % 3;
                        let quarter_start_month = (quarter - 1) * 3 + 1;
                        let target_month = quarter_start_month + month_in_quarter;

                        let result_date = NaiveDate::from_ymd_opt(year, target_month, day)
                            .ok_or_else(|| ExecutionError::InvalidArgument(format!(
                                "Invalid date: year={}, month={}, day={}", year, target_month, day
                            )))?;

                        return Ok(CypherValue::Date(DateValue::new(result_date)));
                    }

                    // Apply simple field overrides
                    let year = get_optional_int_component(components, "year", base_date.year() as i64);
                    let month = get_optional_int_component(components, "month", base_date.month() as i64) as u32;
                    let day = get_optional_int_component(components, "day", base_date.day() as i64) as u32;

                    // Use helper that supports extended year range
                    return create_date_value(year, month, day);
                }

                // No base temporal - construct from components
                // Check if this is an ISO week date (has "week" parameter)
                if components.contains_key("week") {
                    return create_date_from_week(components);
                }

                // Check if this is an ordinal date (has "ordinalDay" parameter)
                if components.contains_key("ordinalDay") {
                    return create_date_from_ordinal(components);
                }

                // Check if this is a quarter date (has "quarter" parameter)
                if components.contains_key("quarter") {
                    return create_date_from_quarter(components);
                }

                // Build from components with defaults
                // year defaults to current year if not provided (cached for query consistency)
                let current_year = get_cached_now().year();
                let year = get_optional_int_component(components, "year", current_year as i64);
                let month = get_optional_int_component(components, "month", 1) as u32;
                let day = get_optional_int_component(components, "day", 1) as u32;

                // Use helper that supports extended year range
                create_date_value(year, month, day)
            }
            CypherValue::Date(d) => {
                // Pass through Date values unchanged
                Ok(CypherValue::Date(d.clone()))
            }
            CypherValue::DateTime(dt) => {
                // Extract date from DateTime
                Ok(CypherValue::Date(DateValue::new(dt.datetime.date_naive())))
            }
            CypherValue::LocalDateTime(ldt) => {
                // Extract date from LocalDateTime
                Ok(CypherValue::Date(DateValue::new(ldt.datetime.date())))
            }
            _ => Err(ExecutionError::InvalidArgument(
                "date() requires a string, map, Date, DateTime, or LocalDateTime".to_string(),
            )),
        },
        _ => Err(ExecutionError::InvalidArgument(
            "date() takes 0 or 1 arguments".to_string(),
        )),
    }
}

/// Create a Time value (with timezone)
/// Supports: time(), time("12:45:30.123+02:00"), time({hour: 12, minute: 45, second: 30, timezone: "+02:00"})
pub fn time(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    match args.len() {
        0 => {
            // No arguments: return current time (cached for query consistency)
            let now = get_cached_now();
            let offset = FixedOffset::east_opt(0).unwrap(); // UTC
            Ok(CypherValue::Time(TimeValue::new(now.time(), offset)))
        }
        1 => match &args[0] {
            CypherValue::Null => Ok(CypherValue::Null),
            CypherValue::String(s) => {
                // Parse from string (ISO 8601: HH:MM:SS[.fff]+HH:MM or Z)
                parse_time_string(s)
            }
            CypherValue::Map(components) => {
                // Check if there's a base temporal value to project from
                let (base_time, base_offset, base_is_tz_aware) = if let Some(base) = components.get("time") {
                    match base {
                        CypherValue::Time(t) => (t.time, t.offset, true),
                        CypherValue::DateTime(dt) => (dt.datetime.time(), *dt.datetime.offset(), true),
                        CypherValue::LocalTime(lt) => (lt.time, FixedOffset::east_opt(0).unwrap(), false),
                        CypherValue::LocalDateTime(ldt) => (ldt.datetime.time(), FixedOffset::east_opt(0).unwrap(), false),
                        _ => return Err(ExecutionError::InvalidArgument(
                            "time parameter must be a Time, DateTime, LocalTime, or LocalDateTime".to_string()
                        )),
                    }
                } else {
                    (NaiveTime::from_hms_opt(0, 0, 0).unwrap(), FixedOffset::east_opt(0).unwrap(), false)
                };

                // Parse timezone if provided, otherwise use base offset
                let new_offset = if let Some(tz) = components.get("timezone") {
                    parse_timezone_offset(tz.as_string().ok_or_else(|| {
                        ExecutionError::InvalidArgument("timezone must be a string".to_string())
                    })?)?
                } else if components.contains_key("time") {
                    base_offset
                } else {
                    FixedOffset::east_opt(0).unwrap() // UTC default
                };

                // If source is timezone-aware (Time/DateTime) and timezone changes, convert the clock time
                let converted_base_time = if base_is_tz_aware && new_offset != base_offset
                    && !components.contains_key("hour")  // Only convert if hour not explicitly set
                {
                    // Convert time: source_time + (new_offset - old_offset)
                    let offset_diff_secs = new_offset.local_minus_utc() - base_offset.local_minus_utc();
                    let base_secs = base_time.num_seconds_from_midnight() as i64;
                    let new_secs = ((base_secs + offset_diff_secs as i64) % 86400 + 86400) % 86400;
                    NaiveTime::from_num_seconds_from_midnight_opt(new_secs as u32, base_time.nanosecond())
                        .unwrap_or(base_time)
                } else {
                    base_time
                };

                // Apply overrides to base time or defaults
                let hour = get_optional_int_component(components, "hour", converted_base_time.hour() as i64) as u32;
                let minute = get_optional_int_component(components, "minute", converted_base_time.minute() as i64) as u32;
                let second = get_optional_int_component(components, "second", converted_base_time.second() as i64) as u32;
                // Combine millisecond, microsecond, nanosecond into total nanoseconds
                let nanosecond = compute_total_nanoseconds(components, converted_base_time.nanosecond() as i64) as u32;

                let time = NaiveTime::from_hms_nano_opt(hour, minute, second, nanosecond)
                    .ok_or_else(|| {
                        ExecutionError::InvalidArgument(format!(
                            "Invalid time: hour={}, minute={}, second={}, nanosecond={}",
                            hour, minute, second, nanosecond
                        ))
                    })?;

                Ok(CypherValue::Time(TimeValue::new(time, new_offset)))
            }
            CypherValue::Time(t) => {
                // Pass through Time values unchanged
                Ok(CypherValue::Time(t.clone()))
            }
            CypherValue::DateTime(dt) => {
                // Extract time from DateTime
                Ok(CypherValue::Time(TimeValue::new(
                    dt.datetime.time(),
                    *dt.datetime.offset(),
                )))
            }
            CypherValue::LocalTime(lt) => {
                // Convert LocalTime to Time with UTC timezone
                Ok(CypherValue::Time(TimeValue::new(
                    lt.time,
                    FixedOffset::east_opt(0).unwrap(),
                )))
            }
            CypherValue::LocalDateTime(ldt) => {
                // Convert LocalDateTime to Time with UTC timezone
                Ok(CypherValue::Time(TimeValue::new(
                    ldt.datetime.time(),
                    FixedOffset::east_opt(0).unwrap(),
                )))
            }
            _ => Err(ExecutionError::InvalidArgument(
                "time() requires a string, map, Time, DateTime, LocalTime, or LocalDateTime".to_string(),
            )),
        },
        _ => Err(ExecutionError::InvalidArgument(
            "time() takes 0 or 1 arguments".to_string(),
        )),
    }
}

/// Create a LocalTime value (without timezone)
/// Supports: localtime(), localtime("12:45:30.123"), localtime({hour: 12, minute: 45, second: 30})
pub fn localtime(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    match args.len() {
        0 => {
            // No arguments: return current local time (cached for query consistency)
            let now = get_cached_now().time();
            Ok(CypherValue::LocalTime(LocalTimeValue::new(now)))
        }
        1 => match &args[0] {
            CypherValue::Null => Ok(CypherValue::Null),
            CypherValue::String(s) => {
                parse_localtime_string(s)
            }
            CypherValue::Map(components) => {
                // Check if there's a base temporal value to project from
                let base_time = if let Some(base) = components.get("time") {
                    match base {
                        CypherValue::LocalTime(lt) => lt.time,
                        CypherValue::Time(t) => t.time,
                        CypherValue::DateTime(dt) => dt.datetime.time(),
                        CypherValue::LocalDateTime(ldt) => ldt.datetime.time(),
                        _ => return Err(ExecutionError::InvalidArgument(
                            "time parameter must be a LocalTime, Time, DateTime, or LocalDateTime".to_string()
                        )),
                    }
                } else {
                    NaiveTime::from_hms_opt(0, 0, 0).unwrap()
                };

                // Apply overrides to base time or defaults
                let hour = get_optional_int_component(components, "hour", base_time.hour() as i64) as u32;
                let minute = get_optional_int_component(components, "minute", base_time.minute() as i64) as u32;
                let second = get_optional_int_component(components, "second", base_time.second() as i64) as u32;
                // Combine millisecond, microsecond, nanosecond into total nanoseconds
                let nanosecond = compute_total_nanoseconds(components, base_time.nanosecond() as i64) as u32;

                let time = NaiveTime::from_hms_nano_opt(hour, minute, second, nanosecond)
                    .ok_or_else(|| {
                        ExecutionError::InvalidArgument(format!(
                            "Invalid time: hour={}, minute={}, second={}, nanosecond={}",
                            hour, minute, second, nanosecond
                        ))
                    })?;

                Ok(CypherValue::LocalTime(LocalTimeValue::new(time)))
            }
            CypherValue::LocalTime(lt) => {
                // Pass through LocalTime values unchanged
                Ok(CypherValue::LocalTime(lt.clone()))
            }
            CypherValue::Time(t) => {
                // Convert Time to LocalTime (drop timezone)
                Ok(CypherValue::LocalTime(LocalTimeValue::new(t.time)))
            }
            CypherValue::DateTime(dt) => {
                // Extract local time from DateTime
                Ok(CypherValue::LocalTime(LocalTimeValue::new(
                    dt.datetime.time(),
                )))
            }
            CypherValue::LocalDateTime(ldt) => {
                // Extract time from LocalDateTime
                Ok(CypherValue::LocalTime(LocalTimeValue::new(ldt.datetime.time())))
            }
            _ => Err(ExecutionError::InvalidArgument(
                "localtime() requires a string, map, LocalTime, Time, DateTime, or LocalDateTime".to_string(),
            )),
        },
        _ => Err(ExecutionError::InvalidArgument(
            "localtime() takes 0 or 1 arguments".to_string(),
        )),
    }
}

/// Create a DateTime value (with timezone)
/// Supports: datetime(), datetime("2020-01-15T12:45:30+02:00"), datetime({year: 2020, month: 1, day: 15, hour: 12, minute: 45})
pub fn datetime(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    match args.len() {
        0 => {
            // No arguments: return current datetime (cached for query consistency)
            let now = get_cached_now();
            let with_offset = now.with_timezone(&FixedOffset::east_opt(0).unwrap());
            Ok(CypherValue::DateTime(DateTimeValue::new(with_offset)))
        }
        1 => match &args[0] {
            CypherValue::Null => Ok(CypherValue::Null),
            CypherValue::String(s) => {
                // Parse from string - try multiple formats
                parse_datetime_string(s)
            }
            CypherValue::Map(components) => {
                // Check if there's a base datetime or date to project from
                let has_base = components.contains_key("datetime") || components.contains_key("date") || components.contains_key("time");

                // Extract base date, time, offset, and timezone name from various source parameters
                let (base_date, mut base_time, mut base_offset, datetime_source_tz_name) = if let Some(base) = components.get("datetime") {
                    match base {
                        CypherValue::DateTime(dt) => (dt.datetime.date_naive(), dt.datetime.time(), *dt.datetime.offset(), dt.timezone_name.clone()),
                        CypherValue::LocalDateTime(ldt) => (ldt.datetime.date(), ldt.datetime.time(), FixedOffset::east_opt(0).unwrap(), None),
                        _ => return Err(ExecutionError::InvalidArgument(
                            "datetime parameter must be a DateTime or LocalDateTime".to_string()
                        )),
                    }
                } else if let Some(base) = components.get("date") {
                    // Support date parameter for projecting date only
                    let d = match base {
                        CypherValue::Date(d) => d.date,
                        CypherValue::DateTime(dt) => dt.datetime.date_naive(),
                        CypherValue::LocalDateTime(ldt) => ldt.datetime.date(),
                        _ => return Err(ExecutionError::InvalidArgument(
                            "date parameter must be a Date, DateTime, or LocalDateTime".to_string()
                        )),
                    };
                    // Time defaults to 00:00:00 unless a time parameter is also provided
                    (d, NaiveTime::from_hms_opt(0, 0, 0).unwrap(), FixedOffset::east_opt(0).unwrap(), None)
                } else {
                    let current_year = get_cached_now().year();
                    (NaiveDate::from_ymd_opt(current_year, 1, 1).unwrap(),
                     NaiveTime::from_hms_opt(0, 0, 0).unwrap(),
                     FixedOffset::east_opt(0).unwrap(),
                     None)
                };

                // Track named timezone from source (may be overridden by explicit timezone parameter)
                // Start with timezone from datetime source, then time parameter may override
                let mut base_timezone_name: Option<String> = datetime_source_tz_name;

                // If there's a time parameter, extract time and timezone from it
                // This overrides the base_time from above (useful for {date: d, time: t})
                if let Some(time_val) = components.get("time") {
                    match time_val {
                        CypherValue::LocalTime(lt) => {
                            base_time = lt.time;
                            // LocalTime has no timezone, keep the default UTC or any timezone override
                        }
                        CypherValue::Time(t) => {
                            base_time = t.time;
                            base_offset = t.offset;
                        }
                        CypherValue::LocalDateTime(ldt) => {
                            base_time = ldt.datetime.time();
                            // LocalDateTime has no timezone
                        }
                        CypherValue::DateTime(dt) => {
                            base_time = dt.datetime.time();
                            base_offset = *dt.datetime.offset();
                            // Preserve named timezone from the source DateTime
                            base_timezone_name = dt.timezone_name.clone();
                        }
                        _ => return Err(ExecutionError::InvalidArgument(
                            "time parameter must be a Time, LocalTime, DateTime, or LocalDateTime".to_string()
                        )),
                    }
                }

                // Determine the date part (with overrides if base exists)
                let date = if has_base && components.contains_key("week") {
                    // Week-based projection with base
                    let week = components.get("week").and_then(|v| v.as_integer()).ok_or_else(||
                        ExecutionError::InvalidArgument("week must be an integer".to_string()))? as u32;
                    // Use ISO week year from base date (can differ from calendar year at year boundaries)
                    let base_iso_year = base_date.iso_week().year() as i64;
                    let year = get_optional_int_component(components, "year", base_iso_year) as i32;
                    // Preserve dayOfWeek from base date if not specified (ISO: 1=Mon, 7=Sun)
                    let base_day_of_week = base_date.weekday().num_days_from_monday() as i64 + 1;
                    let day_of_week = get_optional_int_component(components, "dayOfWeek", base_day_of_week) as u32;

                    let weekday = match day_of_week {
                        1 => chrono::Weekday::Mon, 2 => chrono::Weekday::Tue, 3 => chrono::Weekday::Wed,
                        4 => chrono::Weekday::Thu, 5 => chrono::Weekday::Fri, 6 => chrono::Weekday::Sat,
                        7 => chrono::Weekday::Sun,
                        _ => return Err(ExecutionError::InvalidArgument(format!("dayOfWeek must be 1-7, got {}", day_of_week))),
                    };

                    NaiveDate::from_isoywd_opt(year, week, weekday).ok_or_else(||
                        ExecutionError::InvalidArgument(format!("Invalid ISO week date: year={}, week={}", year, week)))?
                } else if has_base {
                    // Simple field overrides with base (date or datetime)
                    let year = get_optional_int_component(components, "year", base_date.year() as i64) as i32;
                    let month = get_optional_int_component(components, "month", base_date.month() as i64) as u32;
                    let day = get_optional_int_component(components, "day", base_date.day() as i64) as u32;

                    NaiveDate::from_ymd_opt(year, month, day).ok_or_else(||
                        ExecutionError::InvalidArgument(format!("Invalid date: year={}, month={}, day={}", year, month, day)))?
                } else if components.contains_key("week") {
                    match create_date_from_week(components)? {
                        CypherValue::Date(d) => d.date,
                        _ => unreachable!(),
                    }
                } else if components.contains_key("ordinalDay") {
                    match create_date_from_ordinal(components)? {
                        CypherValue::Date(d) => d.date,
                        _ => unreachable!(),
                    }
                } else if components.contains_key("quarter") {
                    match create_date_from_quarter(components)? {
                        CypherValue::Date(d) => d.date,
                        _ => unreachable!(),
                    }
                } else {
                    // Build from year/month/day components (cached for query consistency)
                    let current_year = get_cached_now().year();
                    let year = get_optional_int_component(components, "year", current_year as i64) as i32;
                    let month = get_optional_int_component(components, "month", 1) as u32;
                    let day = get_optional_int_component(components, "day", 1) as u32;

                    NaiveDate::from_ymd_opt(year, month, day).ok_or_else(|| {
                        ExecutionError::InvalidArgument(format!(
                            "Invalid date: year={}, month={}, day={}",
                            year, month, day
                        ))
                    })?
                };

                // Build time components (with overrides if base exists)
                let hour = get_optional_int_component(components, "hour", base_time.hour() as i64) as u32;
                let minute = get_optional_int_component(components, "minute", base_time.minute() as i64) as u32;
                let second = get_optional_int_component(components, "second", base_time.second() as i64) as u32;
                // Combine millisecond, microsecond, nanosecond into total nanoseconds
                let nanosecond = compute_total_nanoseconds(components, base_time.nanosecond() as i64) as u32;

                let time = NaiveTime::from_hms_nano_opt(hour, minute, second, nanosecond)
                    .ok_or_else(|| {
                        ExecutionError::InvalidArgument(format!(
                            "Invalid time: hour={}, minute={}, second={}, nanosecond={}",
                            hour, minute, second, nanosecond
                        ))
                    })?;

                // Determine the target offset (timezone)
                let target_offset = if let Some(tz) = components.get("timezone") {
                    parse_timezone_offset(tz.as_string().ok_or_else(|| {
                        ExecutionError::InvalidArgument("timezone must be a string".to_string())
                    })?)?
                } else if components.contains_key("datetime") || components.contains_key("time") {
                    // Use offset from source datetime or time
                    base_offset
                } else {
                    FixedOffset::east_opt(0).unwrap() // UTC
                };

                // If the source time had a timezone (from Time or DateTime) and we're changing it,
                // we need to convert the time. Otherwise, we interpret the time as local time
                // in the target timezone.
                let time_has_source_tz = components.get("time").is_some_and(|tv| {
                    matches!(tv, CypherValue::Time(_) | CypherValue::DateTime(_))
                }) || components.get("datetime").is_some_and(|dt| {
                    matches!(dt, CypherValue::DateTime(_))
                });

                // Check if an explicit timezone override is provided
                let has_explicit_timezone = components.contains_key("timezone");

                // If the source has a named timezone and we're explicitly changing to a different timezone,
                // re-evaluate the source offset for the new date (handles DST transitions)
                let effective_source_offset = if has_explicit_timezone {
                    if let Some(ref tz_name) = base_timezone_name {
                        // Re-evaluate the named timezone for the new date
                        let probe_time = NaiveTime::from_hms_opt(12, 0, 0).unwrap();
                        let probe_dt = NaiveDateTime::new(date, probe_time);
                        if let Ok(tz) = tz_name.parse::<Tz>() {
                            match tz.from_local_datetime(&probe_dt) {
                                chrono::offset::LocalResult::Single(dt) => dt.offset().fix(),
                                chrono::offset::LocalResult::Ambiguous(dt1, _) => dt1.offset().fix(),
                                chrono::offset::LocalResult::None => base_offset,
                            }
                        } else {
                            base_offset
                        }
                    } else {
                        base_offset
                    }
                } else {
                    base_offset
                };

                // Only convert time when:
                // 1. Source has a timezone
                // 2. An explicit target timezone is provided that's different from the effective source
                let final_time = if time_has_source_tz && has_explicit_timezone && target_offset != effective_source_offset {
                    // Convert the clock time from source timezone to target timezone
                    // time_in_utc = source_time - source_offset
                    // time_in_target = time_in_utc + target_offset
                    let source_secs = time.num_seconds_from_midnight() as i64;
                    let source_offset_secs = effective_source_offset.local_minus_utc() as i64;
                    let target_offset_secs = target_offset.local_minus_utc() as i64;

                    let utc_secs = source_secs - source_offset_secs;
                    let target_secs = utc_secs + target_offset_secs;

                    // Handle day wraparound
                    let adjusted_secs = ((target_secs % 86400) + 86400) % 86400;

                    NaiveTime::from_num_seconds_from_midnight_opt(adjusted_secs as u32, time.nanosecond())
                        .unwrap_or(time)
                } else {
                    // No conversion needed - interpret time as local time in target timezone
                    time
                };

                let naive_dt = NaiveDateTime::new(date, final_time);

                // Handle named timezones (e.g., "Europe/Stockholm") vs fixed offsets ("+01:00")
                // Named timezones need to be resolved using chrono-tz to handle DST correctly
                let tz_str = components.get("timezone").and_then(|v| v.as_string());
                let (dt, named_tz) = if let Some(tz_name) = tz_str {
                    if !tz_name.starts_with('+') && !tz_name.starts_with('-') && tz_name != "Z" {
                        // Named timezone - use chrono-tz for DST-aware conversion
                        if let Ok(tz) = tz_name.parse::<Tz>() {
                            let local_result = tz.from_local_datetime(&naive_dt);
                            match local_result {
                                chrono::offset::LocalResult::Single(dt) => {
                                    // Convert to FixedOffset DateTime with the resolved offset
                                    let fixed = dt.offset().fix();
                                    let result = fixed.from_local_datetime(&naive_dt).single().ok_or_else(|| {
                                        ExecutionError::InvalidArgument(
                                            "Ambiguous or invalid datetime for timezone".to_string(),
                                        )
                                    })?;
                                    (result, Some(tz_name.to_string()))
                                }
                                chrono::offset::LocalResult::Ambiguous(dt1, _dt2) => {
                                    // During DST transition, use the earlier time
                                    let fixed = dt1.offset().fix();
                                    let result = fixed.from_local_datetime(&naive_dt).single().ok_or_else(|| {
                                        ExecutionError::InvalidArgument(
                                            "Ambiguous datetime during DST transition".to_string(),
                                        )
                                    })?;
                                    (result, Some(tz_name.to_string()))
                                }
                                chrono::offset::LocalResult::None => {
                                    return Err(ExecutionError::InvalidArgument(
                                        "Invalid datetime for timezone (DST gap)".to_string(),
                                    ));
                                }
                            }
                        } else {
                            return Err(ExecutionError::InvalidArgument(format!(
                                "Unknown timezone: {}", tz_name
                            )));
                        }
                    } else {
                        // Fixed offset timezone
                        let result = target_offset.from_local_datetime(&naive_dt).single().ok_or_else(|| {
                            ExecutionError::InvalidArgument(
                                "Ambiguous or invalid datetime for timezone".to_string(),
                            )
                        })?;
                        (result, None)
                    }
                } else {
                    // No explicit timezone - check if we should preserve base timezone name
                    if let Some(ref tz_name) = base_timezone_name {
                        // Named timezone from source - use chrono-tz for DST-aware conversion
                        if let Ok(tz) = tz_name.parse::<Tz>() {
                            let local_result = tz.from_local_datetime(&naive_dt);
                            match local_result {
                                chrono::offset::LocalResult::Single(dt) => {
                                    let fixed = dt.offset().fix();
                                    let result = fixed.from_local_datetime(&naive_dt).single().ok_or_else(|| {
                                        ExecutionError::InvalidArgument(
                                            "Ambiguous or invalid datetime for timezone".to_string(),
                                        )
                                    })?;
                                    (result, base_timezone_name.clone())
                                }
                                chrono::offset::LocalResult::Ambiguous(dt1, _dt2) => {
                                    let fixed = dt1.offset().fix();
                                    let result = fixed.from_local_datetime(&naive_dt).single().ok_or_else(|| {
                                        ExecutionError::InvalidArgument(
                                            "Ambiguous datetime during DST transition".to_string(),
                                        )
                                    })?;
                                    (result, base_timezone_name.clone())
                                }
                                chrono::offset::LocalResult::None => {
                                    return Err(ExecutionError::InvalidArgument(
                                        "Invalid datetime for timezone (DST gap)".to_string(),
                                    ));
                                }
                            }
                        } else {
                            // Fallback to fixed offset
                            let result = target_offset.from_local_datetime(&naive_dt).single().ok_or_else(|| {
                                ExecutionError::InvalidArgument(
                                    "Ambiguous or invalid datetime for timezone".to_string(),
                                )
                            })?;
                            (result, None)
                        }
                    } else {
                        let result = target_offset.from_local_datetime(&naive_dt).single().ok_or_else(|| {
                            ExecutionError::InvalidArgument(
                                "Ambiguous or invalid datetime for timezone".to_string(),
                            )
                        })?;
                        (result, None)
                    }
                };

                // Create DateTimeValue with optional named timezone
                if let Some(tz_name) = named_tz {
                    Ok(CypherValue::DateTime(DateTimeValue::with_timezone_name(dt, tz_name)))
                } else {
                    Ok(CypherValue::DateTime(DateTimeValue::new(dt)))
                }
            }
            CypherValue::DateTime(dt) => {
                // Pass through DateTime values unchanged
                Ok(CypherValue::DateTime(dt.clone()))
            }
            CypherValue::LocalDateTime(ldt) => {
                // Convert LocalDateTime to DateTime with UTC timezone
                let utc_offset = FixedOffset::east_opt(0).unwrap();
                let dt = utc_offset.from_local_datetime(&ldt.datetime).single().ok_or_else(|| {
                    ExecutionError::InvalidArgument(
                        "Ambiguous or invalid local datetime".to_string(),
                    )
                })?;
                Ok(CypherValue::DateTime(DateTimeValue::new(dt)))
            }
            _ => Err(ExecutionError::InvalidArgument(
                "datetime() requires a string, map, DateTime, or LocalDateTime".to_string(),
            )),
        },
        _ => Err(ExecutionError::InvalidArgument(
            "datetime() takes 0 or 1 arguments".to_string(),
        )),
    }
}

/// Create a LocalDateTime value (without timezone)
/// Supports: localdatetime(), localdatetime("2020-01-15T12:45:30"), localdatetime({year: 2020, month: 1, day: 15, hour: 12})
pub fn localdatetime(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    match args.len() {
        0 => {
            // No arguments: return current local datetime (cached for query consistency)
            let now = get_cached_now().naive_utc();
            Ok(CypherValue::LocalDateTime(LocalDateTimeValue::new(now)))
        }
        1 => match &args[0] {
            CypherValue::Null => Ok(CypherValue::Null),
            CypherValue::String(s) => {
                parse_localdatetime_string(s)
            }
            CypherValue::Map(components) => {
                // Check if there's a base datetime or date to project from
                let has_base = components.contains_key("datetime") || components.contains_key("date");
                let (base_date, mut base_time) = if let Some(base) = components.get("datetime") {
                    match base {
                        CypherValue::LocalDateTime(ldt) => (ldt.datetime.date(), ldt.datetime.time()),
                        CypherValue::DateTime(dt) => (dt.datetime.date_naive(), dt.datetime.time()),
                        _ => return Err(ExecutionError::InvalidArgument(
                            "datetime parameter must be a LocalDateTime or DateTime".to_string()
                        )),
                    }
                } else if let Some(base) = components.get("date") {
                    // Support date parameter for projecting date only, time defaults to 00:00:00
                    let d = match base {
                        CypherValue::Date(d) => d.date,
                        CypherValue::DateTime(dt) => dt.datetime.date_naive(),
                        CypherValue::LocalDateTime(ldt) => ldt.datetime.date(),
                        _ => return Err(ExecutionError::InvalidArgument(
                            "date parameter must be a Date, DateTime, or LocalDateTime".to_string()
                        )),
                    };
                    (d, NaiveTime::from_hms_opt(0, 0, 0).unwrap())
                } else {
                    let current_year = get_cached_now().year();
                    (NaiveDate::from_ymd_opt(current_year, 1, 1).unwrap(),
                     NaiveTime::from_hms_opt(0, 0, 0).unwrap())
                };

                // Check for a separate time key
                if let Some(time_base) = components.get("time") {
                    base_time = match time_base {
                        CypherValue::Time(t) => t.time,
                        CypherValue::LocalTime(lt) => lt.time,
                        CypherValue::DateTime(dt) => dt.datetime.time(),
                        CypherValue::LocalDateTime(ldt) => ldt.datetime.time(),
                        _ => return Err(ExecutionError::InvalidArgument(
                            "time parameter must be a Time, LocalTime, DateTime, or LocalDateTime".to_string()
                        )),
                    };
                }

                // Determine the date part (with overrides if base exists)
                let date = if has_base && components.contains_key("week") {
                    // Week-based projection with base
                    let week = components.get("week").and_then(|v| v.as_integer()).ok_or_else(||
                        ExecutionError::InvalidArgument("week must be an integer".to_string()))? as u32;
                    // Use ISO week year from base date (can differ from calendar year at year boundaries)
                    let base_iso_year = base_date.iso_week().year() as i64;
                    let year = get_optional_int_component(components, "year", base_iso_year) as i32;
                    // Preserve dayOfWeek from base date if not specified (ISO: 1=Mon, 7=Sun)
                    let base_day_of_week = base_date.weekday().num_days_from_monday() as i64 + 1;
                    let day_of_week = get_optional_int_component(components, "dayOfWeek", base_day_of_week) as u32;

                    let weekday = match day_of_week {
                        1 => chrono::Weekday::Mon, 2 => chrono::Weekday::Tue, 3 => chrono::Weekday::Wed,
                        4 => chrono::Weekday::Thu, 5 => chrono::Weekday::Fri, 6 => chrono::Weekday::Sat,
                        7 => chrono::Weekday::Sun,
                        _ => return Err(ExecutionError::InvalidArgument(format!("dayOfWeek must be 1-7, got {}", day_of_week))),
                    };

                    NaiveDate::from_isoywd_opt(year, week, weekday).ok_or_else(||
                        ExecutionError::InvalidArgument(format!("Invalid ISO week date: year={}, week={}", year, week)))?
                } else if has_base {
                    // Simple field overrides with base (date or datetime)
                    let year = get_optional_int_component(components, "year", base_date.year() as i64) as i32;
                    let month = get_optional_int_component(components, "month", base_date.month() as i64) as u32;
                    let day = get_optional_int_component(components, "day", base_date.day() as i64) as u32;

                    NaiveDate::from_ymd_opt(year, month, day).ok_or_else(||
                        ExecutionError::InvalidArgument(format!("Invalid date: year={}, month={}, day={}", year, month, day)))?
                } else if components.contains_key("week") {
                    match create_date_from_week(components)? {
                        CypherValue::Date(d) => d.date,
                        _ => unreachable!(),
                    }
                } else if components.contains_key("ordinalDay") {
                    match create_date_from_ordinal(components)? {
                        CypherValue::Date(d) => d.date,
                        _ => unreachable!(),
                    }
                } else if components.contains_key("quarter") {
                    match create_date_from_quarter(components)? {
                        CypherValue::Date(d) => d.date,
                        _ => unreachable!(),
                    }
                } else {
                    // Build from year/month/day components (cached for query consistency)
                    let current_year = get_cached_now().year();
                    let year = get_optional_int_component(components, "year", current_year as i64) as i32;
                    let month = get_optional_int_component(components, "month", 1) as u32;
                    let day = get_optional_int_component(components, "day", 1) as u32;

                    NaiveDate::from_ymd_opt(year, month, day).ok_or_else(|| {
                        ExecutionError::InvalidArgument(format!(
                            "Invalid date: year={}, month={}, day={}",
                            year, month, day
                        ))
                    })?
                };

                // Build time components (with overrides if base exists)
                let hour = get_optional_int_component(components, "hour", base_time.hour() as i64) as u32;
                let minute = get_optional_int_component(components, "minute", base_time.minute() as i64) as u32;
                let second = get_optional_int_component(components, "second", base_time.second() as i64) as u32;
                // Combine millisecond, microsecond, nanosecond into total nanoseconds
                let nanosecond = compute_total_nanoseconds(components, base_time.nanosecond() as i64) as u32;

                let time = NaiveTime::from_hms_nano_opt(hour, minute, second, nanosecond)
                    .ok_or_else(|| {
                        ExecutionError::InvalidArgument(format!(
                            "Invalid time: hour={}, minute={}, second={}, nanosecond={}",
                            hour, minute, second, nanosecond
                        ))
                    })?;

                let dt = NaiveDateTime::new(date, time);
                Ok(CypherValue::LocalDateTime(LocalDateTimeValue::new(dt)))
            }
            CypherValue::LocalDateTime(ldt) => {
                // Pass through LocalDateTime values unchanged
                Ok(CypherValue::LocalDateTime(ldt.clone()))
            }
            CypherValue::DateTime(dt) => {
                // Convert DateTime to LocalDateTime (drop timezone)
                Ok(CypherValue::LocalDateTime(LocalDateTimeValue::new(
                    dt.datetime.naive_local(),
                )))
            }
            _ => Err(ExecutionError::InvalidArgument(
                "localdatetime() requires a string, map, LocalDateTime, or DateTime".to_string(),
            )),
        },
        _ => Err(ExecutionError::InvalidArgument(
            "localdatetime() takes 0 or 1 arguments".to_string(),
        )),
    }
}

/// Create a Duration value
/// Supports: duration("P1Y2M3DT4H5M6S"), duration({years: 1, months: 2, days: 3, hours: 4, minutes: 5, seconds: 6})
/// Also supports fractional values like {days: 1.5} which becomes 1 day + 12 hours
pub fn duration(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if args.len() != 1 {
        return Err(ExecutionError::InvalidArgument(
            "duration() takes 1 argument".to_string(),
        ));
    }

    match &args[0] {
        CypherValue::Null => Ok(CypherValue::Null),
        CypherValue::String(s) => {
            // Parse ISO 8601 duration string (basic support)
            parse_duration_string(s)
        }
        CypherValue::Map(components) => {
            // Build from components, supporting fractional values
            // Fractional parts cascade down: years->months, months->days, days->hours, etc.

            let years = get_optional_float_component(components, "years", 0.0);
            let months_input = get_optional_float_component(components, "months", 0.0);
            let weeks = get_optional_float_component(components, "weeks", 0.0);
            let days_input = get_optional_float_component(components, "days", 0.0);
            let hours_input = get_optional_float_component(components, "hours", 0.0);
            let minutes_input = get_optional_float_component(components, "minutes", 0.0);
            let seconds_input = get_optional_float_component(components, "seconds", 0.0);
            let milliseconds = get_optional_float_component(components, "milliseconds", 0.0);
            let microseconds = get_optional_float_component(components, "microseconds", 0.0);
            let nanoseconds = get_optional_float_component(components, "nanoseconds", 0.0);

            // Convert years to months (1 year = 12 months)
            let total_months_f = years * 12.0 + months_input;
            let whole_months = total_months_f.trunc() as i64;
            let frac_months = total_months_f.fract();

            // Fractional months -> days (using average month length: 365.25/12 = 30.436875 days)
            let extra_days_from_months = frac_months * 30.436875;

            // Convert weeks to days and add fractional months contribution
            let total_days_f = weeks * 7.0 + days_input + extra_days_from_months;
            let whole_days = total_days_f.trunc() as i64;
            let frac_days = total_days_f.fract();

            // Fractional days -> hours (1 day = 24 hours)
            let extra_hours_from_days = frac_days * 24.0;

            // Hours with fractional day contribution
            let total_hours_f = hours_input + extra_hours_from_days;
            let whole_hours = total_hours_f.trunc() as i64;
            let frac_hours = total_hours_f.fract();

            // Fractional hours -> minutes (1 hour = 60 minutes)
            let extra_minutes_from_hours = frac_hours * 60.0;

            // Minutes with fractional hour contribution
            let total_minutes_f = minutes_input + extra_minutes_from_hours;
            let whole_minutes = total_minutes_f.trunc() as i64;
            let frac_minutes = total_minutes_f.fract();

            // Fractional minutes -> seconds (1 minute = 60 seconds)
            let extra_seconds_from_minutes = frac_minutes * 60.0;

            // Seconds with fractional minute contribution
            let total_seconds_f = seconds_input + extra_seconds_from_minutes;
            let whole_seconds = total_seconds_f.trunc() as i64;
            let frac_seconds = total_seconds_f.fract();

            // Fractional seconds -> nanoseconds
            let extra_nanos_from_seconds = frac_seconds * 1_000_000_000.0;

            // Total time components
            let total_seconds = whole_hours * 3600 + whole_minutes * 60 + whole_seconds;
            let total_nanos = (milliseconds * 1_000_000.0 + microseconds * 1_000.0 + nanoseconds + extra_nanos_from_seconds) as i32;

            Ok(CypherValue::Duration(DurationValue::new(
                whole_months,
                whole_days,
                total_seconds,
                total_nanos,
            )))
        }
        _ => Err(ExecutionError::InvalidArgument(
            "duration() requires a string or map".to_string(),
        )),
    }
}

/// Parse a timezone offset string like "+02:00", "-05:30", "Z", or named timezone like "Europe/Stockholm"
fn parse_timezone_offset(s: &str) -> ExecutionResult<FixedOffset> {
    // Handle UTC shortcuts
    if s == "Z" || s == "+00:00" || s == "-00:00" {
        return Ok(FixedOffset::east_opt(0).unwrap());
    }

    // Try to parse as a named timezone (IANA format like "Europe/Stockholm")
    if !s.starts_with('+') && !s.starts_with('-') {
        // Attempt to parse as IANA timezone name
        if let Ok(tz) = s.parse::<Tz>() {
            // Get the current offset for this timezone (cached for query consistency)
            // Note: Named timezones have variable offsets (DST), but for simplicity we use current offset
            let datetime_with_tz = get_cached_now().with_timezone(&tz);
            let fixed_offset = datetime_with_tz.offset().fix();
            return Ok(fixed_offset);
        }

        return Err(ExecutionError::InvalidArgument(format!(
            "Invalid timezone offset: {}",
            s
        )));
    }

    // Parse +HH:MM, -HH:MM, +HHMM, or -HHMM (with or without colon)
    let sign = if s.starts_with('+') {
        1
    } else if s.starts_with('-') {
        -1
    } else {
        return Err(ExecutionError::InvalidArgument(format!(
            "Invalid timezone offset: {}",
            s
        )));
    };

    let offset_str = &s[1..];

    // Try parsing with colon first (+HH:MM or +HH:MM:SS)
    if let Some(_colon_pos) = offset_str.find(':') {
        let parts: Vec<&str> = offset_str.split(':').collect();
        if parts.len() == 2 || parts.len() == 3 {
            let hours: i32 = parts[0].parse().map_err(|_| {
                ExecutionError::InvalidArgument(format!("Invalid timezone offset hours: {}", parts[0]))
            })?;

            let minutes: i32 = parts[1].parse().map_err(|_| {
                ExecutionError::InvalidArgument(format!("Invalid timezone offset minutes: {}", parts[1]))
            })?;

            // Parse seconds if present (HH:MM:SS format)
            let seconds: i32 = if parts.len() == 3 {
                parts[2].parse().map_err(|_| {
                    ExecutionError::InvalidArgument(format!("Invalid timezone offset seconds: {}", parts[2]))
                })?
            } else {
                0
            };

            let total_seconds = sign * (hours * 3600 + minutes * 60 + seconds);
            return FixedOffset::east_opt(total_seconds).ok_or_else(|| {
                ExecutionError::InvalidArgument(format!("Invalid timezone offset: {}", s))
            });
        }
    }

    // Try parsing compact format without colon (+HHMM)
    if offset_str.len() == 4 {
        let hours: i32 = offset_str[0..2].parse().map_err(|_| {
            ExecutionError::InvalidArgument(format!("Invalid timezone offset hours: {}", &offset_str[0..2]))
        })?;

        let minutes: i32 = offset_str[2..4].parse().map_err(|_| {
            ExecutionError::InvalidArgument(format!("Invalid timezone offset minutes: {}", &offset_str[2..4]))
        })?;

        let total_seconds = sign * (hours * 3600 + minutes * 60);
        return FixedOffset::east_opt(total_seconds).ok_or_else(|| {
            ExecutionError::InvalidArgument(format!("Invalid timezone offset: {}", s))
        });
    }

    // Try parsing hour-only format (+HH)
    if offset_str.len() == 2 {
        let hours: i32 = offset_str.parse().map_err(|_| {
            ExecutionError::InvalidArgument(format!("Invalid timezone offset hours: {}", offset_str))
        })?;

        let total_seconds = sign * hours * 3600;
        return FixedOffset::east_opt(total_seconds).ok_or_else(|| {
            ExecutionError::InvalidArgument(format!("Invalid timezone offset: {}", s))
        });
    }

    Err(ExecutionError::InvalidArgument(format!(
        "Invalid timezone offset format: {}",
        s
    )))
}

/// Parse a time string with optional timezone (e.g., "12:45:30.123+02:00" or "12:45:30")
fn parse_time_string(s: &str) -> ExecutionResult<CypherValue> {
    // Find timezone part
    let tz_pos = s.rfind(['+', '-', 'Z']);

    let (time_str, tz_str) = if let Some(pos) = tz_pos {
        // Check if this is actually part of the time (negative hours don't exist)
        // or if it's timezone. Heuristic: if it's position 0-2, it's likely timezone
        if pos >= 2 && s.chars().nth(pos) == Some('Z') {
            (&s[..pos], "Z")
        } else if pos >= 2 {
            (&s[..pos], &s[pos..])
        } else {
            // No valid timezone found
            (s, "+00:00")
        }
    } else {
        // No timezone specified - default to UTC (+00:00)
        (s, "+00:00")
    };

    // Parse time part using localtime parser (handles basic formats)
    let time = match parse_localtime_string(time_str)? {
        CypherValue::LocalTime(lt) => lt.time,
        _ => unreachable!(),
    };

    // Parse timezone
    let offset = parse_timezone_offset(tz_str)?;

    Ok(CypherValue::Time(TimeValue::new(time, offset)))
}

/// Parse a datetime string with flexible format support
/// Supports RFC3339, compact offsets, named timezones, and various ISO 8601 variations
fn parse_datetime_string(s: &str) -> ExecutionResult<CypherValue> {
    use chrono::DateTime;
    use chrono_tz::Tz;

    // Try RFC3339 first (standard format like "2020-01-15T12:45:30+02:00")
    if let Ok(dt) = DateTime::parse_from_rfc3339(s) {
        return Ok(CypherValue::DateTime(DateTimeValue::new(dt)));
    }

    // Check for named timezone in brackets (e.g., "2015-07-21T21:40:32.142[Europe/London]")
    if let Some(bracket_start) = s.rfind('[') {
        if s.ends_with(']') {
            let tz_name = &s[bracket_start + 1..s.len() - 1];
            let datetime_part = &s[..bracket_start];

            // Parse the timezone name
            let tz: Tz = tz_name.parse().map_err(|_| {
                ExecutionError::InvalidArgument(format!("Unknown timezone: {}", tz_name))
            })?;

            // Split datetime part by 'T'
            if let Some(t_pos) = datetime_part.find('T') {
                let date_part = &datetime_part[..t_pos];
                let mut time_part = &datetime_part[t_pos + 1..];

                // Strip any explicit offset from time_part (e.g., "23:00+02:00" -> "23:00")
                // The named timezone will determine the actual offset
                if let Some(offset_pos) = time_part.rfind(['+', '-']) {
                    // Make sure it's an offset and not part of the time
                    if offset_pos > 0 {
                        time_part = &time_part[..offset_pos];
                    }
                }

                // Parse date and time parts
                let date = match parse_date_string(date_part)? {
                    CypherValue::Date(d) => d.date,
                    _ => unreachable!(),
                };

                let time = match parse_localtime_string(time_part)? {
                    CypherValue::LocalTime(lt) => lt.time,
                    _ => unreachable!(),
                };

                // Combine into naive datetime and convert to timezone-aware
                let naive_dt = NaiveDateTime::new(date, time);
                let dt = tz.from_local_datetime(&naive_dt)
                    .single()
                    .ok_or_else(|| ExecutionError::InvalidArgument(
                        format!("Ambiguous or invalid datetime for timezone {}: {}", tz_name, s)
                    ))?;

                // Convert to FixedOffset for our DateTimeValue, preserving timezone name
                let fixed_dt = dt.with_timezone(&dt.offset().fix());
                return Ok(CypherValue::DateTime(DateTimeValue::with_timezone_name(fixed_dt, tz_name.to_string())));
            }
        }
    }

    // Find timezone part (Z, +HH:MM, -HH:MM, +HHMM, +HH, etc.)
    let tz_pos = s.rfind(['Z', '+', '-'])
        .filter(|&pos| pos > 4); // Must be after date part

    let (datetime_part, tz_str): (&str, String) = if let Some(pos) = tz_pos {
        if s.chars().nth(pos) == Some('Z') {
            (&s[..pos], "+00:00".to_string())
        } else {
            let tz = &s[pos..];
            // Normalize compact timezone formats
            let normalized_tz = normalize_timezone(tz)?;
            (&s[..pos], normalized_tz)
        }
    } else {
        return Err(ExecutionError::InvalidArgument(format!(
            "Invalid datetime string '{}': missing timezone",
            s
        )));
    };

    // Split datetime part by 'T'
    if let Some(t_pos) = datetime_part.find('T') {
        let date_part = &datetime_part[..t_pos];
        let time_part = &datetime_part[t_pos + 1..];

        // Parse date and time parts using existing parsers
        let date = match parse_date_string(date_part)? {
            CypherValue::Date(d) => d.date,
            _ => unreachable!(),
        };

        let time = match parse_localtime_string(time_part)? {
            CypherValue::LocalTime(lt) => lt.time,
            _ => unreachable!(),
        };

        // Combine and apply timezone
        let naive_dt = NaiveDateTime::new(date, time);
        let offset = parse_timezone_offset(&tz_str)?;

        let dt = offset.from_local_datetime(&naive_dt).single()
            .ok_or_else(|| ExecutionError::InvalidArgument(
                format!("Ambiguous datetime for timezone: {}", s)
            ))?;

        return Ok(CypherValue::DateTime(DateTimeValue::new(dt)));
    }

    Err(ExecutionError::InvalidArgument(format!(
        "Invalid datetime string '{}': must contain 'T' separator",
        s
    )))
}

/// Helper: Normalize timezone string to standard format (+HH:MM)
/// Also strips optional [Zone] suffix (e.g., "+02:00[Europe/Stockholm]" -> "+02:00")
fn normalize_timezone(tz: &str) -> ExecutionResult<String> {
    if tz.is_empty() {
        return Ok("+00:00".to_string());
    }

    // Strip optional [Zone] suffix (e.g., "[Europe/Stockholm]")
    let tz_clean = if let Some(bracket_pos) = tz.find('[') {
        &tz[..bracket_pos]
    } else {
        tz
    };

    if tz_clean.is_empty() {
        return Ok("+00:00".to_string());
    }

    let sign = &tz_clean[0..1];
    let offset_part = &tz_clean[1..];

    // Already in standard format: +HH:MM
    if offset_part.contains(':') {
        return Ok(tz_clean.to_string());
    }

    // Compact formats: +HHMM, +HH
    if offset_part.len() == 4 && offset_part.chars().all(|c| c.is_ascii_digit()) {
        // +0100 -> +01:00
        return Ok(format!("{}{}:{}", sign, &offset_part[0..2], &offset_part[2..4]));
    }
    if offset_part.len() == 2 && offset_part.chars().all(|c| c.is_ascii_digit()) {
        // +01 -> +01:00
        return Ok(format!("{}{}:00", sign, offset_part));
    }

    Ok(tz_clean.to_string())
}

/// Parse an ISO 8601 duration string
/// Supports:
/// - P[n]Y[n]M[n]DT[n]H[n]M[n]S format (e.g., "P12Y5M14DT16H12M70S")
/// - Date-like format (e.g., "P2012-02-02T14:37:21.545")
fn parse_duration_string(s: &str) -> ExecutionResult<CypherValue> {
    if !s.starts_with('P') {
        return Err(ExecutionError::InvalidArgument(
            "Duration string must start with 'P'".to_string(),
        ));
    }

    // Use floats to accumulate values, then convert at the end
    let mut months_f: f64 = 0.0;
    let mut days_f: f64 = 0.0;
    let mut hours_f: f64 = 0.0;
    let mut minutes_f: f64 = 0.0;
    let mut seconds_f: f64 = 0.0;

    let s = &s[1..]; // Remove 'P'
    let parts: Vec<&str> = s.split('T').collect();

    // Check if this is a date-like format (contains dashes in date part)
    // e.g., "2012-02-02" instead of "12Y5M14D"
    let date_part = parts.first().unwrap_or(&"");
    let is_date_format = date_part.contains('-') && !date_part.contains('Y') && !date_part.contains('D');

    // Parse date part (before T)
    if !parts.is_empty() && !parts[0].is_empty() {
        if is_date_format {
            // Date-like format: YYYY-MM-DD or similar
            let date_components: Vec<&str> = parts[0].split('-').collect();
            if !date_components.is_empty() {
                if let Ok(years) = date_components[0].parse::<f64>() {
                    months_f += years * 12.0;
                }
            }
            if date_components.len() >= 2 {
                if let Ok(months) = date_components[1].parse::<f64>() {
                    months_f += months;
                }
            }
            if date_components.len() >= 3 {
                if let Ok(days) = date_components[2].parse::<f64>() {
                    days_f += days;
                }
            }
        } else {
            // Standard format with Y, M, W, D designators
            let mut num_str = String::new();
            for ch in parts[0].chars() {
                if ch.is_ascii_digit() || ch == '.' || ch == '-' {
                    num_str.push(ch);
                } else if !num_str.is_empty() {
                    let num: f64 = num_str.parse().unwrap_or(0.0);
                    match ch {
                        'Y' => months_f += num * 12.0,
                        'M' => months_f += num,
                        'W' => days_f += num * 7.0,
                        'D' => days_f += num,
                        _ => {}
                    }
                    num_str.clear();
                }
            }
        }
    }

    // Parse time part (after T)
    if parts.len() > 1 && !parts[1].is_empty() {
        let time_part = parts[1];
        // Check if this is time-like format (contains colons)
        let is_time_format = time_part.contains(':') && !time_part.contains('H') && !time_part.contains('S');

        if is_time_format {
            // Time-like format: HH:MM:SS.sss or similar
            let time_components: Vec<&str> = time_part.split(':').collect();
            if !time_components.is_empty() {
                if let Ok(h) = time_components[0].parse::<f64>() {
                    hours_f += h;
                }
            }
            if time_components.len() >= 2 {
                if let Ok(m) = time_components[1].parse::<f64>() {
                    minutes_f += m;
                }
            }
            if time_components.len() >= 3 {
                if let Ok(sec) = time_components[2].parse::<f64>() {
                    seconds_f += sec;
                }
            }
        } else {
            // Standard format with H, M, S designators
            let mut num_str = String::new();
            for ch in time_part.chars() {
                if ch.is_ascii_digit() || ch == '.' || ch == '-' {
                    num_str.push(ch);
                } else if !num_str.is_empty() {
                    let num: f64 = num_str.parse().unwrap_or(0.0);
                    match ch {
                        'H' => hours_f += num,
                        'M' => minutes_f += num,
                        'S' => seconds_f += num,
                        _ => {}
                    }
                    num_str.clear();
                }
            }
        }
    }

    // Now cascade fractional parts down the hierarchy
    // Fractional months -> days (using average month length: 365.25/12 = 30.436875 days)
    let whole_months = months_f.trunc() as i64;
    days_f += months_f.fract() * 30.436875;

    // Fractional days -> hours (1 day = 24 hours)
    let whole_days = days_f.trunc() as i64;
    hours_f += days_f.fract() * 24.0;

    // Fractional hours -> minutes (1 hour = 60 minutes)
    let whole_hours = hours_f.trunc() as i64;
    minutes_f += hours_f.fract() * 60.0;

    // Fractional minutes -> seconds (1 minute = 60 seconds)
    let whole_minutes = minutes_f.trunc() as i64;
    seconds_f += minutes_f.fract() * 60.0;

    // Fractional seconds -> nanoseconds
    // Use rounding to handle floating-point precision issues
    let whole_seconds = seconds_f.trunc() as i64;
    let nanos = (seconds_f.fract() * 1_000_000_000.0).round() as i32;

    // Total seconds from time components
    let total_seconds = whole_hours * 3600 + whole_minutes * 60 + whole_seconds;

    Ok(CypherValue::Duration(DurationValue::new(
        whole_months, whole_days, total_seconds, nanos,
    )))
}

/// Helper: Truncate year to millennium (e.g., 2017 -> 2000, 1984 -> 1000)
fn truncate_to_millennium(year: i32) -> i32 {
    (year / 1000) * 1000
}

/// Helper: Truncate year to century (e.g., 2017 -> 2000, 1984 -> 1900)
fn truncate_to_century(year: i32) -> i32 {
    (year / 100) * 100
}

/// Helper: Truncate year to decade (e.g., 1984 -> 1980, 2017 -> 2010)
fn truncate_to_decade(year: i32) -> i32 {
    (year / 10) * 10
}

/// Helper: Get the quarter month for a given month (1->1, 4->4, 7->7, 10->10, 11->10)
fn get_quarter_month(month: u32) -> u32 {
    ((month - 1) / 3) * 3 + 1
}

/// Helper: Get the ISO week year and week start date
/// Returns the Monday of week 1 of the ISO week year for the given date
fn get_iso_week_year_start(date: &NaiveDate) -> NaiveDate {
    // ISO 8601: Week 1 is the week with the year's first Thursday
    // Find the Monday of the week containing this date
    let iso_week = date.iso_week();
    let year = iso_week.year();

    // Find January 4th of the year (always in week 1)
    let jan4 = NaiveDate::from_ymd_opt(year, 1, 4).unwrap();

    // Find the Monday of that week
    let days_from_monday = jan4.weekday().num_days_from_monday();
    jan4 - chrono::Duration::days(days_from_monday as i64)
}

/// Helper: Get the Monday of the week containing the given date
fn get_week_start(date: &NaiveDate) -> NaiveDate {
    let days_from_monday = date.weekday().num_days_from_monday();
    *date - chrono::Duration::days(days_from_monday as i64)
}

/// Helper: Apply component overrides from a map to a date
fn apply_date_overrides(mut date: NaiveDate, map: &IndexMap<String, CypherValue>) -> ExecutionResult<NaiveDate> {
    if map.is_empty() {
        return Ok(date);
    }

    let mut year = date.year();
    let mut month = date.month();
    let mut day = date.day();

    if let Some(v) = map.get("year") {
        year = v.as_integer().ok_or_else(|| ExecutionError::InvalidArgument("year must be integer".to_string()))? as i32;
    }
    if let Some(v) = map.get("month") {
        month = v.as_integer().ok_or_else(|| ExecutionError::InvalidArgument("month must be integer".to_string()))? as u32;
    }
    if let Some(v) = map.get("day") {
        day = v.as_integer().ok_or_else(|| ExecutionError::InvalidArgument("day must be integer".to_string()))? as u32;
    }

    // For week operations, handle dayOfWeek
    if let Some(v) = map.get("dayOfWeek") {
        let target_day = v.as_integer().ok_or_else(|| ExecutionError::InvalidArgument("dayOfWeek must be integer".to_string()))? as u32;
        // dayOfWeek: 1=Monday, 7=Sunday (ISO 8601)
        if !(1..=7).contains(&target_day) {
            return Err(ExecutionError::InvalidArgument("dayOfWeek must be 1-7".to_string()));
        }
        // Current day is Monday (after truncation to week)
        let current_weekday = date.weekday().num_days_from_monday() + 1;
        let offset = (target_day as i32) - (current_weekday as i32);
        date += chrono::Duration::days(offset as i64);
        return Ok(date);
    }

    NaiveDate::from_ymd_opt(year, month, day)
        .ok_or_else(|| ExecutionError::InvalidArgument(format!("Invalid date: {}-{}-{}", year, month, day)))
}

/// Helper: Apply component overrides to a datetime (just time parts, no timezone)
fn apply_datetime_overrides(datetime: NaiveDateTime, map: &IndexMap<String, CypherValue>) -> ExecutionResult<NaiveDateTime> {
    if map.is_empty() {
        return Ok(datetime);
    }

    let date = apply_date_overrides(datetime.date(), map)?;

    let mut hour = datetime.hour();
    let mut minute = datetime.minute();
    let mut second = datetime.second();
    let mut nanosecond = datetime.nanosecond();

    if let Some(v) = map.get("hour") {
        hour = v.as_integer().ok_or_else(|| ExecutionError::InvalidArgument("hour must be integer".to_string()))? as u32;
    }
    if let Some(v) = map.get("minute") {
        minute = v.as_integer().ok_or_else(|| ExecutionError::InvalidArgument("minute must be integer".to_string()))? as u32;
    }
    if let Some(v) = map.get("second") {
        second = v.as_integer().ok_or_else(|| ExecutionError::InvalidArgument("second must be integer".to_string()))? as u32;
    }
    if let Some(v) = map.get("nanosecond") {
        // For truncation override, ADD the nanosecond value to the truncated portion
        // e.g., truncate to millisecond gives 645,000,000, override nanosecond=2 gives 645,000,002
        let override_ns = v.as_integer().ok_or_else(|| ExecutionError::InvalidArgument("nanosecond must be integer".to_string()))? as u32;
        nanosecond += override_ns;
    }

    let time = NaiveTime::from_hms_nano_opt(hour, minute, second, nanosecond)
        .ok_or_else(|| ExecutionError::InvalidArgument(format!("Invalid time: {}:{}:{}.{}", hour, minute, second, nanosecond)))?;

    Ok(NaiveDateTime::new(date, time))
}

/// Helper: Apply component overrides to a time
fn apply_time_overrides(time: NaiveTime, map: &IndexMap<String, CypherValue>) -> ExecutionResult<NaiveTime> {
    if map.is_empty() {
        return Ok(time);
    }

    let mut hour = time.hour();
    let mut minute = time.minute();
    let mut second = time.second();
    let mut nanosecond = time.nanosecond();

    if let Some(v) = map.get("hour") {
        hour = v.as_integer().ok_or_else(|| ExecutionError::InvalidArgument("hour must be integer".to_string()))? as u32;
    }
    if let Some(v) = map.get("minute") {
        minute = v.as_integer().ok_or_else(|| ExecutionError::InvalidArgument("minute must be integer".to_string()))? as u32;
    }
    if let Some(v) = map.get("second") {
        second = v.as_integer().ok_or_else(|| ExecutionError::InvalidArgument("second must be integer".to_string()))? as u32;
    }
    if let Some(v) = map.get("nanosecond") {
        // For truncation override, ADD the nanosecond value to the truncated portion
        let override_ns = v.as_integer().ok_or_else(|| ExecutionError::InvalidArgument("nanosecond must be integer".to_string()))? as u32;
        nanosecond += override_ns;
    }

    NaiveTime::from_hms_nano_opt(hour, minute, second, nanosecond)
        .ok_or_else(|| ExecutionError::InvalidArgument(format!("Invalid time: {}:{}:{}.{}", hour, minute, second, nanosecond)))
}

/// Truncate a Date value
fn truncate_date(unit: &str, date: &NaiveDate, override_map: &IndexMap<String, CypherValue>) -> ExecutionResult<CypherValue> {
    let truncated = match unit.to_lowercase().as_str() {
        "millennium" => {
            let year = truncate_to_millennium(date.year());
            NaiveDate::from_ymd_opt(year, 1, 1).unwrap()
        }
        "century" => {
            let year = truncate_to_century(date.year());
            NaiveDate::from_ymd_opt(year, 1, 1).unwrap()
        }
        "decade" => {
            let year = truncate_to_decade(date.year());
            NaiveDate::from_ymd_opt(year, 1, 1).unwrap()
        }
        "year" => NaiveDate::from_ymd_opt(date.year(), 1, 1).unwrap(),
        "weekyear" => get_iso_week_year_start(date),
        "quarter" => {
            let month = get_quarter_month(date.month());
            NaiveDate::from_ymd_opt(date.year(), month, 1).unwrap()
        }
        "month" => NaiveDate::from_ymd_opt(date.year(), date.month(), 1).unwrap(),
        "week" => get_week_start(date),
        "day" => *date,
        _ => {
            return Err(ExecutionError::InvalidArgument(format!(
                "Invalid truncate unit '{}' for date",
                unit
            )))
        }
    };

    let result = apply_date_overrides(truncated, override_map)?;
    Ok(CypherValue::Date(DateValue::new(result)))
}

/// Truncate a DateTime value - always returns DateTime
fn truncate_datetime(unit: &str, dt: &DateTimeValue, override_map: &IndexMap<String, CypherValue>) -> ExecutionResult<CypherValue> {
    let naive = dt.datetime.naive_local();
    let date = naive.date();

    let unit_lower = unit.to_lowercase();

    // Check if this is a date-level truncation unit
    let is_date_level = matches!(
        unit_lower.as_str(),
        "millennium" | "century" | "decade" | "year" | "weekyear" | "quarter" | "month" | "week" | "day"
    );

    // For date-level truncation, return DateTime with time 00:00:00
    if is_date_level {
        let truncated_date = match unit_lower.as_str() {
            "millennium" => {
                let year = truncate_to_millennium(date.year());
                NaiveDate::from_ymd_opt(year, 1, 1).unwrap()
            }
            "century" => {
                let year = truncate_to_century(date.year());
                NaiveDate::from_ymd_opt(year, 1, 1).unwrap()
            }
            "decade" => {
                let year = truncate_to_decade(date.year());
                NaiveDate::from_ymd_opt(year, 1, 1).unwrap()
            }
            "year" => NaiveDate::from_ymd_opt(date.year(), 1, 1).unwrap(),
            "weekyear" => get_iso_week_year_start(&date),
            "quarter" => {
                let month = get_quarter_month(date.month());
                NaiveDate::from_ymd_opt(date.year(), month, 1).unwrap()
            }
            "month" => NaiveDate::from_ymd_opt(date.year(), date.month(), 1).unwrap(),
            "week" => get_week_start(&date),
            "day" => date,
            _ => unreachable!(),
        };

        // Apply date overrides first
        let result_date = apply_date_overrides(truncated_date, override_map)?;

        // Create base time 00:00:00, then apply time overrides (nanosecond, etc.)
        let base_time = NaiveTime::from_hms_opt(0, 0, 0).unwrap();
        let result_time = apply_time_overrides(base_time, override_map)?;
        let truncated_naive = NaiveDateTime::new(result_date, result_time);

        // Determine the timezone: from override map or keep original
        let (timezone, named_tz) = if let Some(tz_val) = override_map.get("timezone") {
            let tz_str = tz_val.as_string().ok_or_else(|| {
                ExecutionError::InvalidArgument("timezone must be a string".to_string())
            })?;
            // Check if it's a named timezone
            if !tz_str.starts_with('+') && !tz_str.starts_with('-') && tz_str != "Z" {
                // Named timezone - use chrono-tz for DST-aware conversion
                if let Ok(tz) = tz_str.parse::<Tz>() {
                    let local_result = tz.from_local_datetime(&truncated_naive);
                    match local_result {
                        chrono::offset::LocalResult::Single(dt) => {
                            (dt.offset().fix(), Some(tz_str.to_string()))
                        }
                        chrono::offset::LocalResult::Ambiguous(dt1, _) => {
                            (dt1.offset().fix(), Some(tz_str.to_string()))
                        }
                        chrono::offset::LocalResult::None => {
                            return Err(ExecutionError::InvalidArgument(
                                "Invalid datetime for timezone (DST gap)".to_string()
                            ));
                        }
                    }
                } else {
                    return Err(ExecutionError::InvalidArgument(format!(
                        "Unknown timezone: {}", tz_str
                    )));
                }
            } else {
                (parse_timezone_offset(tz_str)?, None)
            }
        } else {
            // Preserve original timezone name if present
            (*dt.datetime.offset(), dt.timezone_name.clone())
        };

        // Create DateTime with the appropriate timezone
        let result_dt = timezone.from_local_datetime(&truncated_naive).single()
            .ok_or_else(|| ExecutionError::InvalidArgument("Invalid datetime for timezone".to_string()))?;
        if let Some(tz_name) = named_tz {
            return Ok(CypherValue::DateTime(DateTimeValue::with_timezone_name(result_dt, tz_name)));
        } else {
            return Ok(CypherValue::DateTime(DateTimeValue::new(result_dt)));
        }
    }

    // For time-level truncation, return a DateTime value
    let truncated_naive = match unit_lower.as_str() {
        "hour" => NaiveDateTime::new(date, NaiveTime::from_hms_opt(naive.hour(), 0, 0).unwrap()),
        "minute" => NaiveDateTime::new(date, NaiveTime::from_hms_opt(naive.hour(), naive.minute(), 0).unwrap()),
        "second" => NaiveDateTime::new(date, NaiveTime::from_hms_opt(naive.hour(), naive.minute(), naive.second()).unwrap()),
        "millisecond" => {
            let millis = (naive.nanosecond() / 1_000_000) * 1_000_000;
            NaiveDateTime::new(date, NaiveTime::from_hms_nano_opt(naive.hour(), naive.minute(), naive.second(), millis).unwrap())
        }
        "microsecond" => {
            let micros = (naive.nanosecond() / 1_000) * 1_000;
            NaiveDateTime::new(date, NaiveTime::from_hms_nano_opt(naive.hour(), naive.minute(), naive.second(), micros).unwrap())
        }
        _ => {
            return Err(ExecutionError::InvalidArgument(format!(
                "Invalid truncate unit '{}' for datetime",
                unit
            )))
        }
    };

    // Apply overrides, including potential timezone change
    let result_naive = apply_datetime_overrides(truncated_naive, override_map)?;

    // Determine the timezone: from override map or keep original
    let (timezone, named_tz) = if let Some(tz_val) = override_map.get("timezone") {
        let tz_str = tz_val.as_string().ok_or_else(|| {
            ExecutionError::InvalidArgument("timezone must be a string".to_string())
        })?;
        // Check if it's a named timezone
        if !tz_str.starts_with('+') && !tz_str.starts_with('-') && tz_str != "Z" {
            if let Ok(tz) = tz_str.parse::<Tz>() {
                let local_result = tz.from_local_datetime(&result_naive);
                match local_result {
                    chrono::offset::LocalResult::Single(dt_result) => {
                        (dt_result.offset().fix(), Some(tz_str.to_string()))
                    }
                    chrono::offset::LocalResult::Ambiguous(dt1, _) => {
                        (dt1.offset().fix(), Some(tz_str.to_string()))
                    }
                    chrono::offset::LocalResult::None => {
                        return Err(ExecutionError::InvalidArgument(
                            "Invalid datetime for timezone (DST gap)".to_string()
                        ));
                    }
                }
            } else {
                return Err(ExecutionError::InvalidArgument(format!(
                    "Unknown timezone: {}", tz_str
                )));
            }
        } else {
            (parse_timezone_offset(tz_str)?, None)
        }
    } else {
        (*dt.datetime.offset(), dt.timezone_name.clone())
    };

    let result_dt = timezone.from_local_datetime(&result_naive).single()
        .ok_or_else(|| ExecutionError::InvalidArgument("Invalid datetime for timezone".to_string()))?;

    if let Some(tz_name) = named_tz {
        Ok(CypherValue::DateTime(DateTimeValue::with_timezone_name(result_dt, tz_name)))
    } else {
        Ok(CypherValue::DateTime(DateTimeValue::new(result_dt)))
    }
}

/// Truncate a LocalDateTime value - always returns LocalDateTime
fn truncate_localdatetime(unit: &str, ldt: &LocalDateTimeValue, override_map: &IndexMap<String, CypherValue>) -> ExecutionResult<CypherValue> {
    let date = ldt.datetime.date();

    let unit_lower = unit.to_lowercase();

    // Check if this is a date-level truncation unit
    let is_date_level = matches!(
        unit_lower.as_str(),
        "millennium" | "century" | "decade" | "year" | "weekyear" | "quarter" | "month" | "week" | "day"
    );

    // For date-level truncation, return LocalDateTime with time 00:00:00
    if is_date_level {
        let truncated_date = match unit_lower.as_str() {
            "millennium" => {
                let year = truncate_to_millennium(date.year());
                NaiveDate::from_ymd_opt(year, 1, 1).unwrap()
            }
            "century" => {
                let year = truncate_to_century(date.year());
                NaiveDate::from_ymd_opt(year, 1, 1).unwrap()
            }
            "decade" => {
                let year = truncate_to_decade(date.year());
                NaiveDate::from_ymd_opt(year, 1, 1).unwrap()
            }
            "year" => NaiveDate::from_ymd_opt(date.year(), 1, 1).unwrap(),
            "weekyear" => get_iso_week_year_start(&date),
            "quarter" => {
                let month = get_quarter_month(date.month());
                NaiveDate::from_ymd_opt(date.year(), month, 1).unwrap()
            }
            "month" => NaiveDate::from_ymd_opt(date.year(), date.month(), 1).unwrap(),
            "week" => get_week_start(&date),
            "day" => date,
            _ => unreachable!(),
        };

        // Apply date overrides
        let result_date = apply_date_overrides(truncated_date, override_map)?;

        // Create base time 00:00:00, then apply time overrides (nanosecond, etc.)
        let base_time = NaiveTime::from_hms_opt(0, 0, 0).unwrap();
        let result_time = apply_time_overrides(base_time, override_map)?;
        let result_ldt = NaiveDateTime::new(result_date, result_time);
        return Ok(CypherValue::LocalDateTime(LocalDateTimeValue::new(result_ldt)));
    }

    // For time-level truncation, return a LocalDateTime value
    let truncated = match unit_lower.as_str() {
        "hour" => NaiveDateTime::new(date, NaiveTime::from_hms_opt(ldt.datetime.hour(), 0, 0).unwrap()),
        "minute" => NaiveDateTime::new(date, NaiveTime::from_hms_opt(ldt.datetime.hour(), ldt.datetime.minute(), 0).unwrap()),
        "second" => NaiveDateTime::new(date, NaiveTime::from_hms_opt(ldt.datetime.hour(), ldt.datetime.minute(), ldt.datetime.second()).unwrap()),
        "millisecond" => {
            let millis = (ldt.datetime.nanosecond() / 1_000_000) * 1_000_000;
            NaiveDateTime::new(date, NaiveTime::from_hms_nano_opt(ldt.datetime.hour(), ldt.datetime.minute(), ldt.datetime.second(), millis).unwrap())
        }
        "microsecond" => {
            let micros = (ldt.datetime.nanosecond() / 1_000) * 1_000;
            NaiveDateTime::new(date, NaiveTime::from_hms_nano_opt(ldt.datetime.hour(), ldt.datetime.minute(), ldt.datetime.second(), micros).unwrap())
        }
        _ => {
            return Err(ExecutionError::InvalidArgument(format!(
                "Invalid truncate unit '{}' for localdatetime",
                unit
            )))
        }
    };

    let result = apply_datetime_overrides(truncated, override_map)?;
    Ok(CypherValue::LocalDateTime(LocalDateTimeValue::new(result)))
}

/// Truncate a Time value
fn truncate_time(unit: &str, t: &TimeValue, override_map: &IndexMap<String, CypherValue>) -> ExecutionResult<CypherValue> {
    let truncated = match unit.to_lowercase().as_str() {
        "day" => NaiveTime::from_hms_opt(0, 0, 0).unwrap(), // Truncate to midnight
        "hour" => NaiveTime::from_hms_opt(t.time.hour(), 0, 0).unwrap(),
        "minute" => NaiveTime::from_hms_opt(t.time.hour(), t.time.minute(), 0).unwrap(),
        "second" => NaiveTime::from_hms_opt(t.time.hour(), t.time.minute(), t.time.second()).unwrap(),
        "millisecond" => {
            let millis = (t.time.nanosecond() / 1_000_000) * 1_000_000;
            NaiveTime::from_hms_nano_opt(t.time.hour(), t.time.minute(), t.time.second(), millis).unwrap()
        }
        "microsecond" => {
            let micros = (t.time.nanosecond() / 1_000) * 1_000;
            NaiveTime::from_hms_nano_opt(t.time.hour(), t.time.minute(), t.time.second(), micros).unwrap()
        }
        _ => {
            return Err(ExecutionError::InvalidArgument(format!(
                "Invalid truncate unit '{}' for time",
                unit
            )))
        }
    };

    let result = apply_time_overrides(truncated, override_map)?;
    Ok(CypherValue::Time(TimeValue::new(result, t.offset)))
}

/// Truncate a LocalTime value
fn truncate_localtime(unit: &str, lt: &LocalTimeValue, override_map: &IndexMap<String, CypherValue>) -> ExecutionResult<CypherValue> {
    let truncated = match unit.to_lowercase().as_str() {
        "day" => NaiveTime::from_hms_opt(0, 0, 0).unwrap(), // Truncate to midnight
        "hour" => NaiveTime::from_hms_opt(lt.time.hour(), 0, 0).unwrap(),
        "minute" => NaiveTime::from_hms_opt(lt.time.hour(), lt.time.minute(), 0).unwrap(),
        "second" => NaiveTime::from_hms_opt(lt.time.hour(), lt.time.minute(), lt.time.second()).unwrap(),
        "millisecond" => {
            let millis = (lt.time.nanosecond() / 1_000_000) * 1_000_000;
            NaiveTime::from_hms_nano_opt(lt.time.hour(), lt.time.minute(), lt.time.second(), millis).unwrap()
        }
        "microsecond" => {
            let micros = (lt.time.nanosecond() / 1_000) * 1_000;
            NaiveTime::from_hms_nano_opt(lt.time.hour(), lt.time.minute(), lt.time.second(), micros).unwrap()
        }
        _ => {
            return Err(ExecutionError::InvalidArgument(format!(
                "Invalid truncate unit '{}' for localtime",
                unit
            )))
        }
    };

    let result = apply_time_overrides(truncated, override_map)?;
    Ok(CypherValue::LocalTime(LocalTimeValue::new(result)))
}

/// Truncate a temporal value to a specified unit
/// Supports: truncate("day", datetime), truncate("day", datetime, {hour: 5}), etc.
/// Wrapper for date.truncate() - always returns Date
pub fn truncate_for_date(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if args.len() < 2 || args.len() > 3 {
        return Err(ExecutionError::InvalidArgument(
            "date.truncate() requires 2 or 3 arguments".to_string(),
        ));
    }

    let unit = args[0].as_string().ok_or_else(|| {
        ExecutionError::InvalidArgument("First argument must be a string unit".to_string())
    })?;

    let override_map = if args.len() == 3 {
        args[2].as_map().ok_or_else(|| {
            ExecutionError::InvalidArgument("Third argument must be a map".to_string())
        })?
    } else {
        &IndexMap::new()
    };

    // Extract date from any temporal type
    let date = match &args[1] {
        CypherValue::Date(d) => d.date,
        CypherValue::DateTime(dt) => dt.datetime.date_naive(),
        CypherValue::LocalDateTime(ldt) => ldt.datetime.date(),
        CypherValue::Null => return Ok(CypherValue::Null),
        other => return Err(ExecutionError::InvalidArgument(format!(
            "date.truncate() requires a temporal value, got {}",
            other.type_name()
        ))),
    };

    truncate_date(unit, &date, override_map)
}

/// Wrapper for datetime.truncate() - always returns DateTime
pub fn truncate_for_datetime(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if args.len() < 2 || args.len() > 3 {
        return Err(ExecutionError::InvalidArgument(
            "datetime.truncate() requires 2 or 3 arguments".to_string(),
        ));
    }

    let unit = args[0].as_string().ok_or_else(|| {
        ExecutionError::InvalidArgument("First argument must be a string unit".to_string())
    })?;

    let override_map = if args.len() == 3 {
        args[2].as_map().ok_or_else(|| {
            ExecutionError::InvalidArgument("Third argument must be a map".to_string())
        })?
    } else {
        &IndexMap::new()
    };

    match &args[1] {
        CypherValue::Null => Ok(CypherValue::Null),
        CypherValue::DateTime(dt) => truncate_datetime(unit, dt, override_map),
        // For Date/LocalDateTime input, convert to DateTime first
        CypherValue::Date(d) => {
            // Truncate the date part
            let truncated_result = truncate_date(unit, &d.date, override_map)?;
            let truncated_date = match truncated_result {
                CypherValue::Date(d) => d.date,
                _ => unreachable!(),
            };

            // Create base time 00:00:00, then apply time overrides (nanosecond, etc.)
            let base_time = NaiveTime::from_hms_opt(0, 0, 0).unwrap();
            let result_time = apply_time_overrides(base_time, override_map)?;
            let dt_naive = NaiveDateTime::new(truncated_date, result_time);

            // Determine timezone (handle named timezones)
            let (timezone, named_tz) = if let Some(tz_val) = override_map.get("timezone") {
                let tz_str = tz_val.as_string().ok_or_else(||
                    ExecutionError::InvalidArgument("timezone must be a string".to_string()))?;
                if !tz_str.starts_with('+') && !tz_str.starts_with('-') && tz_str != "Z" {
                    if let Ok(tz) = tz_str.parse::<Tz>() {
                        let local_result = tz.from_local_datetime(&dt_naive);
                        match local_result {
                            chrono::offset::LocalResult::Single(dt_result) => {
                                (dt_result.offset().fix(), Some(tz_str.to_string()))
                            }
                            chrono::offset::LocalResult::Ambiguous(dt1, _) => {
                                (dt1.offset().fix(), Some(tz_str.to_string()))
                            }
                            chrono::offset::LocalResult::None => {
                                return Err(ExecutionError::InvalidArgument(
                                    "Invalid datetime for timezone (DST gap)".to_string()
                                ));
                            }
                        }
                    } else {
                        return Err(ExecutionError::InvalidArgument(format!(
                            "Unknown timezone: {}", tz_str
                        )));
                    }
                } else {
                    (parse_timezone_offset(tz_str)?, None)
                }
            } else {
                (FixedOffset::east_opt(0).unwrap(), None) // UTC
            };

            let dt = timezone.from_local_datetime(&dt_naive).single()
                .ok_or_else(|| ExecutionError::InvalidArgument("Invalid datetime for timezone".to_string()))?;

            if let Some(tz_name) = named_tz {
                Ok(CypherValue::DateTime(DateTimeValue::with_timezone_name(dt, tz_name)))
            } else {
                Ok(CypherValue::DateTime(DateTimeValue::new(dt)))
            }
        }
        CypherValue::LocalDateTime(ldt) => {
            // For date-level truncation, return DateTime
            let unit_lower = unit.to_lowercase();
            let is_date_level = matches!(
                unit_lower.as_str(),
                "millennium" | "century" | "decade" | "year" | "weekyear" | "quarter" | "month" | "week" | "day"
            );

            if is_date_level {
                let truncated_result = truncate_date(unit, &ldt.datetime.date(), override_map)?;
                let truncated_date = match truncated_result {
                    CypherValue::Date(d) => d.date,
                    _ => unreachable!(),
                };

                // Create base time 00:00:00, then apply time overrides (nanosecond, etc.)
                let base_time = NaiveTime::from_hms_opt(0, 0, 0).unwrap();
                let result_time = apply_time_overrides(base_time, override_map)?;
                let dt_naive = NaiveDateTime::new(truncated_date, result_time);

                // Determine timezone (handle named timezones)
                let (timezone, named_tz) = if let Some(tz_val) = override_map.get("timezone") {
                    let tz_str = tz_val.as_string().ok_or_else(||
                        ExecutionError::InvalidArgument("timezone must be a string".to_string()))?;
                    if !tz_str.starts_with('+') && !tz_str.starts_with('-') && tz_str != "Z" {
                        if let Ok(tz) = tz_str.parse::<Tz>() {
                            let local_result = tz.from_local_datetime(&dt_naive);
                            match local_result {
                                chrono::offset::LocalResult::Single(dt_result) => {
                                    (dt_result.offset().fix(), Some(tz_str.to_string()))
                                }
                                chrono::offset::LocalResult::Ambiguous(dt1, _) => {
                                    (dt1.offset().fix(), Some(tz_str.to_string()))
                                }
                                chrono::offset::LocalResult::None => {
                                    return Err(ExecutionError::InvalidArgument(
                                        "Invalid datetime for timezone (DST gap)".to_string()
                                    ));
                                }
                            }
                        } else {
                            return Err(ExecutionError::InvalidArgument(format!(
                                "Unknown timezone: {}", tz_str
                            )));
                        }
                    } else {
                        (parse_timezone_offset(tz_str)?, None)
                    }
                } else {
                    (FixedOffset::east_opt(0).unwrap(), None) // UTC
                };

                let dt = timezone.from_local_datetime(&dt_naive).single()
                    .ok_or_else(|| ExecutionError::InvalidArgument("Invalid datetime for timezone".to_string()))?;

                if let Some(tz_name) = named_tz {
                    Ok(CypherValue::DateTime(DateTimeValue::with_timezone_name(dt, tz_name)))
                } else {
                    Ok(CypherValue::DateTime(DateTimeValue::new(dt)))
                }
            } else {
                // Time-level truncation
                truncate_localdatetime(unit, ldt, override_map).and_then(|result| {
                    match result {
                        CypherValue::LocalDateTime(ldt_result) => {
                            // Determine timezone (handle named timezones)
                            let (timezone, named_tz) = if let Some(tz_val) = override_map.get("timezone") {
                                let tz_str = match tz_val.as_string() {
                                    Some(s) => s,
                                    None => return Err(ExecutionError::InvalidArgument("timezone must be a string".to_string())),
                                };
                                if !tz_str.starts_with('+') && !tz_str.starts_with('-') && tz_str != "Z" {
                                    if let Ok(tz) = tz_str.parse::<Tz>() {
                                        let local_result = tz.from_local_datetime(&ldt_result.datetime);
                                        match local_result {
                                            chrono::offset::LocalResult::Single(dt_result) => {
                                                (dt_result.offset().fix(), Some(tz_str.to_string()))
                                            }
                                            chrono::offset::LocalResult::Ambiguous(dt1, _) => {
                                                (dt1.offset().fix(), Some(tz_str.to_string()))
                                            }
                                            chrono::offset::LocalResult::None => {
                                                return Err(ExecutionError::InvalidArgument(
                                                    "Invalid datetime for timezone (DST gap)".to_string()
                                                ));
                                            }
                                        }
                                    } else {
                                        return Err(ExecutionError::InvalidArgument(format!(
                                            "Unknown timezone: {}", tz_str
                                        )));
                                    }
                                } else {
                                    match parse_timezone_offset(tz_str) {
                                        Ok(offset) => (offset, None),
                                        Err(e) => return Err(e),
                                    }
                                }
                            } else {
                                (FixedOffset::east_opt(0).unwrap(), None) // UTC
                            };

                            let dt = timezone.from_local_datetime(&ldt_result.datetime).single()
                                .ok_or_else(|| ExecutionError::InvalidArgument("Invalid datetime for timezone".to_string()))?;

                            if let Some(tz_name) = named_tz {
                                Ok(CypherValue::DateTime(DateTimeValue::with_timezone_name(dt, tz_name)))
                            } else {
                                Ok(CypherValue::DateTime(DateTimeValue::new(dt)))
                            }
                        }
                        _ => Ok(result),
                    }
                })
            }
        }
        other => Err(ExecutionError::InvalidArgument(format!(
            "datetime.truncate() requires a temporal value, got {}",
            other.type_name()
        ))),
    }
}

/// Wrapper for localdatetime.truncate() - returns Date or LocalDateTime
pub fn truncate_for_localdatetime(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if args.len() < 2 || args.len() > 3 {
        return Err(ExecutionError::InvalidArgument(
            "localdatetime.truncate() requires 2 or 3 arguments".to_string(),
        ));
    }

    let unit = args[0].as_string().ok_or_else(|| {
        ExecutionError::InvalidArgument("First argument must be a string unit".to_string())
    })?;

    let override_map = if args.len() == 3 {
        args[2].as_map().ok_or_else(|| {
            ExecutionError::InvalidArgument("Third argument must be a map".to_string())
        })?
    } else {
        &IndexMap::new()
    };

    match &args[1] {
        CypherValue::Null => Ok(CypherValue::Null),
        CypherValue::LocalDateTime(ldt) => truncate_localdatetime(unit, ldt, override_map),
        CypherValue::DateTime(dt) => {
            // Convert DateTime to LocalDateTime (drop timezone)
            let ldt = LocalDateTimeValue::new(dt.datetime.naive_local());
            truncate_localdatetime(unit, &ldt, override_map)
        }
        CypherValue::Date(d) => {
            // Convert Date to LocalDateTime for truncation
            let ldt = LocalDateTimeValue::new(NaiveDateTime::new(d.date, NaiveTime::from_hms_opt(0, 0, 0).unwrap()));
            truncate_localdatetime(unit, &ldt, override_map)
        }
        other => Err(ExecutionError::InvalidArgument(format!(
            "localdatetime.truncate() requires a temporal value, got {}",
            other.type_name()
        ))),
    }
}

/// Wrapper for localtime.truncate() - always returns LocalTime
pub fn truncate_for_localtime(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if args.len() < 2 || args.len() > 3 {
        return Err(ExecutionError::InvalidArgument(
            "localtime.truncate() requires 2 or 3 arguments".to_string(),
        ));
    }

    let unit = args[0].as_string().ok_or_else(|| {
        ExecutionError::InvalidArgument("First argument must be a string unit".to_string())
    })?;

    let override_map = if args.len() == 3 {
        args[2].as_map().ok_or_else(|| {
            ExecutionError::InvalidArgument("Third argument must be a map".to_string())
        })?
    } else {
        &IndexMap::new()
    };

    // Extract time from any temporal type (drop timezone if present)
    let time = match &args[1] {
        CypherValue::LocalTime(lt) => lt.time,
        CypherValue::Time(t) => t.time,
        CypherValue::DateTime(dt) => dt.datetime.naive_local().time(),
        CypherValue::LocalDateTime(ldt) => ldt.datetime.time(),
        CypherValue::Null => return Ok(CypherValue::Null),
        other => return Err(ExecutionError::InvalidArgument(format!(
            "localtime.truncate() requires a temporal value, got {}",
            other.type_name()
        ))),
    };

    // Create a temporary LocalTimeValue and truncate it
    let lt = LocalTimeValue::new(time);
    truncate_localtime(unit, &lt, override_map)
}

/// Wrapper for time.truncate() - always returns Time with timezone
pub fn truncate_for_time(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if args.len() < 2 || args.len() > 3 {
        return Err(ExecutionError::InvalidArgument(
            "time.truncate() requires 2 or 3 arguments".to_string(),
        ));
    }

    let unit = args[0].as_string().ok_or_else(|| {
        ExecutionError::InvalidArgument("First argument must be a string unit".to_string())
    })?;

    let override_map = if args.len() == 3 {
        args[2].as_map().ok_or_else(|| {
            ExecutionError::InvalidArgument("Third argument must be a map".to_string())
        })?
    } else {
        &IndexMap::new()
    };

    // Determine timezone and time from input
    let (time, timezone) = match &args[1] {
        CypherValue::Time(t) => (t.time, t.offset),
        CypherValue::DateTime(dt) => (dt.datetime.naive_local().time(), *dt.datetime.offset()),
        CypherValue::LocalTime(lt) => (lt.time, FixedOffset::east_opt(0).unwrap()), // Default to UTC
        CypherValue::LocalDateTime(ldt) => (ldt.datetime.time(), FixedOffset::east_opt(0).unwrap()), // Default to UTC
        CypherValue::Null => return Ok(CypherValue::Null),
        other => return Err(ExecutionError::InvalidArgument(format!(
            "time.truncate() requires a temporal value, got {}",
            other.type_name()
        ))),
    };

    // Override timezone if specified in map
    let final_timezone = if let Some(tz_val) = override_map.get("timezone") {
        let tz_str = tz_val.as_string().ok_or_else(||
            ExecutionError::InvalidArgument("timezone must be a string".to_string()))?;
        parse_timezone_offset(tz_str)?
    } else {
        timezone
    };

    // Create a temporary TimeValue and truncate it
    let t = TimeValue::new(time, final_timezone);
    truncate_time(unit, &t, override_map)
}

/// Legacy generic truncate function (kept for compatibility)
pub fn truncate(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if args.len() < 2 || args.len() > 3 {
        return Err(ExecutionError::InvalidArgument(
            "truncate() requires 2 or 3 arguments: unit, temporal value, and optional map".to_string(),
        ));
    }

    let unit = args[0].as_string().ok_or_else(|| {
        ExecutionError::InvalidArgument("truncate() first argument must be a string unit".to_string())
    })?;

    // Extract optional override map
    let override_map = if args.len() == 3 {
        args[2].as_map().ok_or_else(|| {
            ExecutionError::InvalidArgument("truncate() third argument must be a map".to_string())
        })?
    } else {
        &IndexMap::new()
    };

    match &args[1] {
        CypherValue::Date(d) => truncate_date(unit, &d.date, override_map),
        CypherValue::DateTime(dt) => truncate_datetime(unit, dt, override_map),
        CypherValue::LocalDateTime(ldt) => truncate_localdatetime(unit, ldt, override_map),
        CypherValue::Time(t) => truncate_time(unit, t, override_map),
        CypherValue::LocalTime(lt) => truncate_localtime(unit, lt, override_map),
        CypherValue::Null => Ok(CypherValue::Null),
        other => Err(ExecutionError::InvalidArgument(format!(
            "truncate() requires a temporal value, got {}",
            other.type_name()
        ))),
    }
}

/// (date, (hour, minute, second, nanosecond)) components of a temporal value
type DateTimeParts = (Option<chrono::NaiveDate>, Option<(u32, u32, u32, u32)>);

/// Extract date and time components from a CypherValue
/// Returns (Option<NaiveDate>, Option<(hour, minute, second, nanos)>)
/// Extract date and time parts from a temporal value
/// use_utc_for_datetime: If true, use UTC representation for DateTime values (for datetime-to-datetime comparison)
///                       If false, use local representation (for comparison with local types)
fn extract_date_time_parts(val: &CypherValue, use_utc_for_datetime: bool) -> Option<DateTimeParts> {
    use chrono::Timelike;

    match val {
        CypherValue::Date(d) => Some((Some(d.date), None)),
        CypherValue::LocalTime(lt) => Some((None, Some((lt.hour(), lt.minute(), lt.second(), lt.nanosecond())))),
        CypherValue::Time(t) => {
            // When use_utc_for_datetime is true, convert Time to UTC for proper comparison
            if use_utc_for_datetime {
                // Convert to UTC by subtracting the timezone offset
                let offset_secs = t.offset.local_minus_utc();
                let total_secs = t.time.num_seconds_from_midnight() as i64 - offset_secs as i64;
                // Handle day wraparound
                let adjusted_secs = ((total_secs % 86400) + 86400) % 86400;
                let hour = (adjusted_secs / 3600) as u32;
                let minute = ((adjusted_secs % 3600) / 60) as u32;
                let second = (adjusted_secs % 60) as u32;
                Some((None, Some((hour, minute, second, t.nanosecond()))))
            } else {
                Some((None, Some((t.hour(), t.minute(), t.second(), t.nanosecond()))))
            }
        }
        CypherValue::LocalDateTime(ldt) => {
            let d = ldt.datetime.date();
            let t = ldt.datetime.time();
            Some((Some(d), Some((t.hour(), t.minute(), t.second(), t.nanosecond()))))
        }
        CypherValue::DateTime(dt) => {
            // For DateTime-to-DateTime: use UTC for proper clock time comparison across timezones
            // For DateTime-to-local-type: use local representation
            let naive = if use_utc_for_datetime {
                dt.datetime.naive_utc()
            } else {
                dt.datetime.naive_local()
            };
            let d = naive.date();
            let t = naive.time();
            Some((Some(d), Some((t.hour(), t.minute(), t.second(), t.nanosecond()))))
        }
        _ => None,
    }
}

/// Compute duration for astronomical dates (ExtendedDate/ExtendedLocalDateTime)
/// Returns Some(DurationValue) if either operand is an extended date type, None otherwise
fn compute_extended_duration(from: &CypherValue, to: &CypherValue) -> Option<DurationValue> {
    // Extract extended date info from operands
    let from_ext = match from {
        CypherValue::ExtendedDate(ed) => Some((ed.year, ed.month as i64, ed.day as i64, 0i64, 0i64, 0i64, 0u32)),
        CypherValue::ExtendedLocalDateTime(eldt) => Some((
            eldt.date.year, eldt.date.month as i64, eldt.date.day as i64,
            eldt.hour as i64, eldt.minute as i64, eldt.second as i64, eldt.nanosecond
        )),
        _ => None,
    };

    let to_ext = match to {
        CypherValue::ExtendedDate(ed) => Some((ed.year, ed.month as i64, ed.day as i64, 0i64, 0i64, 0i64, 0u32)),
        CypherValue::ExtendedLocalDateTime(eldt) => Some((
            eldt.date.year, eldt.date.month as i64, eldt.date.day as i64,
            eldt.hour as i64, eldt.minute as i64, eldt.second as i64, eldt.nanosecond
        )),
        _ => None,
    };

    // Only handle if at least one operand is an extended type
    if from_ext.is_none() && to_ext.is_none() {
        return None;
    }

    // Convert regular dates/datetimes to extended format for uniform calculation
    let from_parts = from_ext.or_else(|| {
        match from {
            CypherValue::Date(d) => {
                use chrono::Datelike;
                Some((d.date.year() as i64, d.date.month() as i64, d.date.day() as i64, 0, 0, 0, 0))
            }
            CypherValue::LocalDateTime(ldt) => {
                use chrono::{Datelike, Timelike};
                let d = ldt.datetime.date();
                let t = ldt.datetime.time();
                Some((d.year() as i64, d.month() as i64, d.day() as i64,
                      t.hour() as i64, t.minute() as i64, t.second() as i64, t.nanosecond()))
            }
            _ => None,
        }
    })?;

    let to_parts = to_ext.or_else(|| {
        match to {
            CypherValue::Date(d) => {
                use chrono::Datelike;
                Some((d.date.year() as i64, d.date.month() as i64, d.date.day() as i64, 0, 0, 0, 0))
            }
            CypherValue::LocalDateTime(ldt) => {
                use chrono::{Datelike, Timelike};
                let d = ldt.datetime.date();
                let t = ldt.datetime.time();
                Some((d.year() as i64, d.month() as i64, d.day() as i64,
                      t.hour() as i64, t.minute() as i64, t.second() as i64, t.nanosecond()))
            }
            _ => None,
        }
    })?;

    let (from_year, from_month, from_day, from_hour, from_min, from_sec, from_nanos) = from_parts;
    let (to_year, to_month, to_day, to_hour, to_min, to_sec, to_nanos) = to_parts;

    // Compute months between dates using calendar arithmetic
    let mut total_months = (to_year - from_year) * 12 + (to_month - from_month);

    // Adjust months if the day of "to" is less than the day of "from"
    // (means we haven't completed the final month yet)
    let mut adjusted_from_day = from_day;
    if to_day < from_day {
        total_months -= 1;
        // Calculate the actual number of days in the previous month of "to"
        // For simplicity, approximate with 30 days (calendar arithmetic is complex for astronomical dates)
        adjusted_from_day = from_day;
    }

    // Calculate remaining days after accounting for months
    // For astronomical dates, we use a simplified approach:
    // advance from_date by total_months, then count remaining days
    let advanced_year = from_year + total_months / 12;
    let advanced_month = from_month + total_months % 12;
    let (advanced_year, advanced_month) = if advanced_month > 12 {
        (advanced_year + 1, advanced_month - 12)
    } else if advanced_month < 1 {
        (advanced_year - 1, advanced_month + 12)
    } else {
        (advanced_year, advanced_month)
    };

    // Create ExtendedDateValue for advanced date to compute epoch days
    let from_advanced = ExtendedDateValue::new(advanced_year, advanced_month as u8, adjusted_from_day as u8);
    let to_date_ext = ExtendedDateValue::new(to_year, to_month as u8, to_day as u8);

    // Days difference
    let days = to_date_ext.to_epoch_days() - from_advanced.to_epoch_days();

    // Time difference in nanoseconds
    let from_time_nanos = from_hour * 3_600_000_000_000_i64 + from_min * 60_000_000_000i64 + from_sec * 1_000_000_000i64 + from_nanos as i64;
    let to_time_nanos = to_hour * 3_600_000_000_000_i64 + to_min * 60_000_000_000i64 + to_sec * 1_000_000_000i64 + to_nanos as i64;
    let time_diff_nanos = to_time_nanos - from_time_nanos;

    let total_time_nanos = time_diff_nanos;
    let seconds = total_time_nanos / 1_000_000_000;
    let nanos = (total_time_nanos % 1_000_000_000) as i32;
    let (seconds, nanos) = normalize_duration_components(seconds, nanos);

    Some(DurationValue {
        months: total_months,
        days,
        seconds,
        nanos,
    })
}

/// Helper function to compute duration between two temporal values
fn compute_duration_between(from: &CypherValue, to: &CypherValue) -> ExecutionResult<DurationValue> {
    

    // Handle astronomical dates (ExtendedDate/ExtendedLocalDateTime) specially
    // These have years outside chrono's range (-262143 to +262142)
    if let Some(duration) = compute_extended_duration(from, to) {
        return Ok(duration);
    }

    // Determine if we should use UTC for timezone-aware values
    // Use UTC when BOTH operands have timezone (DateTime/Time to DateTime/Time)
    // Use local representation when either operand is a local type (LocalDateTime, LocalTime, Date)
    // This matches Neo4j behavior where:
    // - DateTime to DateTime: compares clock time in UTC
    // - Time to Time: compares clock time in UTC
    // - Time to DateTime: compares clock time in UTC (both have TZ)
    // - DateTime to LocalDateTime/Date: uses local representation (no TZ conversion)
    let from_has_tz = matches!(from, CypherValue::DateTime(_) | CypherValue::Time(_));
    let to_has_tz = matches!(to, CypherValue::DateTime(_) | CypherValue::Time(_));
    let use_utc = from_has_tz && to_has_tz;

    // Extract date and time parts
    let (from_date, from_time) = extract_date_time_parts(from, use_utc).ok_or_else(|| {
        ExecutionError::InvalidArgument(format!(
            "Cannot compute duration from {}",
            from.type_name()
        ))
    })?;

    let (to_date, to_time) = extract_date_time_parts(to, use_utc).ok_or_else(|| {
        ExecutionError::InvalidArgument(format!(
            "Cannot compute duration to {}",
            to.type_name()
        ))
    })?;

    // Check if either operand is time-only (LocalTime or Time)
    let from_is_time_only = matches!(from, CypherValue::LocalTime(_) | CypherValue::Time(_));
    let to_is_time_only = matches!(to, CypherValue::LocalTime(_) | CypherValue::Time(_));

    // Special case: If either operand is time-only, compute only the time difference (no days/months)
    // This applies to:
    // - time-only to time-only: compare times directly
    // - time-only to date: date treated as midnight
    // - time-only to datetime/localdatetime: compare time parts only, ignore the date
    // - date to time-only: date treated as midnight
    // - datetime/localdatetime to time-only: compare time parts only, ignore the date
    if from_is_time_only || to_is_time_only {
        // Compare times only: date types are treated as midnight, datetime types use their time component
        let from_t = from_time.unwrap_or((0, 0, 0, 0));
        let to_t = to_time.unwrap_or((0, 0, 0, 0));

        let base_date = chrono::NaiveDate::from_ymd_opt(2000, 1, 1).unwrap();
        let from_dt = base_date.and_hms_nano_opt(from_t.0, from_t.1, from_t.2, from_t.3).unwrap();
        let to_dt = base_date.and_hms_nano_opt(to_t.0, to_t.1, to_t.2, to_t.3).unwrap();

        let diff = to_dt.signed_duration_since(from_dt);
        let total_seconds = diff.num_seconds();
        let nanos = (diff.num_nanoseconds().unwrap_or(0) % 1_000_000_000) as i32;
        let (total_seconds, nanos) = normalize_duration_components(total_seconds, nanos);

        return Ok(DurationValue {
            months: 0,
            days: 0,
            seconds: total_seconds,
            nanos,
        });
    }

    // Special case: both are time-only (handled above) or time-only comparison
    let is_time_only_comparison = from_date.is_none() && to_date.is_none();

    // Build NaiveDateTime for both
    let base_date = chrono::NaiveDate::from_ymd_opt(1970, 1, 1).unwrap();

    let from_dt = {
        let date = from_date.unwrap_or(base_date);
        let (h, m, s, n) = from_time.unwrap_or((0, 0, 0, 0));
        date.and_hms_nano_opt(h, m, s, n).unwrap()
    };

    let to_dt = {
        let date = to_date.unwrap_or(base_date);
        let (h, m, s, n) = to_time.unwrap_or((0, 0, 0, 0));
        date.and_hms_nano_opt(h, m, s, n).unwrap()
    };

    // If both are time-only, return seconds-only duration
    if is_time_only_comparison {
        let diff = to_dt.signed_duration_since(from_dt);
        let total_seconds = diff.num_seconds();
        let nanos = (diff.num_nanoseconds().unwrap_or(0) % 1_000_000_000) as i32;
        let (total_seconds, nanos) = normalize_duration_components(total_seconds, nanos);

        return Ok(DurationValue {
            months: 0,
            days: 0,
            seconds: total_seconds,
            nanos,
        });
    }

    // Compute months using calendar arithmetic if both have dates
    let months = if let (Some(fd), Some(td)) = (from_date, to_date) {
        compute_months_between(fd, td)
    } else {
        0
    };

    // Compute remaining duration after accounting for months
    let diff = if months != 0 {
        // Advance from date by months and compute remaining
        let fd = from_date.unwrap();
        let advanced = add_months_to_date(fd, months);
        let advanced_dt = advanced.and_hms_nano_opt(
            from_time.map(|t| t.0).unwrap_or(0),
            from_time.map(|t| t.1).unwrap_or(0),
            from_time.map(|t| t.2).unwrap_or(0),
            from_time.map(|t| t.3).unwrap_or(0),
        ).unwrap();
        to_dt.signed_duration_since(advanced_dt)
    } else {
        to_dt.signed_duration_since(from_dt)
    };

    // Convert to DurationValue with proper normalization
    let days = diff.num_days();
    let remaining_seconds = diff.num_seconds() - (days * 86400);
    let nanos = (diff.num_nanoseconds().unwrap_or(0) % 1_000_000_000) as i32;

    // Normalize: nanoseconds should have same sign as seconds, OR seconds is 0
    let (remaining_seconds, nanos) = normalize_duration_components(remaining_seconds, nanos);

    Ok(DurationValue {
        months,
        days,
        seconds: remaining_seconds,
        nanos,
    })
}

/// Normalize duration components so nanoseconds are in range [0, 999999999]
/// This uses floor division (towards negative infinity) ensuring nanos is always non-negative
/// This matches Neo4j's convention where nanosecondsOfSecond is always non-negative
fn normalize_duration_components(seconds: i64, nanos: i32) -> (i64, i32) {
    // Combine into total nanoseconds for accurate normalization
    let total_nanos = seconds as i128 * 1_000_000_000 + nanos as i128;

    // Use floor division to get seconds (towards negative infinity)
    let normalized_seconds = if total_nanos >= 0 {
        (total_nanos / 1_000_000_000) as i64
    } else {
        // For negative, we need floor division
        ((total_nanos - 999_999_999) / 1_000_000_000) as i64
    };

    // Nanoseconds is always the positive remainder
    let normalized_nanos = (total_nanos - (normalized_seconds as i128 * 1_000_000_000)) as i32;

    (normalized_seconds, normalized_nanos)
}

/// Compute months between two dates using calendar arithmetic
fn compute_months_between(from: chrono::NaiveDate, to: chrono::NaiveDate) -> i64 {
    use chrono::Datelike;

    let from_year = from.year() as i64;
    let from_month = from.month() as i64;
    let from_day = from.day() as i64;

    let to_year = to.year() as i64;
    let to_month = to.month() as i64;
    let to_day = to.day() as i64;

    let mut months = (to_year - from_year) * 12 + (to_month - from_month);

    // Adjust if day of month hasn't been reached yet
    if months > 0 && to_day < from_day {
        months -= 1;
    } else if months < 0 && to_day > from_day {
        months += 1;
    }

    months
}

/// Add months to a date using calendar arithmetic
fn add_months_to_date(date: chrono::NaiveDate, months: i64) -> chrono::NaiveDate {
    use chrono::Datelike;

    let total_months = date.year() as i64 * 12 + date.month() as i64 - 1 + months;
    let year = (total_months / 12) as i32;
    let month = (total_months % 12 + 1) as u32;
    let month = if month == 0 { 12 } else { month };

    // Clamp day to valid range for target month
    let max_day = days_in_month(year, month);
    let day = date.day().min(max_day);

    chrono::NaiveDate::from_ymd_opt(year, month, day).unwrap_or(date)
}

/// Get number of days in a month
fn days_in_month(year: i32, month: u32) -> u32 {
    match month {
        1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
        4 | 6 | 9 | 11 => 30,
        2 => {
            if (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) {
                29
            } else {
                28
            }
        }
        _ => 30,
    }
}

/// Compute duration in seconds for astronomical dates (ExtendedDate/ExtendedLocalDateTime)
/// Returns Some(DurationValue) if either operand is an extended date type, None otherwise
fn compute_extended_duration_in_seconds(from: &CypherValue, to: &CypherValue) -> Option<DurationValue> {
    // Extract extended date info from operands
    let from_ext = match from {
        CypherValue::ExtendedDate(ed) => Some((ed.clone(), 0u8, 0u8, 0u8, 0u32)),
        CypherValue::ExtendedLocalDateTime(eldt) => Some((eldt.date.clone(), eldt.hour, eldt.minute, eldt.second, eldt.nanosecond)),
        _ => None,
    };

    let to_ext = match to {
        CypherValue::ExtendedDate(ed) => Some((ed.clone(), 0u8, 0u8, 0u8, 0u32)),
        CypherValue::ExtendedLocalDateTime(eldt) => Some((eldt.date.clone(), eldt.hour, eldt.minute, eldt.second, eldt.nanosecond)),
        _ => None,
    };

    // Only handle if at least one operand is an extended type
    if from_ext.is_none() && to_ext.is_none() {
        return None;
    }

    // Convert regular dates/datetimes to extended format
    let from_parts = from_ext.or_else(|| {
        match from {
            CypherValue::Date(d) => {
                
                Some((ExtendedDateValue::from_naive_date(&d.date), 0, 0, 0, 0))
            }
            CypherValue::LocalDateTime(ldt) => {
                Some((
                    ExtendedDateValue::from_naive_date(&ldt.datetime.date()),
                    ldt.hour() as u8, ldt.minute() as u8, ldt.second() as u8, ldt.nanosecond()
                ))
            }
            _ => None,
        }
    })?;

    let to_parts = to_ext.or_else(|| {
        match to {
            CypherValue::Date(d) => {

                Some((ExtendedDateValue::from_naive_date(&d.date), 0, 0, 0, 0))
            }
            CypherValue::LocalDateTime(ldt) => {
                Some((
                    ExtendedDateValue::from_naive_date(&ldt.datetime.date()),
                    ldt.hour() as u8, ldt.minute() as u8, ldt.second() as u8, ldt.nanosecond()
                ))
            }
            _ => None,
        }
    })?;

    let (from_date, from_h, from_m, from_s, from_n) = from_parts;
    let (to_date, to_h, to_m, to_s, to_n) = to_parts;

    // Compute total seconds difference
    // Use i128 to avoid overflow for extreme date ranges
    let from_epoch_days = from_date.to_epoch_days() as i128;
    let to_epoch_days = to_date.to_epoch_days() as i128;

    let from_day_nanos = (from_h as i128) * 3_600_000_000_000
        + (from_m as i128) * 60_000_000_000
        + (from_s as i128) * 1_000_000_000
        + (from_n as i128);
    let to_day_nanos = (to_h as i128) * 3_600_000_000_000
        + (to_m as i128) * 60_000_000_000
        + (to_s as i128) * 1_000_000_000
        + (to_n as i128);

    // Total nanoseconds from epoch
    let from_total_nanos = from_epoch_days * 86_400_000_000_000 + from_day_nanos;
    let to_total_nanos = to_epoch_days * 86_400_000_000_000 + to_day_nanos;

    let diff_nanos = to_total_nanos - from_total_nanos;

    // Convert to seconds and nanoseconds
    let total_seconds = (diff_nanos / 1_000_000_000) as i64;
    let nanos = (diff_nanos % 1_000_000_000) as i32;
    let (total_seconds, nanos) = normalize_duration_components(total_seconds, nanos);

    Some(DurationValue {
        months: 0,
        days: 0,
        seconds: total_seconds,
        nanos,
    })
}

/// Compute duration in seconds only (no months/days, just total time)
fn compute_duration_in_seconds(from: &CypherValue, to: &CypherValue) -> ExecutionResult<DurationValue> {
    // Handle astronomical dates (ExtendedDate/ExtendedLocalDateTime) specially
    if let Some(duration) = compute_extended_duration_in_seconds(from, to) {
        return Ok(duration);
    }

    // Check for DST-aware comparison: DateTime with named timezone vs local type
    // In this case, the local type is interpreted in the DateTime's timezone
    if let Some(duration) = compute_duration_in_seconds_dst_aware(from, to)? {
        return Ok(duration);
    }

    // For inSeconds, use UTC when BOTH operands have timezone
    let from_has_tz = matches!(from, CypherValue::DateTime(_) | CypherValue::Time(_));
    let to_has_tz = matches!(to, CypherValue::DateTime(_) | CypherValue::Time(_));
    let use_utc = from_has_tz && to_has_tz;

    // Extract date and time parts
    let (from_date, from_time) = extract_date_time_parts(from, use_utc).ok_or_else(|| {
        ExecutionError::InvalidArgument(format!(
            "Cannot compute duration from {}",
            from.type_name()
        ))
    })?;

    let (to_date, to_time) = extract_date_time_parts(to, use_utc).ok_or_else(|| {
        ExecutionError::InvalidArgument(format!(
            "Cannot compute duration to {}",
            to.type_name()
        ))
    })?;

    // Check if either is time-only (no date component)
    let from_is_time_only = from_date.is_none();
    let to_is_time_only = to_date.is_none();

    // When mixing date-containing and time-only types, compute only time difference
    // Date-only types are treated as midnight (00:00:00)
    if from_is_time_only || to_is_time_only {
        // Compute time-only difference
        let (from_h, from_m, from_s, from_n) = from_time.unwrap_or((0, 0, 0, 0));
        let (to_h, to_m, to_s, to_n) = to_time.unwrap_or((0, 0, 0, 0));

        let from_total_nanos = (from_h as i64) * 3_600_000_000_000
            + (from_m as i64) * 60_000_000_000
            + (from_s as i64) * 1_000_000_000
            + (from_n as i64);
        let to_total_nanos = (to_h as i64) * 3_600_000_000_000
            + (to_m as i64) * 60_000_000_000
            + (to_s as i64) * 1_000_000_000
            + (to_n as i64);

        let diff_nanos = to_total_nanos - from_total_nanos;
        let total_seconds = diff_nanos / 1_000_000_000;
        let nanos = (diff_nanos % 1_000_000_000) as i32;
        let (total_seconds, nanos) = normalize_duration_components(total_seconds, nanos);

        return Ok(DurationValue {
            months: 0,
            days: 0,
            seconds: total_seconds,
            nanos,
        });
    }

    // Both have date components - compute full difference
    let from_dt = {
        let date = from_date.unwrap();
        let (h, m, s, n) = from_time.unwrap_or((0, 0, 0, 0));
        date.and_hms_nano_opt(h, m, s, n).unwrap()
    };

    let to_dt = {
        let date = to_date.unwrap();
        let (h, m, s, n) = to_time.unwrap_or((0, 0, 0, 0));
        date.and_hms_nano_opt(h, m, s, n).unwrap()
    };

    let diff = to_dt.signed_duration_since(from_dt);
    let total_seconds = diff.num_seconds();
    let nanos = (diff.num_nanoseconds().unwrap_or(0) % 1_000_000_000) as i32;
    let (total_seconds, nanos) = normalize_duration_components(total_seconds, nanos);

    Ok(DurationValue {
        months: 0,
        days: 0,
        seconds: total_seconds,
        nanos,
    })
}

/// DST-aware duration calculation for DateTime with named timezone vs local types
/// Returns None if not applicable (no named timezone involved)
fn compute_duration_in_seconds_dst_aware(from: &CypherValue, to: &CypherValue) -> ExecutionResult<Option<DurationValue>> {
    

    // Get the named timezone from DateTime operands
    let from_tz_name = if let CypherValue::DateTime(dt) = from {
        dt.timezone_name.as_ref()
    } else {
        None
    };

    let to_tz_name = if let CypherValue::DateTime(dt) = to {
        dt.timezone_name.as_ref()
    } else {
        None
    };

    // Get the applicable named timezone (from whichever DateTime has one)
    let tz_name = from_tz_name.or(to_tz_name);

    // Only apply DST-aware logic if one operand has a named timezone
    // and the other is a local type (LocalDateTime, LocalTime, or Date)
    let from_is_local = matches!(from, CypherValue::LocalDateTime(_) | CypherValue::LocalTime(_) | CypherValue::Date(_));
    let to_is_local = matches!(to, CypherValue::LocalDateTime(_) | CypherValue::LocalTime(_) | CypherValue::Date(_));

    if tz_name.is_none() || !(from_is_local || to_is_local) {
        return Ok(None);
    }

    let tz_name = tz_name.unwrap();
    let tz: Tz = tz_name.parse().map_err(|_| {
        ExecutionError::InvalidArgument(format!("Invalid timezone: {}", tz_name))
    })?;

    // Convert both operands to UTC timestamps
    // Pass both operands to provide date context for LocalTime
    let from_utc_nanos = convert_to_utc_nanos_with_context(from, to, &tz)?;
    let to_utc_nanos = convert_to_utc_nanos_with_context(to, from, &tz)?;

    let diff_nanos = to_utc_nanos - from_utc_nanos;
    let total_seconds = diff_nanos / 1_000_000_000;
    let nanos = (diff_nanos % 1_000_000_000) as i32;
    let (total_seconds, nanos) = normalize_duration_components(total_seconds, nanos);

    Ok(Some(DurationValue {
        months: 0,
        days: 0,
        seconds: total_seconds,
        nanos,
    }))
}

/// Convert a temporal value to UTC nanoseconds since epoch, using the given timezone for local types
fn convert_to_utc_nanos(val: &CypherValue, tz: &Tz) -> ExecutionResult<i64> {
    use chrono::{Timelike, NaiveDate};

    match val {
        CypherValue::DateTime(dt) => {
            // Already has timezone - use its UTC representation
            let utc = dt.datetime.naive_utc();
            let epoch = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap();
            let diff = utc.signed_duration_since(epoch);
            Ok(diff.num_nanoseconds().unwrap_or(0))
        }
        CypherValue::LocalDateTime(ldt) => {
            // Interpret in the given timezone
            let naive = ldt.datetime;
            let local_result = tz.from_local_datetime(&naive);
            let dt_with_tz = match local_result {
                chrono::offset::LocalResult::Single(dt) => dt,
                chrono::offset::LocalResult::Ambiguous(dt1, _) => dt1, // Use earlier time during DST transition
                chrono::offset::LocalResult::None => {
                    return Err(ExecutionError::InvalidArgument(
                        "Invalid datetime for timezone (DST gap)".to_string()
                    ));
                }
            };
            let utc = dt_with_tz.naive_utc();
            let epoch = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap();
            let diff = utc.signed_duration_since(epoch);
            Ok(diff.num_nanoseconds().unwrap_or(0))
        }
        CypherValue::LocalTime(lt) => {
            // For time-only, use a reference date from the context
            // Since we're comparing to a DateTime, extract its date for the reference
            let time = lt.time;
            // Just return time-of-day in nanoseconds (no date component)
            let nanos = (time.hour() as i64) * 3_600_000_000_000
                + (time.minute() as i64) * 60_000_000_000
                + (time.second() as i64) * 1_000_000_000
                + (time.nanosecond() as i64);
            Ok(nanos)
        }
        CypherValue::Date(d) => {
            // Date treated as midnight in the given timezone
            let naive = d.date.and_hms_opt(0, 0, 0).unwrap();
            let local_result = tz.from_local_datetime(&naive);
            let dt_with_tz = match local_result {
                chrono::offset::LocalResult::Single(dt) => dt,
                chrono::offset::LocalResult::Ambiguous(dt1, _) => dt1,
                chrono::offset::LocalResult::None => {
                    return Err(ExecutionError::InvalidArgument(
                        "Invalid datetime for timezone (DST gap)".to_string()
                    ));
                }
            };
            let utc = dt_with_tz.naive_utc();
            let epoch = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap();
            let diff = utc.signed_duration_since(epoch);
            Ok(diff.num_nanoseconds().unwrap_or(0))
        }
        CypherValue::Time(t) => {
            // Time with offset - convert to UTC
            let offset_secs = t.offset.local_minus_utc();
            let time_secs = t.time.num_seconds_from_midnight() as i64;
            let utc_secs = time_secs - offset_secs as i64;
            Ok(utc_secs * 1_000_000_000 + t.nanosecond() as i64)
        }
        _ => Err(ExecutionError::InvalidArgument(format!(
            "Cannot convert {} to UTC timestamp",
            val.type_name()
        ))),
    }
}

/// Convert a temporal value to UTC nanoseconds, with context from the other operand for date reference
fn convert_to_utc_nanos_with_context(val: &CypherValue, context: &CypherValue, tz: &Tz) -> ExecutionResult<i64> {
    use chrono::{Timelike, NaiveDate};

    match val {
        CypherValue::LocalTime(lt) => {
            // Extract date from context (usually a DateTime)
            let reference_date = match context {
                CypherValue::DateTime(dt) => {
                    // Use the date from the DateTime in its timezone
                    let local_dt = dt.datetime.with_timezone(tz);
                    local_dt.date_naive()
                }
                CypherValue::LocalDateTime(ldt) => {
                    ldt.datetime.date()
                }
                CypherValue::Date(d) => {
                    d.date
                }
                _ => {
                    // No date context available, fall back to epoch date
                    NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()
                }
            };

            // Combine LocalTime with reference date
            let time = lt.time;
            let naive_dt = reference_date.and_hms_nano_opt(
                time.hour(),
                time.minute(),
                time.second(),
                time.nanosecond()
            ).unwrap();

            // Interpret in the given timezone (DST-aware)
            let local_result = tz.from_local_datetime(&naive_dt);
            let dt_with_tz = match local_result {
                chrono::offset::LocalResult::Single(dt) => dt,
                chrono::offset::LocalResult::Ambiguous(dt1, _) => dt1, // Use earlier time during DST transition
                chrono::offset::LocalResult::None => {
                    return Err(ExecutionError::InvalidArgument(
                        "Invalid datetime for timezone (DST gap)".to_string()
                    ));
                }
            };

            // Convert to UTC nanoseconds
            let utc = dt_with_tz.naive_utc();
            let epoch = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap();
            let diff = utc.signed_duration_since(epoch);
            Ok(diff.num_nanoseconds().unwrap_or(0))
        }
        // For all other types, delegate to the original function
        _ => convert_to_utc_nanos(val, tz)
    }
}

/// Compute duration in days only (no months, whole days + time remainder)
fn compute_duration_in_days(from: &CypherValue, to: &CypherValue) -> ExecutionResult<DurationValue> {
    // For inDays, use UTC when BOTH operands have timezone
    let from_has_tz = matches!(from, CypherValue::DateTime(_) | CypherValue::Time(_));
    let to_has_tz = matches!(to, CypherValue::DateTime(_) | CypherValue::Time(_));
    let use_utc = from_has_tz && to_has_tz;

    let (from_date, from_time) = extract_date_time_parts(from, use_utc).ok_or_else(|| {
        ExecutionError::InvalidArgument(format!(
            "Cannot compute duration from {}",
            from.type_name()
        ))
    })?;

    let (to_date, to_time) = extract_date_time_parts(to, use_utc).ok_or_else(|| {
        ExecutionError::InvalidArgument(format!(
            "Cannot compute duration to {}",
            to.type_name()
        ))
    })?;

    // If both are time-only, return PT0S
    if from_date.is_none() && to_date.is_none() {
        return Ok(DurationValue {
            months: 0,
            days: 0,
            seconds: 0,
            nanos: 0,
        });
    }

    // If one is time-only, return PT0S
    if from_date.is_none() || to_date.is_none() {
        return Ok(DurationValue {
            months: 0,
            days: 0,
            seconds: 0,
            nanos: 0,
        });
    }

    let base_date = chrono::NaiveDate::from_ymd_opt(1970, 1, 1).unwrap();

    let from_dt = {
        let date = from_date.unwrap_or(base_date);
        let (h, m, s, n) = from_time.unwrap_or((0, 0, 0, 0));
        date.and_hms_nano_opt(h, m, s, n).unwrap()
    };

    let to_dt = {
        let date = to_date.unwrap_or(base_date);
        let (h, m, s, n) = to_time.unwrap_or((0, 0, 0, 0));
        date.and_hms_nano_opt(h, m, s, n).unwrap()
    };

    let diff = to_dt.signed_duration_since(from_dt);
    let days = diff.num_days();

    // duration.inDays() returns ONLY whole days, no time remainder
    Ok(DurationValue {
        months: 0,
        days,
        seconds: 0,
        nanos: 0,
    })
}

/// Compute duration in months only (calendar months, no days/seconds)
fn compute_duration_in_months(from: &CypherValue, to: &CypherValue) -> ExecutionResult<DurationValue> {
    // For inMonths, use UTC when BOTH operands have timezone
    let from_has_tz = matches!(from, CypherValue::DateTime(_) | CypherValue::Time(_));
    let to_has_tz = matches!(to, CypherValue::DateTime(_) | CypherValue::Time(_));
    let use_utc = from_has_tz && to_has_tz;

    let (from_date, from_time) = extract_date_time_parts(from, use_utc).ok_or_else(|| {
        ExecutionError::InvalidArgument(format!(
            "Cannot compute duration from {}",
            from.type_name()
        ))
    })?;

    let (to_date, to_time) = extract_date_time_parts(to, use_utc).ok_or_else(|| {
        ExecutionError::InvalidArgument(format!(
            "Cannot compute duration to {}",
            to.type_name()
        ))
    })?;

    // If either is time-only, return PT0S
    if from_date.is_none() || to_date.is_none() {
        return Ok(DurationValue {
            months: 0,
            days: 0,
            seconds: 0,
            nanos: 0,
        });
    }

    let fd = from_date.unwrap();
    let td = to_date.unwrap();
    let mut months = compute_months_between(fd, td);

    // When the days of month are the same, we need to check the time component
    // to determine if a full month has actually passed
    use chrono::Datelike;
    if fd.day() == td.day() && months != 0 {
        let from_t = from_time.unwrap_or((0, 0, 0, 0));
        let to_t = to_time.unwrap_or((0, 0, 0, 0));

        // Convert to comparable nanos since midnight
        let from_nanos = from_t.0 as i64 * 3_600_000_000_000
            + from_t.1 as i64 * 60_000_000_000
            + from_t.2 as i64 * 1_000_000_000
            + from_t.3 as i64;
        let to_nanos = to_t.0 as i64 * 3_600_000_000_000
            + to_t.1 as i64 * 60_000_000_000
            + to_t.2 as i64 * 1_000_000_000
            + to_t.3 as i64;

        // If going forward (positive months) but to_time < from_time, we haven't completed a full month
        // If going backward (negative months) but to_time > from_time, we haven't completed a full month
        if months > 0 && to_nanos < from_nanos {
            months -= 1;
        } else if months < 0 && to_nanos > from_nanos {
            months += 1;
        }
    }

    Ok(DurationValue {
        months,
        days: 0,
        seconds: 0,
        nanos: 0,
    })
}

/// Compute duration between two temporal values in seconds
/// Returns a Duration with time component only (no months/days)
pub fn inseconds(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if args.len() != 2 {
        return Err(ExecutionError::InvalidArgument(
            "duration.inSeconds() requires 2 arguments".to_string(),
        ));
    }

    // Handle NULL
    if args[0].is_null() || args[1].is_null() {
        return Ok(CypherValue::Null);
    }

    // Compute duration in seconds
    let duration = compute_duration_in_seconds(&args[0], &args[1])?;

    Ok(CypherValue::Duration(duration))
}

/// Compute duration between two temporal values in months
/// Returns a Duration with months component only
pub fn inmonths(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if args.len() != 2 {
        return Err(ExecutionError::InvalidArgument(
            "duration.inMonths() requires 2 arguments".to_string(),
        ));
    }

    // Handle NULL
    if args[0].is_null() || args[1].is_null() {
        return Ok(CypherValue::Null);
    }

    // Compute duration in months
    let duration = compute_duration_in_months(&args[0], &args[1])?;

    Ok(CypherValue::Duration(duration))
}

/// Compute duration between two temporal values in days
/// Returns a Duration with days component only (no months)
pub fn indays(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if args.len() != 2 {
        return Err(ExecutionError::InvalidArgument(
            "duration.inDays() requires 2 arguments".to_string(),
        ));
    }

    // Handle NULL
    if args[0].is_null() || args[1].is_null() {
        return Ok(CypherValue::Null);
    }

    // Compute duration in days
    let duration = compute_duration_in_days(&args[0], &args[1])?;

    Ok(CypherValue::Duration(duration))
}

/// Compute full duration between two temporal values
/// Returns a duration object representing the difference
pub fn duration_between(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if args.len() != 2 {
        return Err(ExecutionError::InvalidArgument(
            "duration.between() requires 2 arguments".to_string(),
        ));
    }

    // Handle NULL
    if args[0].is_null() || args[1].is_null() {
        return Ok(CypherValue::Null);
    }

    // Compute duration between the two temporal values
    let duration = compute_duration_between(&args[0], &args[1])?;

    Ok(CypherValue::Duration(duration))
}

/// Helper macro for transaction/statement/realtime functions
/// These functions take no arguments, but should propagate null if passed null
fn handle_temporal_realtime_args(args: &[CypherValue], func_name: &str) -> Option<ExecutionResult<CypherValue>> {
    if args.len() == 1 && args[0].is_null() {
        return Some(Ok(CypherValue::Null));
    }
    if !args.is_empty() {
        return Some(Err(ExecutionError::InvalidArgument(
            format!("{}() takes no arguments", func_name),
        )));
    }
    None
}

/// date.transaction() - Get the transaction start date
/// Note: Since we don't track transaction lifecycle, this returns current date
pub fn date_transaction(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if let Some(result) = handle_temporal_realtime_args(&args, "date.transaction") {
        return result;
    }
    date(vec![])
}

/// date.statement() - Get the statement start date
/// Note: Since we don't track statement lifecycle, this returns current date
pub fn date_statement(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if let Some(result) = handle_temporal_realtime_args(&args, "date.statement") {
        return result;
    }
    date(vec![])
}

/// date.realtime() - Get the current real-time date
pub fn date_realtime(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if let Some(result) = handle_temporal_realtime_args(&args, "date.realtime") {
        return result;
    }
    date(vec![])
}

/// time.transaction() - Get the transaction start time
/// Note: Since we don't track transaction lifecycle, this returns current time
pub fn time_transaction(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if let Some(result) = handle_temporal_realtime_args(&args, "time.transaction") {
        return result;
    }
    time(vec![])
}

/// time.statement() - Get the statement start time
/// Note: Since we don't track statement lifecycle, this returns current time
pub fn time_statement(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if let Some(result) = handle_temporal_realtime_args(&args, "time.statement") {
        return result;
    }
    time(vec![])
}

/// time.realtime() - Get the current real-time
pub fn time_realtime(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if let Some(result) = handle_temporal_realtime_args(&args, "time.realtime") {
        return result;
    }
    time(vec![])
}

/// localtime.transaction() - Get the transaction start local time
/// Note: Since we don't track transaction lifecycle, this returns current local time
pub fn localtime_transaction(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if let Some(result) = handle_temporal_realtime_args(&args, "localtime.transaction") {
        return result;
    }
    localtime(vec![])
}

/// localtime.statement() - Get the statement start local time
/// Note: Since we don't track statement lifecycle, this returns current local time
pub fn localtime_statement(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if let Some(result) = handle_temporal_realtime_args(&args, "localtime.statement") {
        return result;
    }
    localtime(vec![])
}

/// localtime.realtime() - Get the current real-time local time
pub fn localtime_realtime(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if let Some(result) = handle_temporal_realtime_args(&args, "localtime.realtime") {
        return result;
    }
    localtime(vec![])
}

/// datetime.transaction() - Get the transaction start datetime
/// Note: Since we don't track transaction lifecycle, this returns current datetime
pub fn datetime_transaction(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if let Some(result) = handle_temporal_realtime_args(&args, "datetime.transaction") {
        return result;
    }
    datetime(vec![])
}

/// datetime.statement() - Get the statement start datetime
/// Note: Since we don't track statement lifecycle, this returns current datetime
pub fn datetime_statement(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if let Some(result) = handle_temporal_realtime_args(&args, "datetime.statement") {
        return result;
    }
    datetime(vec![])
}

/// datetime.realtime() - Get the current real-time datetime
pub fn datetime_realtime(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if let Some(result) = handle_temporal_realtime_args(&args, "datetime.realtime") {
        return result;
    }
    datetime(vec![])
}

/// localdatetime.transaction() - Get the transaction start local datetime
/// Note: Since we don't track transaction lifecycle, this returns current local datetime
pub fn localdatetime_transaction(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if let Some(result) = handle_temporal_realtime_args(&args, "localdatetime.transaction") {
        return result;
    }
    localdatetime(vec![])
}

/// localdatetime.statement() - Get the statement start local datetime
/// Note: Since we don't track statement lifecycle, this returns current local datetime
pub fn localdatetime_statement(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if let Some(result) = handle_temporal_realtime_args(&args, "localdatetime.statement") {
        return result;
    }
    localdatetime(vec![])
}

/// localdatetime.realtime() - Get the current real-time local datetime
pub fn localdatetime_realtime(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if let Some(result) = handle_temporal_realtime_args(&args, "localdatetime.realtime") {
        return result;
    }
    localdatetime(vec![])
}

/// datetime.fromepoch() - Create a datetime from epoch seconds (and optional nanoseconds)
/// Accepts: datetime.fromepoch(seconds) or datetime.fromepoch(seconds, nanoseconds)
pub fn datetime_fromepoch(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if args.is_empty() || args.len() > 2 {
        return Err(ExecutionError::InvalidArgument(
            "datetime.fromepoch() requires 1 or 2 arguments (seconds, [nanoseconds])".to_string(),
        ));
    }

    // Handle null first arg
    if let CypherValue::Null = &args[0] {
        return Ok(CypherValue::Null);
    }

    // Extract seconds
    let secs = match &args[0] {
        CypherValue::Integer(s) => *s,
        _ => return Err(ExecutionError::InvalidArgument(
            "datetime.fromepoch() requires an integer for seconds".to_string(),
        )),
    };

    // Extract nanoseconds (optional second argument, defaults to 0)
    let nanos = if args.len() == 2 {
        match &args[1] {
            CypherValue::Null => return Ok(CypherValue::Null),
            CypherValue::Integer(n) => *n as u32,
            _ => return Err(ExecutionError::InvalidArgument(
                "datetime.fromepoch() requires an integer for nanoseconds".to_string(),
            )),
        }
    } else {
        0
    };

    // Create datetime from timestamp
    let dt = Utc.timestamp_opt(secs, nanos)
        .single()
        .ok_or_else(|| ExecutionError::InvalidArgument(
            format!("Invalid epoch seconds: {}", secs)
        ))?;

    Ok(CypherValue::DateTime(DateTimeValue {
        datetime: dt.fixed_offset(),
        timezone_name: None,
    }))
}

/// datetime.fromepochmillis() - Create a datetime from epoch milliseconds
pub fn datetime_fromepochmillis(args: Vec<CypherValue>) -> ExecutionResult<CypherValue> {
    if args.len() != 1 {
        return Err(ExecutionError::InvalidArgument(
            "datetime.fromepochmillis() requires exactly 1 argument (milliseconds)".to_string(),
        ));
    }

    match &args[0] {
        CypherValue::Null => Ok(CypherValue::Null),
        CypherValue::Integer(millis) => {
            // Convert milliseconds to seconds and nanoseconds
            let secs = millis / 1000;
            let nanos = ((millis % 1000) * 1_000_000) as u32;

            // Create datetime from timestamp
            let dt = Utc.timestamp_opt(secs, nanos)
                .single()
                .ok_or_else(|| ExecutionError::InvalidArgument(
                    format!("Invalid epoch milliseconds: {}", millis)
                ))?;

            Ok(CypherValue::DateTime(DateTimeValue {
                datetime: dt.fixed_offset(),
                timezone_name: None,
            }))
        }
        _ => Err(ExecutionError::InvalidArgument(
            "datetime.fromepochmillis() requires an integer".to_string(),
        )),
    }
}
