#[cfg(all(target_os = "ios", feature = "sensors"))]
use objc2::{msg_send, rc::Retained};
#[cfg(all(target_os = "ios", feature = "sensors"))]
use objc2_core_motion::{CMMotionManager, CMPedometer, CMPedometerData};
#[cfg(all(target_os = "ios", feature = "sensors"))]
use objc2_foundation::{MainThreadMarker, NSDate};
#[cfg(all(target_os = "ios", feature = "sensors"))]
use std::sync::{Arc, Mutex};

#[cfg(all(target_os = "ios", feature = "sensors"))]
pub(crate) struct IosSensorState {
    manager: Retained<CMMotionManager>,
    pedometer: Retained<CMPedometer>,
    latest_steps: Arc<Mutex<Option<f32>>>,
    latest_yesterday_steps: Arc<Mutex<Option<f32>>>,
    previous_steps: Mutex<f32>,
}

#[cfg(all(target_os = "ios", feature = "sensors"))]
impl IosSensorState {
    pub fn new() -> Self {
        let _mtm =
            MainThreadMarker::new().expect("must be on main thread to initialize CMMotionManager");

        let manager = unsafe { CMMotionManager::new() };

        // Settings intervals (50Hz)
        unsafe {
            let _: () = msg_send![&manager, setAccelerometerUpdateInterval: 0.02f64];
            let _: () = msg_send![&manager, setGyroUpdateInterval: 0.02f64];
            let _: () = msg_send![&manager, setMagnetometerUpdateInterval: 0.02f64];
            let _: () = msg_send![&manager, setDeviceMotionUpdateInterval: 0.02f64];
        }

        let pedometer = unsafe { CMPedometer::new() };

        Self {
            manager,
            pedometer,
            latest_steps: Arc::new(Mutex::new(None)),
            latest_yesterday_steps: Arc::new(Mutex::new(None)),
            previous_steps: Mutex::new(0.0),
        }
    }

    pub fn enable(&self) {
        unsafe {
            let accel_avail: bool = msg_send![&self.manager, isAccelerometerAvailable];
            if accel_avail {
                let _: () = msg_send![&self.manager, startAccelerometerUpdates];
            }
            let gyro_avail: bool = msg_send![&self.manager, isGyroAvailable];
            if gyro_avail {
                let _: () = msg_send![&self.manager, startGyroUpdates];
            }
            let mag_avail: bool = msg_send![&self.manager, isMagnetometerAvailable];
            if mag_avail {
                let _: () = msg_send![&self.manager, startMagnetometerUpdates];
            }
            let motion_avail: bool = msg_send![&self.manager, isDeviceMotionAvailable];
            if motion_avail {
                let _: () = msg_send![&self.manager, startDeviceMotionUpdates];
            }

            let pedo_avail: bool = msg_send![objc2::class!(CMPedometer), isStepCountingAvailable];
            if pedo_avail {
                let latest_steps = self.latest_steps.clone();
                let latest_yesterday_steps = self.latest_yesterday_steps.clone();
                let calendar: Retained<objc2_foundation::NSCalendar> =
                    msg_send![objc2::class!(NSCalendar), currentCalendar];
                let now: Retained<objc2_foundation::NSDate> =
                    msg_send![objc2::class!(NSDate), date];
                let start_of_day: Retained<objc2_foundation::NSDate> =
                    msg_send![&calendar, startOfDayForDate: &*now];

                let handler = block2::RcBlock::new(
                    move |data: *mut CMPedometerData, _error: *mut objc2::runtime::AnyObject| {
                        if !data.is_null() {
                            let data = &*data;
                            // Get steps via msg_send! to avoid needing NSNumber feature
                            let steps_obj: *mut objc2::runtime::AnyObject =
                                msg_send![data, numberOfSteps];
                            if !steps_obj.is_null() {
                                let steps: i32 = msg_send![steps_obj, intValue];
                                let count = steps as f32;
                                if let Ok(mut latest) = latest_steps.lock() {
                                    *latest = Some(count);
                                }
                            }
                        }
                    },
                );
                let _: () = msg_send![&self.pedometer, startPedometerUpdatesFromDate: &*start_of_day, withHandler: &*handler];

                let start_of_yesterday_ts: f64 = msg_send![&*start_of_day, timeIntervalSince1970];
                let yesterday_start: Retained<NSDate> = msg_send![
                    objc2::class!(NSDate),
                    dateWithTimeIntervalSince1970: start_of_yesterday_ts - 86_400.0
                ];
                let yesterday_handler = block2::RcBlock::new(
                    move |data: *mut CMPedometerData, _error: *mut objc2::runtime::AnyObject| {
                        if data.is_null() {
                            return;
                        }
                        let data = &*data;
                        let steps_obj: *mut objc2::runtime::AnyObject =
                            msg_send![data, numberOfSteps];
                        if !steps_obj.is_null() {
                            let steps: i32 = msg_send![steps_obj, intValue];
                            if let Ok(mut latest) = latest_yesterday_steps.lock() {
                                *latest = Some(steps as f32);
                            }
                        }
                    },
                );
                let _: () = msg_send![
                    &self.pedometer,
                    queryPedometerDataFromDate: &*yesterday_start,
                    toDate: &*start_of_day,
                    withHandler: &*yesterday_handler
                ];
            }
        }
    }

    pub fn disable(&self) {
        unsafe {
            let _: () = msg_send![&self.manager, stopAccelerometerUpdates];
            let _: () = msg_send![&self.manager, stopGyroUpdates];
            let _: () = msg_send![&self.manager, stopMagnetometerUpdates];
            let _: () = msg_send![&self.manager, stopDeviceMotionUpdates];
            let _: () = msg_send![&self.pedometer, stopPedometerUpdates];
        }
    }

    pub fn poll(&self, input: &mut crate::InputManager) {
        unsafe {
            if let Some(data) = self.manager.accelerometerData() {
                let accel = data.acceleration();
                input.handle_accelerometer(accel.x as f32, accel.y as f32, accel.z as f32);
            }

            if let Some(data) = self.manager.gyroData() {
                let rate = data.rotationRate();
                input.handle_gyroscope(rate.x as f32, rate.y as f32, rate.z as f32);
            }

            if let Some(data) = self.manager.magnetometerData() {
                let field = data.magneticField();
                input.handle_magnetometer(field.x as f32, field.y as f32, field.z as f32);
            }

            if let Some(data) = self.manager.deviceMotion() {
                let attitude = data.attitude();
                let quat = attitude.quaternion();
                input.handle_rotation(quat.x as f32, quat.y as f32, quat.z as f32, quat.w as f32);
            }
        }

        if let Ok(steps) = self.latest_steps.lock() {
            if let Some(count) = *steps {
                input.handle_step_counter(count);
                if let Ok(mut prev) = self.previous_steps.lock() {
                    if count > *prev {
                        input.handle_step_detector();
                    }
                    *prev = count;
                }
            }
        }

        if let Ok(steps) = self.latest_yesterday_steps.lock() {
            if let Some(count) = *steps {
                input.handle_yesterday_step_counter(count);
            }
        }
    }
}
