use crate::host::HostFunction;
use crate::object::function::JSFunction;
use crate::object::object::JSObject;
use crate::runtime::context::JSContext;
use crate::value::JSValue;
use std::time::{SystemTime, UNIX_EPOCH};

fn current_timestamp_ms() -> f64 {
    let now = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap_or_default();
    (now.as_secs() as f64 * 1000.0) + (now.subsec_nanos() as f64 / 1_000_000.0)
}

fn parse_date_string(s: &str) -> f64 {
    let s = s.trim();
    if s.starts_with('+')
        || s.starts_with('-')
        || s.contains('.')
        || s.contains('e')
        || s.contains('E')
    {
        if let Ok(ts) = s.parse::<f64>() {
            return ts;
        }
    }
    if s.strip_prefix("YYYY-").is_some() {}
    let parts: Vec<&str> = if s.contains('T') {
        s.splitn(2, 'T').collect()
    } else if s.contains(' ') {
        s.splitn(2, ' ').collect()
    } else {
        vec![s, ""]
    };
    let date_part = parts[0];
    let time_part = if parts.len() > 1 { parts[1] } else { "" };

    let (year, month, day) = if date_part.starts_with('-') {
        let after = &date_part[1..];
        let dp: Vec<&str> = after.splitn(3, '-').collect();
        let raw_y: i32 = dp[0].parse().unwrap_or(0);
        if raw_y == 0 {
            return f64::NAN;
        }
        let y = -raw_y;
        let m = if dp.len() > 1 {
            dp[1].parse::<u32>().unwrap_or(1)
        } else {
            1
        };
        let d = if dp.len() > 2 {
            dp[2].parse::<u32>().unwrap_or(1)
        } else {
            1
        };
        (y, m, d)
    } else if date_part.contains('-') {
        let dp: Vec<&str> = date_part.split('-').collect();
        if dp.len() < 1 {
            return f64::NAN;
        }
        let y = dp[0].parse::<i32>().unwrap_or(0);
        let m = if dp.len() > 1 {
            dp[1].parse::<u32>().unwrap_or(1)
        } else {
            1
        };
        let d = if dp.len() > 2 {
            dp[2].parse::<u32>().unwrap_or(1)
        } else {
            1
        };
        (y, m, d)
    } else {
        let y = date_part.parse::<i32>().unwrap_or(0);
        (y, 1, 1)
    };

    let (hour, minute, second, ms, tz_offset) = if time_part.is_empty() {
        (0u32, 0u32, 0u32, 0u32, 0i64)
    } else {
        let (time_str, tz) = if time_part.ends_with('Z') {
            (&time_part[..time_part.len() - 1], 0i64)
        } else if let Some(idx) = time_part.rfind('+') {
            let sign = 1i64;
            let offset_str = &time_part[idx + 1..];
            let off = parse_tz_offset(offset_str, sign);
            (&time_part[..idx], off)
        } else if let Some(idx) = time_part.rfind('-') {
            if idx > 0 && time_part.chars().nth(idx - 1) != Some(':') && idx > 5 {
                let sign = -1i64;
                let offset_str = &time_part[idx + 1..];
                let off = parse_tz_offset(offset_str, sign);
                (&time_part[..idx], off)
            } else {
                (time_part, 0)
            }
        } else {
            (time_part, 0)
        };

        let tp: Vec<&str> = time_str.split(':').collect();
        let h = tp.first().and_then(|v| v.parse::<u32>().ok()).unwrap_or(0);
        let mi = tp.get(1).and_then(|v| v.parse::<u32>().ok()).unwrap_or(0);
        let sec_str = tp.get(2).map(|v| *v).unwrap_or("0");
        let (sec, msec) = if sec_str.contains('.') {
            let sp: Vec<&str> = sec_str.split('.').collect();
            let s = sp[0].parse::<u32>().unwrap_or(0);
            let ms = sp.get(1).and_then(|v| v.parse::<u32>().ok()).unwrap_or(0);
            (s, ms)
        } else {
            (sec_str.parse::<u32>().unwrap_or(0), 0)
        };
        (h, mi, sec, msec, tz)
    };

    let ts = make_day_from_parts(year, month, day.max(1), hour, minute, second, ms);
    ts - (tz_offset as f64 * 60000.0)
}

fn parse_tz_offset(s: &str, sign: i64) -> i64 {
    let parts: Vec<&str> = s.split(':').collect();
    let h = parts
        .first()
        .and_then(|v| v.parse::<i64>().ok())
        .unwrap_or(0);
    let m = parts
        .get(1)
        .and_then(|v| v.parse::<i64>().ok())
        .unwrap_or(0);
    sign * (h * 60 + m)
}

pub fn register_builtins(ctx: &mut JSContext) {
    ctx.register_builtin("date_now", HostFunction::new("now", 0, date_now));
    ctx.register_builtin("date_parse", HostFunction::new("parse", 1, date_parse));
    ctx.register_builtin("date_utc", HostFunction::new("UTC", 7, date_utc));
    ctx.register_builtin(
        "date_constructor",
        HostFunction::ctor("Date", 7, date_constructor),
    );
    ctx.register_builtin(
        "date_getTime",
        HostFunction::method("getTime", 0, date_get_time),
    );
    ctx.register_builtin(
        "date_getFullYear",
        HostFunction::method("getFullYear", 0, date_get_full_year),
    );
    ctx.register_builtin(
        "date_getMonth",
        HostFunction::method("getMonth", 0, date_get_month),
    );
    ctx.register_builtin(
        "date_getDate",
        HostFunction::method("getDate", 0, date_get_date),
    );
    ctx.register_builtin(
        "date_getDay",
        HostFunction::method("getDay", 0, date_get_day),
    );
    ctx.register_builtin(
        "date_getHours",
        HostFunction::method("getHours", 0, date_get_hours),
    );
    ctx.register_builtin(
        "date_getMinutes",
        HostFunction::method("getMinutes", 0, date_get_minutes),
    );
    ctx.register_builtin(
        "date_getSeconds",
        HostFunction::method("getSeconds", 0, date_get_seconds),
    );
    ctx.register_builtin(
        "date_getMilliseconds",
        HostFunction::method("getMilliseconds", 0, date_get_milliseconds),
    );
    ctx.register_builtin(
        "date_getTimezoneOffset",
        HostFunction::method("getTimezoneOffset", 0, date_get_timezone_offset),
    );
    ctx.register_builtin(
        "date_getUTCFullYear",
        HostFunction::method("getUTCFullYear", 0, date_get_utc_full_year),
    );
    ctx.register_builtin(
        "date_getUTCMonth",
        HostFunction::method("getUTCMonth", 0, date_get_utc_month),
    );
    ctx.register_builtin(
        "date_getUTCDate",
        HostFunction::method("getUTCDate", 0, date_get_utc_date),
    );
    ctx.register_builtin(
        "date_getUTCDay",
        HostFunction::method("getUTCDay", 0, date_get_utc_day),
    );
    ctx.register_builtin(
        "date_getUTCHours",
        HostFunction::method("getUTCHours", 0, date_get_utc_hours),
    );
    ctx.register_builtin(
        "date_getUTCMinutes",
        HostFunction::method("getUTCMinutes", 0, date_get_utc_minutes),
    );
    ctx.register_builtin(
        "date_getUTCSeconds",
        HostFunction::method("getUTCSeconds", 0, date_get_utc_seconds),
    );
    ctx.register_builtin(
        "date_getUTCMilliseconds",
        HostFunction::method("getUTCMilliseconds", 0, date_get_utc_milliseconds),
    );
    ctx.register_builtin(
        "date_toString",
        HostFunction::method("toString", 0, date_to_string),
    );
    ctx.register_builtin(
        "date_toISOString",
        HostFunction::method("toISOString", 0, date_to_iso_string),
    );
    ctx.register_builtin(
        "date_toUTCString",
        HostFunction::method("toUTCString", 0, date_to_utc_string),
    );
    ctx.register_builtin(
        "date_toDateString",
        HostFunction::method("toDateString", 0, date_to_date_string),
    );
    ctx.register_builtin(
        "date_toTimeString",
        HostFunction::method("toTimeString", 0, date_to_time_string),
    );
    ctx.register_builtin(
        "date_valueOf",
        HostFunction::method("valueOf", 0, date_value_of),
    );
    ctx.register_builtin(
        "date_toJSON",
        HostFunction::method("toJSON", 1, date_to_json),
    );
}

pub fn init_date(ctx: &mut JSContext) {
    fn set_ne(
        obj: &mut crate::object::object::JSObject,
        key: crate::runtime::atom::Atom,
        val: crate::value::JSValue,
    ) {
        obj.define_property(
            key,
            crate::object::object::PropertyDescriptor {
                value: Some(val),
                writable: true,
                enumerable: false,
                configurable: true,
                get: None,
                set: None,
            },
        );
    }

    let date_atom = ctx.intern("Date");

    let mut date_func = JSFunction::new_builtin(date_atom, 7);
    date_func.set_builtin_marker(ctx, "date_constructor");

    crate::builtins::global::set_non_enumerable(
        &mut date_func.base,
        ctx.intern("now"),
        create_builtin_function(ctx, "date_now", 0, None),
    );
    crate::builtins::global::set_non_enumerable(
        &mut date_func.base,
        ctx.intern("parse"),
        create_builtin_function(ctx, "date_parse", 1, None),
    );
    crate::builtins::global::set_non_enumerable(
        &mut date_func.base,
        ctx.intern("UTC"),
        create_builtin_function(ctx, "date_utc", 7, None),
    );

    let date_ptr = Box::into_raw(Box::new(date_func)) as usize;
    ctx.runtime_mut().gc_heap_mut().track_function(date_ptr);
    let date_value = JSValue::new_function(date_ptr);

    let global = ctx.global();
    if global.is_object() {
        let global_obj = global.as_object_mut();
        crate::builtins::global::set_non_enumerable(global_obj, date_atom, date_value);
    }

    let proto_atom = ctx.intern("DatePrototype");
    let mut proto_obj = JSObject::new();

    if let Some(obj_proto_ptr) = ctx.get_object_prototype() {
        proto_obj.prototype = Some(obj_proto_ptr);
    }

    set_ne(&mut proto_obj, ctx.intern("constructor"), date_value);

    set_ne(
        &mut proto_obj,
        ctx.intern("getTime"),
        create_builtin_function(ctx, "date_getTime", 0, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("getFullYear"),
        create_builtin_function(ctx, "date_getFullYear", 0, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("getMonth"),
        create_builtin_function(ctx, "date_getMonth", 0, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("getDate"),
        create_builtin_function(ctx, "date_getDate", 0, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("getDay"),
        create_builtin_function(ctx, "date_getDay", 0, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("getHours"),
        create_builtin_function(ctx, "date_getHours", 0, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("getMinutes"),
        create_builtin_function(ctx, "date_getMinutes", 0, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("getSeconds"),
        create_builtin_function(ctx, "date_getSeconds", 0, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("getMilliseconds"),
        create_builtin_function(ctx, "date_getMilliseconds", 0, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("getTimezoneOffset"),
        create_builtin_function(ctx, "date_getTimezoneOffset", 0, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("getUTCFullYear"),
        create_builtin_function(ctx, "date_getUTCFullYear", 0, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("getUTCMonth"),
        create_builtin_function(ctx, "date_getUTCMonth", 0, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("getUTCDate"),
        create_builtin_function(ctx, "date_getUTCDate", 0, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("getUTCDay"),
        create_builtin_function(ctx, "date_getUTCDay", 0, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("getUTCHours"),
        create_builtin_function(ctx, "date_getUTCHours", 0, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("getUTCMinutes"),
        create_builtin_function(ctx, "date_getUTCMinutes", 0, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("getUTCSeconds"),
        create_builtin_function(ctx, "date_getUTCSeconds", 0, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("getUTCMilliseconds"),
        create_builtin_function(ctx, "date_getUTCMilliseconds", 0, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("setTime"),
        create_builtin_function(ctx, "date_setTime", 1, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("setMilliseconds"),
        create_builtin_function(ctx, "date_setMilliseconds", 1, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("setSeconds"),
        create_builtin_function(ctx, "date_setSeconds", 2, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("setMinutes"),
        create_builtin_function(ctx, "date_setMinutes", 3, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("setHours"),
        create_builtin_function(ctx, "date_setHours", 4, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("setDate"),
        create_builtin_function(ctx, "date_setDate", 1, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("setMonth"),
        create_builtin_function(ctx, "date_setMonth", 2, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("setFullYear"),
        create_builtin_function(ctx, "date_setFullYear", 3, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("setUTCMilliseconds"),
        create_builtin_function(ctx, "date_setUTCMilliseconds", 1, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("setUTCSeconds"),
        create_builtin_function(ctx, "date_setUTCSeconds", 2, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("setUTCMinutes"),
        create_builtin_function(ctx, "date_setUTCMinutes", 3, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("setUTCHours"),
        create_builtin_function(ctx, "date_setUTCHours", 4, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("setUTCDate"),
        create_builtin_function(ctx, "date_setUTCDate", 1, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("setUTCMonth"),
        create_builtin_function(ctx, "date_setUTCMonth", 2, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("setUTCFullYear"),
        create_builtin_function(ctx, "date_setUTCFullYear", 3, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("toString"),
        create_builtin_function(ctx, "date_toString", 0, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("toISOString"),
        create_builtin_function(ctx, "date_toISOString", 0, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("toUTCString"),
        create_builtin_function(ctx, "date_toUTCString", 0, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("toDateString"),
        create_builtin_function(ctx, "date_toDateString", 0, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("toTimeString"),
        create_builtin_function(ctx, "date_toTimeString", 0, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("valueOf"),
        create_builtin_function(ctx, "date_valueOf", 0, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("toJSON"),
        create_builtin_function(ctx, "date_toJSON", 1, None),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("toLocaleString"),
        create_builtin_function(ctx, "date_to_string", 0, Some("toLocaleString")),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("toLocaleDateString"),
        create_builtin_function(ctx, "date_to_date_string", 0, Some("toLocaleDateString")),
    );
    set_ne(
        &mut proto_obj,
        ctx.intern("toLocaleTimeString"),
        create_builtin_function(ctx, "date_to_time_string", 0, Some("toLocaleTimeString")),
    );

    let proto_ptr = Box::into_raw(Box::new(proto_obj)) as usize;
    ctx.runtime_mut().gc_heap_mut().track(proto_ptr);
    ctx.set_date_prototype(proto_ptr);
    let proto_value = JSValue::new_object(proto_ptr);

    if global.is_object() {
        let global_obj = global.as_object_mut();
        crate::builtins::global::set_non_enumerable(global_obj, proto_atom, proto_value);
    }

    let date_func_ref = date_value.as_function_mut();
    date_func_ref.base.define_property(
        ctx.intern("prototype"),
        crate::object::object::PropertyDescriptor {
            value: Some(proto_value),
            writable: false,
            enumerable: false,
            configurable: false,
            get: None,
            set: None,
        },
    );
}

fn create_builtin_function(
    ctx: &mut JSContext,
    internal_name: &str,
    arity: u32,
    display_name: Option<&str>,
) -> JSValue {
    let mut func =
        crate::object::function::JSFunction::new_builtin(ctx.intern(internal_name), arity);
    func.set_builtin_marker(ctx, internal_name);
    let ptr = Box::into_raw(Box::new(func)) as usize;
    ctx.runtime_mut().gc_heap_mut().track_function(ptr);
    if let Some(dname) = display_name {
        let func_obj = unsafe { &mut *(ptr as *mut crate::object::function::JSFunction) };
        func_obj.base.define_property(
            ctx.intern("name"),
            crate::object::object::PropertyDescriptor {
                value: Some(JSValue::new_string(ctx.intern(dname))),
                writable: false,
                enumerable: false,
                configurable: true,
                get: None,
                set: None,
            },
        );
    }
    JSValue::new_function(ptr)
}

pub fn date_now(_ctx: &mut JSContext, _args: &[JSValue]) -> JSValue {
    JSValue::new_float(current_timestamp_ms())
}

pub fn date_parse(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let args = if !args.is_empty() && args[0].is_function() {
        &args[1..]
    } else {
        args
    };
    if args.is_empty() {
        return JSValue::new_float(f64::NAN);
    }

    let date_str = if args[0].is_string() {
        ctx.get_atom_str(args[0].get_atom()).to_string()
    } else {
        return JSValue::new_float(f64::NAN);
    };

    if let Ok(ts) = parse_iso_date(&date_str) {
        let clipped = time_clip(ts);
        return JSValue::new_float(if clipped.is_nan() { f64::NAN } else { ts });
    }

    if let Ok(ts) = parse_common_date(&date_str) {
        let clipped = time_clip(ts);
        return JSValue::new_float(if clipped.is_nan() { f64::NAN } else { ts });
    }

    let ts = parse_date_string(&date_str);
    if !ts.is_nan() {
        let clipped = time_clip(ts);
        return JSValue::new_float(if clipped.is_nan() { f64::NAN } else { ts });
    }

    JSValue::new_float(f64::NAN)
}

fn parse_iso_date(s: &str) -> Result<f64, ()> {
    let s = s.trim();
    let parts: Vec<&str> = s.split('T').collect();
    let date_part = parts[0];

    let (year, rest_date) = if date_part.starts_with('-') {
        let after_sign = &date_part[1..];
        let neg_parts: Vec<&str> = after_sign.splitn(2, '-').collect();
        if neg_parts.len() != 2 {
            let y: i32 = after_sign.parse().map_err(|_| ())?;
            if y == 0 {
                return Err(());
            }
            return Ok(make_day_from_parts(-y, 1, 1, 0, 0, 0, 0));
        }
        let y: i32 = neg_parts[0].parse().map_err(|_| ())?;
        if y == 0 {
            return Err(());
        }
        (-y, neg_parts[1])
    } else {
        let pos_parts: Vec<&str> = date_part.splitn(2, '-').collect();
        if pos_parts.len() != 2 {
            let y: i32 = date_part.parse().map_err(|_| ())?;
            return Ok(make_day_from_parts(y, 1, 1, 0, 0, 0, 0));
        }
        let y: i32 = pos_parts[0].parse().map_err(|_| ())?;
        (y, pos_parts[1])
    };

    let rest_parts: Vec<&str> = rest_date.split('-').collect();
    if rest_parts.len() != 2 {
        return Err(());
    }
    let month: u32 = rest_parts[0].parse().map_err(|_| ())?;
    let day: u32 = rest_parts[1].parse().map_err(|_| ())?;

    let mut hour: u32 = 0;
    let mut minute: u32 = 0;
    let mut second: u32 = 0;
    let mut ms: u32 = 0;

    if parts.len() > 1 {
        let time_part = parts[1].trim_end_matches('Z');
        let time_components: Vec<&str> = time_part.split(':').collect();
        if time_components.len() >= 2 {
            hour = time_components[0].parse().map_err(|_| ())?;
            minute = time_components[1].parse().map_err(|_| ())?;
            if time_components.len() >= 3 {
                let sec_str = time_components[2];
                let sec_parts: Vec<&str> = sec_str.split('.').collect();
                second = sec_parts[0].parse().map_err(|_| ())?;
                if sec_parts.len() > 1 {
                    let frac = sec_parts[1];
                    if frac.len() >= 3 {
                        ms = frac[..3].parse().map_err(|_| ())?;
                    } else {
                        let padded = format!("{:0<3}", frac);
                        ms = padded[..3].parse().map_err(|_| ())?;
                    }
                }
            }
        }
    }

    Ok(make_day_from_parts(
        year, month, day, hour, minute, second, ms,
    ))
}

fn parse_common_date(s: &str) -> Result<f64, ()> {
    let s = s.trim();
    let parts: Vec<&str> = s.split_whitespace().collect();

    if parts.len() >= 3 {
        let mut year: i32 = 0;
        let mut month: u32 = 0;
        let mut day: u32 = 0;
        let hour: u32 = 0;
        let minute: u32 = 0;
        let second: u32 = 0;

        for part in &parts {
            if let Ok(y) = part.parse::<i32>() {
                if y > 1000 {
                    year = y;
                } else if y > 0 && y <= 31 {
                    day = y as u32;
                }
            } else if let Ok(d) = part.parse::<u32>() {
                if d > 0 && d <= 31 {
                    day = d;
                }
            } else {
                let m = month_from_name(part);
                if m > 0 {
                    month = m;
                }
            }
        }

        if year > 0 && month > 0 && day > 0 {
            return Ok(make_day_from_parts(
                year, month, day, hour, minute, second, 0,
            ));
        }
    }

    Err(())
}

fn month_from_name(name: &str) -> u32 {
    match name.to_lowercase().as_str() {
        "jan" | "january" => 1,
        "feb" | "february" => 2,
        "mar" | "march" => 3,
        "apr" | "april" => 4,
        "may" => 5,
        "jun" | "june" => 6,
        "jul" | "july" => 7,
        "aug" | "august" => 8,
        "sep" | "september" => 9,
        "oct" | "october" => 10,
        "nov" | "november" => 11,
        "dec" | "december" => 12,
        _ => 0,
    }
}

fn day_from_year(y: i64) -> i64 {
    365 * (y - 1970) + (y - 1969) / 4 - (y - 1901) / 100 + (y - 1601) / 400
}

fn time_within_day(ms: i64) -> i64 {
    ms % (24 * 60 * 60 * 1000)
}

fn day_from_time(ms: i64) -> i64 {
    ms / (24 * 60 * 60 * 1000)
}

fn days_in_year(y: i32) -> i32 {
    if is_leap_year(y) { 366 } else { 365 }
}

fn day_within_year(y: i32, day: i64) -> i64 {
    day - day_from_year(y as i64)
}

fn month_from_time(ms: i64) -> u32 {
    let day = day_from_time(ms);
    let year = year_from_time(ms);
    let dwy = day_within_year(year, day);
    let leap = is_leap_year(year);
    let month_days: [i64; 12] = if leap {
        [31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366]
    } else {
        [31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365]
    };
    for (i, &d) in month_days.iter().enumerate() {
        if dwy < d {
            return i as u32;
        }
    }
    11
}

fn year_from_time(ms: i64) -> i32 {
    let day = day_from_time(ms);
    let mut y = (day / 365).abs() + 1970;
    if day < 0 {
        y = 1970 - ((-day) / 365 + 1);
    }
    loop {
        let dy = day_from_year(y as i64);
        let dy_next = day_from_year((y + 1) as i64);
        if dy <= day && day < dy_next {
            return y as i32;
        }
        if day < dy {
            y -= 1;
        } else {
            y += 1;
        }
    }
}

fn make_day(year: f64, month: f64, date: f64) -> f64 {
    if year.is_nan() || month.is_nan() || date.is_nan() {
        return f64::NAN;
    }
    let y = year as i64;
    let m = month as i64;
    let d = date as i64;
    let ym = y + (m as f64 / 12.0).floor() as i64;
    let mn = m % 12;
    let mn = if mn < 0 { mn + 12 } else { mn };
    let day =
        day_from_year(ym) + month_to_day_in_year(mn as i32, is_leap_year(ym as i32)) as i64 + d - 1;
    day as f64
}

fn month_to_day_in_year(month: i32, leap: bool) -> i32 {
    let days: [i32; 12] = if leap {
        [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335]
    } else {
        [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]
    };
    if month < 12 && month >= 0 {
        days[month as usize]
    } else {
        0
    }
}

fn make_date(day: f64, time: f64) -> f64 {
    if day.is_nan() || time.is_nan() {
        return f64::NAN;
    }
    day * 24.0 * 60.0 * 60.0 * 1000.0 + time
}

fn make_time(hour: f64, min: f64, sec: f64, ms: f64) -> f64 {
    if hour.is_nan() || min.is_nan() || sec.is_nan() || ms.is_nan() {
        return f64::NAN;
    }
    if !hour.is_finite() || !min.is_finite() || !sec.is_finite() || !ms.is_finite() {
        return f64::NAN;
    }
    let h = hour as i64;
    let m = min as i64;
    let s = sec as i64;
    let milli = ms as i64;
    (h * 3600000 + m * 60000 + s * 1000 + milli) as f64
}

fn make_day_count(year: i32, month: u32, day: u32) -> i64 {
    let mut days: i64 = 0;
    if year >= 1970 {
        for y in 1970..year {
            days += days_in_year(y) as i64;
        }
    } else {
        for y in year..1970 {
            days -= days_in_year(y) as i64;
        }
    }
    let days_in_months = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
    days += days_in_months[(month.saturating_sub(1)).min(11) as usize] as i64;
    if month > 2 && is_leap_year(year) {
        days += 1;
    }
    days += (day.saturating_sub(1)) as i64;
    days
}

fn to_integer_i64(val: f64) -> i64 {
    if val.is_nan() || val.is_infinite() {
        return 0;
    }
    val.trunc() as i64
}

fn to_integer_f64(val: f64) -> f64 {
    if val.is_nan() {
        return 0.0;
    }
    if val.is_infinite() {
        return val;
    }
    val.trunc()
}

fn make_time_ms(hour: f64, minute: f64, second: f64, ms: f64) -> f64 {
    let h = to_integer_f64(hour);
    let m = to_integer_f64(minute);
    let s = to_integer_f64(second);
    let milli = to_integer_f64(ms);
    h * 3600000.0 + m * 60000.0 + s * 1000.0 + milli
}

fn date_to_timestamp_fast(
    year: i32,
    month: u32,
    day: u32,
    hour: u32,
    minute: u32,
    second: u32,
    ms: u32,
) -> f64 {
    let days = make_day_count(year, month, day);
    (days * 86400000
        + hour as i64 * 3600000
        + minute as i64 * 60000
        + second as i64 * 1000
        + ms as i64) as f64
}

fn is_leap_year(year: i32) -> bool {
    (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
}

fn coerce_utc_arg(ctx: &mut JSContext, val: Option<&JSValue>, default: f64) -> f64 {
    match val {
        None => default,
        Some(v) if v.is_undefined() => default,
        Some(v) => match to_number_arg(ctx, v) {
            Ok(n) => n,
            Err(_) => f64::NAN,
        },
    }
}

pub fn date_utc(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let args = if !args.is_empty() && args[0].is_function() {
        &args[1..]
    } else {
        args
    };
    if args.is_empty() {
        return JSValue::new_float(f64::NAN);
    }

    let year_val = match to_number_arg(ctx, &args[0]) {
        Ok(n) => n,
        Err(_) => return JSValue::undefined(),
    };
    if year_val.is_nan() || year_val.is_infinite() {
        return JSValue::new_float(f64::NAN);
    }
    let year_int = to_integer_i64(year_val);
    let mut year = year_int as i32;
    if year_int >= 0 && year_int <= 99 {
        year += 1900;
    }

    let month_val = coerce_utc_arg(ctx, args.get(1), 0.0);
    if month_val.is_nan() || month_val.is_infinite() {
        return JSValue::new_float(f64::NAN);
    }
    let month_raw = month_val as i32;

    let day_val = coerce_utc_arg(ctx, args.get(2), 1.0);
    let hour_val = coerce_utc_arg(ctx, args.get(3), 0.0);
    let minute_val = coerce_utc_arg(ctx, args.get(4), 0.0);
    let second_val = coerce_utc_arg(ctx, args.get(5), 0.0);
    let ms_val = coerce_utc_arg(ctx, args.get(6), 0.0);
    if day_val.is_nan()
        || hour_val.is_nan()
        || minute_val.is_nan()
        || second_val.is_nan()
        || ms_val.is_nan()
    {
        return JSValue::new_float(f64::NAN);
    }
    if day_val.is_infinite()
        || hour_val.is_infinite()
        || minute_val.is_infinite()
        || second_val.is_infinite()
        || ms_val.is_infinite()
    {
        return JSValue::new_float(f64::NAN);
    }

    let mut y = year;
    let mut m = month_raw;
    if m >= 12 || m < 0 {
        let extra_years = m / 12;
        m = m % 12;
        if m < 0 {
            m += 12;
            y -= 1;
        }
        y += extra_years;
    }

    let day_int = to_integer_i64(day_val);
    let day = make_day_count(y, (m as u32) + 1, 1) + day_int - 1;
    let time = make_time_ms(hour_val, minute_val, second_val, ms_val);
    let ts = (day as f64) * 86400000.0 + time;
    JSValue::new_float(time_clip(ts))
}

pub fn date_get_time(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    if args.is_empty() {
        return JSValue::new_float(f64::NAN);
    }

    if let Some(_) = require_date_this(ctx, &args[0]) {
        return JSValue::undefined();
    }

    if args[0].is_object() {
        let obj = args[0].as_object();
        let key = ctx.intern("__dateValue__");
        if let Some(ts) = obj.get(key) {
            return ts;
        }
        let vkey = ctx.common_atoms.__value__;
        if let Some(ts) = obj.get(vkey) {
            return ts;
        }
    }

    JSValue::new_float(f64::NAN)
}

pub fn date_get_full_year(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let this = &args.get(0).cloned().unwrap_or_else(JSValue::undefined);
    if let Some(_) = require_date_this(ctx, this) {
        return JSValue::undefined();
    }
    let timestamp = get_date_timestamp_with_ctx(this, ctx);
    if timestamp.is_nan() {
        return JSValue::new_float(f64::NAN);
    }

    let (year, _, _, _, _, _, _, _) = timestamp_to_date(timestamp);
    JSValue::new_int(year as i64)
}

pub fn date_get_month(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let this = &args.get(0).cloned().unwrap_or_else(JSValue::undefined);
    if let Some(_) = require_date_this(ctx, this) {
        return JSValue::undefined();
    }
    let timestamp = get_date_timestamp_with_ctx(this, ctx);
    if timestamp.is_nan() {
        return JSValue::new_float(f64::NAN);
    }

    let (_, month, _, _, _, _, _, _) = timestamp_to_date(timestamp);
    JSValue::new_int(month as i64 - 1)
}

pub fn date_get_date(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let this = &args.get(0).cloned().unwrap_or_else(JSValue::undefined);
    if let Some(_) = require_date_this(ctx, this) {
        return JSValue::undefined();
    }
    let timestamp = get_date_timestamp_with_ctx(this, ctx);
    if timestamp.is_nan() {
        return JSValue::new_float(f64::NAN);
    }

    let (_, _, day, _, _, _, _, _) = timestamp_to_date(timestamp);
    JSValue::new_int(day as i64)
}

pub fn date_get_day(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let this = &args.get(0).cloned().unwrap_or_else(JSValue::undefined);
    if let Some(_) = require_date_this(ctx, this) {
        return JSValue::undefined();
    }
    let timestamp = get_date_timestamp_with_ctx(this, ctx);
    if timestamp.is_nan() {
        return JSValue::new_float(f64::NAN);
    }

    let (_, _, _, _, _, _, weekday, _) = timestamp_to_date(timestamp);
    JSValue::new_int(weekday as i64)
}

pub fn date_get_hours(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let this = &args.get(0).cloned().unwrap_or_else(JSValue::undefined);
    if let Some(_) = require_date_this(ctx, this) {
        return JSValue::undefined();
    }
    let timestamp = get_date_timestamp_with_ctx(this, ctx);
    if timestamp.is_nan() {
        return JSValue::new_float(f64::NAN);
    }

    let (_, _, _, hour, _, _, _, _) = timestamp_to_date(timestamp);
    JSValue::new_int(hour as i64)
}

pub fn date_get_minutes(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let this = &args.get(0).cloned().unwrap_or_else(JSValue::undefined);
    if let Some(_) = require_date_this(ctx, this) {
        return JSValue::undefined();
    }
    let timestamp = get_date_timestamp_with_ctx(this, ctx);
    if timestamp.is_nan() {
        return JSValue::new_float(f64::NAN);
    }

    let (_, _, _, _, minute, _, _, _) = timestamp_to_date(timestamp);
    JSValue::new_int(minute as i64)
}

pub fn date_get_seconds(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let this = &args.get(0).cloned().unwrap_or_else(JSValue::undefined);
    if let Some(_) = require_date_this(ctx, this) {
        return JSValue::undefined();
    }
    let timestamp = get_date_timestamp_with_ctx(this, ctx);
    if timestamp.is_nan() {
        return JSValue::new_float(f64::NAN);
    }

    let (_, _, _, _, _, second, _, _) = timestamp_to_date(timestamp);
    JSValue::new_int(second as i64)
}

pub fn date_get_milliseconds(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let this = &args.get(0).cloned().unwrap_or_else(JSValue::undefined);
    if let Some(_) = require_date_this(ctx, this) {
        return JSValue::undefined();
    }
    let timestamp = get_date_timestamp_with_ctx(this, ctx);
    if timestamp.is_nan() {
        return JSValue::new_float(f64::NAN);
    }

    let ms = (timestamp % 1000.0).abs();
    JSValue::new_int(ms as i64)
}

pub fn date_get_timezone_offset(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let this = &args.get(0).cloned().unwrap_or_else(JSValue::undefined);
    if let Some(_) = require_date_this(ctx, &this) {
        return JSValue::undefined();
    }
    let ts = get_date_timestamp_with_ctx(&this, ctx);
    if ts.is_nan() {
        return JSValue::new_float(f64::NAN);
    }
    JSValue::new_int(0)
}

fn require_date_this(ctx: &mut JSContext, this: &JSValue) -> Option<JSValue> {
    if !this.is_object() {
        let mut err = JSObject::new();
        err.set(
            ctx.common_atoms.name,
            JSValue::new_string(ctx.intern("TypeError")),
        );
        err.set(
            ctx.common_atoms.message,
            JSValue::new_string(ctx.intern("this is not a Date object")),
        );
        if let Some(proto) = ctx.get_type_error_prototype() {
            err.prototype = Some(proto);
        }
        let ptr = Box::into_raw(Box::new(err)) as usize;
        ctx.runtime_mut().gc_heap_mut().track(ptr);
        ctx.pending_exception = Some(JSValue::new_object(ptr));
        return Some(JSValue::undefined());
    }
    let obj = this.as_object();
    if obj.get(ctx.intern("__dateValue__")).is_none() {
        let has_value = if let Some(v) = obj.get(ctx.common_atoms.__value__) {
            v.is_float() || v.is_int()
        } else {
            false
        };
        if !has_value {
            let mut err = JSObject::new();
            err.set(
                ctx.common_atoms.name,
                JSValue::new_string(ctx.intern("TypeError")),
            );
            err.set(
                ctx.common_atoms.message,
                JSValue::new_string(ctx.intern("this is not a Date object")),
            );
            if let Some(proto) = ctx.get_type_error_prototype() {
                err.prototype = Some(proto);
            }
            let ptr = Box::into_raw(Box::new(err)) as usize;
            ctx.runtime_mut().gc_heap_mut().track(ptr);
            ctx.pending_exception = Some(JSValue::new_object(ptr));
            return Some(JSValue::undefined());
        }
    }
    None
}

macro_rules! date_get_utc_field {
    ($name:ident, $field:expr) => {
        pub fn $name(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
            let this = args.get(0).cloned().unwrap_or_else(JSValue::undefined);
            if let Some(e) = require_date_this(ctx, &this) {
                return e;
            }
            let ts = get_date_timestamp_with_ctx(&this, ctx);
            if ts.is_nan() {
                return JSValue::new_float(f64::NAN);
            }
            let (year, month, day, hour, minute, second, weekday, ms_part) = timestamp_to_date(ts);
            JSValue::new_int($field(
                year, month, day, hour, minute, second, weekday, ms_part,
            ))
        }
    };
}

date_get_utc_field!(date_get_utc_full_year, |y, _, _, _, _, _, _, _| y as i64);
date_get_utc_field!(date_get_utc_month, |_, m, _, _, _, _, _, _| m as i64 - 1);
date_get_utc_field!(date_get_utc_date, |_, _, d, _, _, _, _, _| d as i64);
date_get_utc_field!(date_get_utc_day, |_, _, _, _, _, _, w, _| w as i64);
date_get_utc_field!(date_get_utc_hours, |_, _, _, h, _, _, _, _| h as i64);
date_get_utc_field!(date_get_utc_minutes, |_, _, _, _, m, _, _, _| m as i64);
date_get_utc_field!(date_get_utc_seconds, |_, _, _, _, _, s, _, _| s as i64);

pub fn date_get_utc_milliseconds(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let this = args.get(0).cloned().unwrap_or_else(JSValue::undefined);
    if let Some(e) = require_date_this(ctx, &this) {
        return e;
    }
    let ts = get_date_timestamp_with_ctx(&this, ctx);
    if ts.is_nan() {
        return JSValue::new_float(f64::NAN);
    }
    let ms = (ts % 1000.0).abs();
    JSValue::new_int(ms as i64)
}

fn set_date_timestamp(ctx: &mut JSContext, this: &JSValue, ts: f64) -> JSValue {
    if this.is_object() {
        let obj = this.as_object_mut();
        obj.set(ctx.common_atoms.__value__, JSValue::new_float(ts));
    }
    JSValue::new_float(ts)
}

fn to_number_arg(ctx: &mut JSContext, val: &JSValue) -> Result<f64, ()> {
    if ctx.pending_exception.is_some() {
        return Err(());
    }
    if val.is_int() {
        return Ok(val.get_int() as f64);
    }
    if val.is_float() {
        return Ok(val.get_float());
    }
    if val.is_null() {
        return Ok(0.0);
    }
    if val.is_bool() {
        return Ok(if val.is_truthy() { 1.0 } else { 0.0 });
    }
    if val.is_undefined() {
        return Ok(f64::NAN);
    }
    if val.is_string() {
        let s = ctx.get_atom_str(val.get_atom()).to_string();
        let s = s.trim();
        if s.is_empty() {
            return Ok(0.0);
        }
        if let Ok(n) = s.parse::<i64>() {
            return Ok(n as f64);
        }
        if let Ok(f) = s.parse::<f64>() {
            return Ok(f);
        }
        return Ok(f64::NAN);
    }
    match crate::builtins::global::js_to_number_value(ctx, val) {
        Ok(n) => {
            if ctx.pending_exception.is_some() {
                return Err(());
            }
            Ok(n)
        }
        Err(_) => Err(()),
    }
}

fn coerce_arg(ctx: &mut JSContext, val: Option<&JSValue>) -> Result<Option<f64>, ()> {
    match val {
        None => Ok(None),
        Some(v) => {
            let n = to_number_arg(ctx, v)?;
            if n.is_nan() {
                Ok(Some(f64::NAN))
            } else {
                Ok(Some(n))
            }
        }
    }
}

pub fn date_set_time(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let this = &args.get(0).cloned().unwrap_or_else(JSValue::undefined);
    if let Some(_) = require_date_this(ctx, this) {
        return JSValue::undefined();
    }
    let t = match coerce_arg(ctx, args.get(1)) {
        Ok(Some(n)) => n,
        Ok(None) => f64::NAN,
        Err(()) => return JSValue::undefined(),
    };
    set_date_timestamp(ctx, this, time_clip(t))
}

pub fn date_set_milliseconds(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let this = &args.get(0).cloned().unwrap_or_else(JSValue::undefined);
    if let Some(_) = require_date_this(ctx, this) {
        return JSValue::undefined();
    }
    let ts = get_date_timestamp_with_ctx(this, ctx);
    let ms = match coerce_arg(ctx, args.get(1)) {
        Ok(Some(n)) => n,
        Ok(None) => f64::NAN,
        Err(()) => return JSValue::undefined(),
    };
    if ts.is_nan() {
        return JSValue::new_float(f64::NAN);
    }
    let new_ts = (ts / 1000.0).floor() * 1000.0 + ms;
    set_date_timestamp(ctx, this, time_clip(new_ts))
}

pub fn date_set_seconds(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let this = &args.get(0).cloned().unwrap_or_else(JSValue::undefined);
    if let Some(_) = require_date_this(ctx, this) {
        return JSValue::undefined();
    }
    let ts = get_date_timestamp_with_ctx(this, ctx);
    let sec = match coerce_arg(ctx, args.get(1)) {
        Ok(Some(n)) => n,
        Ok(None) => f64::NAN,
        Err(()) => return JSValue::undefined(),
    };
    let ms = match coerce_arg(ctx, args.get(2)) {
        Ok(Some(n)) => n,
        Ok(None) => {
            if ts.is_nan() {
                0.0
            } else {
                ts % 1000.0
            }
        }
        Err(()) => return JSValue::undefined(),
    };
    if ts.is_nan() {
        return JSValue::new_float(f64::NAN);
    }
    let new_ts = make_date(
        day_from_time(ts as i64) as f64,
        make_time(
            (ts as i64 / 3600000 % 24) as f64,
            (ts as i64 / 60000 % 60) as f64,
            sec,
            ms,
        ),
    );
    set_date_timestamp(ctx, this, time_clip(new_ts))
}

pub fn date_set_minutes(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let this = &args.get(0).cloned().unwrap_or_else(JSValue::undefined);
    if let Some(_) = require_date_this(ctx, this) {
        return JSValue::undefined();
    }
    let ts = get_date_timestamp_with_ctx(this, ctx);
    let min = match coerce_arg(ctx, args.get(1)) {
        Ok(Some(n)) => n,
        Ok(None) => f64::NAN,
        Err(()) => return JSValue::undefined(),
    };
    let sec = match coerce_arg(ctx, args.get(2)) {
        Ok(Some(n)) => n,
        Ok(None) => {
            if ts.is_nan() {
                0.0
            } else {
                ((ts as i64 / 1000) % 60) as f64
            }
        }
        Err(()) => return JSValue::undefined(),
    };
    let ms = match coerce_arg(ctx, args.get(3)) {
        Ok(Some(n)) => n,
        Ok(None) => {
            if ts.is_nan() {
                0.0
            } else {
                ts % 1000.0
            }
        }
        Err(()) => return JSValue::undefined(),
    };
    if ts.is_nan() {
        return JSValue::new_float(f64::NAN);
    }
    let new_ts = make_date(
        day_from_time(ts as i64) as f64,
        make_time((ts as i64 / 3600000 % 24) as f64, min, sec, ms),
    );
    set_date_timestamp(ctx, this, time_clip(new_ts))
}

pub fn date_set_hours(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let this = &args.get(0).cloned().unwrap_or_else(JSValue::undefined);
    if let Some(_) = require_date_this(ctx, this) {
        return JSValue::undefined();
    }
    let ts = get_date_timestamp_with_ctx(this, ctx);
    let hr = match coerce_arg(ctx, args.get(1)) {
        Ok(Some(n)) => n,
        Ok(None) => f64::NAN,
        Err(()) => return JSValue::undefined(),
    };
    let min = match coerce_arg(ctx, args.get(2)) {
        Ok(Some(n)) => n,
        Ok(None) => {
            if ts.is_nan() {
                0.0
            } else {
                ((ts as i64 / 60000) % 60) as f64
            }
        }
        Err(()) => return JSValue::undefined(),
    };
    let sec = match coerce_arg(ctx, args.get(3)) {
        Ok(Some(n)) => n,
        Ok(None) => {
            if ts.is_nan() {
                0.0
            } else {
                ((ts as i64 / 1000) % 60) as f64
            }
        }
        Err(()) => return JSValue::undefined(),
    };
    let ms = match coerce_arg(ctx, args.get(4)) {
        Ok(Some(n)) => n,
        Ok(None) => {
            if ts.is_nan() {
                0.0
            } else {
                ts % 1000.0
            }
        }
        Err(()) => return JSValue::undefined(),
    };
    if ts.is_nan() {
        return JSValue::new_float(f64::NAN);
    }
    let new_ts = make_date(day_from_time(ts as i64) as f64, make_time(hr, min, sec, ms));
    set_date_timestamp(ctx, this, time_clip(new_ts))
}

pub fn date_set_date(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let this = &args.get(0).cloned().unwrap_or_else(JSValue::undefined);
    if let Some(_) = require_date_this(ctx, this) {
        return JSValue::undefined();
    }
    let ts = get_date_timestamp_with_ctx(this, ctx);
    let d = match coerce_arg(ctx, args.get(1)) {
        Ok(Some(n)) => n,
        Ok(None) => f64::NAN,
        Err(()) => return JSValue::undefined(),
    };
    if ts.is_nan() {
        return JSValue::new_float(f64::NAN);
    }
    let tsi = ts as i64;
    let year = year_from_time(tsi);
    let month = month_from_time(tsi);
    let new_ts = make_date(
        make_day(year as f64, month as f64, d),
        time_within_day(tsi) as f64,
    );
    set_date_timestamp(ctx, this, time_clip(new_ts))
}

pub fn date_set_month(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let this = &args.get(0).cloned().unwrap_or_else(JSValue::undefined);
    if let Some(_) = require_date_this(ctx, this) {
        return JSValue::undefined();
    }
    let ts = get_date_timestamp_with_ctx(this, ctx);
    let mo = match coerce_arg(ctx, args.get(1)) {
        Ok(Some(n)) => n,
        Ok(None) => f64::NAN,
        Err(()) => return JSValue::undefined(),
    };
    let d_val = coerce_arg(ctx, args.get(2));
    if ts.is_nan() {
        return JSValue::new_float(f64::NAN);
    }
    let tsi = ts as i64;
    let year = year_from_time(tsi);
    let dt = match d_val {
        Ok(Some(n)) => n,
        Ok(None) => {
            (day_within_year(year, day_from_time(tsi))
                - month_to_day_in_year(month_from_time(tsi) as i32, is_leap_year(year)) as i64
                + 1) as f64
        }
        Err(()) => return JSValue::undefined(),
    };
    let new_ts = make_date(make_day(year as f64, mo, dt), time_within_day(tsi) as f64);
    set_date_timestamp(ctx, this, time_clip(new_ts))
}

pub fn date_set_full_year(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let this = &args.get(0).cloned().unwrap_or_else(JSValue::undefined);
    if let Some(_) = require_date_this(ctx, this) {
        return JSValue::undefined();
    }
    let ts = get_date_timestamp_with_ctx(this, ctx);
    let y = match coerce_arg(ctx, args.get(1)) {
        Ok(Some(n)) => n,
        Ok(None) => f64::NAN,
        Err(()) => return JSValue::undefined(),
    };
    let mo_val = coerce_arg(ctx, args.get(2));
    let d_val = coerce_arg(ctx, args.get(3));
    if ts.is_nan() {
        let dt = match d_val {
            Ok(Some(n)) => n,
            Ok(None) => 1.0,
            Err(()) => return JSValue::undefined(),
        };
        let mo = match mo_val {
            Ok(Some(n)) => n,
            Ok(None) => 0.0,
            Err(()) => return JSValue::undefined(),
        };
        let new_ts = make_date(make_day(y, mo, dt), 0.0);
        return set_date_timestamp(ctx, this, time_clip(new_ts));
    }
    let tsi = ts as i64;
    let year = year_from_time(tsi);
    let month = month_from_time(tsi);
    let dt_from_ts = (day_within_year(year, day_from_time(tsi))
        - month_to_day_in_year(month as i32, is_leap_year(year)) as i64
        + 1) as f64;
    let mo = match mo_val {
        Ok(Some(n)) => n,
        Ok(None) => month as f64,
        Err(()) => return JSValue::undefined(),
    };
    let dt = match d_val {
        Ok(Some(n)) => n,
        Ok(None) => dt_from_ts,
        Err(()) => return JSValue::undefined(),
    };
    let new_ts = make_date(make_day(y, mo, dt), time_within_day(tsi) as f64);
    set_date_timestamp(ctx, this, time_clip(new_ts))
}

pub fn date_set_utc_milliseconds(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    date_set_milliseconds(ctx, args)
}

pub fn date_set_utc_seconds(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    date_set_seconds(ctx, args)
}

pub fn date_set_utc_minutes(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    date_set_minutes(ctx, args)
}

pub fn date_set_utc_hours(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    date_set_hours(ctx, args)
}

pub fn date_set_utc_date(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    date_set_date(ctx, args)
}

pub fn date_set_utc_month(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    date_set_month(ctx, args)
}

pub fn date_set_utc_full_year(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    date_set_full_year(ctx, args)
}

pub fn date_to_string(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let this = &args.get(0).cloned().unwrap_or_else(JSValue::undefined);
    if let Some(_) = require_date_this(ctx, this) {
        return JSValue::undefined();
    }
    let timestamp = get_date_timestamp_with_ctx(this, ctx);
    if timestamp.is_nan() {
        return JSValue::new_string(ctx.intern("Invalid Date"));
    }

    let (year, month, day, hour, minute, second, weekday, _) = timestamp_to_date(timestamp);
    let weekday_names = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
    let month_names = [
        "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
    ];

    let year_str = if year < 0 {
        if year > -10000 {
            format!("{:05}", year)
        } else {
            format!("{}", year)
        }
    } else if year < 10000 {
        format!("{:04}", year)
    } else {
        format!("{:+}", year)
    };

    let result = format!(
        "{} {} {:02} {} {:02}:{:02}:{:02} GMT+0000 (UTC)",
        weekday_names[weekday as usize],
        month_names[(month - 1) as usize],
        day,
        year_str,
        hour,
        minute,
        second
    );

    JSValue::new_string(ctx.intern(&result))
}

pub fn date_to_iso_string(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let this = &args.get(0).cloned().unwrap_or_else(JSValue::undefined);
    if let Some(_) = require_date_this(ctx, this) {
        return JSValue::undefined();
    }
    let timestamp = get_date_timestamp_with_ctx(this, ctx);
    if timestamp.is_nan() {
        let mut err = JSObject::new();
        err.set(
            ctx.common_atoms.name,
            JSValue::new_string(ctx.intern("RangeError")),
        );
        err.set(
            ctx.common_atoms.message,
            JSValue::new_string(ctx.intern("Invalid time value")),
        );
        if let Some(proto) = ctx.get_range_error_prototype() {
            err.prototype = Some(proto);
        }
        let ptr = Box::into_raw(Box::new(err)) as usize;
        ctx.runtime_mut().gc_heap_mut().track(ptr);
        ctx.pending_exception = Some(JSValue::new_object(ptr));
        return JSValue::undefined();
    }

    let (year, month, day, hour, minute, second, _, ms) = timestamp_to_date(timestamp);

    let year_str = if year < 0 {
        if year > -10000 {
            format!("-{:06}", -year)
        } else {
            format!("-{:06}", -year)
        }
    } else if year < 10000 {
        format!("{:04}", year)
    } else {
        format!("+{:06}", year)
    };

    let result = format!(
        "{}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z",
        year_str, month, day, hour, minute, second, ms
    );

    JSValue::new_string(ctx.intern(&result))
}

pub fn date_to_utc_string(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let this = &args.get(0).cloned().unwrap_or_else(JSValue::undefined);
    if let Some(_) = require_date_this(ctx, this) {
        return JSValue::undefined();
    }
    let timestamp = get_date_timestamp_with_ctx(this, ctx);
    if timestamp.is_nan() {
        return JSValue::new_string(ctx.intern("Invalid Date"));
    }

    let (year, month, day, hour, minute, second, weekday, _) = timestamp_to_date(timestamp);
    let weekday_names = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
    let month_names = [
        "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
    ];

    let year_str = if year < 0 {
        if year > -10000 {
            format!("{:05}", year)
        } else {
            format!("{}", year)
        }
    } else if year < 10000 {
        format!("{:04}", year)
    } else {
        format!("{:+}", year)
    };

    let result = format!(
        "{}, {:02} {} {} {:02}:{:02}:{:02} GMT",
        weekday_names[weekday as usize],
        day,
        month_names[(month - 1) as usize],
        year_str,
        hour,
        minute,
        second
    );

    JSValue::new_string(ctx.intern(&result))
}

pub fn date_to_date_string(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let this = &args.get(0).cloned().unwrap_or_else(JSValue::undefined);
    if let Some(_) = require_date_this(ctx, this) {
        return JSValue::undefined();
    }
    let timestamp = get_date_timestamp_with_ctx(this, ctx);
    if timestamp.is_nan() {
        return JSValue::new_string(ctx.intern("Invalid Date"));
    }

    let (year, month, day, _, _, _, weekday, _) = timestamp_to_date(timestamp);
    let weekday_names = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
    let month_names = [
        "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
    ];

    let year_str = if year < 0 {
        if year > -10000 {
            format!("{:05}", year)
        } else {
            format!("{}", year)
        }
    } else if year < 10000 {
        format!("{:04}", year)
    } else {
        format!("{:+}", year)
    };

    let result = format!(
        "{} {} {:02} {}",
        weekday_names[weekday as usize],
        month_names[(month - 1) as usize],
        day,
        year_str
    );

    JSValue::new_string(ctx.intern(&result))
}

pub fn date_to_time_string(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let this = &args.get(0).cloned().unwrap_or_else(JSValue::undefined);
    if let Some(_) = require_date_this(ctx, this) {
        return JSValue::undefined();
    }
    let timestamp = get_date_timestamp_with_ctx(this, ctx);
    if timestamp.is_nan() {
        return JSValue::new_string(ctx.intern("Invalid Date"));
    }

    let (_, _, _, hour, minute, second, _, _) = timestamp_to_date(timestamp);

    let result = format!("{:02}:{:02}:{:02} GMT+0000 (UTC)", hour, minute, second);

    JSValue::new_string(ctx.intern(&result))
}

pub fn date_value_of(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let this = args.get(0).cloned().unwrap_or_else(JSValue::undefined);
    if let Some(_) = require_date_this(ctx, &this) {
        return JSValue::undefined();
    }
    if !this.is_object() {
        return JSValue::new_float(f64::NAN);
    }
    let obj = this.as_object();
    let key = ctx.intern("__dateValue__");
    if let Some(ts) = obj.get(key) {
        return ts;
    }
    let vkey = ctx.common_atoms.__value__;
    if let Some(ts) = obj.get(vkey) {
        return ts;
    }
    JSValue::new_float(f64::NAN)
}

pub fn date_to_json(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let this_val = args.get(0).cloned().unwrap_or_else(JSValue::undefined);
    if !this_val.is_object() {
        let mut err = JSObject::new();
        err.set(
            ctx.common_atoms.name,
            JSValue::new_string(ctx.intern("TypeError")),
        );
        err.set(
            ctx.common_atoms.message,
            JSValue::new_string(ctx.intern("this is not an object")),
        );
        if let Some(proto) = ctx.get_type_error_prototype() {
            err.prototype = Some(proto);
        }
        let ptr = Box::into_raw(Box::new(err)) as usize;
        ctx.runtime_mut().gc_heap_mut().track(ptr);
        ctx.pending_exception = Some(JSValue::new_object(ptr));
        return JSValue::undefined();
    }
    let obj = this_val.as_object();
    let mut tv = JSValue::undefined();
    if let Some(vm_ptr) = ctx.get_register_vm_ptr() {
        let vm = unsafe { &mut *(vm_ptr as *mut crate::runtime::vm::VM) };
        for method_name in &["valueOf", "toString"] {
            let method_key = ctx.intern(method_name);
            if let Some(method) = obj.get(method_key) {
                if method.is_function() {
                    match vm.call_function_with_this(ctx, method, this_val, &[]) {
                        Ok(result) => {
                            if !result.is_object() {
                                tv = result;
                                break;
                            }
                        }
                        Err(_) => {
                            if let Some(exc) = vm.last_caught_exception.take() {
                                ctx.pending_exception = Some(exc);
                            }
                            return JSValue::undefined();
                        }
                    }
                }
            }
        }
    }
    if tv.is_float() {
        let f = tv.get_float();
        if f.is_nan() || f.is_infinite() {
            return JSValue::null();
        }
    }
    if tv.is_int() {
    } else if tv.is_string() {
    }
    let to_iso = obj.get(ctx.intern("toISOString"));
    if let Some(fn_val) = to_iso {
        if fn_val.is_function() {
            if let Some(vm_ptr) = ctx.get_register_vm_ptr() {
                let vm = unsafe { &mut *(vm_ptr as *mut crate::runtime::vm::VM) };
                match vm.call_function_with_this(ctx, fn_val, this_val, &[]) {
                    Ok(val) => return val,
                    Err(_) => {
                        if let Some(exc) = vm.last_caught_exception.take() {
                            ctx.pending_exception = Some(exc);
                        }
                        return JSValue::undefined();
                    }
                }
            }
        }
    }
    JSValue::undefined()
}

pub fn init_date_to_primitive(ctx: &mut JSContext) {
    let global = ctx.global();
    if !global.is_object() {
        return;
    }
    let global_obj = global.as_object();

    let stp_key = ctx.intern("Symbol.toPrimitive");
    let sym_val = match global_obj.get(stp_key) {
        Some(s) if s.is_symbol() => s,
        _ => return,
    };
    let sym_atom = crate::runtime::atom::Atom(0x40000000 | sym_val.get_symbol_id());

    let date_key = ctx.intern("Date");
    let date_ctor = match global_obj.get(date_key) {
        Some(v) if v.is_function() => v,
        _ => return,
    };
    let proto_key = ctx.intern("prototype");
    let proto_val = date_ctor.as_object().get(proto_key);
    if let Some(pv) = proto_val {
        if pv.is_object() {
            let fn_val =
                create_builtin_function(ctx, "date_toPrimitive", 1, Some("[Symbol.toPrimitive]"));
            pv.as_object_mut().define_property(
                sym_atom,
                crate::object::object::PropertyDescriptor {
                    value: Some(fn_val),
                    writable: false,
                    enumerable: false,
                    configurable: true,
                    get: None,
                    set: None,
                },
            );
        }
    }
}

pub fn date_to_primitive(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let this = &args.get(0).cloned().unwrap_or_else(JSValue::undefined);
    if !this.is_object() {
        let mut err = JSObject::new();
        err.set(
            ctx.common_atoms.name,
            JSValue::new_string(ctx.intern("TypeError")),
        );
        err.set(
            ctx.common_atoms.message,
            JSValue::new_string(ctx.intern("this is not an object")),
        );
        if let Some(proto) = ctx.get_type_error_prototype() {
            err.prototype = Some(proto);
        }
        let ptr = Box::into_raw(Box::new(err)) as usize;
        ctx.runtime_mut().gc_heap_mut().track(ptr);
        ctx.pending_exception = Some(JSValue::new_object(ptr));
        return JSValue::undefined();
    }
    let hint_val = args.get(1).cloned().unwrap_or_else(|| JSValue::undefined());
    let hint = if hint_val.is_string() {
        ctx.get_atom_str(hint_val.get_atom())
    } else {
        ""
    };
    let obj = this.as_object();
    let (first_method, second_method) = match hint {
        "string" | "default" => ("toString", "valueOf"),
        "number" => ("valueOf", "toString"),
        _ => {
            let mut err = JSObject::new();
            err.set(
                ctx.common_atoms.name,
                JSValue::new_string(ctx.intern("TypeError")),
            );
            err.set(
                ctx.common_atoms.message,
                JSValue::new_string(ctx.intern("invalid hint")),
            );
            if let Some(proto) = ctx.get_type_error_prototype() {
                err.prototype = Some(proto);
            }
            let ptr = Box::into_raw(Box::new(err)) as usize;
            ctx.runtime_mut().gc_heap_mut().track(ptr);
            ctx.pending_exception = Some(JSValue::new_object(ptr));
            return JSValue::undefined();
        }
    };
    if let Some(vm_ptr) = ctx.get_register_vm_ptr() {
        let vm = unsafe { &mut *(vm_ptr as *mut crate::runtime::vm::VM) };
        for method_name in &[first_method, second_method] {
            let method_key = ctx.intern(method_name);
            if let Some(method) = obj.get(method_key) {
                if method.is_function() {
                    match vm.call_function_with_this(ctx, method, *this, &[]) {
                        Ok(result) => {
                            if !result.is_object() {
                                return result;
                            }
                        }
                        Err(_) => {
                            if let Some(exc) = vm.last_caught_exception.take() {
                                ctx.pending_exception = Some(exc);
                            }
                            return JSValue::undefined();
                        }
                    }
                }
            }
        }
    }
    let mut err = JSObject::new();
    err.set(
        ctx.common_atoms.name,
        JSValue::new_string(ctx.intern("TypeError")),
    );
    err.set(
        ctx.common_atoms.message,
        JSValue::new_string(ctx.intern("can't convert to primitive")),
    );
    if let Some(proto) = ctx.get_type_error_prototype() {
        err.prototype = Some(proto);
    }
    let ptr = Box::into_raw(Box::new(err)) as usize;
    ctx.runtime_mut().gc_heap_mut().track(ptr);
    ctx.pending_exception = Some(JSValue::new_object(ptr));
    JSValue::undefined()
}

fn get_date_timestamp_with_ctx(this: &JSValue, ctx: &mut JSContext) -> f64 {
    if this.is_object() {
        let obj = this.as_object();

        if let Some(ts) = obj.get(ctx.intern("__dateValue__")) {
            if ts.is_int() {
                return ts.get_int() as f64;
            } else if ts.is_float() {
                return ts.get_float();
            }
        }

        if let Some(ts) = obj.get(ctx.common_atoms.__value__) {
            if ts.is_int() {
                return ts.get_int() as f64;
            } else if ts.is_float() {
                return ts.get_float();
            }
        }
    }
    f64::NAN
}

fn timestamp_to_date(ms: f64) -> (i32, u32, u32, u32, u32, u32, u32, u32) {
    if ms.is_nan() || ms.is_infinite() {
        return (0, 0, 0, 0, 0, 0, 0, 0);
    }

    let total_ms = ms as i64;
    let total_seconds = total_ms / 1000;
    let total_minutes = total_seconds / 60;
    let total_hours = total_minutes / 60;
    let total_days = total_hours / 24;

    let mut year = 1970;
    let mut remaining_days = total_days;

    if remaining_days >= 0 {
        while remaining_days >= days_in_year(year) as i64 {
            remaining_days -= days_in_year(year) as i64;
            year += 1;
        }
    } else {
        while remaining_days < 0 {
            year -= 1;
            remaining_days += days_in_year(year) as i64;
        }
    }

    let days_in_months = if is_leap_year(year) {
        [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
    } else {
        [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
    };

    let mut month = 1;
    for &days in &days_in_months {
        if remaining_days < days as i64 {
            break;
        }
        remaining_days -= days as i64;
        month += 1;
    }

    let day = (remaining_days + 1) as u32;
    let hour = ((total_hours % 24 + 24) % 24) as u32;
    let minute = ((total_minutes % 60 + 60) % 60) as u32;
    let second = ((total_seconds % 60 + 60) % 60) as u32;
    let ms_part = (total_ms % 1000 + 1000) % 1000;
    let weekday = ((total_days % 7 + 4 + 7) % 7) as u32;

    (
        year,
        month,
        day,
        hour,
        minute,
        second,
        weekday,
        ms_part as u32,
    )
}

fn make_day_from_parts(
    year: i32,
    month: u32,
    day: u32,
    hour: u32,
    minute: u32,
    second: u32,
    ms: u32,
) -> f64 {
    date_to_timestamp_fast(year, month, day, hour, minute, second, ms)
}

fn time_clip(t: f64) -> f64 {
    if t.is_nan() || t.abs() > 8.64e15 {
        return f64::NAN;
    }
    t + 0.0
}

pub fn date_constructor(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let timestamp = if args.is_empty() {
        current_timestamp_ms()
    } else if args.len() == 1 {
        if args[0].is_string() {
            let s = ctx.get_atom_str(args[0].get_atom()).to_string();
            let parsed = parse_date_string(&s);
            if !parsed.is_nan() {
                time_clip(parsed)
            } else {
                match to_number_arg(ctx, &args[0]) {
                    Ok(n) => n,
                    Err(()) => f64::NAN,
                }
            }
        } else {
            match to_number_arg(ctx, &args[0]) {
                Ok(n) => n,
                Err(()) => return JSValue::undefined(),
            }
        }
    } else {
        let year_val = match to_number_arg(ctx, &args[0]) {
            Ok(n) => n,
            Err(()) => return JSValue::undefined(),
        };
        let mut year = to_integer_f64(year_val);
        if year >= 0.0 && year <= 99.0 {
            year += 1900.0;
        }
        let month_raw = match coerce_utc_arg(ctx, args.get(1), 0.0) {
            n if n.is_nan() => return JSValue::new_float(f64::NAN),
            n => n,
        };
        let day = coerce_utc_arg(ctx, args.get(2), 1.0);
        if day.is_nan() {
            return JSValue::new_float(f64::NAN);
        }
        let hour = coerce_utc_arg(ctx, args.get(3), 0.0);
        if hour.is_nan() {
            return JSValue::new_float(f64::NAN);
        }
        let minute = coerce_utc_arg(ctx, args.get(4), 0.0);
        if minute.is_nan() {
            return JSValue::new_float(f64::NAN);
        }
        let second = coerce_utc_arg(ctx, args.get(5), 0.0);
        if second.is_nan() {
            return JSValue::new_float(f64::NAN);
        }
        let ms = coerce_utc_arg(ctx, args.get(6), 0.0);
        if ms.is_nan() {
            return JSValue::new_float(f64::NAN);
        }

        let day_val = make_day(year, month_raw, day);
        let time_val = make_time_ms(hour, minute, second, ms);
        make_date(day_val, time_val)
    };

    if timestamp.is_nan() {
        return JSValue::new_float(f64::NAN);
    }

    let clipped = time_clip(timestamp);
    if clipped == 0.0 && clipped.is_sign_positive() {
        return JSValue::new_float(0.0);
    }
    JSValue::new_float(clipped)
}
