// client/src/input/keyboard.rs

use evdev::{Device, EventType, InputEvent, KeyCode};
use std::{
    collections::HashSet,
    sync::atomic::{AtomicU32, AtomicU64, Ordering},
    time::{Duration, SystemTime, UNIX_EPOCH},
};
use tokio::sync::broadcast::Sender;
use tokio::sync::mpsc::UnboundedSender;
use tracing::{debug, error, trace};

use lesavka_common::lesavka::KeyboardReport;

use super::keymap::{is_modifier, keycode_to_usage};
use lesavka_common::hid::append_char_reports;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct KeyboardEventUpdate {
    pub code: KeyCode,
    pub value: i32,
    pub swallowed: bool,
    pub report: [u8; 8],
}

pub struct KeyboardAggregator {
    dev: Device,
    tx: Sender<KeyboardReport>,
    dev_mode: bool,
    sending_disabled: bool,
    paste_enabled: bool,
    paste_rpc_enabled: bool,
    paste_tx: Option<UnboundedSender<String>>,
    paste_chord_armed: bool,
    paste_chord_consumed: bool,
    pressed_keys: HashSet<KeyCode>,
    recent_key_presses: HashSet<KeyCode>,
}

/*───────── helpers ───────────────────────────────────────────────────*/
/// Monotonically-increasing ID that can be logged on server & client.
static SEQ: AtomicU32 = AtomicU32::new(0);
static LAST_PASTE_MS: AtomicU64 = AtomicU64::new(0);

fn update_pressed_keys(pressed_keys: &mut HashSet<KeyCode>, code: KeyCode, value: i32) {
    if value == 0 {
        pressed_keys.remove(&code);
    } else if value > 0 {
        pressed_keys.insert(code);
    }
}

fn debounce_gate(last_paste_ms: &AtomicU64, now_ms: u64, debounce_ms: u64) -> bool {
    if debounce_ms == 0 {
        last_paste_ms.store(now_ms, Ordering::Relaxed);
        return true;
    }
    let last = last_paste_ms.load(Ordering::Relaxed);
    let allowed = now_ms.saturating_sub(last) >= debounce_ms;
    last_paste_ms.store(now_ms, Ordering::Relaxed);
    allowed
}

impl KeyboardAggregator {
    pub fn new(
        dev: Device,
        dev_mode: bool,
        tx: Sender<KeyboardReport>,
        paste_tx: Option<UnboundedSender<String>>,
    ) -> Self {
        let _ = dev.set_nonblocking(true);
        Self {
            dev,
            tx,
            dev_mode,
            sending_disabled: false,
            paste_enabled: std::env::var("LESAVKA_CLIPBOARD_PASTE")
                .map(|v| v != "0")
                .unwrap_or(true),
            paste_rpc_enabled: paste_rpc_enabled_from_env(),
            paste_tx,
            paste_chord_armed: false,
            paste_chord_consumed: false,
            pressed_keys: HashSet::new(),
            recent_key_presses: HashSet::new(),
        }
    }

    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 send_empty_report(&self) {
        self.send_report([0; 8]);
    }

    pub fn process_events(&mut self) {
        for update in self.drain_key_updates() {
            if update.swallowed {
                continue;
            }
            self.emit_live_report(update.code, update.value, update.report);
        }
    }

    fn build_report(&self) -> [u8; 8] {
        build_keyboard_report(self.pressed_keys.iter().copied())
    }

    pub fn has_key(&self, kc: KeyCode) -> bool {
        self.pressed_keys.contains(&kc)
    }

    pub fn take_key_activation(&mut self, kc: KeyCode) -> bool {
        self.has_key(kc) || self.recent_key_presses.remove(&kc)
    }

    pub fn pressed_keys_snapshot(&self) -> Vec<KeyCode> {
        self.pressed_keys.iter().copied().collect()
    }

    pub fn sending_enabled(&self) -> bool {
        !self.sending_disabled
    }

    pub fn magic_grab(&self) -> bool {
        self.has_key(KeyCode::KEY_LEFTCTRL)
            && self.has_key(KeyCode::KEY_LEFTSHIFT)
            && self.has_key(KeyCode::KEY_G)
    }

    pub fn magic_left(&self) -> bool {
        self.has_key(KeyCode::KEY_LEFTCTRL)
            && self.has_key(KeyCode::KEY_LEFTSHIFT)
            && self.has_key(KeyCode::KEY_LEFT)
    }

    pub fn magic_right(&self) -> bool {
        self.has_key(KeyCode::KEY_LEFTCTRL)
            && self.has_key(KeyCode::KEY_LEFTSHIFT)
            && self.has_key(KeyCode::KEY_RIGHT)
    }

    pub fn magic_kill(&self) -> bool {
        self.has_key(KeyCode::KEY_LEFTCTRL) && self.has_key(KeyCode::KEY_ESC)
    }

    pub fn reset_state(&mut self) {
        if self.pressed_keys.is_empty() {
            self.recent_key_presses.clear();
            self.send_empty_report();
            return;
        }
        self.pressed_keys.clear();
        self.recent_key_presses.clear();
        self.send_empty_report();
    }

    fn send_report(&self, report: [u8; 8]) {
        if self.sending_disabled {
            return;
        }
        send_keyboard_report(&self.tx, report);
    }

    fn emit_live_report(&self, code: KeyCode, value: i32, report: [u8; 8]) {
        if self.sending_disabled {
            return;
        }
        emit_live_keyboard_report(&self.tx, code, value, report);
    }

    pub fn drain_key_updates(&mut self) -> Vec<KeyboardEventUpdate> {
        self.recent_key_presses.clear();
        let events = self.fetch_events();
        if self.dev_mode && !events.is_empty() {
            trace!(
                "⌨️ {} kbd evts from {}",
                events.len(),
                self.dev.name().unwrap_or("?")
            );
        }

        let mut updates = Vec::with_capacity(events.len());
        for ev in events {
            if ev.event_type() != EventType::KEY {
                continue;
            }
            let code = KeyCode::new(ev.code());
            let value = ev.value();
            update_pressed_keys(&mut self.pressed_keys, code, value);
            if value == 1 {
                self.recent_key_presses.insert(code);
            }

            let swallowed = self.try_handle_paste_event(code, value);
            let report = self.build_report();
            let id = SEQ.fetch_add(1, Ordering::Relaxed);
            if self.dev_mode {
                debug!(seq = id, ?report, code = ?code, value, swallowed, "kbd");
            }
            updates.push(KeyboardEventUpdate {
                code,
                value,
                swallowed,
                report,
            });
        }
        updates
    }

    #[cfg(coverage)]
    fn try_handle_paste_event(&mut self, code: KeyCode, value: i32) -> bool {
        if self.paste_chord_consumed {
            if code == KeyCode::KEY_V && value == 0 {
                self.paste_chord_consumed = false;
                self.paste_chord_armed = false;
            }
            self.send_empty_report();
            return true;
        }

        if self.paste_enabled && code == KeyCode::KEY_V && value == 1 && self.paste_chord_active() {
            self.paste_chord_armed = true;
            if self.paste_debounced() {
                self.consume_paste_chord();
                self.paste_chord_consumed = true;
                self.paste_chord_armed = false;
                let _ = self.paste_rpc_enabled && self.paste_via_rpc();
                self.paste_clipboard();
            }
            self.send_empty_report();
            return true;
        }

        if self.paste_chord_armed && (code == KeyCode::KEY_V || is_paste_modifier(code)) {
            self.send_empty_report();
            return true;
        }

        false
    }

    #[cfg(not(coverage))]
    fn try_handle_paste_event(&mut self, code: KeyCode, value: i32) -> bool {
        if !self.paste_enabled {
            return false;
        }

        // Once a paste chord is consumed, swallow any KEY_V repeats/releases
        // until KEY_V is released to prevent leaking a literal 'v'/'V'.
        if self.paste_chord_consumed {
            if code == KeyCode::KEY_V {
                if value == 0 {
                    self.paste_chord_consumed = false;
                    self.paste_chord_armed = false;
                }
                self.send_empty_report();
                return true;
            }
            return false;
        }

        // Only intercept the complete configured Lesavka paste chord. Plain
        // app shortcuts such as Ctrl+V must keep travelling to the remote HID.
        if code == KeyCode::KEY_V && value == 1 && self.paste_chord_active() {
            self.paste_chord_armed = true;
        }

        if !self.paste_chord_armed {
            return false;
        }

        if self.paste_chord_active() {
            if !self.paste_debounced() {
                self.send_empty_report();
                return true;
            }
            self.consume_paste_chord();
            self.paste_chord_consumed = true;
            self.paste_chord_armed = false;
            if self.paste_rpc_enabled && self.paste_via_rpc() {
                return true;
            }
            self.paste_clipboard();
            return true;
        }

        // Chord armed but not complete: swallow V/modifier events so no junk reaches target.
        if code == KeyCode::KEY_V || is_paste_modifier(code) {
            if code == KeyCode::KEY_V && value == 0 {
                // Aborted/incomplete chord (ex: Ctrl+V only): reset state.
                self.paste_chord_armed = false;
            }
            self.send_empty_report();
            return true;
        }

        false
    }

    fn consume_paste_chord(&mut self) {
        self.pressed_keys.remove(&KeyCode::KEY_V);
        self.pressed_keys.remove(&KeyCode::KEY_LEFTCTRL);
        self.pressed_keys.remove(&KeyCode::KEY_RIGHTCTRL);
        self.pressed_keys.remove(&KeyCode::KEY_LEFTALT);
        self.pressed_keys.remove(&KeyCode::KEY_RIGHTALT);
        self.send_empty_report();
    }

    fn paste_chord_active(&self) -> bool {
        let chord = std::env::var("LESAVKA_CLIPBOARD_CHORD")
            .unwrap_or_else(|_| "ctrl+alt+v".into())
            .to_ascii_lowercase();
        let have_ctrl = self.has_key(KeyCode::KEY_LEFTCTRL) || self.has_key(KeyCode::KEY_RIGHTCTRL);
        let have_alt = self.has_key(KeyCode::KEY_LEFTALT) || self.has_key(KeyCode::KEY_RIGHTALT);
        if chord == "ctrl+v" {
            have_ctrl
        } else {
            have_ctrl && have_alt
        }
    }

    fn paste_debounced(&self) -> bool {
        let debounce_ms = std::env::var("LESAVKA_CLIPBOARD_DEBOUNCE_MS")
            .ok()
            .and_then(|v| v.parse::<u64>().ok())
            .unwrap_or(250);
        let now_ms = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap_or_default()
            .as_millis() as u64;
        debounce_gate(&LAST_PASTE_MS, now_ms, debounce_ms)
    }

    #[cfg(coverage)]
    fn paste_clipboard(&self) {
        let text = read_clipboard_text().unwrap_or_default();
        let max = std::env::var("LESAVKA_CLIPBOARD_MAX")
            .ok()
            .and_then(|v| v.parse::<usize>().ok())
            .unwrap_or(4096);

        for c in text.chars().take(max) {
            let mut reports = Vec::with_capacity(4);
            if append_char_reports(&mut reports, c) {
                for report in reports {
                    self.send_report(report);
                }
            }
        }
    }

    #[cfg(not(coverage))]
    fn paste_clipboard(&self) {
        let text = match read_clipboard_text() {
            Some(t) if !t.is_empty() => t,
            Some(_) => {
                tracing::warn!("📋 clipboard empty");
                return;
            }
            None => {
                tracing::warn!("📋 clipboard read failed");
                return;
            }
        };
        let max = std::env::var("LESAVKA_CLIPBOARD_MAX")
            .ok()
            .and_then(|v| v.parse::<usize>().ok())
            .unwrap_or(4096);
        let delay_ms = std::env::var("LESAVKA_CLIPBOARD_DELAY_MS")
            .ok()
            .and_then(|v| v.parse::<u64>().ok())
            .unwrap_or(8);
        let delay = Duration::from_millis(delay_ms);

        tracing::info!(
            "📋 pasting {} chars over HID with {}ms inter-report delay",
            text.chars().count().min(max),
            delay_ms
        );

        for c in text.chars().take(max) {
            let mut reports = Vec::with_capacity(4);
            if append_char_reports(&mut reports, c) {
                for report in reports {
                    self.send_report(report);
                    if delay_ms > 0 {
                        std::thread::sleep(delay);
                    }
                }
            }
        }
    }

    fn paste_via_rpc(&self) -> bool {
        let Some(tx) = self.paste_tx.as_ref() else {
            return false;
        };
        let text = match read_clipboard_text() {
            Some(t) if !t.is_empty() => t,
            _ => {
                return true;
            }
        };
        tx.send(text).is_ok()
    }

    pub fn trigger_clipboard_paste(&mut self) {
        if !self.paste_enabled {
            tracing::warn!(
                "📋 launcher requested clipboard paste, but clipboard paste is disabled"
            );
            return;
        }
        self.paste_chord_armed = false;
        self.paste_chord_consumed = false;
        if self.paste_rpc_enabled && self.paste_via_rpc() {
            tracing::info!("📋 clipboard paste forwarded through paste RPC");
            return;
        }
        tracing::info!("📋 clipboard paste falling back to live HID typing");
        self.paste_clipboard();
    }
}
