//! Kind-specific instruction metadata (PoC) used to gradually
//! move large enum matches to small, testable structs.
//!
//! Non-functional: only mirrors data for selected instructions and
//! provides introspection (effects/dst/used). Core behavior remains
//! in `MirInstruction`.

use super::{BasicBlockId, ConstValue, Effect, EffectMask, ValueId};
use crate::mir::instruction::MirInstruction;
use crate::mir::types::{
    BarrierOp as MirBarrierOp, BinaryOp as MirBinOp, MirType, TypeOpKind as MirTypeOpKind,
    WeakRefOp as MirWeakRefOp,
};

// Local macro utilities for generating InstructionMeta boilerplate.
// This macro is intentionally scoped to this module to avoid polluting the crate namespace.
macro_rules! inst_meta {
    (
        $(
            pub struct $name:ident { $($field:ident : $fty:ty),* $(,)? }
            => {
                from_mir = |$i:ident| $from_expr:expr;
                effects = $effects:expr;
                dst = $dst:expr;
                used = $used:expr;
            }
        )+
    ) => {
        $(
            #[derive(Debug, Clone)]
            pub struct $name { $(pub $field: $fty),* }

            impl $name {
                pub fn from_mir($i: &MirInstruction) -> Option<Self> { $from_expr }
            }

            impl InstructionMeta for $name {
                fn effects(&self) -> EffectMask { ($effects)(self) }
                fn dst(&self) -> Option<ValueId> { ($dst)(self) }
                fn used(&self) -> Vec<ValueId> { ($used)(self) }
            }
        )+
    };
}

pub trait InstructionMeta {
    fn effects(&self) -> EffectMask;
    fn dst(&self) -> Option<ValueId>;
    fn used(&self) -> Vec<ValueId>;
}

// ---- Const ----
#[derive(Debug, Clone)]
pub struct ConstInst {
    pub dst: ValueId,
    pub value: ConstValue,
}

impl ConstInst {
    pub fn from_mir(i: &MirInstruction) -> Option<Self> {
        match i {
            MirInstruction::Const { dst, value } => Some(ConstInst {
                dst: *dst,
                value: value.clone(),
            }),
            _ => None,
        }
    }
}

impl InstructionMeta for ConstInst {
    fn effects(&self) -> EffectMask {
        EffectMask::PURE
    }
    fn dst(&self) -> Option<ValueId> {
        Some(self.dst)
    }
    fn used(&self) -> Vec<ValueId> {
        Vec::new()
    }
}

// ---- BinOp ----
#[derive(Debug, Clone)]
pub struct BinOpInst {
    pub dst: ValueId,
    pub op: MirBinOp,
    pub lhs: ValueId,
    pub rhs: ValueId,
}

impl BinOpInst {
    pub fn from_mir(i: &MirInstruction) -> Option<Self> {
        match i {
            MirInstruction::BinOp { dst, op, lhs, rhs } => Some(BinOpInst {
                dst: *dst,
                op: *op,
                lhs: *lhs,
                rhs: *rhs,
            }),
            _ => None,
        }
    }
}

impl InstructionMeta for BinOpInst {
    fn effects(&self) -> EffectMask {
        EffectMask::PURE
    }
    fn dst(&self) -> Option<ValueId> {
        Some(self.dst)
    }
    fn used(&self) -> Vec<ValueId> {
        vec![self.lhs, self.rhs]
    }
}

// ---- Helper delegation for MirInstruction methods ----

pub fn effects_via_meta(i: &MirInstruction) -> Option<EffectMask> {
    if let Some(k) = ConstInst::from_mir(i) {
        return Some(k.effects());
    }
    if let Some(k) = BinOpInst::from_mir(i) {
        return Some(k.effects());
    }
    if let Some(k) = UnaryOpInst::from_mir(i) {
        return Some(k.effects());
    }
    if let Some(k) = CompareInst::from_mir(i) {
        return Some(k.effects());
    }
    if let Some(k) = LoadInst::from_mir(i) {
        return Some(k.effects());
    }
    if let Some(k) = CastInst::from_mir(i) {
        return Some(k.effects());
    }
    if let Some(k) = TypeOpInst::from_mir(i) {
        return Some(k.effects());
    }
    if let Some(k) = ArrayGetInst::from_mir(i) {
        return Some(k.effects());
    }
    if let Some(k) = PhiInst::from_mir(i) {
        return Some(k.effects());
    }
    if let Some(k) = NewBoxInst::from_mir(i) {
        return Some(k.effects());
    }
    if let Some(k) = StoreInst::from_mir(i) {
        return Some(k.effects());
    }
    if let Some(k) = ArraySetInst::from_mir(i) {
        return Some(k.effects());
    }
    if let Some(k) = ReturnInst::from_mir(i) {
        return Some(k.effects());
    }
    if let Some(k) = BranchInst::from_mir(i) {
        return Some(k.effects());
    }
    if let Some(k) = JumpInst::from_mir(i) {
        return Some(k.effects());
    }
    if let Some(k) = PrintInst::from_mir(i) {
        return Some(k.effects());
    }
    if let Some(k) = DebugInst::from_mir(i) {
        return Some(k.effects());
    }
    if let Some(k) = TypeCheckInst::from_mir(i) {
        return Some(k.effects());
    }
    if let Some(k) = CopyInst::from_mir(i) {
        return Some(k.effects());
    }
    if let Some(k) = NopInst::from_mir(i) {
        return Some(k.effects());
    }
    if let Some(k) = ThrowInst::from_mir(i) {
        return Some(k.effects());
    }
    if let Some(k) = CatchInst::from_mir(i) {
        return Some(k.effects());
    }
    if let Some(k) = SafepointInst::from_mir(i) {
        return Some(k.effects());
    }
    if let Some(k) = NewClosureInst::from_mir(i) {
        return Some(k.effects());
    }
    None
}

pub fn dst_via_meta(i: &MirInstruction) -> Option<ValueId> {
    if let Some(k) = ConstInst::from_mir(i) {
        return k.dst();
    }
    if let Some(k) = BinOpInst::from_mir(i) {
        return k.dst();
    }
    if let Some(k) = UnaryOpInst::from_mir(i) {
        return k.dst();
    }
    if let Some(k) = CompareInst::from_mir(i) {
        return k.dst();
    }
    if let Some(k) = LoadInst::from_mir(i) {
        return k.dst();
    }
    if let Some(k) = CastInst::from_mir(i) {
        return k.dst();
    }
    if let Some(k) = TypeOpInst::from_mir(i) {
        return k.dst();
    }
    if let Some(k) = ArrayGetInst::from_mir(i) {
        return k.dst();
    }
    if let Some(k) = PhiInst::from_mir(i) {
        return k.dst();
    }
    if let Some(k) = NewBoxInst::from_mir(i) {
        return k.dst();
    }
    if let Some(_k) = StoreInst::from_mir(i) {
        return None;
    }
    if let Some(_k) = ArraySetInst::from_mir(i) {
        return None;
    }
    if let Some(_k) = ReturnInst::from_mir(i) {
        return None;
    }
    if let Some(_k) = BranchInst::from_mir(i) {
        return None;
    }
    if let Some(_k) = JumpInst::from_mir(i) {
        return None;
    }
    if let Some(_k) = PrintInst::from_mir(i) {
        return None;
    }
    if let Some(_k) = DebugInst::from_mir(i) {
        return None;
    }
    if let Some(k) = CallLikeInst::from_mir(i) {
        return k.dst();
    }
    if let Some(k) = TypeCheckInst::from_mir(i) {
        return k.dst();
    }
    if let Some(k) = CopyInst::from_mir(i) {
        return k.dst();
    }
    if let Some(_k) = NopInst::from_mir(i) {
        return None;
    }
    if let Some(_k) = ThrowInst::from_mir(i) {
        return None;
    }
    if let Some(k) = CatchInst::from_mir(i) {
        return k.dst();
    }
    if let Some(_k) = SafepointInst::from_mir(i) {
        return None;
    }
    if let Some(k) = NewClosureInst::from_mir(i) {
        return k.dst();
    }
    None
}

pub fn used_via_meta(i: &MirInstruction) -> Option<Vec<ValueId>> {
    if let Some(k) = ConstInst::from_mir(i) {
        return Some(k.used());
    }
    if let Some(k) = BinOpInst::from_mir(i) {
        return Some(k.used());
    }
    if let Some(k) = UnaryOpInst::from_mir(i) {
        return Some(k.used());
    }
    if let Some(k) = CompareInst::from_mir(i) {
        return Some(k.used());
    }
    if let Some(k) = LoadInst::from_mir(i) {
        return Some(k.used());
    }
    if let Some(k) = CastInst::from_mir(i) {
        return Some(k.used());
    }
    if let Some(k) = TypeOpInst::from_mir(i) {
        return Some(k.used());
    }
    if let Some(k) = ArrayGetInst::from_mir(i) {
        return Some(k.used());
    }
    if let Some(k) = PhiInst::from_mir(i) {
        return Some(k.used());
    }
    if let Some(k) = NewBoxInst::from_mir(i) {
        return Some(k.used());
    }
    if let Some(k) = StoreInst::from_mir(i) {
        return Some(k.used());
    }
    if let Some(k) = ArraySetInst::from_mir(i) {
        return Some(k.used());
    }
    if let Some(k) = ReturnInst::from_mir(i) {
        return Some(k.used());
    }
    if let Some(k) = BranchInst::from_mir(i) {
        return Some(k.used());
    }
    if let Some(k) = JumpInst::from_mir(i) {
        return Some(k.used());
    }
    if let Some(k) = PrintInst::from_mir(i) {
        return Some(k.used());
    }
    if let Some(k) = DebugInst::from_mir(i) {
        return Some(k.used());
    }
    if let Some(k) = CallLikeInst::from_mir(i) {
        return Some(k.used());
    }
    if let Some(k) = TypeCheckInst::from_mir(i) {
        return Some(k.used());
    }
    if let Some(k) = CopyInst::from_mir(i) {
        return Some(k.used());
    }
    if let Some(k) = NopInst::from_mir(i) {
        return Some(k.used());
    }
    if let Some(k) = ThrowInst::from_mir(i) {
        return Some(k.used());
    }
    if let Some(k) = CatchInst::from_mir(i) {
        return Some(k.used());
    }
    if let Some(k) = SafepointInst::from_mir(i) {
        return Some(k.used());
    }
    if let Some(k) = NewClosureInst::from_mir(i) {
        return Some(k.used());
    }
    None
}

// ---- BarrierRead ---- (macro-generated)
inst_meta! {
    pub struct BarrierReadInst { ptr: ValueId }
    => {
        from_mir = |i| match i { MirInstruction::BarrierRead { ptr } => Some(BarrierReadInst { ptr: *ptr }), _ => None };
        effects = |_: &Self| EffectMask::READ.add(Effect::Barrier);
        dst = |_: &Self| None;
        used = |s: &Self| vec![s.ptr];
    }
}

// ---- BarrierWrite ---- (macro-generated)
inst_meta! {
    pub struct BarrierWriteInst { ptr: ValueId }
    => {
        from_mir = |i| match i { MirInstruction::BarrierWrite { ptr } => Some(BarrierWriteInst { ptr: *ptr }), _ => None };
        effects = |_: &Self| EffectMask::WRITE.add(Effect::Barrier);
        dst = |_: &Self| None;
        used = |s: &Self| vec![s.ptr];
    }
}

// ---- Barrier (unified) ---- (macro-generated)
inst_meta! {
    pub struct BarrierInst { op: MirBarrierOp, ptr: ValueId }
    => {
        from_mir = |i| match i { MirInstruction::Barrier { op, ptr } => Some(BarrierInst { op: *op, ptr: *ptr }), _ => None };
        effects = |s: &Self| match s.op { MirBarrierOp::Read => EffectMask::READ.add(Effect::Barrier), MirBarrierOp::Write => EffectMask::WRITE.add(Effect::Barrier) };
        dst = |_: &Self| None;
        used = |s: &Self| vec![s.ptr];
    }
}

// ---- Ref ops ---- (macro-generated)
inst_meta! {
    pub struct RefNewInst { dst: ValueId, box_val: ValueId }
    => {
        from_mir = |i| match i { MirInstruction::RefNew { dst, box_val } => Some(RefNewInst { dst: *dst, box_val: *box_val }), _ => None };
        effects = |_: &Self| EffectMask::PURE;
        dst = |s: &Self| Some(s.dst);
        used = |s: &Self| vec![s.box_val];
    }
}
inst_meta! {
    pub struct RefGetInst { dst: ValueId, reference: ValueId }
    => {
        from_mir = |i| match i { MirInstruction::RefGet { dst, reference, .. } => Some(RefGetInst { dst: *dst, reference: *reference }), _ => None };
        effects = |_: &Self| EffectMask::READ;
        dst = |s: &Self| Some(s.dst);
        used = |s: &Self| vec![s.reference];
    }
}
inst_meta! {
    pub struct RefSetInst { reference: ValueId, value: ValueId }
    => {
        from_mir = |i| match i { MirInstruction::RefSet { reference, value, .. } => Some(RefSetInst { reference: *reference, value: *value }), _ => None };
        effects = |_: &Self| EffectMask::WRITE;
        dst = |_: &Self| None;
        used = |s: &Self| vec![s.reference, s.value];
    }
}

// ---- Weak ops ---- (macro-generated)
inst_meta! {
    pub struct WeakNewInst { dst: ValueId, box_val: ValueId }
    => {
        from_mir = |i| match i { MirInstruction::WeakNew { dst, box_val } => Some(WeakNewInst { dst: *dst, box_val: *box_val }), _ => None };
        effects = |_: &Self| EffectMask::PURE;
        dst = |s: &Self| Some(s.dst);
        used = |s: &Self| vec![s.box_val];
    }
}
inst_meta! {
    pub struct WeakLoadInst { dst: ValueId, weak_ref: ValueId }
    => {
        from_mir = |i| match i { MirInstruction::WeakLoad { dst, weak_ref } => Some(WeakLoadInst { dst: *dst, weak_ref: *weak_ref }), _ => None };
        effects = |_: &Self| EffectMask::READ;
        dst = |s: &Self| Some(s.dst);
        used = |s: &Self| vec![s.weak_ref];
    }
}
inst_meta! {
    pub struct WeakRefInst { dst: ValueId, op: MirWeakRefOp, value: ValueId }
    => {
        from_mir = |i| match i { MirInstruction::WeakRef { dst, op, value } => Some(WeakRefInst { dst: *dst, op: *op, value: *value }), _ => None };
        effects = |s: &Self| match s.op { MirWeakRefOp::New => EffectMask::PURE, MirWeakRefOp::Load => EffectMask::READ };
        dst = |s: &Self| Some(s.dst);
        used = |s: &Self| vec![s.value];
    }
}

// ---- Future ops ---- (macro-generated)
inst_meta! {
    pub struct FutureNewInst { dst: ValueId, value: ValueId }
    => {
        from_mir = |i| match i { MirInstruction::FutureNew { dst, value } => Some(FutureNewInst { dst: *dst, value: *value }), _ => None };
        effects = |_: &Self| EffectMask::PURE.add(Effect::Alloc);
        dst = |s: &Self| Some(s.dst);
        used = |s: &Self| vec![s.value];
    }
}
inst_meta! {
    pub struct FutureSetInst { future: ValueId, value: ValueId }
    => {
        from_mir = |i| match i { MirInstruction::FutureSet { future, value } => Some(FutureSetInst { future: *future, value: *value }), _ => None };
        effects = |_: &Self| EffectMask::WRITE;
        dst = |_: &Self| None;
        used = |s: &Self| vec![s.future, s.value];
    }
}
inst_meta! {
    pub struct AwaitInst { dst: ValueId, future: ValueId }
    => {
        from_mir = |i| match i { MirInstruction::Await { dst, future } => Some(AwaitInst { dst: *dst, future: *future }), _ => None };
        effects = |_: &Self| EffectMask::READ.add(Effect::Async);
        dst = |s: &Self| Some(s.dst);
        used = |s: &Self| vec![s.future];
    }
}

// ---- UnaryOp ---- (macro-generated)
inst_meta! {
    pub struct UnaryOpInst { dst: ValueId, operand: ValueId }
    => {
        from_mir = |i| match i { MirInstruction::UnaryOp { dst, operand, .. } => Some(UnaryOpInst { dst: *dst, operand: *operand }), _ => None };
        effects = |_: &Self| EffectMask::PURE;
        dst = |s: &Self| Some(s.dst);
        used = |s: &Self| vec![s.operand];
    }
}

// ---- Compare ---- (macro-generated)
inst_meta! {
    pub struct CompareInst { dst: ValueId, lhs: ValueId, rhs: ValueId }
    => {
        from_mir = |i| match i { MirInstruction::Compare { dst, lhs, rhs, .. } => Some(CompareInst { dst: *dst, lhs: *lhs, rhs: *rhs }), _ => None };
        effects = |_: &Self| EffectMask::PURE;
        dst = |s: &Self| Some(s.dst);
        used = |s: &Self| vec![s.lhs, s.rhs];
    }
}

// ---- Load ---- (macro-generated)
inst_meta! {
    pub struct LoadInst { dst: ValueId, ptr: ValueId }
    => {
        from_mir = |i| match i { MirInstruction::Load { dst, ptr } => Some(LoadInst { dst: *dst, ptr: *ptr }), _ => None };
        effects = |_: &Self| EffectMask::READ;
        dst = |s: &Self| Some(s.dst);
        used = |s: &Self| vec![s.ptr];
    }
}

// ---- Cast ---- (macro-generated)
inst_meta! {
    pub struct CastInst { dst: ValueId, value: ValueId, target_type: MirType }
    => {
        from_mir = |i| match i { MirInstruction::Cast { dst, value, target_type } => Some(CastInst { dst: *dst, value: *value, target_type: target_type.clone() }), _ => None };
        effects = |_: &Self| EffectMask::PURE;
        dst = |s: &Self| Some(s.dst);
        used = |s: &Self| vec![s.value];
    }
}

// ---- TypeOp ---- (macro-generated)
inst_meta! {
    pub struct TypeOpInst { dst: ValueId, op: MirTypeOpKind, value: ValueId, ty: MirType }
    => {
        from_mir = |i| match i { MirInstruction::TypeOp { dst, op, value, ty } => Some(TypeOpInst { dst: *dst, op: *op, value: *value, ty: ty.clone() }), _ => None };
        effects = |_: &Self| EffectMask::PURE;
        dst = |s: &Self| Some(s.dst);
        used = |s: &Self| vec![s.value];
    }
}

// ---- ArrayGet ---- (macro-generated)
inst_meta! {
    pub struct ArrayGetInst { dst: ValueId, array: ValueId, index: ValueId }
    => {
        from_mir = |i| match i { MirInstruction::ArrayGet { dst, array, index } => Some(ArrayGetInst { dst: *dst, array: *array, index: *index }), _ => None };
        effects = |_: &Self| EffectMask::READ;
        dst = |s: &Self| Some(s.dst);
        used = |s: &Self| vec![s.array, s.index];
    }
}

// ---- Phi ---- (macro-generated)
inst_meta! {
    pub struct PhiInst { dst: ValueId, inputs: Vec<(BasicBlockId, ValueId)> }
    => {
        from_mir = |i| match i { MirInstruction::Phi { dst, inputs, .. } => Some(PhiInst { dst: *dst, inputs: inputs.clone() }), _ => None };
        effects = |_: &Self| EffectMask::PURE;
        dst = |s: &Self| Some(s.dst);
        used = |s: &Self| s.inputs.iter().map(|(_, v)| *v).collect();
    }
}

// ---- NewBox ---- (macro-generated)
inst_meta! {
    pub struct NewBoxInst { dst: ValueId, args: Vec<ValueId> }
    => {
        from_mir = |i| match i { MirInstruction::NewBox { dst, args, .. } => Some(NewBoxInst { dst: *dst, args: args.clone() }), _ => None };
        effects = |_: &Self| EffectMask::PURE.add(Effect::Alloc);
        dst = |s: &Self| Some(s.dst);
        used = |s: &Self| s.args.clone();
    }
}

// ---- NewClosure ---- (macro-generated)
inst_meta! {
    pub struct NewClosureInst { dst: ValueId, captures: Vec<(String, ValueId)>, me: Option<ValueId> }
    => {
        from_mir = |i| match i { MirInstruction::NewClosure { dst, captures, me, .. } => Some(NewClosureInst { dst: *dst, captures: captures.clone(), me: *me }), _ => None };
        effects = |_: &Self| EffectMask::PURE.add(Effect::Alloc);
        dst = |s: &Self| Some(s.dst);
        used = |s: &Self| { let mut v: Vec<ValueId> = s.captures.iter().map(|(_, id)| *id).collect(); if let Some(m) = s.me { v.push(m); } v };
    }
}

// ---- Store ---- (macro-generated)
inst_meta! {
    pub struct StoreInst { value: ValueId, ptr: ValueId }
    => {
        from_mir = |i| match i { MirInstruction::Store { value, ptr } => Some(StoreInst { value: *value, ptr: *ptr }), _ => None };
        effects = |_: &Self| EffectMask::WRITE;
        dst = |_: &Self| None;
        used = |s: &Self| vec![s.value, s.ptr];
    }
}

// ---- ArraySet ---- (macro-generated)
inst_meta! {
    pub struct ArraySetInst { array: ValueId, index: ValueId, value: ValueId }
    => {
        from_mir = |i| match i { MirInstruction::ArraySet { array, index, value } => Some(ArraySetInst { array: *array, index: *index, value: *value }), _ => None };
        effects = |_: &Self| EffectMask::WRITE;
        dst = |_: &Self| None;
        used = |s: &Self| vec![s.array, s.index, s.value];
    }
}

// ---- Return ---- (macro-generated)
inst_meta! {
    pub struct ReturnInst { value: Option<ValueId> }
    => {
        from_mir = |i| match i { MirInstruction::Return { value } => Some(ReturnInst { value: *value }), _ => None };
        effects = |_: &Self| EffectMask::PURE;
        dst = |_: &Self| None;
        used = |s: &Self| s.value.map(|v| vec![v]).unwrap_or_default();
    }
}

// ---- Branch ---- (macro-generated)
inst_meta! {
    pub struct BranchInst { condition: ValueId }
    => {
        from_mir = |i| match i { MirInstruction::Branch { condition, .. } => Some(BranchInst { condition: *condition }), _ => None };
        effects = |_: &Self| EffectMask::PURE;
        dst = |_: &Self| None;
        used = |s: &Self| vec![s.condition];
    }
}

// ---- Jump ---- (macro-generated)
inst_meta! {
    pub struct JumpInst { }
    => {
        from_mir = |i| match i { MirInstruction::Jump { .. } => Some(JumpInst {}), _ => None };
        effects = |_: &Self| EffectMask::PURE;
        dst = |_: &Self| None;
        used = |_: &Self| Vec::new();
    }
}

// ---- Print ---- (macro-generated)
inst_meta! {
    pub struct PrintInst { value: ValueId, effects_mask: EffectMask }
    => {
        from_mir = |i| match i { MirInstruction::Print { value, effects } => Some(PrintInst { value: *value, effects_mask: *effects }), _ => None };
        effects = |s: &Self| s.effects_mask;
        dst = |_: &Self| None;
        used = |s: &Self| vec![s.value];
    }
}

// ---- Debug ---- (macro-generated)
inst_meta! {
    pub struct DebugInst { value: ValueId }
    => {
        from_mir = |i| match i { MirInstruction::Debug { value, .. } => Some(DebugInst { value: *value }), _ => None };
        effects = |_: &Self| EffectMask::PURE.add(Effect::Debug);
        dst = |_: &Self| None;
        used = |s: &Self| vec![s.value];
    }
}

// ---- TypeCheck ---- (macro-generated)
inst_meta! {
    pub struct TypeCheckInst { dst: ValueId, value: ValueId }
    => {
        from_mir = |i| match i { MirInstruction::TypeCheck { dst, value, .. } => Some(TypeCheckInst { dst: *dst, value: *value }), _ => None };
        effects = |_: &Self| EffectMask::PURE;
        dst = |s: &Self| Some(s.dst);
        used = |s: &Self| vec![s.value];
    }
}

// ---- Copy ---- (macro-generated)
inst_meta! {
    pub struct CopyInst { dst: ValueId, src: ValueId }
    => {
        from_mir = |i| match i { MirInstruction::Copy { dst, src } => Some(CopyInst { dst: *dst, src: *src }), _ => None };
        effects = |_: &Self| EffectMask::PURE;
        dst = |s: &Self| Some(s.dst);
        used = |s: &Self| vec![s.src];
    }
}

// ---- Nop ---- (macro-generated)
inst_meta! {
    pub struct NopInst { }
    => {
        from_mir = |i| match i { MirInstruction::Nop => Some(NopInst {}), _ => None };
        effects = |_: &Self| EffectMask::PURE;
        dst = |_: &Self| None;
        used = |_: &Self| Vec::new();
    }
}

// ---- Throw ---- (macro-generated)
inst_meta! {
    pub struct ThrowInst { exception: ValueId, effects_mask: EffectMask }
    => {
        from_mir = |i| match i { MirInstruction::Throw { exception, effects } => Some(ThrowInst { exception: *exception, effects_mask: *effects }), _ => None };
        effects = |s: &Self| s.effects_mask;
        dst = |_: &Self| None;
        used = |s: &Self| vec![s.exception];
    }
}

// ---- Catch ---- (macro-generated)
inst_meta! {
    pub struct CatchInst { exception_value: ValueId }
    => {
        from_mir = |i| match i { MirInstruction::Catch { exception_value, .. } => Some(CatchInst { exception_value: *exception_value }), _ => None };
        effects = |_: &Self| EffectMask::CONTROL;
        dst = |s: &Self| Some(s.exception_value);
        used = |_: &Self| Vec::new();
    }
}

// ---- Safepoint ---- (macro-generated)
inst_meta! {
    pub struct SafepointInst { }
    => {
        from_mir = |i| match i { MirInstruction::Safepoint => Some(SafepointInst {}), _ => None };
        effects = |_: &Self| EffectMask::PURE;
        dst = |_: &Self| None;
        used = |_: &Self| Vec::new();
    }
}

// ---- Call-like (dst/used only; effects fallback in MirInstruction) ----
#[derive(Debug, Clone)]
pub enum CallLikeInst {
    Call {
        dst: Option<ValueId>,
        func: ValueId,
        args: Vec<ValueId>,
    },
    BoxCall {
        dst: Option<ValueId>,
        box_val: ValueId,
        args: Vec<ValueId>,
    },
    PluginInvoke {
        dst: Option<ValueId>,
        box_val: ValueId,
        args: Vec<ValueId>,
    },
    ExternCall {
        dst: Option<ValueId>,
        args: Vec<ValueId>,
    },
}

impl CallLikeInst {
    pub fn from_mir(i: &MirInstruction) -> Option<Self> {
        match i {
            MirInstruction::Call {
                dst, func, args, ..
            } => Some(CallLikeInst::Call {
                dst: *dst,
                func: *func,
                args: args.clone(),
            }),
            MirInstruction::BoxCall {
                dst, box_val, args, ..
            } => Some(CallLikeInst::BoxCall {
                dst: *dst,
                box_val: *box_val,
                args: args.clone(),
            }),
            MirInstruction::PluginInvoke {
                dst, box_val, args, ..
            } => Some(CallLikeInst::PluginInvoke {
                dst: *dst,
                box_val: *box_val,
                args: args.clone(),
            }),
            MirInstruction::ExternCall { dst, args, .. } => Some(CallLikeInst::ExternCall {
                dst: *dst,
                args: args.clone(),
            }),
            _ => None,
        }
    }

    pub fn dst(&self) -> Option<ValueId> {
        match self {
            CallLikeInst::Call { dst, .. }
            | CallLikeInst::BoxCall { dst, .. }
            | CallLikeInst::PluginInvoke { dst, .. }
            | CallLikeInst::ExternCall { dst, .. } => *dst,
        }
    }

    pub fn used(&self) -> Vec<ValueId> {
        match self {
            CallLikeInst::Call { func, args, .. } => {
                let mut v = Vec::new();
                // func は legacy 経路では「関数値」を指すが、unified 経路では
                // ValueId::INVALID がダミーとして入る。INVALID の場合は SSA の
                // 使用値としては数えず、args のみを使用値とみなす。
                if *func != ValueId::INVALID {
                    v.push(*func);
                }
                v.extend(args.iter().copied());
                v
            }
            CallLikeInst::BoxCall { box_val, args, .. }
            | CallLikeInst::PluginInvoke { box_val, args, .. } => {
                let mut v = vec![*box_val];
                v.extend(args.iter().copied());
                v
            }
            CallLikeInst::ExternCall { args, .. } => args.clone(),
        }
    }
}
