use super::*;

pub(super) fn try_handle_string_box(
    this: &mut MirInterpreter,
    dst: Option<ValueId>,
    box_val: ValueId,
    method: &str,
    args: &[ValueId],
) -> Result<bool, VMError> {
    if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
        eprintln!("[vm-trace] try_handle_string_box(method={})", method);
    }
    let recv = this.reg_load(box_val)?;
    // Ultra-fast path: raw VM string receiver for length/size (no boxing at all)
    if (method == "length" || method == "size")
        && std::env::var("NYASH_VM_FAST").ok().as_deref() == Some("1")
    {
        if let VMValue::String(ref raw) = recv {
            let use_cp = std::env::var("NYASH_STR_CP").ok().as_deref() == Some("1");
            let n = if use_cp {
                raw.chars().count() as i64
            } else {
                raw.len() as i64
            };
            this.write_result(dst, VMValue::Integer(n));
            return Ok(true);
        }
    }
    // Handle ONLY when the receiver is actually a string.
    // Do NOT coerce arbitrary boxes to StringBox (e.g., ArrayBox.length()).
    let sb_norm_opt: Option<crate::box_trait::StringBox> = match recv.clone() {
        VMValue::String(s) => Some(crate::box_trait::StringBox::new(s)),
        VMValue::BoxRef(b) => {
            if b.as_any()
                .downcast_ref::<crate::box_trait::StringBox>()
                .is_some()
            {
                Some(b.to_string_box())
            } else {
                None
            }
        }
        _ => None,
    };
    let Some(sb_norm) = sb_norm_opt else {
        return Ok(false);
    };
    // Only handle known string methods here (receiver is confirmed string)
    match method {
        "length" | "size" => {
            // Bench/profile fast path: return VMValue::Integer directly (avoid boxing overhead)
            if std::env::var("NYASH_VM_FAST").ok().as_deref() == Some("1") {
                let use_cp = std::env::var("NYASH_STR_CP").ok().as_deref() == Some("1");
                let n = if use_cp {
                    sb_norm.value.chars().count() as i64
                } else {
                    sb_norm.value.len() as i64
                };
                this.write_result(dst, VMValue::Integer(n));
                return Ok(true);
            }
            let ret = sb_norm.length();
            this.write_result(dst, VMValue::from_nyash_box(ret));
            return Ok(true);
        }
        "replace" => {
            // replace(old, new) -> string with first occurrence replaced (Rust replace = all; match Core minimal
            this.validate_args_exact("replace", args, 2)?;
            let old_s = this.reg_load(args[0])?.to_string();
            let new_s = this.reg_load(args[1])?.to_string();
            // Core policy: replace only the first occurrence
            let out = if let Some(pos) = sb_norm.value.find(&old_s) {
                let mut s = String::with_capacity(sb_norm.value.len() + new_s.len());
                s.push_str(&sb_norm.value[..pos]);
                s.push_str(&new_s);
                s.push_str(&sb_norm.value[pos + old_s.len()..]);
                s
            } else {
                sb_norm.value.clone()
            };
            this.write_result(
                dst,
                VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(out))),
            );
            return Ok(true);
        }
        "trim" => {
            let ret = sb_norm.trim();
            this.write_result(dst, VMValue::from_nyash_box(ret));
            return Ok(true);
        }
        "indexOf" => {
            // Support both 1-arg indexOf(search) and 2-arg indexOf(search, fromIndex)
            let (needle, from_index) = match args.len() {
                1 => {
                    // indexOf(search) - search from beginning
                    let n = this.reg_load(args[0])?.to_string();
                    (n, 0)
                }
                2 => {
                    // indexOf(search, fromIndex) - search from specified position
                    let n = this.reg_load(args[0])?.to_string();
                    let from = this.reg_load(args[1])?.as_integer().unwrap_or(0);
                    (n, from.max(0) as usize)
                }
                _ => {
                    return Err(
                        this.err_invalid("indexOf expects 1 or 2 args (search [, fromIndex])")
                    );
                }
            };

            // Search for needle starting from from_index
            let search_str = if from_index >= sb_norm.value.len() {
                ""
            } else {
                &sb_norm.value[from_index..]
            };

            let idx = search_str
                .find(&needle)
                .map(|i| (from_index + i) as i64)
                .unwrap_or(-1);

            this.write_result(dst, VMValue::Integer(idx));
            return Ok(true);
        }
        "contains" => {
            // contains(search) -> boolean (true if found, false otherwise)
            // Implemented as indexOf(search) >= 0
            this.validate_args_exact("contains", args, 1)?;
            let needle = this.reg_load(args[0])?.to_string();
            let found = sb_norm.value.contains(&needle);
            this.write_result(dst, VMValue::Bool(found));
            return Ok(true);
        }
        "lastIndexOf" => {
            // lastIndexOf(substr) -> last index or -1
            this.validate_args_exact("lastIndexOf", args, 1)?;
            let needle = this.reg_load(args[0])?.to_string();
            let idx = sb_norm.value.rfind(&needle).map(|i| i as i64).unwrap_or(-1);
            this.write_result(dst, VMValue::Integer(idx));
            return Ok(true);
        }
        "stringify" => {
            // JSON-style stringify for strings: quote and escape common characters
            let mut quoted = String::with_capacity(sb_norm.value.len() + 2);
            quoted.push('"');
            for ch in sb_norm.value.chars() {
                match ch {
                    '"' => quoted.push_str("\\\""),
                    '\\' => quoted.push_str("\\\\"),
                    '\n' => quoted.push_str("\\n"),
                    '\r' => quoted.push_str("\\r"),
                    '\t' => quoted.push_str("\\t"),
                    c if c.is_control() => quoted.push(' '),
                    c => quoted.push(c),
                }
            }
            quoted.push('"');
            this.write_result(
                dst,
                VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(quoted))),
            );
            return Ok(true);
        }
        "substring" => {
            // Support both 1-arg (start to end) and 2-arg (start, end) forms
            let (s_idx, e_idx) = match args.len() {
                1 => {
                    // substring(start) - from start to end of string
                    let s = this.reg_load(args[0])?.as_integer().unwrap_or(0);
                    let len = sb_norm.value.chars().count() as i64;
                    (s, len)
                }
                2 => {
                    // substring(start, end) - half-open interval [start, end)
                    let s = this.reg_load(args[0])?.as_integer().unwrap_or(0);
                    let e = this.reg_load(args[1])?.as_integer().unwrap_or(0);
                    (s, e)
                }
                _ => {
                    return Err(this.err_invalid("substring expects 1 or 2 args (start [, end])"));
                }
            };
            let len = sb_norm.value.chars().count() as i64;
            let start = s_idx.max(0).min(len) as usize;
            let end = e_idx.max(start as i64).min(len) as usize;
            let chars: Vec<char> = sb_norm.value.chars().collect();
            let sub: String = chars[start..end].iter().collect();
            this.write_result(
                dst,
                VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(sub))),
            );
            return Ok(true);
        }
        "concat" => {
            this.validate_args_exact("concat", args, 1)?;
            let rhs = this.reg_load(args[0])?;
            let new_s = format!("{}{}", sb_norm.value, rhs.to_string());
            this.write_result(
                dst,
                VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(new_s))),
            );
            return Ok(true);
        }
        "is_digit_char" => {
            // Accept either 0-arg (use first char of receiver) or 1-arg (string/char to test)
            let ch_opt = if args.is_empty() {
                sb_norm.value.chars().next()
            } else if args.len() == 1 {
                let s = this.reg_load(args[0])?.to_string();
                s.chars().next()
            } else {
                return Err(this.err_invalid("is_digit_char expects 0 or 1 arg"));
            };
            let is_digit = ch_opt.map(|c| c.is_ascii_digit()).unwrap_or(false);
            this.write_result(dst, VMValue::Bool(is_digit));
            return Ok(true);
        }
        "is_hex_digit_char" => {
            let ch_opt = if args.is_empty() {
                sb_norm.value.chars().next()
            } else if args.len() == 1 {
                let s = this.reg_load(args[0])?.to_string();
                s.chars().next()
            } else {
                return Err(this.err_invalid("is_hex_digit_char expects 0 or 1 arg"));
            };
            let is_hex = ch_opt.map(|c| c.is_ascii_hexdigit()).unwrap_or(false);
            this.write_result(dst, VMValue::Bool(is_hex));
            return Ok(true);
        }
        _ => {}
    }
    Ok(false)
}
