use super::super::MirBuilder;

/// Phase 287 P5: Trace helper for toString normalization debugging
fn trace_tostring(msg: impl std::fmt::Display) {
    if std::env::var("NYASH_STATIC_CALL_TRACE").ok().as_deref() == Some("1") {
        eprintln!("[P287-BOXCALL] {}", msg);
    }
}

/// Phase 287 P4: toString/stringify/str → BoxCall(slot #0) normalization (SSOT)
///
/// Root cause fix: toString is a universal slot [#0] method that should ALWAYS use BoxCall,
/// not Method or Global calls. This ensures:
/// - VM/LLVM/JoinIR consistent behavior
/// - Primitive types (Integer/Float/Bool/String) work correctly
/// - No receiver type inference errors
///
/// Strategy: Intercept toString/stringify/str EARLY and emit BoxCall directly.
/// Do NOT pass to Known/Unique rewrite (those are for user-defined methods only).
pub(crate) fn try_early_str_like_to_dst(
    builder: &mut MirBuilder,
    want_dst: Option<super::super::ValueId>,
    object_value: super::super::ValueId,
    _class_name_opt: &Option<String>,
    method: &str,
    arity: usize,
) -> Option<Result<super::super::ValueId, String>> {
    // Only handle toString/stringify/str with arity=0
    if !(method == "toString" || method == "stringify" || method == "str") || arity != 0 {
        return None;
    }

    trace_tostring(format!("Normalizing {method} to BoxCall(slot #0) for object_value={:?}", object_value));

    // Phase 287 P4: ALWAYS emit BoxCall for toString/stringify/str (universal slot #0)
    // Do NOT rewrite to Global or Method - BoxCall is the SSOT for display methods
    let actual_dst = want_dst.unwrap_or_else(|| builder.next_value_id());

    if let Err(e) = builder.emit_instruction(super::super::MirInstruction::BoxCall {
        dst: Some(actual_dst),
        box_val: object_value,
        method: method.to_string(),
        method_id: Some(0), // toString/stringify/str are universal slot #0
        args: vec![],
        effects: super::super::EffectMask::PURE, // Pure function (no side effects)
    }) {
        return Some(Err(e.to_string()));
    }

    // Annotate result type as String (toString always returns String)
    builder.type_ctx.value_types.insert(
        actual_dst,
        super::super::MirType::Box("StringBox".to_string()),
    );

    trace_tostring(format!("Emitted BoxCall for {method} -> dst={:?}", actual_dst));

    Some(Ok(actual_dst))
}

/// To-dst variant: equals/1 consolidation with requested destination
pub(crate) fn try_special_equals_to_dst(
    builder: &mut MirBuilder,
    want_dst: Option<super::super::ValueId>,
    object_value: super::super::ValueId,
    class_name_opt: &Option<String>,
    method: &str,
    args: Vec<super::super::ValueId>,
) -> Option<Result<super::super::ValueId, String>> {
    if method != "equals" || args.len() != 1 {
        return None;
    }
    if let Some(cls) = class_name_opt.as_ref() {
        if let Some(res) = super::known::try_known_rewrite_to_dst(
            builder,
            want_dst,
            object_value,
            cls,
            method,
            args.clone(),
        ) {
            return Some(res);
        }
    }
    super::known::try_unique_suffix_rewrite_to_dst(builder, want_dst, object_value, method, args)
}
