//! Bitcoin Script opcodes.
//!
//! All 214 BSV opcodes as a `#[repr(u8)]` enum. Unknown byte values
//! map to `OpInvalidOpcode` via `From<u8>`.

/// All BSV script opcodes with their byte values.
///
/// Variant names use TS-SDK style: `Op::OpDup`, `Op::OpCheckSig`, etc.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
#[allow(non_camel_case_types)]
pub enum Op {
    // push value
    Op0 = 0x00,
    OpPushData1 = 0x4c,
    OpPushData2 = 0x4d,
    OpPushData4 = 0x4e,
    Op1Negate = 0x4f,
    OpReserved = 0x50,
    Op1 = 0x51,
    Op2 = 0x52,
    Op3 = 0x53,
    Op4 = 0x54,
    Op5 = 0x55,
    Op6 = 0x56,
    Op7 = 0x57,
    Op8 = 0x58,
    Op9 = 0x59,
    Op10 = 0x5a,
    Op11 = 0x5b,
    Op12 = 0x5c,
    Op13 = 0x5d,
    Op14 = 0x5e,
    Op15 = 0x5f,
    Op16 = 0x60,

    // control
    OpNop = 0x61,
    OpVer = 0x62,
    OpIf = 0x63,
    OpNotIf = 0x64,
    OpVerIf = 0x65,
    OpVerNotIf = 0x66,
    OpElse = 0x67,
    OpEndIf = 0x68,
    OpVerify = 0x69,
    OpReturn = 0x6a,

    // stack ops
    OpToAltStack = 0x6b,
    OpFromAltStack = 0x6c,
    Op2Drop = 0x6d,
    Op2Dup = 0x6e,
    Op3Dup = 0x6f,
    Op2Over = 0x70,
    Op2Rot = 0x71,
    Op2Swap = 0x72,
    OpIfDup = 0x73,
    OpDepth = 0x74,
    OpDrop = 0x75,
    OpDup = 0x76,
    OpNip = 0x77,
    OpOver = 0x78,
    OpPick = 0x79,
    OpRoll = 0x7a,
    OpRot = 0x7b,
    OpSwap = 0x7c,
    OpTuck = 0x7d,

    // data manipulation ops (BSV-restored)
    OpCat = 0x7e,
    OpSplit = 0x7f,
    OpNum2Bin = 0x80,
    OpBin2Num = 0x81,
    OpSize = 0x82,

    // bit logic
    OpInvert = 0x83,
    OpAnd = 0x84,
    OpOr = 0x85,
    OpXor = 0x86,
    OpEqual = 0x87,
    OpEqualVerify = 0x88,
    OpReserved1 = 0x89,
    OpReserved2 = 0x8a,

    // numeric
    Op1Add = 0x8b,
    Op1Sub = 0x8c,
    Op2Mul = 0x8d,
    Op2Div = 0x8e,
    OpNegate = 0x8f,
    OpAbs = 0x90,
    OpNot = 0x91,
    Op0NotEqual = 0x92,
    OpAdd = 0x93,
    OpSub = 0x94,
    OpMul = 0x95,
    OpDiv = 0x96,
    OpMod = 0x97,
    OpLShift = 0x98,
    OpRShift = 0x99,
    OpBoolAnd = 0x9a,
    OpBoolOr = 0x9b,
    OpNumEqual = 0x9c,
    OpNumEqualVerify = 0x9d,
    OpNumNotEqual = 0x9e,
    OpLessThan = 0x9f,
    OpGreaterThan = 0xa0,
    OpLessThanOrEqual = 0xa1,
    OpGreaterThanOrEqual = 0xa2,
    OpMin = 0xa3,
    OpMax = 0xa4,
    OpWithin = 0xa5,

    // crypto
    OpRipemd160 = 0xa6,
    OpSha1 = 0xa7,
    OpSha256 = 0xa8,
    OpHash160 = 0xa9,
    OpHash256 = 0xaa,
    OpCodeSeparator = 0xab,
    OpCheckSig = 0xac,
    OpCheckSigVerify = 0xad,
    OpCheckMultiSig = 0xae,
    OpCheckMultiSigVerify = 0xaf,

    // expansion
    OpNop1 = 0xb0,
    OpNop2 = 0xb1,
    OpNop3 = 0xb2,
    // Chronicle 2026 opcodes (share byte values with NOP4-NOP8)
    OpSubstr = 0xb3,
    OpLeft = 0xb4,
    OpRight = 0xb5,
    OpLShiftNum = 0xb6,
    OpRShiftNum = 0xb7,
    OpNop9 = 0xb8,
    OpNop10 = 0xb9,
    OpNop11 = 0xba,
    OpNop12 = 0xbb,
    OpNop13 = 0xbc,
    OpNop14 = 0xbd,
    OpNop15 = 0xbe,
    OpNop16 = 0xbf,
    OpNop17 = 0xc0,
    OpNop18 = 0xc1,
    OpNop19 = 0xc2,
    OpNop20 = 0xc3,
    OpNop21 = 0xc4,
    OpNop22 = 0xc5,
    OpNop23 = 0xc6,
    OpNop24 = 0xc7,
    OpNop25 = 0xc8,
    OpNop26 = 0xc9,
    OpNop27 = 0xca,
    OpNop28 = 0xcb,
    OpNop29 = 0xcc,
    OpNop30 = 0xcd,
    OpNop31 = 0xce,
    OpNop32 = 0xcf,
    OpNop33 = 0xd0,
    OpNop34 = 0xd1,
    OpNop35 = 0xd2,
    OpNop36 = 0xd3,
    OpNop37 = 0xd4,
    OpNop38 = 0xd5,
    OpNop39 = 0xd6,
    OpNop40 = 0xd7,
    OpNop41 = 0xd8,
    OpNop42 = 0xd9,
    OpNop43 = 0xda,
    OpNop44 = 0xdb,
    OpNop45 = 0xdc,
    OpNop46 = 0xdd,
    OpNop47 = 0xde,
    OpNop48 = 0xdf,
    OpNop49 = 0xe0,
    OpNop50 = 0xe1,
    OpNop51 = 0xe2,
    OpNop52 = 0xe3,
    OpNop53 = 0xe4,
    OpNop54 = 0xe5,
    OpNop55 = 0xe6,
    OpNop56 = 0xe7,
    OpNop57 = 0xe8,
    OpNop58 = 0xe9,
    OpNop59 = 0xea,
    OpNop60 = 0xeb,
    OpNop61 = 0xec,
    OpNop62 = 0xed,
    OpNop63 = 0xee,
    OpNop64 = 0xef,
    OpNop65 = 0xf0,
    OpNop66 = 0xf1,
    OpNop67 = 0xf2,
    OpNop68 = 0xf3,
    OpNop69 = 0xf4,
    OpNop70 = 0xf5,
    OpNop71 = 0xf6,
    OpNop72 = 0xf7,
    OpNop73 = 0xf8,

    // template matching params
    OpSmallData = 0xf9,
    OpSmallInteger = 0xfa,
    OpPubKeys = 0xfb,
    OpNop77 = 0xfc,
    OpPubKeyHash = 0xfd,
    OpPubKey = 0xfe,

    OpInvalidOpcode = 0xff,
}

impl Op {
    /// Return the canonical name string, e.g. "OP_DUP", "OP_CHECKSIG".
    pub fn to_name(&self) -> &'static str {
        match self {
            Op::Op0 => "OP_0",
            Op::OpPushData1 => "OP_PUSHDATA1",
            Op::OpPushData2 => "OP_PUSHDATA2",
            Op::OpPushData4 => "OP_PUSHDATA4",
            Op::Op1Negate => "OP_1NEGATE",
            Op::OpReserved => "OP_RESERVED",
            Op::Op1 => "OP_1",
            Op::Op2 => "OP_2",
            Op::Op3 => "OP_3",
            Op::Op4 => "OP_4",
            Op::Op5 => "OP_5",
            Op::Op6 => "OP_6",
            Op::Op7 => "OP_7",
            Op::Op8 => "OP_8",
            Op::Op9 => "OP_9",
            Op::Op10 => "OP_10",
            Op::Op11 => "OP_11",
            Op::Op12 => "OP_12",
            Op::Op13 => "OP_13",
            Op::Op14 => "OP_14",
            Op::Op15 => "OP_15",
            Op::Op16 => "OP_16",
            Op::OpNop => "OP_NOP",
            Op::OpVer => "OP_VER",
            Op::OpIf => "OP_IF",
            Op::OpNotIf => "OP_NOTIF",
            Op::OpVerIf => "OP_VERIF",
            Op::OpVerNotIf => "OP_VERNOTIF",
            Op::OpElse => "OP_ELSE",
            Op::OpEndIf => "OP_ENDIF",
            Op::OpVerify => "OP_VERIFY",
            Op::OpReturn => "OP_RETURN",
            Op::OpToAltStack => "OP_TOALTSTACK",
            Op::OpFromAltStack => "OP_FROMALTSTACK",
            Op::Op2Drop => "OP_2DROP",
            Op::Op2Dup => "OP_2DUP",
            Op::Op3Dup => "OP_3DUP",
            Op::Op2Over => "OP_2OVER",
            Op::Op2Rot => "OP_2ROT",
            Op::Op2Swap => "OP_2SWAP",
            Op::OpIfDup => "OP_IFDUP",
            Op::OpDepth => "OP_DEPTH",
            Op::OpDrop => "OP_DROP",
            Op::OpDup => "OP_DUP",
            Op::OpNip => "OP_NIP",
            Op::OpOver => "OP_OVER",
            Op::OpPick => "OP_PICK",
            Op::OpRoll => "OP_ROLL",
            Op::OpRot => "OP_ROT",
            Op::OpSwap => "OP_SWAP",
            Op::OpTuck => "OP_TUCK",
            Op::OpCat => "OP_CAT",
            Op::OpSplit => "OP_SPLIT",
            Op::OpNum2Bin => "OP_NUM2BIN",
            Op::OpBin2Num => "OP_BIN2NUM",
            Op::OpSize => "OP_SIZE",
            Op::OpInvert => "OP_INVERT",
            Op::OpAnd => "OP_AND",
            Op::OpOr => "OP_OR",
            Op::OpXor => "OP_XOR",
            Op::OpEqual => "OP_EQUAL",
            Op::OpEqualVerify => "OP_EQUALVERIFY",
            Op::OpReserved1 => "OP_RESERVED1",
            Op::OpReserved2 => "OP_RESERVED2",
            Op::Op1Add => "OP_1ADD",
            Op::Op1Sub => "OP_1SUB",
            Op::Op2Mul => "OP_2MUL",
            Op::Op2Div => "OP_2DIV",
            Op::OpNegate => "OP_NEGATE",
            Op::OpAbs => "OP_ABS",
            Op::OpNot => "OP_NOT",
            Op::Op0NotEqual => "OP_0NOTEQUAL",
            Op::OpAdd => "OP_ADD",
            Op::OpSub => "OP_SUB",
            Op::OpMul => "OP_MUL",
            Op::OpDiv => "OP_DIV",
            Op::OpMod => "OP_MOD",
            Op::OpLShift => "OP_LSHIFT",
            Op::OpRShift => "OP_RSHIFT",
            Op::OpBoolAnd => "OP_BOOLAND",
            Op::OpBoolOr => "OP_BOOLOR",
            Op::OpNumEqual => "OP_NUMEQUAL",
            Op::OpNumEqualVerify => "OP_NUMEQUALVERIFY",
            Op::OpNumNotEqual => "OP_NUMNOTEQUAL",
            Op::OpLessThan => "OP_LESSTHAN",
            Op::OpGreaterThan => "OP_GREATERTHAN",
            Op::OpLessThanOrEqual => "OP_LESSTHANOREQUAL",
            Op::OpGreaterThanOrEqual => "OP_GREATERTHANOREQUAL",
            Op::OpMin => "OP_MIN",
            Op::OpMax => "OP_MAX",
            Op::OpWithin => "OP_WITHIN",
            Op::OpRipemd160 => "OP_RIPEMD160",
            Op::OpSha1 => "OP_SHA1",
            Op::OpSha256 => "OP_SHA256",
            Op::OpHash160 => "OP_HASH160",
            Op::OpHash256 => "OP_HASH256",
            Op::OpCodeSeparator => "OP_CODESEPARATOR",
            Op::OpCheckSig => "OP_CHECKSIG",
            Op::OpCheckSigVerify => "OP_CHECKSIGVERIFY",
            Op::OpCheckMultiSig => "OP_CHECKMULTISIG",
            Op::OpCheckMultiSigVerify => "OP_CHECKMULTISIGVERIFY",
            Op::OpNop1 => "OP_NOP1",
            Op::OpNop2 => "OP_NOP2",
            Op::OpNop3 => "OP_NOP3",
            Op::OpSubstr => "OP_SUBSTR",
            Op::OpLeft => "OP_LEFT",
            Op::OpRight => "OP_RIGHT",
            Op::OpLShiftNum => "OP_LSHIFTNUM",
            Op::OpRShiftNum => "OP_RSHIFTNUM",
            Op::OpNop9 => "OP_NOP9",
            Op::OpNop10 => "OP_NOP10",
            Op::OpNop11 => "OP_NOP11",
            Op::OpNop12 => "OP_NOP12",
            Op::OpNop13 => "OP_NOP13",
            Op::OpNop14 => "OP_NOP14",
            Op::OpNop15 => "OP_NOP15",
            Op::OpNop16 => "OP_NOP16",
            Op::OpNop17 => "OP_NOP17",
            Op::OpNop18 => "OP_NOP18",
            Op::OpNop19 => "OP_NOP19",
            Op::OpNop20 => "OP_NOP20",
            Op::OpNop21 => "OP_NOP21",
            Op::OpNop22 => "OP_NOP22",
            Op::OpNop23 => "OP_NOP23",
            Op::OpNop24 => "OP_NOP24",
            Op::OpNop25 => "OP_NOP25",
            Op::OpNop26 => "OP_NOP26",
            Op::OpNop27 => "OP_NOP27",
            Op::OpNop28 => "OP_NOP28",
            Op::OpNop29 => "OP_NOP29",
            Op::OpNop30 => "OP_NOP30",
            Op::OpNop31 => "OP_NOP31",
            Op::OpNop32 => "OP_NOP32",
            Op::OpNop33 => "OP_NOP33",
            Op::OpNop34 => "OP_NOP34",
            Op::OpNop35 => "OP_NOP35",
            Op::OpNop36 => "OP_NOP36",
            Op::OpNop37 => "OP_NOP37",
            Op::OpNop38 => "OP_NOP38",
            Op::OpNop39 => "OP_NOP39",
            Op::OpNop40 => "OP_NOP40",
            Op::OpNop41 => "OP_NOP41",
            Op::OpNop42 => "OP_NOP42",
            Op::OpNop43 => "OP_NOP43",
            Op::OpNop44 => "OP_NOP44",
            Op::OpNop45 => "OP_NOP45",
            Op::OpNop46 => "OP_NOP46",
            Op::OpNop47 => "OP_NOP47",
            Op::OpNop48 => "OP_NOP48",
            Op::OpNop49 => "OP_NOP49",
            Op::OpNop50 => "OP_NOP50",
            Op::OpNop51 => "OP_NOP51",
            Op::OpNop52 => "OP_NOP52",
            Op::OpNop53 => "OP_NOP53",
            Op::OpNop54 => "OP_NOP54",
            Op::OpNop55 => "OP_NOP55",
            Op::OpNop56 => "OP_NOP56",
            Op::OpNop57 => "OP_NOP57",
            Op::OpNop58 => "OP_NOP58",
            Op::OpNop59 => "OP_NOP59",
            Op::OpNop60 => "OP_NOP60",
            Op::OpNop61 => "OP_NOP61",
            Op::OpNop62 => "OP_NOP62",
            Op::OpNop63 => "OP_NOP63",
            Op::OpNop64 => "OP_NOP64",
            Op::OpNop65 => "OP_NOP65",
            Op::OpNop66 => "OP_NOP66",
            Op::OpNop67 => "OP_NOP67",
            Op::OpNop68 => "OP_NOP68",
            Op::OpNop69 => "OP_NOP69",
            Op::OpNop70 => "OP_NOP70",
            Op::OpNop71 => "OP_NOP71",
            Op::OpNop72 => "OP_NOP72",
            Op::OpNop73 => "OP_NOP73",
            Op::OpSmallData => "OP_SMALLDATA",
            Op::OpSmallInteger => "OP_SMALLINTEGER",
            Op::OpPubKeys => "OP_PUBKEYS",
            Op::OpNop77 => "OP_NOP77",
            Op::OpPubKeyHash => "OP_PUBKEYHASH",
            Op::OpPubKey => "OP_PUBKEY",
            Op::OpInvalidOpcode => "OP_INVALIDOPCODE",
        }
    }

    /// Return the raw byte value of this opcode.
    pub fn to_byte(&self) -> u8 {
        *self as u8
    }

    /// Look up an Op by its canonical name string (e.g. "OP_DUP").
    /// Returns None if not found. Also handles aliases like "OP_FALSE"
    /// and "OP_TRUE".
    pub fn from_name(name: &str) -> Option<Op> {
        match name {
            "OP_0" | "OP_FALSE" => Some(Op::Op0),
            "OP_PUSHDATA1" => Some(Op::OpPushData1),
            "OP_PUSHDATA2" => Some(Op::OpPushData2),
            "OP_PUSHDATA4" => Some(Op::OpPushData4),
            "OP_1NEGATE" => Some(Op::Op1Negate),
            "OP_RESERVED" => Some(Op::OpReserved),
            "OP_1" | "OP_TRUE" => Some(Op::Op1),
            "OP_2" => Some(Op::Op2),
            "OP_3" => Some(Op::Op3),
            "OP_4" => Some(Op::Op4),
            "OP_5" => Some(Op::Op5),
            "OP_6" => Some(Op::Op6),
            "OP_7" => Some(Op::Op7),
            "OP_8" => Some(Op::Op8),
            "OP_9" => Some(Op::Op9),
            "OP_10" => Some(Op::Op10),
            "OP_11" => Some(Op::Op11),
            "OP_12" => Some(Op::Op12),
            "OP_13" => Some(Op::Op13),
            "OP_14" => Some(Op::Op14),
            "OP_15" => Some(Op::Op15),
            "OP_16" => Some(Op::Op16),
            "OP_NOP" => Some(Op::OpNop),
            "OP_VER" => Some(Op::OpVer),
            "OP_IF" => Some(Op::OpIf),
            "OP_NOTIF" => Some(Op::OpNotIf),
            "OP_VERIF" => Some(Op::OpVerIf),
            "OP_VERNOTIF" => Some(Op::OpVerNotIf),
            "OP_ELSE" => Some(Op::OpElse),
            "OP_ENDIF" => Some(Op::OpEndIf),
            "OP_VERIFY" => Some(Op::OpVerify),
            "OP_RETURN" => Some(Op::OpReturn),
            "OP_TOALTSTACK" => Some(Op::OpToAltStack),
            "OP_FROMALTSTACK" => Some(Op::OpFromAltStack),
            "OP_2DROP" => Some(Op::Op2Drop),
            "OP_2DUP" => Some(Op::Op2Dup),
            "OP_3DUP" => Some(Op::Op3Dup),
            "OP_2OVER" => Some(Op::Op2Over),
            "OP_2ROT" => Some(Op::Op2Rot),
            "OP_2SWAP" => Some(Op::Op2Swap),
            "OP_IFDUP" => Some(Op::OpIfDup),
            "OP_DEPTH" => Some(Op::OpDepth),
            "OP_DROP" => Some(Op::OpDrop),
            "OP_DUP" => Some(Op::OpDup),
            "OP_NIP" => Some(Op::OpNip),
            "OP_OVER" => Some(Op::OpOver),
            "OP_PICK" => Some(Op::OpPick),
            "OP_ROLL" => Some(Op::OpRoll),
            "OP_ROT" => Some(Op::OpRot),
            "OP_SWAP" => Some(Op::OpSwap),
            "OP_TUCK" => Some(Op::OpTuck),
            "OP_CAT" => Some(Op::OpCat),
            "OP_SPLIT" => Some(Op::OpSplit),
            "OP_NUM2BIN" => Some(Op::OpNum2Bin),
            "OP_BIN2NUM" => Some(Op::OpBin2Num),
            "OP_SIZE" => Some(Op::OpSize),
            "OP_INVERT" => Some(Op::OpInvert),
            "OP_AND" => Some(Op::OpAnd),
            "OP_OR" => Some(Op::OpOr),
            "OP_XOR" => Some(Op::OpXor),
            "OP_EQUAL" => Some(Op::OpEqual),
            "OP_EQUALVERIFY" => Some(Op::OpEqualVerify),
            "OP_RESERVED1" => Some(Op::OpReserved1),
            "OP_RESERVED2" => Some(Op::OpReserved2),
            "OP_1ADD" => Some(Op::Op1Add),
            "OP_1SUB" => Some(Op::Op1Sub),
            "OP_2MUL" => Some(Op::Op2Mul),
            "OP_2DIV" => Some(Op::Op2Div),
            "OP_NEGATE" => Some(Op::OpNegate),
            "OP_ABS" => Some(Op::OpAbs),
            "OP_NOT" => Some(Op::OpNot),
            "OP_0NOTEQUAL" => Some(Op::Op0NotEqual),
            "OP_ADD" => Some(Op::OpAdd),
            "OP_SUB" => Some(Op::OpSub),
            "OP_MUL" => Some(Op::OpMul),
            "OP_DIV" => Some(Op::OpDiv),
            "OP_MOD" => Some(Op::OpMod),
            "OP_LSHIFT" => Some(Op::OpLShift),
            "OP_RSHIFT" => Some(Op::OpRShift),
            "OP_BOOLAND" => Some(Op::OpBoolAnd),
            "OP_BOOLOR" => Some(Op::OpBoolOr),
            "OP_NUMEQUAL" => Some(Op::OpNumEqual),
            "OP_NUMEQUALVERIFY" => Some(Op::OpNumEqualVerify),
            "OP_NUMNOTEQUAL" => Some(Op::OpNumNotEqual),
            "OP_LESSTHAN" => Some(Op::OpLessThan),
            "OP_GREATERTHAN" => Some(Op::OpGreaterThan),
            "OP_LESSTHANOREQUAL" => Some(Op::OpLessThanOrEqual),
            "OP_GREATERTHANOREQUAL" => Some(Op::OpGreaterThanOrEqual),
            "OP_MIN" => Some(Op::OpMin),
            "OP_MAX" => Some(Op::OpMax),
            "OP_WITHIN" => Some(Op::OpWithin),
            "OP_RIPEMD160" => Some(Op::OpRipemd160),
            "OP_SHA1" => Some(Op::OpSha1),
            "OP_SHA256" => Some(Op::OpSha256),
            "OP_HASH160" => Some(Op::OpHash160),
            "OP_HASH256" => Some(Op::OpHash256),
            "OP_CODESEPARATOR" => Some(Op::OpCodeSeparator),
            "OP_CHECKSIG" => Some(Op::OpCheckSig),
            "OP_CHECKSIGVERIFY" => Some(Op::OpCheckSigVerify),
            "OP_CHECKMULTISIG" => Some(Op::OpCheckMultiSig),
            "OP_CHECKMULTISIGVERIFY" => Some(Op::OpCheckMultiSigVerify),
            "OP_NOP1" => Some(Op::OpNop1),
            "OP_NOP2" => Some(Op::OpNop2),
            "OP_NOP3" => Some(Op::OpNop3),
            "OP_NOP4" | "OP_SUBSTR" => Some(Op::OpSubstr),
            "OP_NOP5" | "OP_LEFT" => Some(Op::OpLeft),
            "OP_NOP6" | "OP_RIGHT" => Some(Op::OpRight),
            "OP_NOP7" | "OP_LSHIFTNUM" => Some(Op::OpLShiftNum),
            "OP_NOP8" | "OP_RSHIFTNUM" => Some(Op::OpRShiftNum),
            "OP_INVALIDOPCODE" => Some(Op::OpInvalidOpcode),
            _ => {
                // Handle OP_NOP9..OP_NOP77 and template ops
                if let Some(suffix) = name.strip_prefix("OP_NOP") {
                    if let Ok(n) = suffix.parse::<u8>() {
                        return Some(Op::from(0xb8 + n.saturating_sub(9)));
                    }
                }
                None
            }
        }
    }
}

impl From<u8> for Op {
    fn from(byte: u8) -> Self {
        match byte {
            0x00 => Op::Op0,
            // Direct push opcodes 0x01..=0x4b are NOT enum variants;
            // they are handled by the parser as push-length indicators.
            // From<u8> maps them to OpInvalidOpcode (they should never
            // appear as standalone opcode values in ScriptChunk.op).
            0x4c => Op::OpPushData1,
            0x4d => Op::OpPushData2,
            0x4e => Op::OpPushData4,
            0x4f => Op::Op1Negate,
            0x50 => Op::OpReserved,
            0x51 => Op::Op1,
            0x52 => Op::Op2,
            0x53 => Op::Op3,
            0x54 => Op::Op4,
            0x55 => Op::Op5,
            0x56 => Op::Op6,
            0x57 => Op::Op7,
            0x58 => Op::Op8,
            0x59 => Op::Op9,
            0x5a => Op::Op10,
            0x5b => Op::Op11,
            0x5c => Op::Op12,
            0x5d => Op::Op13,
            0x5e => Op::Op14,
            0x5f => Op::Op15,
            0x60 => Op::Op16,
            0x61 => Op::OpNop,
            0x62 => Op::OpVer,
            0x63 => Op::OpIf,
            0x64 => Op::OpNotIf,
            0x65 => Op::OpVerIf,
            0x66 => Op::OpVerNotIf,
            0x67 => Op::OpElse,
            0x68 => Op::OpEndIf,
            0x69 => Op::OpVerify,
            0x6a => Op::OpReturn,
            0x6b => Op::OpToAltStack,
            0x6c => Op::OpFromAltStack,
            0x6d => Op::Op2Drop,
            0x6e => Op::Op2Dup,
            0x6f => Op::Op3Dup,
            0x70 => Op::Op2Over,
            0x71 => Op::Op2Rot,
            0x72 => Op::Op2Swap,
            0x73 => Op::OpIfDup,
            0x74 => Op::OpDepth,
            0x75 => Op::OpDrop,
            0x76 => Op::OpDup,
            0x77 => Op::OpNip,
            0x78 => Op::OpOver,
            0x79 => Op::OpPick,
            0x7a => Op::OpRoll,
            0x7b => Op::OpRot,
            0x7c => Op::OpSwap,
            0x7d => Op::OpTuck,
            0x7e => Op::OpCat,
            0x7f => Op::OpSplit,
            0x80 => Op::OpNum2Bin,
            0x81 => Op::OpBin2Num,
            0x82 => Op::OpSize,
            0x83 => Op::OpInvert,
            0x84 => Op::OpAnd,
            0x85 => Op::OpOr,
            0x86 => Op::OpXor,
            0x87 => Op::OpEqual,
            0x88 => Op::OpEqualVerify,
            0x89 => Op::OpReserved1,
            0x8a => Op::OpReserved2,
            0x8b => Op::Op1Add,
            0x8c => Op::Op1Sub,
            0x8d => Op::Op2Mul,
            0x8e => Op::Op2Div,
            0x8f => Op::OpNegate,
            0x90 => Op::OpAbs,
            0x91 => Op::OpNot,
            0x92 => Op::Op0NotEqual,
            0x93 => Op::OpAdd,
            0x94 => Op::OpSub,
            0x95 => Op::OpMul,
            0x96 => Op::OpDiv,
            0x97 => Op::OpMod,
            0x98 => Op::OpLShift,
            0x99 => Op::OpRShift,
            0x9a => Op::OpBoolAnd,
            0x9b => Op::OpBoolOr,
            0x9c => Op::OpNumEqual,
            0x9d => Op::OpNumEqualVerify,
            0x9e => Op::OpNumNotEqual,
            0x9f => Op::OpLessThan,
            0xa0 => Op::OpGreaterThan,
            0xa1 => Op::OpLessThanOrEqual,
            0xa2 => Op::OpGreaterThanOrEqual,
            0xa3 => Op::OpMin,
            0xa4 => Op::OpMax,
            0xa5 => Op::OpWithin,
            0xa6 => Op::OpRipemd160,
            0xa7 => Op::OpSha1,
            0xa8 => Op::OpSha256,
            0xa9 => Op::OpHash160,
            0xaa => Op::OpHash256,
            0xab => Op::OpCodeSeparator,
            0xac => Op::OpCheckSig,
            0xad => Op::OpCheckSigVerify,
            0xae => Op::OpCheckMultiSig,
            0xaf => Op::OpCheckMultiSigVerify,
            0xb0 => Op::OpNop1,
            0xb1 => Op::OpNop2,
            0xb2 => Op::OpNop3,
            0xb3 => Op::OpSubstr,
            0xb4 => Op::OpLeft,
            0xb5 => Op::OpRight,
            0xb6 => Op::OpLShiftNum,
            0xb7 => Op::OpRShiftNum,
            0xb8 => Op::OpNop9,
            0xb9 => Op::OpNop10,
            0xba => Op::OpNop11,
            0xbb => Op::OpNop12,
            0xbc => Op::OpNop13,
            0xbd => Op::OpNop14,
            0xbe => Op::OpNop15,
            0xbf => Op::OpNop16,
            0xc0 => Op::OpNop17,
            0xc1 => Op::OpNop18,
            0xc2 => Op::OpNop19,
            0xc3 => Op::OpNop20,
            0xc4 => Op::OpNop21,
            0xc5 => Op::OpNop22,
            0xc6 => Op::OpNop23,
            0xc7 => Op::OpNop24,
            0xc8 => Op::OpNop25,
            0xc9 => Op::OpNop26,
            0xca => Op::OpNop27,
            0xcb => Op::OpNop28,
            0xcc => Op::OpNop29,
            0xcd => Op::OpNop30,
            0xce => Op::OpNop31,
            0xcf => Op::OpNop32,
            0xd0 => Op::OpNop33,
            0xd1 => Op::OpNop34,
            0xd2 => Op::OpNop35,
            0xd3 => Op::OpNop36,
            0xd4 => Op::OpNop37,
            0xd5 => Op::OpNop38,
            0xd6 => Op::OpNop39,
            0xd7 => Op::OpNop40,
            0xd8 => Op::OpNop41,
            0xd9 => Op::OpNop42,
            0xda => Op::OpNop43,
            0xdb => Op::OpNop44,
            0xdc => Op::OpNop45,
            0xdd => Op::OpNop46,
            0xde => Op::OpNop47,
            0xdf => Op::OpNop48,
            0xe0 => Op::OpNop49,
            0xe1 => Op::OpNop50,
            0xe2 => Op::OpNop51,
            0xe3 => Op::OpNop52,
            0xe4 => Op::OpNop53,
            0xe5 => Op::OpNop54,
            0xe6 => Op::OpNop55,
            0xe7 => Op::OpNop56,
            0xe8 => Op::OpNop57,
            0xe9 => Op::OpNop58,
            0xea => Op::OpNop59,
            0xeb => Op::OpNop60,
            0xec => Op::OpNop61,
            0xed => Op::OpNop62,
            0xee => Op::OpNop63,
            0xef => Op::OpNop64,
            0xf0 => Op::OpNop65,
            0xf1 => Op::OpNop66,
            0xf2 => Op::OpNop67,
            0xf3 => Op::OpNop68,
            0xf4 => Op::OpNop69,
            0xf5 => Op::OpNop70,
            0xf6 => Op::OpNop71,
            0xf7 => Op::OpNop72,
            0xf8 => Op::OpNop73,
            0xf9 => Op::OpSmallData,
            0xfa => Op::OpSmallInteger,
            0xfb => Op::OpPubKeys,
            0xfc => Op::OpNop77,
            0xfd => Op::OpPubKeyHash,
            0xfe => Op::OpPubKey,
            0xff => Op::OpInvalidOpcode,
            // Direct-push bytes (0x01..=0x4b) are not standalone opcodes.
            // Unknown / gap bytes map to OpInvalidOpcode.
            _ => Op::OpInvalidOpcode,
        }
    }
}

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

    #[test]
    fn test_from_u8_roundtrip_known_opcodes() {
        // Test that all known enum values survive the From<u8> roundtrip
        let known: Vec<(u8, Op)> = vec![
            (0x00, Op::Op0),
            (0x4c, Op::OpPushData1),
            (0x4d, Op::OpPushData2),
            (0x4e, Op::OpPushData4),
            (0x4f, Op::Op1Negate),
            (0x50, Op::OpReserved),
            (0x51, Op::Op1),
            (0x60, Op::Op16),
            (0x61, Op::OpNop),
            (0x63, Op::OpIf),
            (0x68, Op::OpEndIf),
            (0x6a, Op::OpReturn),
            (0x76, Op::OpDup),
            (0x7e, Op::OpCat),
            (0x7f, Op::OpSplit),
            (0x80, Op::OpNum2Bin),
            (0x81, Op::OpBin2Num),
            (0x87, Op::OpEqual),
            (0x88, Op::OpEqualVerify),
            (0x93, Op::OpAdd),
            (0x95, Op::OpMul),
            (0x96, Op::OpDiv),
            (0x97, Op::OpMod),
            (0x98, Op::OpLShift),
            (0x99, Op::OpRShift),
            (0xa9, Op::OpHash160),
            (0xac, Op::OpCheckSig),
            (0xae, Op::OpCheckMultiSig),
            (0xb0, Op::OpNop1),
            (0xb3, Op::OpSubstr),
            (0xb4, Op::OpLeft),
            (0xb5, Op::OpRight),
            (0xb6, Op::OpLShiftNum),
            (0xb7, Op::OpRShiftNum),
            (0xff, Op::OpInvalidOpcode),
        ];

        for (byte, expected) in known {
            let op = Op::from(byte);
            assert_eq!(op, expected, "From<u8> for byte {:#04x} failed", byte);
            assert_eq!(op.to_byte(), byte, "to_byte for {:#04x} failed", byte);
        }
    }

    #[test]
    fn test_unknown_bytes_map_to_invalid() {
        // Bytes 0x01..=0x4b are direct-push, should map to OpInvalidOpcode
        for byte in 0x01..=0x4bu8 {
            assert_eq!(
                Op::from(byte),
                Op::OpInvalidOpcode,
                "Direct-push byte {:#04x} should map to OpInvalidOpcode",
                byte
            );
        }
    }

    #[test]
    fn test_to_name_common_opcodes() {
        assert_eq!(Op::Op0.to_name(), "OP_0");
        assert_eq!(Op::OpDup.to_name(), "OP_DUP");
        assert_eq!(Op::OpCheckSig.to_name(), "OP_CHECKSIG");
        assert_eq!(Op::OpReturn.to_name(), "OP_RETURN");
        assert_eq!(Op::OpHash160.to_name(), "OP_HASH160");
        assert_eq!(Op::OpEqualVerify.to_name(), "OP_EQUALVERIFY");
        assert_eq!(Op::Op1.to_name(), "OP_1");
        assert_eq!(Op::Op16.to_name(), "OP_16");
        assert_eq!(Op::OpCat.to_name(), "OP_CAT");
        assert_eq!(Op::OpMul.to_name(), "OP_MUL");
        assert_eq!(Op::OpSubstr.to_name(), "OP_SUBSTR");
        assert_eq!(Op::OpInvalidOpcode.to_name(), "OP_INVALIDOPCODE");
    }

    #[test]
    fn test_from_name_roundtrip() {
        let names = [
            "OP_0",
            "OP_DUP",
            "OP_CHECKSIG",
            "OP_RETURN",
            "OP_HASH160",
            "OP_EQUALVERIFY",
            "OP_1",
            "OP_16",
            "OP_CAT",
            "OP_MUL",
            "OP_SUBSTR",
            "OP_INVALIDOPCODE",
            "OP_FALSE",
            "OP_TRUE",
        ];
        for name in &names {
            let op = Op::from_name(name).unwrap_or_else(|| panic!("from_name failed for {}", name));
            // For aliases, the canonical name may differ
            let canonical = op.to_name();
            let op2 = Op::from_name(canonical)
                .unwrap_or_else(|| panic!("from_name failed for canonical {}", canonical));
            assert_eq!(op, op2);
        }
    }

    #[test]
    fn test_all_nop_variants_exist() {
        // NOP9 through NOP73 should all be valid
        for byte in 0xb8..=0xf8u8 {
            let op = Op::from(byte);
            assert_ne!(
                op,
                Op::OpInvalidOpcode,
                "Byte {:#04x} should not be OpInvalidOpcode",
                byte
            );
        }
    }

    #[test]
    fn test_opcode_count() {
        // Count all unique opcodes by scanning byte range
        let mut count = 0u32;
        for byte in 0x00..=0xffu16 {
            let op = Op::from(byte as u8);
            if op != Op::OpInvalidOpcode || byte == 0xff {
                count += 1;
            }
        }
        // 0x00, 0x4c..=0xff minus the 0x01..=0x4b gap = 1 + (0xff - 0x4c + 1) = 1 + 180 = 181
        // But many of those are nop/known, and gap bytes are invalid.
        // Let's just verify it's at least 130 (the ~180 named opcodes minus direct-push gap)
        assert!(
            count >= 130,
            "Expected at least 130 named opcodes, got {}",
            count
        );
    }
}
