// SPDX-FileCopyrightText: 2023 CELESTIFYX Team
// SPDX-License-Identifier: GPL-3.0-or-later

use std::time::Duration;

use crossterm::event::{
    self,
    Event as CtEvent,
    KeyEvent,
    KeyEventKind,
    MouseEvent,
    MouseEventKind,
    KeyCode,
    KeyModifiers
};

use super::app::{
    ListEnd,
    Mode
};

#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) enum Event {
    FilterChar(char),
    FilterBackspace,
    MoveCursor(i32),
    Jump(ListEnd),
    OpenActionMenu,
    ActionMenuMove(i32),
    ActionMenuConfirm,
    ActionMenuCancel,
    CycleFilter,
    SyncRepos,
    ToggleMultiSelect,
    Quit,
    DismissToast,
    Redraw
}

pub(super) fn map_event(raw: CtEvent, mode: Mode, filter_active: bool) -> Option<Event> {
    match raw {
        CtEvent::Key(key) if key.kind == KeyEventKind::Press => map_key(key, mode, filter_active),
        CtEvent::Key(_) => None, // Ignore key release/repeat events
        CtEvent::Mouse(mouse) => map_mouse(mouse, mode),
        CtEvent::Resize(_, _) => Some(Event::Redraw),
        CtEvent::FocusGained | CtEvent::FocusLost | CtEvent::Paste(_) => None
    }
}

fn map_key(key: KeyEvent, mode: Mode, filter_active: bool) -> Option<Event> {
    if mode == Mode::Running {
        return None;
    }

    if mode == Mode::Toast {
        return Some(Event::DismissToast);
    }

    if key.modifiers.contains(KeyModifiers::CONTROL) && matches!(key.code, KeyCode::Char('c')) {
        return Some(Event::Quit);
    }

    if mode == Mode::ActionMenu {
        return match key.code {
            KeyCode::Char('q') | KeyCode::Esc => Some(Event::ActionMenuCancel),
            KeyCode::Up => Some(Event::ActionMenuMove(-1)),
            KeyCode::Down => Some(Event::ActionMenuMove(1)),
            KeyCode::Enter => Some(Event::ActionMenuConfirm),

            _ => None
        };
    }

    match key.code {
        KeyCode::Char('q') | KeyCode::Esc if !filter_active => Some(Event::Quit),
        KeyCode::Char('s') if !filter_active => Some(Event::SyncRepos),

        KeyCode::Backspace => Some(Event::FilterBackspace),
        KeyCode::Up => Some(Event::MoveCursor(-1)),
        KeyCode::Down => Some(Event::MoveCursor(1)),
        KeyCode::PageUp => Some(Event::MoveCursor(-10)),
        KeyCode::PageDown => Some(Event::MoveCursor(10)),
        KeyCode::Home => Some(Event::Jump(ListEnd::Top)),
        KeyCode::End => Some(Event::Jump(ListEnd::Bottom)),
        KeyCode::Enter => Some(Event::OpenActionMenu),
        KeyCode::Tab => Some(Event::CycleFilter),
        KeyCode::Char(' ') => Some(Event::ToggleMultiSelect),
        KeyCode::Char(c) => Some(Event::FilterChar(c)),

        _ => None
    }
}

fn map_mouse(mouse: MouseEvent, mode: Mode) -> Option<Event> {
    if mode == Mode::Running {
        return None;
    }

    if mode == Mode::Toast {
        return match mouse.kind {
            MouseEventKind::Down(_) | MouseEventKind::Drag(_) | MouseEventKind::ScrollUp | MouseEventKind::ScrollDown => Some(Event::DismissToast),
            _ => None
        };
    }

    match mouse.kind {
        MouseEventKind::ScrollUp => Some(Event::MoveCursor(-1)),
        MouseEventKind::ScrollDown => Some(Event::MoveCursor(1)),
        MouseEventKind::Down(_) | MouseEventKind::Drag(_) | MouseEventKind::Moved | MouseEventKind::Up(_) => None,

        _ => None
    }
}

pub(super) fn poll(timeout: Duration) -> Option<CtEvent> {
    event::poll(timeout).ok().and_then(|ready: bool| if ready {
        event::read().ok()
    } else {
        None
    })
}

pub(super) fn drain_events(timeout: Duration) -> Option<CtEvent> {
    let mut last_event: Option<CtEvent> = None;

    if let Some(ev) = poll(timeout) {
        last_event = Some(ev);
    }

    while event::poll(Duration::ZERO).unwrap_or(false) {
        if let Some(ev) = event::read().ok() {
            last_event = Some(ev);
        }
    }

    last_event
}
