// Compiled twice via Cargo.toml [[test]] entries — once in async mode (default, no feature flag)
// and once with `blocking` feature. The `run!` macro dispatches each driver call to the right form.

#[cfg(not(feature = "blocking"))]
use core::future::Future;
#[cfg(not(feature = "blocking"))]
use core::pin::pin;
#[cfg(not(feature = "blocking"))]
use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};

use bmi323_driver::{
    AccelConfig, AccelMode, AccelRange, ActiveLevel, AltAccelConfig, AltConfigControl,
    AltConfigSwitchSource, AltGyroConfig, AnyMotionConfig, AverageSamples, Bandwidth, Bmi323,
    EventReportMode, FeatureBlockingMode, FifoConfig, FlatConfig, GyroConfig, GyroMode, GyroRange,
    InterruptChannel, InterruptPinConfig, InterruptRoute, InterruptSource, MotionAxes,
    NoMotionConfig, OrientationConfig, OrientationMode, OutputDataRate, OutputMode,
    ReferenceUpdate, SelfTestSelection, SignificantMotionConfig, StepCounterConfig, TapAxis,
    TapConfig, TapDetectionMode, TapReportingMode, TiltConfig,
};
use embedded_hal_mock::eh1::delay::{CheckedDelay, Transaction as DelayTransaction};
#[cfg(not(feature = "blocking"))]
use embedded_hal_mock::eh1::digital::{
    Mock as PinMock, State as PinState, Transaction as PinTransaction,
};
use embedded_hal_mock::eh1::i2c::{Mock as I2cMock, Transaction as I2cTransaction};

const ADDR: u8 = 0x68;

const CHIP_ID: u8 = 0x00;
const ERR_REG: u8 = 0x01;
const STATUS: u8 = 0x02;
const ACC_DATA_X: u8 = 0x03;
const GYR_DATA_X: u8 = 0x06;
const TEMP_DATA: u8 = 0x09;
const SENSOR_TIME_0: u8 = 0x0A;
const INT_STATUS_INT1: u8 = 0x0D;
const INT_STATUS_INT2: u8 = 0x0E;
const INT_STATUS_IBI: u8 = 0x0F;
const FEATURE_IO0: u8 = 0x10;
const FEATURE_IO1: u8 = 0x11;
const FEATURE_IO2: u8 = 0x12;
const FEATURE_IO3: u8 = 0x13;
const FEATURE_IO_STATUS: u8 = 0x14;
const FIFO_FILL_LEVEL: u8 = 0x15;
const FIFO_DATA: u8 = 0x16;
const ACC_CONF: u8 = 0x20;
const GYR_CONF: u8 = 0x21;
const ALT_ACC_CONF: u8 = 0x28;
const ALT_GYR_CONF: u8 = 0x29;
const ALT_CONF: u8 = 0x2A;
const ALT_STATUS: u8 = 0x2B;
const FIFO_WATERMARK: u8 = 0x35;
const FIFO_CONF: u8 = 0x36;
const FIFO_CTRL: u8 = 0x37;
const IO_INT_CTRL: u8 = 0x38;
const INT_CONF: u8 = 0x39;
const INT_MAP1: u8 = 0x3A;
const FEATURE_CTRL: u8 = 0x40;
const FEATURE_DATA_ADDR: u8 = 0x41;
const FEATURE_DATA_TX: u8 = 0x42;
const CMD: u8 = 0x7E;

const EXT_GEN_SET_1: u16 = 0x02;
const EXT_ANYMO_1: u16 = 0x05;
const EXT_ANYMO_2: u16 = 0x06;
const EXT_ANYMO_3: u16 = 0x07;
const EXT_NOMO_1: u16 = 0x08;
const EXT_NOMO_2: u16 = 0x09;
const EXT_NOMO_3: u16 = 0x0A;
const EXT_FLAT_1: u16 = 0x0B;
const EXT_FLAT_2: u16 = 0x0C;
const EXT_SIGMO_1: u16 = 0x0D;
const EXT_SIGMO_2: u16 = 0x0E;
const EXT_SIGMO_3: u16 = 0x0F;
const EXT_SC_1: u16 = 0x10;
const EXT_ORIENT_1: u16 = 0x1C;
const EXT_ORIENT_2: u16 = 0x1D;
const EXT_TAP_1: u16 = 0x1E;
const EXT_TAP_2: u16 = 0x1F;
const EXT_TAP_3: u16 = 0x20;
const EXT_TILT_1: u16 = 0x21;
const EXT_TILT_2: u16 = 0x22;
const EXT_ALT_CONFIG_CHG: u16 = 0x23;
const EXT_ST_RESULT: u16 = 0x24;
const EXT_ST_SELECT: u16 = 0x25;

const BMI323_CHIP_ID: u16 = 0x0043;
const SOFT_RESET: u16 = 0xDEAF;

/// Minimal executor for driving futures in these unit tests; the mock HAL
/// operations always complete immediately so a no-op waker is sufficient.
#[cfg(not(feature = "blocking"))]
fn block_on<F: Future>(future: F) -> F::Output {
    fn raw_waker() -> RawWaker {
        fn clone(_: *const ()) -> RawWaker {
            raw_waker()
        }
        fn wake(_: *const ()) {}
        fn wake_by_ref(_: *const ()) {}
        fn drop(_: *const ()) {}
        RawWaker::new(
            core::ptr::null(),
            &RawWakerVTable::new(clone, wake, wake_by_ref, drop),
        )
    }
    let waker = unsafe { Waker::from_raw(raw_waker()) };
    let mut context = Context::from_waker(&waker);
    let mut future = pin!(future);
    loop {
        match Future::poll(future.as_mut(), &mut context) {
            Poll::Ready(output) => return output,
            Poll::Pending => core::hint::spin_loop(),
        }
    }
}

/// Drive `expr` to completion: evaluates directly in blocking mode,
/// or runs through `block_on` in async mode.
macro_rules! run {
    ($expr:expr) => {{
        #[cfg(feature = "blocking")]
        let __result = $expr;
        #[cfg(not(feature = "blocking"))]
        let __result = block_on($expr);
        __result
    }};
}

/// Build a `DelayTransaction` for the active feature mode.
macro_rules! delay_tx {
    (ms, $n:expr) => {{
        #[cfg(feature = "blocking")]
        let __tx = DelayTransaction::delay_ms($n);
        #[cfg(not(feature = "blocking"))]
        let __tx = DelayTransaction::async_delay_ms($n);
        __tx
    }};
    (us, $n:expr) => {{
        #[cfg(feature = "blocking")]
        let __tx = DelayTransaction::delay_us($n);
        #[cfg(not(feature = "blocking"))]
        let __tx = DelayTransaction::async_delay_us($n);
        __tx
    }};
}

fn write_word(reg: u8, word: u16) -> I2cTransaction {
    let [lo, hi] = word.to_le_bytes();
    I2cTransaction::write(ADDR, vec![reg, lo, hi])
}

fn read_word(reg: u8, word: u16) -> I2cTransaction {
    let [lo, hi] = word.to_le_bytes();
    I2cTransaction::write_read(ADDR, vec![reg], vec![0, 0, lo, hi])
}

fn read_words(reg: u8, words: &[u16]) -> I2cTransaction {
    let mut response = vec![0, 0];
    for word in words {
        let [lo, hi] = word.to_le_bytes();
        response.push(lo);
        response.push(hi);
    }
    I2cTransaction::write_read(ADDR, vec![reg], response)
}

#[test]
fn init_resets_sensor_and_reads_device_state() {
    let expectations = [
        write_word(CMD, SOFT_RESET),
        read_word(CHIP_ID, BMI323_CHIP_ID),
        read_word(ERR_REG, 0x0000),
        read_word(STATUS, 0x00E1),
    ];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);
    let mut delay = CheckedDelay::new(&[delay_tx!(ms, 2)]);

    let state = run!(imu.init(&mut delay)).unwrap();

    assert_eq!(state.chip_id, BMI323_CHIP_ID as u8);
    assert!(state.status.por_detected());
    assert!(state.status.drdy_temp());
    assert!(state.status.drdy_gyro());
    assert!(state.status.drdy_accel());
    assert!(!state.error.fatal());

    delay.done();
    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn set_sensor_configs_writes_expected_words_and_tracks_ranges() {
    let accel = AccelConfig {
        mode: AccelMode::HighPerformance,
        average: AverageSamples::Avg4,
        bandwidth: Bandwidth::OdrOver4,
        range: AccelRange::G16,
        odr: OutputDataRate::Hz200,
    };
    let gyro = GyroConfig {
        mode: GyroMode::LowPower,
        average: AverageSamples::Avg8,
        bandwidth: Bandwidth::OdrOver2,
        range: GyroRange::Dps500,
        odr: OutputDataRate::Hz100,
    };
    let expectations = [
        write_word(ACC_CONF, accel.to_word()),
        write_word(GYR_CONF, gyro.to_word()),
    ];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);

    run!(imu.set_accel_config(accel)).unwrap();
    run!(imu.set_gyro_config(gyro)).unwrap();

    assert_eq!(imu.accel_range(), AccelRange::G16);
    assert_eq!(imu.gyro_range(), GyroRange::Dps500);

    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn reinit_resets_bookkeeping_to_por_defaults() {
    let accel = AccelConfig {
        mode: AccelMode::HighPerformance,
        average: AverageSamples::Avg4,
        bandwidth: Bandwidth::OdrOver4,
        range: AccelRange::G16,
        odr: OutputDataRate::Hz200,
    };
    let gyro = GyroConfig {
        mode: GyroMode::LowPower,
        average: AverageSamples::Avg8,
        bandwidth: Bandwidth::OdrOver2,
        range: GyroRange::Dps500,
        odr: OutputDataRate::Hz100,
    };
    let expectations = [
        write_word(ACC_CONF, accel.to_word()),
        write_word(GYR_CONF, gyro.to_word()),
        // re-init: soft reset + chip-id + err + status reads
        write_word(CMD, SOFT_RESET),
        read_word(CHIP_ID, BMI323_CHIP_ID),
        read_word(ERR_REG, 0x0000),
        read_word(STATUS, 0x00E1),
    ];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);
    let mut delay = CheckedDelay::new(&[delay_tx!(ms, 2)]);

    run!(imu.set_accel_config(accel)).unwrap();
    run!(imu.set_gyro_config(gyro)).unwrap();
    assert_eq!(imu.accel_range(), AccelRange::G16);
    assert_eq!(imu.gyro_range(), GyroRange::Dps500);

    // Re-init issues a soft reset; chip returns to POR defaults so the
    // bookkeeping cache must also revert to G2/Dps125.
    run!(imu.init(&mut delay)).unwrap();
    assert_eq!(imu.accel_range(), AccelRange::G2);
    assert_eq!(imu.gyro_range(), GyroRange::Dps125);

    delay.done();
    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn enable_feature_engine_uses_expected_register_sequence() {
    let expectations = [
        write_word(ACC_CONF, 0x0000),
        write_word(GYR_CONF, 0x0000),
        write_word(FEATURE_IO2, 0x012C),
        write_word(FEATURE_IO_STATUS, 0x0001),
        write_word(FEATURE_CTRL, 0x0001),
        read_word(FEATURE_IO1, 0x0001),
    ];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);
    // The polling loop delays 200 µs before each read; returns on first poll.
    let mut delay = CheckedDelay::new(&[delay_tx!(us, 200)]);

    run!(imu.enable_feature_engine(&mut delay)).unwrap();

    delay.done();
    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn configure_any_motion_and_interrupt_routing_programs_expected_registers() {
    let config = AnyMotionConfig {
        axes: MotionAxes {
            x: true,
            y: true,
            z: false,
        },
        threshold: 0x007B,
        hysteresis: 0x0009,
        duration: 50,
        wait_time: 2,
        reference_update: ReferenceUpdate::EverySample,
        report_mode: EventReportMode::AllEvents,
        interrupt_hold: 3,
    };
    let expectations = [
        write_word(FEATURE_DATA_ADDR, EXT_GEN_SET_1),
        read_word(FEATURE_DATA_TX, 0x0000),
        write_word(FEATURE_DATA_ADDR, EXT_GEN_SET_1),
        write_word(FEATURE_DATA_TX, 0x0006),
        write_word(FEATURE_DATA_ADDR, EXT_ANYMO_1),
        write_word(FEATURE_DATA_TX, 0x107B),
        write_word(FEATURE_DATA_ADDR, EXT_ANYMO_2),
        write_word(FEATURE_DATA_TX, 0x0009),
        write_word(FEATURE_DATA_ADDR, EXT_ANYMO_3),
        write_word(FEATURE_DATA_TX, 0x4032),
        read_word(FEATURE_IO0, 0x8000),
        write_word(FEATURE_IO0, 0x8018),
        write_word(FEATURE_IO_STATUS, 0x0001),
        read_word(IO_INT_CTRL, 0x0000),
        write_word(IO_INT_CTRL, 0x0007),
        read_word(INT_MAP1, 0x0000),
        write_word(INT_MAP1, 0x0004),
    ];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);

    run!(imu.configure_any_motion(config)).unwrap();
    run!(imu.configure_interrupt_pin(
        InterruptChannel::Int1,
        InterruptPinConfig {
            active_level: ActiveLevel::High,
            output_mode: OutputMode::OpenDrain,
            enabled: true,
        },
    ))
    .unwrap();
    run!(imu.map_interrupt(InterruptSource::AnyMotion, InterruptRoute::Int1)).unwrap();

    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn configure_no_motion_programs_expected_registers() {
    let config = NoMotionConfig {
        axes: MotionAxes {
            x: false,
            y: true,
            z: true,
        },
        threshold: 0x0020,
        hysteresis: 0x0004,
        duration: 100,
        wait_time: 7,
        reference_update: ReferenceUpdate::OnDetection,
        report_mode: EventReportMode::FirstEventOnly,
        interrupt_hold: 20,
    };
    let expectations = [
        write_word(FEATURE_DATA_ADDR, EXT_GEN_SET_1),
        read_word(FEATURE_DATA_TX, 0x0000),
        write_word(FEATURE_DATA_ADDR, EXT_GEN_SET_1),
        write_word(FEATURE_DATA_TX, 0x001B),
        write_word(FEATURE_DATA_ADDR, EXT_NOMO_1),
        write_word(FEATURE_DATA_TX, 0x0020),
        write_word(FEATURE_DATA_ADDR, EXT_NOMO_2),
        write_word(FEATURE_DATA_TX, 0x0004),
        write_word(FEATURE_DATA_ADDR, EXT_NOMO_3),
        write_word(FEATURE_DATA_TX, 0xE064),
        read_word(FEATURE_IO0, 0xFFF8),
        write_word(FEATURE_IO0, 0xFFFE),
        write_word(FEATURE_IO_STATUS, 0x0001),
    ];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);

    run!(imu.configure_no_motion(config)).unwrap();

    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn read_paths_decode_burst_and_feature_words_correctly() {
    let expectations = [
        read_words(
            ACC_DATA_X,
            &[0x0001, 0xFFFE, 0x7FFF, 0x8000, 0x0032, 0xFF9C],
        ),
        read_words(SENSOR_TIME_0, &[0x5678, 0x1234]),
        read_word(FEATURE_IO2, 0xCDEF),
        read_word(FEATURE_IO3, 0x89AB),
    ];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);

    let imu_data = run!(imu.read_imu_data()).unwrap();
    let sensor_time = run!(imu.read_sensor_time()).unwrap();
    let step_count = run!(imu.read_step_count()).unwrap();

    assert_eq!(imu_data.accel.x, 1);
    assert_eq!(imu_data.accel.y, -2);
    assert_eq!(imu_data.accel.z, i16::MAX);
    assert_eq!(imu_data.gyro.x, i16::MIN);
    assert_eq!(imu_data.gyro.y, 50);
    assert_eq!(imu_data.gyro.z, -100);
    assert_eq!(sensor_time, 0x1234_5678);
    assert_eq!(step_count, 0x89AB_CDEF);

    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn configure_alt_config_control_writes_sensor_and_feature_words() {
    let control = AltConfigControl {
        accel_enabled: true,
        gyro_enabled: false,
        reset_on_user_config_write: true,
        switch_to_alternate: AltConfigSwitchSource::AnyMotion,
        switch_to_user: AltConfigSwitchSource::NoMotion,
    };
    let expectations = [
        write_word(ALT_CONF, 0x0101),
        write_word(FEATURE_DATA_ADDR, EXT_ALT_CONFIG_CHG),
        write_word(FEATURE_DATA_TX, 0x0012),
    ];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);

    run!(imu.configure_alt_config_control(control)).unwrap();

    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn run_self_test_uses_expected_sequence() {
    let expectations = [
        write_word(ACC_CONF, 0x0000),
        write_word(GYR_CONF, 0x0000),
        write_word(FEATURE_IO2, 0x012C),
        write_word(FEATURE_IO_STATUS, 0x0001),
        write_word(FEATURE_CTRL, 0x0001),
        read_word(FEATURE_IO1, 0x0001),
        write_word(ACC_CONF, 0x7029),
        write_word(ALT_ACC_CONF, 0x0000),
        write_word(ALT_GYR_CONF, 0x0000),
        write_word(FEATURE_DATA_ADDR, EXT_ST_SELECT),
        write_word(FEATURE_DATA_TX, SelfTestSelection::Gyroscope.to_word()),
        write_word(CMD, 0x0100),
        read_word(FEATURE_IO1, 0x0055),
        write_word(FEATURE_DATA_ADDR, EXT_ST_RESULT),
        read_word(FEATURE_DATA_TX, 0x0078),
    ];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);
    // enable_feature_engine: 1 poll, returns ACTIVATED → 1×200µs pre-poll delay.
    // Self-test result ready on first poll → no 10ms poll delay.
    let mut delay = CheckedDelay::new(&[delay_tx!(us, 200)]);

    let result = run!(imu.run_self_test(&mut delay, SelfTestSelection::Gyroscope)).unwrap();

    assert!(result.passed);
    assert!(!result.sample_rate_error);
    assert_eq!(result.error_status, 5);
    assert!(result.gyroscope_ok());

    delay.done();
    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn configure_flat_programs_expected_registers() {
    let config = FlatConfig {
        theta: 5,
        blocking: FeatureBlockingMode::AccelOver1p5g,
        hold_time: 10,
        slope_threshold: 3,
        hysteresis: 2,
        report_mode: EventReportMode::AllEvents,
        interrupt_hold: 0,
    };
    // EXT_FLAT_1: 5 | (1<<6) | (10<<8) = 0x0A45
    // EXT_FLAT_2: 3 | (2<<8) = 0x0203
    // GEN_SET_1: AllEvents=0, hold=0 → 0x0000
    // FEATURE_IO0: bit 6 → 0x0040
    let expectations = [
        write_word(FEATURE_DATA_ADDR, EXT_GEN_SET_1),
        read_word(FEATURE_DATA_TX, 0x0000),
        write_word(FEATURE_DATA_ADDR, EXT_GEN_SET_1),
        write_word(FEATURE_DATA_TX, 0x0000),
        write_word(FEATURE_DATA_ADDR, EXT_FLAT_1),
        write_word(FEATURE_DATA_TX, 0x0A45),
        write_word(FEATURE_DATA_ADDR, EXT_FLAT_2),
        write_word(FEATURE_DATA_TX, 0x0203),
        read_word(FEATURE_IO0, 0x0000),
        write_word(FEATURE_IO0, 0x0040),
        write_word(FEATURE_IO_STATUS, 0x0001),
    ];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);

    run!(imu.configure_flat(config)).unwrap();

    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn configure_orientation_programs_expected_registers() {
    let config = OrientationConfig {
        upside_down_enabled: true,
        mode: OrientationMode::Symmetrical,
        blocking: FeatureBlockingMode::AccelOver1p5gOrHalfSlope,
        theta: 16,
        hold_time: 3,
        slope_threshold: 8,
        hysteresis: 5,
        report_mode: EventReportMode::FirstEventOnly,
        interrupt_hold: 2,
    };
    // GEN_SET_1: FirstEventOnly=1, hold=2 → 1|(2<<1)=0x0005
    // EXT_ORIENT_1: 1|(0<<1)|(2<<3)|(16<<5)|(3<<11) = 1|0|16|512|6144 = 0x1A11
    // EXT_ORIENT_2: 8|(5<<8) = 0x0508
    // FEATURE_IO0: bit 7 → 0x0080
    let expectations = [
        write_word(FEATURE_DATA_ADDR, EXT_GEN_SET_1),
        read_word(FEATURE_DATA_TX, 0x0000),
        write_word(FEATURE_DATA_ADDR, EXT_GEN_SET_1),
        write_word(FEATURE_DATA_TX, 0x0005),
        write_word(FEATURE_DATA_ADDR, EXT_ORIENT_1),
        write_word(FEATURE_DATA_TX, 0x1A11),
        write_word(FEATURE_DATA_ADDR, EXT_ORIENT_2),
        write_word(FEATURE_DATA_TX, 0x0508),
        read_word(FEATURE_IO0, 0x0000),
        write_word(FEATURE_IO0, 0x0080),
        write_word(FEATURE_IO_STATUS, 0x0001),
    ];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);

    run!(imu.configure_orientation(config)).unwrap();

    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn configure_tap_programs_expected_registers() {
    let config = TapConfig {
        axis: TapAxis::Z,
        reporting_mode: TapReportingMode::Confirmed,
        max_peaks_for_tap: 4,
        mode: TapDetectionMode::Normal,
        single_tap_enabled: true,
        double_tap_enabled: true,
        triple_tap_enabled: false,
        tap_peak_threshold: 0x0100,
        max_gesture_duration: 8,
        max_duration_between_peaks: 3,
        tap_shock_settling_duration: 2,
        min_quiet_duration_between_taps: 1,
        quiet_time_after_gesture: 5,
        report_mode: EventReportMode::AllEvents,
        interrupt_hold: 1,
    };
    // GEN_SET_1: AllEvents=0, hold=1 → (1<<1)=0x0002
    // EXT_TAP_1: Z=2|(Confirmed=1<<2)|(4<<3)|(Normal=1<<6) = 2|4|32|64 = 0x0066
    // EXT_TAP_2: (0x100&0x3FF)|(8<<10) = 256|8192 = 0x2100
    // EXT_TAP_3: 3|(2<<4)|(1<<8)|(5<<12) = 3|32|256|20480 = 0x5123
    // FEATURE_IO0: single(bit12)+double(bit13) → 0x3000
    let expectations = [
        write_word(FEATURE_DATA_ADDR, EXT_GEN_SET_1),
        read_word(FEATURE_DATA_TX, 0x0000),
        write_word(FEATURE_DATA_ADDR, EXT_GEN_SET_1),
        write_word(FEATURE_DATA_TX, 0x0002),
        write_word(FEATURE_DATA_ADDR, EXT_TAP_1),
        write_word(FEATURE_DATA_TX, 0x0066),
        write_word(FEATURE_DATA_ADDR, EXT_TAP_2),
        write_word(FEATURE_DATA_TX, 0x2100),
        write_word(FEATURE_DATA_ADDR, EXT_TAP_3),
        write_word(FEATURE_DATA_TX, 0x5123),
        read_word(FEATURE_IO0, 0x0000),
        write_word(FEATURE_IO0, 0x3000),
        write_word(FEATURE_IO_STATUS, 0x0001),
    ];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);

    run!(imu.configure_tap(config)).unwrap();

    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn configure_significant_motion_programs_expected_registers() {
    let config = SignificantMotionConfig {
        block_size: 250,
        peak_to_peak_min: 256,
        mean_crossing_rate_min: 10,
        peak_to_peak_max: 512,
        mean_crossing_rate_max: 20,
        report_mode: EventReportMode::AllEvents,
        interrupt_hold: 0,
    };
    // GEN_SET_1: AllEvents=0, hold=0 → 0x0000
    // EXT_SIGMO_1: 250 = 0x00FA
    // EXT_SIGMO_2: (256&0x3FF)|(10<<10) = 256|10240 = 0x2900
    // EXT_SIGMO_3: (512&0x3FF)|(20<<10) = 512|20480 = 0x5200
    // FEATURE_IO0: bit 10 → 0x0400
    let expectations = [
        write_word(FEATURE_DATA_ADDR, EXT_GEN_SET_1),
        read_word(FEATURE_DATA_TX, 0x0000),
        write_word(FEATURE_DATA_ADDR, EXT_GEN_SET_1),
        write_word(FEATURE_DATA_TX, 0x0000),
        write_word(FEATURE_DATA_ADDR, EXT_SIGMO_1),
        write_word(FEATURE_DATA_TX, 0x00FA),
        write_word(FEATURE_DATA_ADDR, EXT_SIGMO_2),
        write_word(FEATURE_DATA_TX, 0x2900),
        write_word(FEATURE_DATA_ADDR, EXT_SIGMO_3),
        write_word(FEATURE_DATA_TX, 0x5200),
        read_word(FEATURE_IO0, 0x0000),
        write_word(FEATURE_IO0, 0x0400),
        write_word(FEATURE_IO_STATUS, 0x0001),
    ];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);

    run!(imu.configure_significant_motion(config)).unwrap();

    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn configure_tilt_programs_expected_registers() {
    let config = TiltConfig {
        segment_size: 100,
        min_tilt_angle: 0xC0,
        beta_acc_mean: 0x00AB,
        report_mode: EventReportMode::FirstEventOnly,
        interrupt_hold: 0,
    };
    // GEN_SET_1: FirstEventOnly=1, hold=0 → 0x0001
    // EXT_TILT_1: 100|(0xC0<<8) = 0xC064
    // EXT_TILT_2: 0x00AB
    // FEATURE_IO0: bit 11 → 0x0800
    let expectations = [
        write_word(FEATURE_DATA_ADDR, EXT_GEN_SET_1),
        read_word(FEATURE_DATA_TX, 0x0000),
        write_word(FEATURE_DATA_ADDR, EXT_GEN_SET_1),
        write_word(FEATURE_DATA_TX, 0x0001),
        write_word(FEATURE_DATA_ADDR, EXT_TILT_1),
        write_word(FEATURE_DATA_TX, 0xC064),
        write_word(FEATURE_DATA_ADDR, EXT_TILT_2),
        write_word(FEATURE_DATA_TX, 0x00AB),
        read_word(FEATURE_IO0, 0x0000),
        write_word(FEATURE_IO0, 0x0800),
        write_word(FEATURE_IO_STATUS, 0x0001),
    ];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);

    run!(imu.configure_tilt(config)).unwrap();

    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn step_detector_and_counter_enable_programs_expected_bits() {
    // set_step_detector_enabled sets FEATURE_IO0 bit 8
    // set_step_counter_enabled sets FEATURE_IO0 bit 9
    let expectations = [
        read_word(FEATURE_IO0, 0x0000),
        write_word(FEATURE_IO0, 0x0100),
        write_word(FEATURE_IO_STATUS, 0x0001),
        read_word(FEATURE_IO0, 0x0100),
        write_word(FEATURE_IO0, 0x0300),
        write_word(FEATURE_IO_STATUS, 0x0001),
    ];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);

    run!(imu.set_step_detector_enabled(true)).unwrap();
    run!(imu.set_step_counter_enabled(true)).unwrap();

    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn configure_step_counter_writes_expected_feature_word() {
    let config = StepCounterConfig {
        watermark_level: 100,
        reset_counter: false,
    };
    let expectations = [
        write_word(FEATURE_DATA_ADDR, EXT_SC_1),
        write_word(FEATURE_DATA_TX, 0x0064),
    ];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);

    run!(imu.configure_step_counter(config)).unwrap();

    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn reset_step_counter_sets_reset_bit() {
    // StepCounterConfig { watermark: 0, reset: true } → to_word() = 0x0400
    let expectations = [
        write_word(FEATURE_DATA_ADDR, EXT_SC_1),
        write_word(FEATURE_DATA_TX, 0x0400),
    ];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);

    run!(imu.reset_step_counter()).unwrap();

    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn set_interrupt_latching_writes_int_conf() {
    let expectations = [write_word(INT_CONF, 0x0001), write_word(INT_CONF, 0x0000)];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);

    run!(imu.set_interrupt_latching(true)).unwrap();
    run!(imu.set_interrupt_latching(false)).unwrap();

    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn read_interrupt_status_reads_expected_registers() {
    let expectations = [
        read_word(INT_STATUS_INT1, 0x2000), // accel_data_ready (bit 13)
        read_word(INT_STATUS_INT2, 0x1000), // gyro_data_ready (bit 12)
        read_word(INT_STATUS_IBI, 0x0040),  // significant_motion (bit 6)
    ];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);

    let s1 = run!(imu.read_interrupt_status(InterruptChannel::Int1)).unwrap();
    let s2 = run!(imu.read_interrupt_status(InterruptChannel::Int2)).unwrap();
    let s3 = run!(imu.read_interrupt_status(InterruptChannel::Ibi)).unwrap();

    assert!(s1.accel_data_ready());
    assert!(!s1.gyro_data_ready());
    assert!(s2.gyro_data_ready());
    assert!(!s2.accel_data_ready());
    assert!(s3.significant_motion());
    assert!(!s3.accel_data_ready());

    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn set_fifo_config_writes_watermark_and_conf() {
    let config = FifoConfig {
        stop_on_full: true,
        include_time: false,
        include_accel: true,
        include_gyro: true,
        include_temperature: false,
    };
    // to_word(): 1|(0<<8)|(1<<9)|(1<<10)|(0<<11) = 1|0x200|0x400 = 0x0601
    // watermark 42 → 42 & 0x03FF = 0x002A
    let expectations = [
        write_word(FIFO_WATERMARK, 0x002A),
        write_word(FIFO_CONF, 0x0601),
    ];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);

    run!(imu.set_fifo_config(config, 42)).unwrap();

    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn fifo_fill_level_masks_upper_bits() {
    // raw 0x8ABC → masked with 0x07FF → 0x02BC = 700
    let expectations = [read_word(FIFO_FILL_LEVEL, 0x8ABC)];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);

    let level = run!(imu.fifo_fill_level()).unwrap();

    assert_eq!(level, 0x02BC);

    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn flush_fifo_writes_fifo_ctrl() {
    let expectations = [write_word(FIFO_CTRL, 0x0001)];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);

    run!(imu.flush_fifo()).unwrap();

    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn read_fifo_words_reads_from_fifo_data_register() {
    let expectations = [read_words(FIFO_DATA, &[0x1234, 0x5678])];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);

    let mut words = [0u16; 2];
    run!(imu.read_fifo_words(&mut words)).unwrap();

    assert_eq!(words[0], 0x1234);
    assert_eq!(words[1], 0x5678);

    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn read_temperature_celsius_converts_raw_correctly() {
    // raw = 0x0200 = 512 as i16 → 512.0/512.0 + 23.0 = 24.0
    let expectations = [read_word(TEMP_DATA, 0x0200)];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);

    let temp = run!(imu.read_temperature_celsius()).unwrap();

    assert!((temp - 24.0).abs() < 0.001);

    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn read_accel_and_gyro_read_from_separate_registers() {
    let expectations = [
        read_words(ACC_DATA_X, &[100, 0xFFC8, 1]), // x=100, y=-56, z=1
        read_words(GYR_DATA_X, &[50, 0xFF9C, 3]),  // x=50,  y=-100, z=3
    ];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);

    let accel = run!(imu.read_accel()).unwrap();
    let gyro = run!(imu.read_gyro()).unwrap();

    assert_eq!(accel.x, 100);
    assert_eq!(accel.y, -56);
    assert_eq!(accel.z, 1);
    assert_eq!(gyro.x, 50);
    assert_eq!(gyro.y, -100);
    assert_eq!(gyro.z, 3);

    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn init_returns_invalid_chip_id_error() {
    let expectations = [
        write_word(CMD, SOFT_RESET),
        read_word(CHIP_ID, 0x00AB), // wrong chip ID
    ];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);
    let mut delay = CheckedDelay::new(&[delay_tx!(ms, 2)]);

    let result = run!(imu.init(&mut delay));

    assert!(matches!(
        result,
        Err(bmi323_driver::Error::InvalidChipId(0xAB))
    ));

    delay.done();
    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn init_returns_fatal_error_when_fatal_bit_set() {
    let expectations = [
        write_word(CMD, SOFT_RESET),
        read_word(CHIP_ID, BMI323_CHIP_ID),
        read_word(ERR_REG, 0x0001), // fatal bit set
    ];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);
    let mut delay = CheckedDelay::new(&[delay_tx!(ms, 2)]);

    let result = run!(imu.init(&mut delay));

    assert!(matches!(result, Err(bmi323_driver::Error::FatalError)));

    delay.done();
    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn configure_interrupt_pin_int2_writes_upper_byte_bits() {
    // Int2 shift=8: active_high=bit8, push_pull=bit9(0), enabled=bit10 → 0x0500
    let expectations = [
        read_word(IO_INT_CTRL, 0x0000),
        write_word(IO_INT_CTRL, 0x0500),
    ];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);

    run!(imu.configure_interrupt_pin(
        InterruptChannel::Int2,
        InterruptPinConfig {
            active_level: ActiveLevel::High,
            output_mode: OutputMode::PushPull,
            enabled: true,
        },
    ))
    .unwrap();

    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn configure_interrupt_pin_ibi_returns_without_bus_transaction() {
    let expectations = [];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);

    run!(imu.configure_interrupt_pin(
        InterruptChannel::Ibi,
        InterruptPinConfig {
            active_level: ActiveLevel::High,
            output_mode: OutputMode::PushPull,
            enabled: true,
        },
    ))
    .unwrap();

    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn configure_any_motion_on_detection_sets_bit12_to_zero() {
    // OnDetection → (0 << 12) | threshold, no bit 12 set in ANYMO_1
    let config = AnyMotionConfig {
        axes: MotionAxes {
            x: true,
            y: false,
            z: false,
        },
        threshold: 0x0064,
        hysteresis: 0x0000,
        duration: 0,
        wait_time: 0,
        reference_update: ReferenceUpdate::OnDetection,
        report_mode: EventReportMode::AllEvents,
        interrupt_hold: 0,
    };
    let expectations = [
        write_word(FEATURE_DATA_ADDR, EXT_GEN_SET_1),
        read_word(FEATURE_DATA_TX, 0x0000),
        write_word(FEATURE_DATA_ADDR, EXT_GEN_SET_1),
        write_word(FEATURE_DATA_TX, 0x0000),
        write_word(FEATURE_DATA_ADDR, EXT_ANYMO_1),
        write_word(FEATURE_DATA_TX, 0x0064), // OnDetection: bit 12 = 0
        write_word(FEATURE_DATA_ADDR, EXT_ANYMO_2),
        write_word(FEATURE_DATA_TX, 0x0000),
        write_word(FEATURE_DATA_ADDR, EXT_ANYMO_3),
        write_word(FEATURE_DATA_TX, 0x0000),
        read_word(FEATURE_IO0, 0x0000),
        write_word(FEATURE_IO0, 0x0008), // x_axis = bit 3
        write_word(FEATURE_IO_STATUS, 0x0001),
    ];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);

    run!(imu.configure_any_motion(config)).unwrap();

    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn configure_no_motion_every_sample_sets_bit12_to_one() {
    // EverySample → (1 << 12) | threshold, bit 12 set in NOMO_1
    let config = NoMotionConfig {
        axes: MotionAxes {
            x: true,
            y: false,
            z: false,
        },
        threshold: 0x0040,
        hysteresis: 0x0000,
        duration: 0,
        wait_time: 0,
        reference_update: ReferenceUpdate::EverySample,
        report_mode: EventReportMode::AllEvents,
        interrupt_hold: 0,
    };
    let expectations = [
        write_word(FEATURE_DATA_ADDR, EXT_GEN_SET_1),
        read_word(FEATURE_DATA_TX, 0x0000),
        write_word(FEATURE_DATA_ADDR, EXT_GEN_SET_1),
        write_word(FEATURE_DATA_TX, 0x0000),
        write_word(FEATURE_DATA_ADDR, EXT_NOMO_1),
        write_word(FEATURE_DATA_TX, 0x1040), // EverySample: bit 12 = 1
        write_word(FEATURE_DATA_ADDR, EXT_NOMO_2),
        write_word(FEATURE_DATA_TX, 0x0000),
        write_word(FEATURE_DATA_ADDR, EXT_NOMO_3),
        write_word(FEATURE_DATA_TX, 0x0000),
        read_word(FEATURE_IO0, 0x0000),
        write_word(FEATURE_IO0, 0x0001), // x_axis = bit 0
        write_word(FEATURE_IO_STATUS, 0x0001),
    ];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);

    run!(imu.configure_no_motion(config)).unwrap();

    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn disabling_feature_engine_bits_clears_expected_bits() {
    let expectations = [
        // set_flat_enabled(false): clear bit 6
        read_word(FEATURE_IO0, 0x0040),
        write_word(FEATURE_IO0, 0x0000),
        write_word(FEATURE_IO_STATUS, 0x0001),
        // set_orientation_enabled(false): clear bit 7
        read_word(FEATURE_IO0, 0x0080),
        write_word(FEATURE_IO0, 0x0000),
        write_word(FEATURE_IO_STATUS, 0x0001),
        // set_significant_motion_enabled(false): clear bit 10
        read_word(FEATURE_IO0, 0x0400),
        write_word(FEATURE_IO0, 0x0000),
        write_word(FEATURE_IO_STATUS, 0x0001),
        // set_tilt_enabled(false): clear bit 11
        read_word(FEATURE_IO0, 0x0800),
        write_word(FEATURE_IO0, 0x0000),
        write_word(FEATURE_IO_STATUS, 0x0001),
        // set_step_detector_enabled(false): clear bit 8
        read_word(FEATURE_IO0, 0x0100),
        write_word(FEATURE_IO0, 0x0000),
        write_word(FEATURE_IO_STATUS, 0x0001),
        // set_step_counter_enabled(false): clear bit 9
        read_word(FEATURE_IO0, 0x0200),
        write_word(FEATURE_IO0, 0x0000),
        write_word(FEATURE_IO_STATUS, 0x0001),
    ];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);

    run!(imu.set_flat_enabled(false)).unwrap();
    run!(imu.set_orientation_enabled(false)).unwrap();
    run!(imu.set_significant_motion_enabled(false)).unwrap();
    run!(imu.set_tilt_enabled(false)).unwrap();
    run!(imu.set_step_detector_enabled(false)).unwrap();
    run!(imu.set_step_counter_enabled(false)).unwrap();

    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn set_alt_accel_config_writes_alt_acc_conf() {
    let config = AltAccelConfig {
        mode: AccelMode::HighPerformance,
        average: AverageSamples::Avg4,
        odr: OutputDataRate::Hz100,
    };
    let expectations = [write_word(ALT_ACC_CONF, config.to_word())];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);

    run!(imu.set_alt_accel_config(config)).unwrap();

    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn set_alt_gyro_config_writes_alt_gyr_conf() {
    let config = AltGyroConfig {
        mode: GyroMode::Normal,
        average: AverageSamples::Avg1,
        odr: OutputDataRate::Hz100,
    };
    let expectations = [write_word(ALT_GYR_CONF, config.to_word())];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);

    run!(imu.set_alt_gyro_config(config)).unwrap();

    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn alt_status_reads_alt_status_register() {
    // 0x0011 → accel bit 0 and gyro bit 4 both set
    let expectations = [read_word(ALT_STATUS, 0x0011)];
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);

    let status = run!(imu.alt_status()).unwrap();

    assert!(status.accel_uses_alternate());
    assert!(status.gyro_uses_alternate());

    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn enable_feature_engine_returns_error_when_never_ready() {
    let mut expectations = vec![
        write_word(ACC_CONF, 0x0000),
        write_word(GYR_CONF, 0x0000),
        write_word(FEATURE_IO2, 0x012C),
        write_word(FEATURE_IO_STATUS, 0x0001),
        write_word(FEATURE_CTRL, 0x0001),
    ];
    for _ in 0..32 {
        expectations.push(read_word(FEATURE_IO1, 0x0000));
    }
    expectations.push(read_word(FEATURE_IO1, 0x0002));
    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);
    // 32 polls, each preceded by a 200 µs delay
    let delay_transactions: Vec<_> = (0..32).map(|_| delay_tx!(us, 200)).collect();
    let mut delay = CheckedDelay::new(&delay_transactions);

    let result = run!(imu.enable_feature_engine(&mut delay));

    assert!(matches!(
        result,
        Err(bmi323_driver::Error::FeatureEngineNotReady(2))
    ));
    delay.done();
    let mut i2c = imu.destroy();
    i2c.done();
}

#[test]
fn run_self_test_accelerometer_selection_times_out_when_feature_never_signals() {
    let mut expectations = vec![
        // enable_feature_engine: success on first read
        write_word(ACC_CONF, 0x0000),
        write_word(GYR_CONF, 0x0000),
        write_word(FEATURE_IO2, 0x012C),
        write_word(FEATURE_IO_STATUS, 0x0001),
        write_word(FEATURE_CTRL, 0x0001),
        read_word(FEATURE_IO1, 0x0001),
        // Accelerometer selection: tests_gyroscope() == false → no ACC_CONF write
        write_word(ALT_ACC_CONF, 0x0000),
        write_word(ALT_GYR_CONF, 0x0000),
        write_word(FEATURE_DATA_ADDR, EXT_ST_SELECT),
        write_word(FEATURE_DATA_TX, 0x0001), // Accelerometer.to_word()
        write_word(CMD, 0x0100),             // SELF_TEST
    ];
    // Feature engine: 1 poll → 1×200µs pre-poll delay. Self-test: 50 polls all fail.
    let mut delay_transactions = vec![delay_tx!(us, 200)];
    for _ in 0..50 {
        delay_transactions.push(delay_tx!(ms, 10));
        expectations.push(read_word(FEATURE_IO1, 0x0000));
    }

    let i2c = I2cMock::new(&expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);
    let mut delay = CheckedDelay::new(&delay_transactions);

    let result = run!(imu.run_self_test(&mut delay, SelfTestSelection::Accelerometer));

    assert!(matches!(result, Err(bmi323_driver::Error::SelfTestTimeout)));
    delay.done();
    let mut i2c = imu.destroy();
    i2c.done();
}

/// `wait_for_interrupt` is only available in async builds.
#[cfg(not(feature = "blocking"))]
#[test]
fn wait_for_interrupt_waits_for_high_then_reads_status() {
    let i2c_expectations = [read_word(INT_STATUS_INT1, 0x0002)]; // any_motion bit set
    let pin_expectations = [PinTransaction::wait_for_state(PinState::High)];
    let i2c = I2cMock::new(&i2c_expectations);
    let mut imu = Bmi323::new_i2c(i2c, ADDR);
    let mut pin = PinMock::new(&pin_expectations);

    let status = block_on(imu.wait_for_interrupt(&mut pin, InterruptChannel::Int1)).unwrap();

    assert!(status.any_motion());
    pin.done();
    let mut i2c = imu.destroy();
    i2c.done();
}
