use crate::mir::builder::MirBuilder;
use crate::mir::ValueId;

#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum LocalKind {
    Recv,
    Arg,
    CompareOperand,
    Cond,
    FieldBase,
    Other(u8),
}

impl LocalKind {
    #[inline]
    fn tag(self) -> u8 {
        match self {
            LocalKind::Recv => 0,
            LocalKind::Arg => 1,
            LocalKind::CompareOperand => 2,
            LocalKind::Cond => 4,
            LocalKind::FieldBase => 0, // share recv slot for bases
            LocalKind::Other(k) => k,
        }
    }
}

/// Ensure a value has an in-block definition and cache it per (bb, orig, kind).
/// Always emits a Copy in the current block when not cached.
pub fn ensure(builder: &mut MirBuilder, v: ValueId, kind: LocalKind) -> ValueId {
    let bb_opt = builder.current_block;
    if let Some(bb) = bb_opt {
        if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") {
            eprintln!("[local-ssa] ensure bb={:?} kind={:?} v=%{}", bb, kind, v.0);
        }
        let key = (bb, v, kind.tag());
        if let Some(&loc) = builder.local_ssa_map.get(&key) {
            return loc;
        }

        // Ensure the current basic block exists in the function before emitting a Copy.
        // Stage-B 経路などでは current_block が割り当て済みでも、ブロック自体が
        // function にまだ追加されていない場合があり、そのまま emit_instruction すると
        // Copy が黙って落ちてしまう。ここで best-effort で作成しておく。
        // CRITICAL: Check for errors - if block creation fails, return original value.
        if let Err(e) = builder.ensure_block_exists(bb) {
            if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") {
                eprintln!(
                    "[local-ssa] ensure_block_exists FAILED bb={:?} kind={:?} v=%{} err={}",
                    bb, kind, v.0, e
                );
            }
            return v;
        }

        // CRITICAL FIX: If `v` is from a pinned slot, check if there's a PHI value for that slot
        // in the current block's variable_ctx.variable_map. If so, use the PHI value directly instead of
        // emitting a Copy from the old value (which might not be defined in this block).
        // Try to detect pinned slots for this value and redirect to the latest slot value.
        // 1) First, look for "__pin$" entries in variable_ctx.variable_map that still point to v.
        // 2) If not found, consult builder.pin_slot_names to recover the slot name
        //    and then look up the current ValueId for that slot.
        let mut slot_name_opt: Option<String> = None;

        let names_for_v: Vec<String> = builder
            .variable_ctx
            .variable_map
            .iter()
            .filter(|(k, &vid)| vid == v && k.starts_with("__pin$"))
            .map(|(k, _)| k.clone())
            .collect();

        if let Some(first_pin_name) = names_for_v.first() {
            slot_name_opt = Some(first_pin_name.clone());
        } else if let Some(name) = builder.pin_slot_names.get(&v) {
            slot_name_opt = Some(name.clone());
        }

        if let Some(slot_name) = slot_name_opt {
            if let Some(&current_val) = builder.variable_ctx.variable_map.get(&slot_name) {
                if current_val != v {
                    // The slot has been updated (likely by a PHI or header rewrite).
                    // Use the updated value instead of the stale pinned ValueId.
                    if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") {
                        eprintln!(
                            "[local-ssa] phi-redirect bb={:?} kind={:?} slot={} %{} -> %{}",
                            bb, kind, slot_name, v.0, current_val.0
                        );
                    }
                    builder.local_ssa_map.insert(key, current_val);
                    return current_val;
                }
            }
        }

        let loc = builder.next_value_id();
        // CRITICAL: Check emit_instruction result - if Copy fails, return original value
        // to avoid returning undefined ValueId.
        if let Err(e) =
            builder.emit_instruction(crate::mir::MirInstruction::Copy { dst: loc, src: v })
        {
            if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") {
                eprintln!("[local-ssa] emit_instruction Copy FAILED bb={:?} kind={:?} v=%{} dst=%{} err={}",
                          bb, kind, v.0, loc.0, e);
            }
            // Failed to emit Copy - return original value instead of undefined dst
            return v;
        }
        if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") {
            eprintln!(
                "[local-ssa] copy  bb={:?} kind={:?} %{} -> %{}",
                bb, kind, v.0, loc.0
            );
        }
        // Success: register metadata and cache
        if let Some(t) = builder.type_ctx.value_types.get(&v).cloned() {
            builder.type_ctx.value_types.insert(loc, t);
        }
        if let Some(cls) = builder.type_ctx.value_origin_newbox.get(&v).cloned() {
            builder
                .type_ctx
                .value_origin_newbox
                .insert(loc, cls.clone());

            // CRITICAL FIX: For receiver kind, if type is missing but origin exists,
            // infer MirType::Box from origin
            if kind == LocalKind::Recv && builder.type_ctx.value_types.get(&loc).is_none() {
                builder
                    .type_ctx
                    .value_types
                    .insert(loc, crate::mir::MirType::Box(cls));
            }
        }
        builder.local_ssa_map.insert(key, loc);
        loc
    } else {
        v
    }
}

#[inline]
pub fn recv(builder: &mut MirBuilder, v: ValueId) -> ValueId {
    ensure(builder, v, LocalKind::Recv)
}

#[inline]
pub fn arg(builder: &mut MirBuilder, v: ValueId) -> ValueId {
    ensure(builder, v, LocalKind::Arg)
}

#[inline]
pub fn cond(builder: &mut MirBuilder, v: ValueId) -> ValueId {
    ensure(builder, v, LocalKind::Cond)
}

#[inline]
pub fn field_base(builder: &mut MirBuilder, v: ValueId) -> ValueId {
    ensure(builder, v, LocalKind::FieldBase)
}

#[inline]
pub fn cmp_operand(builder: &mut MirBuilder, v: ValueId) -> ValueId {
    ensure(builder, v, LocalKind::CompareOperand)
}

/// Finalize only the args (legacy Call paths)
pub fn finalize_args(builder: &mut MirBuilder, args: &mut Vec<ValueId>) {
    for a in args.iter_mut() {
        *a = arg(builder, *a);
    }
}

/// Finalize a single branch condition just before emitting a Branch.
/// Ensures the condition has a definition in the current block.
pub fn finalize_branch_cond(builder: &mut MirBuilder, condition_v: &mut ValueId) {
    *condition_v = cond(builder, *condition_v);
    if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") {
        if let Some(bb) = builder.current_block {
            eprintln!(
                "[local-ssa] finalize-branch bb={:?} cond=%{}",
                bb, condition_v.0
            );
        }
    }
}

/// Finalize compare operands just before emitting a Compare.
/// Applies in-block materialization to both lhs and rhs.
pub fn finalize_compare(builder: &mut MirBuilder, lhs: &mut ValueId, rhs: &mut ValueId) {
    *lhs = cmp_operand(builder, *lhs);
    *rhs = cmp_operand(builder, *rhs);
    if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") {
        if let Some(bb) = builder.current_block {
            eprintln!(
                "[local-ssa] finalize-compare bb={:?} lhs=%{} rhs=%{}",
                bb, lhs.0, rhs.0
            );
        }
    }
}

/// Finalize field use sites: ensure base and all args are in the current block.
pub fn finalize_field_base_and_args(
    builder: &mut MirBuilder,
    base: &mut ValueId,
    args: &mut Vec<ValueId>,
) {
    *base = field_base(builder, *base);
    for a in args.iter_mut() {
        *a = arg(builder, *a);
    }
    if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") {
        if let Some(bb) = builder.current_block {
            eprintln!(
                "[local-ssa] finalize-field bb={:?} base=%{} argc={}",
                bb,
                base.0,
                args.len()
            );
        }
    }
}
