// Focused coverage for keyboard quick-toggle activation edges.
//
// Scope: exercise the keyboard aggregator's recent-press tracking directly.
// Targets: `client/src/input/keyboard.rs`.
// Why: the swap key needs to stay reliable even when a tap begins and ends
// before the next launcher/input poll cycle observes the key state.

mod keymap {
    pub use lesavka_client::input::keymap::*;
}

#[allow(warnings)]
mod keyboard_activation_contract {
    include!(env!("LESAVKA_CLIENT_KEYBOARD_SRC"));

    use evdev::AttributeSet;
    use evdev::uinput::VirtualDevice;
    use serial_test::serial;
    use std::thread;

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

    fn build_keyboard(name: &str) -> Option<(VirtualDevice, evdev::Device)> {
        let mut keys = AttributeSet::<evdev::KeyCode>::new();
        keys.insert(evdev::KeyCode::KEY_A);
        keys.insert(evdev::KeyCode::KEY_ENTER);

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

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

    fn new_aggregator(
        dev: evdev::Device,
    ) -> (
        KeyboardAggregator,
        tokio::sync::broadcast::Receiver<KeyboardReport>,
    ) {
        let (tx, rx) = tokio::sync::broadcast::channel(16);
        (KeyboardAggregator::new(dev, false, tx, None), rx)
    }

    #[test]
    #[serial]
    fn take_key_activation_consumes_a_fast_tap_once() {
        let Some((mut vdev, dev)) = build_keyboard("lesavka-kbd-activation-tap") else {
            return;
        };
        let (mut agg, _rx) = new_aggregator(dev);

        vdev.emit(&[
            evdev::InputEvent::new(evdev::EventType::KEY.0, evdev::KeyCode::KEY_A.0, 1),
            evdev::InputEvent::new(evdev::EventType::KEY.0, evdev::KeyCode::KEY_A.0, 0),
        ])
        .expect("emit key tap");
        thread::sleep(std::time::Duration::from_millis(20));
        agg.process_events();

        assert!(agg.take_key_activation(evdev::KeyCode::KEY_A));
        assert!(!agg.take_key_activation(evdev::KeyCode::KEY_A));
    }

    #[test]
    #[serial]
    fn process_events_clears_stale_recent_key_presses_before_polling() {
        let Some((_vdev, dev)) = build_keyboard("lesavka-kbd-activation-clear") else {
            return;
        };
        let (mut agg, _rx) = new_aggregator(dev);
        agg.recent_key_presses.insert(evdev::KeyCode::KEY_A);

        agg.process_events();

        assert!(!agg.take_key_activation(evdev::KeyCode::KEY_A));
    }

    #[test]
    #[serial]
    fn reset_state_clears_recent_key_presses_even_when_idle() {
        let Some((_vdev, dev)) = build_keyboard("lesavka-kbd-activation-reset") else {
            return;
        };
        let (mut agg, mut rx) = new_aggregator(dev);
        agg.recent_key_presses.insert(evdev::KeyCode::KEY_A);

        agg.reset_state();

        assert!(agg.recent_key_presses.is_empty());
        let pkt = rx.try_recv().expect("empty report after idle reset");
        assert_eq!(pkt.data, vec![0; 8]);
    }
}
