use std::{
    sync::mpsc::{SendError, Sender},
    thread::{self, JoinHandle},
    time::Instant,
};

use crossterm::event::{self, Event, KeyEvent, KeyEventKind};

use falling_tetromino_engine::Button;

use crate::keybinds::{normalize, Keybinds};

pub enum LiveTermSignal {
    RecognizedButton(Button, KeyEventKind),
    RawEvent(Event),
}

pub fn spawn(
    input_sender: Sender<(LiveTermSignal, Instant)>,
    keybinds: Keybinds,
    is_stop_key: impl Fn(crossterm::event::KeyCode, crossterm::event::KeyModifiers) -> bool
        + Send
        + 'static,
) -> JoinHandle<()> {
    thread::spawn(move || {
        'detect_events: loop {
            // Read event.
            match event::read() {
                Ok(event) => {
                    let timestamp = Instant::now();

                    let mut stop_thread = false;

                    let signal = match event {
                        Event::Key(KeyEvent {
                            code,
                            modifiers,
                            kind,
                            ..
                        }) => {
                            let is_press_or_repeat = matches!(
                                kind,
                                event::KeyEventKind::Press | event::KeyEventKind::Repeat
                            );

                            if is_press_or_repeat && is_stop_key(code, modifiers) {
                                stop_thread = true;
                            }

                            match keybinds.get(&normalize((code, modifiers))) {
                                // No binding: Just send directly transmit whatever the event was.
                                None => LiveTermSignal::RawEvent(event),

                                // Binding found: send button un-/press.
                                Some(&button) => LiveTermSignal::RecognizedButton(button, kind),
                            }
                        }

                        // Not a key event, just send directly.
                        _ => LiveTermSignal::RawEvent(event),
                    };

                    // Send signal.
                    match input_sender.send((signal, timestamp)) {
                        Ok(()) => {}
                        Err(SendError(_event_which_failed_to_transmit)) => {
                            break 'detect_events;
                        }
                    }

                    if stop_thread {
                        break 'detect_events;
                    }
                }

                // FIXME: Handle io::Error? If not, why not?
                Err(_e) => {}
            }
        }
    })
}
