use super::*;

// -- terminal_name_from_term_program (existing coverage) ------------------

#[test]
fn test_terminal_name_from_term_program() {
    assert_eq!(
        terminal_name_from_term_program("iTerm.app"),
        Some(TerminalName::Iterm2)
    );
    assert_eq!(
        terminal_name_from_term_program("WarpTerminal"),
        Some(TerminalName::WarpTerminal)
    );
    assert_eq!(
        terminal_name_from_term_program("Ghostty"),
        Some(TerminalName::Ghostty)
    );
    assert_eq!(
        terminal_name_from_term_program("Apple_Terminal"),
        Some(TerminalName::AppleTerminal)
    );
    assert_eq!(
        terminal_name_from_term_program("zed"),
        Some(TerminalName::Zed)
    );
    assert_eq!(
        terminal_name_from_term_program("WindowsTerminal"),
        Some(TerminalName::WindowsTerminal)
    );
    assert_eq!(
        terminal_name_from_term_program("rio"),
        Some(TerminalName::Rio)
    );
    assert_eq!(
        terminal_name_from_term_program("otty"),
        Some(TerminalName::Otty)
    );
    assert_eq!(
        terminal_name_from_term_program("Otty"),
        Some(TerminalName::Otty)
    );
    assert_eq!(terminal_name_from_term_program("unknown-term"), None);
}

#[test]
fn brand_otty_from_term_program() {
    let env = env_from(&[("TERM_PROGRAM", "otty")]);
    assert_eq!(detect_terminal_brand_from_env(&env), TerminalName::Otty);
    let env = env_from(&[("TERM_PROGRAM", "Otty")]);
    assert_eq!(detect_terminal_brand_from_env(&env), TerminalName::Otty);
}

#[test]
fn otty_delivers_ime_as_bracketed_paste_only() {
    assert!(TerminalName::Otty.delivers_ime_as_bracketed_paste());
    for brand in [
        TerminalName::AppleTerminal,
        TerminalName::Ghostty,
        TerminalName::Iterm2,
        TerminalName::Unknown,
        TerminalName::WezTerm,
        TerminalName::Kitty,
    ] {
        assert!(
            !brand.delivers_ime_as_bracketed_paste(),
            "{brand:?} must not gate IME bracketed-paste origin"
        );
    }
}

#[test]
fn otty_is_capability_unclassified_like_unknown() {
    assert!(TerminalName::Otty.is_capability_unclassified());
    assert!(TerminalName::Unknown.is_capability_unclassified());
    assert!(!TerminalName::Ghostty.is_capability_unclassified());
    assert!(!TerminalName::AppleTerminal.is_capability_unclassified());
}

#[test]
fn otty_skips_kitty_keyboard_like_unknown() {
    let ctx = TerminalContext {
        brand: TerminalName::Otty,
        env_brand: TerminalName::Otty,
        multiplexer: MultiplexerKind::Undetected,
        ..Default::default()
    };
    assert_eq!(ctx.kitty_skip_reason(), Some("unknown_no_multiplexer"));
    assert!(ctx.shift_enter_unavailable());
}

// -- detect_terminal_brand_from_env (pure) --------------------------------

#[test]
fn brand_ghostty_from_term_program() {
    let env = env_from(&[("TERM_PROGRAM", "Ghostty")]);
    assert_eq!(detect_terminal_brand_from_env(&env), TerminalName::Ghostty);
}

#[test]
fn brand_wezterm_from_version_var() {
    let env = env_from(&[("WEZTERM_VERSION", "20240203")]);
    assert_eq!(detect_terminal_brand_from_env(&env), TerminalName::WezTerm);
}

#[test]
fn brand_iterm2_from_session_id() {
    let env = env_from(&[("ITERM_SESSION_ID", "w0t0p0")]);
    assert_eq!(detect_terminal_brand_from_env(&env), TerminalName::Iterm2);
}

#[test]
fn brand_iterm2_from_lc_terminal_over_ssh() {
    // Only LC_TERMINAL survives the SSH hop; ITERM_SESSION_ID / TERM_PROGRAM do not.
    let env = env_from(&[("LC_TERMINAL", "iTerm2")]);
    assert_eq!(detect_terminal_brand_from_env(&env), TerminalName::Iterm2);
}

#[test]
fn brand_not_iterm2_for_foreign_lc_terminal() {
    // Value-exact gate: a non-iTerm2 LC_TERMINAL value must NOT match
    // (matches on value, not bare presence).
    let env = env_from(&[("LC_TERMINAL", "not-iterm2")]);
    assert_eq!(detect_terminal_brand_from_env(&env), TerminalName::Unknown);
}

#[test]
fn brand_kitty_from_term() {
    let env = env_from(&[("TERM", "xterm-kitty")]);
    assert_eq!(detect_terminal_brand_from_env(&env), TerminalName::Kitty);
}

#[test]
fn brand_alacritty_from_term() {
    let env = env_from(&[("TERM", "alacritty")]);
    assert_eq!(
        detect_terminal_brand_from_env(&env),
        TerminalName::Alacritty
    );
}

#[test]
fn brand_rio_from_term() {
    let env = env_from(&[("TERM", "rio")]);
    assert_eq!(detect_terminal_brand_from_env(&env), TerminalName::Rio);
}

#[test]
fn brand_foot_from_term() {
    let env = env_from(&[("TERM", "foot")]);
    assert_eq!(detect_terminal_brand_from_env(&env), TerminalName::Foot);
}

#[test]
fn brand_foot_extra_from_term() {
    let env = env_from(&[("TERM", "foot-extra")]);
    assert_eq!(detect_terminal_brand_from_env(&env), TerminalName::Foot);
}

#[test]
fn brand_foot_direct_from_term() {
    let env = env_from(&[("TERM", "foot-direct")]);
    assert_eq!(detect_terminal_brand_from_env(&env), TerminalName::Foot);
}

#[test]
fn brand_not_foot_for_unrelated_term() {
    // Over-match guard: a TERM merely containing "foot" is not foot.
    let env = env_from(&[("TERM", "xterm-footbar")]);
    assert_eq!(detect_terminal_brand_from_env(&env), TerminalName::Unknown);
}

#[test]
fn brand_not_foot_for_foot_prefixed_out_of_set_term() {
    // Inverse boundary: a `foot`-prefixed value outside the exact set
    // stays Unknown (pins the exact-set decision over `starts_with`).
    let env = env_from(&[("TERM", "foot-extra-256color")]);
    assert_eq!(detect_terminal_brand_from_env(&env), TerminalName::Unknown);
}

#[test]
fn brand_vte_from_vte_version() {
    let env = env_from(&[("VTE_VERSION", "7402")]);
    assert_eq!(detect_terminal_brand_from_env(&env), TerminalName::Vte);
}

#[test]
fn brand_terminator_from_term_program() {
    let env = env_from(&[("TERM_PROGRAM", "terminator")]);
    assert_eq!(
        detect_terminal_brand_from_env(&env),
        TerminalName::Terminator
    ); // WHY: canonical per detect-terminal
}

#[test]
fn terminator_vte_version_interaction() {
    let env = env_from(&[("TERM_PROGRAM", "terminator"), ("VTE_VERSION", "8200")]);
    let ctx = build_terminal_context_from_env(&env);
    assert_eq!(ctx.brand, TerminalName::Terminator);
    assert!(ctx.is_vte_based()); // WHY: helper covers version + brand
}

#[test]
fn terminator_with_tmux() {
    let env = env_from(&[("TERM_PROGRAM", "terminator"), ("TMUX", "/tmp/tmux")]);
    let ctx = build_terminal_context_from_env(&env);
    assert_eq!(ctx.brand, TerminalName::Terminator);
    assert_eq!(ctx.multiplexer, MultiplexerKind::Tmux);
}

#[test]
fn terminator_over_ssh() {
    let env = env_from(&[("TERM_PROGRAM", "terminator"), ("SSH_TTY", "/dev/pts/0")]);
    let ctx = build_terminal_context_from_env(&env);
    assert_eq!(ctx.brand, TerminalName::Terminator);
    assert!(ctx.is_ssh);
}

#[test]
fn terminator_focus_tracking() {
    let env = env_from(&[("TERM_PROGRAM", "terminator")]);
    let ctx = build_terminal_context_from_env(&env);
    assert!(!matches!(
        ctx.brand,
        TerminalName::AppleTerminal | TerminalName::Unknown
    )); // supports focus like VTE
    assert!(ctx.is_vte_based());
}

#[test]

fn brand_windows_terminal_from_wt_session() {
    let env = env_from(&[("WT_SESSION", "a1b2c3d4-e5f6-7890-abcd-ef1234567890")]);
    assert_eq!(
        detect_terminal_brand_from_env(&env),
        TerminalName::WindowsTerminal
    );
}

#[test]
fn brand_unknown_empty_env() {
    let env = env_from(&[]);
    assert_eq!(detect_terminal_brand_from_env(&env), TerminalName::Unknown);
}

// -- refine_unknown_brand_for_host ---------------------------------------

#[test]
fn refine_unknown_brand_defaults_to_wt_only_on_windows() {
    use super::TerminalName::{Unknown, VsCode, WindowsTerminal};
    use crate::host::HostOs::{Linux, Windows};
    let cases = [
        (Unknown, Windows, WindowsTerminal), // DefTerm handoff: no WT_SESSION
        (Unknown, Linux, Unknown),
        (VsCode, Windows, VsCode), // never override a positively detected brand
    ];
    for (brand, host, expected) in cases {
        assert_eq!(refine_unknown_brand_for_host(brand, host), expected);
    }
}

#[test]
fn env_brand_holds_raw_detection_independent_of_refinement() {
    // `build_*` records the raw detection in both fields.
    let ctx = build_terminal_context_from_env(&env_from(&[]));
    assert_eq!(ctx.brand, TerminalName::Unknown);
    assert_eq!(ctx.env_brand, TerminalName::Unknown);

    // The fallback refines only `brand`; `env_brand` stays raw so the glyphs
    // legacy-console check and shift_enter_unavailable still treat a bare
    // ConHost conservatively.
    let effective = refine_unknown_brand_for_host(ctx.brand, crate::host::HostOs::Windows);
    assert_eq!(effective, TerminalName::WindowsTerminal);
    assert_eq!(ctx.env_brand, TerminalName::Unknown);
}

#[test]
fn mouse_reporting_leaks_only_for_jetbrains_on_windows() {
    use crate::host::HostOs;
    // The reported failure: JediTerm on Windows leaks raw X10 mouse bytes.
    assert!(mouse_reporting_leaks(
        TerminalName::JetBrains,
        HostOs::Windows
    ));
    // JetBrains on macOS/Linux is fine — crossterm's unix parser decodes the
    // reports, so we must NOT downgrade those to minimal.
    assert!(!mouse_reporting_leaks(
        TerminalName::JetBrains,
        HostOs::Macos
    ));
    assert!(!mouse_reporting_leaks(
        TerminalName::JetBrains,
        HostOs::Linux
    ));
    // Other Windows terminals deliver native console mouse records — no leak.
    assert!(!mouse_reporting_leaks(
        TerminalName::WindowsTerminal,
        HostOs::Windows
    ));
    assert!(!mouse_reporting_leaks(TerminalName::Kitty, HostOs::Windows));
}

// -- detect_byobu_from_env ------------------------------------------------

#[test]
fn byobu_tmux_explicit_backend() {
    let env = env_from(&[("BYOBU_BACKEND", "tmux"), ("TMUX", "/tmp/tmux")]);
    assert_eq!(detect_byobu_from_env(&env), Some(ByobuBackend::Tmux));
}

#[test]
fn byobu_screen_explicit_backend() {
    let env = env_from(&[("BYOBU_BACKEND", "screen"), ("STY", "12345.pts-0")]);
    assert_eq!(detect_byobu_from_env(&env), Some(ByobuBackend::Screen));
}

#[test]
fn byobu_inferred_tmux_from_config_dir() {
    let env = env_from(&[
        ("BYOBU_CONFIG_DIR", "/home/user/.byobu"),
        ("TMUX", "/tmp/tmux"),
    ]);
    assert_eq!(detect_byobu_from_env(&env), Some(ByobuBackend::Tmux));
}

#[test]
fn byobu_inferred_screen_from_distro() {
    let env = env_from(&[("BYOBU_DISTRO", "Ubuntu"), ("STY", "12345.pts-0")]);
    assert_eq!(detect_byobu_from_env(&env), Some(ByobuBackend::Screen));
}

#[test]
fn byobu_markers_but_no_mux_markers_returns_none() {
    let env = env_from(&[("BYOBU_CONFIG_DIR", "/home/user/.byobu")]);
    assert_eq!(detect_byobu_from_env(&env), None);
}

#[test]
fn no_byobu_markers_returns_none() {
    let env = env_from(&[("TMUX", "/tmp/tmux")]);
    assert_eq!(detect_byobu_from_env(&env), None);
}

// -- detect_multiplexer_from_env ------------------------------------------

#[test]
fn mux_plain_tmux() {
    let env = env_from(&[("TMUX", "/tmp/tmux-501/default,12345,0")]);
    assert_eq!(detect_multiplexer_from_env(&env), MultiplexerKind::Tmux);
}

#[test]
fn mux_plain_screen() {
    let env = env_from(&[("STY", "12345.pts-0.host")]);
    assert_eq!(detect_multiplexer_from_env(&env), MultiplexerKind::Screen);
}

#[test]
fn mux_zellij() {
    let env = env_from(&[("ZELLIJ", "0")]);
    assert_eq!(detect_multiplexer_from_env(&env), MultiplexerKind::Zellij);
}

#[test]
fn mux_zellij_from_session_name() {
    let env = env_from(&[("ZELLIJ_SESSION_NAME", "my-session")]);
    assert_eq!(detect_multiplexer_from_env(&env), MultiplexerKind::Zellij);
}

#[test]
fn mux_none_empty_env() {
    let env = env_from(&[]);
    assert_eq!(
        detect_multiplexer_from_env(&env),
        MultiplexerKind::Undetected
    );
}

#[test]
fn mux_cmux_from_socket_path() {
    let env = env_from(&[("CMUX_SOCKET_PATH", "/tmp/cmux.sock")]);
    assert_eq!(detect_multiplexer_from_env(&env), MultiplexerKind::Cmux);
}

#[test]
fn mux_cmux_from_panel_id() {
    let env = env_from(&[("CMUX_PANEL_ID", "1")]);
    assert_eq!(detect_multiplexer_from_env(&env), MultiplexerKind::Cmux);
}

#[test]
fn mux_cmux_from_bundle_id() {
    let env = env_from(&[("CMUX_BUNDLE_ID", "com.cmuxterm.app")]);
    assert_eq!(detect_multiplexer_from_env(&env), MultiplexerKind::Cmux);
}

#[test]
fn mux_empty_cmux_socket_alone_is_undetected() {
    // CMUX_SOCKET may be present but empty under cmux; env_get filters empties.
    // Without a non-empty CMUX_* marker, do not classify as Cmux.
    let env = env_from(&[("CMUX_SOCKET", "")]);
    assert_eq!(
        detect_multiplexer_from_env(&env),
        MultiplexerKind::Undetected
    );
}

#[test]
fn mux_tmux_nested_inside_cmux_wins() {
    let env = env_from(&[
        ("TMUX", "/tmp/tmux-501/default,12345,0"),
        ("CMUX_SOCKET_PATH", "/tmp/cmux.sock"),
        ("CMUX_PANEL_ID", "1"),
    ]);
    assert_eq!(detect_multiplexer_from_env(&env), MultiplexerKind::Tmux);
}

// -- ambiguous marker precedence ------------------------------------------

#[test]
fn tmux_beats_zellij_when_both_set() {
    // tmux pane launched from inside Zellij leaves inherited ZELLIJ var.
    let env = env_from(&[("TMUX", "/tmp/tmux-501/default,12345,0"), ("ZELLIJ", "0")]);
    assert_eq!(detect_multiplexer_from_env(&env), MultiplexerKind::Tmux);
}

#[test]
fn byobu_screen_beats_tmux_marker() {
    // BYOBU_BACKEND=screen with a stale TMUX var — Byobu backend wins.
    let env = env_from(&[
        ("BYOBU_BACKEND", "screen"),
        ("TMUX", "/tmp/tmux-old"),
        ("STY", "12345.pts-0"),
    ]);
    assert_eq!(detect_multiplexer_from_env(&env), MultiplexerKind::Screen);
}

#[test]
fn byobu_tmux_explicit_with_sty_stays_tmux() {
    // BYOBU_BACKEND=tmux + stale STY — Byobu backend wins.
    let env = env_from(&[
        ("BYOBU_BACKEND", "tmux"),
        ("STY", "old-screen"),
        ("TMUX", "/tmp/tmux"),
    ]);
    assert_eq!(detect_multiplexer_from_env(&env), MultiplexerKind::Tmux);
}

// -- detect_tmux_meta_from_env --------------------------------------------

#[test]
fn tmux_meta_populated() {
    let env = env_from(&[
        ("TMUX", "/tmp/tmux-501/default,12345,0"),
        ("TMUX_PANE", "%3"),
    ]);
    let meta = detect_tmux_meta_from_env(&env);
    assert_eq!(
        meta.tmux_env.as_deref(),
        Some("/tmp/tmux-501/default,12345,0")
    );
    assert_eq!(meta.tmux_pane.as_deref(), Some("%3"));
}

#[test]
fn tmux_meta_empty_outside_tmux() {
    let env = env_from(&[]);
    let meta = detect_tmux_meta_from_env(&env);
    assert_eq!(meta, TmuxClientMeta::default());
}

// -- build_terminal_context_from_env (integration) ------------------------

#[test]
fn context_plain_terminal() {
    let env = env_from(&[("TERM_PROGRAM", "Ghostty")]);
    let ctx = build_terminal_context_from_env(&env);
    assert_eq!(ctx.brand, TerminalName::Ghostty);
    assert_eq!(ctx.multiplexer, MultiplexerKind::Undetected);
    assert_eq!(ctx.byobu, None);
    assert!(!ctx.is_tmux_backed());
    assert!(!ctx.is_byobu());
}

#[test]
fn context_iterm2_lc_terminal_version_fallback_over_ssh() {
    // Over SSH, TERM_PROGRAM_VERSION is stripped; version gating falls back to
    // iTerm2's SSH-surviving LC_TERMINAL_VERSION.
    let env = env_from(&[("LC_TERMINAL", "iTerm2"), ("LC_TERMINAL_VERSION", "3.5.6")]);
    let ctx = build_terminal_context_from_env(&env);
    assert_eq!(ctx.brand, TerminalName::Iterm2);
    assert_eq!(ctx.term_program_version.as_deref(), Some("3.5.6"));
}

#[test]
fn context_plain_tmux() {
    let env = env_from(&[
        ("TERM_PROGRAM", "iTerm2"),
        ("TMUX", "/tmp/tmux-501/default,12345,0"),
        ("TMUX_PANE", "%0"),
    ]);
    let ctx = build_terminal_context_from_env(&env);
    assert_eq!(ctx.brand, TerminalName::Iterm2);
    assert_eq!(ctx.multiplexer, MultiplexerKind::Tmux);
    assert_eq!(ctx.byobu, None);
    assert!(ctx.is_tmux_backed());
    assert!(!ctx.is_byobu());
    assert_eq!(ctx.tmux_config_path(), "~/.tmux.conf");
    assert_eq!(
        ctx.tmux_meta.tmux_env.as_deref(),
        Some("/tmp/tmux-501/default,12345,0")
    );
}

#[test]
fn context_byobu_tmux() {
    let env = env_from(&[
        ("BYOBU_BACKEND", "tmux"),
        ("TMUX", "/tmp/tmux-501/default,12345,0"),
        ("TMUX_PANE", "%1"),
    ]);
    let ctx = build_terminal_context_from_env(&env);
    assert_eq!(ctx.multiplexer, MultiplexerKind::Tmux);
    assert_eq!(ctx.byobu, Some(ByobuBackend::Tmux));
    assert!(ctx.is_tmux_backed());
    assert!(ctx.is_byobu());
    assert_eq!(ctx.tmux_config_path(), "~/.byobu/.tmux.conf");
}

#[test]
fn context_byobu_screen() {
    let env = env_from(&[("BYOBU_BACKEND", "screen"), ("STY", "12345.pts-0.host")]);
    let ctx = build_terminal_context_from_env(&env);
    assert_eq!(ctx.multiplexer, MultiplexerKind::Screen);
    assert_eq!(ctx.byobu, Some(ByobuBackend::Screen));
    assert!(!ctx.is_tmux_backed());
    assert!(ctx.is_byobu());
}

#[test]
fn context_plain_screen_no_byobu() {
    let env = env_from(&[("STY", "12345.pts-0.host")]);
    let ctx = build_terminal_context_from_env(&env);
    assert_eq!(ctx.multiplexer, MultiplexerKind::Screen);
    assert_eq!(ctx.byobu, None);
    assert!(!ctx.is_byobu());
}

#[test]
fn context_zellij() {
    let env = env_from(&[
        ("ZELLIJ", "0"),
        ("ZELLIJ_SESSION_NAME", "my-session"),
        ("TERM_PROGRAM", "Ghostty"),
    ]);
    let ctx = build_terminal_context_from_env(&env);
    assert_eq!(ctx.brand, TerminalName::Ghostty);
    assert_eq!(ctx.multiplexer, MultiplexerKind::Zellij);
    assert_eq!(ctx.byobu, None);
    assert!(!ctx.is_tmux_backed());
}

#[test]
fn context_tmux_inside_zellij() {
    // tmux launched from Zellij: TMUX is set, inherited ZELLIJ var remains.
    let env = env_from(&[("TMUX", "/tmp/tmux-501/default,12345,0"), ("ZELLIJ", "0")]);
    let ctx = build_terminal_context_from_env(&env);
    assert_eq!(ctx.multiplexer, MultiplexerKind::Tmux);
    assert!(ctx.is_tmux_backed());
}

#[test]
fn context_tmux_meta_not_populated_for_non_tmux() {
    let env = env_from(&[("ZELLIJ", "0")]);
    let ctx = build_terminal_context_from_env(&env);
    assert_eq!(ctx.tmux_meta, TmuxClientMeta::default());
}

#[test]
fn context_ambiguous_byobu_screen_with_tmux() {
    // BYOBU_BACKEND=screen + stale TMUX → screen wins via Byobu backend.
    let env = env_from(&[
        ("BYOBU_BACKEND", "screen"),
        ("TMUX", "/tmp/tmux-old"),
        ("STY", "12345.pts-0"),
    ]);
    let ctx = build_terminal_context_from_env(&env);
    assert_eq!(ctx.multiplexer, MultiplexerKind::Screen);
    assert_eq!(ctx.byobu, Some(ByobuBackend::Screen));
}

#[test]
fn context_empty_env_values_ignored() {
    // Empty string values should not trigger detection.
    let env = env_from(&[("TMUX", ""), ("ZELLIJ", ""), ("STY", "")]);
    let ctx = build_terminal_context_from_env(&env);
    assert_eq!(ctx.multiplexer, MultiplexerKind::Undetected);
}

// =====================================================================
// determine_alt_screen_policy: fullscreen policy matrix
// =====================================================================

fn plain_ctx() -> TerminalContext {
    TerminalContext {
        brand: TerminalName::Ghostty,
        multiplexer: MultiplexerKind::Undetected,
        ..Default::default()
    }
}

fn tmux_ctx() -> TerminalContext {
    TerminalContext {
        brand: TerminalName::Iterm2,
        multiplexer: MultiplexerKind::Tmux,
        tmux_meta: TmuxClientMeta {
            tmux_env: Some("/tmp/tmux-501/default,12345,0".to_owned()),
            tmux_pane: Some("%0".to_owned()),
        },
        ..Default::default()
    }
}

fn zellij_ctx_test() -> TerminalContext {
    TerminalContext {
        brand: TerminalName::Ghostty,
        multiplexer: MultiplexerKind::Zellij,
        ..Default::default()
    }
}

fn screen_ctx() -> TerminalContext {
    TerminalContext {
        brand: TerminalName::Unknown,
        multiplexer: MultiplexerKind::Screen,
        ..Default::default()
    }
}

fn byobu_tmux_ctx() -> TerminalContext {
    TerminalContext {
        brand: TerminalName::Unknown,
        multiplexer: MultiplexerKind::Tmux,
        byobu: Some(ByobuBackend::Tmux),
        tmux_meta: TmuxClientMeta {
            tmux_env: Some("/tmp/tmux".to_owned()),
            tmux_pane: Some("%1".to_owned()),
        },
        ..Default::default()
    }
}

fn byobu_screen_ctx() -> TerminalContext {
    TerminalContext {
        brand: TerminalName::Unknown,
        multiplexer: MultiplexerKind::Screen,
        byobu: Some(ByobuBackend::Screen),
        ..Default::default()
    }
}

// -- Alt-screen policy matrix (all modes × contexts × CLI override) -------

#[derive(Debug)]
struct AltScreenCase {
    name: &'static str,
    cli_no_alt: bool,
    mode: AltScreenMode,
    ctx: TerminalContext,
    control: bool,
    expected: bool,
}

#[test]
fn alt_screen_policy_matrix() {
    let cases = [
        // Auto mode
        AltScreenCase {
            name: "auto_plain_fullscreen",
            cli_no_alt: false,
            mode: AltScreenMode::Auto,
            ctx: plain_ctx(),
            control: false,
            expected: true,
        },
        AltScreenCase {
            name: "auto_tmux_fullscreen",
            cli_no_alt: false,
            mode: AltScreenMode::Auto,
            ctx: tmux_ctx(),
            control: false,
            expected: true,
        },
        AltScreenCase {
            name: "auto_tmux_control_inline",
            cli_no_alt: false,
            mode: AltScreenMode::Auto,
            ctx: tmux_ctx(),
            control: true,
            expected: false,
        },
        AltScreenCase {
            name: "auto_zellij_inline",
            cli_no_alt: false,
            mode: AltScreenMode::Auto,
            ctx: zellij_ctx_test(),
            control: false,
            expected: false,
        },
        AltScreenCase {
            name: "auto_screen_fullscreen",
            cli_no_alt: false,
            mode: AltScreenMode::Auto,
            ctx: screen_ctx(),
            control: false,
            expected: true,
        },
        AltScreenCase {
            name: "auto_byobu_tmux_fullscreen",
            cli_no_alt: false,
            mode: AltScreenMode::Auto,
            ctx: byobu_tmux_ctx(),
            control: false,
            expected: true,
        },
        AltScreenCase {
            name: "auto_byobu_screen_fullscreen",
            cli_no_alt: false,
            mode: AltScreenMode::Auto,
            ctx: byobu_screen_ctx(),
            control: false,
            expected: true,
        },
        AltScreenCase {
            name: "auto_byobu_tmux_control_inline",
            cli_no_alt: false,
            mode: AltScreenMode::Auto,
            ctx: byobu_tmux_ctx(),
            control: true,
            expected: false,
        },
        // Never mode: always inline
        AltScreenCase {
            name: "never_plain_inline",
            cli_no_alt: false,
            mode: AltScreenMode::Never,
            ctx: plain_ctx(),
            control: false,
            expected: false,
        },
        AltScreenCase {
            name: "never_tmux_inline",
            cli_no_alt: false,
            mode: AltScreenMode::Never,
            ctx: tmux_ctx(),
            control: false,
            expected: false,
        },
        AltScreenCase {
            name: "never_screen_inline",
            cli_no_alt: false,
            mode: AltScreenMode::Never,
            ctx: screen_ctx(),
            control: false,
            expected: false,
        },
        AltScreenCase {
            name: "never_zellij_inline",
            cli_no_alt: false,
            mode: AltScreenMode::Never,
            ctx: zellij_ctx_test(),
            control: false,
            expected: false,
        },
        AltScreenCase {
            name: "never_byobu_tmux_inline",
            cli_no_alt: false,
            mode: AltScreenMode::Never,
            ctx: byobu_tmux_ctx(),
            control: false,
            expected: false,
        },
        AltScreenCase {
            name: "never_byobu_screen_inline",
            cli_no_alt: false,
            mode: AltScreenMode::Never,
            ctx: byobu_screen_ctx(),
            control: false,
            expected: false,
        },
        // Always mode: forces fullscreen
        AltScreenCase {
            name: "always_plain_fullscreen",
            cli_no_alt: false,
            mode: AltScreenMode::Always,
            ctx: plain_ctx(),
            control: false,
            expected: true,
        },
        AltScreenCase {
            name: "always_tmux_fullscreen",
            cli_no_alt: false,
            mode: AltScreenMode::Always,
            ctx: tmux_ctx(),
            control: false,
            expected: true,
        },
        AltScreenCase {
            name: "always_tmux_control_fullscreen",
            cli_no_alt: false,
            mode: AltScreenMode::Always,
            ctx: tmux_ctx(),
            control: true,
            expected: true,
        },
        AltScreenCase {
            name: "always_zellij_fullscreen",
            cli_no_alt: false,
            mode: AltScreenMode::Always,
            ctx: zellij_ctx_test(),
            control: false,
            expected: true,
        },
        AltScreenCase {
            name: "always_screen_fullscreen",
            cli_no_alt: false,
            mode: AltScreenMode::Always,
            ctx: screen_ctx(),
            control: false,
            expected: true,
        },
        AltScreenCase {
            name: "always_byobu_screen_fullscreen",
            cli_no_alt: false,
            mode: AltScreenMode::Always,
            ctx: byobu_screen_ctx(),
            control: false,
            expected: true,
        },
        // CLI --no-alt-screen overrides everything
        AltScreenCase {
            name: "cli_no_alt_overrides_auto",
            cli_no_alt: true,
            mode: AltScreenMode::Auto,
            ctx: plain_ctx(),
            control: false,
            expected: false,
        },
        AltScreenCase {
            name: "cli_no_alt_overrides_always",
            cli_no_alt: true,
            mode: AltScreenMode::Always,
            ctx: plain_ctx(),
            control: false,
            expected: false,
        },
        AltScreenCase {
            name: "cli_no_alt_overrides_never",
            cli_no_alt: true,
            mode: AltScreenMode::Never,
            ctx: plain_ctx(),
            control: false,
            expected: false,
        },
        AltScreenCase {
            name: "cli_no_alt_overrides_auto_in_tmux",
            cli_no_alt: true,
            mode: AltScreenMode::Auto,
            ctx: tmux_ctx(),
            control: false,
            expected: false,
        },
        AltScreenCase {
            name: "cli_no_alt_overrides_always_in_zellij",
            cli_no_alt: true,
            mode: AltScreenMode::Always,
            ctx: zellij_ctx_test(),
            control: false,
            expected: false,
        },
        AltScreenCase {
            name: "cli_no_alt_overrides_always_in_tmux_control",
            cli_no_alt: true,
            mode: AltScreenMode::Always,
            ctx: tmux_ctx(),
            control: true,
            expected: false,
        },
    ];

    for case in &cases {
        let result =
            determine_alt_screen_policy(case.cli_no_alt, case.mode, &case.ctx, case.control);
        assert_eq!(
            result, case.expected,
            "alt_screen_policy failed on case '{}'",
            case.name
        );
    }
}

// -- Windows Terminal context integration ---------------------------------

#[test]
fn context_windows_terminal() {
    let env = env_from(&[("WT_SESSION", "a1b2c3d4-e5f6-7890-abcd-ef1234567890")]);
    let ctx = build_terminal_context_from_env(&env);
    assert_eq!(ctx.brand, TerminalName::WindowsTerminal);
    assert_eq!(ctx.multiplexer, MultiplexerKind::Undetected);
    assert!(!ctx.is_ssh);
}

// -- Terminal brand detection edge cases -----------------------------------

// =====================================================================
// Extended environment matrix (final hardening)
// =====================================================================

// -- Byobu-screen: auto keeps fullscreen (screen is not auto-disabled) ----

#[test]
fn auto_byobu_screen_is_fullscreen() {
    let ctx = TerminalContext {
        brand: TerminalName::Unknown,
        multiplexer: MultiplexerKind::Screen,
        byobu: Some(ByobuBackend::Screen),
        ..Default::default()
    };
    assert!(determine_alt_screen_policy(
        false,
        AltScreenMode::Auto,
        &ctx,
        false
    ));
}

// -- Terminal brand detection edge cases -----------------------------------

#[test]
fn brand_vscode_from_term_program() {
    let env = env_from(&[("TERM_PROGRAM", "vscode")]);
    assert_eq!(detect_terminal_brand_from_env(&env), TerminalName::VsCode);
}

#[test]
fn brand_kitty_from_window_id() {
    let env = env_from(&[("KITTY_WINDOW_ID", "1")]);
    assert_eq!(detect_terminal_brand_from_env(&env), TerminalName::Kitty);
}

#[test]
fn brand_alacritty_from_socket() {
    let env = env_from(&[("ALACRITTY_SOCKET", "/tmp/alacritty.sock")]);
    assert_eq!(
        detect_terminal_brand_from_env(&env),
        TerminalName::Alacritty
    );
}

#[test]
fn brand_apple_terminal_from_session_id() {
    let env = env_from(&[("TERM_SESSION_ID", "w0t0p0:ABC123")]);
    assert_eq!(
        detect_terminal_brand_from_env(&env),
        TerminalName::AppleTerminal
    );
}

#[test]
fn brand_term_program_takes_precedence_over_other_vars() {
    // TERM_PROGRAM should win even when other brand-specific vars are set.
    let env = env_from(&[
        ("TERM_PROGRAM", "Ghostty"),
        ("WEZTERM_VERSION", "20240203"),
        ("ITERM_SESSION_ID", "w0t0p0"),
    ]);
    assert_eq!(detect_terminal_brand_from_env(&env), TerminalName::Ghostty);
}

// -- IDE family detection (VS Code forks / xterm.js embeds) ---------------

#[test]
fn brand_cursor_from_cursor_trace_id() {
    // Cursor sets a unique CURSOR_TRACE_ID; it also sets TERM_PROGRAM=vscode
    // (since it's a VS Code fork), so we must detect it before the
    // TERM_PROGRAM lookup.
    let env = env_from(&[
        ("CURSOR_TRACE_ID", "abcdef0123456789"),
        ("TERM_PROGRAM", "vscode"),
    ]);
    assert_eq!(detect_terminal_brand_from_env(&env), TerminalName::Cursor);
}

#[test]
fn brand_cursor_from_askpass_main() {
    // Cursor exposes VSCODE_GIT_ASKPASS_MAIN pointing at its install path.
    let env = env_from(&[
        (
            "VSCODE_GIT_ASKPASS_MAIN",
            "/Applications/Cursor.app/Contents/Resources/app/extensions/git/dist/askpass-main.js",
        ),
        ("TERM_PROGRAM", "vscode"),
    ]);
    assert_eq!(detect_terminal_brand_from_env(&env), TerminalName::Cursor);
}

#[test]
fn brand_windsurf_from_askpass_main() {
    // Another VS Code fork; same askpass path substring pattern.
    let env = env_from(&[
        (
            "VSCODE_GIT_ASKPASS_MAIN",
            "/Applications/Windsurf.app/Contents/Resources/app/extensions/git/dist/askpass-main.js",
        ),
        ("TERM_PROGRAM", "vscode"),
    ]);
    assert_eq!(detect_terminal_brand_from_env(&env), TerminalName::Windsurf);
}

#[test]
fn brand_zed_from_term_program() {
    let env = env_from(&[("TERM_PROGRAM", "zed")]);
    assert_eq!(detect_terminal_brand_from_env(&env), TerminalName::Zed);
}

#[test]
fn brand_vscode_when_no_ide_markers() {
    // Pure VS Code: TERM_PROGRAM=vscode and no CURSOR_TRACE_ID / matching
    // askpass marker. Should remain VsCode, not collapse to anything else.
    let env = env_from(&[("TERM_PROGRAM", "vscode")]);
    assert_eq!(detect_terminal_brand_from_env(&env), TerminalName::VsCode);
}

#[test]
fn brand_vscode_when_askpass_does_not_match_ide() {
    // Pure VS Code askpass path (no "cursor"/"windsurf") — brand is VsCode
    // even when TERM_PROGRAM is also set.
    let env = env_from(&[
        ("VSCODE_GIT_ASKPASS_MAIN", "/usr/local/bin/askpass-main.js"),
        ("TERM_PROGRAM", "vscode"),
    ]);
    assert_eq!(detect_terminal_brand_from_env(&env), TerminalName::VsCode);
}

#[test]
fn brand_vscode_from_askpass_without_term_program() {
    // Remote SSH / tmux: TERM_PROGRAM missing or overwritten, but the VS Code
    // remote agent still injects VSCODE_GIT_ASKPASS_MAIN into the pane env.
    let env = env_from(&[(
        "VSCODE_GIT_ASKPASS_MAIN",
        "/home/user/.vscode-server/bin/abc/askpass",
    )]);
    assert_eq!(detect_terminal_brand_from_env(&env), TerminalName::VsCode);
}

// -- Zellij detection from ZELLIJ_VERSION (no ZELLIJ or SESSION_NAME) -----

#[test]
fn mux_zellij_not_from_version_only() {
    // ZELLIJ_VERSION alone does not trigger Zellij detection — it must be
    // ZELLIJ or ZELLIJ_SESSION_NAME.
    let env = env_from(&[("ZELLIJ_VERSION", "0.43.1")]);
    assert_eq!(
        detect_multiplexer_from_env(&env),
        MultiplexerKind::Undetected
    );
}

// -- Byobu inference edge cases -------------------------------------------

#[test]
fn byobu_unknown_backend_string_with_tmux() {
    // Unknown BYOBU_BACKEND value falls through to mux-marker inference.
    let env = env_from(&[("BYOBU_BACKEND", "unknown"), ("TMUX", "/tmp/tmux")]);
    assert_eq!(detect_byobu_from_env(&env), Some(ByobuBackend::Tmux));
}

#[test]
fn byobu_unknown_backend_string_with_sty() {
    let env = env_from(&[("BYOBU_BACKEND", "fish"), ("STY", "12345.pts-0")]);
    assert_eq!(detect_byobu_from_env(&env), Some(ByobuBackend::Screen));
}

#[test]
fn byobu_unknown_backend_no_mux_returns_none() {
    let env = env_from(&[("BYOBU_BACKEND", "unknown")]);
    assert_eq!(detect_byobu_from_env(&env), None);
}

// -- Context-level edge cases ---------------------------------------------

#[test]
fn context_sty_takes_screen_when_no_tmux_or_zellij() {
    let env = env_from(&[("STY", "12345.pts-0.host")]);
    let ctx = build_terminal_context_from_env(&env);
    assert_eq!(ctx.multiplexer, MultiplexerKind::Screen);
    assert_eq!(ctx.byobu, None);
    assert!(!ctx.is_tmux_backed());
}

#[test]
fn context_screen_does_not_populate_tmux_meta() {
    let env = env_from(&[("STY", "12345.pts-0.host"), ("TMUX_PANE", "%stale")]);
    let ctx = build_terminal_context_from_env(&env);
    assert_eq!(ctx.multiplexer, MultiplexerKind::Screen);
    // tmux_meta should be default since multiplexer is not Tmux.
    assert_eq!(ctx.tmux_meta, TmuxClientMeta::default());
}

#[test]
fn context_tmux_config_path_is_standard_for_plain_tmux() {
    let env = env_from(&[("TMUX", "/tmp/tmux-501/default,1,0")]);
    let ctx = build_terminal_context_from_env(&env);
    assert_eq!(ctx.tmux_config_path(), "~/.tmux.conf");
}

#[test]
fn context_tmux_config_path_is_byobu_for_byobu_tmux() {
    let env = env_from(&[("BYOBU_BACKEND", "tmux"), ("TMUX", "/tmp/tmux")]);
    let ctx = build_terminal_context_from_env(&env);
    assert_eq!(ctx.tmux_config_path(), "~/.byobu/.tmux.conf");
}

#[test]
fn context_is_byobu_returns_true_for_both_backends() {
    let tmux_env = env_from(&[("BYOBU_BACKEND", "tmux"), ("TMUX", "/tmp/tmux")]);
    let screen_env = env_from(&[("BYOBU_BACKEND", "screen"), ("STY", "12345.pts-0")]);

    assert!(build_terminal_context_from_env(&tmux_env).is_byobu());
    assert!(build_terminal_context_from_env(&screen_env).is_byobu());
}

#[test]
fn context_is_byobu_returns_false_without_byobu_markers() {
    let env = env_from(&[("TMUX", "/tmp/tmux")]);
    assert!(!build_terminal_context_from_env(&env).is_byobu());
}

// =====================================================================
// parse_tmux_major_minor: version string parsing
// =====================================================================

#[test]
fn parse_tmux_version_standard() {
    assert_eq!(parse_tmux_major_minor("tmux 3.4"), Some((3, 4)));
}

#[test]
fn parse_tmux_version_with_letter_suffix() {
    assert_eq!(parse_tmux_major_minor("tmux 3.3a"), Some((3, 3)));
}

#[test]
fn parse_tmux_version_old() {
    assert_eq!(parse_tmux_major_minor("tmux 2.9"), Some((2, 9)));
}

#[test]
fn parse_tmux_version_next_gen() {
    assert_eq!(parse_tmux_major_minor("tmux 4.0"), Some((4, 0)));
}

#[test]
fn parse_tmux_version_missing_prefix() {
    assert_eq!(parse_tmux_major_minor("3.4"), None);
}

#[test]
fn parse_tmux_version_empty() {
    assert_eq!(parse_tmux_major_minor(""), None);
}

#[test]
fn parse_tmux_version_garbage() {
    assert_eq!(parse_tmux_major_minor("not a version"), None);
}

#[test]
fn parse_tmux_version_trailing_dot() {
    assert_eq!(parse_tmux_major_minor("tmux 3."), None);
}

#[test]
fn parse_tmux_version_no_minor() {
    assert_eq!(parse_tmux_major_minor("tmux 3"), None);
}

// =====================================================================
// parse_semver_major_minor: TERM_PROGRAM_VERSION parsing
// =====================================================================

#[test]
fn parse_semver_standard() {
    assert_eq!(parse_semver_major_minor("3.6.0"), Some((3, 6)));
}

#[test]
fn parse_semver_two_part() {
    assert_eq!(parse_semver_major_minor("1.2"), Some((1, 2)));
}

#[test]
fn parse_semver_high_version() {
    assert_eq!(parse_semver_major_minor("4.0.0"), Some((4, 0)));
}

#[test]
fn parse_semver_empty() {
    assert_eq!(parse_semver_major_minor(""), None);
}

#[test]
fn parse_semver_garbage() {
    assert_eq!(parse_semver_major_minor("garbage"), None);
}

#[test]
fn parse_semver_major_only() {
    assert_eq!(parse_semver_major_minor("3"), None);
}

// =====================================================================
// graphics_protocol_skip_reason

#[test]
fn graphics_protocol_skip_reason_tmux() {
    let ctx = TerminalContext {
        brand: TerminalName::Kitty,
        multiplexer: MultiplexerKind::Tmux,
        ..Default::default()
    };
    assert_eq!(ctx.graphics_protocol_skip_reason(), Some("tmux"));
}

#[test]
fn graphics_protocol_skip_reason_plain_kitty() {
    let ctx = TerminalContext {
        brand: TerminalName::Kitty,
        ..Default::default()
    };
    assert_eq!(ctx.graphics_protocol_skip_reason(), None);
}

// kitty_skip_reason: Kitty keyboard protocol skip-reason matrix
// =====================================================================

#[test]
fn kitty_skip_vscode() {
    let ctx = TerminalContext {
        brand: TerminalName::VsCode,
        ..Default::default()
    };
    assert_eq!(ctx.kitty_skip_reason(), Some("vscode"));
}

#[test]
fn kitty_skip_cursor() {
    let ctx = TerminalContext {
        brand: TerminalName::Cursor,
        ..Default::default()
    };
    assert_eq!(ctx.kitty_skip_reason(), Some("vscode"));
}

#[test]
fn kitty_skip_windsurf() {
    let ctx = TerminalContext {
        brand: TerminalName::Windsurf,
        ..Default::default()
    };
    assert_eq!(ctx.kitty_skip_reason(), Some("vscode"));
}

#[test]
fn kitty_skip_zed() {
    let ctx = TerminalContext {
        brand: TerminalName::Zed,
        ..Default::default()
    };
    assert_eq!(ctx.kitty_skip_reason(), Some("vscode"));
}

#[test]
fn kitty_skip_apple_terminal() {
    let ctx = TerminalContext {
        brand: TerminalName::AppleTerminal,
        ..Default::default()
    };
    assert_eq!(ctx.kitty_skip_reason(), Some("apple_terminal"));
}

#[test]
fn kitty_skip_screen() {
    let ctx = TerminalContext {
        multiplexer: MultiplexerKind::Screen,
        ..Default::default()
    };
    assert_eq!(ctx.kitty_skip_reason(), Some("screen"));
}

#[test]
fn kitty_skip_old_tmux() {
    let ctx = TerminalContext {
        multiplexer: MultiplexerKind::Tmux,
        tmux_version: Some("tmux 3.2".to_owned()),
        ..Default::default()
    };
    assert_eq!(ctx.kitty_skip_reason(), Some("tmux_old"));
}

#[test]
fn kitty_skip_tmux_no_version() {
    let ctx = TerminalContext {
        multiplexer: MultiplexerKind::Tmux,
        tmux_version: None,
        ..Default::default()
    };
    assert_eq!(ctx.kitty_skip_reason(), Some("tmux_old"));
}

#[test]
fn kitty_allowed_modern_tmux() {
    let ctx = TerminalContext {
        multiplexer: MultiplexerKind::Tmux,
        tmux_version: Some("tmux 3.3".to_owned()),
        ..Default::default()
    };
    assert_eq!(ctx.kitty_skip_reason(), None);
}

#[test]
fn kitty_allowed_modern_tmux_letter() {
    let ctx = TerminalContext {
        multiplexer: MultiplexerKind::Tmux,
        tmux_version: Some("tmux 3.3a".to_owned()),
        ..Default::default()
    };
    assert_eq!(ctx.kitty_skip_reason(), None);
}

#[test]
fn kitty_allowed_tmux_4() {
    let ctx = TerminalContext {
        multiplexer: MultiplexerKind::Tmux,
        tmux_version: Some("tmux 4.0".to_owned()),
        ..Default::default()
    };
    assert_eq!(ctx.kitty_skip_reason(), None);
}

#[test]
fn kitty_skip_unknown_terminal_no_multiplexer() {
    // Unknown brand with no multiplexer = no positive evidence of KKP
    // support. Catches VSCode-over-SSH, bare Docker containers, etc.
    let ctx = TerminalContext::default();
    assert_eq!(ctx.kitty_skip_reason(), Some("unknown_no_multiplexer"));
}

#[test]
fn kitty_allowed_known_good_terminal() {
    let ctx = TerminalContext {
        brand: TerminalName::Ghostty,
        ..Default::default()
    };
    assert_eq!(ctx.kitty_skip_reason(), None);
}

#[test]
fn kitty_allowed_foot_no_multiplexer() {
    // Core fix: a bare foot window negotiates KKP (no skip reason).
    let ctx = TerminalContext {
        brand: TerminalName::Foot,
        ..Default::default()
    };
    assert_eq!(ctx.kitty_skip_reason(), None);
}

#[test]
fn shift_enter_available_on_foot() {
    let ctx = TerminalContext {
        brand: TerminalName::Foot,
        env_brand: TerminalName::Foot,
        ..Default::default()
    };
    assert!(!ctx.shift_enter_unavailable());
}

#[test]
fn foot_ctrl_dot_is_reliable() {
    let ctx = TerminalContext {
        brand: TerminalName::Foot,
        ..Default::default()
    };
    assert!(!ctx.ctrl_dot_unreliable());
}

#[test]
fn kitty_skip_foot_over_old_tmux_uses_mux_logic() {
    let ctx = TerminalContext {
        brand: TerminalName::Foot,
        multiplexer: MultiplexerKind::Tmux,
        tmux_version: Some("tmux 3.0".to_owned()),
        ..Default::default()
    };
    assert_eq!(ctx.kitty_skip_reason(), Some("tmux_old"));
}

#[test]
fn kitty_allowed_zellij() {
    let ctx = TerminalContext {
        multiplexer: MultiplexerKind::Zellij,
        ..Default::default()
    };
    assert_eq!(ctx.kitty_skip_reason(), None);
}

#[test]
fn kitty_skip_byobu_screen() {
    let ctx = TerminalContext {
        multiplexer: MultiplexerKind::Screen,
        byobu: Some(ByobuBackend::Screen),
        ..Default::default()
    };
    assert_eq!(ctx.kitty_skip_reason(), Some("screen"));
}

#[test]
fn kitty_skip_windows_terminal() {
    let ctx = TerminalContext {
        brand: TerminalName::WindowsTerminal,
        ..Default::default()
    };
    assert_eq!(ctx.kitty_skip_reason(), Some("windows_terminal"));
}

#[test]
fn kitty_skip_vscode_over_tmux() {
    // When both brand and multiplexer would skip, brand wins.
    let ctx = TerminalContext {
        brand: TerminalName::VsCode,
        multiplexer: MultiplexerKind::Tmux,
        tmux_version: Some("tmux 3.0".to_owned()),
        ..Default::default()
    };
    assert_eq!(ctx.kitty_skip_reason(), Some("vscode"));
}

#[test]
fn kitty_skip_vte_version() {
    // VTE does not support Kitty keyboard protocol and crossterm's probe
    // can false-positive on it. https://gitlab.gnome.org/GNOME/vte/-/issues/2601
    let ctx = TerminalContext {
        vte_version: Some("7402".to_owned()),
        ..Default::default()
    };
    assert_eq!(ctx.kitty_skip_reason(), Some("vte"));
}

#[test]
fn kitty_skip_vte_brand() {
    let ctx = TerminalContext {
        brand: TerminalName::Vte,
        ..Default::default()
    };
    assert_eq!(ctx.kitty_skip_reason(), Some("vte"));
}

// =====================================================================
// shift_enter_unavailable: VTE version gating for Shift+Enter
// =====================================================================
//
// VTE 0.82.0 (= VTE_VERSION 8200) is the first release containing the
// Kitty keyboard protocol; earlier versions cannot distinguish
// Shift+Enter from bare Enter, so the UI should advertise Alt+Enter
// for newline insertion instead.

#[test]
fn shift_enter_unavailable_legacy_vte_version() {
    // VTE 0.64.2 (real user report) — well below the KKP cutoff.
    let ctx = TerminalContext {
        vte_version: Some("6402".to_owned()),
        ..Default::default()
    };
    assert!(ctx.shift_enter_unavailable());
}

#[test]
fn shift_enter_unavailable_just_below_cutoff() {
    // VTE 0.81.99 — anything below 8200 must return true.
    let ctx = TerminalContext {
        vte_version: Some("8199".to_owned()),
        ..Default::default()
    };
    assert!(ctx.shift_enter_unavailable());
}

#[test]
fn shift_enter_available_modern_vte() {
    // VTE 0.82.0 — first release with KKP.
    let ctx = TerminalContext {
        vte_version: Some("8200".to_owned()),
        ..Default::default()
    };
    assert!(!ctx.shift_enter_unavailable());
}

#[test]
fn shift_enter_available_future_vte() {
    // VTE 0.84.1 — well above the cutoff.
    let ctx = TerminalContext {
        vte_version: Some("8401".to_owned()),
        ..Default::default()
    };
    assert!(!ctx.shift_enter_unavailable());
}

#[test]
fn shift_enter_unavailable_vte_brand_no_version() {
    // Brand detected as VTE but VTE_VERSION missing — conservative: old.
    let ctx = TerminalContext {
        brand: TerminalName::Vte,
        vte_version: None,
        ..Default::default()
    };
    assert!(ctx.shift_enter_unavailable());
}

#[test]
fn shift_enter_unavailable_unparseable_version() {
    // Garbage in VTE_VERSION — conservative: old.
    let ctx = TerminalContext {
        brand: TerminalName::Vte,
        vte_version: Some("not-a-number".to_owned()),
        ..Default::default()
    };
    assert!(ctx.shift_enter_unavailable());
}

#[test]
fn shift_enter_available_kkp_terminals() {
    // Terminals that negotiate the Kitty keyboard protocol (or, for Apple
    // Terminal, recover the modifier via CoreGraphics) deliver a usable
    // Shift+Enter and must never report it as unavailable.
    for brand in [
        TerminalName::Ghostty,
        TerminalName::Kitty,
        TerminalName::WezTerm,
        TerminalName::AppleTerminal,
    ] {
        let ctx = TerminalContext {
            brand,
            env_brand: brand, // lockstep with brand (no Windows refinement)
            ..Default::default()
        };
        assert!(
            !ctx.shift_enter_unavailable(),
            "{brand:?} must not report shift_enter_unavailable"
        );
    }
}

#[test]
fn shift_enter_unavailable_vscode_family() {
    // VS Code's xterm.js (and VS Code-family / xterm.js IDE forks) never
    // negotiate KKP, so Shift+Enter arrives as a bare CR identical to
    // Enter. The UI must advertise Alt+Enter instead.
    for brand in [
        TerminalName::VsCode,
        TerminalName::Cursor,
        TerminalName::Windsurf,
        TerminalName::Zed,
    ] {
        let ctx = TerminalContext {
            brand,
            ..Default::default()
        };
        assert!(
            ctx.shift_enter_unavailable(),
            "{brand:?} (xterm.js) must report shift_enter_unavailable"
        );
    }
}

#[test]
fn shift_enter_unavailable_unknown_no_multiplexer() {
    // The common VS Code-over-SSH shape: TERM_PROGRAM isn't forwarded so
    // the brand falls back to Unknown, and the pager skips KKP. Shift+Enter
    // is indistinguishable from Enter — advertise Alt+Enter.
    let ctx = TerminalContext::default();
    assert_eq!(ctx.brand, TerminalName::Unknown);
    assert_eq!(ctx.env_brand, TerminalName::Unknown);
    assert_eq!(ctx.multiplexer, MultiplexerKind::Undetected);
    assert!(ctx.shift_enter_unavailable());
}

#[test]
fn shift_enter_unavailable_windows_refined_brand_stays_env_unknown() {
    // Native Windows DefTerm / bare ConHost: `brand` is refined to WT for
    // capabilities/label, but `env_brand` stays Unknown. KKP is skipped
    // (WT is in the skip list; ConHost has no KKP either), so Shift+Enter
    // is unreliable — advertise Alt+Enter, same as an unrefined Unknown.
    let ctx = TerminalContext {
        brand: TerminalName::WindowsTerminal,
        env_brand: TerminalName::Unknown,
        multiplexer: MultiplexerKind::Undetected,
        ..Default::default()
    };
    assert!(ctx.shift_enter_unavailable());
}

#[test]
fn shift_enter_available_positively_detected_windows_terminal() {
    // WT detected via WT_SESSION/TERM_PROGRAM: both brand fields are WT.
    // The Unknown gate must not fire; Shift+Enter follows the WT path
    // (KKP is skipped for WT, but that is a separate, pre-existing choice).
    let ctx = TerminalContext {
        brand: TerminalName::WindowsTerminal,
        env_brand: TerminalName::WindowsTerminal,
        multiplexer: MultiplexerKind::Undetected,
        ..Default::default()
    };
    assert!(!ctx.shift_enter_unavailable());
}

#[test]
fn shift_enter_available_unknown_with_multiplexer() {
    // Unknown brand but inside modern tmux: KKP is negotiated through the
    // multiplexer, so Shift+Enter works and must not be flagged.
    let ctx = TerminalContext {
        brand: TerminalName::Unknown,
        env_brand: TerminalName::Unknown,
        multiplexer: MultiplexerKind::Tmux,
        tmux_version: Some("tmux 3.4".to_owned()),
        ..Default::default()
    };
    assert!(!ctx.shift_enter_unavailable());
}

#[test]
fn ctrl_dot_unreliable_on_vte() {
    let ctx = TerminalContext {
        brand: TerminalName::Vte,
        ..Default::default()
    };
    assert!(ctx.ctrl_dot_unreliable());
}

#[test]
fn ctrl_dot_unreliable_when_vte_version_set() {
    let ctx = TerminalContext {
        vte_version: Some("7402".to_owned()),
        ..Default::default()
    };
    assert!(ctx.ctrl_dot_unreliable());
}

#[test]
fn ctrl_dot_reliable_on_kitty() {
    let ctx = TerminalContext {
        brand: TerminalName::Kitty,
        ..Default::default()
    };
    assert!(!ctx.ctrl_dot_unreliable());
}

#[test]
fn ctrl_dot_unreliable_on_windows_terminal() {
    // WT propagates WT_SESSION into WSL via WSLENV, so this also covers
    // WSL-inside-WT — `Ctrl+.` can't survive WT's ConPTY pipeline either
    // way. (Pure WSL with no WT_SESSION is caught by `is_wsl()` in the
    // consumer, not here.)
    let ctx = TerminalContext {
        brand: TerminalName::WindowsTerminal,
        ..Default::default()
    };
    assert!(ctx.ctrl_dot_unreliable());
}

#[test]
fn ctrl_dot_unreliable_on_vscode_forks() {
    // VS Code's integrated terminal swallows `Ctrl+.` for its own command
    // palette. VS Code-family forks and other xterm.js IDE embeds inherit
    // the same behavior (forks share the keymap; others mirror it).
    // Preference is keyed off `kitty_skip_reason` ("vscode").
    for brand in [
        TerminalName::VsCode,
        TerminalName::Cursor,
        TerminalName::Windsurf,
        TerminalName::Zed,
    ] {
        let ctx = TerminalContext {
            brand,
            ..Default::default()
        };
        assert!(
            ctx.ctrl_dot_unreliable(),
            "expected ctrl_dot_unreliable on {brand:?}"
        );
    }
}

#[test]
fn ctrl_dot_unreliable_when_tmux_extended_keys_off() {
    // iTerm2+tmux still used to advertise Ctrl+. while KKP was
    // skipped for `extended-keys off`. Preference must follow the skip.
    let ctx = TerminalContext {
        brand: TerminalName::Iterm2,
        tmux_version: Some("tmux 3.6a".to_owned()),
        tmux_extended_keys: Some("off".to_owned()),
        ..tmux_ctx()
    };
    assert_eq!(ctx.kitty_skip_reason(), Some("tmux_extended_keys_off"));
    assert!(ctx.ctrl_dot_unreliable());
}

#[test]
fn ctrl_dot_reliable_on_iterm2_tmux_extended_keys_on() {
    let ctx = TerminalContext {
        brand: TerminalName::Iterm2,
        tmux_version: Some("tmux 3.6a".to_owned()),
        tmux_extended_keys: Some("on".to_owned()),
        ..tmux_ctx()
    };
    assert_eq!(ctx.kitty_skip_reason(), None);
    assert!(!ctx.ctrl_dot_unreliable());
}

#[test]
fn ctrl_dot_unreliable_on_unknown_no_multiplexer() {
    // Same KKP-skip path as VS Code SSH / unbranded hosts.
    let ctx = TerminalContext {
        brand: TerminalName::Unknown,
        multiplexer: MultiplexerKind::Undetected,
        ..Default::default()
    };
    assert_eq!(ctx.kitty_skip_reason(), Some("unknown_no_multiplexer"));
    assert!(ctx.ctrl_dot_unreliable());
}

// -- tmux extended-keys interaction with kitty_skip_reason ---------------

fn extended_keys_ctx(version: &str, extended_keys: Option<&str>) -> TerminalContext {
    TerminalContext {
        tmux_version: Some(version.to_owned()),
        tmux_extended_keys: extended_keys.map(str::to_owned),
        ..tmux_ctx()
    }
}

#[test]
fn kitty_skip_modern_tmux_extended_keys_off() {
    assert_eq!(
        extended_keys_ctx("tmux 3.4", Some("off")).kitty_skip_reason(),
        Some("tmux_extended_keys_off"),
    );
}

#[test]
fn kitty_allowed_modern_tmux_extended_keys_non_off_values() {
    // `on`/`always`/empty/uppercase/None must all NOT trigger the skip:
    // the comparison against `"off"` is exact and case-sensitive.
    for val in [Some("on"), Some("always"), Some(""), Some("OFF"), None] {
        assert_eq!(
            extended_keys_ctx("tmux 3.4", val).kitty_skip_reason(),
            None,
            "value {val:?} must not trigger skip",
        );
    }
}

#[test]
fn kitty_skip_old_tmux_takes_precedence_over_extended_keys() {
    assert_eq!(
        extended_keys_ctx("tmux 3.2", Some("off")).kitty_skip_reason(),
        Some("tmux_old"),
    );
}

#[test]
fn kitty_skip_vte_takes_precedence_over_tmux_extended_keys_off() {
    // VTE strips kitty CSI-u so `/terminal-setup` should recommend
    // switching terminal emulators rather than editing tmux.conf.
    let mut ctx = extended_keys_ctx("tmux 3.4", Some("off"));
    ctx.vte_version = Some("7402".to_owned());
    assert_eq!(ctx.kitty_skip_reason(), Some("vte"));
}

#[test]
fn kitty_skip_vte_takes_precedence_over_screen() {
    let ctx = TerminalContext {
        multiplexer: MultiplexerKind::Screen,
        vte_version: Some("7402".to_owned()),
        ..Default::default()
    };
    assert_eq!(ctx.kitty_skip_reason(), Some("vte"));
}

#[test]
fn kitty_skip_vte_takes_precedence_over_tmux_old() {
    let mut ctx = extended_keys_ctx("tmux 3.2", None);
    ctx.vte_version = Some("7402".to_owned());
    assert_eq!(ctx.kitty_skip_reason(), Some("vte"));
}

// =====================================================================
// JetBrains JediTerm detection
// =====================================================================

#[test]
fn brand_jetbrains_from_terminal_emulator() {
    let env = env_from(&[("TERMINAL_EMULATOR", "JetBrains-JediTerm")]);
    assert_eq!(
        detect_terminal_brand_from_env(&env),
        TerminalName::JetBrains
    );
}

#[test]
fn brand_jetbrains_case_insensitive() {
    let env = env_from(&[("TERMINAL_EMULATOR", "jetbrains-jediterm")]);
    assert_eq!(
        detect_terminal_brand_from_env(&env),
        TerminalName::JetBrains
    );
}

#[test]
fn brand_jetbrains_beats_term_session_id() {
    // JediTerm on some platforms may set TERM_SESSION_ID, which would
    // otherwise be detected as Apple Terminal. JetBrains must win.
    let env = env_from(&[
        ("TERMINAL_EMULATOR", "JetBrains-JediTerm"),
        ("TERM_SESSION_ID", "w0t0p0:ABC123"),
    ]);
    assert_eq!(
        detect_terminal_brand_from_env(&env),
        TerminalName::JetBrains
    );
}

#[test]
fn context_jetbrains_terminal() {
    let env = env_from(&[("TERMINAL_EMULATOR", "JetBrains-JediTerm")]);
    let ctx = build_terminal_context_from_env(&env);
    assert_eq!(ctx.brand, TerminalName::JetBrains);
    assert_eq!(ctx.multiplexer, MultiplexerKind::Undetected);
    assert!(!ctx.is_ssh);
}

#[test]
fn kitty_skip_jetbrains() {
    let ctx = TerminalContext {
        brand: TerminalName::JetBrains,
        ..Default::default()
    };
    assert_eq!(ctx.kitty_skip_reason(), Some("jetbrains"));
}

#[test]
fn ctrl_dot_unreliable_on_jetbrains() {
    let ctx = TerminalContext {
        brand: TerminalName::JetBrains,
        ..Default::default()
    };
    assert!(ctx.ctrl_dot_unreliable());
}

#[test]
fn repaints_pane_out_of_band_per_arm() {
    // Embedded-editor :terminal (libvterm) can repaint our pane with no PTY resize.
    let editor = TerminalContext {
        embedded_editor: Some(EmbeddedEditor::Neovim),
        multiplexer: MultiplexerKind::Undetected,
        ..Default::default()
    };
    assert!(editor.repaints_pane_out_of_band());

    // Each multiplexer arm fires independently.
    for mux in [
        MultiplexerKind::Tmux,
        MultiplexerKind::Screen,
        MultiplexerKind::Zellij,
    ] {
        let ctx = TerminalContext {
            multiplexer: mux,
            ..Default::default()
        };
        assert!(
            ctx.repaints_pane_out_of_band(),
            "multiplexer {mux:?} should report out-of-band repaint"
        );
    }

    // Plain terminal: no editor, no multiplexer -> no heal (no flicker regression).
    let plain = TerminalContext {
        embedded_editor: None,
        multiplexer: MultiplexerKind::Undetected,
        ..Default::default()
    };
    assert!(!plain.repaints_pane_out_of_band());
}
