use super::{Output, Tab};
use crate::panes::sixel::SixelImageStore;
use crate::screen::CopyOptions;
use crate::Arc;

use crate::{
    os_input_output::ServerOsApi, pane_groups::PaneGroups, panes::PaneId,
    plugins::PluginInstruction, thread_bus::ThreadSenders, ClientId, ServerInstruction,
};
use std::net::{IpAddr, Ipv4Addr};
use std::path::PathBuf;
use std::sync::Mutex;

use zellij_utils::channels::Receiver;
use zellij_utils::data::Direction;
use zellij_utils::data::Resize;
use zellij_utils::data::ResizeStrategy;
use zellij_utils::data::WebSharing;
use zellij_utils::envs::set_session_name;
use zellij_utils::errors::{prelude::*, ErrorContext};
use zellij_utils::input::layout::{
    FloatingPaneLayout, Layout, PercentOrFixed, RunPluginOrAlias, SwapFloatingLayout,
    SwapTiledLayout, TiledPaneLayout,
};
use zellij_utils::input::mouse::MouseEvent;
use zellij_utils::ipc::IpcReceiverWithContext;
use zellij_utils::pane_size::{Size, SizeInPixels};
use zellij_utils::position::Position;

use crate::pty_writer::PtyWriteInstruction;
use zellij_utils::channels::{self, ChannelWithContext, SenderWithContext};

use crate::os_input_output::AsyncReader;
use std::cell::RefCell;
use std::collections::{BTreeMap, HashMap};
use std::rc::Rc;

use interprocess::local_socket::Stream as LocalSocketStream;
use zellij_utils::{
    data::{FloatingPaneCoordinates, InputMode, ModeInfo, NewPanePlacement, Palette, Style},
    input::command::{RunCommand, TerminalAction},
    ipc::{ClientToServerMsg, ServerToClientMsg},
};

#[derive(Clone, Default)]
struct FakeInputOutput {
    file_dumps: Arc<Mutex<HashMap<String, String>>>,
    pub tty_stdin_bytes: Arc<Mutex<BTreeMap<u32, Vec<u8>>>>,
}

impl ServerOsApi for FakeInputOutput {
    fn set_terminal_size_using_terminal_id(
        &self,
        _id: u32,
        _cols: u16,
        _rows: u16,
        _width_in_pixels: Option<u16>,
        _height_in_pixels: Option<u16>,
    ) -> Result<()> {
        // noop
        Ok(())
    }
    fn spawn_terminal(
        &self,
        _file_to_open: TerminalAction,
        _quit_db: Box<dyn Fn(PaneId, Option<i32>, RunCommand) + Send>,
        _default_editor: Option<PathBuf>,
    ) -> Result<(u32, Box<dyn AsyncReader>, Option<u32>)> {
        unimplemented!()
    }
    fn write_to_tty_stdin(&self, id: u32, buf: &[u8]) -> Result<usize> {
        self.tty_stdin_bytes
            .lock()
            .unwrap()
            .entry(id)
            .or_insert_with(|| vec![])
            .extend_from_slice(buf);
        Ok(buf.len())
    }
    fn tcdrain(&self, _id: u32) -> Result<()> {
        unimplemented!()
    }
    fn kill(&self, _pid: u32) -> Result<()> {
        unimplemented!()
    }
    fn force_kill(&self, _pid: u32) -> Result<()> {
        unimplemented!()
    }
    fn box_clone(&self) -> Box<dyn ServerOsApi> {
        Box::new((*self).clone())
    }
    fn send_to_client(&self, _client_id: ClientId, _msg: ServerToClientMsg) -> Result<()> {
        unimplemented!()
    }
    fn new_client(
        &mut self,
        _client_id: ClientId,
        _stream: LocalSocketStream,
    ) -> Result<IpcReceiverWithContext<ClientToServerMsg>> {
        unimplemented!()
    }
    fn new_client_with_reply(
        &mut self,
        _client_id: ClientId,
        _stream: LocalSocketStream,
        _reply_stream: LocalSocketStream,
    ) -> Result<IpcReceiverWithContext<ClientToServerMsg>> {
        unimplemented!()
    }
    fn remove_client(&mut self, _client_id: ClientId) -> Result<()> {
        unimplemented!()
    }
    fn load_palette(&self) -> Palette {
        unimplemented!()
    }
    fn get_cwd(&self, _pid: u32) -> Option<PathBuf> {
        unimplemented!()
    }
    fn write_to_file(&mut self, buf: String, name: Option<String>) -> Result<()> {
        let f: String = match name {
            Some(x) => x,
            None => "tmp-name".to_owned(),
        };
        self.file_dumps.lock().to_anyhow()?.insert(f, buf);
        Ok(())
    }
    fn re_run_command_in_terminal(
        &self,
        _terminal_id: u32,
        _run_command: RunCommand,
        _quit_cb: Box<dyn Fn(PaneId, Option<i32>, RunCommand) + Send>,
    ) -> Result<(Box<dyn AsyncReader>, Option<u32>)> {
        unimplemented!()
    }
    fn clear_terminal_id(&self, _terminal_id: u32) -> Result<()> {
        unimplemented!()
    }
    fn send_sigint(&self, _pid: u32) -> Result<()> {
        unimplemented!()
    }
}

struct MockPtyInstructionBus {
    output: Arc<Mutex<Vec<String>>>,
    pty_writer_sender: SenderWithContext<PtyWriteInstruction>,
    pty_writer_receiver: Arc<Receiver<(PtyWriteInstruction, ErrorContext)>>,
    handle: Option<std::thread::JoinHandle<()>>,
}

impl MockPtyInstructionBus {
    fn new() -> Self {
        let output = Arc::new(Mutex::new(vec![]));
        let (pty_writer_sender, pty_writer_receiver): ChannelWithContext<PtyWriteInstruction> =
            channels::unbounded();
        let pty_writer_sender = SenderWithContext::new(pty_writer_sender);
        let pty_writer_receiver = Arc::new(pty_writer_receiver);

        Self {
            output,
            pty_writer_sender,
            pty_writer_receiver,
            handle: None,
        }
    }

    fn start(&mut self) {
        let output = self.output.clone();
        let pty_writer_receiver = self.pty_writer_receiver.clone();
        let handle = std::thread::Builder::new()
            .name("pty_writer".to_string())
            .spawn({
                move || loop {
                    let (event, _err_ctx) = pty_writer_receiver
                        .recv()
                        .expect("failed to receive event on channel");
                    match event {
                        PtyWriteInstruction::Write(msg, _, _) => output
                            .lock()
                            .unwrap()
                            .push(String::from_utf8_lossy(&msg).to_string()),
                        PtyWriteInstruction::Exit => break,
                        _ => {},
                    }
                }
            })
            .unwrap();
        self.handle = Some(handle);
    }

    fn exit(&mut self) {
        self.pty_writer_sender
            .send(PtyWriteInstruction::Exit)
            .unwrap();
        let handle = self.handle.take().unwrap();
        handle.join().unwrap();
    }

    fn pty_write_sender(&self) -> SenderWithContext<PtyWriteInstruction> {
        self.pty_writer_sender.clone()
    }

    fn clone_output(&self) -> Vec<String> {
        self.output.lock().unwrap().clone()
    }
}

fn create_new_tab(size: Size, default_mode: ModeInfo) -> Tab {
    set_session_name("test".into());
    let index = 0;
    let position = 0;
    let name = String::new();
    let os_api = Box::new(FakeInputOutput::default());
    let senders = ThreadSenders::default().silently_fail_on_send();
    let max_panes = None;
    let mode_info = default_mode;
    let style = Style::default();
    let draw_pane_frames = true;
    let auto_layout = true;
    let client_id = 1;
    let session_is_mirrored = true;
    let mut connected_clients = HashMap::new();
    connected_clients.insert(client_id, false);
    let connected_clients = Rc::new(RefCell::new(connected_clients));
    let character_cell_info = Rc::new(RefCell::new(None));
    let stacked_resize = Rc::new(RefCell::new(true));
    let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
    let copy_options = CopyOptions::default();
    let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
    let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
    let current_group = Rc::new(RefCell::new(PaneGroups::new(ThreadSenders::default())));
    let currently_marking_pane_group = Rc::new(RefCell::new(HashMap::new()));
    let debug = false;
    let arrow_fonts = true;
    let styled_underlines = true;
    let osc8_hyperlinks = true;
    let explicitly_disable_kitty_keyboard_protocol = false;
    let advanced_mouse_actions = true;
    let web_sharing = WebSharing::Off;
    let web_server_ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
    let web_server_port = 8080;
    let mut tab = Tab::new(
        index,
        position,
        name,
        size,
        character_cell_info,
        stacked_resize,
        sixel_image_store,
        os_api,
        senders,
        max_panes,
        style,
        mode_info,
        draw_pane_frames,
        auto_layout,
        connected_clients,
        session_is_mirrored,
        Some(client_id),
        copy_options,
        terminal_emulator_colors,
        terminal_emulator_color_codes,
        (vec![], vec![]),
        PathBuf::from("my_default_shell"),
        debug,
        arrow_fonts,
        styled_underlines,
        osc8_hyperlinks,
        explicitly_disable_kitty_keyboard_protocol,
        None,
        false,
        web_sharing,
        current_group,
        currently_marking_pane_group,
        advanced_mouse_actions,
        true,  // mouse_hover_effects
        false, // focus_follows_mouse
        false, // mouse_click_through
        web_server_ip,
        web_server_port,
    );
    tab.apply_layout(
        TiledPaneLayout::default(),
        vec![],
        vec![(1, None)],
        vec![],
        HashMap::new(),
        client_id,
        None,
    )
    .unwrap();
    tab
}

fn create_new_tab_without_pane_frames(size: Size, default_mode: ModeInfo) -> Tab {
    set_session_name("test".into());
    let index = 0;
    let position = 0;
    let name = String::new();
    let os_api = Box::new(FakeInputOutput::default());
    let senders = ThreadSenders::default().silently_fail_on_send();
    let max_panes = None;
    let mode_info = default_mode;
    let style = Style::default();
    let draw_pane_frames = false;
    let auto_layout = true;
    let client_id = 1;
    let session_is_mirrored = true;
    let mut connected_clients = HashMap::new();
    connected_clients.insert(client_id, false);
    let connected_clients = Rc::new(RefCell::new(connected_clients));
    let character_cell_info = Rc::new(RefCell::new(None));
    let stacked_resize = Rc::new(RefCell::new(true));
    let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
    let copy_options = CopyOptions::default();
    let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
    let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
    let current_group = Rc::new(RefCell::new(PaneGroups::new(ThreadSenders::default())));
    let currently_marking_pane_group = Rc::new(RefCell::new(HashMap::new()));
    let debug = false;
    let arrow_fonts = true;
    let styled_underlines = true;
    let osc8_hyperlinks = true;
    let explicitly_disable_kitty_keyboard_protocol = false;
    let advanced_mouse_actions = true;
    let web_sharing = WebSharing::Off;
    let web_server_ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
    let web_server_port = 8080;
    let mut tab = Tab::new(
        index,
        position,
        name,
        size,
        character_cell_info,
        stacked_resize,
        sixel_image_store,
        os_api,
        senders,
        max_panes,
        style,
        mode_info,
        draw_pane_frames,
        auto_layout,
        connected_clients,
        session_is_mirrored,
        Some(client_id),
        copy_options,
        terminal_emulator_colors,
        terminal_emulator_color_codes,
        (vec![], vec![]),
        PathBuf::from("my_default_shell"),
        debug,
        arrow_fonts,
        styled_underlines,
        osc8_hyperlinks,
        explicitly_disable_kitty_keyboard_protocol,
        None,
        false,
        web_sharing,
        current_group,
        currently_marking_pane_group,
        advanced_mouse_actions,
        true,  // mouse_hover_effects
        false, // focus_follows_mouse
        false, // mouse_click_through
        web_server_ip,
        web_server_port,
    );
    tab.apply_layout(
        TiledPaneLayout::default(),
        vec![],
        vec![(1, None)],
        vec![],
        HashMap::new(),
        client_id,
        None,
    )
    .unwrap();
    tab
}

fn create_new_tab_with_swap_layouts(
    size: Size,
    default_mode: ModeInfo,
    swap_layouts: (Vec<SwapTiledLayout>, Vec<SwapFloatingLayout>),
    base_layout_and_ids: Option<(
        TiledPaneLayout,
        Vec<FloatingPaneLayout>,
        Vec<(u32, Option<RunCommand>)>,
        Vec<(u32, Option<RunCommand>)>,
        HashMap<RunPluginOrAlias, Vec<u32>>,
    )>,
    draw_pane_frames: bool,
    stacked_resize: bool,
) -> Tab {
    set_session_name("test".into());
    let index = 0;
    let position = 0;
    let name = String::new();
    let os_api = Box::new(FakeInputOutput::default());
    let mut senders = ThreadSenders::default().silently_fail_on_send();
    let (mock_plugin_sender, _mock_plugin_receiver): ChannelWithContext<PluginInstruction> =
        channels::unbounded();
    senders.replace_to_plugin(SenderWithContext::new(mock_plugin_sender));
    let max_panes = None;
    let mode_info = default_mode;
    let style = Style::default();
    let auto_layout = true;
    let client_id = 1;
    let session_is_mirrored = true;
    let mut connected_clients = HashMap::new();
    connected_clients.insert(client_id, false);
    let connected_clients = Rc::new(RefCell::new(connected_clients));
    let character_cell_info = Rc::new(RefCell::new(None));
    let stacked_resize = Rc::new(RefCell::new(stacked_resize));
    let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
    let copy_options = CopyOptions::default();
    let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
    let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
    let current_group = Rc::new(RefCell::new(PaneGroups::new(ThreadSenders::default())));
    let currently_marking_pane_group = Rc::new(RefCell::new(HashMap::new()));
    let debug = false;
    let arrow_fonts = true;
    let styled_underlines = true;
    let osc8_hyperlinks = true;
    let explicitly_disable_kitty_keyboard_protocol = false;
    let advanced_mouse_actions = true;
    let web_sharing = WebSharing::Off;
    let web_server_ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
    let web_server_port = 8080;
    let mut tab = Tab::new(
        index,
        position,
        name,
        size,
        character_cell_info,
        stacked_resize,
        sixel_image_store,
        os_api,
        senders,
        max_panes,
        style,
        mode_info,
        draw_pane_frames,
        auto_layout,
        connected_clients,
        session_is_mirrored,
        Some(client_id),
        copy_options,
        terminal_emulator_colors,
        terminal_emulator_color_codes,
        swap_layouts,
        PathBuf::from("my_default_shell"),
        debug,
        arrow_fonts,
        styled_underlines,
        osc8_hyperlinks,
        explicitly_disable_kitty_keyboard_protocol,
        None,
        false,
        web_sharing,
        current_group,
        currently_marking_pane_group,
        advanced_mouse_actions,
        true,  // mouse_hover_effects
        false, // focus_follows_mouse
        false, // mouse_click_through
        web_server_ip,
        web_server_port,
    );
    let (
        base_layout,
        base_floating_layout,
        new_terminal_ids,
        new_floating_terminal_ids,
        new_plugin_ids,
    ) = base_layout_and_ids.unwrap_or_default();
    let new_terminal_ids = if new_terminal_ids.is_empty() {
        vec![(1, None)]
    } else {
        new_terminal_ids
    };
    tab.apply_layout(
        base_layout,
        base_floating_layout,
        new_terminal_ids,
        new_floating_terminal_ids,
        new_plugin_ids,
        client_id,
        None,
    )
    .unwrap();
    tab
}

fn create_new_tab_with_os_api(
    size: Size,
    default_mode: ModeInfo,
    os_api: &Box<FakeInputOutput>,
) -> Tab {
    set_session_name("test".into());
    let index = 0;
    let position = 0;
    let name = String::new();
    let os_api = os_api.clone();
    let senders = ThreadSenders::default().silently_fail_on_send();
    let max_panes = None;
    let mode_info = default_mode;
    let style = Style::default();
    let draw_pane_frames = true;
    let auto_layout = true;
    let client_id = 1;
    let session_is_mirrored = true;
    let mut connected_clients = HashMap::new();
    connected_clients.insert(client_id, false);
    let connected_clients = Rc::new(RefCell::new(connected_clients));
    let character_cell_info = Rc::new(RefCell::new(None));
    let stacked_resize = Rc::new(RefCell::new(true));
    let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
    let copy_options = CopyOptions::default();
    let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
    let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
    let current_group = Rc::new(RefCell::new(PaneGroups::new(ThreadSenders::default())));
    let currently_marking_pane_group = Rc::new(RefCell::new(HashMap::new()));
    let debug = false;
    let arrow_fonts = true;
    let styled_underlines = true;
    let osc8_hyperlinks = true;
    let explicitly_disable_kitty_keyboard_protocol = false;
    let advanced_mouse_actions = true;
    let web_sharing = WebSharing::Off;
    let web_server_ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
    let web_server_port = 8080;
    let mut tab = Tab::new(
        index,
        position,
        name,
        size,
        character_cell_info,
        stacked_resize,
        sixel_image_store,
        os_api,
        senders,
        max_panes,
        style,
        mode_info,
        draw_pane_frames,
        auto_layout,
        connected_clients,
        session_is_mirrored,
        Some(client_id),
        copy_options,
        terminal_emulator_colors,
        terminal_emulator_color_codes,
        (vec![], vec![]), // swap layouts
        PathBuf::from("my_default_shell"),
        debug,
        arrow_fonts,
        styled_underlines,
        osc8_hyperlinks,
        explicitly_disable_kitty_keyboard_protocol,
        None,
        false,
        web_sharing,
        current_group,
        currently_marking_pane_group,
        advanced_mouse_actions,
        true,  // mouse_hover_effects
        false, // focus_follows_mouse
        false, // mouse_click_through
        web_server_ip,
        web_server_port,
    );
    tab.apply_layout(
        TiledPaneLayout::default(),
        vec![],
        vec![(1, None)],
        vec![],
        HashMap::new(),
        client_id,
        None,
    )
    .unwrap();
    tab
}

fn create_new_tab_with_layout(size: Size, default_mode: ModeInfo, layout: &str) -> Tab {
    set_session_name("test".into());
    let index = 0;
    let position = 0;
    let name = String::new();
    let os_api = Box::new(FakeInputOutput::default());
    let senders = ThreadSenders::default().silently_fail_on_send();
    let max_panes = None;
    let mode_info = default_mode;
    let style = Style::default();
    let draw_pane_frames = true;
    let auto_layout = true;
    let client_id = 1;
    let session_is_mirrored = true;
    let mut connected_clients = HashMap::new();
    connected_clients.insert(client_id, false);
    let connected_clients = Rc::new(RefCell::new(connected_clients));
    let character_cell_info = Rc::new(RefCell::new(None));
    let stacked_resize = Rc::new(RefCell::new(true));
    let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
    let copy_options = CopyOptions::default();
    let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
    let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
    let layout = Layout::from_str(layout, "layout_file_name".into(), None, None).unwrap();
    let (tab_layout, floating_panes_layout) = layout.new_tab();
    let current_group = Rc::new(RefCell::new(PaneGroups::new(ThreadSenders::default())));
    let currently_marking_pane_group = Rc::new(RefCell::new(HashMap::new()));
    let debug = false;
    let arrow_fonts = true;
    let styled_underlines = true;
    let osc8_hyperlinks = true;
    let explicitly_disable_kitty_keyboard_protocol = false;
    let advanced_mouse_actions = true;
    let web_sharing = WebSharing::Off;
    let web_server_ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
    let web_server_port = 8080;
    let mut tab = Tab::new(
        index,
        position,
        name,
        size,
        character_cell_info,
        stacked_resize,
        sixel_image_store,
        os_api,
        senders,
        max_panes,
        style,
        mode_info,
        draw_pane_frames,
        auto_layout,
        connected_clients,
        session_is_mirrored,
        Some(client_id),
        copy_options,
        terminal_emulator_colors,
        terminal_emulator_color_codes,
        (vec![], vec![]), // swap layouts
        PathBuf::from("my_default_shell"),
        debug,
        arrow_fonts,
        styled_underlines,
        osc8_hyperlinks,
        explicitly_disable_kitty_keyboard_protocol,
        None,
        false,
        web_sharing,
        current_group,
        currently_marking_pane_group,
        advanced_mouse_actions,
        true,  // mouse_hover_effects
        false, // focus_follows_mouse
        false, // mouse_click_through
        web_server_ip,
        web_server_port,
    );
    let pane_ids = tab_layout
        .extract_run_instructions()
        .iter()
        .enumerate()
        .map(|(i, _)| (i as u32, None))
        .collect();
    let floating_pane_ids = floating_panes_layout
        .iter()
        .enumerate()
        .map(|(i, _)| (i as u32, None))
        .collect();
    tab.apply_layout(
        tab_layout,
        floating_panes_layout,
        pane_ids,
        floating_pane_ids,
        HashMap::new(),
        client_id,
        None,
    )
    .unwrap();
    tab
}

fn create_new_tab_with_mock_pty_writer(
    size: Size,
    default_mode: ModeInfo,
    mock_pty_writer: SenderWithContext<PtyWriteInstruction>,
) -> Tab {
    set_session_name("test".into());
    let index = 0;
    let position = 0;
    let name = String::new();
    let os_api = Box::new(FakeInputOutput::default());
    let mut senders = ThreadSenders::default().silently_fail_on_send();
    senders.replace_to_pty_writer(mock_pty_writer);
    let max_panes = None;
    let mode_info = default_mode;
    let style = Style::default();
    let draw_pane_frames = true;
    let auto_layout = true;
    let client_id = 1;
    let session_is_mirrored = true;
    let mut connected_clients = HashMap::new();
    connected_clients.insert(client_id, false);
    let connected_clients = Rc::new(RefCell::new(connected_clients));
    let character_cell_info = Rc::new(RefCell::new(None));
    let stacked_resize = Rc::new(RefCell::new(true));
    let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
    let copy_options = CopyOptions::default();
    let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
    let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
    let current_group = Rc::new(RefCell::new(PaneGroups::new(ThreadSenders::default())));
    let currently_marking_pane_group = Rc::new(RefCell::new(HashMap::new()));
    let debug = false;
    let arrow_fonts = true;
    let styled_underlines = true;
    let osc8_hyperlinks = true;
    let explicitly_disable_kitty_keyboard_protocol = false;
    let advanced_mouse_actions = true;
    let web_sharing = WebSharing::Off;
    let web_server_ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
    let web_server_port = 8080;
    let mut tab = Tab::new(
        index,
        position,
        name,
        size,
        character_cell_info,
        stacked_resize,
        sixel_image_store,
        os_api,
        senders,
        max_panes,
        style,
        mode_info,
        draw_pane_frames,
        auto_layout,
        connected_clients,
        session_is_mirrored,
        Some(client_id),
        copy_options,
        terminal_emulator_colors,
        terminal_emulator_color_codes,
        (vec![], vec![]), // swap layouts
        PathBuf::from("my_default_shell"),
        debug,
        arrow_fonts,
        styled_underlines,
        osc8_hyperlinks,
        explicitly_disable_kitty_keyboard_protocol,
        None,
        false,
        web_sharing,
        current_group,
        currently_marking_pane_group,
        advanced_mouse_actions,
        true,  // mouse_hover_effects
        false, // focus_follows_mouse
        false, // mouse_click_through
        web_server_ip,
        web_server_port,
    );
    tab.apply_layout(
        TiledPaneLayout::default(),
        vec![],
        vec![(1, None)],
        vec![],
        HashMap::new(),
        client_id,
        None,
    )
    .unwrap();
    tab
}

fn create_new_tab_with_sixel_support(
    size: Size,
    sixel_image_store: Rc<RefCell<SixelImageStore>>,
) -> Tab {
    // this is like the create_new_tab function but includes stuff needed for sixel,
    // eg. character_cell_size
    set_session_name("test".into());
    let index = 0;
    let position = 0;
    let name = String::new();
    let os_api = Box::new(FakeInputOutput::default());
    let senders = ThreadSenders::default().silently_fail_on_send();
    let max_panes = None;
    let mode_info = ModeInfo::default();
    let style = Style::default();
    let draw_pane_frames = true;
    let auto_layout = true;
    let client_id = 1;
    let session_is_mirrored = true;
    let mut connected_clients = HashMap::new();
    connected_clients.insert(client_id, false);
    let connected_clients = Rc::new(RefCell::new(connected_clients));
    let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels {
        width: 8,
        height: 21,
    })));
    let stacked_resize = Rc::new(RefCell::new(true));
    let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
    let copy_options = CopyOptions::default();
    let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
    let current_group = Rc::new(RefCell::new(PaneGroups::new(ThreadSenders::default())));
    let currently_marking_pane_group = Rc::new(RefCell::new(HashMap::new()));
    let debug = false;
    let arrow_fonts = true;
    let styled_underlines = true;
    let osc8_hyperlinks = true;
    let explicitly_disable_kitty_keyboard_protocol = false;
    let advanced_mouse_actions = true;
    let web_sharing = WebSharing::Off;
    let web_server_ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
    let web_server_port = 8080;
    let mut tab = Tab::new(
        index,
        position,
        name,
        size,
        character_cell_size,
        stacked_resize,
        sixel_image_store,
        os_api,
        senders,
        max_panes,
        style,
        mode_info,
        draw_pane_frames,
        auto_layout,
        connected_clients,
        session_is_mirrored,
        Some(client_id),
        copy_options,
        terminal_emulator_colors,
        terminal_emulator_color_codes,
        (vec![], vec![]), // swap layouts
        PathBuf::from("my_default_shell"),
        debug,
        arrow_fonts,
        styled_underlines,
        osc8_hyperlinks,
        explicitly_disable_kitty_keyboard_protocol,
        None,
        false,
        web_sharing,
        current_group,
        currently_marking_pane_group,
        advanced_mouse_actions,
        true,  // mouse_hover_effects
        false, // focus_follows_mouse
        false, // mouse_click_through
        web_server_ip,
        web_server_port,
    );
    tab.apply_layout(
        TiledPaneLayout::default(),
        vec![],
        vec![(1, None)],
        vec![],
        HashMap::new(),
        client_id,
        None,
    )
    .unwrap();
    tab
}

fn read_fixture(fixture_name: &str) -> Vec<u8> {
    let mut path_to_file = std::path::PathBuf::new();
    path_to_file.push("../src");
    path_to_file.push("tests");
    path_to_file.push("fixtures");
    path_to_file.push(fixture_name);
    std::fs::read(path_to_file)
        .unwrap_or_else(|_| panic!("could not read fixture {:?}", &fixture_name))
}

use crate::panes::grid::Grid;
use crate::panes::link_handler::LinkHandler;
use insta::assert_snapshot;
use vte;

fn take_snapshot(ansi_instructions: &str, rows: usize, columns: usize, palette: Palette) -> String {
    let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
    let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
    let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels {
        width: 8,
        height: 21,
    })));
    let debug = false;
    let arrow_fonts = true;
    let styled_underlines = true;
    let osc8_hyperlinks = true;
    let explicitly_disable_kitty_keyboard_protocol = false;
    let mut grid = Grid::new(
        rows,
        columns,
        Rc::new(RefCell::new(palette)),
        terminal_emulator_color_codes,
        Rc::new(RefCell::new(LinkHandler::new())),
        character_cell_size,
        sixel_image_store,
        Style::default(),
        debug,
        arrow_fonts,
        styled_underlines,
        osc8_hyperlinks,
        explicitly_disable_kitty_keyboard_protocol,
    );
    let mut vte_parser = vte::Parser::new();
    for &byte in ansi_instructions.as_bytes() {
        vte_parser.advance(&mut grid, byte);
    }
    format!("{:?}", grid)
}

fn take_snapshot_with_sixel(
    ansi_instructions: &str,
    rows: usize,
    columns: usize,
    palette: Palette,
    sixel_image_store: Rc<RefCell<SixelImageStore>>,
) -> String {
    let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
    let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels {
        width: 8,
        height: 21,
    })));
    let debug = false;
    let arrow_fonts = true;
    let styled_underlines = true;
    let osc8_hyperlinks = true;
    let explicitly_disable_kitty_keyboard_protocol = false;
    let mut grid = Grid::new(
        rows,
        columns,
        Rc::new(RefCell::new(palette)),
        terminal_emulator_color_codes,
        Rc::new(RefCell::new(LinkHandler::new())),
        character_cell_size,
        sixel_image_store,
        Style::default(),
        debug,
        arrow_fonts,
        styled_underlines,
        osc8_hyperlinks,
        explicitly_disable_kitty_keyboard_protocol,
    );
    let mut vte_parser = vte::Parser::new();
    for &byte in ansi_instructions.as_bytes() {
        vte_parser.advance(&mut grid, byte);
    }
    format!("{:?}", grid)
}

fn take_snapshot_and_cursor_position(
    ansi_instructions: &str,
    rows: usize,
    columns: usize,
    palette: Palette,
) -> (String, Option<(usize, usize)>) {
    // snapshot, x_coordinates, y_coordinates
    let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
    let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
    let debug = false;
    let arrow_fonts = true;
    let styled_underlines = true;
    let osc8_hyperlinks = true;
    let explicitly_disable_kitty_keyboard_protocol = false;
    let mut grid = Grid::new(
        rows,
        columns,
        Rc::new(RefCell::new(palette)),
        terminal_emulator_color_codes,
        Rc::new(RefCell::new(LinkHandler::new())),
        Rc::new(RefCell::new(None)),
        sixel_image_store,
        Style::default(),
        debug,
        arrow_fonts,
        styled_underlines,
        osc8_hyperlinks,
        explicitly_disable_kitty_keyboard_protocol,
    );
    let mut vte_parser = vte::Parser::new();
    for &byte in ansi_instructions.as_bytes() {
        vte_parser.advance(&mut grid, byte);
    }
    let coords = grid
        .cursor_coordinates()
        .and_then(|(x, y, visible)| if visible { Some((x, y)) } else { None });
    (format!("{:?}", grid), coords)
}

#[test]
fn increase_tiled_pane_sizes_with_stacked_resizes() {
    // this is the default resizing algorithm
    let size = Size {
        cols: 200,
        rows: 40,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let mut output = Output::default();
    for i in 2..5 {
        let new_pane_id_1 = PaneId::Terminal(i);
        tab.new_pane(
            new_pane_id_1,
            None,
            None,
            false,
            true,
            NewPanePlacement::default(),
            Some(client_id),
            None,
        )
        .unwrap();
    }

    // first we increase until fullscreen and once more to make sure we don't increase beyond it
    for _ in 0..=6 {
        tab.resize(client_id, ResizeStrategy::new(Resize::Increase, None))
            .unwrap();
        tab.render(&mut output, None).unwrap();
        let snapshot = take_snapshot(
            output.serialize().unwrap().get(&client_id).unwrap(),
            size.rows,
            size.cols,
            Palette::default(),
        );
        assert_snapshot!(snapshot);
    }

    // then we decrease until the original position
    for _ in 0..=5 {
        tab.resize(client_id, ResizeStrategy::new(Resize::Decrease, None))
            .unwrap();
        tab.render(&mut output, None).unwrap();
        let snapshot = take_snapshot(
            output.serialize().unwrap().get(&client_id).unwrap(),
            size.rows,
            size.cols,
            Palette::default(),
        );
        assert_snapshot!(snapshot);
    }
}

#[test]
fn increase_tiled_pane_sizes_with_stacked_resizes_into_uneven_panes() {
    // this is the default resizing algorithm
    let size = Size {
        cols: 200,
        rows: 40,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let mut output = Output::default();
    for i in 2..4 {
        let new_pane_id_1 = PaneId::Terminal(i);
        tab.new_pane(
            new_pane_id_1,
            None,
            None,
            false,
            true,
            NewPanePlacement::default(),
            Some(client_id),
            None,
        )
        .unwrap();
    }
    tab.move_focus_up(client_id).unwrap();
    tab.vertical_split(PaneId::Terminal(4), None, client_id, None, None)
        .unwrap();
    tab.move_focus_right(client_id).unwrap();
    tab.horizontal_split(PaneId::Terminal(5), None, client_id, None, None)
        .unwrap();
    tab.move_focus_down(client_id).unwrap();

    // increase twice, once to add the short pane into the stack and shorten the larger one, and
    // once to add the remaining two panes (the one we shortened and the extra one near it)
    for _ in 0..2 {
        tab.resize(client_id, ResizeStrategy::new(Resize::Increase, None))
            .unwrap();
        tab.render(&mut output, None).unwrap();
        let snapshot = take_snapshot(
            output.serialize().unwrap().get(&client_id).unwrap(),
            size.rows,
            size.cols,
            Palette::default(),
        );
        assert_snapshot!(snapshot);
    }

    // then we decrease until the original position
    for _ in 0..2 {
        tab.resize(client_id, ResizeStrategy::new(Resize::Decrease, None))
            .unwrap();
        tab.render(&mut output, None).unwrap();
        let snapshot = take_snapshot(
            output.serialize().unwrap().get(&client_id).unwrap(),
            size.rows,
            size.cols,
            Palette::default(),
        );
        assert_snapshot!(snapshot);
    }
}

#[test]
fn split_stack_vertically() {
    let size = Size {
        cols: 200,
        rows: 40,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let mut output = Output::default();
    for i in 2..4 {
        let new_pane_id_1 = PaneId::Terminal(i);
        tab.new_pane(
            new_pane_id_1,
            None,
            None,
            false,
            true,
            NewPanePlacement::default(),
            Some(client_id),
            None,
        )
        .unwrap();
    }
    // the below resizes will end up stacking the panes
    tab.resize(client_id, ResizeStrategy::new(Resize::Increase, None))
        .unwrap();
    tab.resize(client_id, ResizeStrategy::new(Resize::Increase, None))
        .unwrap();
    tab.vertical_split(PaneId::Terminal(4), None, client_id, None, None)
        .unwrap();

    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn split_stack_horizontally() {
    let size = Size {
        cols: 200,
        rows: 40,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let mut output = Output::default();
    for i in 2..4 {
        let new_pane_id_1 = PaneId::Terminal(i);
        tab.new_pane(
            new_pane_id_1,
            None,
            None,
            false,
            true,
            NewPanePlacement::default(),
            Some(client_id),
            None,
        )
        .unwrap();
    }
    // the below resizes will end up stacking the panes
    tab.resize(client_id, ResizeStrategy::new(Resize::Increase, None))
        .unwrap();
    tab.resize(client_id, ResizeStrategy::new(Resize::Increase, None))
        .unwrap();
    tab.horizontal_split(PaneId::Terminal(4), None, client_id, None, None)
        .unwrap();

    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn render_stacks_without_pane_frames() {
    // this checks various cases and gotchas that have to do with rendering stacked panes when we
    // don't draw frames around panes
    let size = Size {
        cols: 100,
        rows: 40,
    };
    let client_id = 1;
    let mut tab = create_new_tab_without_pane_frames(size, ModeInfo::default());
    let mut output = Output::default();
    for i in 2..4 {
        let new_pane_id_1 = PaneId::Terminal(i);
        tab.new_pane(
            new_pane_id_1,
            None,
            None,
            false,
            true,
            NewPanePlacement::default(),
            Some(client_id),
            None,
        )
        .unwrap();
    }
    // the below resizes will end up stacking the panes
    tab.resize(client_id, ResizeStrategy::new(Resize::Increase, None))
        .unwrap();
    tab.resize(client_id, ResizeStrategy::new(Resize::Increase, None))
        .unwrap();
    tab.vertical_split(PaneId::Terminal(4), None, client_id, None, None)
        .unwrap();
    for i in 5..7 {
        let new_pane_id_1 = PaneId::Terminal(i);
        tab.new_pane(
            new_pane_id_1,
            None,
            None,
            false,
            true,
            NewPanePlacement::default(),
            Some(client_id),
            None,
        )
        .unwrap();
    }
    let _ = tab.focus_pane_with_id(PaneId::Terminal(1), false, false, client_id);
    for i in 7..9 {
        let new_pane_id_1 = PaneId::Terminal(i);
        tab.new_pane(
            new_pane_id_1,
            None,
            None,
            false,
            true,
            NewPanePlacement::default(),
            Some(client_id),
            None,
        )
        .unwrap();
    }
    let _ = tab.focus_pane_with_id(PaneId::Terminal(1), false, false, client_id);
    for i in 9..11 {
        let new_pane_id_1 = PaneId::Terminal(i);
        tab.new_pane(
            new_pane_id_1,
            None,
            None,
            false,
            true,
            NewPanePlacement::default(),
            Some(client_id),
            None,
        )
        .unwrap();
    }
    tab.resize(
        client_id,
        ResizeStrategy::new(Resize::Increase, Some(Direction::Right)),
    )
    .unwrap();
    let _ = tab.focus_pane_with_id(PaneId::Terminal(7), false, false, client_id);
    let _ = tab.focus_pane_with_id(PaneId::Terminal(5), false, false, client_id);

    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn dump_screen() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let map = Arc::new(Mutex::new(HashMap::new()));
    tab.os_api = Box::new(FakeInputOutput {
        file_dumps: map.clone(),
        ..Default::default()
    });
    let new_pane_id = PaneId::Terminal(2);
    tab.new_pane(
        new_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(2, Vec::from("scratch".as_bytes()))
        .unwrap();
    let file = "/tmp/log.sh";
    tab.dump_active_terminal_screen(Some(file.to_string()), client_id, false)
        .unwrap();
    assert_eq!(
        map.lock().unwrap().get(file).unwrap(),
        "scratch",
        "screen was dumped properly"
    );
}

#[test]
fn clear_screen() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let map = Arc::new(Mutex::new(HashMap::new()));
    tab.os_api = Box::new(FakeInputOutput {
        file_dumps: map.clone(),
        ..Default::default()
    });
    let new_pane_id = PaneId::Terminal(2);
    tab.new_pane(
        new_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(2, Vec::from("scratch".as_bytes()))
        .unwrap();
    let file = "/tmp/log-clear-screen.sh";
    tab.clear_active_terminal_screen(client_id).unwrap();
    tab.dump_active_terminal_screen(Some(file.to_string()), client_id, false)
        .unwrap();
    assert_eq!(
        map.lock().unwrap().get(file).unwrap(),
        "",
        "screen was cleared properly"
    );
}

#[test]
fn new_floating_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(
        2,
        Vec::from("\n\n\n                   I am scratch terminal".as_bytes()),
    )
    .unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn new_stacked_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();
    tab.new_pane(
        new_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::Stacked {
            pane_id_to_stack_under: None,
            borderless: None,
        },
        Some(client_id),
        None,
    )
    .unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn floating_panes_persist_across_toggles() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    // here we send bytes to the pane when it's not visible to make sure they're still handled and
    // we see them once we toggle the panes back
    tab.handle_pty_bytes(
        2,
        Vec::from("\n\n\n                   I am scratch terminal".as_bytes()),
    )
    .unwrap();
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn toggle_floating_panes_off() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(
        2,
        Vec::from("\n\n\n                   I am scratch terminal".as_bytes()),
    )
    .unwrap();
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn toggle_floating_panes_on() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(
        2,
        Vec::from("\n\n\n                   I am scratch terminal".as_bytes()),
    )
    .unwrap();
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn five_new_floating_panes() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);
    let new_pane_id_4 = PaneId::Terminal(5);
    let new_pane_id_5 = PaneId::Terminal(6);
    let mut output = Output::default();
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_4,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_5,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(
        2,
        Vec::from("\n\n\n                   I am scratch terminal".as_bytes()),
    )
    .unwrap();
    tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn increase_floating_pane_size() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id_1 = PaneId::Terminal(2);
    let mut output = Output::default();
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(
        2,
        Vec::from("\n\n\n                   I am scratch terminal".as_bytes()),
    )
    .unwrap();
    tab.resize(client_id, ResizeStrategy::new(Resize::Increase, None))
        .unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn decrease_floating_pane_size() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id_1 = PaneId::Terminal(2);
    let mut output = Output::default();
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(
        2,
        Vec::from("\n\n\n                   I am scratch terminal".as_bytes()),
    )
    .unwrap();
    tab.resize(client_id, ResizeStrategy::new(Resize::Decrease, None))
        .unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn resize_floating_pane_left() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id_1 = PaneId::Terminal(2);
    let mut output = Output::default();
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(
        2,
        Vec::from("\n\n\n                   I am scratch terminal".as_bytes()),
    )
    .unwrap();
    tab.resize(
        client_id,
        ResizeStrategy::new(Resize::Increase, Some(Direction::Left)),
    )
    .unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn resize_floating_pane_right() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id_1 = PaneId::Terminal(2);
    let mut output = Output::default();
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(
        2,
        Vec::from("\n\n\n                   I am scratch terminal".as_bytes()),
    )
    .unwrap();
    tab.resize(
        client_id,
        ResizeStrategy::new(Resize::Increase, Some(Direction::Right)),
    )
    .unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn resize_floating_pane_up() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id_1 = PaneId::Terminal(2);
    let mut output = Output::default();
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(
        2,
        Vec::from("\n\n\n                   I am scratch terminal".as_bytes()),
    )
    .unwrap();
    tab.resize(
        client_id,
        ResizeStrategy::new(Resize::Increase, Some(Direction::Up)),
    )
    .unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn resize_floating_pane_down() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id_1 = PaneId::Terminal(2);
    let mut output = Output::default();
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(
        2,
        Vec::from("\n\n\n                   I am scratch terminal".as_bytes()),
    )
    .unwrap();
    tab.resize(
        client_id,
        ResizeStrategy::new(Resize::Increase, Some(Direction::Down)),
    )
    .unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn move_floating_pane_focus_left() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);
    let new_pane_id_4 = PaneId::Terminal(5);
    let new_pane_id_5 = PaneId::Terminal(6);
    let mut output = Output::default();
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_4,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_5,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(
        2,
        Vec::from("\n\n\n                   I am scratch terminal".as_bytes()),
    )
    .unwrap();
    tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.move_focus_left(client_id).unwrap();
    tab.render(&mut output, None).unwrap();
    let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_eq!(
        cursor_coordinates,
        Some((3, 3)),
        "cursor coordinates moved to the pane on the left"
    );

    assert_snapshot!(snapshot);
}

#[test]
fn move_floating_pane_focus_right() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);
    let new_pane_id_4 = PaneId::Terminal(5);
    let new_pane_id_5 = PaneId::Terminal(6);
    let mut output = Output::default();
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_4,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_5,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(
        2,
        Vec::from("\n\n\n                   I am scratch terminal".as_bytes()),
    )
    .unwrap();
    tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.move_focus_left(client_id).unwrap();
    tab.move_focus_right(client_id).unwrap();
    tab.render(&mut output, None).unwrap();
    let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_eq!(
        cursor_coordinates,
        Some((5, 5)),
        "cursor coordinates moved to the pane on the right"
    );

    assert_snapshot!(snapshot);
}

#[test]
fn move_floating_pane_focus_up() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);
    let new_pane_id_4 = PaneId::Terminal(5);
    let new_pane_id_5 = PaneId::Terminal(6);
    let mut output = Output::default();
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_4,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_5,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(
        2,
        Vec::from("\n\n\n                   I am scratch terminal".as_bytes()),
    )
    .unwrap();
    tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.move_focus_up(client_id).unwrap();
    tab.render(&mut output, None).unwrap();
    let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_eq!(
        cursor_coordinates,
        Some((3, 3)),
        "cursor coordinates moved to the pane above"
    );

    assert_snapshot!(snapshot);
}

#[test]
fn move_floating_pane_focus_down() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);
    let new_pane_id_4 = PaneId::Terminal(5);
    let new_pane_id_5 = PaneId::Terminal(6);
    let mut output = Output::default();
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_4,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_5,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(
        2,
        Vec::from("\n\n\n                   I am scratch terminal".as_bytes()),
    )
    .unwrap();
    tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.move_focus_up(client_id).unwrap();
    tab.move_focus_down(client_id).unwrap();
    tab.render(&mut output, None).unwrap();
    let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_eq!(
        cursor_coordinates,
        Some((5, 5)),
        "cursor coordinates moved to the pane below"
    );

    assert_snapshot!(snapshot);
}

#[test]
fn move_floating_pane_focus_with_mouse() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);
    let new_pane_id_4 = PaneId::Terminal(5);
    let new_pane_id_5 = PaneId::Terminal(6);
    let mut output = Output::default();
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_4,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_5,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(
        2,
        Vec::from("\n\n\n                   I am scratch terminal".as_bytes()),
    )
    .unwrap();
    tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_press_event(Position::new(9, 71)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_release_event(Position::new(9, 71)),
        client_id,
    )
    .unwrap();
    tab.render(&mut output, None).unwrap();
    let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_eq!(
        cursor_coordinates,
        Some((35, 10)),
        "cursor coordinates moved to the clicked pane"
    );

    assert_snapshot!(snapshot);
}

#[test]
fn move_pane_focus_with_mouse_to_non_floating_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);
    let new_pane_id_4 = PaneId::Terminal(5);
    let new_pane_id_5 = PaneId::Terminal(6);
    let mut output = Output::default();
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_4,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_5,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(
        2,
        Vec::from("\n\n\n                   I am scratch terminal".as_bytes()),
    )
    .unwrap();
    tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_press_event(Position::new(4, 71)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_release_event(Position::new(4, 71)),
        client_id,
    )
    .unwrap();
    tab.render(&mut output, None).unwrap();
    let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_eq!(
        cursor_coordinates,
        Some((1, 1)),
        "cursor coordinates moved to the clicked pane"
    );

    assert_snapshot!(snapshot);
}

#[test]
fn drag_pane_with_mouse() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);
    let new_pane_id_4 = PaneId::Terminal(5);
    let new_pane_id_5 = PaneId::Terminal(6);
    let mut output = Output::default();
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_4,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_5,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(
        2,
        Vec::from("\n\n\n                   I am scratch terminal".as_bytes()),
    )
    .unwrap();
    tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_press_event(Position::new(5, 71)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_motion_event(Position::new(7, 75)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_release_event(Position::new(7, 75)),
        client_id,
    )
    .unwrap();
    tab.render(&mut output, None).unwrap();
    let (snapshot, _cursor_coordinates) = take_snapshot_and_cursor_position(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_snapshot!(snapshot);
}

#[test]
fn mark_text_inside_floating_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);
    let new_pane_id_4 = PaneId::Terminal(5);
    let new_pane_id_5 = PaneId::Terminal(6);
    let mut output = Output::default();
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_4,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_5,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(
        2,
        Vec::from("\n\n\n                   I am scratch terminal".as_bytes()),
    )
    .unwrap();
    tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_press_event(Position::new(6, 30)),
        client_id,
    )
    .unwrap();
    assert!(
        tab.selecting_with_mouse_in_pane.is_some(),
        "started selecting with mouse on click"
    );
    tab.handle_mouse_event(
        &MouseEvent::new_left_release_event(Position::new(5, 15)),
        client_id,
    )
    .unwrap();
    assert!(
        tab.selecting_with_mouse_in_pane.is_none(),
        "stopped selecting with mouse on release"
    );
    tab.render(&mut output, None).unwrap();
    let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_eq!(
        cursor_coordinates,
        Some((5, 5)),
        "cursor coordinates stayed in clicked pane"
    );

    assert_snapshot!(snapshot);
}

#[test]
fn resize_tab_with_floating_panes() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);
    let new_pane_id_4 = PaneId::Terminal(5);
    let new_pane_id_5 = PaneId::Terminal(6);
    let mut output = Output::default();
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_4,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_5,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(
        2,
        Vec::from("\n\n\n                   I am scratch terminal".as_bytes()),
    )
    .unwrap();
    tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.resize_whole_tab(Size {
        cols: 100,
        rows: 10,
    })
    .unwrap();
    tab.render(&mut output, None).unwrap();
    let (snapshot, _cursor_coordinates) = take_snapshot_and_cursor_position(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_snapshot!(snapshot);
}

#[test]
fn shrink_whole_tab_with_floating_panes_horizontally_and_vertically() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);
    let new_pane_id_4 = PaneId::Terminal(5);
    let new_pane_id_5 = PaneId::Terminal(6);
    let mut output = Output::default();
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_4,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_5,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(
        2,
        Vec::from("\n\n\n                   I am scratch terminal".as_bytes()),
    )
    .unwrap();
    tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.resize_whole_tab(Size { cols: 50, rows: 10 }).unwrap();
    tab.render(&mut output, None).unwrap();
    let (snapshot, _cursor_coordinates) = take_snapshot_and_cursor_position(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_snapshot!(snapshot);
}

#[test]
fn shrink_whole_tab_with_floating_panes_horizontally_and_vertically_and_expand_back() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);
    let new_pane_id_4 = PaneId::Terminal(5);
    let new_pane_id_5 = PaneId::Terminal(6);
    let mut output = Output::default();
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_4,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_5,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(
        2,
        Vec::from("\n\n\n                   I am scratch terminal".as_bytes()),
    )
    .unwrap();
    tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes()))
        .unwrap();
    tab.resize_whole_tab(Size { cols: 50, rows: 10 }).unwrap();
    tab.resize_whole_tab(Size {
        cols: 121,
        rows: 20,
    })
    .unwrap();
    tab.render(&mut output, None).unwrap();
    let (snapshot, _cursor_coordinates) = take_snapshot_and_cursor_position(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_snapshot!(snapshot);
}

#[test]
fn embed_floating_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(
        2,
        Vec::from("\n\n\n                   I am scratch terminal".as_bytes()),
    )
    .unwrap();
    tab.toggle_pane_embed_or_floating(client_id).unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn float_embedded_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();
    tab.new_pane(
        new_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(
        2,
        Vec::from("\n\n\n                   I am an embedded pane".as_bytes()),
    )
    .unwrap();
    tab.toggle_pane_embed_or_floating(client_id).unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn embed_floating_pane_without_pane_frames() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();
    tab.set_pane_frames(false);
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(
        2,
        Vec::from("\n\n\n                   I am scratch terminal".as_bytes()),
    )
    .unwrap();
    tab.toggle_pane_embed_or_floating(client_id).unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn float_embedded_pane_without_pane_frames() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();
    tab.set_pane_frames(false);
    tab.new_pane(
        new_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(
        2,
        Vec::from("\n\n\n                   I am an embedded pane".as_bytes()),
    )
    .unwrap();
    tab.toggle_pane_embed_or_floating(client_id).unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn cannot_float_only_embedded_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let mut output = Output::default();
    tab.handle_pty_bytes(
        1,
        Vec::from("\n\n\n                   I am an embedded pane".as_bytes()),
    )
    .unwrap();
    tab.toggle_pane_embed_or_floating(client_id).unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn replacing_existing_wide_characters() {
    // this is a real world use case using ncmpcpp with wide characters and scrolling
    // the reason we don't break it down is that it exposes quite a few edge cases with wide
    // characters that we should handle properly
    let size = Size {
        cols: 238,
        rows: 48,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let mut output = Output::default();
    let pane_content = read_fixture("ncmpcpp-wide-chars");
    tab.handle_pty_bytes(1, pane_content).unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn rename_embedded_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let mut output = Output::default();
    tab.handle_pty_bytes(
        1,
        Vec::from("\n\n\n                   I am an embedded pane".as_bytes()),
    )
    .unwrap();
    tab.update_active_pane_name("Renamed empedded pane".as_bytes().to_vec(), client_id)
        .unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn rename_floating_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();
    tab.new_pane(
        new_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(
        2,
        Vec::from("\n\n\n                   I am a floating pane".as_bytes()),
    )
    .unwrap();
    tab.toggle_pane_embed_or_floating(client_id).unwrap();
    tab.update_active_pane_name("Renamed floating pane".as_bytes().to_vec(), client_id)
        .unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn wide_characters_in_left_title_side() {
    // this test makes sure the title doesn't overflow when it has wide characters
    let size = Size {
        cols: 238,
        rows: 48,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let mut output = Output::default();
    let pane_content = read_fixture("title-wide-chars");
    tab.handle_pty_bytes(1, pane_content).unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn save_cursor_position_across_resizes() {
    // the save cursor position ANSI instruction (CSI s) needs to point to the same character after we
    // resize the pane
    let size = Size { cols: 100, rows: 5 };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let mut output = Output::default();

    tab.handle_pty_bytes(
        1,
        Vec::from("\n\n\rI am some text\n\rI am another line of text\n\rLet's save the cursor position here \u{1b}[sI should be ovewritten".as_bytes()),
    ).unwrap();

    // We check cursor and saved cursor are handled separately by:
    // 1. moving real cursor up two lines
    let _ = tab.handle_pty_bytes(1, Vec::from("\u{1b}[2A".as_bytes()));
    // 2. resizing so real cursor gets lost above the viewport, which resets it to row 0
    // The saved cursor ends up on row 1, allowing detection if it (incorrectly) gets reset too
    tab.resize_whole_tab(Size { cols: 35, rows: 4 }).unwrap();

    // Now overwrite
    tab.handle_pty_bytes(1, Vec::from("\u{1b}[uthis overwrote me!".as_bytes()))
        .unwrap();

    tab.resize_whole_tab(Size { cols: 100, rows: 3 }).unwrap();

    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn move_floating_pane_with_sixel_image() {
    let new_pane_id = PaneId::Terminal(2);
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
    let mut tab = create_new_tab_with_sixel_support(size, sixel_image_store.clone());
    let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels {
        width: 8,
        height: 21,
    })));
    let mut output = Output::new(sixel_image_store.clone(), character_cell_size, true, true);

    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    let fixture = read_fixture("sixel-image-500px.six");
    tab.handle_pty_bytes(2, fixture).unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_press_event(Position::new(5, 71)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_motion_event(Position::new(7, 75)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_release_event(Position::new(7, 75)),
        client_id,
    )
    .unwrap();

    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot_with_sixel(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
        sixel_image_store,
    );

    assert_snapshot!(snapshot);
}

#[test]
fn floating_pane_above_sixel_image() {
    let new_pane_id = PaneId::Terminal(2);
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
    let mut tab = create_new_tab_with_sixel_support(size, sixel_image_store.clone());
    let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels {
        width: 8,
        height: 21,
    })));
    let mut output = Output::new(sixel_image_store.clone(), character_cell_size, true, true);

    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    let fixture = read_fixture("sixel-image-500px.six");
    tab.handle_pty_bytes(1, fixture).unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_press_event(Position::new(5, 71)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_motion_event(Position::new(7, 75)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_release_event(Position::new(7, 75)),
        client_id,
    )
    .unwrap();

    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot_with_sixel(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
        sixel_image_store,
    );

    assert_snapshot!(snapshot);
}

#[test]
fn suppress_tiled_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();
    tab.replace_active_pane_with_editor_pane(new_pane_id, client_id)
        .unwrap();
    tab.handle_pty_bytes(2, Vec::from("\n\n\nI am an editor pane".as_bytes()))
        .unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn suppress_floating_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let editor_pane_id = PaneId::Terminal(3);
    let mut output = Output::default();

    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.replace_active_pane_with_editor_pane(editor_pane_id, client_id)
        .unwrap();
    tab.handle_pty_bytes(3, Vec::from("\n\n\nI am an editor pane".as_bytes()))
        .unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn close_suppressing_tiled_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();
    tab.replace_active_pane_with_editor_pane(new_pane_id, client_id)
        .unwrap();
    tab.handle_pty_bytes(2, Vec::from("\n\n\nI am an editor pane".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(1, Vec::from("\n\n\nI am the original pane".as_bytes()))
        .unwrap();
    tab.close_pane(new_pane_id, false, None);
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn close_suppressing_floating_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let editor_pane_id = PaneId::Terminal(3);
    let mut output = Output::default();

    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.replace_active_pane_with_editor_pane(editor_pane_id, client_id)
        .unwrap();
    tab.handle_pty_bytes(3, Vec::from("\n\n\nI am an editor pane".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(2, Vec::from("\n\n\nI am the original pane".as_bytes()))
        .unwrap();
    tab.close_pane(editor_pane_id, false, None);
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn suppress_tiled_pane_float_it_and_close() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();
    tab.replace_active_pane_with_editor_pane(new_pane_id, client_id)
        .unwrap();
    tab.handle_pty_bytes(2, Vec::from("\n\n\nI am an editor pane".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(1, Vec::from("\n\n\nI am the original pane".as_bytes()))
        .unwrap();
    tab.toggle_pane_embed_or_floating(client_id).unwrap();
    tab.close_pane(new_pane_id, false, None);
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn suppress_floating_pane_embed_it_and_close_it() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let editor_pane_id = PaneId::Terminal(3);
    let mut output = Output::default();

    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.replace_active_pane_with_editor_pane(editor_pane_id, client_id)
        .unwrap();
    tab.handle_pty_bytes(3, Vec::from("\n\n\nI am an editor pane".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(2, Vec::from("\n\n\nI am the original pane".as_bytes()))
        .unwrap();
    tab.toggle_pane_embed_or_floating(client_id).unwrap();
    tab.close_pane(editor_pane_id, false, None);
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn resize_whole_tab_while_tiled_pane_is_suppressed() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();
    tab.replace_active_pane_with_editor_pane(new_pane_id, client_id)
        .unwrap();
    tab.handle_pty_bytes(2, Vec::from("\n\n\nI am an editor pane".as_bytes()))
        .unwrap();
    tab.resize_whole_tab(Size {
        cols: 100,
        rows: 10,
    })
    .unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn resize_whole_tab_while_floting_pane_is_suppressed() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let editor_pane_id = PaneId::Terminal(3);
    let mut output = Output::default();

    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.replace_active_pane_with_editor_pane(editor_pane_id, client_id)
        .unwrap();
    tab.handle_pty_bytes(3, Vec::from("\n\n\nI am an editor pane".as_bytes()))
        .unwrap();
    tab.resize_whole_tab(Size {
        cols: 100,
        rows: 10,
    })
    .unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn enter_search_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mode_info = ModeInfo {
        mode: InputMode::Search,
        ..Default::default()
    };
    let mut tab = create_new_tab(size, mode_info);
    let mut output = Output::default();
    let pane_content = read_fixture("grid_copy");
    tab.handle_pty_bytes(1, pane_content).unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!("search_tab_nothing_highlighted", snapshot);

    // Pane title should show 'tortor' as search term
    // Only lines containing 'tortor' get marked as render-targets, so
    // only those are updated (search-styling is not visible here).
    tab.update_search_term("tortor".as_bytes().to_vec(), client_id)
        .unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!("search_tab_highlight_tortor", snapshot);

    // Pane title should show search modifiers
    tab.toggle_search_wrap(client_id);
    tab.toggle_search_whole_words(client_id);
    tab.toggle_search_case_sensitivity(client_id);
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!("search_tab_highlight_tortor_modified", snapshot);

    // And only the search term again
    tab.toggle_search_wrap(client_id);
    tab.toggle_search_whole_words(client_id);
    tab.toggle_search_case_sensitivity(client_id);

    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!("search_tab_highlight_tortor", snapshot);
}

#[test]
fn enter_search_floating_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mode_info = ModeInfo {
        mode: InputMode::Search,
        ..Default::default()
    };
    let mut tab = create_new_tab(size, mode_info);
    let new_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();

    let pane_content = read_fixture("grid_copy");
    tab.handle_pty_bytes(2, pane_content).unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!("search_floating_tab_nothing_highlighted", snapshot);

    // Only the line inside the floating tab which contain 'fring' should be in the new snapshot
    tab.update_search_term("fring".as_bytes().to_vec(), client_id)
        .unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!("search_floating_tab_highlight_fring", snapshot);
}

#[test]
fn pane_in_sgr_button_event_tracking_mouse_mode() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;

    let mut pty_instruction_bus = MockPtyInstructionBus::new();
    let mut tab = create_new_tab_with_mock_pty_writer(
        size,
        ModeInfo::default(),
        pty_instruction_bus.pty_write_sender(),
    );
    pty_instruction_bus.start();

    let sgr_mouse_mode_any_button = String::from("\u{1b}[?1002;1006h"); // button event tracking (1002) with SGR encoding (1006)
    tab.handle_pty_bytes(1, sgr_mouse_mode_any_button.as_bytes().to_vec())
        .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_press_event(Position::new(5, 71)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_motion_event(Position::new(9, 72)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_release_event(Position::new(7, 75)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_right_press_event(Position::new(5, 71)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_right_motion_event(Position::new(9, 72)),
        client_id,
    )
    .unwrap();
    // tab.handle_right_mouse_release(&Position::new(7, 75), client_id)
    tab.handle_mouse_event(
        &MouseEvent::new_right_release_event(Position::new(7, 75)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_middle_press_event(Position::new(5, 71)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_middle_motion_event(Position::new(9, 72)),
        client_id,
    )
    .unwrap();
    // tab.handle_middle_mouse_release(&Position::new(7, 75), client_id)
    tab.handle_mouse_event(
        &MouseEvent::new_middle_release_event(Position::new(7, 75)),
        client_id,
    )
    .unwrap();
    tab.handle_scrollwheel_up(&Position::new(5, 71), 1, client_id)
        .unwrap();
    tab.handle_scrollwheel_down(&Position::new(5, 71), 1, client_id)
        .unwrap();

    pty_instruction_bus.exit();

    assert_eq!(
        pty_instruction_bus.clone_output(),
        vec![
            "\u{1b}[<0;71;5M".to_string(),  // SGR left click
            "\u{1b}[<32;72;9M".to_string(), // SGR left click (hold)
            "\u{1b}[<0;75;7m".to_string(),  // SGR left button release
            "\u{1b}[<2;71;5M".to_string(),  // SGR right click
            "\u{1b}[<34;72;9M".to_string(), // SGR right click (hold)
            "\u{1b}[<2;75;7m".to_string(),  // SGR right button release
            "\u{1b}[<1;71;5M".to_string(),  // SGR middle click
            "\u{1b}[<33;72;9M".to_string(), // SGR middle click (hold)
            "\u{1b}[<1;75;7m".to_string(),  // SGR middle button release
            "\u{1b}[<64;71;5M".to_string(), // SGR scroll up
            "\u{1b}[<65;71;5M".to_string(), // SGR scroll down
        ]
    );
}

#[test]
fn pane_in_sgr_normal_event_tracking_mouse_mode() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;

    let mut pty_instruction_bus = MockPtyInstructionBus::new();
    let mut tab = create_new_tab_with_mock_pty_writer(
        size,
        ModeInfo::default(),
        pty_instruction_bus.pty_write_sender(),
    );
    pty_instruction_bus.start();

    let sgr_mouse_mode_any_button = String::from("\u{1b}[?1000;1006h"); // normal event tracking (1000) with sgr encoding (1006)
    tab.handle_pty_bytes(1, sgr_mouse_mode_any_button.as_bytes().to_vec())
        .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_press_event(Position::new(5, 71)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_motion_event(Position::new(9, 72)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_release_event(Position::new(7, 75)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_right_press_event(Position::new(5, 71)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_right_motion_event(Position::new(9, 72)),
        client_id,
    )
    .unwrap();
    // tab.handle_right_mouse_release(&Position::new(7, 75), client_id)
    tab.handle_mouse_event(
        &MouseEvent::new_right_release_event(Position::new(7, 75)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_middle_press_event(Position::new(5, 71)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_middle_motion_event(Position::new(9, 72)),
        client_id,
    )
    .unwrap();
    // tab.handle_middle_mouse_release(&Position::new(7, 75), client_id)
    tab.handle_mouse_event(
        &MouseEvent::new_middle_release_event(Position::new(7, 75)),
        client_id,
    )
    .unwrap();
    tab.handle_scrollwheel_up(&Position::new(5, 71), 1, client_id)
        .unwrap();
    tab.handle_scrollwheel_down(&Position::new(5, 71), 1, client_id)
        .unwrap();

    pty_instruction_bus.exit();

    assert_eq!(
        pty_instruction_bus.clone_output(),
        vec![
            "\u{1b}[<0;71;5M".to_string(), // SGR left click
            // no hold event here, as hold events are not reported in normal mode
            "\u{1b}[<0;75;7m".to_string(), // SGR left button release
            "\u{1b}[<2;71;5M".to_string(), // SGR right click
            // no hold event here, as hold events are not reported in normal mode
            "\u{1b}[<2;75;7m".to_string(), // SGR right button release
            "\u{1b}[<1;71;5M".to_string(), // SGR middle click
            // no hold event here, as hold events are not reported in normal mode
            "\u{1b}[<1;75;7m".to_string(),  // SGR middle button release
            "\u{1b}[<64;71;5M".to_string(), // SGR scroll up
            "\u{1b}[<65;71;5M".to_string(), // SGR scroll down
        ]
    );
}

#[test]
fn pane_in_sgr_any_event_tracking_mouse_mode() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;

    let mut pty_instruction_bus = MockPtyInstructionBus::new();
    let mut tab = create_new_tab_with_mock_pty_writer(
        size,
        ModeInfo::default(),
        pty_instruction_bus.pty_write_sender(),
    );
    pty_instruction_bus.start();

    let sgr_mouse_mode_any_button = String::from("\u{1b}[?1003;1006h"); // any event tracking (1003) with SGR encoding (1006)
    tab.handle_pty_bytes(1, sgr_mouse_mode_any_button.as_bytes().to_vec())
        .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_press_event(Position::new(5, 71)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_motion_event(Position::new(9, 72)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_release_event(Position::new(7, 75)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_right_press_event(Position::new(5, 71)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_right_motion_event(Position::new(9, 72)),
        client_id,
    )
    .unwrap();
    // tab.handle_right_mouse_release(&Position::new(7, 75), client_id)
    tab.handle_mouse_event(
        &MouseEvent::new_right_release_event(Position::new(7, 75)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_middle_press_event(Position::new(5, 71)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_middle_motion_event(Position::new(9, 72)),
        client_id,
    )
    .unwrap();
    // tab.handle_middle_mouse_release(&Position::new(7, 75), client_id)
    tab.handle_mouse_event(
        &MouseEvent::new_middle_release_event(Position::new(7, 75)),
        client_id,
    )
    .unwrap();
    tab.handle_scrollwheel_up(&Position::new(5, 71), 1, client_id)
        .unwrap();
    tab.handle_scrollwheel_down(&Position::new(5, 71), 1, client_id)
        .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_buttonless_motion(Position::new(9, 72)),
        client_id,
    )
    .unwrap();

    pty_instruction_bus.exit();

    assert_eq!(
        pty_instruction_bus.clone_output(),
        vec![
            "\u{1b}[<0;71;5M".to_string(),  // SGR left click
            "\u{1b}[<32;72;9M".to_string(), // SGR left click (hold)
            "\u{1b}[<0;75;7m".to_string(),  // SGR left button release
            "\u{1b}[<2;71;5M".to_string(),  // SGR right click
            "\u{1b}[<34;72;9M".to_string(), // SGR right click (hold)
            "\u{1b}[<2;75;7m".to_string(),  // SGR right button release
            "\u{1b}[<1;71;5M".to_string(),  // SGR middle click
            "\u{1b}[<33;72;9M".to_string(), // SGR middle click (hold)
            "\u{1b}[<1;75;7m".to_string(),  // SGR middle button release
            "\u{1b}[<64;71;5M".to_string(), // SGR scroll up
            "\u{1b}[<65;71;5M".to_string(), // SGR scroll down
            "\u{1b}[<35;72;9M".to_string(), // SGR buttonless motion
        ]
    );
}

#[test]
fn pane_in_utf8_button_event_tracking_mouse_mode() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;

    let mut pty_instruction_bus = MockPtyInstructionBus::new();
    let mut tab = create_new_tab_with_mock_pty_writer(
        size,
        ModeInfo::default(),
        pty_instruction_bus.pty_write_sender(),
    );
    pty_instruction_bus.start();

    let sgr_mouse_mode_any_button = String::from("\u{1b}[?1002;1005h"); // button event tracking (1002) with utf8 encoding (1005)
    tab.handle_pty_bytes(1, sgr_mouse_mode_any_button.as_bytes().to_vec())
        .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_press_event(Position::new(5, 71)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_motion_event(Position::new(9, 72)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_release_event(Position::new(7, 75)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_right_press_event(Position::new(5, 71)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_right_motion_event(Position::new(9, 72)),
        client_id,
    )
    .unwrap();
    // tab.handle_right_mouse_release(&Position::new(7, 75), client_id)
    tab.handle_mouse_event(
        &MouseEvent::new_right_release_event(Position::new(7, 75)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_middle_press_event(Position::new(5, 71)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_middle_motion_event(Position::new(9, 72)),
        client_id,
    )
    .unwrap();
    // tab.handle_middle_mouse_release(&Position::new(7, 75), client_id)
    tab.handle_mouse_event(
        &MouseEvent::new_middle_release_event(Position::new(7, 75)),
        client_id,
    )
    .unwrap();
    tab.handle_scrollwheel_up(&Position::new(5, 71), 1, client_id)
        .unwrap();
    tab.handle_scrollwheel_down(&Position::new(5, 71), 1, client_id)
        .unwrap();

    pty_instruction_bus.exit();

    assert_eq!(
        pty_instruction_bus.clone_output(),
        vec![
            "\u{1b}[M g%".to_string(),  // utf8 left click
            "\u{1b}[M@h)".to_string(),  // utf8 left click (hold)
            "\u{1b}[M#k'".to_string(),  // utf8 left button release
            "\u{1b}[M\"g%".to_string(), // utf8 right click
            "\u{1b}[MBh)".to_string(),  // utf8 right click (hold)
            "\u{1b}[M#k'".to_string(),  // utf8 right button release
            "\u{1b}[M!g%".to_string(),  // utf8 middle click
            "\u{1b}[MAh)".to_string(),  // utf8 middle click (hold)
            "\u{1b}[M#k'".to_string(),  // utf8 middle click release
            "\u{1b}[M`g%".to_string(),  // utf8 scroll up
            "\u{1b}[Mag%".to_string(),  // utf8 scroll down
        ]
    );
}

#[test]
fn pane_in_utf8_normal_event_tracking_mouse_mode() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;

    let mut pty_instruction_bus = MockPtyInstructionBus::new();
    let mut tab = create_new_tab_with_mock_pty_writer(
        size,
        ModeInfo::default(),
        pty_instruction_bus.pty_write_sender(),
    );
    pty_instruction_bus.start();

    let sgr_mouse_mode_any_button = String::from("\u{1b}[?1000;1005h"); // normal event tracking (1000) with sgr encoding (1006)
    tab.handle_pty_bytes(1, sgr_mouse_mode_any_button.as_bytes().to_vec())
        .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_press_event(Position::new(5, 71)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_motion_event(Position::new(9, 72)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_release_event(Position::new(7, 75)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_right_press_event(Position::new(5, 71)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_right_motion_event(Position::new(9, 72)),
        client_id,
    )
    .unwrap();
    // tab.handle_right_mouse_release(&Position::new(7, 75), client_id)
    tab.handle_mouse_event(
        &MouseEvent::new_right_release_event(Position::new(7, 75)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_middle_press_event(Position::new(5, 71)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_middle_motion_event(Position::new(9, 72)),
        client_id,
    )
    .unwrap();
    // tab.handle_middle_mouse_release(&Position::new(7, 75), client_id)
    tab.handle_mouse_event(
        &MouseEvent::new_middle_release_event(Position::new(7, 75)),
        client_id,
    )
    .unwrap();
    tab.handle_scrollwheel_up(&Position::new(5, 71), 1, client_id)
        .unwrap();
    tab.handle_scrollwheel_down(&Position::new(5, 71), 1, client_id)
        .unwrap();

    pty_instruction_bus.exit();

    assert_eq!(
        pty_instruction_bus.clone_output(),
        vec![
            "\u{1b}[M g%".to_string(), // utf8 left click
            // no hold event here, as hold events are not reported in normal mode
            "\u{1b}[M#k'".to_string(),  // utf8 left button release
            "\u{1b}[M\"g%".to_string(), // utf8 right click
            // no hold event here, as hold events are not reported in normal mode
            "\u{1b}[M#k'".to_string(), // utf8 right button release
            "\u{1b}[M!g%".to_string(), // utf8 middle click
            // no hold event here, as hold events are not reported in normal mode
            "\u{1b}[M#k'".to_string(), // utf8 middle click release
            "\u{1b}[M`g%".to_string(), // utf8 scroll up
            "\u{1b}[Mag%".to_string(), // utf8 scroll down
        ]
    );
}

#[test]
fn pane_in_utf8_any_event_tracking_mouse_mode() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;

    let mut pty_instruction_bus = MockPtyInstructionBus::new();
    let mut tab = create_new_tab_with_mock_pty_writer(
        size,
        ModeInfo::default(),
        pty_instruction_bus.pty_write_sender(),
    );
    pty_instruction_bus.start();

    let sgr_mouse_mode_any_button = String::from("\u{1b}[?1003;1005h"); // any event tracking (1002) with utf8 encoding (1005)
    tab.handle_pty_bytes(1, sgr_mouse_mode_any_button.as_bytes().to_vec())
        .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_press_event(Position::new(5, 71)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_motion_event(Position::new(9, 72)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_release_event(Position::new(7, 75)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_right_press_event(Position::new(5, 71)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_right_motion_event(Position::new(9, 72)),
        client_id,
    )
    .unwrap();
    // tab.handle_right_mouse_release(&Position::new(7, 75), client_id)
    tab.handle_mouse_event(
        &MouseEvent::new_right_release_event(Position::new(7, 75)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_middle_press_event(Position::new(5, 71)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_middle_motion_event(Position::new(9, 72)),
        client_id,
    )
    .unwrap();
    // tab.handle_middle_mouse_release(&Position::new(7, 75), client_id)
    tab.handle_mouse_event(
        &MouseEvent::new_middle_release_event(Position::new(7, 75)),
        client_id,
    )
    .unwrap();
    tab.handle_scrollwheel_up(&Position::new(5, 71), 1, client_id)
        .unwrap();
    tab.handle_scrollwheel_down(&Position::new(5, 71), 1, client_id)
        .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_buttonless_motion(Position::new(9, 72)),
        client_id,
    )
    .unwrap();

    pty_instruction_bus.exit();

    assert_eq!(
        pty_instruction_bus.clone_output(),
        vec![
            "\u{1b}[M g%".to_string(),  // utf8 left click
            "\u{1b}[M@h)".to_string(),  // utf8 left click (hold)
            "\u{1b}[M#k'".to_string(),  // utf8 left button release
            "\u{1b}[M\"g%".to_string(), // utf8 right click
            "\u{1b}[MBh)".to_string(),  // utf8 right click (hold)
            "\u{1b}[M#k'".to_string(),  // utf8 right button release
            "\u{1b}[M!g%".to_string(),  // utf8 middle click
            "\u{1b}[MAh)".to_string(),  // utf8 middle click (hold)
            "\u{1b}[M#k'".to_string(),  // utf8 middle click release
            "\u{1b}[M`g%".to_string(),  // utf8 scroll up
            "\u{1b}[Mag%".to_string(),  // utf8 scroll down
            "\u{1b}[MCh)".to_string(),  // urf8 buttonless motion
        ]
    );
}

#[test]
fn tab_with_basic_layout() {
    let layout = r#"
        layout {
            pane split_direction="Vertical" {
                pane
                pane split_direction="Horizontal" {
                    pane
                    pane
                }
            }
        }
    "#;
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab_with_layout(size, ModeInfo::default(), layout);
    let mut output = Output::default();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn tab_with_layout_that_has_floating_panes() {
    let layout = r#"
        layout {
            pane
            floating_panes {
                pane
                pane
            }
        }
    "#;
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab_with_layout(size, ModeInfo::default(), layout);
    let mut output = Output::default();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn tab_with_nested_layout() {
    let layout = r#"
        layout {
            pane_template name="top-and-vertical-sandwich" {
                pane
                vertical-sandwich {
                    pane
                }
            }
            pane_template name="vertical-sandwich" split_direction="vertical" {
                pane
                children
                pane
            }
            pane_template name="nested-vertical-sandwich" split_direction="vertical" {
                pane
                top-and-vertical-sandwich
                pane
            }
            nested-vertical-sandwich
        }
    "#;
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab_with_layout(size, ModeInfo::default(), layout);
    let mut output = Output::default();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn tab_with_nested_uneven_layout() {
    let layout = r#"
        layout {
            pane_template name="horizontal-with-vertical-top" {
                pane split_direction="Vertical" {
                    pane
                    children
                }
                pane
            }
            horizontal-with-vertical-top name="my tab" {
                pane
                pane
            }
        }
    "#;
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab_with_layout(size, ModeInfo::default(), layout);
    let mut output = Output::default();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn pane_bracketed_paste_ignored_when_not_in_bracketed_paste_mode() {
    // regression test for: https://github.com/zellij-org/zellij/issues/1687
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id: u16 = 1;

    let mut pty_instruction_bus = MockPtyInstructionBus::new();
    let mut tab = create_new_tab_with_mock_pty_writer(
        size,
        ModeInfo::default(),
        pty_instruction_bus.pty_write_sender(),
    );
    pty_instruction_bus.start();

    let bracketed_paste_start = vec![27, 91, 50, 48, 48, 126]; // \u{1b}[200~
    let bracketed_paste_end = vec![27, 91, 50, 48, 49, 126]; // \u{1b}[201
    tab.write_to_active_terminal(&None, bracketed_paste_start, false, client_id)
        .unwrap();
    tab.write_to_active_terminal(&None, "test".as_bytes().to_vec(), false, client_id)
        .unwrap();
    tab.write_to_active_terminal(&None, bracketed_paste_end, false, client_id)
        .unwrap();

    pty_instruction_bus.exit();

    assert_eq!(pty_instruction_bus.clone_output(), vec!["", "test", ""]);
}

#[test]
fn pane_faux_scrolling_in_alternate_mode() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id: u16 = 1;
    let lines_to_scroll = 3;

    let mut pty_instruction_bus = MockPtyInstructionBus::new();
    let mut tab = create_new_tab_with_mock_pty_writer(
        size,
        ModeInfo::default(),
        pty_instruction_bus.pty_write_sender(),
    );
    pty_instruction_bus.start();

    let enable_alternate_screen = String::from("\u{1b}[?1049h"); // CSI ? 1049 h -> switch to the Alternate Screen Buffer
    let set_application_mode = String::from("\u{1b}[?1h");

    // no output since alternate scren not active yet
    tab.handle_scrollwheel_up(&Position::new(1, 1), lines_to_scroll, client_id)
        .unwrap();
    tab.handle_scrollwheel_down(&Position::new(1, 1), lines_to_scroll, client_id)
        .unwrap();

    tab.handle_pty_bytes(1, enable_alternate_screen.as_bytes().to_vec())
        .unwrap();
    // CSI A * lines_to_scroll, CSI B * lines_to_scroll
    tab.handle_scrollwheel_up(&Position::new(1, 1), lines_to_scroll, client_id)
        .unwrap();
    tab.handle_scrollwheel_down(&Position::new(1, 1), lines_to_scroll, client_id)
        .unwrap();

    tab.handle_pty_bytes(1, set_application_mode.as_bytes().to_vec())
        .unwrap();
    // SS3 A * lines_to_scroll, SS3 B * lines_to_scroll
    tab.handle_scrollwheel_up(&Position::new(1, 1), lines_to_scroll, client_id)
        .unwrap();
    tab.handle_scrollwheel_down(&Position::new(1, 1), lines_to_scroll, client_id)
        .unwrap();

    pty_instruction_bus.exit();

    let mut expected: Vec<&str> = Vec::new();
    expected.append(&mut vec!["\u{1b}[A"; lines_to_scroll]);
    expected.append(&mut vec!["\u{1b}[B"; lines_to_scroll]);
    expected.append(&mut vec!["\u{1b}OA"; lines_to_scroll]);
    expected.append(&mut vec!["\u{1b}OB"; lines_to_scroll]);

    assert_eq!(pty_instruction_bus.clone_output(), expected);
}

#[test]
fn move_pane_focus_sends_tty_csi_event() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let tty_stdin_bytes = Arc::new(Mutex::new(BTreeMap::new()));
    let os_api = Box::new(FakeInputOutput {
        tty_stdin_bytes: tty_stdin_bytes.clone(),
        ..Default::default()
    });
    let mut tab = create_new_tab_with_os_api(size, ModeInfo::default(), &os_api);
    let new_pane_id_1 = PaneId::Terminal(2);
    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(
        1,
        // subscribe to focus events
        Vec::from("\u{1b}[?1004h".as_bytes()),
    )
    .unwrap();
    tab.handle_pty_bytes(
        2,
        // subscribe to focus events
        Vec::from("\u{1b}[?1004h".as_bytes()),
    )
    .unwrap();
    tab.move_focus_left(client_id).unwrap();
    assert_snapshot!(format!("{:?}", *tty_stdin_bytes.lock().unwrap()));
}

#[test]
fn move_floating_pane_focus_sends_tty_csi_event() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let tty_stdin_bytes = Arc::new(Mutex::new(BTreeMap::new()));
    let os_api = Box::new(FakeInputOutput {
        tty_stdin_bytes: tty_stdin_bytes.clone(),
        ..Default::default()
    });
    let mut tab = create_new_tab_with_os_api(size, ModeInfo::default(), &os_api);
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);

    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(
        1,
        // subscribe to focus events
        Vec::from("\u{1b}[?1004h".as_bytes()),
    )
    .unwrap();
    tab.handle_pty_bytes(
        2,
        // subscribe to focus events
        Vec::from("\u{1b}[?1004h".as_bytes()),
    )
    .unwrap();
    tab.handle_pty_bytes(
        3,
        // subscribe to focus events
        Vec::from("\u{1b}[?1004h".as_bytes()),
    )
    .unwrap();
    tab.move_focus_left(client_id).unwrap();
    assert_snapshot!(format!("{:?}", *tty_stdin_bytes.lock().unwrap()));
}

#[test]
fn toggle_floating_panes_on_sends_tty_csi_event() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let tty_stdin_bytes = Arc::new(Mutex::new(BTreeMap::new()));
    let os_api = Box::new(FakeInputOutput {
        tty_stdin_bytes: tty_stdin_bytes.clone(),
        ..Default::default()
    });
    let mut tab = create_new_tab_with_os_api(size, ModeInfo::default(), &os_api);
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);

    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.handle_pty_bytes(
        1,
        // subscribe to focus events
        Vec::from("\u{1b}[?1004h".as_bytes()),
    )
    .unwrap();
    tab.handle_pty_bytes(
        2,
        // subscribe to focus events
        Vec::from("\u{1b}[?1004h".as_bytes()),
    )
    .unwrap();
    tab.handle_pty_bytes(
        3,
        // subscribe to focus events
        Vec::from("\u{1b}[?1004h".as_bytes()),
    )
    .unwrap();
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    assert_snapshot!(format!("{:?}", *tty_stdin_bytes.lock().unwrap()));
}

#[test]
fn toggle_floating_panes_off_sends_tty_csi_event() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let tty_stdin_bytes = Arc::new(Mutex::new(BTreeMap::new()));
    let os_api = Box::new(FakeInputOutput {
        tty_stdin_bytes: tty_stdin_bytes.clone(),
        ..Default::default()
    });
    let mut tab = create_new_tab_with_os_api(size, ModeInfo::default(), &os_api);
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);

    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(
        1,
        // subscribe to focus events
        Vec::from("\u{1b}[?1004h".as_bytes()),
    )
    .unwrap();
    tab.handle_pty_bytes(
        2,
        // subscribe to focus events
        Vec::from("\u{1b}[?1004h".as_bytes()),
    )
    .unwrap();
    tab.handle_pty_bytes(
        3,
        // subscribe to focus events
        Vec::from("\u{1b}[?1004h".as_bytes()),
    )
    .unwrap();
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    assert_snapshot!(format!("{:?}", *tty_stdin_bytes.lock().unwrap()));
}

#[test]
fn can_swap_tiled_layout_at_runtime() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab max_panes=2 split_direction="vertical" {
                    pane
                    pane
                }
            }
            swap_tiled_layout {
                tab max_panes=2 {
                    pane
                    pane
                }
            }
        }
    "#;
    let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        None,
        true,
        stacked_resize,
    );
    let new_pane_id_1 = PaneId::Terminal(2);

    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.next_swap_layout().unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn can_swap_floating_layout_at_runtime() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let swap_layouts = r#"
        layout {
            swap_floating_layout {
                floating_panes max_panes=2 {
                    pane
                    pane
                }
            }
            swap_floating_layout {
                floating_panes max_panes=2 {
                    pane {
                        x "0%"
                    }
                    pane {
                        x "100%"
                    }
                }
            }
        }
    "#;
    let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_floating_layouts = layout.swap_floating_layouts.clone();
    let swap_tiled_layouts = layout.swap_tiled_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        None,
        true,
        stacked_resize,
    );
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);

    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.next_swap_layout().unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn swapping_layouts_after_resize_snaps_to_current_layout() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane
                    pane
                }
            }
            swap_tiled_layout {
                tab {
                    pane split_direction="vertical" {
                        pane
                        pane
                    }
                }
            }
        }
    "#;
    let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        None,
        true,
        stacked_resize,
    );
    let new_pane_id_1 = PaneId::Terminal(2);

    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.next_swap_layout().unwrap();
    tab.resize(client_id, ResizeStrategy::new(Resize::Increase, None))
        .unwrap();
    tab.next_swap_layout().unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn swap_tiled_layout_with_stacked_children() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane split_direction="vertical" {
                        pane focus=true
                        pane stacked=true { children; }
                    }
                }
            }
        }
    "#;
    let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        None,
        true,
        stacked_resize,
    );
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);

    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn swap_tiled_layout_with_only_stacked_children() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane stacked=true { children; }
                }
            }
        }
    "#;
    let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        None,
        true,
        stacked_resize,
    );
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);

    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn swap_tiled_layout_with_stacked_children_and_no_pane_frames() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane split_direction="vertical" {
                        pane focus=true
                        pane stacked=true { children; }
                    }
                }
            }
        }
    "#;
    let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        None,
        false,
        stacked_resize,
    );
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);

    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn move_focus_up_with_stacked_panes() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane split_direction="vertical" {
                        pane focus=true
                        pane stacked=true { children; }
                    }
                }
            }
        }
    "#;
    let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        None,
        true,
        stacked_resize,
    );
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);

    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    let _ = tab.move_focus_right(client_id);
    let _ = tab.move_focus_up(client_id);
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn move_focus_down_with_stacked_panes() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane split_direction="vertical" {
                        pane focus=true
                        pane stacked=true { children; }
                    }
                }
            }
        }
    "#;
    let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        None,
        true,
        stacked_resize,
    );
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);

    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    let _ = tab.move_focus_right(client_id);
    let _ = tab.move_focus_up(client_id);
    let _ = tab.move_focus_down(client_id);
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn move_focus_right_into_stacked_panes() {
    // here we make sure that when we focus right into a stack,
    // we will always focus on the "main" pane of the stack
    // and not on one of its folds
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane split_direction="vertical" {
                        pane
                        pane stacked=true { children; }
                    }
                }
            }
        }
    "#;
    let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        None,
        true,
        stacked_resize,
    );
    for i in 0..12 {
        let new_pane_id = i + 2;
        tab.new_pane(
            PaneId::Terminal(new_pane_id),
            None,
            None,
            false,
            true,
            NewPanePlacement::default(),
            Some(client_id),
            None,
        )
        .unwrap();
    }
    let _ = tab.move_focus_left(client_id);
    tab.horizontal_split(PaneId::Terminal(16), None, client_id, None, None)
        .unwrap();

    let _ = tab.move_focus_up(client_id);
    let _ = tab.move_focus_right(client_id);
    tab.render(&mut output, None).unwrap();

    let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_eq!(
        cursor_coordinates,
        Some((62, 12)),
        "cursor coordinates moved to the main pane of the stack",
    );

    assert_snapshot!(snapshot);
}

#[test]
fn move_focus_left_into_stacked_panes() {
    // here we make sure that when we focus left into a stack,
    // we will always focus on the "main" pane of the stack
    // and not on one of its folds
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane split_direction="vertical" {
                        pane stacked=true { children; }
                        pane focus=true
                    }
                }
            }
        }
    "#;
    let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        None,
        true,
        stacked_resize,
    );
    for i in 0..13 {
        let new_pane_id = i + 2;
        tab.new_pane(
            PaneId::Terminal(new_pane_id),
            None,
            None,
            false,
            true,
            NewPanePlacement::default(),
            Some(client_id),
            None,
        )
        .unwrap();
    }
    let _ = tab.move_focus_right(client_id);
    tab.horizontal_split(PaneId::Terminal(1), None, client_id, None, None)
        .unwrap();

    let _ = tab.move_focus_up(client_id);
    let _ = tab.move_focus_left(client_id);
    tab.render(&mut output, None).unwrap();

    let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_eq!(
        cursor_coordinates,
        Some((1, 12)),
        "cursor coordinates moved to the main pane of the stack",
    );

    assert_snapshot!(snapshot);
}

#[test]
fn move_focus_up_into_stacked_panes() {
    // here we make sure that when we focus up into a stack,
    // the main pane will become the lowest pane and the sizes
    // in the stack will be adjusted accordingly
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane
                    pane split_direction="vertical" {
                        pane focus=true
                        pane stacked=true { children; }
                    }
                    pane
                }
            }
        }
    "#;
    let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        None,
        true,
        stacked_resize,
    );
    for i in 0..4 {
        let new_pane_id = i + 3;
        tab.new_pane(
            PaneId::Terminal(new_pane_id),
            None,
            None,
            false,
            true,
            NewPanePlacement::default(),
            Some(client_id),
            None,
        )
        .unwrap();
    }
    let _ = tab.move_focus_right(client_id);
    let _ = tab.move_focus_up(client_id);
    let _ = tab.move_focus_left(client_id);
    let _ = tab.move_focus_down(client_id);
    tab.vertical_split(PaneId::Terminal(7), None, client_id, None, None)
        .unwrap();

    let _ = tab.move_focus_up(client_id);
    tab.render(&mut output, None).unwrap();

    let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_eq!(
        cursor_coordinates,
        Some((62, 9)),
        "cursor coordinates moved to the main pane of the stack",
    );
    assert_snapshot!(snapshot);
}

#[test]
fn move_focus_down_into_stacked_panes() {
    // here we make sure that when we focus down into a stack,
    // the main pane will become the highest pane and the sizes
    // in the stack will be adjusted accordingly
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane
                    pane split_direction="vertical" {
                        pane
                        pane stacked=true { children; }
                    }
                    pane
                }
            }
        }
    "#;
    let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        None,
        true,
        stacked_resize,
    );
    for i in 0..4 {
        let new_pane_id = i + 3;
        tab.new_pane(
            PaneId::Terminal(new_pane_id),
            None,
            None,
            false,
            true,
            NewPanePlacement::default(),
            Some(client_id),
            None,
        )
        .unwrap();
    }
    let _ = tab.move_focus_left(client_id);
    let _ = tab.move_focus_up(client_id);
    tab.vertical_split(PaneId::Terminal(7), None, client_id, None, None)
        .unwrap();

    let _ = tab.move_focus_down(client_id);
    tab.render(&mut output, None).unwrap();

    let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_eq!(
        cursor_coordinates,
        Some((62, 8)),
        "cursor coordinates moved to the main pane of the stack",
    );

    assert_snapshot!(snapshot);
}

#[test]
fn close_main_stacked_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane split_direction="vertical" {
                        pane focus=true
                        pane stacked=true { children; }
                    }
                }
            }
        }
    "#;
    let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        None,
        true,
        stacked_resize,
    );
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);

    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.close_pane(new_pane_id_2, false, None);
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn close_main_stacked_pane_in_mid_stack() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane split_direction="vertical" {
                        pane
                        pane stacked=true { children; }
                    }
                }
            }
        }
    "#;
    let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        None,
        true,
        stacked_resize,
    );
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);
    let new_pane_id_4 = PaneId::Terminal(5);
    let new_pane_id_5 = PaneId::Terminal(6);

    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_4,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_5,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    let _ = tab.move_focus_right(client_id);
    let _ = tab.move_focus_up(client_id);
    let _ = tab.move_focus_up(client_id);
    tab.close_pane(new_pane_id_3, false, None);
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn close_one_liner_stacked_pane_below_main_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane split_direction="vertical" {
                        pane
                        pane stacked=true { children; }
                    }
                }
            }
        }
    "#;
    let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        None,
        true,
        stacked_resize,
    );
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);
    let new_pane_id_4 = PaneId::Terminal(5);
    let new_pane_id_5 = PaneId::Terminal(6);

    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_4,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_5,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    let _ = tab.move_focus_left(client_id);
    let _ = tab.move_focus_right(client_id);
    let _ = tab.move_focus_up(client_id);
    let _ = tab.move_focus_up(client_id);
    tab.close_pane(new_pane_id_2, false, None);
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn close_one_liner_stacked_pane_above_main_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane split_direction="vertical" {
                        pane
                        pane stacked=true { children; }
                    }
                }
            }
        }
    "#;
    let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        None,
        true,
        stacked_resize,
    );
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);
    let new_pane_id_4 = PaneId::Terminal(5);
    let new_pane_id_5 = PaneId::Terminal(6);

    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_4,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_5,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    let _ = tab.move_focus_right(client_id);
    let _ = tab.move_focus_up(client_id);
    let _ = tab.move_focus_up(client_id);
    tab.close_pane(new_pane_id_2, false, None);
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn can_increase_size_of_main_pane_in_stack_horizontally() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane split_direction="vertical" {
                        pane focus=true
                        pane stacked=true { children; }
                    }
                }
            }
        }
    "#;
    let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        None,
        true,
        stacked_resize,
    );
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);
    let new_pane_id_4 = PaneId::Terminal(5);
    let new_pane_id_5 = PaneId::Terminal(6);

    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_4,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_5,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    let _ = tab.move_focus_right(client_id);
    tab.resize(
        client_id,
        ResizeStrategy::new(Resize::Increase, Some(Direction::Left)),
    )
    .unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn can_increase_size_of_main_pane_in_stack_vertically() {
    let size = Size {
        cols: 121,
        rows: 40,
    };
    let client_id = 1;
    let mut output = Output::default();
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane
                    pane split_direction="vertical" {
                        pane focus=true
                        pane stacked=true { children; }
                    }
                    pane
                }
            }
        }
    "#;
    let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        None,
        true,
        stacked_resize,
    );
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);
    let new_pane_id_4 = PaneId::Terminal(5);
    let new_pane_id_5 = PaneId::Terminal(6);

    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_4,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_5,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    let _ = tab.move_focus_right(client_id);
    tab.resize(
        client_id,
        ResizeStrategy::new(Resize::Increase, Some(Direction::Down)),
    )
    .unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn can_increase_size_of_main_pane_in_stack_non_directionally() {
    let size = Size {
        cols: 121,
        rows: 40,
    };
    let client_id = 1;
    let mut output = Output::default();
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane
                    pane split_direction="vertical" {
                        pane focus=true
                        pane stacked=true { children; }
                    }
                    pane
                }
            }
        }
    "#;
    let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = layout.swap_floating_layouts.clone();
    let stacked_resize = false;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        None,
        true,
        stacked_resize,
    );
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);
    let new_pane_id_4 = PaneId::Terminal(5);
    let new_pane_id_5 = PaneId::Terminal(6);

    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_4,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_5,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    let _ = tab.move_focus_right(client_id);
    tab.resize(client_id, ResizeStrategy::new(Resize::Increase, None))
        .unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn can_increase_size_into_pane_stack_horizontally() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane split_direction="vertical" {
                        pane focus=true
                        pane stacked=true { children; }
                    }
                }
            }
        }
    "#;
    let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        None,
        true,
        stacked_resize,
    );
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);
    let new_pane_id_4 = PaneId::Terminal(5);
    let new_pane_id_5 = PaneId::Terminal(6);

    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_4,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_5,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.resize(
        client_id,
        ResizeStrategy::new(Resize::Increase, Some(Direction::Right)),
    )
    .unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn can_increase_size_into_pane_stack_vertically() {
    let size = Size {
        cols: 121,
        rows: 40,
    };
    let client_id = 1;
    let mut output = Output::default();
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane
                    pane split_direction="vertical" {
                        pane focus=true
                        pane stacked=true { children; }
                    }
                    pane
                }
            }
        }
    "#;
    let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        None,
        true,
        stacked_resize,
    );
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);
    let new_pane_id_4 = PaneId::Terminal(5);
    let new_pane_id_5 = PaneId::Terminal(6);

    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_4,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_5,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    let _ = tab.move_focus_right(client_id);
    let _ = tab.move_focus_down(client_id);
    tab.resize(
        client_id,
        ResizeStrategy::new(Resize::Increase, Some(Direction::Up)),
    )
    .unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn can_increase_size_into_pane_stack_non_directionally() {
    // note - this is not the default behavior, by default stacked_resize is enabled (set to true)
    let size = Size {
        cols: 121,
        rows: 40,
    };
    let client_id = 1;
    let mut output = Output::default();
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane
                    pane split_direction="vertical" {
                        pane focus=true
                        pane stacked=true { children; }
                    }
                    pane
                }
            }
        }
    "#;
    let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = layout.swap_floating_layouts.clone();
    let stacked_resize = false;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        None,
        true,
        stacked_resize,
    );
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);
    let new_pane_id_4 = PaneId::Terminal(5);
    let new_pane_id_5 = PaneId::Terminal(6);

    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_4,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_5,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.resize(client_id, ResizeStrategy::new(Resize::Increase, None))
        .unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn decreasing_size_of_whole_tab_treats_stacked_panes_properly() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane split_direction="vertical" {
                        pane focus=true
                        pane stacked=true { children; }
                    }
                }
            }
        }
    "#;
    let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        None,
        true,
        stacked_resize,
    );
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);
    let new_pane_id_4 = PaneId::Terminal(5);
    let new_pane_id_5 = PaneId::Terminal(6);

    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_4,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_5,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    let _ = tab.resize_whole_tab(Size {
        cols: 100,
        rows: 10,
    });
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn increasing_size_of_whole_tab_treats_stacked_panes_properly() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane split_direction="vertical" {
                        pane focus=true
                        pane stacked=true { children; }
                    }
                }
            }
        }
    "#;
    let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        None,
        true,
        stacked_resize,
    );
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);
    let new_pane_id_4 = PaneId::Terminal(5);
    let new_pane_id_5 = PaneId::Terminal(6);

    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_4,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_5,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    let _ = tab.resize_whole_tab(Size {
        cols: 100,
        rows: 10,
    });
    let _ = tab.resize_whole_tab(Size {
        cols: 121,
        rows: 20,
    });
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn cannot_decrease_stack_size_beyond_minimum_height() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane split_direction="vertical" {
                        pane focus=true
                        pane stacked=true { children; }
                    }
                    pane
                }
            }
        }
    "#;
    let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        None,
        true,
        stacked_resize,
    );
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);
    let new_pane_id_4 = PaneId::Terminal(5);
    let new_pane_id_5 = PaneId::Terminal(6);

    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_4,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_5,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    let _ = tab.move_focus_down(client_id);
    for _ in 0..6 {
        tab.resize(
            client_id,
            ResizeStrategy::new(Resize::Increase, Some(Direction::Up)),
        )
        .unwrap();
    }
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn focus_stacked_pane_over_flexible_pane_with_the_mouse() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane split_direction="vertical" {
                        pane focus=true
                        pane stacked=true { children; }
                    }
                    pane
                }
            }
        }
    "#;
    let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        None,
        true,
        stacked_resize,
    );
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);
    let new_pane_id_4 = PaneId::Terminal(5);
    let new_pane_id_5 = PaneId::Terminal(6);

    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_4,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_5,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_press_event(Position::new(1, 71)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_release_event(Position::new(1, 71)),
        client_id,
    )
    .unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn focus_stacked_pane_under_flexible_pane_with_the_mouse() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane split_direction="vertical" {
                        pane focus=true
                        pane stacked=true { children; }
                    }
                    pane
                }
            }
        }
    "#;
    let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        None,
        true,
        stacked_resize,
    );
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);
    let new_pane_id_4 = PaneId::Terminal(5);
    let new_pane_id_5 = PaneId::Terminal(6);

    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_4,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_5,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_press_event(Position::new(1, 71)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_press_event(Position::new(9, 71)),
        client_id,
    )
    .unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn close_stacked_pane_with_previously_focused_other_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane split_direction="vertical" {
                        pane
                        pane stacked=true { children; }
                    }
                    pane
                }
            }
        }
    "#;
    let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        None,
        true,
        stacked_resize,
    );
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);
    let new_pane_id_4 = PaneId::Terminal(5);
    let new_pane_id_5 = PaneId::Terminal(6);

    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_4,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_5,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_press_event(Position::new(2, 71)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_release_event(Position::new(2, 71)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_press_event(Position::new(1, 71)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_release_event(Position::new(1, 71)),
        client_id,
    )
    .unwrap();
    tab.close_pane(PaneId::Terminal(4), false, None);
    tab.render(&mut output, None).unwrap();
    let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_eq!(
        cursor_coordinates,
        Some((62, 2)),
        "cursor coordinates moved to the main pane of the stack",
    );

    assert_snapshot!(snapshot);
}

#[test]
fn close_pane_near_stacked_panes() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane split_direction="vertical" {
                        pane
                        pane stacked=true { children; }
                    }
                }
            }
        }
    "#;
    let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        None,
        true,
        stacked_resize,
    );
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);
    let new_pane_id_4 = PaneId::Terminal(5);
    let new_pane_id_5 = PaneId::Terminal(6);

    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_4,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_5,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.close_pane(PaneId::Terminal(6), false, None);
    tab.render(&mut output, None).unwrap();
    let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_eq!(
        cursor_coordinates,
        Some((62, 4)),
        "cursor coordinates moved to the main pane of the stack",
    );

    assert_snapshot!(snapshot);
}

#[test]
fn focus_next_pane_expands_stacked_panes() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane split_direction="vertical" {
                        pane focus=true
                        pane stacked=true { children; }
                    }
                    pane
                }
            }
        }
    "#;
    let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        None,
        true,
        stacked_resize,
    );
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);
    let new_pane_id_4 = PaneId::Terminal(5);
    let new_pane_id_5 = PaneId::Terminal(6);

    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_4,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_5,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    let _ = tab.move_focus_left(client_id);
    tab.focus_next_pane(client_id);
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_snapshot!(snapshot);
}

#[test]
fn stacked_panes_can_become_fullscreen() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane split_direction="vertical" {
                        pane focus=true
                        pane stacked=true { children; }
                    }
                    pane
                }
            }
        }
    "#;
    let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        None,
        true,
        stacked_resize,
    );
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);
    let new_pane_id_4 = PaneId::Terminal(5);
    let new_pane_id_5 = PaneId::Terminal(6);

    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_4,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_5,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    let _ = tab.move_focus_up(client_id);
    tab.toggle_active_pane_fullscreen(client_id);
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_snapshot!(snapshot);
}

#[test]
fn layout_with_plugins_and_commands_swaped_properly() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let base_layout = r#"
        layout {
            pane size=1 borderless=true {
                plugin location="zellij:tab-bar"
            }
            pane split_direction="vertical" {
                pane command="command1"
                pane
                pane command="command2"
            }
            pane size=2 borderless=true {
                plugin location="zellij:status-bar"
            }
        }
    "#;
    // this swap layout changes both the split direction of the two command panes and the location
    // of the plugins - we want to make sure that they are all placed properly and not switched
    // around
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane size=2 borderless=true {
                        plugin location="zellij:status-bar"
                    }
                    pane command="command2"
                    pane command="command1"
                    pane
                    pane size=1 borderless=true {
                        plugin location="zellij:tab-bar"
                    }
                }
            }
        }
    "#;
    let (base_layout, base_floating_layout) =
        Layout::from_kdl(base_layout, Some("file_name.kdl".into()), None, None)
            .unwrap()
            .template
            .unwrap();

    let mut command_1 = RunCommand::default();
    command_1.command = PathBuf::from("command1");
    command_1.hold_on_close = true;
    let mut command_2 = RunCommand::default();
    command_2.command = PathBuf::from("command2");
    command_2.hold_on_close = true;
    let new_terminal_ids = vec![(1, Some(command_1)), (2, None), (3, Some(command_2))];
    let new_floating_terminal_ids = vec![];
    let mut new_plugin_ids = HashMap::new();
    new_plugin_ids.insert(
        RunPluginOrAlias::from_url("zellij:tab-bar", &None, None, None).unwrap(),
        vec![1],
    );
    new_plugin_ids.insert(
        RunPluginOrAlias::from_url("zellij:status-bar", &None, None, None).unwrap(),
        vec![2],
    );

    let swap_layout =
        Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = swap_layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        Some((
            base_layout,
            base_floating_layout,
            new_terminal_ids,
            new_floating_terminal_ids,
            new_plugin_ids,
        )),
        true,
        stacked_resize,
    );
    let _ = tab.handle_plugin_bytes(1, 1, "I am a tab bar".as_bytes().to_vec());
    let _ = tab.handle_plugin_bytes(2, 1, "I am a\n\rstatus bar".as_bytes().to_vec());
    tab.next_swap_layout().unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_snapshot!(snapshot);
}

#[test]
fn base_layout_is_included_in_swap_layouts() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let base_layout = r#"
        layout {
            pane size=1 borderless=true {
                plugin location="zellij:tab-bar"
            }
            pane split_direction="vertical" {
                pane command="command1"
                pane
                pane command="command2"
            }
            pane size=2 borderless=true {
                plugin location="zellij:status-bar"
            }
        }
    "#;
    // this swap layout changes both the split direction of the two command panes and the location
    // of the plugins - we want to make sure that they are all placed properly and not switched
    // around
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane size=2 borderless=true {
                        plugin location="zellij:status-bar"
                    }
                    pane command="command2"
                    pane command="command1"
                    pane
                    pane size=1 borderless=true {
                        plugin location="zellij:tab-bar"
                    }
                }
            }
        }
    "#;
    let (base_layout, base_floating_layout) =
        Layout::from_kdl(base_layout, Some("file_name.kdl".into()), None, None)
            .unwrap()
            .template
            .unwrap();

    let mut command_1 = RunCommand::default();
    command_1.command = PathBuf::from("command1");
    let mut command_2 = RunCommand::default();
    command_2.command = PathBuf::from("command2");
    let new_terminal_ids = vec![(1, Some(command_1)), (2, None), (3, Some(command_2))];
    let new_floating_terminal_ids = vec![];
    let mut new_plugin_ids = HashMap::new();
    new_plugin_ids.insert(
        RunPluginOrAlias::from_url("zellij:tab-bar", &None, None, None).unwrap(),
        vec![1],
    );
    new_plugin_ids.insert(
        RunPluginOrAlias::from_url("zellij:status-bar", &None, None, None).unwrap(),
        vec![2],
    );

    let swap_layout =
        Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = swap_layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        Some((
            base_layout,
            base_floating_layout,
            new_terminal_ids,
            new_floating_terminal_ids,
            new_plugin_ids,
        )),
        true,
        stacked_resize,
    );
    let _ = tab.handle_plugin_bytes(1, 1, "I am a tab bar".as_bytes().to_vec());
    let _ = tab.handle_plugin_bytes(2, 1, "I am a\n\rstatus bar".as_bytes().to_vec());
    tab.next_swap_layout().unwrap();
    tab.previous_swap_layout().unwrap(); // move back to the base layout
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_snapshot!(snapshot);
}

#[test]
fn swap_layouts_including_command_panes_absent_from_existing_layout() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let base_layout = r#"
        layout {
            pane size=1 borderless=true {
                plugin location="zellij:tab-bar"
            }
            pane split_direction="vertical" {
                pane
                pane
                pane
            }
            pane size=2 borderless=true {
                plugin location="zellij:status-bar"
            }
        }
    "#;
    // this swap layout changes both the split direction of the two command panes and the location
    // of the plugins - we want to make sure that they are all placed properly and not switched
    // around
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane size=2 borderless=true {
                        plugin location="zellij:status-bar"
                    }
                    pane command="command2"
                    pane command="command1"
                    pane
                    pane size=1 borderless=true {
                        plugin location="zellij:tab-bar"
                    }
                }
            }
        }
    "#;
    let (base_layout, base_floating_layout) =
        Layout::from_kdl(base_layout, Some("file_name.kdl".into()), None, None)
            .unwrap()
            .template
            .unwrap();

    let new_terminal_ids = vec![(1, None), (2, None), (3, None)];
    let new_floating_terminal_ids = vec![];
    let mut new_plugin_ids = HashMap::new();
    new_plugin_ids.insert(
        RunPluginOrAlias::from_url("zellij:tab-bar", &None, None, None).unwrap(),
        vec![1],
    );
    new_plugin_ids.insert(
        RunPluginOrAlias::from_url("zellij:status-bar", &None, None, None).unwrap(),
        vec![2],
    );

    let swap_layout =
        Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = swap_layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        Some((
            base_layout,
            base_floating_layout,
            new_terminal_ids,
            new_floating_terminal_ids,
            new_plugin_ids,
        )),
        true,
        stacked_resize,
    );
    let _ = tab.handle_plugin_bytes(1, 1, "I am a tab bar".as_bytes().to_vec());
    let _ = tab.handle_plugin_bytes(2, 1, "I am a\n\rstatus bar".as_bytes().to_vec());
    tab.next_swap_layout().unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_snapshot!(snapshot);
}

#[test]
fn swap_layouts_not_including_command_panes_present_in_existing_layout() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let base_layout = r#"
        layout {
            pane size=1 borderless=true {
                plugin location="zellij:tab-bar"
            }
            pane split_direction="vertical" {
                pane command="command1"
                pane
                pane command="command2"
            }
            pane size=2 borderless=true {
                plugin location="zellij:status-bar"
            }
        }
    "#;
    // this swap layout changes both the split direction of the two command panes and the location
    // of the plugins - we want to make sure that they are all placed properly and not switched
    // around
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane size=2 borderless=true {
                        plugin location="zellij:status-bar"
                    }
                    pane
                    pane
                    pane
                    pane size=1 borderless=true {
                        plugin location="zellij:tab-bar"
                    }
                }
            }
        }
    "#;
    let (base_layout, base_floating_layout) =
        Layout::from_kdl(base_layout, Some("file_name.kdl".into()), None, None)
            .unwrap()
            .template
            .unwrap();

    let mut command_1 = RunCommand::default();
    command_1.command = PathBuf::from("command1");
    let mut command_2 = RunCommand::default();
    command_2.command = PathBuf::from("command2");
    let new_terminal_ids = vec![(1, Some(command_1)), (2, None), (3, Some(command_2))];
    let new_floating_terminal_ids = vec![];
    let mut new_plugin_ids = HashMap::new();
    new_plugin_ids.insert(
        RunPluginOrAlias::from_url("zellij:tab-bar", &None, None, None).unwrap(),
        vec![1],
    );
    new_plugin_ids.insert(
        RunPluginOrAlias::from_url("zellij:status-bar", &None, None, None).unwrap(),
        vec![2],
    );

    let swap_layout =
        Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = swap_layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        Some((
            base_layout,
            base_floating_layout,
            new_terminal_ids,
            new_floating_terminal_ids,
            new_plugin_ids,
        )),
        true,
        stacked_resize,
    );
    let _ = tab.handle_plugin_bytes(1, 1, "I am a tab bar".as_bytes().to_vec());
    let _ = tab.handle_plugin_bytes(2, 1, "I am a\n\rstatus bar".as_bytes().to_vec());
    tab.next_swap_layout().unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_snapshot!(snapshot);
}

#[test]
fn swap_layouts_including_plugin_panes_absent_from_existing_layout() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let base_layout = r#"
        layout {
            pane size=2 borderless=true
            pane split_direction="vertical" {
                pane
                pane
                pane
            }
            pane size=1 borderless=true
        }
    "#;
    // this swap layout changes both the split direction of the two command panes and the location
    // of the plugins - we want to make sure that they are all placed properly and not switched
    // around
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane size=2 borderless=true {
                        plugin location="zellij:status-bar"
                    }
                    pane
                    pane
                    pane
                    pane size=1 borderless=true {
                        plugin location="zellij:tab-bar"
                    }
                }
            }
        }
    "#;
    let (base_layout, base_floating_layout) =
        Layout::from_kdl(base_layout, Some("file_name.kdl".into()), None, None)
            .unwrap()
            .template
            .unwrap();

    let new_terminal_ids = vec![(1, None), (2, None), (3, None)];
    let new_floating_terminal_ids = vec![];
    let new_plugin_ids = HashMap::new();

    let swap_layout =
        Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = swap_layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        Some((
            base_layout,
            base_floating_layout,
            new_terminal_ids,
            new_floating_terminal_ids,
            new_plugin_ids,
        )),
        true,
        stacked_resize,
    );
    tab.next_swap_layout().unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_snapshot!(snapshot);
}

#[test]
fn swap_layouts_not_including_plugin_panes_present_in_existing_layout() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let base_layout = r#"
        layout {
            pane size=1 borderless=true {
                plugin location="zellij:tab-bar"
            }
            pane split_direction="vertical" {
                pane command="command1"
                pane
                pane command="command2"
            }
            pane size=2 borderless=true {
                plugin location="zellij:status-bar"
            }
        }
    "#;
    // this swap layout changes both the split direction of the two command panes and the location
    // of the plugins - we want to make sure that they are all placed properly and not switched
    // around
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane size=2 borderless=true {
                        plugin location="zellij:status-bar"
                    }
                    pane
                    pane
                    pane
                    pane size=1 borderless=true {
                        plugin location="zellij:tab-bar"
                    }
                }
            }
        }
    "#;
    let (base_layout, base_floating_layout) =
        Layout::from_kdl(base_layout, Some("file_name.kdl".into()), None, None)
            .unwrap()
            .template
            .unwrap();

    let mut command_1 = RunCommand::default();
    command_1.command = PathBuf::from("command1");
    let mut command_2 = RunCommand::default();
    command_2.command = PathBuf::from("command2");
    let new_terminal_ids = vec![(1, Some(command_1)), (2, None), (3, Some(command_2))];
    let new_floating_terminal_ids = vec![];
    let mut new_plugin_ids = HashMap::new();
    new_plugin_ids.insert(
        RunPluginOrAlias::from_url("zellij:tab-bar", &None, None, None).unwrap(),
        vec![1],
    );
    new_plugin_ids.insert(
        RunPluginOrAlias::from_url("zellij:status-bar", &None, None, None).unwrap(),
        vec![2],
    );

    let swap_layout =
        Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = swap_layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        Some((
            base_layout,
            base_floating_layout,
            new_terminal_ids,
            new_floating_terminal_ids,
            new_plugin_ids,
        )),
        true,
        stacked_resize,
    );
    let _ = tab.handle_plugin_bytes(1, 1, "I am a tab bar".as_bytes().to_vec());
    let _ = tab.handle_plugin_bytes(2, 1, "I am a\n\rstatus bar".as_bytes().to_vec());
    tab.next_swap_layout().unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_snapshot!(snapshot);
}

#[test]
fn new_pane_in_auto_layout() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let base_layout = r#"
        layout
    "#;
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab max_panes=5 {
                    pane split_direction="vertical" {
                        pane
                        pane { children; }
                    }
                }
                tab max_panes=8 {
                    pane split_direction="vertical" {
                        pane { children; }
                        pane { pane; pane; pane; pane; }
                    }
                }
            }
        }
    "#;
    let (base_layout, base_floating_layout) =
        Layout::from_kdl(base_layout, Some("file_name.kdl".into()), None, None)
            .unwrap()
            .template
            .unwrap();

    let new_terminal_ids = vec![(1, None), (2, None), (3, None)];
    let new_floating_terminal_ids = vec![];
    let new_plugin_ids = HashMap::new();

    let swap_layout =
        Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = swap_layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        Some((
            base_layout,
            base_floating_layout,
            new_terminal_ids,
            new_floating_terminal_ids,
            new_plugin_ids,
        )),
        true,
        stacked_resize,
    );

    let mut expected_cursor_coordinates = vec![
        (62, 1),
        (62, 11),
        (62, 15),
        (62, 16),
        (62, 16),
        (62, 16),
        (62, 16),
    ];
    for i in 0..7 {
        let new_pane_id = i + 2;
        tab.new_pane(
            PaneId::Terminal(new_pane_id),
            None,
            None,
            false,
            true,
            NewPanePlacement::default(),
            Some(client_id),
            None,
        )
        .unwrap();
        tab.render(&mut output, None).unwrap();

        let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
            output.serialize().unwrap().get(&client_id).unwrap(),
            size.rows,
            size.cols,
            Palette::default(),
        );
        let (expected_x, expected_y) = expected_cursor_coordinates.remove(0);
        assert_eq!(
            cursor_coordinates,
            Some((expected_x, expected_y)),
            "cursor coordinates moved to the new pane",
        );
        assert_snapshot!(snapshot);
    }
}

#[test]
fn new_pane_in_stacked_resizes() {
    let size = Size {
        cols: 200,
        rows: 40,
    };
    let client_id = 1;
    let mut output = Output::default();
    let base_layout = r#"
        layout
    "#;
    let (base_layout, base_floating_layout) =
        Layout::from_kdl(base_layout, Some("file_name.kdl".into()), None, None)
            .unwrap()
            .template
            .unwrap();

    let new_terminal_ids = vec![(1, None), (2, None), (3, None)];
    let new_floating_terminal_ids = vec![];
    let new_plugin_ids = HashMap::new();

    let swap_tiled_layouts = vec![];
    let swap_floating_layouts = vec![];
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        Some((
            base_layout,
            base_floating_layout,
            new_terminal_ids,
            new_floating_terminal_ids,
            new_plugin_ids,
        )),
        true,
        stacked_resize,
    );

    let mut expected_cursor_coordinates = vec![(101, 1), (101, 21), (151, 21), (151, 22)];
    for i in 0..4 {
        let new_pane_id = i + 2;
        tab.new_pane(
            PaneId::Terminal(new_pane_id),
            None,
            None,
            false,
            true,
            NewPanePlacement::default(),
            Some(client_id),
            None,
        )
        .unwrap();
        tab.render(&mut output, None).unwrap();

        let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
            output.serialize().unwrap().get(&client_id).unwrap(),
            size.rows,
            size.cols,
            Palette::default(),
        );
        let (expected_x, expected_y) = expected_cursor_coordinates.remove(0);
        assert_eq!(
            cursor_coordinates,
            Some((expected_x, expected_y)),
            "cursor coordinates moved to the new pane",
        );
        assert_snapshot!(snapshot);
    }
}

#[test]
fn when_swapping_tiled_layouts_in_a_damaged_state_layout_and_pane_focus_are_unchanged() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let base_layout = r#"
        layout {
            pane
            pane
            pane
        }
    "#;
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane split_direction="vertical" {
                        pane
                        pane
                        pane
                    }
                }
            }
        }
    "#;
    let (base_layout, base_floating_layout) =
        Layout::from_kdl(base_layout, Some("file_name.kdl".into()), None, None)
            .unwrap()
            .template
            .unwrap();

    let new_terminal_ids = vec![(1, None), (2, None), (3, None)];
    let new_floating_terminal_ids = vec![];
    let new_plugin_ids = HashMap::new();

    let swap_layout =
        Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = swap_layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        Some((
            base_layout,
            base_floating_layout,
            new_terminal_ids,
            new_floating_terminal_ids,
            new_plugin_ids,
        )),
        true,
        stacked_resize,
    );
    let _ = tab.move_focus_down(client_id);
    tab.resize(
        client_id,
        ResizeStrategy::new(Resize::Increase, Some(Direction::Down)),
    )
    .unwrap();
    tab.next_swap_layout().unwrap();
    tab.render(&mut output, None).unwrap();

    let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_eq!(
        cursor_coordinates,
        Some((1, 8)),
        "cursor coordinates moved to the new pane",
    );

    assert_snapshot!(snapshot);
}

#[test]
fn when_swapping_tiled_layouts_in_an_undamaged_state_pane_focuses_on_focused_node() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let base_layout = r#"
        layout {
            pane
            pane
            pane
        }
    "#;
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane split_direction="vertical" {
                        pane focus=true
                        pane
                        pane
                    }
                }
            }
        }
    "#;
    let (base_layout, base_floating_layout) =
        Layout::from_kdl(base_layout, Some("file_name.kdl".into()), None, None)
            .unwrap()
            .template
            .unwrap();

    let new_terminal_ids = vec![(1, None), (2, None), (3, None)];
    let new_floating_terminal_ids = vec![];
    let new_plugin_ids = HashMap::new();

    let swap_layout =
        Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = swap_layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        Some((
            base_layout,
            base_floating_layout,
            new_terminal_ids,
            new_floating_terminal_ids,
            new_plugin_ids,
        )),
        true,
        stacked_resize,
    );
    let _ = tab.move_focus_down(client_id);
    tab.next_swap_layout().unwrap();
    tab.render(&mut output, None).unwrap();

    let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_eq!(
        cursor_coordinates,
        Some((1, 1)),
        "cursor coordinates moved to the new pane",
    );

    assert_snapshot!(snapshot);
}

#[test]
fn when_swapping_tiled_layouts_in_an_undamaged_state_with_no_focus_node_pane_focuses_on_deepest_node(
) {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let base_layout = r#"
        layout {
            pane
            pane
            pane
        }
    "#;
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane split_direction="vertical" {
                        pane
                        pane
                        pane
                    }
                }
            }
        }
    "#;
    let (base_layout, base_floating_layout) =
        Layout::from_kdl(base_layout, Some("file_name.kdl".into()), None, None)
            .unwrap()
            .template
            .unwrap();

    let new_terminal_ids = vec![(1, None), (2, None), (3, None)];
    let new_floating_terminal_ids = vec![];
    let new_plugin_ids = HashMap::new();

    let swap_layout =
        Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = swap_layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        Some((
            base_layout,
            base_floating_layout,
            new_terminal_ids,
            new_floating_terminal_ids,
            new_plugin_ids,
        )),
        true,
        stacked_resize,
    );
    let _ = tab.move_focus_down(client_id);
    let _ = tab.move_focus_down(client_id);
    tab.next_swap_layout().unwrap();
    tab.render(&mut output, None).unwrap();

    let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_eq!(
        cursor_coordinates,
        Some((82, 1)),
        "cursor coordinates moved to the new pane",
    );

    assert_snapshot!(snapshot);
}

#[test]
fn when_closing_a_pane_in_auto_layout_the_focus_goes_to_last_focused_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let base_layout = r#"
        layout {
            pane
            pane
            pane
        }
    "#;
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane split_direction="vertical" {
                        pane
                        pane
                        pane
                    }
                }
            }
        }
    "#;
    let (base_layout, base_floating_layout) =
        Layout::from_kdl(base_layout, Some("file_name.kdl".into()), None, None)
            .unwrap()
            .template
            .unwrap();

    let new_terminal_ids = vec![(1, None), (2, None), (3, None)];
    let new_floating_terminal_ids = vec![];
    let new_plugin_ids = HashMap::new();

    let swap_layout =
        Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = swap_layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        Some((
            base_layout,
            base_floating_layout,
            new_terminal_ids,
            new_floating_terminal_ids,
            new_plugin_ids,
        )),
        true,
        stacked_resize,
    );
    let _ = tab.move_focus_down(client_id);
    let _ = tab.move_focus_down(client_id);
    tab.close_pane(PaneId::Terminal(3), false, None);
    tab.render(&mut output, None).unwrap();

    let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_eq!(
        cursor_coordinates,
        Some((62, 1)),
        "cursor coordinates moved to the new pane",
    );
    assert_snapshot!(snapshot);
}

#[test]
fn floating_layout_with_plugins_and_commands_swaped_properly() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let base_layout = r#"
        layout {
            floating_panes {
                pane x=0 y=0 {
                    plugin location="zellij:tab-bar"
                }
                pane x=0 y=10 command="command1"
                pane
                pane x=50 y=10 command="command2"
                pane x=50 y=0 {
                    plugin location="zellij:status-bar"
                }
            }
        }
    "#;
    // this swap layout swaps between the location of the plugins and the commands
    let swap_layouts = r#"
        layout {
            swap_floating_layout {
                floating_panes {
                    pane x=0 y=0 {
                        plugin location="zellij:status-bar"
                    }
                    pane x=0 y=10 command="command2"
                    pane
                    pane x=50 y=10 command="command1"
                    pane x=50 y=0 {
                        plugin location="zellij:tab-bar"
                    }
                }
            }
        }
    "#;
    let (base_layout, base_floating_layout) =
        Layout::from_kdl(base_layout, Some("file_name.kdl".into()), None, None)
            .unwrap()
            .template
            .unwrap();

    let mut command_1 = RunCommand::default();
    command_1.command = PathBuf::from("command1");
    let mut command_2 = RunCommand::default();
    command_2.command = PathBuf::from("command2");
    let new_floating_terminal_ids = vec![(1, Some(command_1)), (2, None), (3, Some(command_2))];
    let new_terminal_ids = vec![(4, None)];
    let mut new_plugin_ids = HashMap::new();
    new_plugin_ids.insert(
        RunPluginOrAlias::from_url("zellij:tab-bar", &None, None, None).unwrap(),
        vec![1],
    );
    new_plugin_ids.insert(
        RunPluginOrAlias::from_url("zellij:status-bar", &None, None, None).unwrap(),
        vec![2],
    );

    let swap_layout =
        Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = swap_layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        Some((
            base_layout,
            base_floating_layout,
            new_terminal_ids,
            new_floating_terminal_ids,
            new_plugin_ids,
        )),
        true,
        stacked_resize,
    );
    let _ = tab.handle_plugin_bytes(1, 1, "I am a tab bar".as_bytes().to_vec());
    let _ = tab.handle_plugin_bytes(2, 1, "I am a\n\rstatus bar".as_bytes().to_vec());
    tab.next_swap_layout().unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_snapshot!(snapshot);
}

#[test]
fn base_floating_layout_is_included_in_swap_layouts() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let base_layout = r#"
        layout {
            floating_panes {
                pane x=0 y=0 {
                    plugin location="zellij:tab-bar"
                }
                pane x=0 y=10 command="command1"
                pane
                pane x=50 y=10 command="command2"
                pane x=50 y=0 {
                    plugin location="zellij:status-bar"
                }
            }
        }
    "#;
    // this swap layout swaps between the location of the plugins and the commands
    let swap_layouts = r#"
        layout {
            swap_floating_layout {
                floating_panes {
                    pane x=0 y=0 {
                        plugin location="zellij:status-bar"
                    }
                    pane x=0 y=10 command="command2"
                    pane
                    pane x=50 y=10 command="command1"
                    pane x=50 y=0 {
                        plugin location="zellij:tab-bar"
                    }
                }
            }
        }
    "#;
    let (base_layout, base_floating_layout) =
        Layout::from_kdl(base_layout, Some("file_name.kdl".into()), None, None)
            .unwrap()
            .template
            .unwrap();

    let mut command_1 = RunCommand::default();
    command_1.command = PathBuf::from("command1");
    let mut command_2 = RunCommand::default();
    command_2.command = PathBuf::from("command2");
    let new_floating_terminal_ids = vec![(1, Some(command_1)), (2, None), (3, Some(command_2))];
    let new_terminal_ids = vec![(4, None)];
    let mut new_plugin_ids = HashMap::new();
    new_plugin_ids.insert(
        RunPluginOrAlias::from_url("zellij:tab-bar", &None, None, None).unwrap(),
        vec![1],
    );
    new_plugin_ids.insert(
        RunPluginOrAlias::from_url("zellij:status-bar", &None, None, None).unwrap(),
        vec![2],
    );

    let swap_layout =
        Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = swap_layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        Some((
            base_layout,
            base_floating_layout,
            new_terminal_ids,
            new_floating_terminal_ids,
            new_plugin_ids,
        )),
        true,
        stacked_resize,
    );
    let _ = tab.handle_plugin_bytes(1, 1, "I am a tab bar".as_bytes().to_vec());
    let _ = tab.handle_plugin_bytes(2, 1, "I am a\n\rstatus bar".as_bytes().to_vec());
    tab.next_swap_layout().unwrap();
    tab.previous_swap_layout().unwrap(); // move back to the base layout
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_snapshot!(snapshot);
}

#[test]
fn swap_floating_layouts_including_command_panes_absent_from_existing_layout() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let base_layout = r#"
        layout {
            floating_panes {
                pane {
                    plugin location="zellij:tab-bar"
                }
                pane
                pane
                pane
                pane {
                    plugin location="zellij:status-bar"
                }
            }
        }
    "#;
    // this swap layout changes both the split direction of the two command panes and the location
    // of the plugins - we want to make sure that they are all placed properly and not switched
    // around
    let swap_layouts = r#"
        layout {
            swap_floating_layout {
                floating_panes {
                    pane {
                        plugin location="zellij:status-bar"
                    }
                    pane x=0 y=0 command="command1"
                    pane x=10 y=10 command="command2"
                    pane
                    pane {
                        plugin location="zellij:tab-bar"
                    }
                }
            }
        }
    "#;
    let (base_layout, base_floating_layout) =
        Layout::from_kdl(base_layout, Some("file_name.kdl".into()), None, None)
            .unwrap()
            .template
            .unwrap();

    let new_floating_terminal_ids = vec![(1, None), (2, None), (3, None)];
    let new_terminal_ids = vec![(4, None)];
    let mut new_plugin_ids = HashMap::new();
    new_plugin_ids.insert(
        RunPluginOrAlias::from_url("zellij:tab-bar", &None, None, None).unwrap(),
        vec![1],
    );
    new_plugin_ids.insert(
        RunPluginOrAlias::from_url("zellij:status-bar", &None, None, None).unwrap(),
        vec![2],
    );

    let swap_layout =
        Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = swap_layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        Some((
            base_layout,
            base_floating_layout,
            new_terminal_ids,
            new_floating_terminal_ids,
            new_plugin_ids,
        )),
        true,
        stacked_resize,
    );
    let _ = tab.handle_plugin_bytes(1, 1, "I am a tab bar".as_bytes().to_vec());
    let _ = tab.handle_plugin_bytes(2, 1, "I am a\n\rstatus bar".as_bytes().to_vec());
    tab.next_swap_layout().unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_snapshot!(snapshot);
}

#[test]
fn swap_floating_layouts_not_including_command_panes_present_in_existing_layout() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let base_layout = r#"
        layout {
            floating_panes {
                pane {
                    plugin location="zellij:tab-bar"
                }
                pane command="command1"
                pane
                pane command="command2"
                pane {
                    plugin location="zellij:status-bar"
                }
            }
        }
    "#;
    // this swap layout changes both the split direction of the two command panes and the location
    // of the plugins - we want to make sure that they are all placed properly and not switched
    // around
    let swap_layouts = r#"
        layout {
            swap_floating_layout {
                floating_panes {
                    pane {
                        plugin location="zellij:status-bar"
                    }
                    pane
                    pane
                    pane
                    pane {
                        plugin location="zellij:tab-bar"
                    }
                }
            }
        }
    "#;
    let (base_layout, base_floating_layout) =
        Layout::from_kdl(base_layout, Some("file_name.kdl".into()), None, None)
            .unwrap()
            .template
            .unwrap();

    let mut command_1 = RunCommand::default();
    command_1.command = PathBuf::from("command1");
    let mut command_2 = RunCommand::default();
    command_2.command = PathBuf::from("command2");
    let new_floating_terminal_ids = vec![(1, Some(command_1)), (2, None), (3, Some(command_2))];
    let new_terminal_ids = vec![(4, None)];
    let mut new_plugin_ids = HashMap::new();
    new_plugin_ids.insert(
        RunPluginOrAlias::from_url("zellij:tab-bar", &None, None, None).unwrap(),
        vec![1],
    );
    new_plugin_ids.insert(
        RunPluginOrAlias::from_url("zellij:status-bar", &None, None, None).unwrap(),
        vec![2],
    );

    let swap_layout =
        Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = swap_layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        Some((
            base_layout,
            base_floating_layout,
            new_terminal_ids,
            new_floating_terminal_ids,
            new_plugin_ids,
        )),
        true,
        stacked_resize,
    );
    let _ = tab.handle_plugin_bytes(1, 1, "I am a tab bar".as_bytes().to_vec());
    let _ = tab.handle_plugin_bytes(2, 1, "I am a\n\rstatus bar".as_bytes().to_vec());
    tab.next_swap_layout().unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_snapshot!(snapshot);
}

#[test]
fn swap_floating_layouts_including_plugin_panes_absent_from_existing_layout() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let base_layout = r#"
        layout {
            floating_panes {
                pane
                pane
                pane
            }
        }
    "#;
    let swap_layouts = r#"
        layout {
            swap_floating_layout {
                floating_panes {
                    pane {
                        plugin location="zellij:status-bar"
                    }
                    pane
                    pane {
                        plugin location="zellij:tab-bar"
                    }
                }
            }
        }
    "#;
    let (base_layout, base_floating_layout) =
        Layout::from_kdl(base_layout, Some("file_name.kdl".into()), None, None)
            .unwrap()
            .template
            .unwrap();

    let new_floating_terminal_ids = vec![(1, None), (2, None), (3, None)];
    let new_terminal_ids = vec![(4, None)];
    let new_plugin_ids = HashMap::new();

    let swap_layout =
        Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = swap_layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        Some((
            base_layout,
            base_floating_layout,
            new_terminal_ids,
            new_floating_terminal_ids,
            new_plugin_ids,
        )),
        true,
        stacked_resize,
    );
    tab.next_swap_layout().unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_snapshot!(snapshot);
}

#[test]
fn swap_floating_layouts_not_including_plugin_panes_present_in_existing_layout() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let base_layout = r#"
        layout {
            floating_panes {
                pane {
                    plugin location="zellij:tab-bar"
                }
                pane
                pane {
                    plugin location="zellij:status-bar"
                }
            }
        }
    "#;
    // this swap layout changes both the split direction of the two command panes and the location
    // of the plugins - we want to make sure that they are all placed properly and not switched
    // around
    let swap_layouts = r#"
        layout {
            swap_floating_layout {
                floating_panes {
                    pane
                    pane
                    pane
                }
            }
        }
    "#;
    let (base_layout, base_floating_layout) =
        Layout::from_kdl(base_layout, Some("file_name.kdl".into()), None, None)
            .unwrap()
            .template
            .unwrap();

    let mut command_1 = RunCommand::default();
    command_1.command = PathBuf::from("command1");
    let mut command_2 = RunCommand::default();
    command_2.command = PathBuf::from("command2");
    let new_floating_terminal_ids = vec![(1, Some(command_1)), (2, None), (3, Some(command_2))];
    let new_terminal_ids = vec![(4, None)];
    let mut new_plugin_ids = HashMap::new();
    new_plugin_ids.insert(
        RunPluginOrAlias::from_url("zellij:tab-bar", &None, None, None).unwrap(),
        vec![1],
    );
    new_plugin_ids.insert(
        RunPluginOrAlias::from_url("zellij:status-bar", &None, None, None).unwrap(),
        vec![2],
    );

    let swap_layout =
        Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = swap_layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        Some((
            base_layout,
            base_floating_layout,
            new_terminal_ids,
            new_floating_terminal_ids,
            new_plugin_ids,
        )),
        true,
        stacked_resize,
    );
    let _ = tab.handle_plugin_bytes(1, 1, "I am a tab bar".as_bytes().to_vec());
    let _ = tab.handle_plugin_bytes(2, 1, "I am a\n\rstatus bar".as_bytes().to_vec());
    tab.next_swap_layout().unwrap();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_snapshot!(snapshot);
}

#[test]
fn new_floating_pane_in_auto_layout() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let base_layout = r#"
        layout
    "#;
    let swap_layouts = r#"
        layout {
            swap_floating_layout name="spread" {
                floating_panes max_panes=1 {
                    pane {y "50%"; x "50%"; }
                }
                floating_panes max_panes=2 {
                    pane { x "1%"; y "25%"; width "45%"; }
                    pane { x "50%"; y "25%"; width "45%"; }
                }
                floating_panes max_panes=3 {
                    pane { x "1%"; y "1%"; width "45%"; }
                    pane { x "50%"; y "1%"; width "45%"; }
                    pane { y "55%"; width "45%"; height "45%"; }
                }
            }
        }
    "#;
    let (base_layout, base_floating_layout) =
        Layout::from_kdl(base_layout, Some("file_name.kdl".into()), None, None)
            .unwrap()
            .template
            .unwrap();

    let new_terminal_ids = vec![(1, None)];
    let new_floating_terminal_ids = vec![];
    let new_plugin_ids = HashMap::new();

    let swap_layout =
        Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = swap_layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        Some((
            base_layout,
            base_floating_layout,
            new_terminal_ids,
            new_floating_terminal_ids,
            new_plugin_ids,
        )),
        true,
        stacked_resize,
    );

    let mut expected_cursor_coordinates = vec![(61, 11), (61, 6), (34, 12)];
    for i in 0..3 {
        let new_pane_id = i + 2;
        tab.new_pane(
            PaneId::Terminal(new_pane_id),
            None,
            None,
            false,
            true,
            NewPanePlacement::Floating(None),
            Some(client_id),
            None,
        )
        .unwrap();
        tab.render(&mut output, None).unwrap();

        let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
            output.serialize().unwrap().get(&client_id).unwrap(),
            size.rows,
            size.cols,
            Palette::default(),
        );
        let (expected_x, expected_y) = expected_cursor_coordinates.remove(0);
        assert_eq!(
            cursor_coordinates,
            Some((expected_x, expected_y)),
            "cursor coordinates moved to the new pane",
        );
        assert_snapshot!(snapshot);
    }
}

#[test]
fn when_swapping_floating_layouts_in_a_damaged_state_layout_and_pane_focus_are_unchanged() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let base_layout = r#"
        layout {
            floating_panes {
                pane x=0 y=0
                pane
                pane
            }
        }
    "#;
    let swap_layouts = r#"
        layout {
            swap_floating_layout {
                floating_panes {
                    pane
                    pane
                    pane
                }
            }
        }
    "#;
    let (base_layout, base_floating_layout) =
        Layout::from_kdl(base_layout, Some("file_name.kdl".into()), None, None)
            .unwrap()
            .template
            .unwrap();

    let new_floating_terminal_ids = vec![(1, None), (2, None), (3, None)];
    let new_terminal_ids = vec![(4, None)];
    let new_plugin_ids = HashMap::new();

    let swap_layout =
        Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = swap_layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        Some((
            base_layout,
            base_floating_layout,
            new_terminal_ids,
            new_floating_terminal_ids,
            new_plugin_ids,
        )),
        true,
        stacked_resize,
    );
    tab.resize(
        client_id,
        ResizeStrategy::new(Resize::Increase, Some(Direction::Down)),
    )
    .unwrap();
    tab.next_swap_layout().unwrap();
    tab.render(&mut output, None).unwrap();

    let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_eq!(
        cursor_coordinates,
        Some((33, 8)),
        "cursor coordinates moved to the new pane",
    );

    assert_snapshot!(snapshot);
}

#[test]
fn when_swapping_floating_layouts_in_an_undamaged_state_pane_focuses_on_focused_node() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let base_layout = r#"
        layout {
            floating_panes {
                pane x=0 y=0
                pane
                pane
            }
        }
    "#;
    let swap_layouts = r#"
        layout {
            swap_floating_layout {
                floating_panes {
                    pane
                    pane
                    pane
                }
            }
        }
    "#;
    let (base_layout, base_floating_layout) =
        Layout::from_kdl(base_layout, Some("file_name.kdl".into()), None, None)
            .unwrap()
            .template
            .unwrap();

    let new_floating_terminal_ids = vec![(1, None), (2, None), (3, None)];
    let new_terminal_ids = vec![(4, None)];
    let new_plugin_ids = HashMap::new();

    let swap_layout =
        Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = swap_layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        Some((
            base_layout,
            base_floating_layout,
            new_terminal_ids,
            new_floating_terminal_ids,
            new_plugin_ids,
        )),
        true,
        stacked_resize,
    );
    tab.next_swap_layout().unwrap();
    tab.render(&mut output, None).unwrap();

    let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_eq!(
        cursor_coordinates,
        Some((35, 10)),
        "cursor coordinates moved to the new pane",
    );

    assert_snapshot!(snapshot);
}

#[test]
fn when_swapping_floating_layouts_in_an_undamaged_state_with_no_focus_node_pane_focuses_on_deepest_node(
) {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let base_layout = r#"
        layout {
            floating_panes {
                pane
                pane
                pane
            }
        }
    "#;
    let swap_layouts = r#"
        layout {
            swap_floating_layout {
                floating_panes {
                    pane
                    pane
                    pane
                }
            }
        }
    "#;
    let (base_layout, base_floating_layout) =
        Layout::from_kdl(base_layout, Some("file_name.kdl".into()), None, None)
            .unwrap()
            .template
            .unwrap();

    let new_floating_terminal_ids = vec![(1, None), (2, None), (3, None)];
    let new_terminal_ids = vec![(4, None)];
    let new_plugin_ids = HashMap::new();

    let swap_layout =
        Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = swap_layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        Some((
            base_layout,
            base_floating_layout,
            new_terminal_ids,
            new_floating_terminal_ids,
            new_plugin_ids,
        )),
        true,
        stacked_resize,
    );
    tab.next_swap_layout().unwrap();
    tab.render(&mut output, None).unwrap();

    let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_eq!(
        cursor_coordinates,
        Some((35, 10)),
        "cursor coordinates moved to the new pane",
    );

    assert_snapshot!(snapshot);
}

#[test]
fn when_closing_a_floating_pane_in_auto_layout_the_focus_goes_to_last_focused_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let base_layout = r#"
        layout {
            floating_panes {
                pane
                pane
                pane
            }
        }
    "#;
    let swap_layouts = r#"
        layout {
            swap_floating_layout {
                floating_panes {
                    pane
                    pane
                    pane
                }
            }
        }
    "#;
    let (base_layout, base_floating_layout) =
        Layout::from_kdl(base_layout, Some("file_name.kdl".into()), None, None)
            .unwrap()
            .template
            .unwrap();

    let new_floating_terminal_ids = vec![(1, None), (2, None), (3, None)];
    let new_terminal_ids = vec![(4, None)];
    let new_plugin_ids = HashMap::new();

    let swap_layout =
        Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = swap_layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        Some((
            base_layout,
            base_floating_layout,
            new_terminal_ids,
            new_floating_terminal_ids,
            new_plugin_ids,
        )),
        true,
        stacked_resize,
    );
    let _ = tab.move_focus_up(client_id);
    let _ = tab.move_focus_up(client_id);
    tab.close_pane(PaneId::Terminal(1), false, None);
    tab.render(&mut output, None).unwrap();

    let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_eq!(
        cursor_coordinates,
        Some((31, 6)),
        "cursor coordinates moved to the new pane",
    );
    assert_snapshot!(snapshot);
}

#[test]
fn when_resizing_whole_tab_with_auto_layout_and_floating_panes_the_layout_is_maintained() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut output = Output::default();
    let base_layout = r#"
        layout {
            floating_panes {
                pane
                pane
                pane
            }
        }
    "#;
    let swap_layouts = r#"
        layout
    "#;
    let (base_layout, base_floating_layout) =
        Layout::from_kdl(base_layout, Some("file_name.kdl".into()), None, None)
            .unwrap()
            .template
            .unwrap();

    let new_floating_terminal_ids = vec![(1, None), (2, None), (3, None)];
    let new_terminal_ids = vec![(4, None)];
    let new_plugin_ids = HashMap::new();

    let swap_layout =
        Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = swap_layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        Some((
            base_layout,
            base_floating_layout,
            new_terminal_ids,
            new_floating_terminal_ids,
            new_plugin_ids,
        )),
        true,
        stacked_resize,
    );
    let new_size = Size {
        cols: 150,
        rows: 30,
    };
    let _ = tab.resize_whole_tab(new_size);
    tab.render(&mut output, None).unwrap();

    let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
        output.serialize().unwrap().get(&client_id).unwrap(),
        new_size.rows,
        new_size.cols,
        Palette::default(),
    );
    assert_eq!(
        cursor_coordinates,
        Some((43, 13)),
        "cursor coordinates moved to the new pane",
    );
    assert_snapshot!(snapshot);
}

#[test]
fn when_applying_a_truncated_swap_layout_child_attributes_are_not_ignored() {
    // here we want to make sure that the nested borderless is preserved on resize (when the layout
    // is reapplied, and thus is truncated to just one pane rather than a logical container pane
    // and an actual pane as it is described here)
    let layout = r#"
        layout {
            pane {
                pane borderless=true
            }
        }
    "#;
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab_with_layout(size, ModeInfo::default(), layout);
    let mut output = Output::default();
    let new_size = Size {
        cols: 122,
        rows: 20,
    };
    let _ = tab.resize_whole_tab(new_size);
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        new_size.rows,
        new_size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn can_define_expanded_pane_in_stack() {
    let layout = r#"
        layout {
            pane split_direction="Vertical" {
                pane
                pane stacked=true {
                    pane
                    pane expanded=true
                    pane
                    pane
                }
            }
        }
    "#;
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab_with_layout(size, ModeInfo::default(), layout);
    let mut output = Output::default();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn borderless_floating_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();

    // Create borderless floating pane
    let coordinates = FloatingPaneCoordinates {
        x: Some(PercentOrFixed::Fixed(10)),
        y: Some(PercentOrFixed::Fixed(5)),
        width: Some(PercentOrFixed::Fixed(50)),
        height: Some(PercentOrFixed::Fixed(10)),
        pinned: None,
        borderless: Some(true),
    };

    tab.new_floating_pane(
        new_pane_id,
        None,
        None,
        false,
        true,
        Some(coordinates),
        None,
    )
    .unwrap();

    tab.handle_pty_bytes(
        2,
        Vec::from("\n\n\n                   I am borderless".as_bytes()),
    )
    .unwrap();

    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn borderless_pane_content_fills_edges() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();

    let coordinates = FloatingPaneCoordinates {
        x: Some(PercentOrFixed::Fixed(10)),
        y: Some(PercentOrFixed::Fixed(5)),
        width: Some(PercentOrFixed::Fixed(30)),
        height: Some(PercentOrFixed::Fixed(5)),
        pinned: None,
        borderless: Some(true),
    };

    tab.new_floating_pane(
        new_pane_id,
        None,
        None,
        false,
        true,
        Some(coordinates),
        None,
    )
    .unwrap();

    // Fill with X's to verify content reaches edges
    tab.handle_pty_bytes(
        2,
        Vec::from("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\r\nXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\r\nXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\r\nXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\r\nXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX".as_bytes()),
    )
    .unwrap();

    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn borderless_pinned_floating_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let borderless_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();

    let coordinates = FloatingPaneCoordinates {
        x: Some(PercentOrFixed::Fixed(30)),
        y: Some(PercentOrFixed::Fixed(5)),
        width: Some(PercentOrFixed::Fixed(60)),
        height: Some(PercentOrFixed::Fixed(8)),
        pinned: Some(true),
        borderless: Some(true),
    };

    tab.new_floating_pane(
        borderless_pane_id,
        None,
        None,
        false,
        true,
        Some(coordinates),
        None,
    )
    .unwrap();

    // Toggle floating panes off to test pinned behavior
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();

    tab.handle_pty_bytes(1, Vec::from("Content behind pinned pane".as_bytes()))
        .unwrap();

    tab.handle_pty_bytes(2, Vec::from("\n\nPinned borderless pane".as_bytes()))
        .unwrap();

    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn cursor_hidden_when_floating_pane_is_under_pinned_pane() {
    // Test that cursor is correctly hidden when focused on a floating pane that's under a pinned pane
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let mut output = Output::default();

    // Create first floating pane (will be underneath)
    let bottom_pane_coordinates = FloatingPaneCoordinates {
        x: Some(PercentOrFixed::Fixed(10)),
        y: Some(PercentOrFixed::Fixed(5)),
        width: Some(PercentOrFixed::Fixed(40)),
        height: Some(PercentOrFixed::Fixed(10)),
        pinned: Some(false),
        borderless: Some(false),
    };

    tab.new_floating_pane(
        PaneId::Terminal(2),
        None,
        None,
        false,
        true,
        Some(bottom_pane_coordinates),
        None,
    )
    .unwrap();

    // Create overlapping pinned pane on top
    let top_pane_coordinates = FloatingPaneCoordinates {
        x: Some(PercentOrFixed::Fixed(15)),
        y: Some(PercentOrFixed::Fixed(6)),
        width: Some(PercentOrFixed::Fixed(30)),
        height: Some(PercentOrFixed::Fixed(8)),
        pinned: Some(true),
        borderless: Some(false),
    };

    tab.new_floating_pane(
        PaneId::Terminal(3),
        None,
        None,
        false,
        true,
        Some(top_pane_coordinates),
        None,
    )
    .unwrap();

    // Add some text to both panes so we can see them
    tab.handle_pty_bytes(2, Vec::from("Bottom floating pane".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(3, Vec::from("Top pinned pane".as_bytes()))
        .unwrap();

    // Explicitly focus the bottom floating pane (PaneId::Terminal(2))
    // Use move_focus_left to switch from pane 3 to pane 2
    tab.move_focus_left(client_id).unwrap();

    // Render
    tab.render(&mut output, None).unwrap();
    let (_snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    // The cursor should be hidden because the focused pane (2) is under the pinned pane (3)
    // and the cursor position falls within the overlapping area
    assert_eq!(
        cursor_coordinates, None,
        "Cursor should be hidden when focused floating pane is under a pinned pane that covers it"
    );
}

#[test]
fn cursor_visible_when_pinned_pane_is_focused() {
    // Test that cursor is visible when focused on a pinned pane that's on top
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let mut output = Output::default();

    // Create first floating pane (will be underneath)
    let bottom_pane_coordinates = FloatingPaneCoordinates {
        x: Some(PercentOrFixed::Fixed(10)),
        y: Some(PercentOrFixed::Fixed(5)),
        width: Some(PercentOrFixed::Fixed(40)),
        height: Some(PercentOrFixed::Fixed(10)),
        pinned: Some(false),
        borderless: Some(false),
    };

    tab.new_floating_pane(
        PaneId::Terminal(2),
        None,
        None,
        false,
        true,
        Some(bottom_pane_coordinates),
        None,
    )
    .unwrap();

    // Create overlapping pinned pane on top
    let top_pane_coordinates = FloatingPaneCoordinates {
        x: Some(PercentOrFixed::Fixed(15)),
        y: Some(PercentOrFixed::Fixed(6)),
        width: Some(PercentOrFixed::Fixed(30)),
        height: Some(PercentOrFixed::Fixed(8)),
        pinned: Some(true),
        borderless: Some(false),
    };

    tab.new_floating_pane(
        PaneId::Terminal(3),
        None,
        None,
        false,
        true,
        Some(top_pane_coordinates),
        None,
    )
    .unwrap();

    // Add some text to both panes
    tab.handle_pty_bytes(2, Vec::from("Bottom floating pane".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(3, Vec::from("Top pinned pane".as_bytes()))
        .unwrap();

    // The pinned pane should be focused by default (last created)
    // Render
    tab.render(&mut output, None).unwrap();
    let (_snapshot, cursor_coordinates) = take_snapshot_and_cursor_position(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    // The cursor should be visible because the focused pane is the pinned pane on top
    assert!(
        cursor_coordinates.is_some(),
        "Cursor should be visible when focused on pinned pane that's on top"
    );
}

#[test]
fn test_left_click_on_inactive_tiled_pane_changes_focus() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);

    tab.vertical_split(new_pane_id, None, client_id, None, None)
        .unwrap();

    tab.handle_pty_bytes(1, Vec::from("Left pane".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(2, Vec::from("Right pane".as_bytes()))
        .unwrap();

    assert_eq!(tab.get_active_pane_id(client_id), Some(new_pane_id));

    let effect = tab
        .handle_mouse_event(
            &MouseEvent::new_left_press_event(Position::new(5, 30)),
            client_id,
        )
        .unwrap();

    assert!(effect.state_changed);
    assert_eq!(tab.get_active_pane_id(client_id), Some(PaneId::Terminal(1)));
}

#[test]
fn test_left_release_after_selection_copies_to_clipboard() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let _output = Output::default();

    // Fill the pane with some text content
    tab.handle_pty_bytes(1, Vec::from("Selectable text content here".as_bytes()))
        .unwrap();

    // Start text selection with left press
    tab.handle_mouse_event(
        &MouseEvent::new_left_press_event(Position::new(1, 5)),
        client_id,
    )
    .unwrap();

    // Drag to create selection
    tab.handle_mouse_event(
        &MouseEvent::new_left_motion_event(Position::new(1, 15)),
        client_id,
    )
    .unwrap();

    // Release should trigger clipboard copy
    let release_effect = tab
        .handle_mouse_event(
            &MouseEvent::new_left_release_event(Position::new(1, 15)),
            client_id,
        )
        .unwrap();

    // Verify clipboard message was sent
    assert!(release_effect.leave_clipboard_message);
}

#[test]
fn test_ctrl_click_on_tiled_pane_edge_starts_resize() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let _output = Output::default();

    // Create a vertical split
    tab.vertical_split(new_pane_id, None, client_id, None, None)
        .unwrap();

    tab.handle_pty_bytes(1, Vec::from("Left".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(2, Vec::from("Right".as_bytes()))
        .unwrap();

    // Click on the edge between panes with Ctrl
    // For a vertical split at cols=121, the edge is around column 60-61
    let edge_position = Position::new(5, 60);
    let effect = tab
        .handle_mouse_event(
            &MouseEvent::new_left_press_with_ctrl_event(edge_position),
            client_id,
        )
        .unwrap();

    // Verify resize started
    assert!(effect.state_changed);
}

#[test]
fn test_ctrl_drag_resizes_tiled_pane_horizontally() {
    let size = Size {
        cols: 120,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();

    tab.vertical_split(new_pane_id, None, client_id, None, None)
        .unwrap();

    tab.handle_pty_bytes(1, Vec::from("Left pane content".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(2, Vec::from("Right pane content".as_bytes()))
        .unwrap();

    tab.render(&mut output, None).unwrap();
    let snapshot_before = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    let edge_position = Position::new(5, 60);
    tab.handle_mouse_event(
        &MouseEvent::new_left_press_with_ctrl_event(edge_position),
        client_id,
    )
    .unwrap();

    let motion_position = Position::new(5, 61);
    let motion_effect = tab
        .handle_mouse_event(
            &MouseEvent::new_left_motion_with_ctrl_event(motion_position),
            client_id,
        )
        .unwrap();

    tab.handle_mouse_event(
        &MouseEvent::new_left_release_with_ctrl_event(motion_position),
        client_id,
    )
    .unwrap();

    assert!(motion_effect.state_changed);

    let mut output = Output::default();
    tab.render(&mut output, None).unwrap();
    let snapshot_after = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_ne!(snapshot_before, snapshot_after);
    assert_snapshot!(format!("{}", snapshot_after));
}

#[test]
fn test_ctrl_drag_resizes_tiled_pane_vertically() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();

    tab.horizontal_split(new_pane_id, None, client_id, None, None)
        .unwrap();

    tab.handle_pty_bytes(1, Vec::from("Top pane".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(2, Vec::from("Bottom pane".as_bytes()))
        .unwrap();

    tab.render(&mut output, None).unwrap();
    let snapshot_before = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    let edge_position = Position::new(10, 60);
    tab.handle_mouse_event(
        &MouseEvent::new_left_press_with_ctrl_event(edge_position),
        client_id,
    )
    .unwrap();

    let motion_position = Position::new(11, 60);
    let motion_effect = tab
        .handle_mouse_event(
            &MouseEvent::new_left_motion_with_ctrl_event(motion_position),
            client_id,
        )
        .unwrap();

    tab.handle_mouse_event(
        &MouseEvent::new_left_release_with_ctrl_event(motion_position),
        client_id,
    )
    .unwrap();

    assert!(motion_effect.state_changed);

    let mut output = Output::default();
    tab.render(&mut output, None).unwrap();
    let snapshot_after = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_ne!(snapshot_before, snapshot_after);
    assert_snapshot!(format!("{}", snapshot_after));
}

#[test]
fn test_ctrl_drag_resizes_unfocused_tiled_pane_vertically() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();

    tab.horizontal_split(new_pane_id, None, client_id, None, None)
        .unwrap();

    tab.handle_pty_bytes(1, Vec::from("Top pane".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(2, Vec::from("Bottom pane".as_bytes()))
        .unwrap();

    tab.render(&mut output, None).unwrap();
    let snapshot_before = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    let edge_position = Position::new(10, 60);
    let _ = tab.focus_pane_with_id(PaneId::Terminal(1), false, false, client_id); // focus top pane
    tab.handle_mouse_event(
        &MouseEvent::new_left_press_with_ctrl_event(edge_position),
        client_id,
    )
    .unwrap();

    let motion_position = Position::new(11, 60);
    let motion_effect = tab
        .handle_mouse_event(
            &MouseEvent::new_left_motion_with_ctrl_event(motion_position),
            client_id,
        )
        .unwrap();

    tab.handle_mouse_event(
        &MouseEvent::new_left_release_with_ctrl_event(motion_position),
        client_id,
    )
    .unwrap();

    assert!(motion_effect.state_changed);

    let mut output = Output::default();
    tab.render(&mut output, None).unwrap();
    let snapshot_after = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    let focused_pane = tab.get_active_pane_id(client_id).unwrap();

    assert_ne!(focused_pane, new_pane_id, "resize did not change focus");
    assert_ne!(snapshot_before, snapshot_after);
    assert_snapshot!(format!("{}", snapshot_after));
}

#[test]
fn test_ctrl_click_on_pane_body_does_nothing() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let _output = Output::default();

    // Create a vertical split
    tab.vertical_split(new_pane_id, None, client_id, None, None)
        .unwrap();

    tab.handle_pty_bytes(1, Vec::from("Left".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(2, Vec::from("Right".as_bytes()))
        .unwrap();

    // Click in the center of a pane (not on an edge)
    let center_position = Position::new(10, 30);
    let effect = tab
        .handle_mouse_event(
            &MouseEvent::new_left_press_with_ctrl_event(center_position),
            client_id,
        )
        .unwrap();

    // Should not trigger any state change
    assert!(!effect.state_changed);
}

#[test]
fn test_ctrl_drag_resizes_floating_pane_from_edge() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let floating_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();

    let coordinates = FloatingPaneCoordinates {
        x: Some(PercentOrFixed::Fixed(5)),
        y: Some(PercentOrFixed::Fixed(5)),
        width: Some(PercentOrFixed::Fixed(10)),
        height: Some(PercentOrFixed::Fixed(10)),
        pinned: None,
        borderless: Some(false),
    };

    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        floating_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::Floating(Some(coordinates)),
        Some(client_id),
        None,
    )
    .unwrap();

    tab.handle_pty_bytes(2, Vec::from("Floating pane".as_bytes()))
        .unwrap();

    tab.render(&mut output, None).unwrap();
    let snapshot_before = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    let edge_position = Position::new(7, 14); // Approximate right edge
    tab.handle_mouse_event(
        &MouseEvent::new_left_press_with_ctrl_event(edge_position),
        client_id,
    )
    .unwrap();

    let motion_position = Position::new(7, 15);
    let motion_effect = tab
        .handle_mouse_event(
            &MouseEvent::new_left_motion_with_ctrl_event(motion_position),
            client_id,
        )
        .unwrap();

    tab.handle_mouse_event(
        &MouseEvent::new_left_release_with_ctrl_event(motion_position),
        client_id,
    )
    .unwrap();

    assert!(motion_effect.state_changed);

    let mut output = Output::default();
    tab.render(&mut output, None).unwrap();
    let snapshot_after = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_ne!(snapshot_before, snapshot_after);
    assert_snapshot!(format!("{}", snapshot_after));
}

#[test]
fn test_ctrl_drag_resizes_floating_pane_from_corner() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let floating_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();

    let coordinates = FloatingPaneCoordinates {
        x: Some(PercentOrFixed::Fixed(5)),
        y: Some(PercentOrFixed::Fixed(5)),
        width: Some(PercentOrFixed::Fixed(10)),
        height: Some(PercentOrFixed::Fixed(10)),
        pinned: None,
        borderless: Some(false),
    };

    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        floating_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::Floating(Some(coordinates)),
        Some(client_id),
        None,
    )
    .unwrap();

    tab.handle_pty_bytes(2, Vec::from("Corner resize test".as_bytes()))
        .unwrap();

    tab.render(&mut output, None).unwrap();
    let snapshot_before = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    let corner_position = Position::new(14, 14);
    tab.handle_mouse_event(
        &MouseEvent::new_left_press_with_ctrl_event(corner_position),
        client_id,
    )
    .unwrap();

    let motion_position = Position::new(15, 15);
    let motion_effect = tab
        .handle_mouse_event(
            &MouseEvent::new_left_motion_with_ctrl_event(motion_position),
            client_id,
        )
        .unwrap();

    tab.handle_mouse_event(
        &MouseEvent::new_left_release_with_ctrl_event(motion_position),
        client_id,
    )
    .unwrap();

    assert!(motion_effect.state_changed);

    let mut output = Output::default();
    tab.render(&mut output, None).unwrap();
    let snapshot_after = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_ne!(snapshot_before, snapshot_after);
    assert_snapshot!(format!("{}", snapshot_after));
}

#[test]
fn test_ctrl_drag_resizes_pinned_floating_pane_when_floating_panes_not_shown() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let floating_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();

    let coordinates = FloatingPaneCoordinates {
        x: Some(PercentOrFixed::Fixed(5)),
        y: Some(PercentOrFixed::Fixed(5)),
        width: Some(PercentOrFixed::Fixed(25)),
        height: Some(PercentOrFixed::Fixed(10)),
        pinned: Some(true),
        borderless: Some(false),
    };

    tab.new_pane(
        floating_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::Floating(Some(coordinates)),
        Some(client_id),
        None,
    )
    .unwrap();

    tab.handle_pty_bytes(2, Vec::from("Corner resize test".as_bytes()))
        .unwrap();

    tab.render(&mut output, None).unwrap();
    let snapshot_before = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    eprintln!("{}", snapshot_before);

    let corner_position = Position::new(14, 29);
    tab.handle_mouse_event(
        &MouseEvent::new_left_press_with_ctrl_event(corner_position),
        client_id,
    )
    .unwrap();

    let motion_position = Position::new(15, 30);
    let motion_effect = tab
        .handle_mouse_event(
            &MouseEvent::new_left_motion_with_ctrl_event(motion_position),
            client_id,
        )
        .unwrap();

    tab.handle_mouse_event(
        &MouseEvent::new_left_release_with_ctrl_event(motion_position),
        client_id,
    )
    .unwrap();

    assert!(motion_effect.state_changed);

    let mut output = Output::default();
    tab.render(&mut output, None).unwrap();
    let snapshot_after = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_ne!(snapshot_before, snapshot_after);
    eprintln!("{}", snapshot_after);
    assert_snapshot!(format!("{}", snapshot_after));
}

#[test]
fn test_ctrl_click_on_floating_pin_button_toggles_pin() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let floating_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();

    // Toggle floating panes mode and create a floating pane
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        floating_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();

    tab.handle_pty_bytes(2, Vec::from("Pin toggle test".as_bytes()))
        .unwrap();

    // Render before pin toggle
    tab.render(&mut output, None).unwrap();

    // Calculate pin button position (3 cells left of top-right corner of floating pane)
    // Default floating pane is roughly at y=5, right edge around x=90
    // Pin button should be at approximately (5, 87)
    let pin_position = Position::new(5, 87);

    let effect = tab
        .handle_mouse_event(
            &MouseEvent::new_left_press_with_ctrl_event(pin_position),
            client_id,
        )
        .unwrap();

    // Verify state changed (pin toggle should trigger this)
    assert!(effect.state_changed);

    // Render after pin toggle
    let mut output = Output::default();
    tab.render(&mut output, None).unwrap();
    let snapshot_after = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    // Snapshot should show pin indicator change
    assert_snapshot!(format!("{}", snapshot_after));
}

#[test]
fn test_ctrl_click_on_floating_frame_not_on_pin_starts_resize() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let floating_pane_id = PaneId::Terminal(2);
    let _output = Output::default();

    // Toggle floating panes mode and create a floating pane
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        floating_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();

    tab.handle_pty_bytes(2, Vec::from("Frame click test".as_bytes()))
        .unwrap();

    // Click on frame but NOT on pin button location
    // Use left edge of frame instead
    let frame_position = Position::new(5, 30);

    let effect = tab
        .handle_mouse_event(
            &MouseEvent::new_left_press_with_ctrl_event(frame_position),
            client_id,
        )
        .unwrap();

    // Should start resize (if on edge), state_changed should be true
    assert!(effect.state_changed);
}

#[test]
fn test_left_drag_moves_floating_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let floating_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();

    // Toggle floating panes mode and create a floating pane
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        floating_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();

    tab.handle_pty_bytes(2, Vec::from("Moving pane".as_bytes()))
        .unwrap();

    // Render before move
    tab.render(&mut output, None).unwrap();
    let snapshot_before = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    // Click on frame (not ctrl, not pin location) to start move
    let frame_position = Position::new(5, 30);
    let press_effect = tab
        .handle_mouse_event(&MouseEvent::new_left_press_event(frame_position), client_id)
        .unwrap();

    // Drag to move the pane
    let motion_position = Position::new(6, 31);
    tab.handle_mouse_event(
        &MouseEvent::new_left_motion_event(motion_position),
        client_id,
    )
    .unwrap();

    // Release
    tab.handle_mouse_event(
        &MouseEvent::new_left_release_event(motion_position),
        client_id,
    )
    .unwrap();

    // Verify state changed
    assert!(press_effect.state_changed);

    // Render after move
    let mut output = Output::default();
    tab.render(&mut output, None).unwrap();
    let snapshot_after = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    // Snapshots should be different
    assert_ne!(snapshot_before, snapshot_after);
    assert_snapshot!(format!("{}", snapshot_after));
}

#[test]
fn test_left_click_on_floating_pane_changes_focus() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let floating_pane_id_1 = PaneId::Terminal(2);
    let floating_pane_id_2 = PaneId::Terminal(3);

    let coordinates_1 = FloatingPaneCoordinates {
        x: Some(PercentOrFixed::Fixed(5)),
        y: Some(PercentOrFixed::Fixed(5)),
        width: Some(PercentOrFixed::Fixed(10)),
        height: Some(PercentOrFixed::Fixed(10)),
        pinned: None,
        borderless: Some(false),
    };

    let coordinates_2 = FloatingPaneCoordinates {
        x: Some(PercentOrFixed::Fixed(20)),
        y: Some(PercentOrFixed::Fixed(5)),
        width: Some(PercentOrFixed::Fixed(10)),
        height: Some(PercentOrFixed::Fixed(10)),
        pinned: None,
        borderless: Some(false),
    };

    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        floating_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::Floating(Some(coordinates_1)),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        floating_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::Floating(Some(coordinates_2)),
        Some(client_id),
        None,
    )
    .unwrap();

    tab.handle_pty_bytes(2, Vec::from("Pane 1".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(3, Vec::from("Pane 2".as_bytes()))
        .unwrap();

    assert_eq!(tab.get_active_pane_id(client_id), Some(floating_pane_id_2));

    let pane1_position = Position::new(7, 7);
    let effect = tab
        .handle_mouse_event(&MouseEvent::new_left_press_event(pane1_position), client_id)
        .unwrap();

    assert!(effect.state_changed);
    assert_eq!(tab.get_active_pane_id(client_id), Some(floating_pane_id_1));
}

#[test]
fn test_left_click_on_pinned_floating_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let floating_pane_id_1 = PaneId::Terminal(2);
    let floating_pane_id_2 = PaneId::Terminal(3);
    let mut output = Output::default();

    let coordinates_1 = FloatingPaneCoordinates {
        x: Some(PercentOrFixed::Fixed(5)),
        y: Some(PercentOrFixed::Fixed(5)),
        width: Some(PercentOrFixed::Fixed(30)),
        height: Some(PercentOrFixed::Fixed(10)),
        pinned: None,
        borderless: Some(false),
    };

    let coordinates_2 = FloatingPaneCoordinates {
        x: Some(PercentOrFixed::Fixed(60)),
        y: Some(PercentOrFixed::Fixed(5)),
        width: Some(PercentOrFixed::Fixed(30)),
        height: Some(PercentOrFixed::Fixed(10)),
        pinned: None,
        borderless: Some(false),
    };

    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();

    // will be pinned
    tab.new_pane(
        floating_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::Floating(Some(coordinates_1)),
        Some(client_id),
        None,
    )
    .unwrap();

    // will not be pinned
    tab.new_pane(
        floating_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::Floating(Some(coordinates_2)),
        Some(client_id),
        None,
    )
    .unwrap();

    tab.handle_pty_bytes(2, Vec::from("Pinned pane".as_bytes()))
        .unwrap();

    // render once so that the client's frame will be registered
    tab.render(&mut output, None).unwrap();

    let pin_position = Position::new(5, 31);
    tab.handle_mouse_event(&MouseEvent::new_left_press_event(pin_position), client_id)
        .unwrap();

    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();

    let pane_position = Position::new(6, 25);
    let _effect = tab
        .handle_mouse_event(&MouseEvent::new_left_press_event(pane_position), client_id)
        .unwrap();

    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    // Verify pinned pane is still visible in snapshot
    assert_snapshot!(format!("{}", snapshot));
}

#[test]
fn test_alt_left_click_toggles_pane_group() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let _output = Output::default();

    // Create a vertical split
    tab.vertical_split(new_pane_id, None, client_id, None, None)
        .unwrap();

    tab.handle_pty_bytes(1, Vec::from("Left".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(2, Vec::from("Right".as_bytes()))
        .unwrap();

    // Alt+click on a pane to toggle group
    let effect = tab
        .handle_mouse_event(
            &MouseEvent::new_left_press_with_alt_event(Position::new(5, 30)),
            client_id,
        )
        .unwrap();

    // Verify group_toggle effect returned
    assert!(effect.group_toggle.is_some());
    assert_eq!(effect.group_toggle, Some(PaneId::Terminal(1)));
}

#[test]
fn test_alt_left_drag_adds_panes_to_group() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id_2 = PaneId::Terminal(2);
    let new_pane_id_3 = PaneId::Terminal(3);
    let _output = Output::default();

    // Create two vertical splits (3 panes)
    tab.vertical_split(new_pane_id_2, None, client_id, None, None)
        .unwrap();
    tab.vertical_split(new_pane_id_3, None, client_id, None, None)
        .unwrap();

    tab.handle_pty_bytes(1, Vec::from("Pane 1".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(2, Vec::from("Pane 2".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(3, Vec::from("Pane 3".as_bytes()))
        .unwrap();

    // Alt+press on first pane
    let press_effect = tab
        .handle_mouse_event(
            &MouseEvent::new_left_press_with_alt_event(Position::new(5, 10)),
            client_id,
        )
        .unwrap();
    assert!(press_effect.group_toggle.is_some());

    // Alt+drag to second pane
    let motion_effect_1 = tab
        .handle_mouse_event(
            &MouseEvent::new_left_motion_with_alt_event(Position::new(5, 40)),
            client_id,
        )
        .unwrap();

    // Verify group_add effect
    assert!(motion_effect_1.group_add.is_some());

    // Alt+drag to third pane
    let motion_effect_2 = tab
        .handle_mouse_event(
            &MouseEvent::new_left_motion_with_alt_event(Position::new(5, 80)),
            client_id,
        )
        .unwrap();

    // Verify group_add effect
    assert!(motion_effect_2.group_add.is_some());
}

#[test]
fn test_right_alt_click_ungroups_panes() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let _output = Output::default();

    // Create a vertical split
    tab.vertical_split(new_pane_id, None, client_id, None, None)
        .unwrap();

    tab.handle_pty_bytes(1, Vec::from("Left".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(2, Vec::from("Right".as_bytes()))
        .unwrap();

    // First, create a group with Alt+click
    tab.handle_mouse_event(
        &MouseEvent::new_left_press_with_alt_event(Position::new(5, 30)),
        client_id,
    )
    .unwrap();

    // Right+Alt click to ungroup
    let effect = tab
        .handle_mouse_event(
            &MouseEvent::new_right_press_with_alt_event(Position::new(5, 30)),
            client_id,
        )
        .unwrap();

    // Verify ungroup effect returned
    assert!(effect.ungroup);
}

#[test]
fn test_scroll_wheel_up_scrolls_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let mut output = Output::default();

    // Fill the pane with many lines to enable scrolling
    let mut content = String::new();
    for i in 0..50 {
        content.push_str(&format!("Line {}\r\n", i));
    }
    tab.handle_pty_bytes(1, Vec::from(content.as_bytes()))
        .unwrap();

    // Render before scroll
    tab.render(&mut output, None).unwrap();
    let snapshot_before = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    // Scroll up
    let _effect = tab
        .handle_mouse_event(
            &MouseEvent::new_scroll_up_event(Position::new(10, 60)),
            client_id,
        )
        .unwrap();

    // Render after scroll
    let mut output = Output::default();
    tab.render(&mut output, None).unwrap();
    let snapshot_after = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    // Snapshots should be different (viewport moved)
    assert_ne!(snapshot_before, snapshot_after);
    assert_snapshot!(format!("{}", snapshot_after));
}

#[test]
fn test_scroll_wheel_down_scrolls_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let _output = Output::default();

    // Fill the pane with many lines to enable scrolling
    let mut content = String::new();
    for i in 0..50 {
        content.push_str(&format!("Line {}\r\n", i));
    }
    tab.handle_pty_bytes(1, Vec::from(content.as_bytes()))
        .unwrap();

    // First scroll up to have content above viewport
    tab.handle_mouse_event(
        &MouseEvent::new_scroll_up_event(Position::new(10, 60)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_scroll_up_event(Position::new(10, 60)),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_scroll_up_event(Position::new(10, 60)),
        client_id,
    )
    .unwrap();

    // Render before scrolling down
    let mut output = Output::default();
    tab.render(&mut output, None).unwrap();
    let snapshot_before = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    // Scroll down
    let _effect = tab
        .handle_mouse_event(
            &MouseEvent::new_scroll_down_event(Position::new(10, 60)),
            client_id,
        )
        .unwrap();

    // Render after scroll
    let mut output = Output::default();
    tab.render(&mut output, None).unwrap();
    let snapshot_after = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    // Snapshots should be different (viewport moved)
    assert_ne!(snapshot_before, snapshot_after);
    assert_snapshot!(format!("{}", snapshot_after));
}

#[test]
fn test_scroll_on_inactive_pane_scrolls_that_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();

    // Create a vertical split
    tab.vertical_split(new_pane_id, None, client_id, None, None)
        .unwrap();

    // Fill both panes with scrollable content
    let mut content = String::new();
    for i in 0..50 {
        content.push_str(&format!("Left Line {}\r\n", i));
    }
    tab.handle_pty_bytes(1, Vec::from(content.as_bytes()))
        .unwrap();

    let mut content = String::new();
    for i in 0..50 {
        content.push_str(&format!("Right Line {}\r\n", i));
    }
    tab.handle_pty_bytes(2, Vec::from(content.as_bytes()))
        .unwrap();

    // Active pane is pane 2 (right), scroll on pane 1 (left)
    assert_eq!(tab.get_active_pane_id(client_id), Some(PaneId::Terminal(2)));

    // Render before scroll
    tab.render(&mut output, None).unwrap();
    let snapshot_before = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    // Scroll up on the left (inactive) pane
    let _effect = tab
        .handle_mouse_event(
            &MouseEvent::new_scroll_up_event(Position::new(10, 30)),
            client_id,
        )
        .unwrap();

    // Render after scroll
    let mut output = Output::default();
    tab.render(&mut output, None).unwrap();
    let snapshot_after = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    // Snapshots should be different (left pane scrolled)
    assert_ne!(snapshot_before, snapshot_after);
    assert_snapshot!(format!("{}", snapshot_after));
}

#[test]
fn test_right_click_forwards_to_active_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let _output = Output::default();

    tab.handle_pty_bytes(1, Vec::from("Active pane".as_bytes()))
        .unwrap();

    // Right click on the active pane
    let effect = tab
        .handle_mouse_event(
            &MouseEvent::new_right_press_event(Position::new(10, 60)),
            client_id,
        )
        .unwrap();

    // Event should be forwarded (verified via MouseEffect or no error)
    // The effect may not indicate state change, but should succeed
    assert!(effect.group_toggle.is_none());
}

#[test]
fn test_middle_click_forwards_to_active_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let _output = Output::default();

    tab.handle_pty_bytes(1, Vec::from("Active pane".as_bytes()))
        .unwrap();

    // Middle click on the active pane
    let effect = tab
        .handle_mouse_event(
            &MouseEvent::new_middle_press_event(Position::new(10, 60)),
            client_id,
        )
        .unwrap();

    // Event should be forwarded (verified via MouseEffect or no error)
    assert!(effect.group_toggle.is_none());
}

#[test]
fn test_hover_over_inactive_pane_sets_hover_state() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let _output = Output::default();

    // Create a vertical split
    tab.vertical_split(new_pane_id, None, client_id, None, None)
        .unwrap();

    tab.handle_pty_bytes(1, Vec::from("Left".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(2, Vec::from("Right".as_bytes()))
        .unwrap();

    // Active pane is pane 2 (right)
    assert_eq!(tab.get_active_pane_id(client_id), Some(PaneId::Terminal(2)));

    // Hover over inactive pane (left) with buttonless motion
    let effect = tab
        .handle_mouse_event(
            &MouseEvent::new_buttonless_motion(Position::new(10, 30)),
            client_id,
        )
        .unwrap();

    let hover_panes = tab.query_mouse_hover_pane_id();
    assert_snapshot!(format!("{:?}", hover_panes));
    assert!(effect.state_changed);
}

#[test]
fn test_hover_over_active_pane_unsets_hover() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let _output = Output::default();

    // Create a vertical split
    tab.vertical_split(new_pane_id, None, client_id, None, None)
        .unwrap();

    tab.handle_pty_bytes(1, Vec::from("Left".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(2, Vec::from("Right".as_bytes()))
        .unwrap();

    // Active pane is pane 2 (right)
    assert_eq!(tab.get_active_pane_id(client_id), Some(PaneId::Terminal(2)));

    // Hover over inactive pane (left) with buttonless motion
    let _effect = tab
        .handle_mouse_event(
            &MouseEvent::new_buttonless_motion(Position::new(10, 30)),
            client_id,
        )
        .unwrap();
    // Hover over active pane (right) with buttonless motion
    let effect = tab
        .handle_mouse_event(
            &MouseEvent::new_buttonless_motion(Position::new(10, 90)),
            client_id,
        )
        .unwrap();

    let hover_panes = tab.query_mouse_hover_pane_id();
    assert_snapshot!(format!("{:?}", hover_panes));
    assert!(effect.state_changed);
}

#[test]
fn test_resize_tiled_then_move_floating_sequence() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let tiled_pane_id = PaneId::Terminal(2);
    let floating_pane_id = PaneId::Terminal(3);
    let _output = Output::default();

    // Create tiled split
    tab.vertical_split(tiled_pane_id, None, client_id, None, None)
        .unwrap();

    tab.handle_pty_bytes(1, Vec::from("Tiled Left".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(2, Vec::from("Tiled Right".as_bytes()))
        .unwrap();

    // Resize tiled pane with Ctrl+drag
    let edge_position = Position::new(5, 60);
    tab.handle_mouse_event(
        &MouseEvent::new_left_press_with_ctrl_event(edge_position),
        client_id,
    )
    .unwrap();
    let motion_position = Position::new(5, 70);
    let resize_effect = tab
        .handle_mouse_event(
            &MouseEvent::new_left_motion_with_ctrl_event(motion_position),
            client_id,
        )
        .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_release_with_ctrl_event(motion_position),
        client_id,
    )
    .unwrap();

    assert!(resize_effect.state_changed);

    // Now toggle floating and create a floating pane
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        floating_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();

    tab.handle_pty_bytes(3, Vec::from("Floating".as_bytes()))
        .unwrap();

    // Move floating pane
    let frame_position = Position::new(5, 30);
    let move_effect = tab
        .handle_mouse_event(&MouseEvent::new_left_press_event(frame_position), client_id)
        .unwrap();
    let motion_position = Position::new(8, 40);
    tab.handle_mouse_event(
        &MouseEvent::new_left_motion_event(motion_position),
        client_id,
    )
    .unwrap();
    tab.handle_mouse_event(
        &MouseEvent::new_left_release_event(motion_position),
        client_id,
    )
    .unwrap();

    // Both operations should have succeeded
    assert!(move_effect.state_changed);

    // Render final state
    let mut output = Output::default();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_snapshot!(format!("{}", snapshot));
}

#[test]
fn test_alt_click_works_on_floating_panes() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let floating_pane_id = PaneId::Terminal(2);
    let _output = Output::default();

    // Toggle floating panes mode and create a floating pane
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        floating_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();

    tab.handle_pty_bytes(2, Vec::from("Floating pane".as_bytes()))
        .unwrap();

    // Alt+click on floating pane to toggle group
    let effect = tab
        .handle_mouse_event(
            &MouseEvent::new_left_press_with_alt_event(Position::new(10, 60)),
            client_id,
        )
        .unwrap();

    // Verify group_toggle effect returned
    assert!(effect.group_toggle.is_some());
    assert_eq!(effect.group_toggle, Some(PaneId::Terminal(2)));
}

#[test]
fn test_left_click_on_tiled_frame_edge_starts_resize() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let _output = Output::default();

    // Create a vertical split
    tab.vertical_split(new_pane_id, None, client_id, None, None)
        .unwrap();

    tab.handle_pty_bytes(1, Vec::from("Left".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(2, Vec::from("Right".as_bytes()))
        .unwrap();

    // Click on the edge between panes (without Ctrl)
    let edge_position = Position::new(5, 60);
    let effect = tab
        .handle_mouse_event(&MouseEvent::new_left_press_event(edge_position), client_id)
        .unwrap();

    // Edge detection should start resize even without Ctrl for tiled panes
    assert!(effect.state_changed);
}

#[test]
fn test_left_click_on_floating_frame_not_on_pin_moves_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let floating_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();

    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        floating_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();

    tab.handle_pty_bytes(2, Vec::from("Movable pane".as_bytes()))
        .unwrap();

    tab.render(&mut output, None).unwrap();
    let snapshot_before = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    let frame_position = Position::new(5, 30);
    let press_effect = tab
        .handle_mouse_event(&MouseEvent::new_left_press_event(frame_position), client_id)
        .unwrap();

    let motion_position = Position::new(6, 31);
    tab.handle_mouse_event(
        &MouseEvent::new_left_motion_event(motion_position),
        client_id,
    )
    .unwrap();

    tab.handle_mouse_event(
        &MouseEvent::new_left_release_event(motion_position),
        client_id,
    )
    .unwrap();

    assert!(press_effect.state_changed);

    let mut output = Output::default();
    tab.render(&mut output, None).unwrap();
    let snapshot_after = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_ne!(snapshot_before, snapshot_after);
    assert_snapshot!(format!("{}", snapshot_after));
}

#[test]
fn test_ctrl_scroll_up_on_active_tiled_pane_increases_pane_size() {
    let size = Size {
        cols: 120,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();

    tab.vertical_split(new_pane_id, None, client_id, None, None)
        .unwrap();

    tab.handle_pty_bytes(1, Vec::from("Left pane".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(2, Vec::from("Right pane".as_bytes()))
        .unwrap();

    tab.render(&mut output, None).unwrap();
    let snapshot_before = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    let active_pane_position = Position::new(5, 70);
    let effect = tab
        .handle_mouse_event(
            &MouseEvent::new_ctrl_scroll_up_event(active_pane_position),
            client_id,
        )
        .unwrap();

    assert!(effect.state_changed);

    let mut output = Output::default();
    tab.render(&mut output, None).unwrap();
    let snapshot_after = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_ne!(snapshot_before, snapshot_after);
    assert_snapshot!(format!("{}", snapshot_after));
}

#[test]
fn test_ctrl_scroll_down_on_active_tiled_pane_decreases_pane_size() {
    let size = Size {
        cols: 120,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();

    tab.vertical_split(new_pane_id, None, client_id, None, None)
        .unwrap();

    tab.handle_pty_bytes(1, Vec::from("Left pane".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(2, Vec::from("Right pane".as_bytes()))
        .unwrap();

    tab.render(&mut output, None).unwrap();
    let snapshot_before = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    let active_pane_position = Position::new(5, 70);
    let effect = tab
        .handle_mouse_event(
            &MouseEvent::new_ctrl_scroll_down_event(active_pane_position),
            client_id,
        )
        .unwrap();

    assert!(effect.state_changed);

    let mut output = Output::default();
    tab.render(&mut output, None).unwrap();
    let snapshot_after = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_ne!(snapshot_before, snapshot_after);
    assert_snapshot!(format!("{}", snapshot_after));
}

#[test]
fn test_ctrl_scroll_up_on_inactive_tiled_pane_increases_active_tiled_pane_size() {
    let size = Size {
        cols: 120,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();

    tab.vertical_split(new_pane_id, None, client_id, None, None)
        .unwrap();

    tab.handle_pty_bytes(1, Vec::from("Left pane".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(2, Vec::from("Right pane".as_bytes()))
        .unwrap();

    tab.render(&mut output, None).unwrap();
    let snapshot_before = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    let inactive_pane_position = Position::new(5, 10);
    let effect = tab
        .handle_mouse_event(
            &MouseEvent::new_ctrl_scroll_up_event(inactive_pane_position),
            client_id,
        )
        .unwrap();

    assert!(effect.state_changed);

    let mut output = Output::default();
    tab.render(&mut output, None).unwrap();
    let snapshot_after = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_ne!(snapshot_before, snapshot_after);
    assert_snapshot!(format!("{}", snapshot_after));
}

#[test]
fn test_ctrl_scroll_down_on_inactive_tiled_pane_decreases_active_tiled_pane_size() {
    let size = Size {
        cols: 120,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();

    tab.vertical_split(new_pane_id, None, client_id, None, None)
        .unwrap();

    tab.handle_pty_bytes(1, Vec::from("Left pane".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(2, Vec::from("Right pane".as_bytes()))
        .unwrap();

    tab.render(&mut output, None).unwrap();
    let snapshot_before = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    let inactive_pane_position = Position::new(5, 10);
    let effect = tab
        .handle_mouse_event(
            &MouseEvent::new_ctrl_scroll_down_event(inactive_pane_position),
            client_id,
        )
        .unwrap();

    assert!(effect.state_changed);

    let mut output = Output::default();
    tab.render(&mut output, None).unwrap();
    let snapshot_after = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_ne!(snapshot_before, snapshot_after);
    assert_snapshot!(format!("{}", snapshot_after));
}

#[test]
fn test_ctrl_scroll_up_increases_floating_pane_size() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();

    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();

    tab.handle_pty_bytes(1, Vec::from("Tiled pane".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(2, Vec::from("Floating pane".as_bytes()))
        .unwrap();

    tab.render(&mut output, None).unwrap();
    let snapshot_before = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    let floating_pane_position = Position::new(5, 50);
    let effect = tab
        .handle_mouse_event(
            &MouseEvent::new_ctrl_scroll_up_event(floating_pane_position),
            client_id,
        )
        .unwrap();

    assert!(effect.state_changed);

    let mut output = Output::default();
    tab.render(&mut output, None).unwrap();
    let snapshot_after = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_ne!(snapshot_before, snapshot_after);
    assert_snapshot!(format!("{}", snapshot_after));
}

#[test]
fn test_ctrl_scroll_down_decreases_floating_pane_size() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();

    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        new_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();

    tab.handle_pty_bytes(1, Vec::from("Tiled pane".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(2, Vec::from("Floating pane".as_bytes()))
        .unwrap();

    tab.render(&mut output, None).unwrap();
    let snapshot_before = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    let floating_pane_position = Position::new(5, 50);
    let effect = tab
        .handle_mouse_event(
            &MouseEvent::new_ctrl_scroll_down_event(floating_pane_position),
            client_id,
        )
        .unwrap();

    assert!(effect.state_changed);

    let mut output = Output::default();
    tab.render(&mut output, None).unwrap();
    let snapshot_after = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_ne!(snapshot_before, snapshot_after);
    assert_snapshot!(format!("{}", snapshot_after));
}

#[test]
fn test_ctrl_scroll_up_increases_pinned_floating_pane_size_when_floating_panes_hidden() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();

    let coordinates = FloatingPaneCoordinates {
        x: Some(PercentOrFixed::Fixed(5)),
        y: Some(PercentOrFixed::Fixed(5)),
        width: Some(PercentOrFixed::Fixed(30)),
        height: Some(PercentOrFixed::Fixed(10)),
        pinned: Some(true),
        borderless: Some(false),
    };

    tab.new_pane(
        new_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::Floating(Some(coordinates)),
        Some(client_id),
        None,
    )
    .unwrap();

    tab.handle_pty_bytes(1, Vec::from("Tiled pane".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(2, Vec::from("Floating pane".as_bytes()))
        .unwrap();

    tab.render(&mut output, None).unwrap();
    let snapshot_before = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    let floating_pane_position = Position::new(10, 10);
    let effect = tab
        .handle_mouse_event(
            &MouseEvent::new_ctrl_scroll_up_event(floating_pane_position),
            client_id,
        )
        .unwrap();

    assert!(effect.state_changed);

    let mut output = Output::default();
    tab.render(&mut output, None).unwrap();
    let snapshot_after = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_ne!(snapshot_before, snapshot_after);
    assert_snapshot!(format!("{}", snapshot_after));
}

#[test]
fn test_ctrl_scroll_down_decreases_pinned_floating_pane_size_when_floating_panes_hidden() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();

    let coordinates = FloatingPaneCoordinates {
        x: Some(PercentOrFixed::Fixed(5)),
        y: Some(PercentOrFixed::Fixed(5)),
        width: Some(PercentOrFixed::Fixed(30)),
        height: Some(PercentOrFixed::Fixed(10)),
        pinned: Some(true),
        borderless: Some(false),
    };

    tab.new_pane(
        new_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::Floating(Some(coordinates)),
        Some(client_id),
        None,
    )
    .unwrap();

    tab.handle_pty_bytes(1, Vec::from("Tiled pane".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(2, Vec::from("Floating pane".as_bytes()))
        .unwrap();

    tab.render(&mut output, None).unwrap();
    let snapshot_before = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    let floating_pane_position = Position::new(6, 10);
    let effect = tab
        .handle_mouse_event(
            &MouseEvent::new_ctrl_scroll_down_event(floating_pane_position),
            client_id,
        )
        .unwrap();

    assert!(effect.state_changed);

    let mut output = Output::default();
    tab.render(&mut output, None).unwrap();
    let snapshot_after = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );

    assert_ne!(snapshot_before, snapshot_after);
    eprintln!("{}", snapshot_before);
    eprintln!("{}", snapshot_after);
    assert_snapshot!(format!("{}", snapshot_after));
}

#[test]
fn in_place_pane_with_close_replaced_pane_false_restores_original() {
    // When an in-place pane is closed and close_replaced_pane=false (the default),
    // the pane that was replaced is restored to its original position.
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let mut output = Output::default();

    // Create a second tiled pane so closing the in-place pane doesn't leave an empty tab
    tab.new_pane(
        PaneId::Terminal(2),
        None,
        None,
        false,
        false,
        NewPanePlacement::Tiled {
            direction: None,
            borderless: None,
        },
        Some(client_id),
        None,
    )
    .unwrap();

    // Write distinguishing content to each pane
    tab.handle_pty_bytes(1, Vec::from("\n\n\nI am pane one".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(2, Vec::from("\n\n\nI am pane two".as_bytes()))
        .unwrap();

    // Open pane 3 in-place of pane 2 without closing the replaced pane (suppress it)
    tab.new_in_place_pane(
        PaneId::Terminal(3),
        None,
        None,
        Some(PaneId::Terminal(2)),
        false, // close_replaced_pane
        Some(client_id),
        None,
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(3, Vec::from("\n\n\nI am the in-place pane".as_bytes()))
        .unwrap();

    // Close the in-place pane — pane 2 should be restored
    tab.close_pane(PaneId::Terminal(3), false, None);

    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    // Snapshot shows both pane 1 and the restored pane 2
    assert_snapshot!(snapshot);
}

#[test]
fn in_place_pane_with_close_replaced_pane_true_closes_original() {
    // When an in-place pane is closed and close_replaced_pane=true,
    // the replaced pane is permanently destroyed rather than restored.
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let mut output = Output::default();

    // Create a second tiled pane
    tab.new_pane(
        PaneId::Terminal(2),
        None,
        None,
        false,
        false,
        NewPanePlacement::Tiled {
            direction: None,
            borderless: None,
        },
        Some(client_id),
        None,
    )
    .unwrap();

    tab.handle_pty_bytes(1, Vec::from("\n\n\nI am pane one".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(2, Vec::from("\n\n\nI am pane two".as_bytes()))
        .unwrap();

    // Open pane 3 in-place of pane 2, closing the replaced pane permanently
    tab.new_in_place_pane(
        PaneId::Terminal(3),
        None,
        None,
        Some(PaneId::Terminal(2)),
        true, // close_replaced_pane
        Some(client_id),
        None,
        None,
    )
    .unwrap();
    tab.handle_pty_bytes(3, Vec::from("\n\n\nI am the in-place pane".as_bytes()))
        .unwrap();

    // Close the in-place pane — pane 2 should NOT be restored (it was closed)
    tab.close_pane(PaneId::Terminal(3), false, None);

    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    // Snapshot shows only pane 1 (pane 2 was permanently closed, not restored)
    assert_snapshot!(snapshot);
}

// =====================================================================
// Plugin Highlight Mouse Integration Tests
// =====================================================================

use zellij_utils::data::{HighlightLayer, HighlightStyle, RegexHighlight};

fn create_new_tab_with_plugin_receiver(
    size: Size,
    default_mode: ModeInfo,
) -> (Tab, Receiver<(PluginInstruction, ErrorContext)>) {
    set_session_name("test".into());
    let index = 0;
    let position = 0;
    let name = String::new();
    let os_api = Box::new(FakeInputOutput::default());
    let mut senders = ThreadSenders::default().silently_fail_on_send();
    let (mock_plugin_sender, mock_plugin_receiver): ChannelWithContext<PluginInstruction> =
        channels::unbounded();
    senders.replace_to_plugin(SenderWithContext::new(mock_plugin_sender));
    let max_panes = None;
    let mode_info = default_mode;
    let style = Style::default();
    let draw_pane_frames = true;
    let auto_layout = true;
    let client_id = 1;
    let session_is_mirrored = true;
    let mut connected_clients = HashMap::new();
    connected_clients.insert(client_id, false);
    let connected_clients = Rc::new(RefCell::new(connected_clients));
    let character_cell_info = Rc::new(RefCell::new(None));
    let stacked_resize = Rc::new(RefCell::new(true));
    let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
    let copy_options = CopyOptions::default();
    let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
    let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
    let current_group = Rc::new(RefCell::new(PaneGroups::new(ThreadSenders::default())));
    let currently_marking_pane_group = Rc::new(RefCell::new(HashMap::new()));
    let debug = false;
    let arrow_fonts = true;
    let styled_underlines = true;
    let osc8_hyperlinks = true;
    let explicitly_disable_kitty_keyboard_protocol = false;
    let advanced_mouse_actions = true;
    let web_sharing = WebSharing::Off;
    let web_server_ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
    let web_server_port = 8080;
    let mut tab = Tab::new(
        index,
        position,
        name,
        size,
        character_cell_info,
        stacked_resize,
        sixel_image_store,
        os_api,
        senders,
        max_panes,
        style,
        mode_info,
        draw_pane_frames,
        auto_layout,
        connected_clients,
        session_is_mirrored,
        Some(client_id),
        copy_options,
        terminal_emulator_colors,
        terminal_emulator_color_codes,
        (vec![], vec![]),
        PathBuf::from("my_default_shell"),
        debug,
        arrow_fonts,
        styled_underlines,
        osc8_hyperlinks,
        explicitly_disable_kitty_keyboard_protocol,
        None,
        false,
        web_sharing,
        current_group,
        currently_marking_pane_group,
        advanced_mouse_actions,
        true,  // mouse_hover_effects
        false, // focus_follows_mouse
        false, // mouse_click_through
        web_server_ip,
        web_server_port,
    );
    tab.apply_layout(
        TiledPaneLayout::default(),
        vec![],
        vec![(1, None)],
        vec![],
        HashMap::new(),
        client_id,
        None,
    )
    .unwrap();
    (tab, mock_plugin_receiver)
}

#[test]
fn click_on_plugin_highlight_sends_highlight_clicked() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let (mut tab, mock_plugin_receiver) =
        create_new_tab_with_plugin_receiver(size, ModeInfo::default());

    // Feed PTY bytes to pane 1
    tab.handle_pty_bytes(1, Vec::from("click here foo bar".as_bytes()))
        .unwrap();

    // Set plugin highlight on pane 1 for pattern "foo"
    let highlights = vec![RegexHighlight {
        pattern: "foo".into(),
        style: HighlightStyle::Emphasis0,
        layer: HighlightLayer::Hint,
        context: BTreeMap::new(),
        on_hover: false,
        bold: false,
        italic: false,
        underline: true,
        tooltip_text: None,
    }];
    tab.set_plugin_regex_highlights_for_pane(
        PaneId::Terminal(1),
        42,
        highlights,
        &Style::default(),
    );

    // Alt+Click at position where "foo" appears
    // With draw_pane_frames=true, content starts at row 1, col 1
    // "click here foo bar" -> "foo" starts at offset 11, so col = 1 + 11 = 12
    let click_position = Position::new(1, 12);
    let mut alt_click = MouseEvent::new_left_press_event(click_position);
    alt_click.alt = true;
    let effect = tab.handle_mouse_event(&alt_click, client_id).unwrap();

    assert!(effect.state_changed);

    // Check that HighlightClicked was sent to the plugin channel
    let mut found_highlight_clicked = false;
    while let Ok((instruction, _ctx)) = mock_plugin_receiver.try_recv() {
        if let PluginInstruction::HighlightClicked {
            plugin_id,
            pane_id,
            pattern,
            matched_string,
            ..
        } = instruction
        {
            assert_eq!(plugin_id, 42);
            assert_eq!(pane_id, PaneId::Terminal(1));
            assert_eq!(pattern, "foo");
            assert_eq!(matched_string, "foo");
            found_highlight_clicked = true;
            break;
        }
    }
    assert!(
        found_highlight_clicked,
        "Expected HighlightClicked instruction to be sent"
    );
}

#[test]
fn click_outside_highlight_starts_normal_selection() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let (mut tab, mock_plugin_receiver) =
        create_new_tab_with_plugin_receiver(size, ModeInfo::default());

    tab.handle_pty_bytes(1, Vec::from("click here foo bar".as_bytes()))
        .unwrap();

    let highlights = vec![RegexHighlight {
        pattern: "foo".into(),
        style: HighlightStyle::Emphasis0,
        layer: HighlightLayer::Hint,
        context: BTreeMap::new(),
        on_hover: false,
        bold: false,
        italic: false,
        underline: true,
        tooltip_text: None,
    }];
    tab.set_plugin_regex_highlights_for_pane(
        PaneId::Terminal(1),
        42,
        highlights,
        &Style::default(),
    );

    // Click on "click" (col 1), not on "foo"
    let click_position = Position::new(1, 1);
    let _effect = tab
        .handle_mouse_event(&MouseEvent::new_left_press_event(click_position), client_id)
        .unwrap();

    // No HighlightClicked should be sent
    let mut found_highlight_clicked = false;
    while let Ok((instruction, _ctx)) = mock_plugin_receiver.try_recv() {
        if matches!(instruction, PluginInstruction::HighlightClicked { .. }) {
            found_highlight_clicked = true;
        }
    }
    assert!(
        !found_highlight_clicked,
        "HighlightClicked should NOT be sent for miss"
    );

    // Selection should have started
    assert!(tab.selecting_with_mouse_in_pane.is_some());
}

#[test]
fn hover_over_highlight_shows_styling() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let mut output = Output::default();

    tab.handle_pty_bytes(1, Vec::from("hello link_text bar".as_bytes()))
        .unwrap();

    let highlights = vec![RegexHighlight {
        pattern: "link_text".into(),
        style: HighlightStyle::None,
        layer: HighlightLayer::Hint,
        context: BTreeMap::new(),
        on_hover: true,
        bold: false,
        italic: true,
        underline: true,
        tooltip_text: None,
    }];
    tab.set_plugin_regex_highlights_for_pane(PaneId::Terminal(1), 1, highlights, &Style::default());

    // Hover over the match (link_text starts at offset 6, with frame offset col = 7)
    let hover_position = Position::new(1, 9);
    let _effect = tab
        .handle_mouse_event(
            &MouseEvent::new_buttonless_motion(hover_position),
            client_id,
        )
        .unwrap();

    tab.render(&mut output, None).unwrap();
    let snapshot_with_hover = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot_with_hover);

    // Move hover away from the match
    let mut output2 = Output::default();
    let far_position = Position::new(1, 1);
    let _effect2 = tab
        .handle_mouse_event(&MouseEvent::new_buttonless_motion(far_position), client_id)
        .unwrap();

    tab.render(&mut output2, None).unwrap();
    let snapshot_without_hover = take_snapshot(
        output2.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot_without_hover);
}

#[test]
fn hover_on_unfocused_pane_no_highlight() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);
    let mut output = Output::default();

    // Create a vertical split
    tab.vertical_split(new_pane_id, None, client_id, None, None)
        .unwrap();

    tab.handle_pty_bytes(1, Vec::from("link_text".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(2, Vec::from("right pane".as_bytes()))
        .unwrap();

    // Active pane is pane 2 (right)
    assert_eq!(tab.get_active_pane_id(client_id), Some(PaneId::Terminal(2)));

    // Set on_hover highlight on pane 1 (left, unfocused)
    let highlights = vec![RegexHighlight {
        pattern: "link_text".into(),
        style: HighlightStyle::None,
        layer: HighlightLayer::Hint,
        context: BTreeMap::new(),
        on_hover: true,
        bold: false,
        italic: true,
        underline: true,
        tooltip_text: None,
    }];
    tab.set_plugin_regex_highlights_for_pane(PaneId::Terminal(1), 1, highlights, &Style::default());

    // Hover over left pane (unfocused)
    let hover_position = Position::new(1, 5);
    let _effect = tab
        .handle_mouse_event(
            &MouseEvent::new_buttonless_motion(hover_position),
            client_id,
        )
        .unwrap();

    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    // Hover highlight should NOT be visible on the unfocused pane
    assert_snapshot!(snapshot);
}

#[test]
fn highlight_click_in_mouse_mode_pane_suppressed() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let (mut tab, mock_plugin_receiver) =
        create_new_tab_with_plugin_receiver(size, ModeInfo::default());

    // Feed PTY bytes that enable mouse tracking (SGR mode + any-event tracking)
    tab.handle_pty_bytes(1, Vec::from("\x1b[?1006h\x1b[?1003h".as_bytes()))
        .unwrap();
    // Then feed text with matchable content
    tab.handle_pty_bytes(1, Vec::from("foo bar baz".as_bytes()))
        .unwrap();

    // Set plugin highlight
    let highlights = vec![RegexHighlight {
        pattern: "foo".into(),
        style: HighlightStyle::Emphasis0,
        layer: HighlightLayer::Hint,
        context: BTreeMap::new(),
        on_hover: false,
        bold: false,
        italic: false,
        underline: true,
        tooltip_text: None,
    }];
    tab.set_plugin_regex_highlights_for_pane(
        PaneId::Terminal(1),
        42,
        highlights,
        &Style::default(),
    );

    // Left-click on the highlight position
    // With mouse tracking, the click goes through SendToTerminal, not StartSelection
    let click_position = Position::new(1, 1);
    let _effect = tab
        .handle_mouse_event(&MouseEvent::new_left_press_event(click_position), client_id)
        .unwrap();

    // No HighlightClicked should be sent (the click is forwarded to the terminal)
    let mut found_highlight_clicked = false;
    while let Ok((instruction, _ctx)) = mock_plugin_receiver.try_recv() {
        if matches!(instruction, PluginInstruction::HighlightClicked { .. }) {
            found_highlight_clicked = true;
        }
    }
    assert!(
        !found_highlight_clicked,
        "HighlightClicked should NOT be sent when terminal has mouse tracking"
    );
}

#[test]
fn set_and_clear_highlights_across_tiled_and_floating() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let pane2 = PaneId::Terminal(2);
    let pane3 = PaneId::Terminal(3);

    // Create a second tiled pane
    tab.vertical_split(pane2, None, client_id, None, None)
        .unwrap();

    // Create a floating pane
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        pane3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();

    // Feed content to all panes
    tab.handle_pty_bytes(1, Vec::from("aaa bbb".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(2, Vec::from("aaa bbb".as_bytes()))
        .unwrap();
    tab.handle_pty_bytes(3, Vec::from("aaa bbb".as_bytes()))
        .unwrap();

    // Set highlights from plugin 1 (pattern "aaa") on all panes
    let h1 = vec![RegexHighlight {
        pattern: "aaa".into(),
        style: HighlightStyle::Emphasis0,
        layer: HighlightLayer::Hint,
        context: BTreeMap::new(),
        on_hover: false,
        bold: false,
        italic: false,
        underline: true,
        tooltip_text: None,
    }];
    tab.set_plugin_regex_highlights_for_pane(PaneId::Terminal(1), 1, h1.clone(), &Style::default());
    tab.set_plugin_regex_highlights_for_pane(pane2, 1, h1.clone(), &Style::default());
    tab.set_plugin_regex_highlights_for_pane(pane3, 1, h1.clone(), &Style::default());

    // Set highlights from plugin 2 (pattern "bbb") on all panes
    let h2 = vec![RegexHighlight {
        pattern: "bbb".into(),
        style: HighlightStyle::Emphasis1,
        layer: HighlightLayer::Hint,
        context: BTreeMap::new(),
        on_hover: false,
        bold: false,
        italic: false,
        underline: false,
        tooltip_text: None,
    }];
    tab.set_plugin_regex_highlights_for_pane(PaneId::Terminal(1), 2, h2.clone(), &Style::default());
    tab.set_plugin_regex_highlights_for_pane(pane2, 2, h2.clone(), &Style::default());
    tab.set_plugin_regex_highlights_for_pane(pane3, 2, h2.clone(), &Style::default());

    // Clear plugin 1 highlights across all panes
    tab.clear_all_plugin_highlights(1);

    // Verify plugin 2 highlights remain — we can use the tab render to snapshot,
    // or directly check that plugin 2 pattern still matches on pane 1
    // Since clear_all_plugin_highlights iterates all panes, we test via
    // the set_plugin_regex_highlights_for_pane / clear flow:
    // Plugin 2 "bbb" highlights should still be active.
    let mut output = Output::default();
    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot);
}

#[test]
fn plain_click_on_highlight_does_not_send_highlight_clicked() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let (mut tab, mock_plugin_receiver) =
        create_new_tab_with_plugin_receiver(size, ModeInfo::default());

    tab.handle_pty_bytes(1, Vec::from("click here foo bar".as_bytes()))
        .unwrap();

    let highlights = vec![RegexHighlight {
        pattern: "foo".into(),
        style: HighlightStyle::Emphasis0,
        layer: HighlightLayer::Hint,
        context: BTreeMap::new(),
        on_hover: false,
        bold: false,
        italic: false,
        underline: true,
        tooltip_text: None,
    }];
    tab.set_plugin_regex_highlights_for_pane(
        PaneId::Terminal(1),
        42,
        highlights,
        &Style::default(),
    );

    // Plain left-click on "foo" — should start selection, NOT send HighlightClicked
    let click_position = Position::new(1, 12);
    let _effect = tab
        .handle_mouse_event(&MouseEvent::new_left_press_event(click_position), client_id)
        .unwrap();

    let mut found_highlight_clicked = false;
    while let Ok((instruction, _ctx)) = mock_plugin_receiver.try_recv() {
        if matches!(instruction, PluginInstruction::HighlightClicked { .. }) {
            found_highlight_clicked = true;
        }
    }
    assert!(
        !found_highlight_clicked,
        "Plain click should NOT send HighlightClicked after Alt+Click change"
    );

    // Selection should have started instead
    assert!(tab.selecting_with_mouse_in_pane.is_some());
}

#[test]
fn alt_click_outside_highlight_falls_through_to_group_toggle() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let (mut tab, mock_plugin_receiver) =
        create_new_tab_with_plugin_receiver(size, ModeInfo::default());

    tab.handle_pty_bytes(1, Vec::from("click here foo bar".as_bytes()))
        .unwrap();

    let highlights = vec![RegexHighlight {
        pattern: "foo".into(),
        style: HighlightStyle::Emphasis0,
        layer: HighlightLayer::Hint,
        context: BTreeMap::new(),
        on_hover: false,
        bold: false,
        italic: false,
        underline: true,
        tooltip_text: None,
    }];
    tab.set_plugin_regex_highlights_for_pane(
        PaneId::Terminal(1),
        42,
        highlights,
        &Style::default(),
    );

    // Alt+Click on "click" (col 1), not on "foo" — should fall through to group toggle
    let click_position = Position::new(1, 1);
    let mut alt_click = MouseEvent::new_left_press_event(click_position);
    alt_click.alt = true;
    let effect = tab.handle_mouse_event(&alt_click, client_id).unwrap();

    // No HighlightClicked should be sent
    let mut found_highlight_clicked = false;
    while let Ok((instruction, _ctx)) = mock_plugin_receiver.try_recv() {
        if matches!(instruction, PluginInstruction::HighlightClicked { .. }) {
            found_highlight_clicked = true;
        }
    }
    assert!(
        !found_highlight_clicked,
        "Alt+Click outside highlight should NOT send HighlightClicked"
    );

    // Group toggle effect should have been produced
    assert_eq!(effect.group_toggle, Some(PaneId::Terminal(1)));
}

#[test]
fn hover_over_highlight_with_tooltip_caches_tooltip() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let mut output = Output::default();

    tab.handle_pty_bytes(1, Vec::from("hello link_text bar".as_bytes()))
        .unwrap();

    let highlights = vec![RegexHighlight {
        pattern: "link_text".into(),
        style: HighlightStyle::None,
        layer: HighlightLayer::Hint,
        context: BTreeMap::new(),
        on_hover: true,
        bold: false,
        italic: true,
        underline: true,
        tooltip_text: Some("Open".to_string()),
    }];
    tab.set_plugin_regex_highlights_for_pane(PaneId::Terminal(1), 1, highlights, &Style::default());

    // Hover over the match (link_text starts at offset 6, with frame offset col = 7)
    let hover_position = Position::new(1, 9);
    let _effect = tab
        .handle_mouse_event(
            &MouseEvent::new_buttonless_motion(hover_position),
            client_id,
        )
        .unwrap();

    tab.render(&mut output, None).unwrap();
    let snapshot_with_tooltip = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    // The bottom frame row should contain the tooltip text
    assert_snapshot!(snapshot_with_tooltip);

    // Move hover away — tooltip should disappear
    let mut output2 = Output::default();
    let far_position = Position::new(1, 1);
    let _effect2 = tab
        .handle_mouse_event(&MouseEvent::new_buttonless_motion(far_position), client_id)
        .unwrap();

    tab.render(&mut output2, None).unwrap();
    let snapshot_without_tooltip = take_snapshot(
        output2.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    assert_snapshot!(snapshot_without_tooltip);
}

// =====================================================================
// Highlight Layer Priority Integration Tests
// =====================================================================

#[test]
fn higher_layer_highlight_style_wins_in_rendered_output() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let mut output = Output::default();

    tab.handle_pty_bytes(1, Vec::from("overlap_text here\n".as_bytes()))
        .unwrap();

    // Plugin 1 (id: 10): Hint layer, underline only
    let h1 = vec![RegexHighlight {
        pattern: "overlap_text".into(),
        style: HighlightStyle::Emphasis0,
        layer: HighlightLayer::Hint,
        context: BTreeMap::new(),
        on_hover: false,
        bold: false,
        italic: false,
        underline: true,
        tooltip_text: None,
    }];
    // Plugin 2 (id: 20): Tool layer, bold only
    let h2 = vec![RegexHighlight {
        pattern: "overlap_text".into(),
        style: HighlightStyle::Emphasis1,
        layer: HighlightLayer::Tool,
        context: BTreeMap::new(),
        on_hover: false,
        bold: true,
        italic: false,
        underline: false,
        tooltip_text: None,
    }];
    tab.set_plugin_regex_highlights_for_pane(PaneId::Terminal(1), 10, h1, &Style::default());
    tab.set_plugin_regex_highlights_for_pane(PaneId::Terminal(1), 20, h2, &Style::default());

    tab.render(&mut output, None).unwrap();
    let raw_output = output.serialize().unwrap();
    let raw_str = raw_output.get(&client_id).unwrap();

    // The renderer emits a series of individual SGR codes before the
    // highlighted text.  Extract the full escape prefix preceding
    // "overlap_text" and verify that bold (\x1b[1m) is present and
    // underline (\x1b[4m) is absent.
    let idx = raw_str
        .find("overlap_text")
        .expect("overlap_text should appear in raw output");
    let preceding = &raw_str[..idx];
    // Walk backwards to find the reset (\x1b[m) that starts this cell's styling
    let reset_pos = preceding
        .rfind("\x1b[m")
        .expect("should find a reset before overlap_text");
    let sgr_block = &raw_str[reset_pos..idx];

    assert!(
        sgr_block.contains("\x1b[1m"),
        "Tool layer bold styling (\\x1b[1m) should be present before overlap_text, got: {:?}",
        sgr_block
    );
    assert!(
        !sgr_block.contains("\x1b[4m"),
        "Hint layer underline styling (\\x1b[4m) should NOT be present before overlap_text, got: {:?}",
        sgr_block
    );
}

#[test]
fn click_dispatches_to_higher_layer_plugin() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let (mut tab, mock_plugin_receiver) =
        create_new_tab_with_plugin_receiver(size, ModeInfo::default());

    tab.handle_pty_bytes(1, Vec::from("click here shared_text bar\n".as_bytes()))
        .unwrap();

    // Plugin 1 (id: 10): Hint layer
    let h1 = vec![RegexHighlight {
        pattern: "shared_text".into(),
        style: HighlightStyle::Emphasis0,
        layer: HighlightLayer::Hint,
        context: BTreeMap::new(),
        on_hover: false,
        bold: false,
        italic: false,
        underline: true,
        tooltip_text: None,
    }];
    // Plugin 2 (id: 20): Tool layer
    let h2 = vec![RegexHighlight {
        pattern: "shared_text".into(),
        style: HighlightStyle::Emphasis1,
        layer: HighlightLayer::Tool,
        context: BTreeMap::new(),
        on_hover: false,
        bold: false,
        italic: false,
        underline: true,
        tooltip_text: None,
    }];
    tab.set_plugin_regex_highlights_for_pane(PaneId::Terminal(1), 10, h1, &Style::default());
    tab.set_plugin_regex_highlights_for_pane(PaneId::Terminal(1), 20, h2, &Style::default());

    // Alt+Click on "shared_text" (starts at offset 11, frame adds +1 => col 12)
    let click_position = Position::new(1, 12);
    let mut alt_click = MouseEvent::new_left_press_event(click_position);
    alt_click.alt = true;
    let _effect = tab.handle_mouse_event(&alt_click, client_id).unwrap();

    // Drain the plugin receiver and find the HighlightClicked instruction
    let mut found_plugin_id = None;
    while let Ok((instruction, _ctx)) = mock_plugin_receiver.try_recv() {
        if let PluginInstruction::HighlightClicked { plugin_id, .. } = instruction {
            found_plugin_id = Some(plugin_id);
        }
    }
    assert_eq!(
        found_plugin_id,
        Some(20),
        "HighlightClicked should be dispatched to the Tool layer plugin (id: 20)"
    );
}

#[test]
fn hover_tooltip_shows_higher_layer_tooltip() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let mut output = Output::default();

    tab.handle_pty_bytes(1, Vec::from("hover here tooltipped bar\n".as_bytes()))
        .unwrap();

    // Plugin 1 (id: 10): Hint layer with tooltip
    let h1 = vec![RegexHighlight {
        pattern: "tooltipped".into(),
        style: HighlightStyle::None,
        layer: HighlightLayer::Hint,
        context: BTreeMap::new(),
        on_hover: true,
        bold: false,
        italic: true,
        underline: true,
        tooltip_text: Some("Hint Tooltip".to_string()),
    }];
    // Plugin 2 (id: 20): Tool layer with tooltip
    let h2 = vec![RegexHighlight {
        pattern: "tooltipped".into(),
        style: HighlightStyle::None,
        layer: HighlightLayer::Tool,
        context: BTreeMap::new(),
        on_hover: true,
        bold: false,
        italic: true,
        underline: true,
        tooltip_text: Some("Tool Tooltip".to_string()),
    }];
    tab.set_plugin_regex_highlights_for_pane(PaneId::Terminal(1), 10, h1, &Style::default());
    tab.set_plugin_regex_highlights_for_pane(PaneId::Terminal(1), 20, h2, &Style::default());

    // Hover over "tooltipped" (starts at offset 11, frame adds +1 => col 12)
    let hover_position = Position::new(1, 12);
    let _effect = tab
        .handle_mouse_event(
            &MouseEvent::new_buttonless_motion(hover_position),
            client_id,
        )
        .unwrap();

    tab.render(&mut output, None).unwrap();
    let snapshot = take_snapshot(
        output.serialize().unwrap().get(&client_id).unwrap(),
        size.rows,
        size.cols,
        Palette::default(),
    );
    // The bottom frame row should contain "Tool Tooltip" from the Tool layer
    assert_snapshot!(snapshot);
}

#[test]
fn non_overlapping_highlights_from_different_layers_coexist() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let mut output = Output::default();

    tab.handle_pty_bytes(1, Vec::from("aaa bbb ccc\n".as_bytes()))
        .unwrap();

    // Plugin 1 (id: 10): Hint layer, underline on "aaa"
    let h1 = vec![RegexHighlight {
        pattern: "aaa".into(),
        style: HighlightStyle::None,
        layer: HighlightLayer::Hint,
        context: BTreeMap::new(),
        on_hover: false,
        bold: false,
        italic: false,
        underline: true,
        tooltip_text: None,
    }];
    // Plugin 2 (id: 20): Tool layer, bold on "bbb"
    let h2 = vec![RegexHighlight {
        pattern: "bbb".into(),
        style: HighlightStyle::None,
        layer: HighlightLayer::Tool,
        context: BTreeMap::new(),
        on_hover: false,
        bold: true,
        italic: false,
        underline: false,
        tooltip_text: None,
    }];
    // Plugin 3 (id: 30): ActionFeedback layer, italic on "ccc"
    let h3 = vec![RegexHighlight {
        pattern: "ccc".into(),
        style: HighlightStyle::None,
        layer: HighlightLayer::ActionFeedback,
        context: BTreeMap::new(),
        on_hover: false,
        bold: false,
        italic: true,
        underline: false,
        tooltip_text: None,
    }];
    tab.set_plugin_regex_highlights_for_pane(PaneId::Terminal(1), 10, h1, &Style::default());
    tab.set_plugin_regex_highlights_for_pane(PaneId::Terminal(1), 20, h2, &Style::default());
    tab.set_plugin_regex_highlights_for_pane(PaneId::Terminal(1), 30, h3, &Style::default());

    tab.render(&mut output, None).unwrap();
    let raw_output = output.serialize().unwrap();
    let raw_str = raw_output.get(&client_id).unwrap();

    // The renderer emits individual SGR codes in sequence before each
    // highlighted word.  Extract the escape prefix block before each word
    // and verify the expected attribute is present.

    // Helper: extract the SGR block between the last \x1b[m reset and the text
    let sgr_before = |text: &str| -> String {
        let idx = raw_str
            .find(text)
            .unwrap_or_else(|| panic!("{} not found in raw output", text));
        let preceding = &raw_str[..idx];
        let reset_pos = preceding
            .rfind("\x1b[m")
            .unwrap_or_else(|| panic!("no reset before {}", text));
        raw_str[reset_pos..idx].to_string()
    };

    let aaa_sgr = sgr_before("aaa");
    let bbb_sgr = sgr_before("bbb");
    let ccc_sgr = sgr_before("ccc");

    assert!(
        aaa_sgr.contains("\x1b[4m"),
        "Hint layer underline (\\x1b[4m) should be applied to 'aaa', got: {:?}",
        aaa_sgr
    );
    assert!(
        bbb_sgr.contains("\x1b[1m"),
        "Tool layer bold (\\x1b[1m) should be applied to 'bbb', got: {:?}",
        bbb_sgr
    );
    assert!(
        ccc_sgr.contains("\x1b[3m"),
        "ActionFeedback layer italic (\\x1b[3m) should be applied to 'ccc', got: {:?}",
        ccc_sgr
    );
}

#[test]
fn focus_follows_mouse_focuses_tiled_pane_on_hover() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);

    // Create a vertical split: pane 1 (left), pane 2 (right)
    tab.vertical_split(new_pane_id, None, client_id, None, None)
        .unwrap();

    // Active pane is pane 2 (right) after split
    assert_eq!(tab.get_active_pane_id(client_id), Some(PaneId::Terminal(2)));

    // Enable focus follows mouse
    tab.update_focus_follows_mouse(true);

    // Hover over the left pane (pane 1) — column 30 is in the left half of 121 cols
    let effect = tab
        .handle_mouse_event(
            &MouseEvent::new_buttonless_motion(Position::new(10, 30)),
            client_id,
        )
        .unwrap();

    // Focus should have changed to pane 1
    assert!(effect.state_changed);
    assert_eq!(tab.get_active_pane_id(client_id), Some(PaneId::Terminal(1)));
}

#[test]
fn focus_follows_mouse_disabled_does_not_change_focus() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);

    tab.vertical_split(new_pane_id, None, client_id, None, None)
        .unwrap();

    // Active pane is pane 2 (right) after split
    assert_eq!(tab.get_active_pane_id(client_id), Some(PaneId::Terminal(2)));

    // focus_follows_mouse is false by default — do NOT enable it

    // Hover over the left pane (pane 1)
    let _effect = tab
        .handle_mouse_event(
            &MouseEvent::new_buttonless_motion(Position::new(10, 30)),
            client_id,
        )
        .unwrap();

    // Focus should remain on pane 2
    assert_eq!(tab.get_active_pane_id(client_id), Some(PaneId::Terminal(2)));
}

#[test]
fn focus_follows_mouse_focuses_floating_pane_on_hover() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let floating_pane_id_1 = PaneId::Terminal(2);
    let floating_pane_id_2 = PaneId::Terminal(3);

    let coordinates_1 = FloatingPaneCoordinates {
        x: Some(PercentOrFixed::Fixed(5)),
        y: Some(PercentOrFixed::Fixed(5)),
        width: Some(PercentOrFixed::Fixed(10)),
        height: Some(PercentOrFixed::Fixed(10)),
        pinned: None,
        borderless: Some(false),
    };

    let coordinates_2 = FloatingPaneCoordinates {
        x: Some(PercentOrFixed::Fixed(20)),
        y: Some(PercentOrFixed::Fixed(5)),
        width: Some(PercentOrFixed::Fixed(10)),
        height: Some(PercentOrFixed::Fixed(10)),
        pinned: None,
        borderless: Some(false),
    };

    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        floating_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::Floating(Some(coordinates_1)),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        floating_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::Floating(Some(coordinates_2)),
        Some(client_id),
        None,
    )
    .unwrap();

    // Active pane is floating_pane_id_2 (last created)
    assert_eq!(tab.get_active_pane_id(client_id), Some(floating_pane_id_2));

    // Enable focus follows mouse
    tab.update_focus_follows_mouse(true);

    // Hover over floating pane 1 (at coordinates x=5, y=5, so position row=7, col=7 is inside it)
    let effect = tab
        .handle_mouse_event(
            &MouseEvent::new_buttonless_motion(Position::new(7, 7)),
            client_id,
        )
        .unwrap();

    // Focus should have changed to floating pane 1
    assert!(effect.state_changed);
    assert_eq!(tab.get_active_pane_id(client_id), Some(floating_pane_id_1));
}

#[test]
fn focus_follows_mouse_skips_unselectable_panes() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);

    tab.vertical_split(new_pane_id, None, client_id, None, None)
        .unwrap();

    // Mark pane 1 (left) as unselectable
    tab.set_pane_selectable(PaneId::Terminal(1), false);

    // Active pane is pane 2 (right)
    assert_eq!(tab.get_active_pane_id(client_id), Some(PaneId::Terminal(2)));

    // Enable focus follows mouse
    tab.update_focus_follows_mouse(true);

    // Hover over the unselectable left pane (pane 1)
    let _effect = tab
        .handle_mouse_event(
            &MouseEvent::new_buttonless_motion(Position::new(10, 30)),
            client_id,
        )
        .unwrap();

    // Focus should remain on pane 2 — unselectable panes are skipped
    assert_eq!(tab.get_active_pane_id(client_id), Some(PaneId::Terminal(2)));
}

#[test]
fn focus_follows_mouse_no_op_on_active_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);

    tab.vertical_split(new_pane_id, None, client_id, None, None)
        .unwrap();

    // Active pane is pane 2 (right)
    assert_eq!(tab.get_active_pane_id(client_id), Some(PaneId::Terminal(2)));

    // Enable focus follows mouse
    tab.update_focus_follows_mouse(true);

    // Hover over the already-active right pane (pane 2) — column 90 is in the right half
    let _effect = tab
        .handle_mouse_event(
            &MouseEvent::new_buttonless_motion(Position::new(10, 90)),
            client_id,
        )
        .unwrap();

    // Focus should remain on pane 2
    assert_eq!(tab.get_active_pane_id(client_id), Some(PaneId::Terminal(2)));
}

#[test]
fn focus_follows_mouse_respects_live_toggle() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);

    tab.vertical_split(new_pane_id, None, client_id, None, None)
        .unwrap();

    // Active pane is pane 2 (right)
    assert_eq!(tab.get_active_pane_id(client_id), Some(PaneId::Terminal(2)));

    // Enable focus follows mouse and hover left — should switch focus
    tab.update_focus_follows_mouse(true);
    tab.handle_mouse_event(
        &MouseEvent::new_buttonless_motion(Position::new(10, 30)),
        client_id,
    )
    .unwrap();
    assert_eq!(tab.get_active_pane_id(client_id), Some(PaneId::Terminal(1)));

    // Disable focus follows mouse (simulating live config reload)
    tab.update_focus_follows_mouse(false);

    // Hover back to the right pane — should NOT switch focus
    tab.handle_mouse_event(
        &MouseEvent::new_buttonless_motion(Position::new(10, 90)),
        client_id,
    )
    .unwrap();

    // Focus should remain on pane 1 (where it was left)
    assert_eq!(tab.get_active_pane_id(client_id), Some(PaneId::Terminal(1)));
}

#[test]
fn focus_follows_mouse_repeated_hover_same_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);

    tab.vertical_split(new_pane_id, None, client_id, None, None)
        .unwrap();

    assert_eq!(tab.get_active_pane_id(client_id), Some(PaneId::Terminal(2)));

    tab.update_focus_follows_mouse(true);

    // First hover over left pane — switches focus
    tab.handle_mouse_event(
        &MouseEvent::new_buttonless_motion(Position::new(10, 30)),
        client_id,
    )
    .unwrap();
    assert_eq!(tab.get_active_pane_id(client_id), Some(PaneId::Terminal(1)));

    // Second hover over same left pane at a different position — focus stays
    tab.handle_mouse_event(
        &MouseEvent::new_buttonless_motion(Position::new(5, 25)),
        client_id,
    )
    .unwrap();
    assert_eq!(tab.get_active_pane_id(client_id), Some(PaneId::Terminal(1)));
}

#[test]
fn focus_follows_mouse_ignores_tiled_pane_when_floating_visible() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);

    // Create a vertical split so there are two tiled panes
    tab.vertical_split(new_pane_id, None, client_id, None, None)
        .unwrap();

    // Open floating layer with a floating pane
    let floating_pane_id = PaneId::Terminal(3);
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        floating_pane_id,
        None,
        None,
        false,
        true,
        NewPanePlacement::Floating(Some(FloatingPaneCoordinates {
            x: Some(PercentOrFixed::Fixed(50)),
            y: Some(PercentOrFixed::Fixed(5)),
            width: Some(PercentOrFixed::Fixed(20)),
            height: Some(PercentOrFixed::Fixed(10)),
            pinned: None,
            borderless: Some(false),
        })),
        Some(client_id),
        None,
    )
    .unwrap();

    // Active pane is the floating pane
    assert_eq!(tab.get_active_pane_id(client_id), Some(floating_pane_id));

    // Enable focus follows mouse
    tab.update_focus_follows_mouse(true);

    // Hover over a tiled pane (left side, column 10) — should NOT change focus
    tab.handle_mouse_event(
        &MouseEvent::new_buttonless_motion(Position::new(10, 10)),
        client_id,
    )
    .unwrap();

    // Focus should remain on the floating pane
    assert_eq!(tab.get_active_pane_id(client_id), Some(floating_pane_id));
}

#[test]
fn focus_follows_mouse_focuses_floating_pane_when_floating_visible() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());

    // Open floating layer with two floating panes
    let floating_pane_id_1 = PaneId::Terminal(2);
    let floating_pane_id_2 = PaneId::Terminal(3);
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        floating_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::Floating(Some(FloatingPaneCoordinates {
            x: Some(PercentOrFixed::Fixed(5)),
            y: Some(PercentOrFixed::Fixed(5)),
            width: Some(PercentOrFixed::Fixed(10)),
            height: Some(PercentOrFixed::Fixed(10)),
            pinned: None,
            borderless: Some(false),
        })),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        floating_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::Floating(Some(FloatingPaneCoordinates {
            x: Some(PercentOrFixed::Fixed(50)),
            y: Some(PercentOrFixed::Fixed(5)),
            width: Some(PercentOrFixed::Fixed(10)),
            height: Some(PercentOrFixed::Fixed(10)),
            pinned: None,
            borderless: Some(false),
        })),
        Some(client_id),
        None,
    )
    .unwrap();

    // Active pane is floating_pane_id_2 (last created)
    assert_eq!(tab.get_active_pane_id(client_id), Some(floating_pane_id_2));

    // Enable focus follows mouse
    tab.update_focus_follows_mouse(true);

    // Hover over floating pane 1 (at x=5, y=5 — position row=7, col=7 is inside it)
    let effect = tab
        .handle_mouse_event(
            &MouseEvent::new_buttonless_motion(Position::new(7, 7)),
            client_id,
        )
        .unwrap();

    // Focus should have changed to floating pane 1
    assert!(effect.state_changed);
    assert_eq!(tab.get_active_pane_id(client_id), Some(floating_pane_id_1));
}

#[test]
fn focus_follows_mouse_skips_stacked_one_liner_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let swap_layouts = r#"
        layout {
            swap_tiled_layout {
                tab {
                    pane split_direction="vertical" {
                        pane
                        pane stacked=true { children; }
                    }
                }
            }
        }
    "#;
    let layout = Layout::from_kdl(swap_layouts, Some("file_name.kdl".into()), None, None).unwrap();
    let swap_tiled_layouts = layout.swap_tiled_layouts.clone();
    let swap_floating_layouts = layout.swap_floating_layouts.clone();
    let stacked_resize = true;
    let mut tab = create_new_tab_with_swap_layouts(
        size,
        ModeInfo::default(),
        (swap_tiled_layouts, swap_floating_layouts),
        None,
        true,
        stacked_resize,
    );

    // Add panes so the stack has one-liners
    let new_pane_id_1 = PaneId::Terminal(2);
    let new_pane_id_2 = PaneId::Terminal(3);
    let new_pane_id_3 = PaneId::Terminal(4);
    tab.new_pane(
        new_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        new_pane_id_3,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();

    // Focus the left (non-stacked) tiled pane
    let _ = tab.move_focus_left(client_id);
    assert_eq!(tab.get_active_pane_id(client_id), Some(PaneId::Terminal(1)));

    // Enable focus follows mouse
    tab.update_focus_follows_mouse(true);

    // Find a one-liner pane dynamically by inspecting geometries
    let one_liner_geom = tab
        .get_all_pane_ids()
        .iter()
        .filter_map(|pid| {
            let pane = tab.get_pane_with_id(*pid)?;
            let geom = pane.current_geom();
            if geom.is_stacked() && geom.rows.is_fixed() {
                Some(geom)
            } else {
                None
            }
        })
        .next()
        .expect("Expected at least one stacked one-liner pane");

    // Hover in the middle of the one-liner pane
    let hover_row = one_liner_geom.y as i32;
    let hover_col = (one_liner_geom.x + one_liner_geom.cols.as_usize() / 2) as u16;
    let _effect = tab
        .handle_mouse_event(
            &MouseEvent::new_buttonless_motion(Position::new(hover_row, hover_col)),
            client_id,
        )
        .unwrap();

    // Focus should remain on the left tiled pane — the one-liner should be skipped
    assert_eq!(tab.get_active_pane_id(client_id), Some(PaneId::Terminal(1)));
}

#[test]
fn mouse_click_through_sends_click_to_newly_focused_tiled_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;

    let mut pty_instruction_bus = MockPtyInstructionBus::new();
    let mut tab = create_new_tab_with_mock_pty_writer(
        size,
        ModeInfo::default(),
        pty_instruction_bus.pty_write_sender(),
    );
    pty_instruction_bus.start();

    let new_pane_id = PaneId::Terminal(2);
    tab.vertical_split(new_pane_id, None, client_id, None, None)
        .unwrap();

    // Active pane is pane 2 (right) after split
    assert_eq!(tab.get_active_pane_id(client_id), Some(PaneId::Terminal(2)));

    // Enable SGR mouse mode on pane 1 (left, inactive)
    let sgr_mouse_mode = String::from("\u{1b}[?1000;1006h");
    tab.handle_pty_bytes(1, sgr_mouse_mode.as_bytes().to_vec())
        .unwrap();

    // Enable click-through
    tab.update_mouse_click_through(true);

    // Click on pane 1 (left half) — column 30 is in the left pane of a 121-col split
    let click_position = Position::new(5, 30);
    let effect = tab
        .handle_mouse_event(&MouseEvent::new_left_press_event(click_position), client_id)
        .unwrap();

    assert!(effect.state_changed);
    assert_eq!(tab.get_active_pane_id(client_id), Some(PaneId::Terminal(1)));

    pty_instruction_bus.exit();

    // Verify the SGR left-click escape sequence was sent to the terminal.
    let output = pty_instruction_bus.clone_output();
    assert!(
        !output.is_empty(),
        "Expected click to be forwarded to terminal"
    );
    // The output should contain an SGR mouse press sequence: \x1b[<0;COL;ROWM
    assert!(
        output[0].starts_with("\u{1b}[<0;"),
        "Expected SGR left-click sequence, got: {:?}",
        output[0]
    );
}

#[test]
fn mouse_click_through_disabled_does_not_send_click_to_focused_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;

    let mut pty_instruction_bus = MockPtyInstructionBus::new();
    let mut tab = create_new_tab_with_mock_pty_writer(
        size,
        ModeInfo::default(),
        pty_instruction_bus.pty_write_sender(),
    );
    pty_instruction_bus.start();

    let new_pane_id = PaneId::Terminal(2);
    tab.vertical_split(new_pane_id, None, client_id, None, None)
        .unwrap();

    // Enable SGR mouse mode on pane 1 (inactive)
    let sgr_mouse_mode = String::from("\u{1b}[?1000;1006h");
    tab.handle_pty_bytes(1, sgr_mouse_mode.as_bytes().to_vec())
        .unwrap();

    // mouse_click_through is false by default — do NOT enable it

    // Click on pane 1
    let effect = tab
        .handle_mouse_event(
            &MouseEvent::new_left_press_event(Position::new(5, 30)),
            client_id,
        )
        .unwrap();

    // Focus changed
    assert!(effect.state_changed);
    assert_eq!(tab.get_active_pane_id(client_id), Some(PaneId::Terminal(1)));

    pty_instruction_bus.exit();

    // No click was forwarded
    let output = pty_instruction_bus.clone_output();
    assert!(
        output.is_empty(),
        "Expected no click forwarded, got: {:?}",
        output
    );
}

#[test]
fn mouse_click_through_sends_click_to_newly_focused_floating_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;

    let mut pty_instruction_bus = MockPtyInstructionBus::new();
    let mut tab = create_new_tab_with_mock_pty_writer(
        size,
        ModeInfo::default(),
        pty_instruction_bus.pty_write_sender(),
    );
    pty_instruction_bus.start();

    // Create two floating panes
    let floating_pane_id_1 = PaneId::Terminal(2);
    let floating_pane_id_2 = PaneId::Terminal(3);
    tab.toggle_floating_panes(Some(client_id), None, None)
        .unwrap();
    tab.new_pane(
        floating_pane_id_1,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();
    tab.new_pane(
        floating_pane_id_2,
        None,
        None,
        false,
        true,
        NewPanePlacement::default(),
        Some(client_id),
        None,
    )
    .unwrap();

    // Enable SGR mouse mode on floating pane 1
    let sgr_mouse_mode = String::from("\u{1b}[?1000;1006h");
    tab.handle_pty_bytes(2, sgr_mouse_mode.as_bytes().to_vec())
        .unwrap();

    tab.update_mouse_click_through(true);

    // Click on floating pane 1 (need to find its position — render first)
    let mut output = Output::default();
    tab.render(&mut output, None).unwrap();

    // Focus floating pane 2 first to make pane 1 inactive
    tab.focus_pane_with_id(floating_pane_id_2, false, false, client_id)
        .unwrap();
    assert_eq!(tab.get_active_pane_id(client_id), Some(floating_pane_id_2));

    // Now click on floating pane 1's area
    let _effect = tab
        .handle_mouse_event(
            &MouseEvent::new_left_press_event(Position::new(5, 30)),
            client_id,
        )
        .unwrap();

    // Verify focus changed and click was forwarded
    if tab.get_active_pane_id(client_id) == Some(floating_pane_id_1) {
        pty_instruction_bus.exit();
        let pty_output = pty_instruction_bus.clone_output();
        assert!(
            !pty_output.is_empty(),
            "Expected click forwarded to floating pane terminal"
        );
    } else {
        pty_instruction_bus.exit();
        // Position may not have landed on floating pane 1 — this is a layout-dependent test.
        // Adjust the position if this branch is hit.
    }
}

#[test]
fn mouse_click_through_does_not_apply_when_focus_follows_mouse_is_enabled() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;

    let mut pty_instruction_bus = MockPtyInstructionBus::new();
    let mut tab = create_new_tab_with_mock_pty_writer(
        size,
        ModeInfo::default(),
        pty_instruction_bus.pty_write_sender(),
    );
    pty_instruction_bus.start();

    let new_pane_id = PaneId::Terminal(2);
    tab.vertical_split(new_pane_id, None, client_id, None, None)
        .unwrap();
    assert_eq!(tab.get_active_pane_id(client_id), Some(PaneId::Terminal(2)));

    // Enable SGR mouse mode on pane 1
    let sgr_mouse_mode = String::from("\u{1b}[?1000;1006h");
    tab.handle_pty_bytes(1, sgr_mouse_mode.as_bytes().to_vec())
        .unwrap();

    // Enable BOTH features
    tab.update_focus_follows_mouse(true);
    tab.update_mouse_click_through(true);

    // Step 1: Hover over pane 1 — this focuses it via focus-follows-mouse
    let hover_position = Position::new(5, 30);
    tab.handle_mouse_event(
        &MouseEvent::new_buttonless_motion(hover_position),
        client_id,
    )
    .unwrap();
    assert_eq!(tab.get_active_pane_id(client_id), Some(PaneId::Terminal(1)));

    // Step 2: Click on pane 1 — now this is a click on the ACTIVE pane
    // It should go through the normal SendToTerminal path
    let click_position = Position::new(5, 30);
    tab.handle_mouse_event(&MouseEvent::new_left_press_event(click_position), client_id)
        .unwrap();

    pty_instruction_bus.exit();

    // Click was sent to terminal via the normal path (not click-through)
    let output = pty_instruction_bus.clone_output();
    assert!(
        !output.is_empty(),
        "Expected click sent to active pane terminal"
    );
}

#[test]
fn mouse_click_through_starts_selection_on_non_mouse_mode_pane() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());
    let new_pane_id = PaneId::Terminal(2);

    tab.vertical_split(new_pane_id, None, client_id, None, None)
        .unwrap();
    assert_eq!(tab.get_active_pane_id(client_id), Some(PaneId::Terminal(2)));

    // Pane 1 is NOT in mouse mode — no SGR escape sent

    tab.update_mouse_click_through(true);

    // Click on pane 1
    let effect = tab
        .handle_mouse_event(
            &MouseEvent::new_left_press_event(Position::new(5, 30)),
            client_id,
        )
        .unwrap();

    assert!(effect.state_changed);
    assert_eq!(tab.get_active_pane_id(client_id), Some(PaneId::Terminal(1)));

    // Selection should have started
    assert_eq!(
        tab.selecting_with_mouse_in_pane,
        Some(PaneId::Terminal(1)),
        "Expected text selection to start on the newly focused pane"
    );
}

#[test]
fn mouse_click_through_respects_live_toggle() {
    let size = Size {
        cols: 121,
        rows: 20,
    };
    let client_id = 1;

    let mut pty_instruction_bus = MockPtyInstructionBus::new();
    let mut tab = create_new_tab_with_mock_pty_writer(
        size,
        ModeInfo::default(),
        pty_instruction_bus.pty_write_sender(),
    );
    pty_instruction_bus.start();

    let new_pane_id = PaneId::Terminal(2);
    tab.vertical_split(new_pane_id, None, client_id, None, None)
        .unwrap();

    // Enable SGR mouse mode on pane 1
    let sgr_mouse_mode = String::from("\u{1b}[?1000;1006h");
    tab.handle_pty_bytes(1, sgr_mouse_mode.as_bytes().to_vec())
        .unwrap();

    // mouse_click_through starts disabled

    // Click pane 1 — should only focus, no forwarding
    tab.handle_mouse_event(
        &MouseEvent::new_left_press_event(Position::new(5, 30)),
        client_id,
    )
    .unwrap();
    assert_eq!(tab.get_active_pane_id(client_id), Some(PaneId::Terminal(1)));

    // Click pane 2 to refocus it
    tab.handle_mouse_event(
        &MouseEvent::new_left_press_event(Position::new(5, 90)),
        client_id,
    )
    .unwrap();
    assert_eq!(tab.get_active_pane_id(client_id), Some(PaneId::Terminal(2)));

    // Now enable click-through
    tab.update_mouse_click_through(true);

    // Click pane 1 again — this time the click should be forwarded
    tab.handle_mouse_event(
        &MouseEvent::new_left_press_event(Position::new(5, 30)),
        client_id,
    )
    .unwrap();
    assert_eq!(tab.get_active_pane_id(client_id), Some(PaneId::Terminal(1)));

    pty_instruction_bus.exit();

    // Only the LAST click (after enabling) should have produced terminal output
    let output = pty_instruction_bus.clone_output();
    assert_eq!(
        output.len(),
        1,
        "Expected exactly one forwarded click, got: {:?}",
        output
    );
    assert!(
        output[0].starts_with("\u{1b}[<0;"),
        "Expected SGR left-click sequence, got: {:?}",
        output[0]
    );
}

// ========================================================================
// OSC 99 Desktop Notification Integration Tests
// ========================================================================

/// Creates a Tab with a real `to_server` sender so that `ServerInstruction::Render`
/// calls from `forward_desktop_notifications` can be captured on the receiver.
fn create_new_tab_with_server_receiver(
    size: Size,
    default_mode: ModeInfo,
) -> (Tab, Receiver<(ServerInstruction, ErrorContext)>) {
    set_session_name("test".into());
    let index = 0;
    let position = 0;
    let name = String::new();
    let os_api = Box::new(FakeInputOutput::default());

    // Set up real server channel to capture ServerInstruction::Render
    let (server_sender, server_receiver) = channels::unbounded();
    let server_sender = SenderWithContext::new(server_sender);
    let mut senders = ThreadSenders::default().silently_fail_on_send();
    senders.to_server = Some(server_sender);

    let max_panes = None;
    let client_id = 1;
    let mut connected_clients = HashMap::new();
    connected_clients.insert(client_id, false);
    let connected_clients = Rc::new(RefCell::new(connected_clients));
    let character_cell_info = Rc::new(RefCell::new(None));
    let stacked_resize = Rc::new(RefCell::new(true));
    let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default()));
    let copy_options = CopyOptions::default();
    let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
    let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
    let current_group = Rc::new(RefCell::new(PaneGroups::new(ThreadSenders::default())));
    let currently_marking_pane_group = Rc::new(RefCell::new(HashMap::new()));
    let mut tab = Tab::new(
        index,
        position,
        name,
        size,
        character_cell_info,
        stacked_resize,
        sixel_image_store,
        os_api,
        senders,
        max_panes,
        Style::default(),
        default_mode,
        true, // draw_pane_frames
        true, // auto_layout
        connected_clients,
        true, // session_is_mirrored
        Some(client_id),
        copy_options,
        terminal_emulator_colors,
        terminal_emulator_color_codes,
        (vec![], vec![]),
        PathBuf::from("my_default_shell"),
        false, // debug
        true,  // arrow_fonts
        true,  // styled_underlines
        true,  // osc8_hyperlinks
        false, // explicitly_disable_kitty_keyboard_protocol
        None,
        false,
        WebSharing::Off,
        current_group,
        currently_marking_pane_group,
        true,  // advanced_mouse_actions
        true,  // mouse_hover_effects
        false, // focus_follows_mouse
        false, // mouse_click_through
        IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
        8080,
    );
    tab.apply_layout(
        TiledPaneLayout::default(),
        vec![],
        vec![(1, None)],
        vec![],
        HashMap::new(),
        client_id,
        None,
    )
    .unwrap();
    (tab, server_receiver)
}

/// Helper: collect all ServerInstruction::Render messages from the receiver,
/// concatenate the per-client strings, and return the combined output.
fn collect_render_output(receiver: &Receiver<(ServerInstruction, ErrorContext)>) -> String {
    let mut output = String::new();
    while let Ok((instruction, _)) = receiver.try_recv() {
        if let ServerInstruction::Render(Some(client_map)) = instruction {
            for (_client_id, content) in client_map {
                output.push_str(&content);
            }
        }
    }
    output
}

#[test]
fn osc99_notification_forwarded_through_tab_to_server_render() {
    // Integration test: OSC 99 sequence emitted by a pane's PTY is parsed by Grid,
    // drained by Tab, namespaced, and forwarded via ServerInstruction::Render.
    let size = Size { cols: 80, rows: 24 };
    let (mut tab, server_receiver) = create_new_tab_with_server_receiver(size, ModeInfo::default());

    // Feed OSC 99 sequence to pane (pid=1) — BEL terminated
    tab.handle_pty_bytes(1, Vec::from("\x1b]99;i=test1:p=title;Hello World\x07"))
        .unwrap();

    let output = collect_render_output(&server_receiver);

    // The forwarded output should contain a namespaced OSC 99 sequence
    assert!(
        output.contains("\x1b]99;"),
        "Render output should contain OSC 99 prefix, got: {:?}",
        output
    );
    // The identifier should be namespaced with pane ID (pid=1)
    assert!(
        output.contains("i=p1.test1"),
        "Identifier should be namespaced as i=p1.test1, got: {:?}",
        output
    );
    // The payload should be preserved
    assert!(
        output.contains("Hello World"),
        "Payload 'Hello World' should be forwarded, got: {:?}",
        output
    );
}

#[test]
fn osc99_notification_st_terminator_forwarded() {
    // Same as above but with ESC \ (ST) terminator
    let size = Size { cols: 80, rows: 24 };
    let (mut tab, server_receiver) = create_new_tab_with_server_receiver(size, ModeInfo::default());

    tab.handle_pty_bytes(1, Vec::from("\x1b]99;i=test2:p=title;Build done\x1b\\"))
        .unwrap();

    let output = collect_render_output(&server_receiver);

    assert!(
        output.contains("i=p1.test2"),
        "Identifier should be namespaced, got: {:?}",
        output
    );
    assert!(
        output.contains("Build done"),
        "Payload should be forwarded, got: {:?}",
        output
    );
}

#[test]
fn osc99_notification_without_identifier_gets_default() {
    // When no i= key is present, namespace_notification_id adds i=p<N>.0
    let size = Size { cols: 80, rows: 24 };
    let (mut tab, server_receiver) = create_new_tab_with_server_receiver(size, ModeInfo::default());

    tab.handle_pty_bytes(1, Vec::from("\x1b]99;p=title;No ID here\x07"))
        .unwrap();

    let output = collect_render_output(&server_receiver);

    assert!(
        output.contains("i=p1.:"),
        "Missing i= should get default i=p1. (empty original id), got: {:?}",
        output
    );
    assert!(
        output.contains("No ID here"),
        "Payload should be forwarded, got: {:?}",
        output
    );
}

#[test]
fn osc99_multiple_notifications_forwarded() {
    // Multiple OSC 99 sequences in one PTY write should all be forwarded
    let size = Size { cols: 80, rows: 24 };
    let (mut tab, server_receiver) = create_new_tab_with_server_receiver(size, ModeInfo::default());

    tab.handle_pty_bytes(
        1,
        Vec::from("\x1b]99;i=n1:p=title;First\x07\x1b]99;i=n2:p=title;Second\x07"),
    )
    .unwrap();

    let output = collect_render_output(&server_receiver);

    assert!(
        output.contains("i=p1.n1"),
        "First notification should be namespaced, got: {:?}",
        output
    );
    assert!(
        output.contains("i=p1.n2"),
        "Second notification should be namespaced, got: {:?}",
        output
    );
    assert!(
        output.contains("First") && output.contains("Second"),
        "Both payloads should be forwarded, got: {:?}",
        output
    );
}

#[test]
fn osc99_notification_mixed_with_regular_output() {
    // OSC 99 embedded in regular terminal output should be extracted and forwarded
    let size = Size { cols: 80, rows: 24 };
    let (mut tab, server_receiver) = create_new_tab_with_server_receiver(size, ModeInfo::default());

    tab.handle_pty_bytes(
        1,
        Vec::from("hello\x1b]99;i=mid:p=title;Inline notification\x07world"),
    )
    .unwrap();

    let output = collect_render_output(&server_receiver);

    assert!(
        output.contains("i=p1.mid"),
        "Notification should be forwarded, got: {:?}",
        output
    );
    assert!(
        output.contains("Inline notification"),
        "Notification payload should be present, got: {:?}",
        output
    );
}

#[test]
fn osc99_notification_preserves_metadata_keys() {
    // All metadata keys (p=, a=, u=, etc.) should be preserved through forwarding
    let size = Size { cols: 80, rows: 24 };
    let (mut tab, server_receiver) = create_new_tab_with_server_receiver(size, ModeInfo::default());

    tab.handle_pty_bytes(
        1,
        Vec::from("\x1b]99;i=rich:p=title:a=report:u=2;Important!\x07"),
    )
    .unwrap();

    let output = collect_render_output(&server_receiver);

    assert!(
        output.contains("i=p1r.rich"),
        "Identifier should be namespaced with 'r' flag (a=report present), got: {:?}",
        output
    );
    assert!(
        output.contains("p=title"),
        "p=title metadata should be preserved, got: {:?}",
        output
    );
    assert!(
        output.contains("a=report"),
        "a=report metadata should be preserved, got: {:?}",
        output
    );
    assert!(
        output.contains("u=2"),
        "u=2 metadata should be preserved, got: {:?}",
        output
    );
}

#[test]
fn osc99_grid_parses_and_stores_notification() {
    // Direct Grid-level test: feed OSC 99 bytes through vte parser,
    // verify pending_desktop_notifications is populated correctly.
    use crate::panes::grid::Grid;
    use crate::panes::link_handler::LinkHandler;

    let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
    let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
    let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels {
        width: 8,
        height: 21,
    })));

    let mut grid = Grid::new(
        24,
        80,
        Rc::new(RefCell::new(Palette::default())),
        terminal_emulator_color_codes,
        Rc::new(RefCell::new(LinkHandler::new())),
        character_cell_size,
        sixel_image_store,
        Style::default(),
        false, // debug
        true,  // arrow_fonts
        true,  // styled_underlines
        true,  // osc8_hyperlinks
        false, // explicitly_disable_kitty_keyboard_protocol
    );

    // Feed OSC 99 through vte parser
    let mut vte_parser = vte::Parser::new();
    for &byte in b"\x1b]99;i=gridtest:p=title;Grid notification\x07" {
        vte_parser.advance(&mut grid, byte);
    }

    assert_eq!(
        grid.pending_desktop_notifications.len(),
        1,
        "Should have one pending notification"
    );

    let (ref payload, ref _terminator) = grid.pending_desktop_notifications.first().unwrap();
    assert!(
        payload.contains("i=gridtest"),
        "Payload should contain i=gridtest, got: {:?}",
        payload
    );
}

#[test]
fn osc99_namespace_denormalize_roundtrip() {
    // Test that namespace_notification_id and denormalize_notification_response
    // are inverse operations.
    use crate::panes::grid::namespace_notification_id;
    use crate::screen::denormalize_notification_response;

    let original_metadata = "i=mynotif:p=title:a=report";
    let pane_id: u32 = 42;

    // Namespace — a=report means 'r' flag is set
    let namespaced = namespace_notification_id(original_metadata, pane_id);
    assert!(
        namespaced.contains("i=p42r.mynotif"),
        "Should namespace to i=p42r.mynotif (a=report → 'r' flag), got: {:?}",
        namespaced
    );

    // Simulate a response with the namespaced ID
    let response_payload = format!("i=p42r.mynotif:p=close;activated");
    let result = denormalize_notification_response(response_payload.as_bytes());
    assert!(result.is_some(), "Should successfully denormalize");

    let (terminal_id, app_wants_report, is_query, response_bytes) = result.unwrap();
    assert_eq!(terminal_id, 42, "Should extract pane_id 42");
    assert!(
        app_wants_report,
        "'r' flag in i=p42r.mynotif means app_wants_report should be true"
    );
    assert!(!is_query, "No 'q' flag means is_query should be false");

    let response_str = String::from_utf8_lossy(&response_bytes);
    assert!(
        response_str.contains("i=mynotif"),
        "Should restore original i=mynotif, got: {:?}",
        response_str
    );
    assert!(
        !response_str.contains("p42"),
        "Should not contain namespaced prefix, got: {:?}",
        response_str
    );
}

#[test]
fn osc99_denormalize_with_no_namespace_returns_none() {
    use crate::screen::denormalize_notification_response;

    // A response without the p<N>. namespace pattern should return None
    let result = denormalize_notification_response(b"i=plain_id;data");
    assert!(
        result.is_none(),
        "Should return None for non-namespaced response"
    );
}

#[test]
fn osc99_namespace_without_identifier_adds_default() {
    use crate::panes::grid::namespace_notification_id;

    // With a=report → 'r' flag
    let metadata = "p=title:a=report";
    let namespaced = namespace_notification_id(metadata, 7);
    assert!(
        namespaced.contains("i=p7r.:"),
        "Should add default i=p7r. when no i= present (a=report → 'r' flag), got: {:?}",
        namespaced
    );

    // Without a=report → no 'r' flag
    let metadata = "p=title";
    let namespaced = namespace_notification_id(metadata, 7);
    assert!(
        namespaced.contains("i=p7.:"),
        "Should add default i=p7. when no i= and no a=report, got: {:?}",
        namespaced
    );
}

#[test]
fn osc99_namespace_ensures_report_action() {
    use crate::panes::grid::namespace_notification_id;

    // a=focus → a=focus,report
    let result = namespace_notification_id("i=test:p=title:a=focus", 1);
    assert!(
        result.contains("a=focus,report"),
        "a=focus should be augmented with report, got: {:?}",
        result
    );

    // a=report → unchanged
    let result = namespace_notification_id("i=test:p=title:a=report", 1);
    assert!(
        result.contains("a=report"),
        "a=report should be preserved, got: {:?}",
        result
    );
    assert!(
        !result.contains("a=report,report"),
        "Should not duplicate report, got: {:?}",
        result
    );

    // a=focus,report → unchanged
    let result = namespace_notification_id("i=test:p=title:a=focus,report", 1);
    assert!(
        result.contains("a=focus,report"),
        "a=focus,report should be preserved, got: {:?}",
        result
    );

    // No a= key → a=report added
    let result = namespace_notification_id("i=test:p=title", 1);
    assert!(
        result.contains("a=report"),
        "Missing a= should get a=report appended, got: {:?}",
        result
    );
}

#[test]
fn osc99_report_flag_roundtrip() {
    use crate::panes::grid::namespace_notification_id;
    use crate::screen::denormalize_notification_response;

    // App sends a=report → namespaced with 'r' flag → denormalize returns app_wants_report=true
    let namespaced = namespace_notification_id("i=myid:p=title:a=report", 5);
    assert!(
        namespaced.contains("i=p5r.myid"),
        "a=report should produce 'r' flag in namespace, got: {:?}",
        namespaced
    );
    let response = format!("i=p5r.myid;activated");
    let (pane_id, wants_report, _is_query, _bytes) =
        denormalize_notification_response(response.as_bytes()).unwrap();
    assert_eq!(pane_id, 5);
    assert!(wants_report, "Should detect 'r' flag as app_wants_report");

    // App sends a=focus (no report) → namespaced without 'r' flag → denormalize returns false
    let namespaced = namespace_notification_id("i=myid:p=title:a=focus", 5);
    assert!(
        namespaced.contains("i=p5.myid"),
        "a=focus should NOT produce 'r' flag, got: {:?}",
        namespaced
    );
    assert!(
        namespaced.contains("a=focus,report"),
        "a=focus should be augmented with report for the host terminal, got: {:?}",
        namespaced
    );
    let response = format!("i=p5.myid;activated");
    let (pane_id, wants_report, _is_query, _bytes) =
        denormalize_notification_response(response.as_bytes()).unwrap();
    assert_eq!(pane_id, 5);
    assert!(!wants_report, "No 'r' flag means app did not want report");
}

#[test]
fn osc99_query_flag_roundtrip() {
    use crate::panes::grid::namespace_notification_id;
    use crate::screen::denormalize_notification_response;

    // Capability query (p=?) gets 'q' flag
    let namespaced = namespace_notification_id("i=qid:p=?", 3);
    assert!(
        namespaced.contains("i=p3q.qid"),
        "p=? should produce 'q' flag, got: {:?}",
        namespaced
    );
    let response = format!("i=p3q.qid;p=title,body");
    let (pane_id, wants_report, is_query, _bytes) =
        denormalize_notification_response(response.as_bytes()).unwrap();
    assert_eq!(pane_id, 3);
    assert!(!wants_report);
    assert!(is_query, "Should detect 'q' flag");

    // Both flags: a=report + p=? (unlikely but valid)
    let namespaced = namespace_notification_id("i=both:p=?:a=report", 3);
    assert!(
        namespaced.contains("i=p3rq.both"),
        "Both flags should be present, got: {:?}",
        namespaced
    );
    let response = format!("i=p3rq.both;p=title,body");
    let (pane_id, wants_report, is_query, _bytes) =
        denormalize_notification_response(response.as_bytes()).unwrap();
    assert_eq!(pane_id, 3);
    assert!(wants_report);
    assert!(is_query);

    // Regular notification (no p=?, no a=report) — no flags
    let namespaced = namespace_notification_id("i=plain:p=title:a=focus", 3);
    assert!(
        namespaced.contains("i=p3.plain"),
        "No flags expected, got: {:?}",
        namespaced
    );
    let response = format!("i=p3.plain;activated");
    let (pane_id, wants_report, is_query, _bytes) =
        denormalize_notification_response(response.as_bytes()).unwrap();
    assert_eq!(pane_id, 3);
    assert!(!wants_report);
    assert!(!is_query);
}

#[test]
fn hidden_cursor_still_emits_cup_for_host_terminal_positioning() {
    // When an app hides the cursor the host
    // terminal still needs a CUP sequence so that IME composition candidates and
    // other cursor-position-dependent features appear in the correct location.
    let size = Size { cols: 80, rows: 24 };
    let client_id = 1;
    let mut tab = create_new_tab(size, ModeInfo::default());

    // Move cursor to a known position, then hide it
    tab.handle_pty_bytes(1, Vec::from("\u{1b}[10;20H\u{1b}[?25l".as_bytes()))
        .unwrap();

    let mut output = Output::default();
    tab.render(&mut output, None).unwrap();
    let serialized = output.serialize().unwrap();
    let client_output = serialized.get(&client_id).unwrap();

    // The output must contain a hide-cursor instruction
    assert!(
        client_output.contains("\u{1b}[?25l"),
        "Expected hide-cursor sequence in output"
    );

    // The output must contain a CUP sequence positioning the cursor where the app
    // left it (row 10, col 20 — 1-indexed in the pane, plus the content offset
    // from the pane frame). The key point is that a CUP is emitted at all, even
    // though the cursor is hidden.
    //
    // With draw_pane_frames=true the content offset is (1, 1), so the absolute
    // position is row 11, col 21.
    assert!(
        client_output.contains("\u{1b}[11;21H"),
        "Expected CUP sequence for hidden cursor positioning, got: {:?}",
        client_output
    );

    // The output must NOT contain a show-cursor instruction
    assert!(
        !client_output.contains("\u{1b}[?25h"),
        "Show-cursor sequence must not be present when app has hidden the cursor"
    );
}
