#![cfg(unix)]

use insta::assert_snapshot;
use zellij_integration_tests::{
    keys, normalized, GridSnapshot, NestedHarness, PROMPT, TERMINAL_SIZE,
};

const ARROW_DOWN: &[u8] = b"\x1b[B";

fn last_line_contains(grid_snapshot: &GridSnapshot, needle: &str) -> bool {
    grid_snapshot
        .lines()
        .last()
        .map_or(false, |last_line| last_line.contains(needle))
}

fn modal_visible(grid_snapshot: &GridSnapshot) -> bool {
    grid_snapshot.contains("Nested Zellij session detected")
}

fn grid_contains_line_starting_with(grid_snapshot: &GridSnapshot, prefix: &str) -> bool {
    grid_snapshot
        .lines()
        .iter()
        .any(|line| line.trim_start().starts_with(prefix))
}

fn wait_for_modal(nested: &NestedHarness) -> GridSnapshot {
    nested.wait_for_guest_to_announce();
    nested.wait_for_host_to_acknowledge_guest();
    nested
        .host
        .wait_until("host shows the guest-detected modal", modal_visible)
}

fn focus_guest_binding_config() -> &'static str {
    r#"keybinds {
    normal {
        bind "Ctrl y" { FocusGuestSession; }
    }
}"#
}

fn modal_shortcut_binding_config() -> &'static str {
    r#"keybinds {
    normal {
        bind "Ctrl y" { ToggleHostFullscreen; }
        bind "Ctrl u" { FocusHostSession; }
        bind "Ctrl i" { FocusGuestSession; }
    }
}"#
}

#[test]
fn modal_renders_expected_ui() {
    let mut nested =
        NestedHarness::start_with_host_config(TERMINAL_SIZE, modal_shortcut_binding_config());

    let host_with_modal = wait_for_modal(&nested);
    assert!(host_with_modal.contains("What would you like to do?"));
    assert!(host_with_modal.contains("Zoom in and control this session"));
    assert!(host_with_modal.contains("Control this session on focus"));
    assert!(host_with_modal.contains("<Ctrl y>"));
    assert!(host_with_modal.contains("<Ctrl u>"));
    assert!(host_with_modal.contains("<↓↑> select"));

    let steady_modal = nested.host.wait_until(
        "guest-detected modal fully rendered with all options and keybindings",
        |host_grid| {
            modal_visible(host_grid)
                && host_grid.contains("What would you like to do?")
                && host_grid.contains("Control this session on focus")
                && host_grid.contains("<Ctrl y>")
                && host_grid.contains("<Ctrl u>")
                && host_grid.contains("<↓↑> select")
        },
    );
    assert_snapshot!(normalized(&steady_modal));

    nested.guest.wait_for_app_load();
    nested.guest.quit();
    nested.host.quit();
}

#[test]
fn modal_selection_moves_with_arrow_keys() {
    let mut nested =
        NestedHarness::start_with_host_config(TERMINAL_SIZE, modal_shortcut_binding_config());

    wait_for_modal(&nested);
    let first_selection = nested
        .host
        .wait_until("first option selected on initial render", |host_grid| {
            grid_contains_line_starting_with(host_grid, "> 1.")
        });
    assert!(grid_contains_line_starting_with(&first_selection, "> 1."));
    assert!(!grid_contains_line_starting_with(&first_selection, "> 2."));

    nested.host.send_stdin(ARROW_DOWN);
    let second_selection = nested.host.wait_until(
        "selection moved to the second option after arrow down",
        |host_grid| grid_contains_line_starting_with(host_grid, "> 2."),
    );
    assert!(grid_contains_line_starting_with(&second_selection, "> 2."));
    assert!(!grid_contains_line_starting_with(&second_selection, "> 1."));

    nested.guest.wait_for_app_load();
    nested.guest.quit();
    nested.host.quit();
}

#[test]
fn modal_appears_on_announce_and_occludes_guest_content() {
    let mut nested = NestedHarness::start(TERMINAL_SIZE);

    let host_with_modal = wait_for_modal(&nested);
    assert!(modal_visible(&host_with_modal));
    assert!(host_with_modal.contains("Zoom in and control this session"));
    assert!(host_with_modal.contains("Control this session on focus"));
    assert_eq!(
        nested.focus_gained_count(),
        0,
        "the modal must not enter passthrough by itself"
    );

    nested.guest.wait_for_app_load();
    nested.guest.quit();
    nested.host.quit();
}

#[test]
fn selecting_descend_enters_passthrough() {
    let mut nested = NestedHarness::start(TERMINAL_SIZE);

    wait_for_modal(&nested);
    let descended = nested.mark_host_to_guest();
    nested.host.send_stdin(ARROW_DOWN);
    nested.host.send_stdin(b"\r");
    nested.wait_for_host_to_descend_into_guest_after(descended);
    nested
        .host
        .wait_until("modal dismissed after descend", |host_grid| {
            !modal_visible(host_grid)
        });

    nested.guest.wait_for_app_load();
    nested.host.send_stdin(&keys::alt('n'));
    let guest_new_pane = nested.guest.expect_pty_spawn();
    guest_new_pane.output(PROMPT);
    nested.guest.wait_until(
        "guest spawned a pane from the passed-through key after descend",
        |guest_grid| guest_grid.contains("Pane #2"),
    );

    nested.guest.quit();
    nested.wait_for_host_to_reclaim_focus_after_guest_exit();
    nested.host.quit();
}

#[test]
fn digit_one_zooms_and_enters_passthrough() {
    let mut nested = NestedHarness::start(TERMINAL_SIZE);

    wait_for_modal(&nested);
    let zoomed = nested.mark_host_to_guest();
    nested.host.send_stdin(b"1");
    nested.wait_for_host_to_descend_into_guest_after(zoomed);
    nested
        .host
        .wait_until("modal dismissed after zoom", |host_grid| {
            !modal_visible(host_grid)
        });

    nested.guest.wait_for_app_load();
    nested.host.send_stdin(&keys::alt('n'));
    let guest_new_pane = nested.guest.expect_pty_spawn();
    guest_new_pane.output(PROMPT);
    nested.guest.wait_until(
        "guest spawned a pane from the passed-through key after zoom",
        |guest_grid| guest_grid.contains("Pane #2"),
    );

    nested.guest.quit();
    nested.wait_for_host_to_reclaim_focus_after_guest_exit();
    nested.host.quit();
}

#[test]
fn focus_guest_session_redescends_after_ascending() {
    let mut nested =
        NestedHarness::start_with_host_config(TERMINAL_SIZE, focus_guest_binding_config());

    wait_for_modal(&nested);

    let descended = nested.mark_host_to_guest();
    nested.host.send_stdin(b"2");
    nested.wait_for_host_to_descend_into_guest_after(descended);
    nested
        .host
        .wait_until("modal answered", |host_grid| !modal_visible(host_grid));
    nested.guest.wait_for_app_load();

    let ascended = nested.mark_host_to_guest();
    nested.host.send_stdin(&keys::ctrl('o'));
    nested.guest.wait_until(
        "guest entered session mode before the ascend key",
        |guest_grid| last_line_contains(guest_grid, "SESSION"),
    );
    nested.host.send_stdin(b"]");
    nested.wait_for_host_to_ascend_from_guest_after(ascended);

    let redescended = nested.mark_host_to_guest();
    nested.host.send_stdin(&keys::ctrl('y'));
    nested.wait_for_host_to_descend_into_guest_after(redescended);

    nested.host.send_stdin(&keys::alt('n'));
    let guest_new_pane = nested.guest.expect_pty_spawn();
    guest_new_pane.output(PROMPT);
    nested.guest.wait_until(
        "guest spawned a pane from the passed-through key after FocusGuestSession re-descended",
        |guest_grid| guest_grid.contains("Pane #2"),
    );

    nested.guest.quit();
    nested.wait_for_host_to_reclaim_focus_after_guest_exit();
    nested.host.quit();
}

#[test]
fn two_clients_answer_their_own_modals_independently() {
    let mut nested = NestedHarness::start(TERMINAL_SIZE);

    let first_with_modal = wait_for_modal(&nested);
    assert!(modal_visible(&first_with_modal));

    let second = nested.host.attach_client(TERMINAL_SIZE);
    let second_with_modal =
        second.wait_until("the second client also sees the guest modal", modal_visible);
    assert!(modal_visible(&second_with_modal));

    let descended = nested.mark_host_to_guest();
    nested.host.send_stdin(b"2");
    nested.wait_for_host_to_descend_into_guest_after(descended);
    nested.host.wait_until(
        "the first client's modal is dismissed after it descends",
        |host_grid| !modal_visible(host_grid),
    );

    second.wait_until(
        "the second client's modal stays up while the first client descended",
        modal_visible,
    );

    nested.guest.wait_for_app_load();
    second.quit();
    nested.guest.quit();
    nested.wait_for_host_to_reclaim_focus_after_guest_exit();
    nested.host.quit();
}

#[test]
fn host_stops_pinging_a_frozen_guest_after_answering_the_modal() {
    let mut nested = NestedHarness::start(TERMINAL_SIZE);

    wait_for_modal(&nested);
    nested.guest.wait_for_app_load();

    let descended = nested.mark_host_to_guest();
    nested.host.send_stdin(b"2");
    nested.wait_for_host_to_descend_into_guest_after(descended);
    nested
        .host
        .wait_until("modal answered", |host_grid| !modal_visible(host_grid));

    nested.freeze_guest();
    nested.assert_host_stops_pinging_frozen_guest();

    nested.guest.quit();
    nested.host.quit();
}
