use crate::host::HostFunction;
use crate::object::object::JSObject;
use crate::runtime::context::JSContext;
use crate::value::JSValue;

fn create_builtin_function(ctx: &mut JSContext, name: &str) -> JSValue {
    let mut func = crate::object::function::JSFunction::new_builtin(ctx.intern(name), 1);
    func.set_builtin_marker(ctx, name);
    let ptr = Box::into_raw(Box::new(func)) as usize;
    ctx.runtime_mut().gc_heap_mut().track_function(ptr);
    JSValue::new_function(ptr)
}

pub fn init_string(ctx: &mut JSContext) {
    let string_atom = ctx.common_atoms.string;

    let mut string_func = crate::object::function::JSFunction::new_builtin(string_atom, 1);
    string_func.set_builtin_marker(ctx, "string_constructor");

    string_func.base.set(
        ctx.intern("fromCharCode"),
        create_builtin_function(ctx, "string_fromCharCode"),
    );
    string_func.base.set(
        ctx.intern("fromCodePoint"),
        create_builtin_function(ctx, "string_fromCodePoint"),
    );

    let string_ptr = Box::into_raw(Box::new(string_func)) as usize;
    ctx.runtime_mut().gc_heap_mut().track_function(string_ptr);
    let string_value = JSValue::new_function(string_ptr);
    let global = ctx.global();
    if global.is_object() {
        let global_obj = global.as_object_mut();
        global_obj.set(string_atom, string_value);
    }

    let proto_atom = ctx.intern("StringPrototype");
    let mut proto_obj = JSObject::new();
    proto_obj.set(
        ctx.intern("charAt"),
        create_builtin_function(ctx, "string_charAt"),
    );
    proto_obj.set(
        ctx.intern("charCodeAt"),
        create_builtin_function(ctx, "string_charCodeAt"),
    );
    proto_obj.set(
        ctx.intern("concat"),
        create_builtin_function(ctx, "string_concat"),
    );
    proto_obj.set(
        ctx.intern("indexOf"),
        create_builtin_function(ctx, "string_indexOf"),
    );
    proto_obj.set(
        ctx.intern("lastIndexOf"),
        create_builtin_function(ctx, "string_lastIndexOf"),
    );
    proto_obj.set(
        ctx.intern("slice"),
        create_builtin_function(ctx, "string_slice"),
    );
    proto_obj.set(
        ctx.intern("substring"),
        create_builtin_function(ctx, "string_substring"),
    );
    proto_obj.set(
        ctx.intern("toString"),
        create_builtin_function(ctx, "string_toString"),
    );
    proto_obj.set(
        ctx.intern("valueOf"),
        create_builtin_function(ctx, "string_toString"),
    );
    proto_obj.set(
        ctx.intern("toLowerCase"),
        create_builtin_function(ctx, "string_toLowerCase"),
    );
    proto_obj.set(
        ctx.intern("toUpperCase"),
        create_builtin_function(ctx, "string_toUpperCase"),
    );
    proto_obj.set(
        ctx.intern("split"),
        create_builtin_function(ctx, "string_split"),
    );
    proto_obj.set(
        ctx.common_atoms.length,
        create_builtin_function(ctx, "string_length"),
    );
    proto_obj.set(
        ctx.intern("trim"),
        create_builtin_function(ctx, "string_trim"),
    );
    proto_obj.set(
        ctx.intern("trimStart"),
        create_builtin_function(ctx, "string_trimStart"),
    );
    proto_obj.set(
        ctx.intern("trimLeft"),
        create_builtin_function(ctx, "string_trimStart"),
    );
    proto_obj.set(
        ctx.intern("trimEnd"),
        create_builtin_function(ctx, "string_trimEnd"),
    );
    proto_obj.set(
        ctx.intern("trimRight"),
        create_builtin_function(ctx, "string_trimEnd"),
    );
    proto_obj.set(
        ctx.intern("startsWith"),
        create_builtin_function(ctx, "string_startsWith"),
    );
    proto_obj.set(
        ctx.intern("endsWith"),
        create_builtin_function(ctx, "string_endsWith"),
    );
    proto_obj.set(
        ctx.intern("repeat"),
        create_builtin_function(ctx, "string_repeat"),
    );
    proto_obj.set(
        ctx.intern("includes"),
        create_builtin_function(ctx, "string_includes"),
    );
    proto_obj.set(
        ctx.intern("replace"),
        create_builtin_function(ctx, "string_replace"),
    );
    proto_obj.set(
        ctx.intern("padStart"),
        create_builtin_function(ctx, "string_padStart"),
    );
    proto_obj.set(
        ctx.intern("padEnd"),
        create_builtin_function(ctx, "string_padEnd"),
    );
    proto_obj.set(
        ctx.intern("replaceAll"),
        create_builtin_function(ctx, "string_replaceAll"),
    );
    proto_obj.set(
        ctx.intern("search"),
        create_builtin_function(ctx, "string_search"),
    );
    proto_obj.set(
        ctx.intern("match"),
        create_builtin_function(ctx, "string_match"),
    );
    proto_obj.set(ctx.intern("at"), create_builtin_function(ctx, "string_at"));
    proto_obj.set(
        ctx.intern("isWellFormed"),
        create_builtin_function(ctx, "string_isWellFormed"),
    );
    proto_obj.set(
        ctx.intern("toWellFormed"),
        create_builtin_function(ctx, "string_toWellFormed"),
    );
    proto_obj.set(
        ctx.intern("codePointAt"),
        create_builtin_function(ctx, "string_codePointAt"),
    );
    proto_obj.set(
        ctx.intern("matchAll"),
        create_builtin_function(ctx, "string_matchAll"),
    );
    proto_obj.set(
        ctx.intern("substr"),
        create_builtin_function(ctx, "string_substr"),
    );

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

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

    ctx.set_string_prototype(proto_ptr);
    let proto_value = JSValue::new_object(proto_ptr);

    let string_func_ptr = string_ptr as *mut crate::object::function::JSFunction;
    unsafe {
        (*string_func_ptr)
            .base
            .set(ctx.common_atoms.prototype, proto_value);
    }

    if global.is_object() {
        let global_obj = global.as_object_mut();
        global_obj.set(proto_atom, proto_value);
    }
}

pub fn register_builtins(ctx: &mut JSContext) {
    ctx.register_builtin(
        "string_constructor",
        HostFunction::new("String", 1, string_constructor),
    );
    ctx.register_builtin(
        "string_charAt",
        HostFunction::new("charAt", 1, string_char_at),
    );
    ctx.register_builtin(
        "string_charCodeAt",
        HostFunction::new("charCodeAt", 1, string_char_code_at),
    );
    ctx.register_builtin(
        "string_concat",
        HostFunction::new("concat", 1, string_concat),
    );
    ctx.register_builtin(
        "string_indexOf",
        HostFunction::new("indexOf", 1, string_index_of),
    );
    ctx.register_builtin(
        "string_lastIndexOf",
        HostFunction::new("lastIndexOf", 1, string_last_index_of),
    );
    ctx.register_builtin("string_slice", HostFunction::new("slice", 2, string_slice));
    ctx.register_builtin(
        "string_substring",
        HostFunction::new("substring", 2, string_substring),
    );
    ctx.register_builtin(
        "string_toString",
        HostFunction::new("toString", 0, string_to_string),
    );
    ctx.register_builtin(
        "string_toLowerCase",
        HostFunction::new("toLowerCase", 0, string_to_lower_case),
    );
    ctx.register_builtin(
        "string_toUpperCase",
        HostFunction::new("toUpperCase", 0, string_to_upper_case),
    );
    ctx.register_builtin("string_split", HostFunction::new("split", 1, string_split));
    ctx.register_builtin(
        "string_length",
        HostFunction::new("length", 0, string_length),
    );
    ctx.register_builtin(
        "string_fromCharCode",
        HostFunction::new("fromCharCode", 1, string_fromcharcode),
    );
    ctx.register_builtin(
        "string_fromCodePoint",
        HostFunction::new("fromCodePoint", 1, string_fromcodepoint),
    );
    ctx.register_builtin("string_trim", HostFunction::new("trim", 0, string_trim));
    ctx.register_builtin(
        "string_trimStart",
        HostFunction::new("trimStart", 0, string_trim_start),
    );
    ctx.register_builtin(
        "string_trimEnd",
        HostFunction::new("trimEnd", 0, string_trim_end),
    );
    ctx.register_builtin(
        "string_startsWith",
        HostFunction::new("startsWith", 1, string_starts_with),
    );
    ctx.register_builtin(
        "string_endsWith",
        HostFunction::new("endsWith", 1, string_ends_with),
    );
    ctx.register_builtin(
        "string_repeat",
        HostFunction::new("repeat", 1, string_repeat),
    );
    ctx.register_builtin(
        "string_includes",
        HostFunction::new("includes", 1, string_includes),
    );
    ctx.register_builtin(
        "string_replace",
        HostFunction::new("replace", 2, string_replace),
    );
    ctx.register_builtin(
        "string_padStart",
        HostFunction::new("padStart", 1, string_pad_start),
    );
    ctx.register_builtin(
        "string_padEnd",
        HostFunction::new("padEnd", 1, string_pad_end),
    );
    ctx.register_builtin(
        "string_replaceAll",
        HostFunction::new("replaceAll", 2, string_replace_all),
    );
    ctx.register_builtin(
        "string_search",
        HostFunction::new("search", 1, string_search),
    );
    ctx.register_builtin("string_match", HostFunction::new("match", 1, string_match));
    ctx.register_builtin("string_at", HostFunction::new("at", 1, string_at));
    ctx.register_builtin(
        "string_isWellFormed",
        HostFunction::new("isWellFormed", 0, string_is_well_formed),
    );
    ctx.register_builtin(
        "string_toWellFormed",
        HostFunction::new("toWellFormed", 0, string_to_well_formed),
    );
    ctx.register_builtin(
        "string_codePointAt",
        HostFunction::new("codePointAt", 1, string_code_point_at),
    );
    ctx.register_builtin(
        "string_matchAll",
        HostFunction::new("matchAll", 1, string_match_all),
    );
    ctx.register_builtin(
        "string_substr",
        HostFunction::new("substr", 2, string_substr),
    );
}

fn string_constructor(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let s = if args.is_empty() {
        JSValue::new_string(ctx.intern(""))
    } else {
        let val = &args[0];
        if val.is_string() {
            val.clone()
        } else if val.is_int() {
            JSValue::new_string(ctx.intern(&val.get_int().to_string()))
        } else if val.is_float() {
            JSValue::new_string(ctx.intern(&val.get_float().to_string()))
        } else if val.is_bigint() {
            let obj = unsafe { crate::value::JSValue::object_from_ptr(val.get_ptr()) };
            let n = obj.get_bigint_value();
            JSValue::new_string(ctx.intern(&n.to_string()))
        } else if val.is_bool() {
            JSValue::new_string(ctx.intern(&val.get_bool().to_string()))
        } else if val.is_null() {
            JSValue::new_string(ctx.common_atoms.null)
        } else if val.is_undefined() {
            JSValue::new_string(ctx.common_atoms.undefined)
        } else if val.is_object() {
            JSValue::new_string(ctx.intern("[object Object]"))
        } else {
            JSValue::new_string(ctx.intern(""))
        }
    };
    s
}

fn char_count(s: &str) -> usize {
    s.chars().count()
}

fn byte_index_for_char_pos(s: &str, pos: usize) -> usize {
    if pos == 0 {
        return 0;
    }
    match s.char_indices().nth(pos) {
        Some((idx, _)) => idx,
        None => s.len(),
    }
}

fn string_char_at(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    if args.is_empty() {
        return JSValue::new_string(ctx.intern(""));
    }

    let atom = if args[0].is_string() {
        args[0].get_atom()
    } else {
        return JSValue::new_string(ctx.intern(""));
    };

    let index = if args.len() > 1 {
        args[1].get_int() as usize
    } else {
        0
    };

    if index >= ctx.string_char_count(atom) {
        return JSValue::new_string(ctx.intern(""));
    }

    match ctx.string_char_code_at(atom, index) {
        Some(code) => {
            let c = char::from_u32(code).unwrap_or('\0');
            let mut buf = [0u8; 4];
            let s = c.encode_utf8(&mut buf);
            JSValue::new_string(ctx.intern(s))
        }
        None => JSValue::new_string(ctx.intern("")),
    }
}

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

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

    let index = if args.len() > 1 {
        let v = args[1];
        if v.is_int() {
            let i = v.get_int();
            if i < 0 {
                return JSValue::new_float(f64::NAN);
            }
            i as usize
        } else if v.is_float() {
            let f = v.get_float();
            if f < 0.0 || f.is_nan() || f.is_infinite() {
                return JSValue::new_float(f64::NAN);
            }
            f as usize
        } else {
            0
        }
    } else {
        0
    };

    match ctx.string_char_code_at(atom, index) {
        Some(code) => JSValue::new_int(code as i64),
        None => JSValue::new_float(f64::NAN),
    }
}

fn string_concat(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    if args.is_empty() {
        return JSValue::new_string(ctx.intern(""));
    }

    let mut result = if args[0].is_string() {
        ctx.get_atom_str(args[0].get_atom()).to_string()
    } else {
        String::new()
    };

    for arg in args.iter().skip(1) {
        if arg.is_string() {
            result.push_str(ctx.get_atom_str(arg.get_atom()));
        } else if arg.is_int() {
            result.push_str(&arg.get_int().to_string());
        } else if arg.is_float() {
            result.push_str(&arg.get_float().to_string());
        } else if arg.is_bool() {
            result.push_str(&arg.get_bool().to_string());
        }
    }

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

fn string_index_of(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    if args.len() < 2 {
        return JSValue::new_int(-1);
    }

    let s = if args[0].is_string() {
        ctx.get_atom_str(args[0].get_atom()).to_string()
    } else {
        return JSValue::new_int(-1);
    };

    let search = if args[1].is_string() {
        ctx.get_atom_str(args[1].get_atom()).to_string()
    } else {
        return JSValue::new_int(-1);
    };

    let from_index = if args.len() > 2 {
        args[2].get_int().max(0) as usize
    } else {
        0
    };

    let s_char_len = char_count(&s);
    if from_index > s_char_len {
        return JSValue::new_int(-1);
    }

    let from_byte = byte_index_for_char_pos(&s, from_index);

    match s[from_byte..].find(&search) {
        Some(byte_pos) => {
            let byte_idx = from_byte + byte_pos;
            let char_idx = s[..byte_idx].chars().count();
            JSValue::new_int(char_idx as i64)
        }
        None => JSValue::new_int(-1),
    }
}

fn string_last_index_of(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    if args.len() < 2 {
        return JSValue::new_int(-1);
    }

    let s = if args[0].is_string() {
        ctx.get_atom_str(args[0].get_atom()).to_string()
    } else {
        return JSValue::new_int(-1);
    };

    let search = if args[1].is_string() {
        ctx.get_atom_str(args[1].get_atom()).to_string()
    } else {
        return JSValue::new_int(-1);
    };

    match s.rfind(&search) {
        Some(byte_pos) => {
            let char_idx = s[..byte_pos].chars().count();
            JSValue::new_int(char_idx as i64)
        }
        None => JSValue::new_int(-1),
    }
}

fn string_substring(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    if args.is_empty() {
        return JSValue::new_string(ctx.intern(""));
    }

    let s = if args[0].is_string() {
        ctx.get_atom_str(args[0].get_atom()).to_string()
    } else {
        return JSValue::new_string(ctx.intern(""));
    };

    let len = char_count(&s);
    let start = if args.len() > 1 {
        let v = args[1].get_int().max(0) as usize;
        v.min(len)
    } else {
        0
    };

    let end = if args.len() > 2 {
        let v = args[2].get_int().max(0) as usize;
        v.min(len)
    } else {
        len
    };

    let (start, end) = if start > end {
        (end, start)
    } else {
        (start, end)
    };

    let start_b = byte_index_for_char_pos(&s, start);
    let end_b = byte_index_for_char_pos(&s, end);
    JSValue::new_string(ctx.intern(&s[start_b..end_b]))
}

fn string_slice(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    if args.is_empty() {
        return JSValue::new_string(ctx.intern(""));
    }

    let s = if args[0].is_string() {
        ctx.get_atom_str(args[0].get_atom()).to_string()
    } else {
        return JSValue::new_string(ctx.intern(""));
    };

    let len = char_count(&s);
    let start = if args.len() > 1 {
        let v = args[1].get_int();
        if v < 0 {
            (len as i64 + v).max(0) as usize
        } else {
            (v as usize).min(len)
        }
    } else {
        0
    };

    let end = if args.len() > 2 {
        let v = args[2].get_int();
        if v < 0 {
            (len as i64 + v).max(0) as usize
        } else {
            (v as usize).min(len)
        }
    } else {
        len
    };

    if start >= end {
        return JSValue::new_string(ctx.intern(""));
    }

    let start_b = byte_index_for_char_pos(&s, start);
    let end_b = byte_index_for_char_pos(&s, end);
    JSValue::new_string(ctx.intern(&s[start_b..end_b]))
}

fn string_to_string(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    if args.is_empty() {
        return JSValue::new_string(ctx.intern(""));
    }

    if args[0].is_string() {
        return args[0].clone();
    }

    if args[0].is_object() {
        let obj = args[0].as_object();
        let prim = obj.get(ctx.common_atoms.__value__);
        if let Some(v) = prim {
            if v.is_string() {
                return v;
            }
        }
    }
    args[0].clone()
}

fn string_to_lower_case(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    if args.is_empty() {
        return JSValue::new_string(ctx.intern(""));
    }

    let s = if args[0].is_string() {
        ctx.get_atom_str(args[0].get_atom()).to_lowercase()
    } else {
        return JSValue::new_string(ctx.intern(""));
    };

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

fn string_to_upper_case(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    if args.is_empty() {
        return JSValue::new_string(ctx.intern(""));
    }

    let s = if args[0].is_string() {
        ctx.get_atom_str(args[0].get_atom()).to_uppercase()
    } else {
        return JSValue::new_string(ctx.intern(""));
    };

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

fn string_split(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    if args.is_empty() {
        let mut result = JSObject::new_array();
        result.set(ctx.common_atoms.length, JSValue::new_int(0));
        let ptr = Box::into_raw(Box::new(result)) as usize;
        return JSValue::new_object(ptr);
    }

    let s_atom = if args[0].is_string() {
        args[0].get_atom()
    } else {
        let mut result = JSObject::new_array();
        result.set(ctx.common_atoms.length, JSValue::new_int(0));
        let ptr = Box::into_raw(Box::new(result)) as usize;
        return JSValue::new_object(ptr);
    };

    let length_atom = ctx.common_atoms.length;

    if args.len() > 1 && args[1].is_object() {
        let sep_obj = args[1].as_object();
        let pattern_atom = ctx.common_atoms.__pattern__;
        if sep_obj.get(pattern_atom).is_some() {
            let s = ctx.get_atom_str(s_atom).to_string();
            let limit = if args.len() > 2 && args[2].is_int() {
                args[2].get_int() as usize
            } else {
                usize::MAX
            };

            let mut parts: Vec<String> = Vec::new();
            let compiled_re = args[1]
                .as_object()
                .get_compiled_regex()
                .map(|re| re as *const crate::regexp::Regex);
            if let Some(re_ptr) = compiled_re {
                let re = unsafe { &*re_ptr };
                let mut last = 0usize;
                let mut search_start = 0usize;
                loop {
                    if parts.len() >= limit {
                        break;
                    }
                    if search_start > s.len() {
                        break;
                    }
                    let sub = &s[search_start..];
                    if let Some(m) = re.find(sub) {
                        let match_start = search_start + m.start();
                        let match_end = search_start + m.end();
                        if match_end == search_start && match_start == last {
                            search_start += s[search_start..]
                                .chars()
                                .next()
                                .map(|c| c.len_utf8())
                                .unwrap_or(1);
                            continue;
                        }
                        parts.push(s[last..match_start].to_string());

                        for cap in m.iter().skip(1) {
                            parts.push(cap.unwrap_or("").to_string());
                        }
                        last = match_end;
                        search_start = match_end;
                        if match_end == match_start {
                            search_start += s[search_start..]
                                .chars()
                                .next()
                                .map(|c| c.len_utf8())
                                .unwrap_or(1);
                        }
                    } else {
                        break;
                    }
                }
                if parts.len() < limit {
                    parts.push(s[last..].to_string());
                }
            } else {
                parts.push(s);
            }

            let mut result = JSObject::new_array();
            for (i, part) in parts.iter().enumerate() {
                let key = ctx.int_atom_mut(i);
                let part_atom = ctx.intern(part);
                result.set(key, JSValue::new_string(part_atom));
            }
            result.set(length_atom, JSValue::new_int(parts.len() as i64));
            let ptr = Box::into_raw(Box::new(result)) as usize;
            return JSValue::new_object(ptr);
        }
    }

    let sep_atom = if args.len() > 1 && args[1].is_string() {
        Some(args[1].get_atom())
    } else {
        None
    };

    let s = ctx.get_atom_str(s_atom);
    let separator = sep_atom.map(|a| ctx.get_atom_str(a)).unwrap_or("");

    let mut result = JSObject::new_array();

    if separator.is_empty() {
        let chars: Vec<char> = s.chars().collect();
        for (i, c) in chars.iter().enumerate() {
            let key = ctx.int_atom_mut(i);
            let c_str = c.to_string();
            let c_atom = ctx.intern(&c_str);
            result.set(key, JSValue::new_string(c_atom));
        }
        result.set(length_atom, JSValue::new_int(chars.len() as i64));
    } else {
        let parts: Vec<String> = s.split(separator).map(|s| s.to_string()).collect();
        for (i, part) in parts.iter().enumerate() {
            let key = ctx.int_atom_mut(i);
            let part_atom = ctx.intern(part);
            result.set(key, JSValue::new_string(part_atom));
        }
        result.set(length_atom, JSValue::new_int(parts.len() as i64));
    }

    let ptr = Box::into_raw(Box::new(result)) as usize;
    JSValue::new_object(ptr)
}

fn string_length(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    if args.is_empty() {
        return JSValue::new_int(0);
    }

    if args[0].is_string() {
        JSValue::new_int(ctx.string_char_count(args[0].get_atom()) as i64)
    } else {
        JSValue::new_int(0)
    }
}

fn from_str_or_hex(s: &str) -> u32 {
    let s = s.trim();
    if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
        u32::from_str_radix(hex, 16).unwrap_or(0)
    } else {
        s.parse::<f64>().unwrap_or(0.0) as u32
    }
}

fn string_fromcharcode(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let mut result = String::new();
    for arg in args {
        let code = if arg.is_string() {
            from_str_or_hex(ctx.get_atom_str(arg.get_atom()))
        } else if arg.is_int() {
            arg.get_int() as u32
        } else {
            0
        };
        if let Some(c) = char::from_u32(code & 0xFFFF) {
            result.push(c);
        }
    }
    JSValue::new_string(ctx.intern(&result))
}

fn string_fromcodepoint(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    let mut result = String::new();
    for arg in args {
        let code = arg.get_int() as u32;
        if let Some(c) = char::from_u32(code) {
            result.push(c);
        }
    }
    JSValue::new_string(ctx.intern(&result))
}

fn string_trim(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    if args.is_empty() {
        return JSValue::new_string(ctx.intern(""));
    }
    let s = ctx.get_atom_str(args[0].get_atom()).to_string();
    let trimmed = s.trim();
    JSValue::new_string(ctx.intern(trimmed))
}

fn string_trim_start(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    if args.is_empty() {
        return JSValue::new_string(ctx.intern(""));
    }
    let s = ctx.get_atom_str(args[0].get_atom()).to_string();
    let trimmed = s.trim_start();
    JSValue::new_string(ctx.intern(trimmed))
}

fn string_trim_end(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    if args.is_empty() {
        return JSValue::new_string(ctx.intern(""));
    }
    let s = ctx.get_atom_str(args[0].get_atom()).to_string();
    let trimmed = s.trim_end();
    JSValue::new_string(ctx.intern(trimmed))
}

fn string_starts_with(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    if args.len() < 2 {
        return JSValue::bool(false);
    }
    let s = ctx.get_atom_str(args[0].get_atom()).to_string();
    let prefix = ctx.get_atom_str(args[1].get_atom()).to_string();
    JSValue::bool(s.starts_with(&prefix))
}

fn string_ends_with(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    if args.len() < 2 {
        return JSValue::bool(false);
    }
    let s = ctx.get_atom_str(args[0].get_atom()).to_string();
    let suffix = ctx.get_atom_str(args[1].get_atom()).to_string();
    JSValue::bool(s.ends_with(&suffix))
}

fn string_repeat(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    if args.len() < 2 {
        return JSValue::new_string(ctx.intern(""));
    }
    let s = ctx.get_atom_str(args[0].get_atom()).to_string();
    let count = args[1].get_int();
    if count < 0 {
        return JSValue::new_string(ctx.intern(""));
    }
    let count = count as usize;
    let repeated = s.repeat(count);
    JSValue::new_string(ctx.intern(&repeated))
}

fn string_includes(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    if args.len() < 2 {
        return JSValue::bool(false);
    }
    let s = ctx.get_atom_str(args[0].get_atom()).to_string();
    let search = ctx.get_atom_str(args[1].get_atom()).to_string();
    JSValue::bool(s.contains(&search))
}

fn string_replace(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    if args.len() < 3 {
        if args.is_empty() {
            return JSValue::new_string(ctx.intern(""));
        }
        return args[0].clone();
    }
    let s = if args[0].is_string() {
        ctx.get_atom_str(args[0].get_atom()).to_string()
    } else {
        return args[0].clone();
    };

    if args[1].is_object() {
        let regexp_obj = args[1].as_object();

        let pattern_atom = ctx.common_atoms.__pattern__;
        let flags_atom = ctx.common_atoms.__flags__;

        let pattern = if let Some(p) = regexp_obj.get(pattern_atom) {
            if p.is_string() {
                ctx.get_atom_str(p.get_atom()).to_string()
            } else {
                return JSValue::new_string(ctx.intern(&s));
            }
        } else {
            return JSValue::new_string(ctx.intern(&s));
        };

        let flags = if let Some(f) = regexp_obj.get(flags_atom) {
            if f.is_string() {
                ctx.get_atom_str(f.get_atom()).to_string()
            } else {
                String::new()
            }
        } else {
            String::new()
        };

        let replacement = if args[2].is_string() {
            ctx.get_atom_str(args[2].get_atom()).to_string()
        } else {
            String::new()
        };

        let ignore_case = flags.contains('i');
        let is_global = flags.contains('g');

        if is_global {
            let mut result = String::new();
            let mut last_end = 0;
            let mut search_from = 0;
            let pat_lower = if ignore_case {
                pattern.to_lowercase()
            } else {
                String::new()
            };
            let s_lower = if ignore_case {
                s.to_lowercase()
            } else {
                String::new()
            };

            loop {
                let found = if ignore_case {
                    s_lower[search_from..]
                        .find(&pat_lower)
                        .map(|i| i + search_from)
                } else {
                    s[search_from..].find(&pattern).map(|i| i + search_from)
                };

                if let Some(pos) = found {
                    result.push_str(&s[last_end..pos]);
                    result.push_str(&replacement);
                    last_end = pos + pattern.len();
                    search_from = last_end;
                    if pattern.is_empty() {
                        if search_from < s.len() {
                            result.push_str(&s[search_from..search_from + 1]);
                            search_from += 1;
                            last_end = search_from;
                        } else {
                            break;
                        }
                    }
                } else {
                    break;
                }
            }
            result.push_str(&s[last_end..]);
            JSValue::new_string(ctx.intern(&result))
        } else {
            let found = if ignore_case {
                s.to_lowercase().find(&pattern.to_lowercase())
            } else {
                s.find(&pattern)
            };

            if let Some(pos) = found {
                let mut result = String::new();
                result.push_str(&s[..pos]);
                result.push_str(&replacement);
                result.push_str(&s[pos + pattern.len()..]);
                JSValue::new_string(ctx.intern(&result))
            } else {
                JSValue::new_string(ctx.intern(&s))
            }
        }
    } else {
        let search = if args[1].is_string() {
            ctx.get_atom_str(args[1].get_atom()).to_string()
        } else {
            return JSValue::new_string(ctx.intern(&s));
        };
        let replacement = if args[2].is_string() {
            ctx.get_atom_str(args[2].get_atom()).to_string()
        } else if args[2].is_function() {
            if let Some(vm_ptr) = ctx.get_register_vm_ptr() {
                let vm = unsafe { &mut *(vm_ptr as *mut crate::runtime::vm::VM) };
                let result = vm.call_function_with_this(
                    ctx,
                    args[2],
                    JSValue::undefined(),
                    &[args[1].clone(), JSValue::new_int(0), args[0].clone()],
                );
                match result {
                    Ok(v) => {
                        if v.is_string() {
                            ctx.get_atom_str(v.get_atom()).to_string()
                        } else if v.is_undefined() {
                            "undefined".to_string()
                        } else if v.is_null() {
                            "null".to_string()
                        } else {
                            format!("{}", v.get_int())
                        }
                    }
                    Err(_) => String::new(),
                }
            } else {
                String::new()
            }
        } else {
            String::new()
        };
        let result = s.replace(&search, &replacement);
        JSValue::new_string(ctx.intern(&result))
    }
}

fn string_pad_start(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    if args.is_empty() {
        return JSValue::new_string(ctx.intern(""));
    }
    let s = ctx.get_atom_str(args[0].get_atom()).to_string();
    let target_len = if args.len() > 1 {
        args[1].get_int() as usize
    } else {
        0
    };
    let pad_str = if args.len() > 2 && args[2].is_string() {
        ctx.get_atom_str(args[2].get_atom()).to_string()
    } else {
        " ".to_string()
    };

    if target_len <= s.len() {
        return args[0];
    }

    let mut pad_count = target_len - s.len();
    let mut prepend = String::new();
    while pad_count > 0 {
        if pad_count >= pad_str.len() {
            prepend.push_str(&pad_str);
            pad_count -= pad_str.len();
        } else {
            prepend.push_str(&pad_str[..pad_count]);
            pad_count = 0;
        }
    }

    JSValue::new_string(ctx.intern(&(prepend + &s)))
}

fn string_pad_end(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    if args.is_empty() {
        return JSValue::new_string(ctx.intern(""));
    }
    let s = ctx.get_atom_str(args[0].get_atom()).to_string();
    let target_len = if args.len() > 1 {
        args[1].get_int() as usize
    } else {
        0
    };
    let pad_str = if args.len() > 2 && args[2].is_string() {
        ctx.get_atom_str(args[2].get_atom()).to_string()
    } else {
        " ".to_string()
    };

    if target_len <= s.len() {
        return args[0];
    }

    let mut pad_count = target_len - s.len();
    let mut append = String::new();
    while pad_count > 0 {
        if pad_count >= pad_str.len() {
            append.push_str(&pad_str);
            pad_count -= pad_str.len();
        } else {
            append.push_str(&pad_str[..pad_count]);
            pad_count = 0;
        }
    }

    JSValue::new_string(ctx.intern(&(s + &append)))
}

fn string_replace_all(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    if args.len() < 3 {
        if args.is_empty() {
            return JSValue::new_string(ctx.intern(""));
        }
        return args[0];
    }
    let s = if args[0].is_string() {
        ctx.get_atom_str(args[0].get_atom()).to_string()
    } else {
        return args[0];
    };
    let search = if args[1].is_string() {
        ctx.get_atom_str(args[1].get_atom()).to_string()
    } else {
        return args[0];
    };
    let replacement = if args[2].is_string() {
        ctx.get_atom_str(args[2].get_atom()).to_string()
    } else {
        String::new()
    };

    let result = s.replace(&search as &str, &replacement as &str);
    JSValue::new_string(ctx.intern(&result))
}

fn string_at(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    if args.is_empty() {
        return JSValue::undefined();
    }
    let s = if args[0].is_string() {
        ctx.get_atom_str(args[0].get_atom()).to_string()
    } else {
        return JSValue::undefined();
    };
    let chars: Vec<char> = s.chars().collect();
    let len = chars.len();
    let idx = if args.len() > 1 { args[1].get_int() } else { 0 };
    let actual_idx = if idx < 0 { len as i64 + idx } else { idx };
    if actual_idx < 0 || actual_idx as usize >= len {
        return JSValue::undefined();
    }
    let c = chars[actual_idx as usize];
    JSValue::new_string(ctx.intern(&c.to_string()))
}

fn string_is_well_formed(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    if args.is_empty() {
        return JSValue::bool(false);
    }
    let this = args[0];
    if !this.is_string() {
        return JSValue::bool(false);
    }

    JSValue::bool(true)
}

fn string_to_well_formed(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    if args.is_empty() {
        return JSValue::undefined();
    }
    let this = args[0];
    if !this.is_string() {
        return this;
    }
    let s = ctx.get_atom_str(this.get_atom()).to_string();

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

fn string_code_point_at(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    if args.is_empty() {
        return JSValue::undefined();
    }
    let this = args[0];
    if !this.is_string() {
        return JSValue::undefined();
    }
    let s = ctx.get_atom_str(this.get_atom()).to_string();
    let chars: Vec<char> = s.chars().collect();
    let len = chars.len();
    let index = if args.len() > 1 {
        args[1].get_int() as i64
    } else {
        0
    };
    let actual_index = if index < 0 { len as i64 + index } else { index };
    if actual_index < 0 || actual_index as usize >= len {
        return JSValue::undefined();
    }
    let c = chars[actual_index as usize];
    JSValue::new_int(c as u32 as i64)
}

fn string_match_all(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    if args.is_empty() {
        let mut result = JSObject::new_array();
        result.set(ctx.common_atoms.length, JSValue::new_int(0));
        let ptr = Box::into_raw(Box::new(result)) as usize;
        return JSValue::new_object(ptr);
    }

    let this = args[0];
    let s = if this.is_string() {
        ctx.get_atom_str(this.get_atom()).to_string()
    } else {
        let mut result = JSObject::new_array();
        result.set(ctx.common_atoms.length, JSValue::new_int(0));
        let ptr = Box::into_raw(Box::new(result)) as usize;
        return JSValue::new_object(ptr);
    };

    let pattern = if args.len() > 1 {
        if args[1].is_object() {
            let regexp_obj = args[1].as_object();

            let flags_atom = ctx.common_atoms.__flags__;
            let flags = if let Some(f) = regexp_obj.get(flags_atom) {
                if f.is_string() {
                    ctx.get_atom_str(f.get_atom()).to_string()
                } else {
                    String::new()
                }
            } else {
                String::new()
            };

            if !flags.contains('g') {
                let mut err_obj = JSObject::new();
                err_obj.set(
                    ctx.common_atoms.name,
                    JSValue::new_string(ctx.intern("TypeError")),
                );
                err_obj.set(
                    ctx.common_atoms.message,
                    JSValue::new_string(ctx.intern("matchAll requires a global RegExp")),
                );
                let ptr = Box::into_raw(Box::new(err_obj)) as usize;
                return JSValue::new_object(ptr);
            }

            let pattern_atom = ctx.common_atoms.__pattern__;
            if let Some(p) = regexp_obj.get(pattern_atom) {
                if p.is_string() {
                    ctx.get_atom_str(p.get_atom()).to_string()
                } else {
                    String::new()
                }
            } else {
                String::new()
            }
        } else if args[1].is_string() {
            ctx.get_atom_str(args[1].get_atom()).to_string()
        } else {
            String::new()
        }
    } else {
        String::new()
    };

    let mut result_array = JSObject::new_array();
    let length_atom = ctx.common_atoms.length;
    let mut match_count = 0;

    if pattern.is_empty() {
        result_array.set(length_atom, JSValue::new_int(0));
        let ptr = Box::into_raw(Box::new(result_array)) as usize;
        return JSValue::new_object(ptr);
    }

    let mut search_pos = 0;
    while search_pos <= s.len() {
        let max_start = if pattern.len() <= s.len() {
            s.len() - pattern.len()
        } else {
            break;
        };

        let mut found = false;
        for i in search_pos..=max_start {
            if i + pattern.len() <= s.len() {
                let slice = &s[i..i + pattern.len()];
                if slice == pattern {
                    found = true;
                    let mut match_obj = JSObject::new();
                    match_obj.set(ctx.intern("0"), JSValue::new_string(ctx.intern(slice)));
                    match_obj.set(ctx.common_atoms.index, JSValue::new_int(i as i64));
                    match_obj.set(ctx.common_atoms.input, JSValue::new_string(ctx.intern(&s)));
                    match_obj.set(ctx.common_atoms.length, JSValue::new_int(1));

                    let key = ctx.int_atom_mut(match_count);
                    result_array.set(
                        key,
                        JSValue::new_object(Box::into_raw(Box::new(match_obj)) as usize),
                    );

                    match_count += 1;
                    search_pos = i + pattern.len();
                    if pattern.len() == 0 {
                        search_pos += 1;
                    }
                    break;
                }
            }
        }

        if !found {
            break;
        }
    }

    result_array.set(length_atom, JSValue::new_int(match_count as i64));
    let ptr = Box::into_raw(Box::new(result_array)) as usize;
    JSValue::new_object(ptr)
}

fn string_search(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    if args.len() < 2 {
        return JSValue::new_int(-1);
    }
    let s = if args[0].is_string() {
        ctx.get_atom_str(args[0].get_atom()).to_string()
    } else {
        return JSValue::new_int(-1);
    };

    if args[1].is_object() {
        let regexp_obj = args[1].as_object();
        let pattern_atom = ctx.common_atoms.__pattern__;
        let flags_atom = ctx.common_atoms.__flags__;

        let pattern = if let Some(p) = regexp_obj.get(pattern_atom) {
            if p.is_string() {
                ctx.get_atom_str(p.get_atom()).to_string()
            } else {
                return JSValue::new_int(-1);
            }
        } else {
            return JSValue::new_int(-1);
        };

        let flags = if let Some(f) = regexp_obj.get(flags_atom) {
            if f.is_string() {
                ctx.get_atom_str(f.get_atom()).to_string()
            } else {
                String::new()
            }
        } else {
            String::new()
        };

        let ignore_case = flags.contains('i');
        let s_lower = if ignore_case {
            s.to_lowercase()
        } else {
            String::new()
        };
        let p_lower = if ignore_case {
            pattern.to_lowercase()
        } else {
            String::new()
        };

        let found = if ignore_case {
            s_lower.find(&p_lower)
        } else {
            s.find(&pattern)
        };
        JSValue::new_int(found.map(|i| i as i64).unwrap_or(-1))
    } else if args[1].is_string() {
        let search = ctx.get_atom_str(args[1].get_atom()).to_string();
        JSValue::new_int(s.find(&search).map(|i| i as i64).unwrap_or(-1))
    } else {
        JSValue::new_int(-1)
    }
}

fn string_match(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    if args.len() < 2 {
        return JSValue::null();
    }
    let s = if args[0].is_string() {
        ctx.get_atom_str(args[0].get_atom()).to_string()
    } else {
        return JSValue::null();
    };

    if args[1].is_object() {
        let regexp_obj = args[1].as_object();
        let pattern_atom = ctx.common_atoms.__pattern__;
        let flags_atom = ctx.common_atoms.__flags__;

        let pattern = if let Some(p) = regexp_obj.get(pattern_atom) {
            if p.is_string() {
                ctx.get_atom_str(p.get_atom()).to_string()
            } else {
                return JSValue::null();
            }
        } else {
            return JSValue::null();
        };

        let flags = if let Some(f) = regexp_obj.get(flags_atom) {
            if f.is_string() {
                ctx.get_atom_str(f.get_atom()).to_string()
            } else {
                String::new()
            }
        } else {
            String::new()
        };

        let is_global = flags.contains('g');
        let ignore_case = flags.contains('i');
        let s_lower = if ignore_case {
            s.to_lowercase()
        } else {
            String::new()
        };
        let p_lower = if ignore_case {
            pattern.to_lowercase()
        } else {
            String::new()
        };

        if is_global {
            let mut results: Vec<String> = Vec::new();
            let mut search_from = 0;
            loop {
                let found = if ignore_case {
                    s_lower[search_from..]
                        .find(&p_lower)
                        .map(|i| i + search_from)
                } else {
                    s[search_from..].find(&pattern).map(|i| i + search_from)
                };
                if let Some(pos) = found {
                    let end = (pos + pattern.len()).min(s.len());
                    results.push(s[pos..end].to_string());
                    search_from = end;
                    if pattern.is_empty() {
                        if search_from < s.len() {
                            search_from += 1;
                        } else {
                            break;
                        }
                    }
                } else {
                    break;
                }
            }
            if results.is_empty() {
                return JSValue::null();
            }
            let mut arr = JSObject::new_array();
            let length_atom = ctx.common_atoms.length;
            for (i, m) in results.iter().enumerate() {
                let key = ctx.int_atom_mut(i);
                arr.set(key, JSValue::new_string(ctx.intern(m)));
            }
            arr.set(length_atom, JSValue::new_int(results.len() as i64));
            let ptr = Box::into_raw(Box::new(arr)) as usize;
            JSValue::new_object(ptr)
        } else {
            let found = if ignore_case {
                s_lower.find(&p_lower)
            } else {
                s.find(&pattern)
            };
            if let Some(pos) = found {
                let end = (pos + pattern.len()).min(s.len());
                let match_str = &s[pos..end];
                let mut result = JSObject::new_array();
                result.set(ctx.intern("0"), JSValue::new_string(ctx.intern(match_str)));
                result.set(ctx.common_atoms.index, JSValue::new_int(pos as i64));
                result.set(ctx.common_atoms.input, JSValue::new_string(ctx.intern(&s)));
                result.set(ctx.common_atoms.length, JSValue::new_int(1));
                let ptr = Box::into_raw(Box::new(result)) as usize;
                JSValue::new_object(ptr)
            } else {
                JSValue::null()
            }
        }
    } else {
        JSValue::null()
    }
}

fn string_substr(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
    if args.is_empty() {
        return JSValue::new_string(ctx.intern(""));
    }
    let s = if args[0].is_string() {
        ctx.get_atom_str(args[0].get_atom()).to_string()
    } else {
        return JSValue::new_string(ctx.intern(""));
    };
    let len = char_count(&s);

    let start = if args.len() > 1 {
        let v = args[1].get_int();
        if v < 0 {
            (len as i64 + v).max(0) as usize
        } else {
            (v as usize).min(len)
        }
    } else {
        0
    };

    let end = if args.len() > 2 {
        let v = args[2].get_int();
        if v <= 0 {
            return JSValue::new_string(ctx.intern(""));
        }
        (start + v as usize).min(len)
    } else {
        len
    };
    let start_b = byte_index_for_char_pos(&s, start);
    let end_b = byte_index_for_char_pos(&s, end);
    JSValue::new_string(ctx.intern(&s[start_b..end_b]))
}
