//! 应用级宏，用于压缩样板代码、提升可读性。
//!
//! - [`key!`]: 压缩 crossterm `KeyEvent` 的匹配。
//! - [`cmd!`]: 压缩 `Command::new` + 链式 `.arg` 样板。
//! - [`log!`]: 向应用内部日志缓冲写入（TUI 内不能用 `println!`）。

/// 匹配 crossterm `KeyEvent`，避免每次写冗长的 `KeyCode` / `KeyModifiers` 组合。
///
/// # 用法
/// ```
/// use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
/// use mac_route::key;
///
/// let ev = KeyEvent::new(KeyCode::Char('j'), KeyModifiers::NONE);
/// assert!( key!(ev, 'j') );
/// assert!( !key!(ev, 'k') );
/// ```
///
/// 普通按键用字符：`key!(ev, 'j')`；
/// 特殊按键直接用 `KeyCode` 的变体名（PascalCase）：`key!(ev, Enter)`、`key!(ev, Esc)`、
/// `key!(ev, Up)`、`key!(ev, Tab)` ...
///
/// 带修饰键：`key!(ev, ctrl => 'c')`、`key!(ev, ctrl => Enter)`。
/// 修饰键别名：`ctrl` / `alt` / `shift` / `super` / `meta`。
#[macro_export]
macro_rules! key {
    // 裸字符键（无修饰）：key!(ev, 'j')
    ($ev:expr, $ch:literal) => {
        matches!(
            $ev,
            crossterm::event::KeyEvent {
                code: crossterm::event::KeyCode::Char($ch),
                modifiers: crossterm::event::KeyModifiers::NONE,
                ..
            }
        )
    };

    // ─── 修饰键 → KeyModifiers 常量映射 ───
    // 用别名让调用方写 ctrl/alt/shift，内部映射到 crossterm 的常量名。
    (ctrl) => { crossterm::event::KeyModifiers::CONTROL };
    (alt) => { crossterm::event::KeyModifiers::ALT };
    (shift) => { crossterm::event::KeyModifiers::SHIFT };
    (super) => { crossterm::event::KeyModifiers::SUPER };
    (meta) => { crossterm::event::KeyModifiers::META };

    // 修饰键 + 字符：key!(ev, ctrl => 'c')
    ($ev:expr, $mod:tt => $ch:literal) => {
        matches!(
            $ev,
            crossterm::event::KeyEvent {
                code: crossterm::event::KeyCode::Char($ch),
                modifiers: $crate::key!($mod),
                ..
            }
        )
    };

    // 修饰键 + 特殊键：key!(ev, ctrl => Enter)
    ($ev:expr, $mod:tt => $name:ident) => {
        matches!(
            $ev,
            crossterm::event::KeyEvent {
                code: crossterm::event::KeyCode::$name,
                modifiers: $crate::key!($mod),
                ..
            }
        )
    };

    // 裸特殊键（无修饰）：key!(ev, Enter)、key!(ev, Esc)、key!(ev, Up) ...
    // 放在最后，避免与上面的"别名"规则冲突。
    ($ev:expr, $name:ident) => {
        matches!(
            $ev,
            crossterm::event::KeyEvent {
                code: crossterm::event::KeyCode::$name,
                modifiers: crossterm::event::KeyModifiers::NONE,
                ..
            }
        )
    };
}

/// 构造一个 `std::process::Command` 并一次性压入多个参数。
///
/// 形参可以是：
/// - 字面量：`"string with space"`、`123`
/// - 以 `-` 开头的 flag：`-getadditionalroutes`
/// - 标识符（局部变量，会被当作表达式求值）
///
/// # 例子
/// ```
/// use mac_route::cmd;
/// let service = "Wi-Fi";
/// let c = cmd!(networksetup -getadditionalroutes service);
/// ```
#[macro_export]
macro_rules! cmd {
    // 入口：程序名（ident）+ 剩余 token 树。
    ($prog:ident $($args:tt)*) => {{
        let mut __c = std::process::Command::new(stringify!($prog));
        $crate::cmd__args!(__c $($args)*);
        __c
    }};
}

/// `cmd!` 内部：逐个消化参数 token。
#[macro_export]
macro_rules! cmd__args {
    // 终止
    ($c:ident) => {};
    // 以 `-` 开头的 flag：`-getadditionalroutes` → 把 "-" 和 ident 拼成字符串
    ($c:ident - $name:ident $($rest:tt)*) => {
        {
            let mut __s = std::stringify!($name).to_string();
            __s.insert(0, '-');
            $c.arg(__s);
        }
        $crate::cmd__args!($c $($rest)*);
    };
    // 字面量参数（字符串/数字/字符）
    ($c:ident $lit:literal $($rest:tt)*) => {
        $c.arg($lit);
        $crate::cmd__args!($c $($rest)*);
    };
    // 其余 token（标识符/表达式）—— 用 tt 收一个
    ($c:ident $first:tt $($rest:tt)*) => {
        $c.arg($first);
        $crate::cmd__args!($c $($rest)*);
    };
}

/// 向 `App` 的日志缓冲追加一条消息。
///
/// 第一个参数必须是可变 `&mut App`，后面是 `format!` 风格的参数。
#[macro_export]
macro_rules! log {
    ($app:expr, $($arg:tt)+) => {
        $app.push_log(format!($($arg)+))
    };
}

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

    #[test]
    fn key_char_matches() {
        let ev = KeyEvent::new(KeyCode::Char('j'), KeyModifiers::NONE);
        assert!(key!(ev, 'j'));
        assert!(!key!(ev, 'k'));
    }

    #[test]
    fn key_special_matches() {
        assert!(key!(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE), Enter));
        assert!(key!(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE), Esc));
        assert!(key!(KeyEvent::new(KeyCode::Up, KeyModifiers::NONE), Up));
    }

    #[test]
    fn key_modifier_matches() {
        let ev = KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL);
        assert!(key!(ev, ctrl => 'c'));
        assert!(!key!(ev, 'c')); // 裸 'c' 要求无修饰
    }

    #[test]
    fn key_modifier_with_special() {
        let ev = KeyEvent::new(KeyCode::Enter, KeyModifiers::CONTROL);
        assert!(key!(ev, ctrl => Enter));
    }

    #[test]
    fn cmd_builds_args() {
        let c = cmd!(echo -n "hello world");
        let args: Vec<String> = c.get_args().map(|s| s.to_string_lossy().to_string()).collect();
        assert_eq!(args, vec!["-n", "hello world"]);
        assert_eq!(c.get_program().to_string_lossy(), "echo");
    }

    #[test]
    fn cmd_with_flag_and_var() {
        let service = String::from("Wi-Fi");
        let c = cmd!(networksetup -getadditionalroutes service);
        let args: Vec<String> = c.get_args().map(|s| s.to_string_lossy().to_string()).collect();
        assert_eq!(args, vec!["-getadditionalroutes", "Wi-Fi"]);
    }

    #[test]
    fn cmd_long_flag() {
        let c = cmd!(networksetup -listnetworkserviceorder);
        let args: Vec<String> = c.get_args().map(|s| s.to_string_lossy().to_string()).collect();
        assert_eq!(args, vec!["-listnetworkserviceorder"]);
    }
}
