//! Live keyboard InputSource. Translates crossterm key events (Kitty protocol
//! Press/Release where available) into game FrameInputs at frame granularity.

use std::io;

use ratatui::crossterm::event::{
    KeyEvent, KeyEventKind, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags,
    PushKeyboardEnhancementFlags,
};
use ratatui::crossterm::{execute, terminal};

use crate::config::keymap::KeyMap;
use crate::sim::input::{FrameInput, InputKind, InputSource};

/// Accumulates this frame's translated key edges; drained once per frame.
pub struct LiveInput {
    buffer: Vec<FrameInput>,
    keymap: KeyMap,
}

impl LiveInput {
    pub fn new() -> LiveInput {
        LiveInput {
            buffer: Vec::new(),
            keymap: KeyMap::default(),
        }
    }

    pub fn with_keymap(keymap: KeyMap) -> LiveInput {
        LiveInput {
            buffer: Vec::new(),
            keymap,
        }
    }

    /// Push a pre-translated FrameInput directly (used by the raw-input backend and tests).
    pub fn push_frame_input(&mut self, fi: crate::sim::input::FrameInput) {
        self.buffer.push(fi);
    }

    /// Translate and buffer a crossterm key event. Press -> Down, Release -> Up.
    /// Repeat events (auto-repeat / held) and unbound keys are ignored: DAS/ARR is
    /// driven by the engine from the down/up edges, not by repeats.
    pub fn push(&mut self, key: KeyEvent) {
        let kind = match key.kind {
            KeyEventKind::Press => InputKind::Down,
            KeyEventKind::Release => InputKind::Up,
            KeyEventKind::Repeat => return,
        };
        let Some(action) = self.keymap.action(key.code) else {
            return;
        };
        self.buffer.push(FrameInput {
            subframe: 0.0,
            kind,
            action,
            hoisted: false,
        });
    }
}

impl Default for LiveInput {
    fn default() -> Self {
        LiveInput::new()
    }
}

impl InputSource for LiveInput {
    fn frame_inputs(&mut self, _frame: i32) -> Vec<FrameInput> {
        std::mem::take(&mut self.buffer)
    }

    fn has_frame(&self, _frame: i32) -> bool {
        true
    }
}

/// True if the terminal supports the Kitty keyboard protocol (real key release).
pub fn kitty_supported() -> bool {
    terminal::supports_keyboard_enhancement().unwrap_or(false)
}

/// Enable key Release/Repeat reporting (no-op return Ok if unsupported; caller
/// checks `kitty_supported()` first for the degraded-mode notice).
pub fn enable_kitty<W: io::Write>(w: &mut W) -> io::Result<()> {
    execute!(
        w,
        PushKeyboardEnhancementFlags(KeyboardEnhancementFlags::REPORT_EVENT_TYPES)
    )
}

pub fn disable_kitty<W: io::Write>(w: &mut W) -> io::Result<()> {
    execute!(w, PopKeyboardEnhancementFlags)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::sim::input::{Action, InputKind, InputSource};
    use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers};

    fn key(code: KeyCode, kind: KeyEventKind) -> KeyEvent {
        KeyEvent {
            code,
            modifiers: KeyModifiers::NONE,
            kind,
            state: KeyEventState::NONE,
        }
    }

    #[test]
    fn buffers_press_and_release_as_frame_inputs_then_drains() {
        let mut live = LiveInput::new();
        live.push(key(KeyCode::Left, KeyEventKind::Press));
        live.push(key(KeyCode::Char(' '), KeyEventKind::Press));
        let inputs = live.frame_inputs(0);
        assert_eq!(inputs.len(), 2);
        assert_eq!(inputs[0].action, Action::MoveLeft);
        assert_eq!(inputs[0].kind, InputKind::Down);
        assert_eq!(inputs[0].subframe, 0.0);
        assert_eq!(inputs[1].action, Action::HardDrop);
        // Draining empties the buffer.
        assert!(live.frame_inputs(1).is_empty());
    }

    #[test]
    fn release_maps_to_up_and_unbound_repeat_ignored() {
        let mut live = LiveInput::new();
        live.push(key(KeyCode::Left, KeyEventKind::Release));
        live.push(key(KeyCode::Char('q'), KeyEventKind::Press)); // unbound -> ignored
        live.push(key(KeyCode::Left, KeyEventKind::Repeat)); // repeat -> ignored (held state, not an edge)
        let inputs = live.frame_inputs(0);
        assert_eq!(inputs.len(), 1);
        assert_eq!(inputs[0].action, Action::MoveLeft);
        assert_eq!(inputs[0].kind, InputKind::Up);
    }

    #[test]
    fn has_frame_is_always_true() {
        let live = LiveInput::new();
        assert!(live.has_frame(0));
        assert!(live.has_frame(1_000_000));
    }
}
