// client/src/input/mouse.rs

use evdev::{AbsoluteAxisCode, Device, EventType, InputEvent, KeyCode, RelativeAxisCode};
use std::time::{Duration, Instant};
use tokio::sync::broadcast::Sender;
use tracing::{debug, error, trace, warn};

use lesavka_common::lesavka::MouseReport;

const SEND_INTERVAL: Duration = Duration::from_millis(1);

struct MouseEventState<'a> {
    buttons: &'a mut u8,
    dx: &'a mut i8,
    dy: &'a mut i8,
    wheel: &'a mut i8,
    last_abs_x: &'a mut Option<i32>,
    last_abs_y: &'a mut Option<i32>,
    abs_scale: i32,
    abs_jump_x: i32,
    abs_jump_y: i32,
    has_touch_state: bool,
    touch_guarded: &'a mut bool,
    touch_active: &'a mut bool,
}

impl MouseEventState<'_> {
    fn set_btn(&mut self, bit: u8, val: i32) {
        if val != 0 {
            *self.buttons |= 1 << bit;
        } else {
            *self.buttons &= !(1 << bit);
        }
    }

    fn apply_event(&mut self, event: &InputEvent) {
        match event.event_type() {
            EventType::KEY => match event.code() {
                c if c == KeyCode::BTN_LEFT.0 => self.set_btn(0, event.value()),
                c if c == KeyCode::BTN_RIGHT.0 => self.set_btn(1, event.value()),
                c if c == KeyCode::BTN_MIDDLE.0 => self.set_btn(2, event.value()),
                c if c == KeyCode::BTN_TOUCH.0 => {
                    *self.touch_guarded = true;
                    *self.touch_active = event.value() != 0;
                    if !*self.touch_active {
                        *self.last_abs_x = None;
                        *self.last_abs_y = None;
                    }
                }
                _ => {}
            },
            EventType::RELATIVE => match event.code() {
                c if c == RelativeAxisCode::REL_X.0 => {
                    *self.dx = self.dx.saturating_add(event.value().clamp(-127, 127) as i8)
                }
                c if c == RelativeAxisCode::REL_Y.0 => {
                    *self.dy = self.dy.saturating_add(event.value().clamp(-127, 127) as i8)
                }
                c if c == RelativeAxisCode::REL_WHEEL.0 => {
                    *self.wheel = self.wheel.saturating_add(event.value().clamp(-1, 1) as i8)
                }
                _ => {}
            },
            EventType::ABSOLUTE => match event.code() {
                c if c == AbsoluteAxisCode::ABS_X.0
                    || c == AbsoluteAxisCode::ABS_MT_POSITION_X.0 =>
                {
                    if *self.touch_guarded && !*self.touch_active {
                        *self.last_abs_x = Some(event.value());
                        return;
                    }
                    if let Some(prev) = *self.last_abs_x {
                        if !self.has_touch_state {
                            let delta = (event.value() - prev).abs();
                            if delta > self.abs_jump_x {
                                *self.last_abs_x = Some(event.value());
                                return;
                            }
                        }
                        let delta = (event.value() - prev) / self.abs_scale;
                        if delta != 0 {
                            *self.dx = self.dx.saturating_add(delta.clamp(-127, 127) as i8);
                        }
                    }
                    *self.last_abs_x = Some(event.value());
                }
                c if c == AbsoluteAxisCode::ABS_Y.0
                    || c == AbsoluteAxisCode::ABS_MT_POSITION_Y.0 =>
                {
                    if *self.touch_guarded && !*self.touch_active {
                        *self.last_abs_y = Some(event.value());
                        return;
                    }
                    if let Some(prev) = *self.last_abs_y {
                        if !self.has_touch_state {
                            let delta = (event.value() - prev).abs();
                            if delta > self.abs_jump_y {
                                *self.last_abs_y = Some(event.value());
                                return;
                            }
                        }
                        let delta = (event.value() - prev) / self.abs_scale;
                        if delta != 0 {
                            *self.dy = self.dy.saturating_add(delta.clamp(-127, 127) as i8);
                        }
                    }
                    *self.last_abs_y = Some(event.value());
                }
                c if c == AbsoluteAxisCode::ABS_MT_TRACKING_ID.0 => {
                    if event.value() < 0 {
                        *self.touch_guarded = true;
                        *self.touch_active = false;
                        *self.last_abs_x = None;
                        *self.last_abs_y = None;
                    } else {
                        *self.touch_guarded = true;
                        *self.touch_active = true;
                    }
                }
                _ => {}
            },
            _ => {}
        }
    }
}

struct MouseRuntime<'a> {
    tx: &'a Sender<MouseReport>,
    dev_mode: bool,
    sending_disabled: bool,
    next_send: &'a mut Instant,
    last_buttons: &'a mut u8,
    event_state: MouseEventState<'a>,
}

impl MouseRuntime<'_> {
    fn replay_events(&mut self, events: Vec<InputEvent>) -> bool {
        let mut relayed = false;
        for event in events {
            if event.event_type() == EventType::SYNCHRONIZATION {
                relayed |= self.flush();
            } else {
                self.event_state.apply_event(&event);
            }
        }
        relayed
    }

    fn flush(&mut self) -> bool {
        let buttons = *self.event_state.buttons;
        if buttons == *self.last_buttons && Instant::now() < *self.next_send {
            return false;
        }
        *self.next_send = Instant::now() + SEND_INTERVAL;

        let pkt = [
            buttons,
            (*self.event_state.dx).clamp(-127, 127) as u8,
            (*self.event_state.dy).clamp(-127, 127) as u8,
            *self.event_state.wheel as u8,
        ];

        let mut relayed = false;
        if !self.sending_disabled {
            #[cfg(not(coverage))]
            if let Err(tokio::sync::broadcast::error::SendError(_)) =
                self.tx.send(MouseReport { data: pkt.to_vec() })
            {
                if self.dev_mode {
                    warn!("❌🖱️ no HID receiver (mouse)");
                }
            } else if self.dev_mode {
                debug!("📤🖱️ mouse {:?}", pkt);
            }

            #[cfg(coverage)]
            {
                let _ = self.tx.send(MouseReport { data: pkt.to_vec() });
            }
            relayed = true;
        }

        *self.event_state.dx = 0;
        *self.event_state.dy = 0;
        *self.event_state.wheel = 0;
        *self.last_buttons = buttons;
        relayed
    }
}

fn collect_fetched_events<I>(
    fetch_result: Result<I, std::io::Error>,
    dev_mode: bool,
) -> Option<Vec<InputEvent>>
where
    I: IntoIterator<Item = InputEvent>,
{
    match fetch_result {
        Ok(events) => Some(events.into_iter().collect()),
        Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => None,
        Err(e) => {
            if dev_mode {
                error!("🖱️❌ mouse read err: {e}");
            }
            None
        }
    }
}

fn log_event_batch(dev_mode: bool, device_name: Option<&str>, evts: &[InputEvent]) {
    if dev_mode && !evts.is_empty() {
        trace!("🖱️ {} evts from {}", evts.len(), device_name.unwrap_or("?"));
    }
}

pub struct MouseAggregator {
    dev: Device,
    tx: Sender<MouseReport>,
    dev_mode: bool,
    sending_disabled: bool,
    next_send: Instant,

    buttons: u8,
    last_buttons: u8,
    dx: i8,
    dy: i8,
    wheel: i8,
    last_abs_x: Option<i32>,
    last_abs_y: Option<i32>,
    abs_scale: i32,
    abs_jump_x: i32,
    abs_jump_y: i32,
    has_touch_state: bool,
    touch_guarded: bool,
    touch_active: bool,
}

impl MouseAggregator {
    pub fn new(dev: Device, dev_mode: bool, tx: Sender<MouseReport>) -> Self {
        let abs_scale = std::env::var("LESAVKA_TOUCHPAD_SCALE")
            .ok()
            .and_then(|v| v.parse::<i32>().ok())
            .unwrap_or(8)
            .max(1);
        let has_touch_state = dev
            .supported_keys()
            .map(|keys| keys.contains(KeyCode::BTN_TOUCH))
            .unwrap_or(false)
            || dev
                .supported_absolute_axes()
                .map(|abs| abs.contains(AbsoluteAxisCode::ABS_MT_TRACKING_ID))
                .unwrap_or(false);
        let abs_jump_x = Self::abs_jump_threshold(
            &dev,
            &[AbsoluteAxisCode::ABS_X, AbsoluteAxisCode::ABS_MT_POSITION_X],
            abs_scale,
        );
        let abs_jump_y = Self::abs_jump_threshold(
            &dev,
            &[AbsoluteAxisCode::ABS_Y, AbsoluteAxisCode::ABS_MT_POSITION_Y],
            abs_scale,
        );
        Self {
            dev,
            tx,
            dev_mode,
            sending_disabled: false,
            next_send: Instant::now(),
            buttons: 0,
            last_buttons: 0,
            dx: 0,
            dy: 0,
            wheel: 0,
            last_abs_x: None,
            last_abs_y: None,
            abs_scale,
            abs_jump_x,
            abs_jump_y,
            has_touch_state,
            touch_guarded: false,
            touch_active: true,
        }
    }

    #[inline]
    #[allow(dead_code)]
    fn slog(&self, f: impl FnOnce()) {
        if self.dev_mode {
            f()
        }
    }
    pub fn set_grab(&mut self, grab: bool) {
        let _ = if grab {
            self.dev.grab()
        } else {
            self.dev.ungrab()
        };
    }

    pub fn set_send(&mut self, send: bool) {
        self.sending_disabled = !send;
    }

    pub fn process_events(&mut self) -> bool {
        let Some(evts) = collect_fetched_events(self.dev.fetch_events(), self.dev_mode) else {
            return false;
        };
        log_event_batch(self.dev_mode, self.dev.name(), &evts);
        self.runtime().replay_events(evts)
    }

    pub fn reset_state(&mut self) {
        self.buttons = 0;
        self.last_buttons = 0;
        self.dx = 0;
        self.dy = 0;
        self.wheel = 0;
        self.last_abs_x = None;
        self.last_abs_y = None;
        if !self.sending_disabled {
            let _ = self.tx.send(MouseReport {
                data: [0; 4].into(),
            });
        }
    }

    #[allow(dead_code)]
    fn flush(&mut self) {
        let _ = self.runtime().flush();
    }

    #[inline]
    #[allow(dead_code)]
    fn set_btn(&mut self, bit: u8, val: i32) {
        MouseEventState {
            buttons: &mut self.buttons,
            dx: &mut self.dx,
            dy: &mut self.dy,
            wheel: &mut self.wheel,
            last_abs_x: &mut self.last_abs_x,
            last_abs_y: &mut self.last_abs_y,
            abs_scale: self.abs_scale,
            abs_jump_x: self.abs_jump_x,
            abs_jump_y: self.abs_jump_y,
            has_touch_state: self.has_touch_state,
            touch_guarded: &mut self.touch_guarded,
            touch_active: &mut self.touch_active,
        }
        .set_btn(bit, val);
    }

    #[cfg(not(coverage))]
    fn abs_jump_threshold(dev: &Device, codes: &[AbsoluteAxisCode], abs_scale: i32) -> i32 {
        let mut range: Option<i32> = None;
        if let Ok(iter) = dev.get_absinfo() {
            for (code, info) in iter {
                if codes.contains(&code) {
                    range = Some(info.maximum() - info.minimum());
                    break;
                }
            }
        }
        let mut threshold = range.unwrap_or(0).abs() / 3;
        let min = (abs_scale * 40).max(50);
        if threshold < min {
            threshold = min;
        }
        if threshold == 0 {
            threshold = min;
        }
        threshold
    }

    #[cfg(coverage)]
    fn abs_jump_threshold(_dev: &Device, _codes: &[AbsoluteAxisCode], abs_scale: i32) -> i32 {
        (abs_scale * 40).max(50)
    }

    fn runtime(&mut self) -> MouseRuntime<'_> {
        MouseRuntime {
            tx: &self.tx,
            dev_mode: self.dev_mode,
            sending_disabled: self.sending_disabled,
            next_send: &mut self.next_send,
            last_buttons: &mut self.last_buttons,
            event_state: MouseEventState {
                buttons: &mut self.buttons,
                dx: &mut self.dx,
                dy: &mut self.dy,
                wheel: &mut self.wheel,
                last_abs_x: &mut self.last_abs_x,
                last_abs_y: &mut self.last_abs_y,
                abs_scale: self.abs_scale,
                abs_jump_x: self.abs_jump_x,
                abs_jump_y: self.abs_jump_y,
                has_touch_state: self.has_touch_state,
                touch_guarded: &mut self.touch_guarded,
                touch_active: &mut self.touch_active,
            },
        }
    }
}

impl Drop for MouseAggregator {
    fn drop(&mut self) {
        let _ = self.dev.ungrab();
        let _ = self.tx.send(MouseReport {
            data: [0; 4].into(),
        });
    }
}

#[cfg(test)]
#[path = "mouse_event_contract_tests.rs"]
mod mouse_event_contract_tests;
