// Copyright 2026 California Institute of Technology
//
// You should have received a copy of the licensing terms for this
// software included in the file “LICENSE” located in the top-level
// directory of this package. If you did not, you can view a copy at
// http://dcc.ligo.org/M1500244/LICENSE.txt

//! The command stream driving the app: raw terminal events are wrapped as
//! commands, a mode-aware transform (in `app`) turns them into the semantic
//! commands below (or consumes them), and the app then applies those.

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

#[derive(Debug, Clone, PartialEq)]
pub enum Command {
    /// Raw key press, not yet translated.
    KeyboardEvent(KeyEvent),
    /// Raw mouse event, not yet translated.
    MouseEvent(MouseEvent),
    /// Move the selection to the line above / below.
    SelectUp,
    SelectDown,
    /// Move the selection one viewport worth of rows up / down, clamped to
    /// the first / last channel; the viewport follows as usual.
    PageUp,
    PageDown,
    /// Scroll the list viewport this many rows toward the end of the list
    /// (negative: toward the start), leaving the selection where it is;
    /// the mouse-drag scroll.
    ScrollBy(i32),
    Quit,
    /// Copy this text to the system clipboard.
    ValueToClipboard(String),
    TogglePause,
    /// Enter scope mode for the selected channel / return to the list.
    SetScopeMode,
    SetListMode,
    /// Grow / shrink the global history lookback by one step.
    ExpandHistory,
    ShrinkHistory,
    /// Zoom the scope view window in / out by one step.
    ZoomIn,
    ZoomOut,
    /// Pan the scope view window one step toward older / newer data.
    PanLeft,
    PanRight,
    /// Pan the scope view window this many seconds toward older data
    /// (negative: newer); the mouse-drag pan.
    PanBy(f64),
    /// Scale the scope view span by `factor` (< 1 zooms in), keeping the
    /// sample `anchor_secs` behind the newest sample at the same window
    /// fraction; the mouse right-drag zoom.
    ZoomAbout {
        factor: f64,
        anchor_secs: f64,
    },
    /// Restore the scope's full-history view.
    ResetView,
    /// The history sample period elapsed: push one sample per channel.
    TimerEvent,
}

impl Command {
    /// Wrap a raw crossterm event.  `None` for events the app ignores
    /// (key releases/repeats, resize, focus, paste).
    pub fn from_event(event: &Event) -> Option<Command> {
        match event {
            Event::Key(key) if key.kind == KeyEventKind::Press => {
                Some(Command::KeyboardEvent(*key))
            }
            Event::Mouse(mouse) => Some(Command::MouseEvent(*mouse)),
            _ => None,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crossterm::event::{KeyCode, KeyModifiers};

    #[test]
    fn key_presses_wrap_but_releases_do_not() {
        let press = KeyEvent::new(KeyCode::Char('q'), KeyModifiers::NONE);
        assert_eq!(
            Command::from_event(&Event::Key(press)),
            Some(Command::KeyboardEvent(press))
        );

        let mut release = press;
        release.kind = KeyEventKind::Release;
        assert_eq!(Command::from_event(&Event::Key(release)), None);
    }

    #[test]
    fn non_input_events_are_ignored() {
        assert_eq!(Command::from_event(&Event::Resize(80, 24)), None);
        assert_eq!(Command::from_event(&Event::FocusGained), None);
    }
}
