//! Dalvik opcode definitions and operand formats.
#![allow(missing_docs, reason = "internal")]
/// Dalvik opcode. Covers DEX versions 035 through 041.
///
/// Sourced from AOSP `dalvik/opcode-gen/bytecode.txt`.
/// Full 220-variant enum will be generated from that file in a later milestone.
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
#[repr(u8)]
#[allow(non_camel_case_types, reason = "DEX opcode names (e.g., MoveResult, ConstString) follow the Dalvik spec's camelCase convention which Rust's lint rejects.")]
#[non_exhaustive]
pub enum Opcode {
    Nop = 0x00,
    Move = 0x01,
    MoveFrom16 = 0x02,
    Move16 = 0x03,
    MoveWide = 0x04,
    MoveWideFrom16 = 0x05,
    MoveWide16 = 0x06,
    MoveObject = 0x07,
    MoveObjectFrom16 = 0x08,
    MoveObject16 = 0x09,
    MoveResult = 0x0A,
    MoveResultWide = 0x0B,
    MoveResultObject = 0x0C,
    MoveException = 0x0D,
    ReturnVoid = 0x0E,
    Return = 0x0F,
    ReturnWide = 0x10,
    ReturnObject = 0x11,
    Const4 = 0x12,
    Const16 = 0x13,
    Const = 0x14,
    ConstHigh16 = 0x15,
    ConstWide16 = 0x16,
    ConstWide32 = 0x17,
    ConstWide = 0x18,
    ConstWideHigh16 = 0x19,
    ConstString = 0x1A,
    ConstStringJumbo = 0x1B,
    ConstClass = 0x1C,
    MonitorEnter = 0x1D,
    MonitorExit = 0x1E,
    CheckCast = 0x1F,
    InstanceOf = 0x20,
    ArrayLength = 0x21,
    NewInstance = 0x22,
    NewArray = 0x23,
    FilledNewArray = 0x24,
    FilledNewArrayRange = 0x25,
    FillArrayData = 0x26,
    Throw = 0x27,
    Goto = 0x28,
    Goto16 = 0x29,
    Goto32 = 0x2A,
    PackedSwitch = 0x2B,
    SparseSwitch = 0x2C,
    CmplFloat = 0x2D,
    CmpgFloat = 0x2E,
    CmplDouble = 0x2F,
    CmpgDouble = 0x30,
    CmpLong = 0x31,
    IfEq = 0x32,
    IfNe = 0x33,
    IfLt = 0x34,
    IfGe = 0x35,
    IfGt = 0x36,
    IfLe = 0x37,
    IfEqz = 0x38,
    IfNez = 0x39,
    IfLtz = 0x3A,
    IfGez = 0x3B,
    IfGtz = 0x3C,
    IfLez = 0x3D,
    Aget = 0x44,
    AgetWide = 0x45,
    AgetObject = 0x46,
    AgetBoolean = 0x47,
    AgetByte = 0x48,
    AgetChar = 0x49,
    AgetShort = 0x4A,
    Aput = 0x4B,
    AputWide = 0x4C,
    AputObject = 0x4D,
    AputBoolean = 0x4E,
    AputByte = 0x4F,
    AputChar = 0x50,
    AputShort = 0x51,
    Iget = 0x52,
    IgetWide = 0x53,
    IgetObject = 0x54,
    IgetBoolean = 0x55,
    IgetByte = 0x56,
    IgetChar = 0x57,
    IgetShort = 0x58,
    Iput = 0x59,
    IputWide = 0x5A,
    IputObject = 0x5B,
    IputBoolean = 0x5C,
    IputByte = 0x5D,
    IputChar = 0x5E,
    IputShort = 0x5F,
    Sget = 0x60,
    SgetWide = 0x61,
    SgetObject = 0x62,
    SgetBoolean = 0x63,
    SgetByte = 0x64,
    SgetChar = 0x65,
    SgetShort = 0x66,
    Sput = 0x67,
    SputWide = 0x68,
    SputObject = 0x69,
    SputBoolean = 0x6A,
    SputByte = 0x6B,
    SputChar = 0x6C,
    SputShort = 0x6D,
    InvokeVirtual = 0x6E,
    InvokeSuper = 0x6F,
    InvokeDirect = 0x70,
    InvokeStatic = 0x71,
    InvokeInterface = 0x72,
    InvokeVirtualRange = 0x74,
    InvokeSuperRange = 0x75,
    InvokeDirectRange = 0x76,
    InvokeStaticRange = 0x77,
    InvokeInterfaceRange = 0x78,
    NegInt = 0x7B,
    NotInt = 0x7C,
    NegLong = 0x7D,
    NotLong = 0x7E,
    NegFloat = 0x7F,
    NegDouble = 0x80,
    IntToLong = 0x81,
    IntToFloat = 0x82,
    IntToDouble = 0x83,
    LongToInt = 0x84,
    LongToFloat = 0x85,
    LongToDouble = 0x86,
    FloatToInt = 0x87,
    FloatToLong = 0x88,
    FloatToDouble = 0x89,
    DoubleToInt = 0x8A,
    DoubleToLong = 0x8B,
    DoubleToFloat = 0x8C,
    IntToByte = 0x8D,
    IntToChar = 0x8E,
    IntToShort = 0x8F,
    AddInt = 0x90,
    SubInt = 0x91,
    MulInt = 0x92,
    DivInt = 0x93,
    RemInt = 0x94,
    AndInt = 0x95,
    OrInt = 0x96,
    XorInt = 0x97,
    ShlInt = 0x98,
    ShrInt = 0x99,
    UshrInt = 0x9A,
    AddLong = 0x9B,
    SubLong = 0x9C,
    MulLong = 0x9D,
    DivLong = 0x9E,
    RemLong = 0x9F,
    AndLong = 0xA0,
    OrLong = 0xA1,
    XorLong = 0xA2,
    ShlLong = 0xA3,
    ShrLong = 0xA4,
    UshrLong = 0xA5,
    AddFloat = 0xA6,
    SubFloat = 0xA7,
    MulFloat = 0xA8,
    DivFloat = 0xA9,
    RemFloat = 0xAA,
    AddDouble = 0xAB,
    SubDouble = 0xAC,
    MulDouble = 0xAD,
    DivDouble = 0xAE,
    RemDouble = 0xAF,
    AddInt2Addr = 0xB0,
    SubInt2Addr = 0xB1,
    MulInt2Addr = 0xB2,
    DivInt2Addr = 0xB3,
    RemInt2Addr = 0xB4,
    AndInt2Addr = 0xB5,
    OrInt2Addr = 0xB6,
    XorInt2Addr = 0xB7,
    ShlInt2Addr = 0xB8,
    ShrInt2Addr = 0xB9,
    UshrInt2Addr = 0xBA,
    AddLong2Addr = 0xBB,
    SubLong2Addr = 0xBC,
    MulLong2Addr = 0xBD,
    DivLong2Addr = 0xBE,
    RemLong2Addr = 0xBF,
    AndLong2Addr = 0xC0,
    OrLong2Addr = 0xC1,
    XorLong2Addr = 0xC2,
    ShlLong2Addr = 0xC3,
    ShrLong2Addr = 0xC4,
    UshrLong2Addr = 0xC5,
    AddFloat2Addr = 0xC6,
    SubFloat2Addr = 0xC7,
    MulFloat2Addr = 0xC8,
    DivFloat2Addr = 0xC9,
    RemFloat2Addr = 0xCA,
    AddDouble2Addr = 0xCB,
    SubDouble2Addr = 0xCC,
    MulDouble2Addr = 0xCD,
    DivDouble2Addr = 0xCE,
    RemDouble2Addr = 0xCF,
    AddIntLit16 = 0xD0,
    RsubInt = 0xD1,
    MulIntLit16 = 0xD2,
    DivIntLit16 = 0xD3,
    RemIntLit16 = 0xD4,
    AndIntLit16 = 0xD5,
    OrIntLit16 = 0xD6,
    XorIntLit16 = 0xD7,
    AddIntLit8 = 0xD8,
    RsubIntLit8 = 0xD9,
    MulIntLit8 = 0xDA,
    DivIntLit8 = 0xDB,
    RemIntLit8 = 0xDC,
    AndIntLit8 = 0xDD,
    OrIntLit8 = 0xDE,
    XorIntLit8 = 0xDF,
    ShlIntLit8 = 0xE0,
    ShrIntLit8 = 0xE1,
    UshrIntLit8 = 0xE2,
    InvokePolymorphic = 0xFA,
    InvokePolymorphicRange = 0xFB,
    InvokeCustom = 0xFC,
    InvokeCustomRange = 0xFD,
    ConstMethodHandle = 0xFE,
    ConstMethodType = 0xFF,
}

impl Opcode {
    /// Encode an opcode to the low byte of a 16-bit code unit.
    ///
    /// Symmetric partner to [`Opcode::from_u8`]. The implementation
    /// uses `self as u8` which relies on `#[repr(u8)]` discriminants.
    /// The `Opcode::from_u8_to_u8_symmetry` test (in this module)
    /// enforces `from_u8(op.to_u8()) == Some(op)` for every u8, which
    /// catches any drift between the two directions at test time.
    #[inline]
    pub fn to_u8(self) -> u8 {
        // PROOF: `Opcode` is `#[repr(u8)]` (line 53), so `self as u8` is the
        // canonical discriminant cast — losslessly returns the declared
        // discriminant byte. The `from_u8_to_u8_symmetry` test in this module
        // dynamically verifies `from_u8(op.to_u8()) == Some(op)` for every
        // variant.
        #[allow(clippy::as_conversions, reason = "PROOF: #[repr(u8)] enum → u8 discriminant cast; canonical and dynamically tested.")]
        {
            self as u8
        }
    }

    /// Try to decode an opcode from the low byte of a 16-bit code unit.
    pub fn from_u8(byte: u8) -> Option<Self> {
        // Safety: repr(u8) but not contiguous, so we must match
        Some(match byte {
            0x00 => Self::Nop,
            0x01 => Self::Move,
            0x02 => Self::MoveFrom16,
            0x03 => Self::Move16,
            0x04 => Self::MoveWide,
            0x05 => Self::MoveWideFrom16,
            0x06 => Self::MoveWide16,
            0x07 => Self::MoveObject,
            0x08 => Self::MoveObjectFrom16,
            0x09 => Self::MoveObject16,
            0x0A => Self::MoveResult,
            0x0B => Self::MoveResultWide,
            0x0C => Self::MoveResultObject,
            0x0D => Self::MoveException,
            0x0E => Self::ReturnVoid,
            0x0F => Self::Return,
            0x10 => Self::ReturnWide,
            0x11 => Self::ReturnObject,
            0x12 => Self::Const4,
            0x13 => Self::Const16,
            0x14 => Self::Const,
            0x15 => Self::ConstHigh16,
            0x16 => Self::ConstWide16,
            0x17 => Self::ConstWide32,
            0x18 => Self::ConstWide,
            0x19 => Self::ConstWideHigh16,
            0x1A => Self::ConstString,
            0x1B => Self::ConstStringJumbo,
            0x1C => Self::ConstClass,
            0x1D => Self::MonitorEnter,
            0x1E => Self::MonitorExit,
            0x1F => Self::CheckCast,
            0x20 => Self::InstanceOf,
            0x21 => Self::ArrayLength,
            0x22 => Self::NewInstance,
            0x23 => Self::NewArray,
            0x24 => Self::FilledNewArray,
            0x25 => Self::FilledNewArrayRange,
            0x26 => Self::FillArrayData,
            0x27 => Self::Throw,
            0x28 => Self::Goto,
            0x29 => Self::Goto16,
            0x2A => Self::Goto32,
            0x2B => Self::PackedSwitch,
            0x2C => Self::SparseSwitch,
            0x2D => Self::CmplFloat,
            0x2E => Self::CmpgFloat,
            0x2F => Self::CmplDouble,
            0x30 => Self::CmpgDouble,
            0x31 => Self::CmpLong,
            0x32 => Self::IfEq,
            0x33 => Self::IfNe,
            0x34 => Self::IfLt,
            0x35 => Self::IfGe,
            0x36 => Self::IfGt,
            0x37 => Self::IfLe,
            0x38 => Self::IfEqz,
            0x39 => Self::IfNez,
            0x3A => Self::IfLtz,
            0x3B => Self::IfGez,
            0x3C => Self::IfGtz,
            0x3D => Self::IfLez,
            0x44 => Self::Aget,
            0x45 => Self::AgetWide,
            0x46 => Self::AgetObject,
            0x47 => Self::AgetBoolean,
            0x48 => Self::AgetByte,
            0x49 => Self::AgetChar,
            0x4A => Self::AgetShort,
            0x4B => Self::Aput,
            0x4C => Self::AputWide,
            0x4D => Self::AputObject,
            0x4E => Self::AputBoolean,
            0x4F => Self::AputByte,
            0x50 => Self::AputChar,
            0x51 => Self::AputShort,
            0x52 => Self::Iget,
            0x53 => Self::IgetWide,
            0x54 => Self::IgetObject,
            0x55 => Self::IgetBoolean,
            0x56 => Self::IgetByte,
            0x57 => Self::IgetChar,
            0x58 => Self::IgetShort,
            0x59 => Self::Iput,
            0x5A => Self::IputWide,
            0x5B => Self::IputObject,
            0x5C => Self::IputBoolean,
            0x5D => Self::IputByte,
            0x5E => Self::IputChar,
            0x5F => Self::IputShort,
            0x60 => Self::Sget,
            0x61 => Self::SgetWide,
            0x62 => Self::SgetObject,
            0x63 => Self::SgetBoolean,
            0x64 => Self::SgetByte,
            0x65 => Self::SgetChar,
            0x66 => Self::SgetShort,
            0x67 => Self::Sput,
            0x68 => Self::SputWide,
            0x69 => Self::SputObject,
            0x6A => Self::SputBoolean,
            0x6B => Self::SputByte,
            0x6C => Self::SputChar,
            0x6D => Self::SputShort,
            0x6E => Self::InvokeVirtual,
            0x6F => Self::InvokeSuper,
            0x70 => Self::InvokeDirect,
            0x71 => Self::InvokeStatic,
            0x72 => Self::InvokeInterface,
            0x74 => Self::InvokeVirtualRange,
            0x75 => Self::InvokeSuperRange,
            0x76 => Self::InvokeDirectRange,
            0x77 => Self::InvokeStaticRange,
            0x78 => Self::InvokeInterfaceRange,
            0x7B => Self::NegInt,
            0x7C => Self::NotInt,
            0x7D => Self::NegLong,
            0x7E => Self::NotLong,
            0x7F => Self::NegFloat,
            0x80 => Self::NegDouble,
            0x81 => Self::IntToLong,
            0x82 => Self::IntToFloat,
            0x83 => Self::IntToDouble,
            0x84 => Self::LongToInt,
            0x85 => Self::LongToFloat,
            0x86 => Self::LongToDouble,
            0x87 => Self::FloatToInt,
            0x88 => Self::FloatToLong,
            0x89 => Self::FloatToDouble,
            0x8A => Self::DoubleToInt,
            0x8B => Self::DoubleToLong,
            0x8C => Self::DoubleToFloat,
            0x8D => Self::IntToByte,
            0x8E => Self::IntToChar,
            0x8F => Self::IntToShort,
            0x90 => Self::AddInt,
            0x91 => Self::SubInt,
            0x92 => Self::MulInt,
            0x93 => Self::DivInt,
            0x94 => Self::RemInt,
            0x95 => Self::AndInt,
            0x96 => Self::OrInt,
            0x97 => Self::XorInt,
            0x98 => Self::ShlInt,
            0x99 => Self::ShrInt,
            0x9A => Self::UshrInt,
            0x9B => Self::AddLong,
            0x9C => Self::SubLong,
            0x9D => Self::MulLong,
            0x9E => Self::DivLong,
            0x9F => Self::RemLong,
            0xA0 => Self::AndLong,
            0xA1 => Self::OrLong,
            0xA2 => Self::XorLong,
            0xA3 => Self::ShlLong,
            0xA4 => Self::ShrLong,
            0xA5 => Self::UshrLong,
            0xA6 => Self::AddFloat,
            0xA7 => Self::SubFloat,
            0xA8 => Self::MulFloat,
            0xA9 => Self::DivFloat,
            0xAA => Self::RemFloat,
            0xAB => Self::AddDouble,
            0xAC => Self::SubDouble,
            0xAD => Self::MulDouble,
            0xAE => Self::DivDouble,
            0xAF => Self::RemDouble,
            0xB0 => Self::AddInt2Addr,
            0xB1 => Self::SubInt2Addr,
            0xB2 => Self::MulInt2Addr,
            0xB3 => Self::DivInt2Addr,
            0xB4 => Self::RemInt2Addr,
            0xB5 => Self::AndInt2Addr,
            0xB6 => Self::OrInt2Addr,
            0xB7 => Self::XorInt2Addr,
            0xB8 => Self::ShlInt2Addr,
            0xB9 => Self::ShrInt2Addr,
            0xBA => Self::UshrInt2Addr,
            0xBB => Self::AddLong2Addr,
            0xBC => Self::SubLong2Addr,
            0xBD => Self::MulLong2Addr,
            0xBE => Self::DivLong2Addr,
            0xBF => Self::RemLong2Addr,
            0xC0 => Self::AndLong2Addr,
            0xC1 => Self::OrLong2Addr,
            0xC2 => Self::XorLong2Addr,
            0xC3 => Self::ShlLong2Addr,
            0xC4 => Self::ShrLong2Addr,
            0xC5 => Self::UshrLong2Addr,
            0xC6 => Self::AddFloat2Addr,
            0xC7 => Self::SubFloat2Addr,
            0xC8 => Self::MulFloat2Addr,
            0xC9 => Self::DivFloat2Addr,
            0xCA => Self::RemFloat2Addr,
            0xCB => Self::AddDouble2Addr,
            0xCC => Self::SubDouble2Addr,
            0xCD => Self::MulDouble2Addr,
            0xCE => Self::DivDouble2Addr,
            0xCF => Self::RemDouble2Addr,
            0xD0 => Self::AddIntLit16,
            0xD1 => Self::RsubInt,
            0xD2 => Self::MulIntLit16,
            0xD3 => Self::DivIntLit16,
            0xD4 => Self::RemIntLit16,
            0xD5 => Self::AndIntLit16,
            0xD6 => Self::OrIntLit16,
            0xD7 => Self::XorIntLit16,
            0xD8 => Self::AddIntLit8,
            0xD9 => Self::RsubIntLit8,
            0xDA => Self::MulIntLit8,
            0xDB => Self::DivIntLit8,
            0xDC => Self::RemIntLit8,
            0xDD => Self::AndIntLit8,
            0xDE => Self::OrIntLit8,
            0xDF => Self::XorIntLit8,
            0xE0 => Self::ShlIntLit8,
            0xE1 => Self::ShrIntLit8,
            0xE2 => Self::UshrIntLit8,
            0xFA => Self::InvokePolymorphic,
            0xFB => Self::InvokePolymorphicRange,
            0xFC => Self::InvokeCustom,
            0xFD => Self::InvokeCustomRange,
            0xFE => Self::ConstMethodHandle,
            0xFF => Self::ConstMethodType,
            _ => return None,
        })
    }

    pub fn mnemonic(&self) -> &'static str {
        match self {
            Self::Nop => "nop",
            Self::Move => "move",
            Self::MoveFrom16 => "move/from16",
            Self::Move16 => "move/16",
            Self::MoveWide => "move-wide",
            Self::MoveWideFrom16 => "move-wide/from16",
            Self::MoveWide16 => "move-wide/16",
            Self::MoveObject => "move-object",
            Self::MoveObjectFrom16 => "move-object/from16",
            Self::MoveObject16 => "move-object/16",
            Self::MoveResult => "move-result",
            Self::MoveResultWide => "move-result-wide",
            Self::MoveResultObject => "move-result-object",
            Self::MoveException => "move-exception",
            Self::ReturnVoid => "return-void",
            Self::Return => "return",
            Self::ReturnWide => "return-wide",
            Self::ReturnObject => "return-object",
            Self::Const4 => "const/4",
            Self::Const16 => "const/16",
            Self::Const => "const",
            Self::ConstHigh16 => "const/high16",
            Self::ConstWide16 => "const-wide/16",
            Self::ConstWide32 => "const-wide/32",
            Self::ConstWide => "const-wide",
            Self::ConstWideHigh16 => "const-wide/high16",
            Self::ConstString => "const-string",
            Self::ConstStringJumbo => "const-string/jumbo",
            Self::ConstClass => "const-class",
            Self::MonitorEnter => "monitor-enter",
            Self::MonitorExit => "monitor-exit",
            Self::CheckCast => "check-cast",
            Self::InstanceOf => "instance-of",
            Self::ArrayLength => "array-length",
            Self::NewInstance => "new-instance",
            Self::NewArray => "new-array",
            Self::FilledNewArray => "filled-new-array",
            Self::FilledNewArrayRange => "filled-new-array/range",
            Self::FillArrayData => "fill-array-data",
            Self::Throw => "throw",
            Self::Goto => "goto",
            Self::Goto16 => "goto/16",
            Self::Goto32 => "goto/32",
            Self::PackedSwitch => "packed-switch",
            Self::SparseSwitch => "sparse-switch",
            Self::CmplFloat => "cmpl-float",
            Self::CmpgFloat => "cmpg-float",
            Self::CmplDouble => "cmpl-double",
            Self::CmpgDouble => "cmpg-double",
            Self::CmpLong => "cmp-long",
            Self::IfEq => "if-eq",
            Self::IfNe => "if-ne",
            Self::IfLt => "if-lt",
            Self::IfGe => "if-ge",
            Self::IfGt => "if-gt",
            Self::IfLe => "if-le",
            Self::IfEqz => "if-eqz",
            Self::IfNez => "if-nez",
            Self::IfLtz => "if-ltz",
            Self::IfGez => "if-gez",
            Self::IfGtz => "if-gtz",
            Self::IfLez => "if-lez",
            Self::Aget => "aget",
            Self::AgetWide => "aget-wide",
            Self::AgetObject => "aget-object",
            Self::AgetBoolean => "aget-boolean",
            Self::AgetByte => "aget-byte",
            Self::AgetChar => "aget-char",
            Self::AgetShort => "aget-short",
            Self::Aput => "aput",
            Self::AputWide => "aput-wide",
            Self::AputObject => "aput-object",
            Self::AputBoolean => "aput-boolean",
            Self::AputByte => "aput-byte",
            Self::AputChar => "aput-char",
            Self::AputShort => "aput-short",
            Self::Iget => "iget",
            Self::IgetWide => "iget-wide",
            Self::IgetObject => "iget-object",
            Self::IgetBoolean => "iget-boolean",
            Self::IgetByte => "iget-byte",
            Self::IgetChar => "iget-char",
            Self::IgetShort => "iget-short",
            Self::Iput => "iput",
            Self::IputWide => "iput-wide",
            Self::IputObject => "iput-object",
            Self::IputBoolean => "iput-boolean",
            Self::IputByte => "iput-byte",
            Self::IputChar => "iput-char",
            Self::IputShort => "iput-short",
            Self::Sget => "sget",
            Self::SgetWide => "sget-wide",
            Self::SgetObject => "sget-object",
            Self::SgetBoolean => "sget-boolean",
            Self::SgetByte => "sget-byte",
            Self::SgetChar => "sget-char",
            Self::SgetShort => "sget-short",
            Self::Sput => "sput",
            Self::SputWide => "sput-wide",
            Self::SputObject => "sput-object",
            Self::SputBoolean => "sput-boolean",
            Self::SputByte => "sput-byte",
            Self::SputChar => "sput-char",
            Self::SputShort => "sput-short",
            Self::InvokeVirtual => "invoke-virtual",
            Self::InvokeSuper => "invoke-super",
            Self::InvokeDirect => "invoke-direct",
            Self::InvokeStatic => "invoke-static",
            Self::InvokeInterface => "invoke-interface",
            Self::InvokeVirtualRange => "invoke-virtual/range",
            Self::InvokeSuperRange => "invoke-super/range",
            Self::InvokeDirectRange => "invoke-direct/range",
            Self::InvokeStaticRange => "invoke-static/range",
            Self::InvokeInterfaceRange => "invoke-interface/range",
            Self::NegInt => "neg-int",
            Self::NotInt => "not-int",
            Self::NegLong => "neg-long",
            Self::NotLong => "not-long",
            Self::NegFloat => "neg-float",
            Self::NegDouble => "neg-double",
            Self::IntToLong => "int-to-long",
            Self::IntToFloat => "int-to-float",
            Self::IntToDouble => "int-to-double",
            Self::LongToInt => "long-to-int",
            Self::LongToFloat => "long-to-float",
            Self::LongToDouble => "long-to-double",
            Self::FloatToInt => "float-to-int",
            Self::FloatToLong => "float-to-long",
            Self::FloatToDouble => "float-to-double",
            Self::DoubleToInt => "double-to-int",
            Self::DoubleToLong => "double-to-long",
            Self::DoubleToFloat => "double-to-float",
            Self::IntToByte => "int-to-byte",
            Self::IntToChar => "int-to-char",
            Self::IntToShort => "int-to-short",
            Self::AddInt => "add-int",
            Self::SubInt => "sub-int",
            Self::MulInt => "mul-int",
            Self::DivInt => "div-int",
            Self::RemInt => "rem-int",
            Self::AndInt => "and-int",
            Self::OrInt => "or-int",
            Self::XorInt => "xor-int",
            Self::ShlInt => "shl-int",
            Self::ShrInt => "shr-int",
            Self::UshrInt => "ushr-int",
            Self::AddLong => "add-long",
            Self::SubLong => "sub-long",
            Self::MulLong => "mul-long",
            Self::DivLong => "div-long",
            Self::RemLong => "rem-long",
            Self::AndLong => "and-long",
            Self::OrLong => "or-long",
            Self::XorLong => "xor-long",
            Self::ShlLong => "shl-long",
            Self::ShrLong => "shr-long",
            Self::UshrLong => "ushr-long",
            Self::AddFloat => "add-float",
            Self::SubFloat => "sub-float",
            Self::MulFloat => "mul-float",
            Self::DivFloat => "div-float",
            Self::RemFloat => "rem-float",
            Self::AddDouble => "add-double",
            Self::SubDouble => "sub-double",
            Self::MulDouble => "mul-double",
            Self::DivDouble => "div-double",
            Self::RemDouble => "rem-double",
            Self::AddInt2Addr => "add-int/2addr",
            Self::SubInt2Addr => "sub-int/2addr",
            Self::MulInt2Addr => "mul-int/2addr",
            Self::DivInt2Addr => "div-int/2addr",
            Self::RemInt2Addr => "rem-int/2addr",
            Self::AndInt2Addr => "and-int/2addr",
            Self::OrInt2Addr => "or-int/2addr",
            Self::XorInt2Addr => "xor-int/2addr",
            Self::ShlInt2Addr => "shl-int/2addr",
            Self::ShrInt2Addr => "shr-int/2addr",
            Self::UshrInt2Addr => "ushr-int/2addr",
            Self::AddLong2Addr => "add-long/2addr",
            Self::SubLong2Addr => "sub-long/2addr",
            Self::MulLong2Addr => "mul-long/2addr",
            Self::DivLong2Addr => "div-long/2addr",
            Self::RemLong2Addr => "rem-long/2addr",
            Self::AndLong2Addr => "and-long/2addr",
            Self::OrLong2Addr => "or-long/2addr",
            Self::XorLong2Addr => "xor-long/2addr",
            Self::ShlLong2Addr => "shl-long/2addr",
            Self::ShrLong2Addr => "shr-long/2addr",
            Self::UshrLong2Addr => "ushr-long/2addr",
            Self::AddFloat2Addr => "add-float/2addr",
            Self::SubFloat2Addr => "sub-float/2addr",
            Self::MulFloat2Addr => "mul-float/2addr",
            Self::DivFloat2Addr => "div-float/2addr",
            Self::RemFloat2Addr => "rem-float/2addr",
            Self::AddDouble2Addr => "add-double/2addr",
            Self::SubDouble2Addr => "sub-double/2addr",
            Self::MulDouble2Addr => "mul-double/2addr",
            Self::DivDouble2Addr => "div-double/2addr",
            Self::RemDouble2Addr => "rem-double/2addr",
            Self::AddIntLit16 => "add-int/lit16",
            Self::RsubInt => "rsub-int",
            Self::MulIntLit16 => "mul-int/lit16",
            Self::DivIntLit16 => "div-int/lit16",
            Self::RemIntLit16 => "rem-int/lit16",
            Self::AndIntLit16 => "and-int/lit16",
            Self::OrIntLit16 => "or-int/lit16",
            Self::XorIntLit16 => "xor-int/lit16",
            Self::AddIntLit8 => "add-int/lit8",
            Self::RsubIntLit8 => "rsub-int/lit8",
            Self::MulIntLit8 => "mul-int/lit8",
            Self::DivIntLit8 => "div-int/lit8",
            Self::RemIntLit8 => "rem-int/lit8",
            Self::AndIntLit8 => "and-int/lit8",
            Self::OrIntLit8 => "or-int/lit8",
            Self::XorIntLit8 => "xor-int/lit8",
            Self::ShlIntLit8 => "shl-int/lit8",
            Self::ShrIntLit8 => "shr-int/lit8",
            Self::UshrIntLit8 => "ushr-int/lit8",
            Self::InvokePolymorphic => "invoke-polymorphic",
            Self::InvokePolymorphicRange => "invoke-polymorphic/range",
            Self::InvokeCustom => "invoke-custom",
            Self::InvokeCustomRange => "invoke-custom/range",
            Self::ConstMethodHandle => "const-method-handle",
            Self::ConstMethodType => "const-method-type",
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn roundtrip_all_opcodes() {
        let all = [
            0x00u8, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
            0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B,
            0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,
            0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
            0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B,
            0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
            0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
            0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x74, 0x75, 0x76,
            0x77, 0x78, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86,
            0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94,
            0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2,
            0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0,
            0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE,
            0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC,
            0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA,
            0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE0, 0xE1, 0xE2, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
        ];
        for &byte in &all {
            let op = Opcode::from_u8(byte).unwrap_or_else(|| panic!("missing opcode {byte:#04x}"));
            assert!(!op.mnemonic().is_empty());
        }
    }

    #[test]
    fn unused_opcodes_return_none() {
        // Gaps: 0x3E-0x43, 0x73, 0x79-0x7A, 0xE3-0xF9
        assert!(Opcode::from_u8(0x3E).is_none());
        assert!(Opcode::from_u8(0x73).is_none());
        assert!(Opcode::from_u8(0x79).is_none());
        assert!(Opcode::from_u8(0xE3).is_none());
        assert!(Opcode::from_u8(0xF9).is_none());
    }

    /// Cross-direction symmetry: for every byte accepted by `from_u8`,
    /// the resulting Opcode must encode back to that same byte via
    /// `to_u8`. This is the acceptance test for `to_u8`'s reliance on
    /// `repr(u8)` discriminants — any drift between the enum
    /// declaration and `from_u8` surfaces here.
    #[test]
    fn from_u8_to_u8_symmetry() {
        for byte in 0u8..=0xFF {
            if let Some(op) = Opcode::from_u8(byte) {
                assert_eq!(
                    op.to_u8(),
                    byte,
                    "asymmetry: from_u8({byte:#04x}) = {op:?} but to_u8 = {:#04x}",
                    op.to_u8()
                );
            }
        }
    }
}
