use std::cell::Cell;
use std::cell::RefCell;

#[cfg(feature = "mrubyedge-debug")]
use std::env;
use std::rc::Rc;

use crate::Error;
use crate::rite::insn::{Fetched, OpCode};
use crate::yamrb::helpers::mrb_call_inspect;

use super::prelude::hash::mrb_hash_delete;
use super::prelude::object::mrb_object_is_equal;
use super::value::RHashMap;
use super::{helpers::mrb_funcall, value::*, vm::*};

// OpCodes of mruby 3.2.0 from mruby/op.h:
// OPCODE(NOP,        Z)        /* no operation */
// OPCODE(MOVE,       BB)       /* R[a] = R[b] */
// OPCODE(LOADL,      BB)       /* R[a] = Pool[b] */
// OPCODE(LOADI,      BB)       /* R[a] = mrb_int(b) */
// OPCODE(LOADINEG,   BB)       /* R[a] = mrb_int(-b) */
// OPCODE(LOADI__1,   B)        /* R[a] = mrb_int(-1) */
// OPCODE(LOADI_0,    B)        /* R[a] = mrb_int(0) */
// OPCODE(LOADI_1,    B)        /* R[a] = mrb_int(1) */
// OPCODE(LOADI_2,    B)        /* R[a] = mrb_int(2) */
// OPCODE(LOADI_3,    B)        /* R[a] = mrb_int(3) */
// OPCODE(LOADI_4,    B)        /* R[a] = mrb_int(4) */
// OPCODE(LOADI_5,    B)        /* R[a] = mrb_int(5) */
// OPCODE(LOADI_6,    B)        /* R[a] = mrb_int(6) */
// OPCODE(LOADI_7,    B)        /* R[a] = mrb_int(7) */
// OPCODE(LOADI16,    BS)       /* R[a] = mrb_int(b) */
// OPCODE(LOADI32,    BSS)      /* R[a] = mrb_int((b<<16)+c) */
// OPCODE(LOADSYM,    BB)       /* R[a] = Syms[b] */
// OPCODE(LOADNIL,    B)        /* R[a] = nil */
// OPCODE(LOADSELF,   B)        /* R[a] = self */
// OPCODE(LOADT,      B)        /* R[a] = true */
// OPCODE(LOADF,      B)        /* R[a] = false */
// OPCODE(GETGV,      BB)       /* R[a] = getglobal(Syms[b]) */
// OPCODE(SETGV,      BB)       /* setglobal(Syms[b], R[a]) */
// OPCODE(GETSV,      BB)       /* R[a] = Special[Syms[b]] */
// OPCODE(SETSV,      BB)       /* Special[Syms[b]] = R[a] */
// OPCODE(GETIV,      BB)       /* R[a] = ivget(Syms[b]) */
// OPCODE(SETIV,      BB)       /* ivset(Syms[b],R[a]) */
// OPCODE(GETCV,      BB)       /* R[a] = cvget(Syms[b]) */
// OPCODE(SETCV,      BB)       /* cvset(Syms[b],R[a]) */
// OPCODE(GETCONST,   BB)       /* R[a] = constget(Syms[b]) */
// OPCODE(SETCONST,   BB)       /* constset(Syms[b],R[a]) */
// OPCODE(GETMCNST,   BB)       /* R[a] = R[a]::Syms[b] */
// OPCODE(SETMCNST,   BB)       /* R[a+1]::Syms[b] = R[a] */
// OPCODE(GETUPVAR,   BBB)      /* R[a] = uvget(b,c) */
// OPCODE(SETUPVAR,   BBB)      /* uvset(b,c,R[a]) */
// OPCODE(GETIDX,     B)        /* R[a] = R[a][R[a+1]] */
// OPCODE(SETIDX,     B)        /* R[a][R[a+1]] = R[a+2] */
// OPCODE(JMP,        S)        /* pc+=a */
// OPCODE(JMPIF,      BS)       /* if R[a] pc+=b */
// OPCODE(JMPNOT,     BS)       /* if !R[a] pc+=b */
// OPCODE(JMPNIL,     BS)       /* if R[a]==nil pc+=b */
// OPCODE(JMPUW,      S)        /* unwind_and_jump_to(a) */
// OPCODE(EXCEPT,     B)        /* R[a] = exc */
// OPCODE(RESCUE,     BB)       /* R[b] = R[a].isa?(R[b]) */
// OPCODE(RAISEIF,    B)        /* raise(R[a]) if R[a] */
// OPCODE(SSEND,      BBB)      /* R[a] = self.send(Syms[b],R[a+1]..,R[a+n+1]:R[a+n+2]..) (c=n|k<<4) */
// OPCODE(SSENDB,     BBB)      /* R[a] = self.send(Syms[b],R[a+1]..,R[a+n+1]:R[a+n+2]..,&R[a+n+2k+1]) */
// OPCODE(SEND,       BBB)      /* R[a] = R[a].send(Syms[b],R[a+1]..,R[a+n+1]:R[a+n+2]..) (c=n|k<<4) */
// OPCODE(SENDB,      BBB)      /* R[a] = R[a].send(Syms[b],R[a+1]..,R[a+n+1]:R[a+n+2]..,&R[a+n+2k+1]) */
// OPCODE(CALL,       Z)        /* self.call(*, **, &) (But overlay the current call frame; tailcall) */
// OPCODE(SUPER,      BB)       /* R[a] = super(R[a+1],... ,R[a+b+1]) */
// OPCODE(ARGARY,     BS)       /* R[a] = argument array (16=m5:r1:m5:d1:lv4) */
// OPCODE(ENTER,      W)        /* arg setup according to flags (23=m5:o5:r1:m5:k5:d1:b1) */
// OPCODE(KEY_P,      BB)       /* R[a] = kdict.key?(Syms[b]) */
// OPCODE(KEYEND,     Z)        /* raise unless kdict.empty? */
// OPCODE(KARG,       BB)       /* R[a] = kdict[Syms[b]]; kdict.delete(Syms[b]) */
// OPCODE(RETURN,     B)        /* return R[a] (normal) */
// OPCODE(RETURN_BLK, B)        /* return R[a] (in-block return) */
// OPCODE(BREAK,      B)        /* break R[a] */
// OPCODE(BLKPUSH,    BS)       /* R[a] = block (16=m5:r1:m5:d1:lv4) */
// OPCODE(ADD,        B)        /* R[a] = R[a]+R[a+1] */
// OPCODE(ADDI,       BB)       /* R[a] = R[a]+mrb_int(b) */
// OPCODE(SUB,        B)        /* R[a] = R[a]-R[a+1] */
// OPCODE(SUBI,       BB)       /* R[a] = R[a]-mrb_int(b) */
// OPCODE(MUL,        B)        /* R[a] = R[a]*R[a+1] */
// OPCODE(DIV,        B)        /* R[a] = R[a]/R[a+1] */
// OPCODE(EQ,         B)        /* R[a] = R[a]==R[a+1] */
// OPCODE(LT,         B)        /* R[a] = R[a]<R[a+1] */
// OPCODE(LE,         B)        /* R[a] = R[a]<=R[a+1] */
// OPCODE(GT,         B)        /* R[a] = R[a]>R[a+1] */
// OPCODE(GE,         B)        /* R[a] = R[a]>=R[a+1] */
// OPCODE(ARRAY,      BB)       /* R[a] = ary_new(R[a],R[a+1]..R[a+b]) */
// OPCODE(ARRAY2,     BBB)      /* R[a] = ary_new(R[b],R[b+1]..R[b+c]) */
// OPCODE(ARYCAT,     B)        /* ary_cat(R[a],R[a+1]) */
// OPCODE(ARYPUSH,    BB)       /* ary_push(R[a],R[a+1]..R[a+b]) */
// OPCODE(ARYSPLAT,   B)        /* R[a] = ary_splat(R[a]) */
// OPCODE(AREF,       BBB)      /* R[a] = R[b][c] */
// OPCODE(ASET,       BBB)      /* R[b][c] = R[a] */
// OPCODE(APOST,      BBB)      /* *R[a],R[a+1]..R[a+c] = R[a][b..] */
// OPCODE(INTERN,     B)        /* R[a] = intern(R[a]) */
// OPCODE(SYMBOL,     BB)       /* R[a] = intern(Pool[b]) */
// OPCODE(STRING,     BB)       /* R[a] = str_dup(Pool[b]) */
// OPCODE(STRCAT,     B)        /* str_cat(R[a],R[a+1]) */
// OPCODE(HASH,       BB)       /* R[a] = hash_new(R[a],R[a+1]..R[a+b*2-1]) */
// OPCODE(HASHADD,    BB)       /* hash_push(R[a],R[a+1]..R[a+b*2]) */
// OPCODE(HASHCAT,    B)        /* R[a] = hash_cat(R[a],R[a+1]) */
// OPCODE(LAMBDA,     BB)       /* R[a] = lambda(Irep[b],L_LAMBDA) */
// OPCODE(BLOCK,      BB)       /* R[a] = lambda(Irep[b],L_BLOCK) */
// OPCODE(METHOD,     BB)       /* R[a] = lambda(Irep[b],L_METHOD) */
// OPCODE(RANGE_INC,  B)        /* R[a] = range_new(R[a],R[a+1],FALSE) */
// OPCODE(RANGE_EXC,  B)        /* R[a] = range_new(R[a],R[a+1],TRUE) */
// OPCODE(OCLASS,     B)        /* R[a] = ::Object */
// OPCODE(CLASS,      BB)       /* R[a] = newclass(R[a],Syms[b],R[a+1]) */
// OPCODE(MODULE,     BB)       /* R[a] = newmodule(R[a],Syms[b]) */
// OPCODE(EXEC,       BB)       /* R[a] = blockexec(R[a],Irep[b]) */
// OPCODE(DEF,        BB)       /* R[a].newmethod(Syms[b],R[a+1]); R[a] = Syms[b] */
// OPCODE(ALIAS,      BB)       /* alias_method(target_class,Syms[a],Syms[b]) */
// OPCODE(UNDEF,      B)        /* undef_method(target_class,Syms[a]) */
// OPCODE(SCLASS,     B)        /* R[a] = R[a].singleton_class */
// OPCODE(TCLASS,     B)        /* R[a] = target_class */
// OPCODE(DEBUG,      BBB)      /* print a,b,c */
// OPCODE(ERR,        B)        /* raise(LocalJumpError, Pool[a]) */
// OPCODE(EXT1,       Z)        /* make 1st operand (a) 16bit */
// OPCODE(EXT2,       Z)        /* make 2nd operand (b) 16bit */
// OPCODE(EXT3,       Z)        /* make 1st and 2nd operands 16bit */
// OPCODE(STOP,       Z)        /* stop VM */
// functions that represent each opcode are defined in this file.
// to understand the meaning of each operand mark, see enum Fetched in rite/insn.rs:
// pub enum Fetched {
//     Z,
//     B(u8),
//     BB(u8, u8),
//     BBB(u8, u8, u8),
//     BS(u8, u16),
//     BSS(u8, u16, u16),
//     S(u16),
//     W(u32), // u24 in real layout
// }
//

const ENTER_M1_MASK: u32 = 0b11111 << 18;
const ENTER_O_MASK: u32 = 0b11111 << 13;
const ENTER_R_MASK: u32 = 0b1 << 12;
const ENTER_M2_MASK: u32 = 0b11111 << 7;
const ENTER_K_MASK: u32 = 0b11111 << 2;
const ENTER_D_MASK: u32 = 0b1 << 1;
const ENTER_B_MASK: u32 = 0b1 << 0;

pub(crate) fn consume_expr(
    vm: &mut VM,
    code: OpCode,
    operand: &Fetched,
    pos: usize,
    len: usize,
) -> Result<(), Error> {
    use crate::rite::insn::OpCode::*;
    match code {
        NOP => {
            op_nop(vm, operand)?;
        }
        MOVE => {
            op_move(vm, operand)?;
        }
        LOADL => {
            op_loadl(vm, operand)?;
        }
        LOADI => {
            op_loadi(vm, operand)?;
        }
        LOADINEG => {
            op_loadineg(vm, operand)?;
        }
        LOADI__1 => {
            op_loadi_n(vm, -1, operand)?;
        }
        LOADI_0 => {
            op_loadi_n(vm, 0, operand)?;
        }
        LOADI_1 => {
            op_loadi_n(vm, 1, operand)?;
        }
        LOADI_2 => {
            op_loadi_n(vm, 2, operand)?;
        }
        LOADI_3 => {
            op_loadi_n(vm, 3, operand)?;
        }
        LOADI_4 => {
            op_loadi_n(vm, 4, operand)?;
        }
        LOADI_5 => {
            op_loadi_n(vm, 5, operand)?;
        }
        LOADI_6 => {
            op_loadi_n(vm, 6, operand)?;
        }
        LOADI_7 => {
            op_loadi_n(vm, 7, operand)?;
        }
        LOADI16 => {
            op_loadi16(vm, operand)?;
        }
        LOADI32 => {
            op_loadi32(vm, operand)?;
        }
        LOADSYM => {
            op_loadsym(vm, operand)?;
        }
        LOADNIL => {
            op_loadnil(vm, operand)?;
        }
        LOADSELF => {
            op_loadself(vm, operand)?;
        }
        LOADT => {
            op_loadt(vm, operand)?;
        }
        LOADF => {
            op_loadf(vm, operand)?;
        }
        GETGV => {
            op_getgv(vm, operand)?;
        }
        SETGV => {
            op_setgv(vm, operand)?;
        }
        // GETSV => {
        //     // op_getsv(vm, &operand)?;
        // }
        // SETSV => {
        //     // op_setsv(vm, &operand)?;
        // }
        GETIV => {
            op_getiv(vm, operand)?;
        }
        SETIV => {
            op_setiv(vm, operand)?;
        }
        // GETCV => {
        //     // op_getcv(vm, &operand)?;
        // }
        // SETCV => {
        //     // op_setcv(vm, &operand)?;
        // }
        GETCONST => {
            op_getconst(vm, operand)?;
        }
        SETCONST => {
            op_setconst(vm, operand)?;
        }
        GETMCNST => {
            op_getmcnst(vm, operand)?;
        }
        // SETMCNST => {
        //     // op_setmcnst(vm, &operand)?;
        // }
        GETUPVAR => {
            op_getupvar(vm, operand)?;
        }
        SETUPVAR => {
            op_setupvar(vm, operand)?;
        }
        GETIDX => {
            op_getidx(vm, operand)?;
        }
        SETIDX => {
            op_setidx(vm, operand)?;
        }
        JMP => {
            op_jmp(vm, operand, pos + len)?;
        }
        JMPIF => {
            op_jmpif(vm, operand, pos + len)?;
        }
        JMPNOT => {
            op_jmpnot(vm, operand, pos + len)?;
        }
        JMPNIL => {
            op_jmpnil(vm, operand, pos + len)?;
        }
        JMPUW => {
            op_jmpuw(vm, operand, pos + len)?;
        }
        EXCEPT => {
            op_except(vm, operand)?;
        }
        RESCUE => {
            op_rescue(vm, operand)?;
        }
        RAISEIF => {
            op_raiseif(vm, operand)?;
        }
        SSEND => {
            op_ssend(vm, operand)?;
        }
        SSENDB => {
            op_ssendb(vm, operand)?;
        }
        SEND => {
            op_send(vm, operand)?;
        }
        SENDB => {
            op_sendb(vm, operand)?;
        }
        CALL => {
            op_call(vm, operand)?;
        }
        SUPER => {
            op_super(vm, operand)?;
        }
        // ARGARY => {
        //     // op_argary(vm, &operand)?;
        // }
        ENTER => {
            op_enter(vm, operand)?;
        }
        KEY_P => {
            op_key_p(vm, operand)?;
        }
        KEYEND => {
            op_keyend(vm, operand)?;
        }
        KARG => {
            op_karg(vm, operand)?;
        }
        RETURN => {
            op_return(vm, operand)?;
        }
        RETURN_BLK => {
            op_return_blk(vm, operand)?;
        }
        BREAK => {
            op_break(vm, operand)?;
        }
        BLKPUSH => {
            op_blkpush(vm, operand)?;
        }
        ADD => {
            op_add(vm, operand)?;
        }
        ADDI => {
            op_addi(vm, operand)?;
        }
        SUB => {
            op_sub(vm, operand)?;
        }
        SUBI => {
            op_subi(vm, operand)?;
        }
        MUL => {
            op_mul(vm, operand)?;
        }
        DIV => {
            op_div(vm, operand)?;
        }
        EQ => {
            op_eq(vm, operand)?;
        }
        LT => {
            op_lt(vm, operand)?;
        }
        LE => {
            op_le(vm, operand)?;
        }
        GT => {
            op_gt(vm, operand)?;
        }
        GE => {
            op_ge(vm, operand)?;
        }
        ARRAY => {
            op_array(vm, operand)?;
        }
        ARRAY2 => {
            op_array2(vm, operand)?;
        }
        ARYCAT => {
            op_arycat(vm, operand)?;
        }
        // ARYPUSH => {
        //     // op_arypush(vm, &operand)?;
        // }
        // ARYSPLAT => {
        //     // op_arysplat(vm, &operand)?;
        // }
        AREF => {
            op_aref(vm, operand)?;
        }
        // ASET => {
        //     // op_aset(vm, &operand)?;
        // }
        APOST => {
            op_apost(vm, operand)?;
        }
        // INTERN => {
        //     // op_intern(vm, &operand)?;
        // }
        SYMBOL => {
            op_symbol(vm, operand)?;
        }
        STRING => {
            op_string(vm, operand)?;
        }
        STRCAT => {
            op_strcat(vm, operand)?;
        }
        HASH => {
            op_hash(vm, operand)?;
        }
        // HASHADD => {
        //     // op_hashadd(vm, &operand)?;
        // }
        // HASHCAT => {
        //     // op_hashcat(vm, &operand)?;
        // }
        LAMBDA => {
            op_lambda(vm, operand)?;
        }
        BLOCK => {
            op_block(vm, operand)?;
        }
        METHOD => {
            op_method(vm, operand)?;
        }
        RANGE_INC => {
            op_range_inc(vm, operand)?;
        }
        RANGE_EXC => {
            op_range_exc(vm, operand)?;
        }
        OCLASS => {
            op_oclass(vm, operand)?;
        }
        CLASS => {
            op_class(vm, operand)?;
        }
        MODULE => {
            op_module(vm, operand)?;
        }
        EXEC => {
            op_exec(vm, operand)?;
        }
        DEF => {
            op_def(vm, operand)?;
        }
        ALIAS => {
            op_alias(vm, operand)?;
        }
        UNDEF => {
            op_undef(vm, operand)?;
        }
        SCLASS => {
            op_sclass(vm, operand)?;
        }
        TCLASS => {
            op_tclass(vm, operand)?;
        }
        // DEBUG => {
        //     // op_debug(vm, &operand)?;
        // }
        // ERR => {
        //     // op_err(vm, &operand)?;
        // }
        // EXT1 => {
        //     // op_ext1(vm, &operand)?;
        // }
        // EXT2 => {
        //     // op_ext2(vm, &operand)?;
        // }
        // EXT3 => {
        //     // op_ext3(vm, &operand)?;
        // }
        STOP => {
            op_stop(vm, operand)?;
        }
        _ => {
            unimplemented!("{:?}: Not supported yet", code)
        }
    }
    Ok(())
}

pub(crate) fn push_callinfo(
    vm: &mut VM,
    method_id: RSym,
    n_args: usize,
    method_owner: Option<Rc<RModule>>,
    return_reg: usize,
) {
    let callinfo = CALLINFO {
        prev: vm.current_callinfo.clone(),
        method_id,
        pc_irep: vm.current_irep.clone(),
        pc: vm.pc.get(),
        current_regs_offset: vm.current_regs_offset,
        n_args,
        return_reg,
        target_class: vm.target_class.clone(),
        method_owner,
        has_block: Cell::new(false),
    };
    vm.current_callinfo = Some(Rc::new(callinfo));
}

#[allow(dead_code)]
pub(crate) fn pop_callinfo(vm: &mut VM) {
    let ci = vm.current_callinfo.take();
    if ci.is_none() {
        unreachable!("callinfo underflow");
    }

    let ci = ci.unwrap();
    if let Some(prev) = &ci.prev {
        vm.current_callinfo.replace(prev.clone());
    }
    vm.current_irep = ci.pc_irep.clone();
    vm.pc.set(ci.pc);
    vm.current_regs_offset = ci.current_regs_offset;
    vm.target_class = ci.target_class.clone();
}

fn calcurate_pc(irep: &IREP, pc: usize, original_pc: usize) -> usize {
    let mut next_pc = pc;
    loop {
        let op = irep.code.get(next_pc).expect("cannot fetch op anymore");
        if op.pos == original_pc {
            break;
        }
        next_pc += 1;
    }
    next_pc
}

pub(crate) fn op_nop(_vm: &mut VM, _operand: &Fetched) -> Result<(), Error> {
    // NOOP
    Ok(())
}

pub(crate) fn op_loadi_n(vm: &mut VM, n: i32, operand: &Fetched) -> Result<(), Error> {
    let a = operand.as_b()? as usize;
    let val = RObject::integer(n as i64);
    vm.current_regs()[a].replace(Rc::new(val));
    Ok(())
}

pub(crate) fn op_loadl(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b) = operand.as_bb()?;
    let pool_val = vm.current_irep.pool[b as usize].clone();
    let val = match pool_val {
        RPool::Str(s) => Rc::new(RObject::string(s)),
        RPool::Int(i) => Rc::new(RObject::integer(i)),
        RPool::Float(f) => Rc::new(RObject::float(f)),
        RPool::Data(_) => {
            return Err(Error::Internal(
                "Binary data in pool not supported yet".to_string(),
            ));
        }
    };
    vm.current_regs()[a as usize].replace(val);
    Ok(())
}

pub(crate) fn op_loadi16(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b) = operand.as_bs()?;
    let val = RObject::integer(b as i64);
    vm.current_regs()[a as usize].replace(Rc::new(val));
    Ok(())
}

pub(crate) fn op_loadi32(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b, c) = operand.as_bss()?;
    let val = RObject::integer((b as i64) << 16 | c as i64);
    vm.current_regs()[a as usize].replace(Rc::new(val));
    Ok(())
}

pub(crate) fn op_loadi(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b) = operand.as_bb()?;
    let val = RObject::integer(b as i64);
    vm.current_regs()[a as usize].replace(Rc::new(val));
    Ok(())
}

pub(crate) fn op_loadineg(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b) = operand.as_bb()?;
    let val = RObject::integer(-(b as i64));
    vm.current_regs()[a as usize].replace(Rc::new(val));
    Ok(())
}

pub(crate) fn op_loadsym(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b) = operand.as_bb()?;
    let val = vm.current_irep.syms[b as usize].clone();
    vm.current_regs()[a as usize].replace(Rc::new(RObject::symbol(val)));
    Ok(())
}

pub(crate) fn op_loadnil(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let a = operand.as_b()? as usize;
    let val = RObject::nil();
    vm.current_regs()[a].replace(Rc::new(val));
    Ok(())
}

pub(crate) fn op_loadself(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let a = operand.as_b()? as usize;
    let val: Rc<RObject> = vm.getself()?;
    vm.current_regs()[a].replace(val);
    Ok(())
}

pub(crate) fn op_loadt(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let a = operand.as_b()? as usize;
    let val = RObject::boolean(true);
    vm.current_regs()[a].replace(Rc::new(val));
    Ok(())
}

pub(crate) fn op_loadf(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let a = operand.as_b()? as usize;
    let val = RObject::boolean(false);
    vm.current_regs()[a].replace(Rc::new(val));
    Ok(())
}

pub(crate) fn op_getgv(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b) = operand.as_bb()?;
    let val = vm.current_irep.syms[b as usize].clone();
    let val = vm
        .globals
        .get(&val.name)
        .ok_or_else(|| Error::internal(format!("global variable not found {}", val.name)))?
        .clone();
    vm.current_regs()[a as usize].replace(val);
    Ok(())
}

pub(crate) fn op_setgv(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b) = operand.as_bb()?;
    let val = vm.get_current_regs_cloned(a as usize)?;
    let sym = vm.current_irep.syms[b as usize].clone();
    vm.globals.insert(sym.name.clone(), val);
    Ok(())
}

pub(crate) fn op_getiv(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b) = operand.as_bb()?;
    let this = vm.getself()?;
    let key = vm.current_irep.syms[b as usize].name.clone();
    vm.current_regs()[a as usize].replace(this.get_ivar(&key));
    Ok(())
}

pub(crate) fn op_setiv(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b) = operand.as_bb()?;
    let this = vm.getself()?;
    let val = vm.get_current_regs_cloned(a as usize)?;
    let key = vm.current_irep.syms[b as usize].name.clone();
    this.set_ivar(&key, val.clone());
    Ok(())
}

pub(crate) fn op_getconst(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b) = operand.as_bb()?;
    let name = vm.current_irep.syms[b as usize].name.clone();
    let mut current = current_namespace(vm);

    // Walk namespace chain upwards until found or reach top-level
    while let Some(ns) = current.clone() {
        if let Some(val) = ns.consts.borrow().get(&name).cloned() {
            vm.current_regs()[a as usize].replace(val);
            return Ok(());
        }
        current = ns.parent.borrow().clone();
    }

    if let Some(val) = vm.consts.get(&name).cloned() {
        vm.current_regs()[a as usize].replace(val);
        return Ok(());
    }

    Err(Error::NameError(name))
}

pub(crate) fn op_setconst(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b) = operand.as_bb()?;
    let name = vm.current_irep.syms[b as usize].name.clone();
    let val = vm.get_current_regs_cloned(a as usize)?;
    vm.consts.insert(name, val);
    Ok(())
}

pub(crate) fn op_getmcnst(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b) = operand.as_bb()?;
    let recv = vm.get_current_regs_cloned(a as usize)?;
    let name = vm.current_irep.syms[b as usize].name.clone();
    let mut module = match &recv.value {
        RValue::Class(klass) => Some(klass.module.clone()),
        RValue::Module(module) => Some(module.clone()),
        _ => None,
    };

    while let Some(current) = module.clone() {
        if let Some(val) = current.consts.borrow().get(&name).cloned() {
            vm.current_regs()[a as usize].replace(val);
            return Ok(());
        }
        module = current.parent.borrow().clone();
    }

    Err(Error::NameError(name.clone()))
}

pub(crate) fn op_getupvar(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b, c) = operand.as_bbb()?;
    let n = c as usize;
    let mut environ = vm
        .upper
        .as_ref()
        .ok_or_else(|| Error::internal("op_getupvar expects upper env"))?;
    for _ in 0..n {
        environ = environ
            .upper
            .as_ref()
            .ok_or_else(|| Error::internal("op_getupvar failed to find upvar"))?;
    }
    let environ = environ.clone();
    let up_regs = &vm.regs[environ.current_regs_offset..];
    if !environ.expired() {
        if let Some(val) = up_regs[b as usize].as_ref().cloned() {
            vm.current_regs()[a as usize].replace(val);
        } else {
            return Err(Error::internal(format!("register {} is empty", b)));
        }
    } else {
        let captured = environ.captured.borrow();
        let val = &captured
            .as_ref()
            .ok_or_else(|| Error::internal("captured environment not found"))?[b as usize];
        let val = val.clone();
        vm.current_regs()[a as usize]
            .replace(val.ok_or_else(|| Error::internal("captured value not found"))?);
    }
    Ok(())
}

pub(crate) fn op_setupvar(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b, c) = operand.as_bbb()?;
    let n = c as usize;
    let mut environ = vm
        .upper
        .as_ref()
        .ok_or_else(|| Error::internal("op_getupvar expects upper env"))?;
    for _ in 0..n {
        environ = environ
            .upper
            .as_ref()
            .ok_or_else(|| Error::internal("op_getupvar failed to find upvar"))?;
    }
    let environ = environ.clone();
    let current_regs_offset = environ.current_regs_offset;

    let val = vm.get_current_regs_cloned(a as usize)?;
    if !environ.expired() {
        let up_regs = &mut vm.regs[current_regs_offset..];
        let target = &mut up_regs[b as usize];
        target.replace(val);
    } else {
        let mut captured = environ.captured.borrow_mut();
        let captured = captured
            .as_mut()
            .ok_or_else(|| Error::internal("captured environment not found"))?;
        let target = &mut captured[b as usize];
        target.replace(val);
    }
    Ok(())
}

pub(crate) fn op_getidx(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let a = operand.as_b()? as usize;
    let recv = vm.get_current_regs_cloned(a)?;
    let idx = vm.get_current_regs_cloned(a + 1)?;
    let args = vec![idx];
    // TODO: direct call of array_index for performance
    let val = mrb_funcall(vm, Some(recv), "[]", &args)?;
    vm.current_regs()[a].replace(val);
    Ok(())
}

pub(crate) fn op_setidx(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let a = operand.as_b()? as usize;
    let recv = vm.get_current_regs_cloned(a)?;
    let idx = vm.get_current_regs_cloned(a + 1)?;
    let val = vm.get_current_regs_cloned(a + 2)?;
    let args = vec![idx, val];
    mrb_funcall(vm, Some(recv), "[]=", &args)?;
    Ok(())
}

pub(crate) fn op_jmp(vm: &mut VM, operand: &Fetched, end_pos: usize) -> Result<(), Error> {
    let a = operand.as_s()?;
    let offset = a as i16;
    let next_pc = calcurate_pc(
        &vm.current_irep,
        0,
        (end_pos as isize + offset as isize) as usize,
    );
    vm.pc.set(next_pc);
    Ok(())
}

pub(crate) fn op_jmpif(vm: &mut VM, operand: &Fetched, end_pos: usize) -> Result<(), Error> {
    let (a, b) = operand.as_bs()?;
    let val = vm.get_current_regs_cloned(a as usize)?;
    if val.is_truthy() {
        let offset = b as i16;
        let next_pc = calcurate_pc(
            &vm.current_irep,
            0,
            (end_pos as isize + offset as isize) as usize,
        );
        vm.pc.set(next_pc);
    }
    Ok(())
}

pub(crate) fn op_jmpnot(vm: &mut VM, operand: &Fetched, end_pos: usize) -> Result<(), Error> {
    let (a, b) = operand.as_bs()?;
    let val = vm.get_current_regs_cloned(a as usize)?;
    if val.is_falsy() {
        let offset = b as i16;
        let next_pc = calcurate_pc(
            &vm.current_irep,
            0,
            (end_pos as isize + offset as isize) as usize,
        );
        vm.pc.set(next_pc);
    }
    Ok(())
}

pub(crate) fn op_jmpnil(vm: &mut VM, operand: &Fetched, end_pos: usize) -> Result<(), Error> {
    let (a, b) = operand.as_bs()?;
    let val = vm.get_current_regs_cloned(a as usize)?;
    if val.is_nil() {
        let offset = b as i16;
        let next_pc = calcurate_pc(
            &vm.current_irep,
            0,
            (end_pos as isize + offset as isize) as usize,
        );
        vm.pc.set(next_pc);
    }
    Ok(())
}

pub(crate) fn op_jmpuw(vm: &mut VM, operand: &Fetched, end_pos: usize) -> Result<(), Error> {
    if vm.current_irep.catch_target_pos.is_empty() {
        op_jmp(vm, operand, end_pos)
    } else {
        // TODO multiple catch targets... :(
        let target_pos = vm.current_irep.catch_target_pos[0];
        vm.pc.set(target_pos);

        consume_ensure_block(vm)?;
        op_jmp(vm, operand, end_pos)
    }
}

fn consume_ensure_block(vm: &mut VM) -> Result<(), Error> {
    loop {
        let pc = vm.pc.get();
        if vm.current_irep.code.len() <= pc {
            // reached end of the IREP
            return Err(Error::internal(
                "end of opcode reached while consuming ensure block",
            ));
        }
        let op = *vm
            .current_irep
            .code
            .get(pc)
            .ok_or_else(|| Error::internal("end of opcode reached"))?;
        let operand = op.operand;
        vm.pc.set(pc + 1);

        if matches!(op.code, OpCode::RAISEIF) {
            return Ok(());
        }

        #[cfg(feature = "mrubyedge-debug")]
        if let Ok(v) = env::var("MRUBYEDGE_DEBUG") {
            let level: i32 = v.parse().unwrap_or(1);
            if level >= 2 {
                vm.debug_dump_to_stdout(32);
            }
            eprintln!(
                "{:?}: {:?} (pos={} len={})",
                op.code, &operand, op.pos, op.len
            );
        }

        match consume_expr(vm, op.code, &operand, op.pos, op.len) {
            Ok(_) => {}
            Err(e) => {
                let exception = RException::from_error(vm, &e);
                vm.exception = Some(Rc::new(exception));
                continue;
            }
        }
    }
}

pub(crate) fn op_except(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let a = operand.as_b()?;
    let val = vm
        .exception
        .take()
        .map(|e| RObject::exception(e).to_refcount_assigned())
        .unwrap_or_else(|| RObject::nil().to_refcount_assigned());
    vm.current_regs()[a as usize].replace(val);
    Ok(())
}

pub(crate) fn op_rescue(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b) = operand.as_bb()?;
    let val = vm.get_current_regs_cloned(a as usize)?;
    let exc_klass = vm.take_current_regs(b as usize)?;
    match (&val.value, exc_klass.value.clone()) {
        (RValue::Exception(exc), RValue::Class(klass)) => {
            let etype = exc.error_type.borrow();
            let is_rescued = etype.is_a(vm, klass);
            let val = RObject::boolean(is_rescued);
            vm.current_regs()[b as usize].replace(val.to_refcount_assigned());
        }
        _ => unreachable!("rescue must be called on exception"),
    };
    Ok(())
}

pub(crate) fn op_raiseif(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let a = operand.as_b()?;
    let val = vm.current_regs()[a as usize].as_ref().cloned();
    if let Some(val) = val
        && let RValue::Exception(e) = &val.value
    {
        return Err(e.as_ref().error_type.borrow().clone());
    }
    Ok(())
}

pub(crate) fn op_move(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b) = operand.as_bb()?;
    let val = vm.get_current_regs_cloned(b as usize)?;

    let _old = vm.current_regs()[a as usize].replace(val);
    Ok(())
}

pub(crate) fn op_ssend(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b, c) = operand.as_bbb()?;
    do_op_send(vm, 0, None, a, b, c)
}

pub(crate) fn op_ssendb(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b, c) = operand.as_bbb()?;
    let n: usize = (c & 0x0f) as usize;
    let k: usize = (c >> 4) as usize;
    do_op_send(vm, 0, Some(a as usize + n + k * 2 + 1), a, b, c)
}

pub(crate) fn op_send(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b, c) = operand.as_bbb()?;
    do_op_send(vm, a as usize, None, a, b, c)
}

pub(crate) fn op_sendb(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b, c) = operand.as_bbb()?;
    let n: usize = (c & 0x0f) as usize;
    let k: usize = (c >> 4) as usize;
    do_op_send(vm, a as usize, Some(a as usize + n + k * 2 + 1), a, b, c)
}

pub(crate) fn do_op_send(
    vm: &mut VM,
    recv_index: usize,
    blk_index: Option<usize>,
    a: u8,
    b: u8,
    c: u8,
) -> Result<(), Error> {
    let mut n: usize = (c & 0x0f) as usize;
    let k: usize = (c >> 4) as usize;

    let method_id = vm.current_irep.syms[b as usize].clone();
    if &method_id.name == "__debug__vm_info" {
        // Special debug method to dump VM info
        vm.debug_dump_to_stdout(32);
        vm.current_regs()[a as usize].replace(Rc::new(RObject::nil()));
        return Ok(());
    }

    let block_index = a as usize + n + k * 2 + 1;

    let recv = if recv_index == 0 {
        vm.getself()?
    } else {
        vm.get_current_regs_cloned(recv_index)?
    };
    let mut args = (0..n)
        .map(|i| {
            vm.get_current_regs_cloned(a as usize + i + 1)
                .expect("args too short for required")
        })
        .collect::<Vec<_>>();

    let mut map = RHashMap::default();
    for i in 0..k {
        let key = vm
            .get_current_regs_cloned(a as usize + n + i * 2 + 1)?
            .intern()?;
        let val = vm
            .get_current_regs_cloned(a as usize + n + i * 2 + 2)?
            .clone();
        map.insert(key, val);
    }
    vm.kargs.borrow_mut().replace(map);

    if let Some(blk_index) = blk_index {
        let blk_val = vm.get_current_regs_cloned(blk_index)?;
        if matches!(blk_val.tt, RType::Symbol) {
            let proc_val = mrb_funcall(vm, Some(blk_val), "to_proc", &[])?;
            args.push(proc_val);
        } else {
            args.push(blk_val);
        }
    } else {
        // When no block is provided, do not push a nil placeholder
        vm.current_regs()[block_index].replace(Rc::new(RObject::nil()));
    }

    let klass = recv.get_class(vm);
    let klass = if klass.is_singleton {
        klass
    } else {
        recv.singleton_or_this_class(vm)
    };
    let (owner_module, method) = resolve_method(&klass, &method_id.name)
        .or_else(|| {
            unshift_method_name(vm, &mut args, &method_id, a as usize, n + k * 2 + 1);
            n += 1;
            resolve_method(&klass, "method_missing")
        })
        .ok_or_else(|| {
            Error::Internal(format!(
                "[BUG] method_missing not defined. {} for {}",
                method_id.name,
                klass.full_name()
            ))
        })?;

    let upper = vm.current_breadcrumb.take();
    let new_breadcrumb = Rc::new(Breadcrumb {
        upper,
        event: "do_op_send",
        caller: Some(method_id.name.clone()),
        return_reg: Some(a as usize),
    });
    vm.current_breadcrumb.replace(new_breadcrumb);

    vm.current_regs()[a as usize].replace(recv.clone());
    if !method.is_rb_func {
        kwarg_op_enter(vm, 0);

        let func = vm
            .get_fn(method.func.unwrap())
            .ok_or_else(|| Error::internal("function not found"))?;
        vm.current_regs_offset += a as usize;

        let res = func(vm, &args);

        kwarg_op_return(vm);

        vm.current_regs_offset -= a as usize;
        for i in (a as usize + 1)..block_index {
            vm.current_regs()[i].take();
        }

        match res {
            Ok(val) => {
                vm.current_regs()[a as usize].replace(val);
                let cur = vm
                    .current_breadcrumb
                    .take()
                    .expect("send should push breadcrumb");
                let upper = cur.upper.clone();
                vm.current_breadcrumb
                    .replace(upper.expect("should have upper breadcrumb"));
            }
            Err(e) => {
                vm.current_regs()[a as usize].replace(Rc::new(RObject::nil()));
                return Err(e);
            }
        }

        return Ok(());
    }

    push_callinfo(vm, method_id, n, Some(owner_module), a as usize);

    // Set has_block flag based on whether a block was provided
    if let Some(ci) = vm.current_callinfo.as_ref() {
        ci.has_block.set(blk_index.is_some());
    }

    vm.pc.set(0);
    vm.current_irep = method.irep.ok_or_else(|| Error::internal("empry irep"))?;
    vm.current_regs_offset += a as usize;
    Ok(())
}

fn unshift_method_name(
    vm: &mut VM,
    args: &mut Vec<Rc<RObject>>,
    method_id: &RSym,
    a: usize,
    total_args: usize,
) {
    let method_name = RObject::symbol(method_id.clone()).to_refcount_assigned();
    for i in (a + 1..=a + total_args).rev() {
        let val = vm.current_regs().get(i).and_then(|r| r.as_ref().cloned());
        val.as_ref().cloned().map(|v| mrb_call_inspect(vm, v));
        vm.current_regs()[i + 1].replace(val.unwrap_or_else(|| Rc::new(RObject::nil())));
    }
    args.insert(0, method_name.clone());
    vm.current_regs()[a + 1].replace(method_name);
}

fn kwarg_op_enter(vm: &mut VM, rest_pos: usize) {
    let kwrest_reg = Cell::new(rest_pos);
    let current_arg = if let Some(args) = vm.kargs.borrow_mut().take() {
        let upper = vm.current_kargs.borrow_mut().take();
        KArgs {
            args: RefCell::new(args),
            kwrest_reg,
            upper,
        }
    } else {
        KArgs {
            args: RefCell::new(RHashMap::default()),
            kwrest_reg,
            upper: None,
        }
    };
    vm.current_kargs.borrow_mut().replace(Rc::new(current_arg));
}

fn kwarg_op_return(vm: &mut VM) {
    let old_kargs = vm.current_kargs.borrow_mut().take();
    if let Some(upper) = old_kargs.as_ref().and_then(|kargs| kargs.upper.clone()) {
        vm.current_kargs.borrow_mut().replace(upper);
    }
}

pub(crate) fn op_call(vm: &mut VM, _operand: &Fetched) -> Result<(), Error> {
    let upper = vm.current_breadcrumb.take();
    let new_breadcrumb = Rc::new(Breadcrumb {
        upper,
        event: "op_call",
        caller: Some("<tailcall>".into()),
        return_reg: None,
    });
    vm.current_breadcrumb.replace(new_breadcrumb);
    push_callinfo(vm, "<tailcall>".into(), 0, None, 0);

    vm.pc.set(0);
    let proc = vm.current_regs()[0]
        .as_ref()
        .cloned()
        .ok_or_else(|| Error::internal("proc not found"))?;
    match &proc.value {
        RValue::Proc(proc) => {
            vm.current_irep = proc
                .irep
                .as_ref()
                .ok_or_else(|| Error::internal("empry irep"))?
                .clone();
        }
        _ => unreachable!("call must be called on proc"),
    }
    Ok(())
}

pub(crate) fn op_super(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b) = operand.as_bb()?;
    let callinfo = vm
        .current_callinfo
        .as_ref()
        .ok_or_else(|| Error::internal("no current callinfo"))?;
    let sym_id = callinfo.method_id.name.clone();
    let owner_module = callinfo
        .method_owner
        .clone()
        .ok_or_else(|| Error::RuntimeError("super called outside of method".to_string()))?;
    let recv = vm.getself()?;
    let args = (0..b)
        .map(|i| {
            vm.get_current_regs_cloned((a + i + 1) as usize)
                .expect("args too short for super")
        })
        .collect::<Vec<_>>();

    let klass = match &recv.value {
        RValue::Instance(ins) => ins.class.clone(),
        _ => recv.initialize_or_get_singleton_class(vm),
    };
    let (next_owner, method) =
        resolve_next_method(&klass, &sym_id, &owner_module).ok_or_else(|| {
            Error::NoMethodError(format!("{} for {}", sym_id.clone(), klass.full_name()))
        })?;
    if !method.is_rb_func {
        let func = vm.get_fn(method.func.unwrap()).ok_or_else(|| {
            Error::internal(format!("functon registerd but no entry found: {}", &sym_id))
        })?;
        let res = func(vm, &args);
        for i in (a as usize + 1)..(a as usize + b as usize + 1) {
            vm.current_regs()[i].take();
        }
        match res {
            Ok(val) => {
                vm.current_regs()[a as usize].replace(val);
            }
            Err(e) => {
                vm.current_regs()[a as usize].replace(Rc::new(RObject::nil()));
                return Err(e);
            }
        }
        return Ok(());
    }

    let upper = vm.current_breadcrumb.take();
    let new_breadcrumb = Rc::new(Breadcrumb {
        upper,
        event: "super",
        caller: Some(format!("super({})", sym_id)),
        return_reg: None,
    });
    vm.current_breadcrumb.replace(new_breadcrumb);

    vm.current_regs()[a as usize].replace(recv.clone());
    push_callinfo(
        vm,
        method.sym_id.clone().unwrap(),
        b as usize,
        Some(next_owner),
        a as usize,
    );

    vm.pc.set(0);
    vm.current_irep = method
        .irep
        .as_ref()
        .ok_or_else(|| Error::internal("empty irep"))?
        .clone();
    vm.current_regs_offset += a as usize;
    Ok(())
}

#[allow(dead_code)]
#[derive(Debug, Copy, Clone)]
pub(crate) struct EnterArgInfo {
    pub m1: u32,
    pub o: u32,
    pub r: u32,
    pub m2: u32,
    pub k: u32,
    pub d: u32,
    pub b: u32,
}

impl From<u32> for EnterArgInfo {
    fn from(val: u32) -> Self {
        EnterArgInfo {
            m1: (val & ENTER_M1_MASK) >> 18,
            o: (val & ENTER_O_MASK) >> 13,
            r: (val & ENTER_R_MASK) >> 12,
            m2: (val & ENTER_M2_MASK) >> 7,
            k: (val & ENTER_K_MASK) >> 2,
            d: (val & ENTER_D_MASK) >> 1,
            b: (val & ENTER_B_MASK),
        }
    }
}

pub(crate) fn op_enter(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let a = operand.as_w()?;
    let argc = vm.current_callinfo.as_ref().map_or(0, |ci| ci.n_args);
    let arg_info = EnterArgInfo::from(a);
    let m1_argc = arg_info.m1 as usize;
    for i in 0..m1_argc {
        match vm.current_regs()[i + 1].as_ref() {
            Some(_) => {}
            None => {
                return Err(Error::ArgumentError(format!(
                    "argument {} not passed",
                    i + 1
                )));
            }
        }
    }
    let optional_arg = arg_info.o as usize;
    if optional_arg > 0 {
        let m2_argc = arg_info.m2 as usize;
        let total_preset_args = argc.saturating_sub(m1_argc + m2_argc);
        for peek_pc in 0..total_preset_args {
            match vm.current_irep.code[vm.pc.get() + peek_pc].code {
                OpCode::JMP => {}
                _ => {
                    unreachable!("unexpected opcode while processing optional args")
                }
            }
        }
        vm.pc.set(vm.pc.get() + total_preset_args);
    }

    let splat_arg = arg_info.r as usize;
    if splat_arg == 1 {
        let total_args = argc;
        let passed_args = total_args.saturating_sub(m1_argc);
        let mut array = Vec::new();
        for i in 0..passed_args {
            if let Some(val) = vm.current_regs()[m1_argc + i + 1].take() {
                array.push(val);
            }
        }
        let splat = RObject::array(array);
        vm.current_regs()[m1_argc + splat_arg].replace(splat.to_refcount_assigned());
    }
    let kwrest_arg = arg_info.d as usize;
    let kwrest_pos = if kwrest_arg == 1 {
        m1_argc + splat_arg + kwrest_arg
    } else {
        0
    };
    kwarg_op_enter(vm, kwrest_pos);
    if kwrest_arg == 1 {
        let mut map = RHashMap::default();
        for (k, v) in vm
            .get_kwargs()
            .ok_or_else(|| Error::RuntimeError("kwargs not defined".to_string()))?
            .iter()
        {
            let k = RObject::symbol(RSym::new(k.clone())).to_refcount_assigned();
            map.insert(k.as_hash_key()?, (k, v.clone()));
        }

        let kwrest = RObject::hash(map);
        vm.current_regs()[kwrest_pos].replace(kwrest.to_refcount_assigned());
    }

    Ok(())
}

pub(crate) fn op_key_p(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b) = operand.as_bb()?;
    let key = vm.current_irep.syms[b as usize].clone();
    let key_robj = RObject::symbol(key.clone()).to_refcount_assigned();

    let (val, kwrest_pos) = {
        let kargs = vm.current_kargs.borrow();
        let kargs = kargs
            .as_ref()
            .ok_or_else(|| Error::internal("no kargs found"))?;

        let kwrest_pos = kargs.kwrest_reg.get();

        (
            RObject::boolean(kargs.args.borrow().contains_key(&key)),
            kwrest_pos,
        )
    };

    if kwrest_pos != 0 {
        let kwrest = vm.get_current_regs_cloned(kwrest_pos)?;
        mrb_hash_delete(kwrest, key_robj)?;
    }

    vm.current_regs()[a as usize].replace(val.to_refcount_assigned());
    Ok(())
}

pub(crate) fn op_keyend(vm: &mut VM, _operand: &Fetched) -> Result<(), Error> {
    match vm.current_kargs.borrow().as_deref() {
        Some(kargs) => {
            let is_empty = kargs.args.borrow().is_empty();
            if is_empty {
                Ok(())
            } else {
                Err(Error::ArgumentError(
                    "unexpected keyword arguments".to_string(),
                ))
            }
        }
        None => Err(Error::internal("no kargs found")),
    }
}

pub(crate) fn op_karg(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b) = operand.as_bb()?;
    let val = {
        let key = vm.current_irep.syms[b as usize].clone();
        let kargs = vm.current_kargs.borrow();
        let kargs = kargs
            .as_ref()
            .ok_or_else(|| Error::internal("no kargs found"))?;

        let mut args = kargs.args.borrow_mut();
        args.remove(&key).ok_or_else(|| {
            Error::ArgumentError(format!("keyword argument '{}' not found", key.name))
        })?
    };
    vm.current_regs()[a as usize].replace(val);
    Ok(())
}

pub(crate) fn op_return(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let a = operand.as_b()? as usize;
    let old_irep = vm.current_irep.clone();
    let nregs = old_irep.nregs;
    // let no_return = vm.current_callinfo.is_some();

    let regs0_cloned: Vec<_> = vm.current_regs()[0..nregs].to_vec();
    if let Some(environ) = vm.cur_env.get(&vm.current_irep.__id) {
        environ.capture_no_clone(regs0_cloned);
        environ.as_ref().expire();
        vm.has_env_ref.remove(&vm.current_irep.__id);
    }

    let regs0 = vm.current_regs();
    if let Some(regs_a) = regs0[a].clone() {
        regs0[0].replace(regs_a);
    }
    // TODO: inspect if this is needed
    // if nregs > 0 && no_return {
    //     regs0[1..=nregs].iter_mut().for_each(|reg| {
    //         reg.take();
    //     });
    // }

    let ci = vm.current_callinfo.take();
    if ci.is_none() {
        let cur = vm.current_breadcrumb.take().expect("not found breadcrumb");
        if let Some(upper) = &cur.as_ref().upper {
            vm.current_breadcrumb.replace(upper.clone());
        }
        // When called from mrb_funcall, return error if there's an exception

        if let Some(e) = &vm.exception {
            return Err(e.error_type.borrow().clone());
        }
        // For normal completion, set preemption flag and terminate
        vm.flag_preemption.set(true);
        return Ok(());
    }

    let ci = ci.unwrap();
    if let Some(prev) = &ci.prev {
        vm.current_callinfo.replace(prev.clone());
    }
    vm.current_irep = ci.pc_irep.clone();
    vm.pc.set(ci.pc);
    vm.current_regs_offset = ci.current_regs_offset;
    vm.target_class = ci.target_class.clone();
    if vm.current_regs()[0].is_none() {
        unreachable!("debug");
    }

    kwarg_op_return(vm);

    let cur = vm.current_breadcrumb.take().expect("not found breadcrumb");
    if let Some(upper) = &cur.as_ref().upper {
        vm.current_breadcrumb.replace(upper.clone());
    }
    Ok(())
}

pub(crate) fn op_return_blk(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let a = operand.as_b()? as usize;
    let val = vm.get_current_regs_cloned(a)?;
    let target_irep_id = vm
        .get_outermost_env()
        .expect("not found outermost env")
        .__irep_id;

    Err(Error::BlockReturn(target_irep_id, val))
}

pub(crate) fn op_break(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let a = operand.as_b()? as usize;
    let val = vm.get_current_regs_cloned(a)?;

    Err(Error::Break(val))
}

pub(crate) fn op_blkpush(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, _s) = operand.as_bs()?;
    let n = vm.current_callinfo.as_ref().unwrap().n_args;
    let block = vm.get_current_regs_cloned(n + 1)?;
    vm.current_regs()[a as usize].replace(block);
    Ok(())
}

pub(crate) fn op_add(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let a = operand.as_b()? as usize;
    let b = a + 1;
    let val1 = vm.take_current_regs(a)?;
    let val2 = vm.get_current_regs_cloned(b)?;
    let result = match (&val1.value, &val2.value) {
        (RValue::Integer(n1), RValue::Integer(n2)) => Rc::new(RObject::integer(n1 + n2)),
        (RValue::Float(n1), RValue::Float(n2)) => Rc::new(RObject::float(n1 + n2)),
        (RValue::Integer(n1), RValue::Float(n2)) => Rc::new(RObject::float(*n1 as f64 + n2)),
        (RValue::Float(n1), RValue::Integer(n2)) => Rc::new(RObject::float(n1 + *n2 as f64)),
        (RValue::String(n1, _), RValue::String(n2, _)) => {
            let mut n1 = n1.borrow_mut();
            let n2 = n2.borrow();
            for c in n2.iter() {
                n1.push(*c);
            }
            val1.clone()
        }
        _ => {
            let args = vec![val2.clone()];
            mrb_funcall(vm, Some(val1.clone()), "+", &args)?
        }
    };
    vm.current_regs()[a].replace(result);
    Ok(())
}

pub(crate) fn op_addi(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b) = operand.as_bb()?;
    let val1 = vm.take_current_regs(a as usize)?;
    let val2 = b as i64;
    let result = match &val1.value {
        RValue::Integer(n1) => RObject::integer(*n1 + val2),
        RValue::Float(n1) => RObject::float(n1 + val2 as f64),
        _ => {
            unreachable!("addi supports only integer and float")
        }
    };
    vm.current_regs()[a as usize].replace(result.to_refcount_assigned());
    Ok(())
}

pub(crate) fn op_sub(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let a = operand.as_b()? as usize;
    let b = a + 1;
    let val1 = vm.take_current_regs(a)?;
    let val2 = vm.get_current_regs_cloned(b)?;
    let result = match (&val1.value, &val2.value) {
        (RValue::Integer(n1), RValue::Integer(n2)) => {
            RObject::integer(n1 - n2).to_refcount_assigned()
        }
        (RValue::Float(n1), RValue::Float(n2)) => RObject::float(n1 - n2).to_refcount_assigned(),
        (RValue::Integer(n1), RValue::Float(n2)) => {
            RObject::float(*n1 as f64 - n2).to_refcount_assigned()
        }
        (RValue::Float(n1), RValue::Integer(n2)) => {
            RObject::float(n1 - *n2 as f64).to_refcount_assigned()
        }
        _ => {
            let args = vec![val2.clone()];
            mrb_funcall(vm, Some(val1.clone()), "-", &args)?
        }
    };
    vm.current_regs()[a].replace(result);
    Ok(())
}

pub(crate) fn op_subi(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b) = operand.as_bb()?;
    let val1 = vm.take_current_regs(a as usize)?;
    let val2 = b as i64;
    let result = match &val1.value {
        RValue::Integer(n1) => RObject::integer(*n1 - val2),
        _ => {
            unreachable!("subi supports only integer")
        }
    };
    vm.current_regs()[a as usize].replace(result.to_refcount_assigned());
    Ok(())
}

pub(crate) fn op_mul(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let a = operand.as_b()? as usize;
    let b = a + 1;
    let val1 = vm.take_current_regs(a)?;
    let val2 = vm.get_current_regs_cloned(b)?;
    let result = match (&val1.value, &val2.value) {
        (RValue::Integer(n1), RValue::Integer(n2)) => {
            RObject::integer(n1 * n2).to_refcount_assigned()
        }
        (RValue::Float(n1), RValue::Float(n2)) => RObject::float(n1 * n2).to_refcount_assigned(),
        (RValue::Integer(n1), RValue::Float(n2)) => {
            RObject::float(*n1 as f64 * n2).to_refcount_assigned()
        }
        (RValue::Float(n1), RValue::Integer(n2)) => {
            RObject::float(n1 * *n2 as f64).to_refcount_assigned()
        }
        _ => mrb_funcall(vm, Some(val1), "*", &[val2])?,
    };
    vm.current_regs()[a].replace(result);
    Ok(())
}

pub(crate) fn op_div(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let a = operand.as_b()? as usize;
    let b = a + 1;
    let val1 = vm.take_current_regs(a)?;
    let val2 = vm.get_current_regs_cloned(b)?;
    let result = match (&val1.value, &val2.value) {
        (RValue::Integer(n1), RValue::Integer(n2)) => {
            RObject::integer(n1 / n2).to_refcount_assigned()
        }
        (RValue::Float(n1), RValue::Float(n2)) => RObject::float(n1 / n2).to_refcount_assigned(),
        (RValue::Integer(n1), RValue::Float(n2)) => {
            RObject::float(*n1 as f64 / n2).to_refcount_assigned()
        }
        (RValue::Float(n1), RValue::Integer(n2)) => {
            RObject::float(n1 / *n2 as f64).to_refcount_assigned()
        }
        _ => mrb_funcall(vm, Some(val1), "/", &[val2])?,
    };
    vm.current_regs()[a].replace(result);
    Ok(())
}

pub(crate) fn op_lt(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let a = operand.as_b()? as usize;
    let b = a + 1;
    let val1 = vm.take_current_regs(a)?;
    let val2 = vm.get_current_regs_cloned(b)?;
    let result = match (&val1.value, &val2.value) {
        (RValue::Integer(n1), RValue::Integer(n2)) => RObject::boolean(n1 < n2),
        (RValue::Float(n1), RValue::Float(n2)) => RObject::boolean(n1 < n2),
        (RValue::Integer(n1), RValue::Float(n2)) => RObject::boolean((*n1 as f64) < *n2),
        (RValue::Float(n1), RValue::Integer(n2)) => RObject::boolean(*n1 < (*n2 as f64)),
        _ => {
            unreachable!("lt supports only numeric")
        }
    };
    vm.current_regs()[a].replace(Rc::new(result));
    Ok(())
}

pub(crate) fn op_le(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let a = operand.as_b()? as usize;
    let b = a + 1;
    let val1 = vm.take_current_regs(a)?;
    let val2 = vm.get_current_regs_cloned(b)?;
    let result = match (&val1.value, &val2.value) {
        (RValue::Integer(n1), RValue::Integer(n2)) => RObject::boolean(n1 <= n2),
        (RValue::Float(n1), RValue::Float(n2)) => RObject::boolean(n1 <= n2),
        (RValue::Integer(n1), RValue::Float(n2)) => RObject::boolean((*n1 as f64) <= *n2),
        (RValue::Float(n1), RValue::Integer(n2)) => RObject::boolean(*n1 <= (*n2 as f64)),
        _ => {
            unreachable!("le supports only numeric")
        }
    };
    vm.current_regs()[a].replace(Rc::new(result));
    Ok(())
}

pub(crate) fn op_eq(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let a = operand.as_b()? as usize;
    let b = a + 1;
    let lhs = vm.take_current_regs(a)?;
    let rhs = vm.get_current_regs_cloned(b)?;
    let result = mrb_object_is_equal(vm, lhs, rhs);
    vm.current_regs()[a].replace(result);
    Ok(())
}

pub(crate) fn op_gt(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let a = operand.as_b()? as usize;
    let b = a + 1;
    let val1 = vm.take_current_regs(a)?;
    let val2 = vm.get_current_regs_cloned(b)?;
    let result = match (&val1.value, &val2.value) {
        (RValue::Integer(n1), RValue::Integer(n2)) => RObject::boolean(n1 > n2),
        (RValue::Float(n1), RValue::Float(n2)) => RObject::boolean(n1 > n2),
        (RValue::Integer(n1), RValue::Float(n2)) => RObject::boolean((*n1 as f64) > *n2),
        (RValue::Float(n1), RValue::Integer(n2)) => RObject::boolean(*n1 > (*n2 as f64)),
        _ => {
            unreachable!("gt supports only numeric")
        }
    };
    vm.current_regs()[a].replace(Rc::new(result));
    Ok(())
}

pub(crate) fn op_ge(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let a = operand.as_b()? as usize;
    let b = a + 1;
    let val1 = vm.take_current_regs(a)?;
    let val2 = vm.get_current_regs_cloned(b)?;
    let result = match (&val1.value, &val2.value) {
        (RValue::Integer(n1), RValue::Integer(n2)) => RObject::boolean(n1 >= n2),
        (RValue::Float(n1), RValue::Float(n2)) => RObject::boolean(n1 >= n2),
        (RValue::Integer(n1), RValue::Float(n2)) => RObject::boolean((*n1 as f64) >= *n2),
        (RValue::Float(n1), RValue::Integer(n2)) => RObject::boolean(*n1 >= (*n2 as f64)),
        _ => {
            unreachable!("ge supports only numeric")
        }
    };
    vm.current_regs()[a].replace(Rc::new(result));
    Ok(())
}

pub(crate) fn op_array(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b) = operand.as_bb()?;
    do_op_array(vm, a as usize, a as usize, b as usize)
}

pub(crate) fn op_array2(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b, c) = operand.as_bbb()?;
    do_op_array(vm, a as usize, b as usize, c as usize)
}

fn do_op_array(vm: &mut VM, this: usize, start: usize, n: usize) -> Result<(), Error> {
    let mut ary = Vec::with_capacity(n);
    for i in 0..n {
        if this == start && i == 0 {
            ary.push(vm.take_current_regs(start)?);
        } else {
            ary.push(vm.get_current_regs_cloned(start + i)?);
        }
    }
    let val = RObject::array(ary);
    vm.current_regs()[this].replace(val.to_refcount_assigned());
    Ok(())
}

pub(crate) fn op_arycat(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let a = operand.as_b()? as usize;
    let b = a + 1;
    let val1 = vm.get_current_regs_cloned(a)?;
    let val2 = vm.take_current_regs(b)?;
    match (&val1.value, &val2.value) {
        (RValue::Array(ary1), RValue::Array(ary2)) => {
            let mut ary1 = ary1.borrow_mut();
            let ary2 = ary2.borrow();
            for item in ary2.iter() {
                ary1.push(item.clone());
            }
        }
        (RValue::Nil, RValue::Array(ary2)) => {
            let mut ary1 = Vec::new();
            let ary2 = ary2.borrow();
            for item in ary2.iter() {
                ary1.push(item.clone());
            }
            let val = RObject::array(ary1);
            vm.current_regs()[a].replace(val.to_refcount_assigned());
        }
        _ => {
            unreachable!("arycat supports only array")
        }
    };
    Ok(())
}

pub(crate) fn op_aref(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b, c) = operand.as_bbb()?;
    let array = vm.get_current_regs_cloned(b as usize)?;
    let index = c as usize;
    match &array.value {
        RValue::Array(ary) => {
            let ary = ary.borrow();
            let val = ary
                .get(index)
                .cloned()
                .unwrap_or_else(|| Rc::new(RObject::nil()));
            vm.current_regs()[a as usize].replace(val);
        }
        _ => {
            unreachable!("aref supports only array")
        }
    };
    Ok(())
}

pub(crate) fn op_apost(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b, c) = operand.as_bbb()?;
    if c != 0 {
        return Err(Error::internal(
            "apost with 3 operands is not supported yet",
        ));
    }
    let array = vm.get_current_regs_cloned(a as usize)?;
    let n = b as usize;
    match &array.value {
        RValue::Array(ary) => {
            let mut dest = Vec::new();
            let ary = ary.borrow();
            for i in n..ary.len() {
                dest.push(ary[i].clone());
            }
            let newval = RObject::array(dest).to_refcount_assigned();
            vm.current_regs()[a as usize].replace(newval);
        }
        _ => {
            unreachable!("apost supports only array")
        }
    };
    Ok(())
}

pub(crate) fn op_symbol(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b) = operand.as_bb()?;
    let symstr = vm.current_irep.pool[b as usize].as_str().to_string();
    let sym = RSym::new(symstr);
    let val = RObject::symbol(sym);
    vm.current_regs()[a as usize].replace(val.to_refcount_assigned());
    Ok(())
}

pub(crate) fn op_string(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b) = operand.as_bb()?;
    let str = vm.current_irep.pool[b as usize].as_str().to_string();
    let val = RObject::string(str);
    vm.current_regs()[a as usize].replace(val.to_refcount_assigned());
    Ok(())
}

pub(crate) fn op_strcat(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let a = operand.as_b()? as usize;
    let b = a + 1;
    let val1 = vm.get_current_regs_cloned(a)?;
    let val2 = vm.get_current_regs_cloned(b)?;
    match (&val1.value, &val2.value) {
        (RValue::String(s1, _), RValue::String(s2, _)) => {
            let mut s1 = s1.borrow_mut();
            let s2 = s2.borrow();
            for c in s2.iter() {
                s1.push(*c);
            }
        }
        (RValue::String(s1, _), RValue::Integer(s2)) => {
            let mut s1 = s1.borrow_mut();
            let s2 = s2.to_string();
            for c in s2.as_bytes() {
                s1.push(*c);
            }
        }
        (RValue::String(s1, _), _) => {
            let mut s1 = s1.borrow_mut();
            let s2 = mrb_funcall(vm, Some(val2.clone()), "to_s", &[])?;
            let s2 = match &s2.value {
                RValue::String(s, _) => s.borrow(),
                _ => unreachable!("to_s must return string"),
            };
            for c in s2.to_vec().iter() {
                s1.push(*c);
            }
        }
        _ => {
            unreachable!("strcat supports only string")
        }
    };
    Ok(())
}

pub(crate) fn op_hash(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b) = operand.as_bb()?;
    let a = a as usize;
    let b = b as usize;
    let mut hash = RHashMap::default();
    for i in 0..b {
        let key = vm.get_current_regs_cloned(a + i * 2)?;
        let val = vm.get_current_regs_cloned(a + i * 2 + 1)?;
        hash.insert(key.as_hash_key()?, (key, val));
    }
    let val = RObject::hash(hash);
    vm.current_regs()[a].replace(Rc::new(val));
    Ok(())
}

pub(crate) fn op_lambda(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b) = operand.as_bb()?;
    let irep = Some(vm.current_irep.reps[b as usize].clone());
    let environ = ENV {
        __irep_id: vm.current_irep.__id,
        upper: vm.upper.clone(),
        current_regs_offset: vm.current_regs_offset,
        is_expired: Cell::new(false),
        captured: RefCell::new(None),
    };
    //let nregs = vm.current_irep.nregs;
    //environ.capture(&vm.current_regs()[0..nregs]);
    let environ = Rc::new(environ);
    vm.cur_env.insert(vm.current_irep.__id, environ.clone());
    vm.has_env_ref.insert(vm.current_irep.__id, true);

    let val = RObject {
        tt: RType::Proc,
        value: RValue::Proc(RProc {
            irep,
            is_rb_func: true,
            is_fnblock: false,
            sym_id: Some("<lambda>".into()),
            next: None,
            func: None,
            environ: Some(environ),
            block_self: Some(vm.getself()?),
        }),
        object_id: u64::MAX.into(),
        singleton_class: RefCell::new(None),
        ivar: RefCell::new(RHashMap::default()),
    };
    vm.current_regs()[a as usize].replace(val.to_refcount_assigned());
    Ok(())
}

pub(crate) fn op_block(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b) = operand.as_bb()?;
    let irep = Some(vm.current_irep.reps[b as usize].clone());
    let environ = ENV {
        __irep_id: vm.current_irep.__id,
        upper: vm.upper.clone(),
        current_regs_offset: vm.current_regs_offset,
        is_expired: Cell::new(false),
        captured: RefCell::new(None),
    };
    let environ = Rc::new(environ);
    vm.cur_env.insert(vm.current_irep.__id, environ.clone());
    vm.has_env_ref.insert(vm.current_irep.__id, true);

    let val = RObject {
        tt: RType::Proc,
        value: RValue::Proc(RProc {
            irep,
            is_rb_func: true,
            is_fnblock: false,
            sym_id: Some("<block>".into()),
            next: None,
            func: None,
            environ: Some(environ),
            block_self: Some(vm.getself()?),
        }),
        object_id: u64::MAX.into(),
        singleton_class: RefCell::new(None),
        ivar: RefCell::new(RHashMap::default()),
    };
    vm.current_regs()[a as usize].replace(val.to_refcount_assigned());
    Ok(())
}

pub(crate) fn op_method(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b) = operand.as_bb()?;
    let irep = Some(vm.current_irep.reps[b as usize].clone());
    let val = RObject {
        tt: super::value::RType::Proc,
        value: super::value::RValue::Proc(super::value::RProc {
            irep,
            is_rb_func: true,
            is_fnblock: false,
            sym_id: None,
            next: None,
            func: None,
            environ: None,
            block_self: None,
        }),
        object_id: u64::MAX.into(),
        singleton_class: RefCell::new(None),
        ivar: RefCell::new(RHashMap::default()),
    };
    vm.current_regs()[a as usize].replace(val.to_refcount_assigned());
    Ok(())
}

pub(crate) fn op_range_inc(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let a = operand.as_b()?;
    do_op_range(vm, a as usize, a as usize + 1, false)
}

pub(crate) fn op_range_exc(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let a = operand.as_b()?;
    do_op_range(vm, a as usize, a as usize + 1, true)
}

fn do_op_range(vm: &mut VM, a: usize, b: usize, exclusive: bool) -> Result<(), Error> {
    let val1 = vm.get_current_regs_cloned(a)?;
    let val2 = vm.get_current_regs_cloned(b)?;
    let val = RObject::range(val1, val2, exclusive);
    vm.current_regs()[a].replace(val.to_refcount_assigned());
    Ok(())
}

pub(crate) fn op_oclass(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let a = operand.as_b()? as usize;
    let val = RObject::class(vm.object_class.clone(), vm);
    vm.current_regs()[a].replace(val);
    Ok(())
}

fn current_namespace(vm: &mut VM) -> Option<Rc<RModule>> {
    match vm.current_regs()[0].as_ref() {
        Some(obj) => match &obj.value {
            RValue::Class(klass) => Some(klass.module.clone()),
            RValue::Module(module) => Some(module.clone()),
            _ => None,
        },
        None => None,
    }
}

pub(crate) fn op_class(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b) = operand.as_bb()?;
    let superclass = vm.current_regs()[a as usize + 1].as_ref().cloned();
    let name = vm.current_irep.syms[b as usize].clone();
    let superclass = match superclass {
        Some(superclass) => {
            if let RValue::Class(klass) = &superclass.value {
                klass.clone()
            } else {
                vm.object_class.clone()
            }
        }
        None => vm.object_class.clone(),
    };
    let parent_module = current_namespace(vm);
    let name = name.name;
    let klass = vm.define_class(&name, Some(superclass), parent_module.clone());

    // register constant under parent namespace (if any) or top-level
    let class_value = RObject::class(klass.clone(), vm);
    class_value.initialize_or_get_singleton_class_for_class(vm);
    if let Some(parent) = parent_module {
        parent
            .consts
            .borrow_mut()
            .insert(name.clone(), class_value.clone());
    } else {
        vm.consts.insert(name.clone(), class_value.clone());
    }

    vm.current_regs()[a as usize].replace(class_value);
    Ok(())
}

pub(crate) fn op_module(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b) = operand.as_bb()?;
    let name = vm.current_irep.syms[b as usize].clone();
    let name = name.name;
    let parent_module = current_namespace(vm);
    let module = vm.define_module(&name, parent_module.clone());

    let module_value = RObject::module(module.clone()).to_refcount_assigned();
    if let Some(parent) = parent_module {
        parent
            .consts
            .borrow_mut()
            .insert(name.clone(), module_value);
    } else {
        vm.consts.insert(name.clone(), module_value);
    }

    vm.current_regs()[a as usize].replace(Rc::new(module.into()));
    Ok(())
}

pub(crate) fn op_exec(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b) = operand.as_bb()?;
    let recv = vm.get_current_regs_cloned(a as usize)?;

    let upper = vm.current_breadcrumb.take();
    let new_breadcrumb = Rc::new(Breadcrumb {
        upper,
        event: "exec",
        caller: Some("<exec>".into()),
        return_reg: None,
    });
    vm.current_breadcrumb.replace(new_breadcrumb);
    push_callinfo(vm, "<exec>".into(), 0, None, a as usize);

    vm.pc.set(0);
    let irep = vm.current_irep.reps[b as usize].clone();
    vm.current_irep = irep;
    vm.current_regs_offset += a as usize;

    // If recv is a Class or Module, set target_class accordingly
    vm.target_class = match &recv.value {
        RValue::Class(klass) => TargetContext::Class(klass.clone()),
        RValue::Module(module) => TargetContext::Module(module.clone()),
        _ => TargetContext::Class(recv.get_class(vm)),
    };
    Ok(())
}

pub(crate) fn op_def(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b) = operand.as_bb()?;
    let target = vm.get_current_regs_cloned(a as usize)?;
    let method = vm.get_current_regs_cloned(a as usize + 1)?;
    let sym = vm.current_irep.syms[b as usize].clone();

    let method_ref = method.as_ref();

    // First, extract and prepare the method from the Proc
    let method = match &method_ref.value {
        RValue::Proc(proc) => {
            let mut method = proc.clone();
            method.environ = None; // method cannot trace the upper environment
            method.sym_id = Some(sym.clone());
            Ok(method)
        }
        _ => Err(Error::ArgumentError(
            "def operand 2 must be Proc (method)".to_string(),
        )),
    }?;

    // Then, define it on the receiver
    let target_ref = target.as_ref();
    match &target_ref.value {
        RValue::Class(klass) => {
            let mut procs = klass.procs.borrow_mut();
            procs.insert(sym.name.clone(), method);
        }
        RValue::Module(module) => {
            let mut procs = module.procs.borrow_mut();
            procs.insert(sym.name.clone(), method);
        }
        _ => {
            let robject = target.clone();
            let current_class = robject.get_class(vm);
            let sclass = if current_class.is_singleton {
                current_class
            } else {
                robject.initialize_or_get_singleton_class(vm)
            };
            let mut procs = sclass.procs.borrow_mut();
            procs.insert(sym.name.clone(), method);
        }
    }
    vm.current_regs()[a as usize].replace(RObject::symbol(sym).to_refcount_assigned());
    Ok(())
}

pub(crate) fn op_alias(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let (a, b) = operand.as_bb()?;
    let new_name = vm.current_irep.syms[a as usize].clone();
    let old_name = vm.current_irep.syms[b as usize].clone();

    let owner = vm.target_class.clone();
    let (owner_module, method) = match &owner {
        TargetContext::Class(klass) => {
            let (owner_module, method) = resolve_method(klass, &old_name.name)
                .ok_or_else(|| Error::NoMethodError(old_name.name.clone()))?;
            (owner_module, method)
        }
        TargetContext::Module(module) => {
            let method = module
                .procs
                .borrow()
                .get(&old_name.name)
                .cloned()
                .ok_or_else(|| Error::NoMethodError(old_name.name.clone()))?;
            (module.clone(), method)
        }
    };

    let mut new_method = method.clone();
    new_method.sym_id = Some(new_name.clone());

    let mut procs = owner_module.procs.borrow_mut();
    procs.insert(new_name.name.clone(), new_method);

    Ok(())
}

pub(crate) fn op_undef(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let a = operand.as_b()?;
    let sym = vm.current_irep.syms[a as usize].clone();

    let owner = vm.target_class.clone();
    match &owner {
        TargetContext::Class(klass) => {
            let mut procs = klass.procs.borrow_mut();
            procs.remove(&sym.name);
        }
        TargetContext::Module(module) => {
            let mut procs = module.procs.borrow_mut();
            procs.remove(&sym.name);
        }
    };
    Ok(())
}

pub(crate) fn op_sclass(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let a = operand.as_b()? as usize;
    let val = vm.current_regs()[a]
        .take()
        .expect("SCLASS: operand too short");
    let singleton_class = match val.tt {
        RType::Class | RType::Module => val.initialize_or_get_singleton_class_for_class(vm),
        _ => val.initialize_or_get_singleton_class(vm),
    };
    let robj = RObject::class(singleton_class.clone(), vm);
    vm.current_regs()[a].replace(robj);
    Ok(())
}

pub(crate) fn op_tclass(vm: &mut VM, operand: &Fetched) -> Result<(), Error> {
    let a = operand.as_b()? as usize;
    let val: Rc<RObject> = match &vm.target_class {
        TargetContext::Class(klass) => RObject::class(klass.clone(), vm),
        TargetContext::Module(module) => Rc::new(module.clone().into()),
    };
    vm.current_regs()[a].replace(val);
    Ok(())
}

pub(crate) fn op_stop(vm: &mut VM, _operand: &Fetched) -> Result<(), Error> {
    vm.flag_preemption.set(true);
    Ok(())
}
