//! Command definitions — types for keybindings, screen filtering, and command metadata.

use ftui::{KeyCode, Modifiers};

use crate::message::Screen;

// ---------------------------------------------------------------------------
// Key formatting
// ---------------------------------------------------------------------------

/// Format a key code + modifiers as a human-readable string.
pub(crate) fn format_key(code: KeyCode, modifiers: Modifiers) -> String {
    let mut parts = Vec::new();
    if modifiers.contains(Modifiers::CTRL) {
        parts.push("Ctrl");
    }
    if modifiers.contains(Modifiers::ALT) {
        parts.push("Alt");
    }
    if modifiers.contains(Modifiers::SHIFT) {
        parts.push("Shift");
    }
    let key_name = match code {
        KeyCode::Char(c) => c.to_string(),
        KeyCode::Enter => "Enter".to_string(),
        KeyCode::Escape => "Esc".to_string(),
        KeyCode::Tab => "Tab".to_string(),
        KeyCode::Backspace => "Backspace".to_string(),
        KeyCode::Delete => "Del".to_string(),
        KeyCode::Up => "Up".to_string(),
        KeyCode::Down => "Down".to_string(),
        KeyCode::Left => "Left".to_string(),
        KeyCode::Right => "Right".to_string(),
        KeyCode::Home => "Home".to_string(),
        KeyCode::End => "End".to_string(),
        KeyCode::PageUp => "PgUp".to_string(),
        KeyCode::PageDown => "PgDn".to_string(),
        KeyCode::F(n) => format!("F{n}"),
        _ => "?".to_string(),
    };
    parts.push(&key_name);
    // We need to own the joined string.
    let joined: String = parts.join("+");
    joined
}

// ---------------------------------------------------------------------------
// KeyCombo
// ---------------------------------------------------------------------------

/// A keybinding: either a single key or a two-key sequence.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum KeyCombo {
    /// Single key press (e.g., `q`, `Esc`, `Ctrl+P`).
    Single { code: KeyCode, modifiers: Modifiers },
    /// Two-key sequence (e.g., `g` then `i` for go-to-issues).
    Sequence {
        first_code: KeyCode,
        first_modifiers: Modifiers,
        second_code: KeyCode,
        second_modifiers: Modifiers,
    },
}

impl KeyCombo {
    /// Convenience: single key with no modifiers.
    #[must_use]
    pub const fn key(code: KeyCode) -> Self {
        Self::Single {
            code,
            modifiers: Modifiers::NONE,
        }
    }

    /// Convenience: single key with Ctrl modifier.
    #[must_use]
    pub const fn ctrl(code: KeyCode) -> Self {
        Self::Single {
            code,
            modifiers: Modifiers::CTRL,
        }
    }

    /// Convenience: g-prefix sequence (g + char).
    #[must_use]
    pub const fn g_then(c: char) -> Self {
        Self::Sequence {
            first_code: KeyCode::Char('g'),
            first_modifiers: Modifiers::NONE,
            second_code: KeyCode::Char(c),
            second_modifiers: Modifiers::NONE,
        }
    }

    /// Human-readable display string for this key combo.
    #[must_use]
    pub fn display(&self) -> String {
        match self {
            Self::Single { code, modifiers } => format_key(*code, *modifiers),
            Self::Sequence {
                first_code,
                first_modifiers,
                second_code,
                second_modifiers,
            } => {
                let first = format_key(*first_code, *first_modifiers);
                let second = format_key(*second_code, *second_modifiers);
                format!("{first} {second}")
            }
        }
    }

    /// Whether this combo starts with the given key.
    #[must_use]
    pub fn starts_with(&self, code: &KeyCode, modifiers: &Modifiers) -> bool {
        match self {
            Self::Single {
                code: c,
                modifiers: m,
            } => c == code && m == modifiers,
            Self::Sequence {
                first_code,
                first_modifiers,
                ..
            } => first_code == code && first_modifiers == modifiers,
        }
    }
}

// ---------------------------------------------------------------------------
// ScreenFilter
// ---------------------------------------------------------------------------

/// Specifies which screens a command is available on.
#[derive(Debug, Clone)]
pub enum ScreenFilter {
    /// Available on all screens.
    Global,
    /// Available only on specific screens.
    Only(Vec<Screen>),
}

impl ScreenFilter {
    /// Whether the command is available on the given screen.
    #[must_use]
    pub fn matches(&self, screen: &Screen) -> bool {
        match self {
            Self::Global => true,
            Self::Only(screens) => screens.contains(screen),
        }
    }
}

// ---------------------------------------------------------------------------
// CommandDef
// ---------------------------------------------------------------------------

/// Unique command identifier.
pub type CommandId = &'static str;

/// A registered command with its keybinding, help text, and scope.
#[derive(Debug, Clone)]
pub struct CommandDef {
    /// Unique identifier (e.g., "quit", "go_issues").
    pub id: CommandId,
    /// Human-readable label for palette and help overlay.
    pub label: &'static str,
    /// Keybinding (if any).
    pub keybinding: Option<KeyCombo>,
    /// Equivalent `lore` CLI command (for "Show CLI equivalent" feature).
    pub cli_equivalent: Option<&'static str>,
    /// Description for help overlay.
    pub help_text: &'static str,
    /// Short hint for status bar (e.g., "q:quit").
    pub status_hint: &'static str,
    /// Which screens this command is available on.
    pub available_in: ScreenFilter,
    /// Whether this command works in Text input mode.
    pub available_in_text_mode: bool,
}
