//! 32-bit ARM architecture support (code-generated from ARM ISA XML).

#[allow(dead_code, non_snake_case, clippy::all)]
mod a32 {
    include!(concat!(env!("OUT_DIR"), "/arm_a32.rs"));
}
use a32::*;

use crate::{
    arch::{Arch, LiftArch, LiftEnv, RegisterDesc},
    flow::{FlowInfo, FlowKind, FlowTarget},
    ir::{Block, FrontendBuilder, IntCC, MemSize, Type, Value, Variable},
};
use cranelift_entity::EntityRef;

/// ARM execution mode.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum ArmMode {
    Arm,
    Thumb,
}

/// ARM endianness.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum ArmEndian {
    Little,
    Big,
}

/// ARM architectural profile.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum ArmProfile {
    A,
    R,
    M,
}

/// ARM ISA version / feature profile.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ArmFeatures {
    pub version: ArmVersion,
    pub thumb2: bool,
    pub vfp: bool,
    pub neon: bool,
    pub dsp: bool,
}

impl Default for ArmFeatures {
    fn default() -> Self {
        Self {
            version: ArmVersion::V7,
            thumb2: true,
            vfp: false,
            neon: false,
            dsp: false,
        }
    }
}

/// ARM ISA version.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum ArmVersion {
    V5,
    V6,
    V7,
}

/// Flat ARM architectural register identity.
#[repr(u8)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum ArmReg {
    R0 = 0,
    R1 = 1,
    R2 = 2,
    R3 = 3,
    R4 = 4,
    R5 = 5,
    R6 = 6,
    R7 = 7,
    R8 = 8,
    R9 = 9,
    R10 = 10,
    R11 = 11,
    R12 = 12,
    Sp = 13,
    Lr = 14,
    Pc = 15,
}

/// ARM status flag identity.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum ArmFlag {
    N,
    Z,
    C,
    V,
    Q,
}

/// Calling-convention placeholder.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum ArmCallingConvention {
    Aapcs,
    AapcsVfp,
}

/// Reusable ARM architecture object.
#[derive(Clone, Debug)]
pub struct Arm {
    pub endian: ArmEndian,
    pub profile: ArmProfile,
    pub features: ArmFeatures,
    pub default_cc: ArmCallingConvention,
}

impl Default for Arm {
    fn default() -> Self {
        Self {
            endian: ArmEndian::Little,
            profile: ArmProfile::A,
            features: ArmFeatures::default(),
            default_cc: ArmCallingConvention::Aapcs,
        }
    }
}

const FLAG_VAR_BASE: usize = 0x100;

pub struct ArmLiftCtx<'ir, 'fb> {
    pub arch: &'ir Arm,
    pub env: &'ir dyn LiftEnv,
    pub fb: &'fb mut FrontendBuilder<'ir>,
    pub pc: u64,
    pub mode: ArmMode,
}

impl<'ir, 'fb> ArmLiftCtx<'ir, 'fb> {
    pub fn new(
        arch: &'ir Arm,
        env: &'ir dyn LiftEnv,
        fb: &'fb mut FrontendBuilder<'ir>,
        pc: u64,
        mode: ArmMode,
    ) -> Self {
        let word_ty = Type::i32();
        for reg in REGISTERS {
            fb.declare_var(Self::reg_var(reg.reg), word_ty);
        }
        for flag in [ArmFlag::N, ArmFlag::Z, ArmFlag::C, ArmFlag::V, ArmFlag::Q] {
            fb.declare_var(Self::flag_var(flag), Type::bool());
        }
        Self {
            arch,
            env,
            fb,
            pc,
            mode,
        }
    }

    pub fn word_ty(&self) -> Type {
        Type::i32()
    }

    pub fn reg_var(reg: ArmReg) -> Variable {
        Variable::new(reg as usize)
    }

    pub fn flag_var(flag: ArmFlag) -> Variable {
        Variable::new(FLAG_VAR_BASE + flag as usize)
    }

    pub fn read_reg(&mut self, reg: ArmReg) -> Value {
        self.fb.use_var(Self::reg_var(reg))
    }

    pub fn write_reg(&mut self, reg: ArmReg, value: Value) {
        self.fb.def_var(Self::reg_var(reg), value);
    }

    pub fn read_flag(&mut self, flag: ArmFlag) -> Value {
        self.fb.use_var(Self::flag_var(flag))
    }

    pub fn write_flag(&mut self, flag: ArmFlag, value: Value) {
        self.fb.def_var(Self::flag_var(flag), value);
    }

    pub fn direct_block(&self, target_addr: u64) -> Option<Block> {
        self.env.block_for_target(target_addr)
    }

    pub fn fallthrough_block(&self) -> Option<Block> {
        self.env.fallthrough_block()
    }
}

pub const REGISTERS: [RegisterDesc<ArmReg>; 16] = [
    RegisterDesc {
        reg: ArmReg::R0,
        name: "r0",
        bits: 32,
    },
    RegisterDesc {
        reg: ArmReg::R1,
        name: "r1",
        bits: 32,
    },
    RegisterDesc {
        reg: ArmReg::R2,
        name: "r2",
        bits: 32,
    },
    RegisterDesc {
        reg: ArmReg::R3,
        name: "r3",
        bits: 32,
    },
    RegisterDesc {
        reg: ArmReg::R4,
        name: "r4",
        bits: 32,
    },
    RegisterDesc {
        reg: ArmReg::R5,
        name: "r5",
        bits: 32,
    },
    RegisterDesc {
        reg: ArmReg::R6,
        name: "r6",
        bits: 32,
    },
    RegisterDesc {
        reg: ArmReg::R7,
        name: "r7",
        bits: 32,
    },
    RegisterDesc {
        reg: ArmReg::R8,
        name: "r8",
        bits: 32,
    },
    RegisterDesc {
        reg: ArmReg::R9,
        name: "r9",
        bits: 32,
    },
    RegisterDesc {
        reg: ArmReg::R10,
        name: "r10",
        bits: 32,
    },
    RegisterDesc {
        reg: ArmReg::R11,
        name: "r11",
        bits: 32,
    },
    RegisterDesc {
        reg: ArmReg::R12,
        name: "r12",
        bits: 32,
    },
    RegisterDesc {
        reg: ArmReg::Sp,
        name: "sp",
        bits: 32,
    },
    RegisterDesc {
        reg: ArmReg::Lr,
        name: "lr",
        bits: 32,
    },
    RegisterDesc {
        reg: ArmReg::Pc,
        name: "pc",
        bits: 32,
    },
];

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ArmLiftError {
    UnsupportedInstruction,
    UnsupportedCondition,
    UnsupportedAddressing,
    MissingTargetBlock,
    UnsupportedControlFlow,
}

// ---------------------------------------------------------------------------
// Helpers shared by Arch impl and lifter
// ---------------------------------------------------------------------------

/// Map a 4-bit register index to `ArmReg`.
const fn reg_of(r: u8) -> ArmReg {
    match r & 0xF {
        0 => ArmReg::R0,
        1 => ArmReg::R1,
        2 => ArmReg::R2,
        3 => ArmReg::R3,
        4 => ArmReg::R4,
        5 => ArmReg::R5,
        6 => ArmReg::R6,
        7 => ArmReg::R7,
        8 => ArmReg::R8,
        9 => ArmReg::R9,
        10 => ArmReg::R10,
        11 => ArmReg::R11,
        12 => ArmReg::R12,
        13 => ArmReg::Sp,
        14 => ArmReg::Lr,
        _ => ArmReg::Pc,
    }
}

/// Compute a boolean condition value from a 4-bit ARM condition code.
/// Returns `Ok(None)` for AL (always / unconditional).
fn cond_value(cx: &mut ArmLiftCtx<'_, '_>, cond: u8) -> Result<Option<Value>, ArmLiftError> {
    match cond & 0xF {
        0xE => Ok(None), // AL

        0x0 => Ok(Some(cx.read_flag(ArmFlag::Z))), // EQ: Z=1

        0x1 => {
            // NE: Z=0
            let z = cx.read_flag(ArmFlag::Z);
            Ok(Some(cx.fb.not(z)))
        }

        0xB => {
            // LT: N != V — placeholder using N alone
            Ok(Some(cx.read_flag(ArmFlag::N)))
        }

        0xA => {
            // GE: N == V — placeholder using !N alone
            let n = cx.read_flag(ArmFlag::N);
            Ok(Some(cx.fb.not(n)))
        }

        0xC => {
            // GT: Z=0 and N==V — placeholder using Z=0 and N=0
            let z = cx.read_flag(ArmFlag::Z);
            let n = cx.read_flag(ArmFlag::N);
            let not_z = cx.fb.not(z);
            let not_n = cx.fb.not(n);
            Ok(Some(cx.fb.and(not_z, not_n, Type::bool())))
        }

        0xD => {
            // LE: Z=1 or N!=V — placeholder using Z=1 or N=1
            let z = cx.read_flag(ArmFlag::Z);
            let n = cx.read_flag(ArmFlag::N);
            Ok(Some(cx.fb.or(z, n, Type::bool())))
        }

        // C and V flags not yet modelled
        _ => Err(ArmLiftError::UnsupportedCondition),
    }
}

/// Apply an immediate shift to a register value.
fn apply_shift_imm(
    cx: &mut ArmLiftCtx<'_, '_>,
    base: Value,
    stype: u8,
    imm5: u8,
) -> Result<Value, ArmLiftError> {
    if imm5 == 0 && stype == 0 {
        return Ok(base); // LSL #0 = identity
    }
    let amt = cx.fb.iconst(Type::i32(), imm5 as i64);
    Ok(match stype & 3 {
        0 => cx.fb.shl(base, amt, Type::i32()),                // LSL
        1 => cx.fb.lshr(base, amt, Type::i32()),               // LSR
        2 => cx.fb.ashr(base, amt, Type::i32()),               // ASR
        _ => return Err(ArmLiftError::UnsupportedInstruction), // ROR
    })
}

/// Compute the effective address for an offset-form LDR/STR: base ± imm12.
fn addr_imm_off(cx: &mut ArmLiftCtx<'_, '_>, rn: u8, u: bool, imm12: u16) -> Value {
    let base = cx.read_reg(reg_of(rn));
    if imm12 == 0 {
        return base;
    }
    let off = cx.fb.iconst(Type::i32(), imm12 as i64);
    if u {
        cx.fb.iadd(base, off, Type::i32())
    } else {
        cx.fb.isub(base, off, Type::i32())
    }
}

/// Compute the effective address for an offset-form LDR/STR: base ± shifted rm.
fn addr_reg_off(
    cx: &mut ArmLiftCtx<'_, '_>,
    rn: u8,
    u: bool,
    rm: u8,
    stype: u8,
    imm5: u8,
) -> Result<Value, ArmLiftError> {
    let base = cx.read_reg(reg_of(rn));
    let idx_raw = cx.read_reg(reg_of(rm));
    let idx = apply_shift_imm(cx, idx_raw, stype, imm5)?;
    Ok(if u {
        cx.fb.iadd(base, idx, Type::i32())
    } else {
        cx.fb.isub(base, idx, Type::i32())
    })
}

// ---------------------------------------------------------------------------
// Arch impl
// ---------------------------------------------------------------------------

impl Arch for Arm {
    type Mode = ArmMode;
    type Inst = A32Inst;
    type Reg = ArmReg;
    type DecodeError = A32DecodeError;

    fn name(&self) -> &'static str {
        "arm"
    }
    fn address_size(&self, _mode: Self::Mode) -> u8 {
        4
    }
    fn max_instruction_len(&self) -> u8 {
        4
    }
    fn registers(&self) -> &'static [RegisterDesc<Self::Reg>] {
        &REGISTERS
    }
    fn stack_pointer(&self) -> Option<Self::Reg> {
        Some(ArmReg::Sp)
    }

    fn decode(
        &self,
        bytes: &[u8],
        mode: Self::Mode,
    ) -> Result<(usize, Self::Inst), Self::DecodeError> {
        match mode {
            ArmMode::Arm => decode_a32(bytes),
            ArmMode::Thumb => Err(A32DecodeError::Unknown),
        }
    }

    fn flow_info(&self, inst: &Self::Inst, pc: u64, _mode: Self::Mode) -> FlowInfo {
        // PC-relative branch offset: target = PC + 8 + (imm24 sign-extended << 2)
        let branch_target = |imm24: u32| -> u64 {
            pc.wrapping_add(8)
                .wrapping_add_signed(a32_branch_offset(imm24) as i64)
        };

        match inst {
            A32Inst::BxA1 { rm, .. } => {
                if *rm == 14 {
                    FlowInfo::new(4, FlowKind::Return)
                } else {
                    FlowInfo::new(
                        4,
                        FlowKind::Jump {
                            target: FlowTarget::Indirect,
                        },
                    )
                }
            }

            A32Inst::BlxRA1 { .. } => FlowInfo::new(
                4,
                FlowKind::Call {
                    target: FlowTarget::Indirect,
                    returns: true,
                },
            ),

            A32Inst::BA1 { cond, imm24 } => {
                let target = FlowTarget::Direct(branch_target(*imm24));
                if *cond == 0xE {
                    FlowInfo::new(4, FlowKind::Jump { target })
                } else {
                    FlowInfo::new(4, FlowKind::CondJump { target })
                }
            }

            A32Inst::BlIA1 { imm24, .. } => {
                let target = FlowTarget::Direct(branch_target(*imm24));
                FlowInfo::new(
                    4,
                    FlowKind::Call {
                        target,
                        returns: true,
                    },
                )
            }

            _ => FlowInfo::new(4, FlowKind::FallThrough),
        }
    }

    fn disasm(&self, inst: &Self::Inst, _pc: u64, _mode: Self::Mode) -> String {
        format!("{inst}")
    }
}

// ---------------------------------------------------------------------------
// LiftArch impl
// ---------------------------------------------------------------------------

impl LiftArch for Arm {
    type LiftError = ArmLiftError;
    type LiftCtx<'ir, 'fb>
        = ArmLiftCtx<'ir, 'fb>
    where
        Self: 'ir,
        'ir: 'fb;

    fn lift(
        &self,
        cx: &mut Self::LiftCtx<'_, '_>,
        inst: &Self::Inst,
    ) -> Result<(), Self::LiftError> {
        lift_inst(cx, inst)
    }
}

fn lift_inst(cx: &mut ArmLiftCtx<'_, '_>, inst: &A32Inst) -> Result<(), ArmLiftError> {
    match inst {
        // ---- NOP ----------------------------------------------------------------
        A32Inst::NopA1 { .. } => Ok(()),

        // ---- MOV immediate ------------------------------------------------------
        A32Inst::MovIA1 { cond, rd, imm12 } | A32Inst::MovsIA1 { cond, rd, imm12 } => {
            if cond_value(cx, *cond)?.is_some() {
                return Err(ArmLiftError::UnsupportedCondition);
            }
            let expanded = a32_expand_imm12(*imm12 as u32);
            let v = cx.fb.iconst(Type::i32(), expanded as i64);
            cx.write_reg(reg_of(*rd), v);
            Ok(())
        }

        // MOV wide immediate (MOVW)
        A32Inst::MovIA2 {
            cond,
            rd,
            imm12,
            imm4,
        } => {
            if cond_value(cx, *cond)?.is_some() {
                return Err(ArmLiftError::UnsupportedCondition);
            }
            let imm16 = ((*imm4 as u32) << 12) | (*imm12 as u32);
            let v = cx.fb.iconst(Type::i32(), imm16 as i64);
            cx.write_reg(reg_of(*rd), v);
            Ok(())
        }

        // ---- MOV register -------------------------------------------------------
        A32Inst::MovRA1 {
            cond,
            rd,
            stype,
            rm,
        }
        | A32Inst::MovsRA1 {
            cond,
            rd,
            stype,
            rm,
        } => {
            if cond_value(cx, *cond)?.is_some() {
                return Err(ArmLiftError::UnsupportedCondition);
            }
            let base = cx.read_reg(reg_of(*rm));
            // No imm5 in these variants — shift amount is 0 (LSL #0 = identity)
            let v = apply_shift_imm(cx, base, *stype, 0)?;
            cx.write_reg(reg_of(*rd), v);
            Ok(())
        }

        // ---- ADD register -------------------------------------------------------
        A32Inst::AddRA1 {
            cond,
            rn,
            rd,
            stype,
            rm,
        }
        | A32Inst::AddsRA1 {
            cond,
            rn,
            rd,
            stype,
            rm,
        } => {
            if cond_value(cx, *cond)?.is_some() {
                return Err(ArmLiftError::UnsupportedCondition);
            }
            let lhs = cx.read_reg(reg_of(*rn));
            let rhs_raw = cx.read_reg(reg_of(*rm));
            let rhs = apply_shift_imm(cx, rhs_raw, *stype, 0)?;
            let out = cx.fb.iadd(lhs, rhs, Type::i32());
            cx.write_reg(reg_of(*rd), out);
            Ok(())
        }

        // ---- SUB register -------------------------------------------------------
        A32Inst::SubRA1 {
            cond,
            rn,
            rd,
            stype,
            rm,
        }
        | A32Inst::SubsRA1 {
            cond,
            rn,
            rd,
            stype,
            rm,
        } => {
            if cond_value(cx, *cond)?.is_some() {
                return Err(ArmLiftError::UnsupportedCondition);
            }
            let lhs = cx.read_reg(reg_of(*rn));
            let rhs_raw = cx.read_reg(reg_of(*rm));
            let rhs = apply_shift_imm(cx, rhs_raw, *stype, 0)?;
            let out = cx.fb.isub(lhs, rhs, Type::i32());
            cx.write_reg(reg_of(*rd), out);
            Ok(())
        }

        // ---- CMP register -------------------------------------------------------
        A32Inst::CmpRA1 {
            cond,
            rn,
            stype,
            rm,
        } => {
            if cond_value(cx, *cond)?.is_some() {
                return Err(ArmLiftError::UnsupportedCondition);
            }
            let lhs = cx.read_reg(reg_of(*rn));
            let rhs_raw = cx.read_reg(reg_of(*rm));
            let rhs = apply_shift_imm(cx, rhs_raw, *stype, 0)?;
            let diff = cx.fb.isub(lhs, rhs, Type::i32());
            let zero = cx.fb.iconst(Type::i32(), 0);
            let z = cx.fb.icmp(IntCC::Eq, diff, zero);
            let n = cx.fb.icmp(IntCC::Slt, diff, zero);
            cx.write_flag(ArmFlag::Z, z);
            cx.write_flag(ArmFlag::N, n);
            Ok(())
        }

        // ---- CMP immediate ------------------------------------------------------
        A32Inst::CmpIA1 { cond, rn, imm12 } => {
            if cond_value(cx, *cond)?.is_some() {
                return Err(ArmLiftError::UnsupportedCondition);
            }
            let lhs = cx.read_reg(reg_of(*rn));
            let expanded = a32_expand_imm12(*imm12 as u32);
            let rhs = cx.fb.iconst(Type::i32(), expanded as i64);
            let diff = cx.fb.isub(lhs, rhs, Type::i32());
            let zero = cx.fb.iconst(Type::i32(), 0);
            let z = cx.fb.icmp(IntCC::Eq, diff, zero);
            let n = cx.fb.icmp(IntCC::Slt, diff, zero);
            cx.write_flag(ArmFlag::Z, z);
            cx.write_flag(ArmFlag::N, n);
            Ok(())
        }

        // ---- LDR immediate (offset / pre-index / post-index) --------------------
        A32Inst::LdrIA1Off {
            cond,
            u,
            rn,
            rt,
            imm12,
        } => {
            if cond_value(cx, *cond)?.is_some() {
                return Err(ArmLiftError::UnsupportedCondition);
            }
            let addr = addr_imm_off(cx, *rn, *u, *imm12);
            let v = cx.fb.load(addr, MemSize::S4, Type::i32());
            cx.write_reg(reg_of(*rt), v);
            Ok(())
        }

        A32Inst::LdrIA1Pre {
            cond,
            u,
            rn,
            rt,
            imm12,
        } => {
            if cond_value(cx, *cond)?.is_some() {
                return Err(ArmLiftError::UnsupportedCondition);
            }
            let addr = addr_imm_off(cx, *rn, *u, *imm12);
            cx.write_reg(reg_of(*rn), addr);
            let v = cx.fb.load(addr, MemSize::S4, Type::i32());
            cx.write_reg(reg_of(*rt), v);
            Ok(())
        }

        A32Inst::LdrIA1Post {
            cond,
            u,
            rn,
            rt,
            imm12,
        } => {
            if cond_value(cx, *cond)?.is_some() {
                return Err(ArmLiftError::UnsupportedCondition);
            }
            let base = cx.read_reg(reg_of(*rn));
            let v = cx.fb.load(base, MemSize::S4, Type::i32());
            cx.write_reg(reg_of(*rt), v);
            let updated = addr_imm_off(cx, *rn, *u, *imm12);
            cx.write_reg(reg_of(*rn), updated);
            Ok(())
        }

        // ---- LDR register (offset / pre-index / post-index) ---------------------
        A32Inst::LdrRA1Off {
            cond,
            u,
            rn,
            rt,
            imm5,
            stype,
            rm,
        } => {
            if cond_value(cx, *cond)?.is_some() {
                return Err(ArmLiftError::UnsupportedCondition);
            }
            let addr = addr_reg_off(cx, *rn, *u, *rm, *stype, *imm5)?;
            let v = cx.fb.load(addr, MemSize::S4, Type::i32());
            cx.write_reg(reg_of(*rt), v);
            Ok(())
        }

        A32Inst::LdrRA1Pre {
            cond,
            u,
            rn,
            rt,
            imm5,
            stype,
            rm,
        } => {
            if cond_value(cx, *cond)?.is_some() {
                return Err(ArmLiftError::UnsupportedCondition);
            }
            let addr = addr_reg_off(cx, *rn, *u, *rm, *stype, *imm5)?;
            cx.write_reg(reg_of(*rn), addr);
            let v = cx.fb.load(addr, MemSize::S4, Type::i32());
            cx.write_reg(reg_of(*rt), v);
            Ok(())
        }

        A32Inst::LdrRA1Post {
            cond,
            u,
            rn,
            rt,
            imm5,
            stype,
            rm,
        } => {
            if cond_value(cx, *cond)?.is_some() {
                return Err(ArmLiftError::UnsupportedCondition);
            }
            let base = cx.read_reg(reg_of(*rn));
            let v = cx.fb.load(base, MemSize::S4, Type::i32());
            cx.write_reg(reg_of(*rt), v);
            let updated = addr_reg_off(cx, *rn, *u, *rm, *stype, *imm5)?;
            cx.write_reg(reg_of(*rn), updated);
            Ok(())
        }

        // ---- STR immediate ------------------------------------------------------
        A32Inst::StrIA1Off {
            cond,
            u,
            rn,
            rt,
            imm12,
        } => {
            if cond_value(cx, *cond)?.is_some() {
                return Err(ArmLiftError::UnsupportedCondition);
            }
            let addr = addr_imm_off(cx, *rn, *u, *imm12);
            let v = cx.read_reg(reg_of(*rt));
            cx.fb.store(addr, v, MemSize::S4);
            Ok(())
        }

        A32Inst::StrIA1Pre {
            cond,
            u,
            rn,
            rt,
            imm12,
        } => {
            if cond_value(cx, *cond)?.is_some() {
                return Err(ArmLiftError::UnsupportedCondition);
            }
            let addr = addr_imm_off(cx, *rn, *u, *imm12);
            cx.write_reg(reg_of(*rn), addr);
            let v = cx.read_reg(reg_of(*rt));
            cx.fb.store(addr, v, MemSize::S4);
            Ok(())
        }

        A32Inst::StrIA1Post {
            cond,
            u,
            rn,
            rt,
            imm12,
        } => {
            if cond_value(cx, *cond)?.is_some() {
                return Err(ArmLiftError::UnsupportedCondition);
            }
            let base = cx.read_reg(reg_of(*rn));
            let v = cx.read_reg(reg_of(*rt));
            cx.fb.store(base, v, MemSize::S4);
            let updated = addr_imm_off(cx, *rn, *u, *imm12);
            cx.write_reg(reg_of(*rn), updated);
            Ok(())
        }

        // ---- STR register -------------------------------------------------------
        A32Inst::StrRA1Off {
            cond,
            u,
            rn,
            rt,
            imm5,
            stype,
            rm,
        } => {
            if cond_value(cx, *cond)?.is_some() {
                return Err(ArmLiftError::UnsupportedCondition);
            }
            let addr = addr_reg_off(cx, *rn, *u, *rm, *stype, *imm5)?;
            let v = cx.read_reg(reg_of(*rt));
            cx.fb.store(addr, v, MemSize::S4);
            Ok(())
        }

        A32Inst::StrRA1Pre {
            cond,
            u,
            rn,
            rt,
            imm5,
            stype,
            rm,
        } => {
            if cond_value(cx, *cond)?.is_some() {
                return Err(ArmLiftError::UnsupportedCondition);
            }
            let addr = addr_reg_off(cx, *rn, *u, *rm, *stype, *imm5)?;
            cx.write_reg(reg_of(*rn), addr);
            let v = cx.read_reg(reg_of(*rt));
            cx.fb.store(addr, v, MemSize::S4);
            Ok(())
        }

        A32Inst::StrRA1Post {
            cond,
            u,
            rn,
            rt,
            imm5,
            stype,
            rm,
        } => {
            if cond_value(cx, *cond)?.is_some() {
                return Err(ArmLiftError::UnsupportedCondition);
            }
            let base = cx.read_reg(reg_of(*rn));
            let v = cx.read_reg(reg_of(*rt));
            cx.fb.store(base, v, MemSize::S4);
            let updated = addr_reg_off(cx, *rn, *u, *rm, *stype, *imm5)?;
            cx.write_reg(reg_of(*rn), updated);
            Ok(())
        }

        // ---- B ------------------------------------------------------------------
        A32Inst::BA1 { cond, imm24 } => {
            let target_addr = cx
                .pc
                .wrapping_add(8)
                .wrapping_add_signed(a32_branch_offset(*imm24) as i64);
            let target_block = cx
                .direct_block(target_addr)
                .ok_or(ArmLiftError::MissingTargetBlock)?;

            match cond_value(cx, *cond)? {
                None => {
                    cx.fb.jump(target_block);
                    Ok(())
                }
                Some(c) => {
                    let fallthrough = cx
                        .fallthrough_block()
                        .ok_or(ArmLiftError::MissingTargetBlock)?;
                    cx.fb.br_if(c, target_block, fallthrough);
                    Ok(())
                }
            }
        }

        // ---- BL -----------------------------------------------------------------
        A32Inst::BlIA1 { cond, imm24 } => {
            if cond_value(cx, *cond)?.is_some() {
                return Err(ArmLiftError::UnsupportedCondition);
            }
            let target_addr = cx
                .pc
                .wrapping_add(8)
                .wrapping_add_signed(a32_branch_offset(*imm24) as i64);
            let callee = cx.fb.iconst(Type::ptr(32), target_addr as i64);
            cx.fb.call(callee, &[]);
            Ok(())
        }

        // ---- BLX register -------------------------------------------------------
        A32Inst::BlxRA1 { cond, rm } => {
            if cond_value(cx, *cond)?.is_some() {
                return Err(ArmLiftError::UnsupportedCondition);
            }
            let callee = cx.read_reg(reg_of(*rm));
            cx.fb.call(callee, &[]);
            Ok(())
        }

        // ---- BX -----------------------------------------------------------------
        A32Inst::BxA1 { cond, rm } => {
            if cond_value(cx, *cond)?.is_some() {
                return Err(ArmLiftError::UnsupportedCondition);
            }
            if *rm == 14 {
                let ret0 = cx.read_reg(ArmReg::R0);
                cx.fb.ret(&[ret0]);
                Ok(())
            } else {
                Err(ArmLiftError::UnsupportedControlFlow)
            }
        }

        _ => Err(ArmLiftError::UnsupportedInstruction),
    }
}

pub struct DummyEnv;

impl LiftEnv for DummyEnv {
    fn block_for_target(&self, _addr: u64) -> Option<Block> {
        None
    }
    fn fallthrough_block(&self) -> Option<Block> {
        None
    }
}
