// Extra include-based coverage for mouse aggregator branches.
//
// Scope: keep additional branch assertions in a separate file so each test
// module stays under the 500 LOC contract.
// Targets: `client/src/input/mouse.rs`.
// Why: keep branch coverage growing without violating test module size contracts.

#[allow(warnings)]
mod mouse_contract_extra {
    include!(env!("LESAVKA_CLIENT_MOUSE_SRC"));

    use serial_test::serial;
    use std::path::PathBuf;
    use std::thread;

    fn open_virtual_node(vdev: &mut evdev::uinput::VirtualDevice) -> Option<PathBuf> {
        for _ in 0..40 {
            if let Ok(mut nodes) = vdev.enumerate_dev_nodes_blocking() {
                if let Some(Ok(path)) = nodes.next() {
                    return Some(path);
                }
            }
            thread::sleep(std::time::Duration::from_millis(10));
        }
        None
    }

    fn open_virtual_device(vdev: &mut evdev::uinput::VirtualDevice) -> Option<evdev::Device> {
        let node = open_virtual_node(vdev)?;
        let dev = evdev::Device::open(node).ok()?;
        dev.set_nonblocking(true).ok()?;
        Some(dev)
    }

    fn open_any_mouse_device() -> Option<evdev::Device> {
        let entries = std::fs::read_dir("/dev/input").ok()?;
        for entry in entries.flatten() {
            let path = entry.path();
            let name = path.file_name()?.to_string_lossy();
            if !name.starts_with("event") {
                continue;
            }
            let dev = evdev::Device::open(path).ok()?;
            let _ = dev.set_nonblocking(true);
            let rel_mouse = dev
                .supported_relative_axes()
                .map(|axes| {
                    axes.contains(evdev::RelativeAxisCode::REL_X)
                        && axes.contains(evdev::RelativeAxisCode::REL_Y)
                })
                .unwrap_or(false)
                && dev
                    .supported_keys()
                    .map(|keys| {
                        keys.contains(evdev::KeyCode::BTN_LEFT)
                            || keys.contains(evdev::KeyCode::BTN_RIGHT)
                    })
                    .unwrap_or(false);
            let abs_touch = dev
                .supported_absolute_axes()
                .map(|axes| {
                    axes.contains(evdev::AbsoluteAxisCode::ABS_X)
                        || axes.contains(evdev::AbsoluteAxisCode::ABS_MT_POSITION_X)
                })
                .unwrap_or(false);
            if rel_mouse || abs_touch {
                return Some(dev);
            }
        }
        None
    }

    fn build_relative_mouse(name: &str) -> Option<(evdev::uinput::VirtualDevice, evdev::Device)> {
        let mut keys = evdev::AttributeSet::<evdev::KeyCode>::new();
        keys.insert(evdev::KeyCode::BTN_LEFT);
        keys.insert(evdev::KeyCode::BTN_RIGHT);
        keys.insert(evdev::KeyCode::BTN_MIDDLE);

        let mut rel = evdev::AttributeSet::<evdev::RelativeAxisCode>::new();
        rel.insert(evdev::RelativeAxisCode::REL_X);
        rel.insert(evdev::RelativeAxisCode::REL_Y);
        rel.insert(evdev::RelativeAxisCode::REL_WHEEL);

        let mut vdev = evdev::uinput::VirtualDevice::builder()
            .ok()?
            .name(name)
            .with_keys(&keys)
            .ok()?
            .with_relative_axes(&rel)
            .ok()?
            .build()
            .ok()?;

        let dev = open_virtual_device(&mut vdev)?;
        Some((vdev, dev))
    }

    #[test]
    #[serial]
    fn set_grab_path_and_slog_behave_across_dev_mode_flags() {
        let Some(dev_true) = open_any_mouse_device().or_else(|| {
            build_relative_mouse("lesavka-include-mouse-slog-true").map(|(_, dev)| dev)
        }) else {
            return;
        };
        let Some(dev_false) = open_any_mouse_device().or_else(|| {
            build_relative_mouse("lesavka-include-mouse-slog-false").map(|(_, dev)| dev)
        }) else {
            return;
        };

        let (tx_true, _rx_true) = tokio::sync::broadcast::channel(4);
        let (tx_false, _rx_false) = tokio::sync::broadcast::channel(4);
        let mut agg_true = MouseAggregator::new(dev_true, true, tx_true);
        let mut agg_false = MouseAggregator::new(dev_false, false, tx_false);

        agg_true.set_grab(false);
        agg_false.set_grab(false);

        let called_true = std::cell::Cell::new(0usize);
        agg_true.slog(|| called_true.set(called_true.get() + 1));
        assert_eq!(called_true.get(), 1);

        let called_false = std::cell::Cell::new(0usize);
        agg_false.slog(|| called_false.set(called_false.get() + 1));
        assert_eq!(called_false.get(), 0);
    }

    #[test]
    #[serial]
    fn flush_covers_dev_mode_send_error_and_send_success_paths() {
        let Some(dev_err) = open_any_mouse_device().or_else(|| {
            build_relative_mouse("lesavka-include-mouse-flush-dev-err").map(|(_, dev)| dev)
        }) else {
            return;
        };
        let Some(dev_ok) = open_any_mouse_device().or_else(|| {
            build_relative_mouse("lesavka-include-mouse-flush-dev-ok").map(|(_, dev)| dev)
        }) else {
            return;
        };

        let (tx_err, rx_err) = tokio::sync::broadcast::channel(1);
        drop(rx_err);
        let mut agg_err = MouseAggregator::new(dev_err, true, tx_err);
        agg_err.buttons = 1;
        agg_err.last_buttons = 0;
        agg_err.next_send = std::time::Instant::now() - std::time::Duration::from_millis(1);
        agg_err.flush();
        assert_eq!(agg_err.last_buttons, 1);

        let (tx_ok, mut rx_ok) = tokio::sync::broadcast::channel(4);
        let mut agg_ok = MouseAggregator::new(dev_ok, true, tx_ok);
        agg_ok.buttons = 2;
        agg_ok.last_buttons = 0;
        agg_ok.dx = 3;
        agg_ok.dy = -2;
        agg_ok.next_send = std::time::Instant::now() - std::time::Duration::from_millis(1);
        agg_ok.flush();
        let pkt = rx_ok.try_recv().expect("flush packet");
        assert_eq!(pkt.data[0], 2);
        assert_eq!(pkt.data[1], 3);
        assert_eq!(pkt.data[2], (-2_i8) as u8);
    }

    #[test]
    #[serial]
    fn process_events_tolerates_idle_nonblocking_device() {
        let Some((_vdev, dev)) = build_relative_mouse("lesavka-include-mouse-idle") else {
            return;
        };
        let (tx, _rx) = tokio::sync::broadcast::channel(4);
        let mut agg = MouseAggregator::new(dev, true, tx);
        agg.process_events();
    }
}
