pub mod floating_pane_grid;
use zellij_utils::{
    data::{Direction, FloatingPaneCoordinates, PaneInfo, ResizeStrategy},
    position::Position,
};

use crate::panes::kitty_graphics::KittyHostSupport;
use crate::resize_pty;
use crate::tab::{pane_info_for_pane, Pane};
use floating_pane_grid::FloatingPaneGrid;

use crate::{
    os_input_output::ServerOsApi,
    output::{FloatingPanesStack, Output},
    panes::{ActivePanes, PaneId},
    plugins::PluginInstruction,
    thread_bus::ThreadSenders,
    ui::pane_contents_and_ui::PaneContentsAndUi,
    ClientId,
};
use std::cell::RefCell;
use std::cmp::Ordering;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::rc::Rc;
use std::time::Instant;
use zellij_utils::{
    data::{ModeInfo, Style, Styling},
    errors::prelude::*,
    input::command::RunCommand,
    input::layout::{FloatingPaneLayout, Run, RunPluginOrAlias},
    input::options::PaneFrameStyle,
    pane_size::{Dimension, Offset, PaneGeom, Size, SizeInPixels, Viewport},
};

const RESIZE_INCREMENT_WIDTH: usize = 5;
const RESIZE_INCREMENT_HEIGHT: usize = 2;

pub struct FloatingPanes {
    panes: BTreeMap<PaneId, Box<dyn Pane>>,
    display_area: Rc<RefCell<Size>>,
    viewport: Rc<RefCell<Viewport>>,
    connected_clients: Rc<RefCell<HashSet<ClientId>>>,
    connected_clients_in_app: Rc<RefCell<HashMap<ClientId, bool>>>, // bool -> is_web_client
    mode_info: Rc<RefCell<HashMap<ClientId, ModeInfo>>>,
    character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
    default_mode_info: ModeInfo,
    style: Style,
    session_is_mirrored: bool,
    desired_pane_positions: HashMap<PaneId, PaneGeom>, // this represents the positions of panes the user moved with intention, rather than by resizing the terminal window
    z_indices: Vec<PaneId>,
    active_panes: ActivePanes,
    show_panes: bool,
    pane_being_moved_with_mouse: Option<(PaneId, Position, Position)>, // (pane-id,
    // initial_position,
    // last_position)
    senders: ThreadSenders,
    window_title: Option<String>,
    dimmed_clients: HashSet<ClientId>,
    fullscreen_covers_ui: Rc<RefCell<bool>>,
    fullscreen_pane_id: Option<PaneId>,
    floating_fullscreen_covers_ui: bool,
    pane_frame_style: PaneFrameStyle,
}

#[allow(clippy::borrowed_box)]
#[allow(clippy::too_many_arguments)]
impl FloatingPanes {
    pub fn new(
        display_area: Rc<RefCell<Size>>,
        viewport: Rc<RefCell<Viewport>>,
        connected_clients: Rc<RefCell<HashSet<ClientId>>>,
        connected_clients_in_app: Rc<RefCell<HashMap<ClientId, bool>>>, // bool -> is_web_client
        mode_info: Rc<RefCell<HashMap<ClientId, ModeInfo>>>,
        character_cell_size: Rc<RefCell<Option<SizeInPixels>>>,
        fullscreen_covers_ui: Rc<RefCell<bool>>,
        pane_frame_style: PaneFrameStyle,
        session_is_mirrored: bool,
        default_mode_info: ModeInfo,
        style: Style,
        os_input: Box<dyn ServerOsApi>,
        senders: ThreadSenders,
    ) -> Self {
        FloatingPanes {
            panes: BTreeMap::new(),
            display_area,
            viewport,
            connected_clients,
            connected_clients_in_app,
            mode_info,
            character_cell_size,
            session_is_mirrored,
            default_mode_info,
            style,
            desired_pane_positions: HashMap::new(),
            z_indices: vec![],
            show_panes: false,
            active_panes: ActivePanes::new(&os_input),
            pane_being_moved_with_mouse: None,
            senders,
            window_title: None,
            dimmed_clients: HashSet::new(),
            fullscreen_covers_ui,
            fullscreen_pane_id: None,
            floating_fullscreen_covers_ui: false,
            pane_frame_style,
        }
    }
    pub fn set_pane_frame_style(&mut self, pane_frame_style: PaneFrameStyle) {
        self.pane_frame_style = pane_frame_style;
        if self.fullscreen_pane_id.is_some() && !self.floating_fullscreen_covers_ui {
            let _ = self.set_pane_frames();
        }
    }
    pub fn set_client_dimmed(&mut self, client_id: ClientId, dimmed: bool) {
        if dimmed {
            self.dimmed_clients.insert(client_id);
        } else {
            self.dimmed_clients.remove(&client_id);
        }
        self.set_force_render();
    }
    pub fn stack(&self) -> Option<FloatingPanesStack> {
        if *self.fullscreen_covers_ui.borrow() {
            return None;
        }
        if let Some(fullscreen_pane_id) = self.fullscreen_pane_id {
            return self
                .panes
                .get(&fullscreen_pane_id)
                .map(|p| FloatingPanesStack {
                    layers: vec![p.current_geom()],
                });
        }
        if self.panes_are_visible() {
            let layers: Vec<PaneGeom> = self
                .z_indices
                .iter()
                .filter_map(|pane_id| self.panes.get(pane_id).map(|p| p.position_and_size()))
                .collect();
            if layers.is_empty() {
                None
            } else {
                Some(FloatingPanesStack { layers })
            }
        } else if self.has_pinned_panes() {
            let layers = self
                .z_indices
                .iter()
                .filter_map(|pane_id| {
                    self.panes
                        .get(pane_id)
                        .map(|p| p.position_and_size())
                        .and_then(|p| if p.is_pinned { Some(p) } else { None })
                })
                .collect();
            Some(FloatingPanesStack { layers })
        } else {
            None
        }
    }
    pub fn has_pinned_panes(&self) -> bool {
        self.panes
            .iter()
            .any(|(_, p)| p.position_and_size().is_pinned)
    }
    pub fn pane_ids(&self) -> impl Iterator<Item = &PaneId> {
        self.panes.keys()
    }
    pub fn rendered_pane_ids(&self) -> Vec<PaneId> {
        if self.panes_are_visible() {
            self.panes.keys().copied().collect()
        } else {
            self.panes
                .iter()
                .filter(|(_, pane)| pane.position_and_size().is_pinned)
                .map(|(pane_id, _)| *pane_id)
                .collect()
        }
    }
    pub fn add_pane(&mut self, pane_id: PaneId, pane: Box<dyn Pane>) {
        self.desired_pane_positions
            .insert(pane_id, pane.position_and_size());
        self.panes.insert(pane_id, pane);
        self.z_indices.push(pane_id);
        self.make_sure_pinned_panes_are_on_top();
    }
    pub fn replace_active_pane(
        &mut self,
        pane: Box<dyn Pane>,
        client_id: ClientId,
    ) -> Result<Box<dyn Pane>> {
        self.active_panes
            .get(&client_id)
            .with_context(|| format!("failed to determine active pane for client {client_id}"))
            .copied()
            .and_then(|active_pane_id| self.replace_pane(active_pane_id, pane))
            .with_context(|| format!("failed to replace active pane for client {client_id}"))
    }
    pub fn replace_pane(
        &mut self,
        pane_id: PaneId,
        mut with_pane: Box<dyn Pane>,
    ) -> Result<Box<dyn Pane>> {
        let err_context = || format!("failed to replace pane {pane_id:?} with pane");

        let with_pane_id = with_pane.pid();
        with_pane.set_content_offset(Offset::frame(1));
        let removed_pane = self
            .panes
            .remove(&pane_id)
            .with_context(|| format!("failed to remove unknown pane with ID {pane_id:?}"))
            .and_then(|removed_pane| {
                let removed_pane_id = removed_pane.pid();
                let with_pane_id = with_pane.pid();
                let removed_pane_geom = removed_pane.position_and_size();
                let removed_pane_geom_override = removed_pane.geom_override();
                with_pane.set_geom(removed_pane_geom);
                match removed_pane_geom_override {
                    Some(geom_override) => with_pane.set_geom_override(geom_override),
                    None => with_pane.reset_size_and_position_override(),
                };
                self.panes.insert(with_pane_id, with_pane);
                if self.fullscreen_pane_id == Some(pane_id) {
                    self.fullscreen_pane_id = Some(with_pane_id);
                }
                let z_index = self
                    .z_indices
                    .iter()
                    .position(|pane_id| pane_id == &removed_pane_id)
                    .context("no z-index found for pane to be removed with ID {removed_pane_id:?}")
                    .with_context(err_context)?;
                self.z_indices.remove(z_index);
                self.z_indices.insert(z_index, with_pane_id);
                self.make_sure_pinned_panes_are_on_top();
                Ok(removed_pane)
            });

        // update the desired_pane_positions to relate to the new pane
        if let Some(desired_pane_position) = self.desired_pane_positions.remove(&pane_id) {
            self.desired_pane_positions
                .insert(with_pane_id, desired_pane_position);
        }

        // move clients from the previously active pane to the new pane we just inserted
        self.move_clients_between_panes(pane_id, with_pane_id);
        let _ = self.set_pane_frames();
        removed_pane
    }
    pub fn remove_pane(&mut self, pane_id: PaneId) -> Option<Box<dyn Pane>> {
        self.z_indices.retain(|p_id| *p_id != pane_id);
        self.desired_pane_positions.remove(&pane_id);
        self.panes.remove(&pane_id)
    }
    pub fn hold_pane(
        &mut self,
        pane_id: PaneId,
        exit_status: Option<i32>,
        is_first_run: bool,
        run_command: RunCommand,
    ) {
        self.panes
            .get_mut(&pane_id)
            .map(|p| p.hold(exit_status, is_first_run, run_command));
    }
    pub fn get(&self, pane_id: &PaneId) -> Option<&Box<dyn Pane>> {
        self.panes.get(pane_id)
    }
    pub fn get_mut(&mut self, pane_id: &PaneId) -> Option<&mut Box<dyn Pane>> {
        self.panes.get_mut(pane_id)
    }
    pub fn get_active_pane(&self, client_id: ClientId) -> Option<&Box<dyn Pane>> {
        self.active_panes
            .get(&client_id)
            .and_then(|active_pane_id| self.panes.get(active_pane_id))
    }
    pub fn get_active_pane_mut(&mut self, client_id: ClientId) -> Option<&mut Box<dyn Pane>> {
        self.active_panes
            .get(&client_id)
            .and_then(|active_pane_id| self.panes.get_mut(active_pane_id))
    }
    pub fn panes_are_visible(&self) -> bool {
        self.show_panes && !*self.fullscreen_covers_ui.borrow()
    }
    pub fn panes_are_shown(&self) -> bool {
        self.show_panes
    }
    pub fn has_active_panes(&self) -> bool {
        !self.active_panes.is_empty()
    }
    pub fn has_panes(&self) -> bool {
        !self.panes.is_empty()
    }
    pub fn active_pane_id(&self, client_id: ClientId) -> Option<PaneId> {
        self.active_panes.get(&client_id).copied()
    }
    pub fn get_pane_z_index(&self, pane_id: PaneId) -> Option<usize> {
        self.z_indices.iter().position(|id| id == &pane_id)
    }
    pub fn active_pane_id_or_focused_pane_id(&self, client_id: Option<ClientId>) -> Option<PaneId> {
        // returns the focused pane of any client_id - should be safe because the way things are
        // set up at the time of writing, all clients are focused on the same floating pane due to
        // z_index issues
        client_id
            .and_then(|client_id| self.active_panes.get(&client_id).copied())
            .or_else(|| self.panes.keys().next().copied())
    }
    pub fn toggle_show_panes(&mut self, should_show_floating_panes: bool) {
        self.window_title = None; // clear so that it will be re-rendered once we toggle back
        self.show_panes = should_show_floating_panes;
        if should_show_floating_panes {
            self.active_panes.focus_all_panes(&mut self.panes);
        } else {
            self.active_panes.unfocus_all_panes(&mut self.panes);
        }
    }
    pub fn active_panes_contain(&self, client_id: &ClientId) -> bool {
        self.active_panes.contains_key(client_id)
    }
    pub fn panes_contain(&self, pane_id: &PaneId) -> bool {
        self.panes.contains_key(pane_id)
    }
    pub fn find_room_for_new_pane(&mut self) -> Option<PaneGeom> {
        let display_area = *self.display_area.borrow();
        let viewport = *self.viewport.borrow();
        let floating_pane_grid = FloatingPaneGrid::new(
            &mut self.panes,
            &mut self.desired_pane_positions,
            display_area,
            viewport,
        );
        floating_pane_grid.find_room_for_new_pane()
    }
    pub fn move_client_focus_to_existing_panes(&mut self) {
        let existing_pane_ids: Vec<PaneId> = self.panes.keys().copied().collect();
        let nonexisting_panes_that_are_focused = self
            .active_panes
            .values()
            .filter(|pane_id| !existing_pane_ids.contains(pane_id))
            .copied()
            .collect::<Vec<_>>();
        for pane_id in nonexisting_panes_that_are_focused {
            self.move_clients_out_of_pane(pane_id);
        }
    }
    pub fn position_floating_pane_layout(
        &mut self,
        floating_pane_layout: &FloatingPaneLayout,
    ) -> Result<PaneGeom> {
        let err_context = || format!("failed to find position for floating pane");
        let display_area = *self.display_area.borrow();
        let viewport = *self.viewport.borrow();
        let floating_pane_grid = FloatingPaneGrid::new(
            &mut self.panes,
            &mut self.desired_pane_positions,
            display_area,
            viewport,
        );
        let mut position = floating_pane_grid
            .find_room_for_new_pane()
            .with_context(err_context)?;
        position.apply_floating_pane_position(
            floating_pane_layout.x.clone(),
            floating_pane_layout.y.clone(),
            floating_pane_layout.width.clone(),
            floating_pane_layout.height.clone(),
            viewport.cols,
            viewport.rows,
        );
        if let Some(is_pinned) = &floating_pane_layout.pinned {
            position.is_pinned = *is_pinned;
        }
        if let Some(logical_position) = &floating_pane_layout.logical_position {
            position.logical_position = Some(*logical_position);
        }
        if position.cols.as_usize() > viewport.cols {
            position.cols = Dimension::fixed(viewport.cols);
        }
        if position.rows.as_usize() > viewport.rows {
            position.rows = Dimension::fixed(viewport.rows);
        }
        if position.x + position.cols.as_usize() > viewport.cols {
            position.x = position
                .x
                .saturating_sub((position.x + position.cols.as_usize()) - viewport.cols);
        }
        if position.y + position.rows.as_usize() > viewport.rows {
            position.y = position
                .y
                .saturating_sub((position.y + position.rows.as_usize()) - viewport.rows);
        }
        Ok(position)
    }
    pub fn first_floating_pane_id(&self) -> Option<PaneId> {
        self.panes.keys().next().copied()
    }
    pub fn last_floating_pane_id(&self) -> Option<PaneId> {
        self.panes.keys().last().copied()
    }
    pub fn last_selectable_floating_pane_id(&self) -> Option<PaneId> {
        self.panes
            .iter()
            .filter(|(_p_id, p)| p.selectable())
            .last()
            .map(|(p_id, _p)| *p_id)
    }
    pub fn has_selectable_panes(&self) -> bool {
        self.panes
            .iter()
            .filter(|(_p_id, p)| p.selectable())
            .last()
            .is_some()
    }
    pub fn first_active_floating_pane_id(&self) -> Option<PaneId> {
        self.active_panes.values().next().copied()
    }
    pub fn set_force_render(&mut self) {
        for pane in self.panes.values_mut() {
            pane.set_should_render(true);
            pane.set_should_render_boundaries(true);
            pane.render_full_viewport();
        }
    }
    pub fn set_pane_frames(&mut self) -> Result<()> {
        let err_context =
            |pane_id: &PaneId| format!("failed to activate frame on pane {pane_id:?}");

        let no_ui_fullscreen_pane_id = if self.floating_fullscreen_covers_ui {
            self.fullscreen_pane_id
        } else {
            None
        };
        let regular_fullscreen_pane_id = if self.floating_fullscreen_covers_ui {
            None
        } else {
            self.fullscreen_pane_id
        };
        let pane_frame_style = self.pane_frame_style;
        for pane in self.panes.values_mut() {
            if no_ui_fullscreen_pane_id == Some(pane.pid()) {
                pane.set_frame(false);
                pane.set_content_offset(Offset::default());
                resize_pty!(pane, os_api, self.senders, self.character_cell_size)
                    .with_context(|| err_context(&pane.pid()))?;
                continue;
            }
            if regular_fullscreen_pane_id == Some(pane.pid()) {
                if pane.borderless() {
                    pane.set_frame(false);
                    pane.set_content_offset(Offset::default());
                } else if pane_frame_style.draws_full_frames() {
                    pane.set_frame(true);
                    pane.set_content_offset(Offset::frame(1));
                } else if pane_frame_style.draws_titles() {
                    pane.set_frame(false);
                    pane.set_content_offset(Offset::shift_right_top_and_bottom(0, 1, 0));
                } else {
                    pane.set_frame(false);
                    pane.set_content_offset(Offset::default());
                }
                resize_pty!(pane, os_api, self.senders, self.character_cell_size)
                    .with_context(|| err_context(&pane.pid()))?;
                continue;
            }
            // floating panes should always have a frame unless explicitly set otherwise
            if !pane.borderless() {
                pane.set_frame(true);
                pane.set_content_offset(Offset::frame(1));
            } else {
                pane.set_content_offset(Offset::default());
            }
            resize_pty!(pane, os_api, self.senders, self.character_cell_size)
                .with_context(|| err_context(&pane.pid()))?;
        }
        Ok(())
    }
    pub fn render(
        &mut self,
        output: &mut Output,
        mouse_hover_pane_id: &HashMap<ClientId, PaneId>,
        current_pane_group: HashMap<ClientId, Vec<PaneId>>,
        client_id_override: Option<ClientId>,
        help_text_visible: &HashMap<ClientId, bool>,
        mouse_scroll_resize: bool,
        mouse_hover_tips: bool,
    ) -> Result<()> {
        let err_context = || "failed to render output";
        let mut connected_clients: HashSet<ClientId> =
            { self.connected_clients.borrow().iter().copied().collect() };

        // If we have a client_id_override (for watcher rendering), add it temporarily
        if let Some(override_id) = client_id_override {
            connected_clients.insert(override_id);
        }

        let connected_clients: Vec<ClientId> = connected_clients.into_iter().collect();
        let active_panes = if self.panes_are_visible() {
            self.active_panes.clone_active_panes()
        } else {
            Default::default()
        };

        for (kind, pane) in &self.panes {
            if let Some(fullscreen_pane_id) = self.fullscreen_pane_id {
                if kind != &fullscreen_pane_id {
                    continue;
                }
            }
            match kind {
                PaneId::Terminal(_) => {
                    output.add_pane_contents(
                        &connected_clients,
                        pane.pid(),
                        pane.pane_contents(None, false, None),
                    );
                    if output.collect_ansi_pane_contents {
                        output.add_pane_contents_with_ansi(
                            &connected_clients,
                            pane.pid(),
                            pane.pane_contents_with_ansi(None, false, None),
                        );
                    }
                },
                PaneId::Plugin(_) => {
                    for client_id in &connected_clients {
                        output.add_pane_contents(
                            &[*client_id],
                            pane.pid(),
                            pane.pane_contents(Some(*client_id), false, None),
                        );
                        if output.collect_ansi_pane_contents {
                            output.add_pane_contents_with_ansi(
                                &[*client_id],
                                pane.pid(),
                                pane.pane_contents_with_ansi(Some(*client_id), false, None),
                            );
                        }
                    }
                },
            }
        }

        let fullscreen_pane_id = self.fullscreen_pane_id;
        let no_ui_fullscreen = self.floating_fullscreen_covers_ui && fullscreen_pane_id.is_some();
        let regular_fullscreen =
            fullscreen_pane_id.is_some() && !self.floating_fullscreen_covers_ui;
        let pane_frame_style = self.pane_frame_style;
        let mut floating_panes: Vec<_> = if let Some(fullscreen_pane_id) = fullscreen_pane_id {
            self.panes
                .iter_mut()
                .filter(|(id, _)| **id == fullscreen_pane_id)
                .collect()
        } else if self.panes_are_visible() {
            self.panes.iter_mut().collect()
        } else if self.has_pinned_panes() {
            self.panes
                .iter_mut()
                .filter(|(_, p)| p.position_and_size().is_pinned)
                .collect()
        } else {
            vec![]
        };
        floating_panes.sort_by(|(a_id, _a_pane), (b_id, _b_pane)| {
            let a_pos = self.z_indices.iter().position(|id| id == *a_id);
            let b_pos = self.z_indices.iter().position(|id| id == *b_id);
            a_pos.cmp(&b_pos)
        });
        for (z_index, (kind, pane)) in floating_panes.iter_mut().enumerate() {
            let mut active_panes = active_panes.clone();
            let multiple_users_exist_in_session =
                { self.connected_clients_in_app.borrow().len() > 1 };
            active_panes.retain(|c_id, _| self.connected_clients.borrow().contains(c_id));
            let pane_is_selectable = pane.selectable();
            let show_help_text = active_panes.iter().any(|(client_id, pane_id)| {
                pane_id == &pane.pid() && help_text_visible.get(client_id).copied().unwrap_or(false)
            });
            let pane_has_guest_modal = pane.has_guest_modal_for_any_client();
            let pane_is_regular_fullscreen =
                regular_fullscreen && Some(**kind) == fullscreen_pane_id;
            let should_draw_pane_frames = if pane_is_regular_fullscreen {
                pane_frame_style.draws_full_frames()
            } else {
                true
            };
            let mut pane_contents_and_ui = PaneContentsAndUi::new(
                pane,
                output,
                self.style,
                &active_panes,
                multiple_users_exist_in_session,
                Some(z_index + 1), // +1 because 0 is reserved for non-floating panes
                false,
                false,
                should_draw_pane_frames,
                mouse_hover_pane_id,
                current_pane_group.clone(),
                show_help_text,
                false,
                mouse_scroll_resize,
                mouse_hover_tips,
                self.dimmed_clients.clone(),
            );
            for client_id in &connected_clients {
                let client_mode = self
                    .mode_info
                    .borrow()
                    .get(client_id)
                    .unwrap_or(&self.default_mode_info)
                    .mode;
                let is_floating = true;
                let skip_frame = no_ui_fullscreen
                    || (pane_is_regular_fullscreen
                        && !pane_frame_style.draws_full_frames()
                        && !pane_frame_style.draws_titles());
                if !skip_frame {
                    pane_contents_and_ui
                        .render_pane_frame(
                            *client_id,
                            client_mode,
                            self.session_is_mirrored,
                            is_floating,
                            pane_is_selectable,
                        )
                        .with_context(err_context)?;
                }
                if let PaneId::Plugin(..) = kind {
                    pane_contents_and_ui
                        .render_pane_contents_for_client(*client_id)
                        .with_context(err_context)?;
                }
                pane_contents_and_ui.render_terminal_title_if_needed(
                    *client_id,
                    client_mode,
                    &mut self.window_title,
                );
                // this is done for panes that don't have their own cursor (eg. panes of
                // another user)
                pane_contents_and_ui
                    .render_fake_cursor_if_needed(*client_id)
                    .with_context(err_context)?;
            }
            if let PaneId::Terminal(..) = kind {
                if pane_has_guest_modal {
                    let (modal_clients, plain_clients): (Vec<ClientId>, Vec<ClientId>) =
                        connected_clients.iter().copied().partition(|client_id| {
                            pane_contents_and_ui.client_has_guest_modal(*client_id)
                        });
                    if !plain_clients.is_empty() {
                        pane_contents_and_ui
                            .render_pane_contents_to_multiple_clients(plain_clients.iter().copied())
                            .with_context(err_context)?;
                    } else {
                        pane_contents_and_ui.drain_pane_render_state();
                    }
                    for client_id in modal_clients {
                        pane_contents_and_ui
                            .render_guest_modal_for_client(client_id)
                            .with_context(err_context)?;
                    }
                } else {
                    pane_contents_and_ui
                        .render_pane_contents_to_multiple_clients(connected_clients.iter().copied())
                        .with_context(err_context)?;
                }
            }
        }
        Ok(())
    }

    pub fn resize(&mut self, new_screen_size: Size) {
        let display_area = *self.display_area.borrow();
        let viewport = *self.viewport.borrow();
        let mut floating_pane_grid = FloatingPaneGrid::new(
            &mut self.panes,
            &mut self.desired_pane_positions,
            display_area,
            viewport,
        );
        floating_pane_grid.resize(new_screen_size).non_fatal();
        self.set_force_render();
    }

    pub fn resize_pty_all_panes(&mut self, _os_api: &mut Box<dyn ServerOsApi>) -> Result<()> {
        for pane in self.panes.values_mut() {
            resize_pty!(pane, os_api, self.senders, self.character_cell_size)
                .with_context(|| format!("failed to resize PTY in pane {:?}", pane.pid()))?;
        }
        Ok(())
    }

    pub fn resize_active_pane(
        &mut self,
        client_id: ClientId,
        _os_api: &mut Box<dyn ServerOsApi>,
        strategy: &ResizeStrategy,
    ) -> Result<bool> {
        // true => successfully resized
        if let Some(active_floating_pane_id) = self.active_panes.get(&client_id) {
            return self.resize_pane_with_id(*strategy, *active_floating_pane_id);
        }
        Ok(false)
    }
    pub fn resize_pane_with_id(
        &mut self,
        strategy: ResizeStrategy,
        pane_id: PaneId,
    ) -> Result<bool> {
        // true => successfully resized
        let err_context = || format!("Failed to resize pane with id: {:?}", pane_id);
        let display_area = *self.display_area.borrow();
        let viewport = *self.viewport.borrow();
        let mut floating_pane_grid = FloatingPaneGrid::new(
            &mut self.panes,
            &mut self.desired_pane_positions,
            display_area,
            viewport,
        );
        floating_pane_grid
            .change_pane_size(
                &pane_id,
                &strategy,
                (RESIZE_INCREMENT_WIDTH, RESIZE_INCREMENT_HEIGHT),
            )
            .with_context(err_context)?;

        for pane in self.panes.values_mut() {
            resize_pty!(pane, os_api, self.senders, self.character_cell_size)
                .with_context(err_context)?;
        }
        self.set_force_render();
        Ok(true)
    }

    pub fn resize_pane_with_strategies(
        &mut self,
        pane_id: PaneId,
        strategies: &[ResizeStrategy],
        change_by: (usize, usize),
    ) -> Result<()> {
        let err_context = || format!("Failed to resize pane {:?} with strategies", pane_id);
        let display_area = *self.display_area.borrow();
        let viewport = *self.viewport.borrow();
        let mut floating_pane_grid = FloatingPaneGrid::new(
            &mut self.panes,
            &mut self.desired_pane_positions,
            display_area,
            viewport,
        );

        // Apply each strategy
        for strategy in strategies {
            floating_pane_grid
                .change_pane_size(&pane_id, strategy, change_by)
                .with_context(err_context)?;
        }

        for pane in self.panes.values_mut() {
            resize_pty!(pane, os_api, self.senders, self.character_cell_size)
                .with_context(err_context)?;
        }
        self.set_force_render();
        Ok(())
    }

    fn set_pane_active_at(&mut self, pane_id: PaneId) {
        if let Some(pane) = self.panes.get_mut(&pane_id) {
            pane.set_active_at(Instant::now());
        }
    }

    fn reenter_fullscreen_on_active_pane(&mut self, client_id: ClientId, covers_ui: bool) {
        if let Some(active_pane_id) = self.get_active_pane_id(client_id) {
            self.set_fullscreen(active_pane_id, covers_ui);
        }
    }
    pub fn move_focus(
        &mut self,
        client_id: ClientId,
        connected_clients: &HashSet<ClientId>,
        direction: &Direction,
    ) -> Result<bool> {
        // true => successfully moved
        let _err_context = || {
            format!("failed to move focus of floating pane {direction:?} for client {client_id}")
        };
        if self.fullscreen_pane_id.is_some() {
            let covers_ui = self.floating_fullscreen_covers_ui;
            self.unset_fullscreen();
            let ret = self.move_focus(client_id, connected_clients, direction);
            self.reenter_fullscreen_on_active_pane(client_id, covers_ui);
            return ret;
        }

        let display_area = *self.display_area.borrow();
        let viewport = *self.viewport.borrow();
        let active_pane_id = self.active_panes.get(&client_id).copied();
        let updated_active_pane = if let Some(active_pane_id) = active_pane_id {
            let floating_pane_grid = FloatingPaneGrid::new(
                &mut self.panes,
                &mut self.desired_pane_positions,
                display_area,
                viewport,
            );
            let next_index = match direction {
                Direction::Left => {
                    floating_pane_grid.next_selectable_pane_id_to_the_left(&active_pane_id)
                },
                Direction::Down => {
                    floating_pane_grid.next_selectable_pane_id_below(&active_pane_id)
                },
                Direction::Up => floating_pane_grid.next_selectable_pane_id_above(&active_pane_id),
                Direction::Right => {
                    floating_pane_grid.next_selectable_pane_id_to_the_right(&active_pane_id)
                },
            };
            match next_index {
                Some(p) => {
                    // render previously active pane so that its frame does not remain actively
                    // colored
                    let Some(previously_active_pane) = self.get_active_pane_mut(client_id) else {
                        log::error!("Failed to get active pane");
                        return Ok(false);
                    };

                    previously_active_pane.set_should_render(true);
                    // we render the full viewport to remove any ui elements that might have been
                    // there before (eg. another user's cursor)
                    previously_active_pane.render_full_viewport();

                    let Some(next_active_pane) = self.get_pane_mut(p) else {
                        log::error!("Failed to get next active pane");
                        return Ok(false);
                    };
                    next_active_pane.set_should_render(true);
                    // we render the full viewport to remove any ui elements that might have been
                    // there before (eg. another user's cursor)
                    next_active_pane.render_full_viewport();

                    // move all clients
                    let connected_clients: Vec<ClientId> =
                        connected_clients.iter().copied().collect();
                    for client_id in connected_clients {
                        self.focus_pane(p, client_id);
                    }
                    self.set_pane_active_at(p);

                    self.set_force_render();
                    return Ok(true);
                },
                None => Some(active_pane_id),
            }
        } else {
            active_pane_id
        };
        match updated_active_pane {
            Some(updated_active_pane) => {
                let connected_clients: Vec<ClientId> = connected_clients.iter().copied().collect();
                for client_id in connected_clients {
                    self.focus_pane(updated_active_pane, client_id);
                }
                self.set_pane_active_at(updated_active_pane);
                self.set_force_render();
            },
            None => {
                // TODO: can this happen?
                self.active_panes.clear(&mut self.panes);
                self.z_indices.clear();
            },
        }
        Ok(false)
    }
    pub fn focus_pane_on_edge(&mut self, direction: Direction, client_id: ClientId) {
        if self.fullscreen_pane_id.is_some() {
            let covers_ui = self.floating_fullscreen_covers_ui;
            self.unset_fullscreen();
            self.focus_pane_on_edge(direction, client_id);
            self.reenter_fullscreen_on_active_pane(client_id, covers_ui);
            return;
        }
        let display_area = *self.display_area.borrow();
        let viewport = *self.viewport.borrow();
        let floating_pane_grid = FloatingPaneGrid::new(
            &mut self.panes,
            &mut self.desired_pane_positions,
            display_area,
            viewport,
        );
        if let Some(pane_id) = floating_pane_grid.pane_id_on_edge(direction) {
            self.focus_pane(pane_id, client_id);
            self.set_force_render();
        }
    }
    pub fn focus_pane_adjacent_to(
        &mut self,
        pane_id: PaneId,
        direction: Direction,
        client_id: ClientId,
    ) -> Option<PaneId> {
        if !self.panes.contains_key(&pane_id) {
            return None;
        }
        let display_area = *self.display_area.borrow();
        let viewport = *self.viewport.borrow();
        let floating_pane_grid = FloatingPaneGrid::new(
            &mut self.panes,
            &mut self.desired_pane_positions,
            display_area,
            viewport,
        );
        let next_index = match direction {
            Direction::Left => floating_pane_grid.next_selectable_pane_id_to_the_left(&pane_id),
            Direction::Down => floating_pane_grid.next_selectable_pane_id_below(&pane_id),
            Direction::Up => floating_pane_grid.next_selectable_pane_id_above(&pane_id),
            Direction::Right => floating_pane_grid.next_selectable_pane_id_to_the_right(&pane_id),
        }?;
        if let Some(previously_active_pane) = self.get_active_pane_mut(client_id) {
            previously_active_pane.set_should_render(true);
            previously_active_pane.render_full_viewport();
        }
        if let Some(next_active_pane) = self.get_pane_mut(next_index) {
            next_active_pane.set_should_render(true);
            next_active_pane.render_full_viewport();
        }
        self.focus_pane(next_index, client_id);
        self.set_pane_active_at(next_index);
        self.set_force_render();
        Some(next_index)
    }

    pub fn focus_last_pane(&mut self, client_id: ClientId) {
        if self.fullscreen_pane_id.is_some() {
            let covers_ui = self.floating_fullscreen_covers_ui;
            self.unset_fullscreen();
            self.focus_last_pane(client_id);
            self.reenter_fullscreen_on_active_pane(client_id, covers_ui);
            return;
        }
        let currently_active_pane_id = self.active_panes.get(&client_id).copied();
        let last_pane_id = self
            .active_panes
            .get_last(&client_id)
            .copied()
            .filter(|last_pane_id| {
                Some(*last_pane_id) != currently_active_pane_id
                    && self
                        .panes
                        .get(last_pane_id)
                        .map(|p| p.selectable())
                        .unwrap_or(false)
            });
        let target_pane_id = last_pane_id
            .or_else(|| self.most_recently_active_selectable_pane(currently_active_pane_id));
        if let Some(target_pane_id) = target_pane_id {
            self.focus_pane(target_pane_id, client_id);
            self.set_force_render();
        }
    }
    fn most_recently_active_selectable_pane(&self, exclude: Option<PaneId>) -> Option<PaneId> {
        self.panes
            .iter()
            .filter(|(pane_id, pane)| Some(**pane_id) != exclude && pane.selectable())
            .max_by_key(|(_pane_id, pane)| pane.active_at())
            .map(|(pane_id, _pane)| *pane_id)
    }

    pub fn move_active_pane_down(&mut self, client_id: ClientId) {
        if let Some(active_pane_id) = self.active_panes.get(&client_id) {
            self.move_pane_down(*active_pane_id);
        }
    }
    pub fn move_pane_down(&mut self, pane_id: PaneId) {
        if self.fullscreen_pane_id.is_some() {
            return;
        }
        let display_area = *self.display_area.borrow();
        let viewport = *self.viewport.borrow();
        let mut floating_pane_grid = FloatingPaneGrid::new(
            &mut self.panes,
            &mut self.desired_pane_positions,
            display_area,
            viewport,
        );
        floating_pane_grid.move_pane_down(&pane_id).non_fatal();
        self.set_force_render();
    }
    pub fn move_active_pane_up(&mut self, client_id: ClientId) {
        if let Some(active_pane_id) = self.active_panes.get(&client_id) {
            self.move_pane_up(*active_pane_id);
        }
    }
    pub fn move_pane_up(&mut self, pane_id: PaneId) {
        if self.fullscreen_pane_id.is_some() {
            return;
        }
        let display_area = *self.display_area.borrow();
        let viewport = *self.viewport.borrow();
        let mut floating_pane_grid = FloatingPaneGrid::new(
            &mut self.panes,
            &mut self.desired_pane_positions,
            display_area,
            viewport,
        );
        floating_pane_grid.move_pane_up(&pane_id).non_fatal();
        self.set_force_render();
    }
    pub fn move_active_pane_left(&mut self, client_id: ClientId) {
        if let Some(active_pane_id) = self.active_panes.get(&client_id) {
            self.move_pane_left(*active_pane_id);
        }
    }
    pub fn move_pane_left(&mut self, pane_id: PaneId) {
        if self.fullscreen_pane_id.is_some() {
            return;
        }
        let display_area = *self.display_area.borrow();
        let viewport = *self.viewport.borrow();
        let mut floating_pane_grid = FloatingPaneGrid::new(
            &mut self.panes,
            &mut self.desired_pane_positions,
            display_area,
            viewport,
        );
        floating_pane_grid.move_pane_left(&pane_id).non_fatal();
        self.set_force_render();
    }
    pub fn move_active_pane_right(&mut self, client_id: ClientId) {
        if let Some(active_pane_id) = self.active_panes.get(&client_id) {
            self.move_pane_right(*active_pane_id);
        }
    }
    pub fn move_pane_right(&mut self, pane_id: PaneId) {
        if self.fullscreen_pane_id.is_some() {
            return;
        }
        let display_area = *self.display_area.borrow();
        let viewport = *self.viewport.borrow();
        let mut floating_pane_grid = FloatingPaneGrid::new(
            &mut self.panes,
            &mut self.desired_pane_positions,
            display_area,
            viewport,
        );
        floating_pane_grid.move_pane_right(&pane_id).non_fatal();
        self.set_force_render();
    }
    pub fn move_active_pane(
        &mut self,
        search_backwards: bool,
        _os_api: &mut Box<dyn ServerOsApi>,
        client_id: ClientId,
    ) {
        if let Some(active_pane_id) = self.get_active_pane_id(client_id) {
            self.move_pane(search_backwards, active_pane_id)
        }
    }
    pub fn move_pane(&mut self, search_backwards: bool, pane_id: PaneId) {
        if self.fullscreen_pane_id.is_some() {
            return;
        }
        let new_position_id = {
            let pane_grid = FloatingPaneGrid::new(
                &mut self.panes,
                &mut self.desired_pane_positions,
                *self.display_area.borrow(),
                *self.viewport.borrow(),
            );
            if search_backwards {
                pane_grid.previous_selectable_pane_id(&pane_id)
            } else {
                pane_grid.next_selectable_pane_id(&pane_id)
            }
        };
        if let Some(new_position_id) = new_position_id {
            let Some(current_position) = self.panes.get(&pane_id) else {
                log::error!("Failed to find current position");
                return;
            };
            let prev_geom = current_position.position_and_size();
            let prev_geom_override = current_position.geom_override();

            let Some(new_position) = self.panes.get_mut(&new_position_id) else {
                log::error!("Failed to find new position");
                return;
            };
            let next_geom = new_position.position_and_size();
            let next_geom_override = new_position.geom_override();
            new_position.set_geom(prev_geom);
            if let Some(geom) = prev_geom_override {
                new_position.set_geom_override(geom);
            }
            new_position.set_should_render(true);

            let Some(current_position) = self.panes.get_mut(&pane_id) else {
                log::error!("Failed to find current position");
                return;
            };
            current_position.set_geom(next_geom);
            if let Some(geom) = next_geom_override {
                current_position.set_geom_override(geom);
            }
            current_position.set_should_render(true);
            let _ = self.set_pane_frames();
        }
    }
    pub fn change_pane_coordinates(
        &mut self,
        pane_id: PaneId,
        new_coordinates: FloatingPaneCoordinates,
    ) -> Result<()> {
        let err_context = || format!("Failed to change_pane_coordinates");

        {
            let viewport = { self.viewport.borrow().clone() };
            let pane = self.get_pane_mut(pane_id).with_context(err_context)?;
            let mut pane_geom = pane.position_and_size();
            if let Some(pinned) = new_coordinates.pinned.as_ref() {
                pane.set_pinned(*pinned);
            }
            if let Some(should_be_borderless) = new_coordinates.borderless {
                if should_be_borderless {
                    pane.set_borderless(true);
                } else {
                    pane.set_borderless(false);
                };
            }
            pane_geom.adjust_coordinates(new_coordinates, viewport);
            pane.set_geom(pane_geom);
            pane.set_should_render(true);

            // we do this in case this moves the pane under another pane so that the pane user's
            // are focused on will always be on top
            let is_focused = self.active_panes.pane_id_is_focused(&pane_id);
            if is_focused {
                self.z_indices.retain(|p_id| *p_id != pane_id);
                self.z_indices.push(pane_id);
                self.make_sure_pinned_panes_are_on_top();
            }

            self.desired_pane_positions.insert(pane_id, pane_geom);
        }
        let _ = self.set_pane_frames();
        Ok(())
    }
    pub fn move_clients_out_of_pane(&mut self, pane_id: PaneId) {
        let active_panes: Vec<(ClientId, PaneId)> = self
            .active_panes
            .iter()
            .map(|(cid, pid)| (*cid, *pid))
            .collect();

        // find the most recently active pane
        let mut next_active_pane_candidates: Vec<(&PaneId, &Box<dyn Pane>)> = self
            .panes
            .iter()
            .filter(|(_p_id, p)| p.selectable())
            .collect();
        next_active_pane_candidates.sort_by(|(_pane_id_a, pane_a), (_pane_id_b, pane_b)| {
            pane_a.active_at().cmp(&pane_b.active_at())
        });
        let next_active_pane_id = next_active_pane_candidates
            .last()
            .map(|(pane_id, _pane)| **pane_id);
        let last_active_pane_id = next_active_pane_candidates
            .iter()
            .rev()
            .nth(1)
            .map(|(pane_id, _pane)| **pane_id);

        for (client_id, active_pane_id) in active_panes {
            if active_pane_id == pane_id {
                match next_active_pane_id {
                    Some(next_active_pane_id) => {
                        self.active_panes
                            .insert(client_id, next_active_pane_id, &mut self.panes);
                        self.focus_pane(next_active_pane_id, client_id);
                        if let Some(last_active_pane_id) = last_active_pane_id {
                            self.active_panes
                                .set_last_pane(client_id, last_active_pane_id);
                        }
                    },
                    None => {
                        self.defocus_pane(client_id);
                    },
                }
            }
        }
    }
    pub fn focus_pane_for_all_clients(&mut self, pane_id: PaneId) {
        let connected_clients: Vec<ClientId> =
            self.connected_clients.borrow().iter().copied().collect();
        for client_id in connected_clients {
            self.active_panes
                .insert(client_id, pane_id, &mut self.panes);
        }
        self.z_indices.retain(|p_id| *p_id != pane_id);
        self.z_indices.push(pane_id);
        self.make_sure_pinned_panes_are_on_top();
        self.set_pane_active_at(pane_id);
        self.set_force_render();
    }
    pub fn fullscreen_is_active(&self) -> bool {
        self.fullscreen_pane_id.is_some()
    }
    pub fn fullscreen_pane_id(&self) -> Option<PaneId> {
        self.fullscreen_pane_id
    }
    pub fn fullscreen_covers_ui(&self) -> bool {
        self.fullscreen_pane_id.is_some() && self.floating_fullscreen_covers_ui
    }
    fn expand_pane_over_area(
        &mut self,
        pane_id: PaneId,
        x: usize,
        y: usize,
        rows: usize,
        cols: usize,
    ) {
        if let Some(pane) = self.panes.get_mut(&pane_id) {
            let mut expanded_geom = PaneGeom {
                x,
                y,
                ..Default::default()
            };
            expanded_geom.rows.set_inner(rows);
            expanded_geom.cols.set_inner(cols);
            pane.set_geom_override(expanded_geom);
        }
    }
    fn set_fullscreen(&mut self, pane_id: PaneId, covers_ui: bool) {
        if !self.panes.contains_key(&pane_id) {
            log::error!("No floating pane with id: {:?} found", pane_id);
            return;
        }
        if covers_ui {
            let display_area = { *self.display_area.borrow() };
            self.expand_pane_over_area(pane_id, 0, 0, display_area.rows, display_area.cols);
        } else {
            let viewport = { *self.viewport.borrow() };
            self.expand_pane_over_area(
                pane_id,
                viewport.x,
                viewport.y,
                viewport.rows,
                viewport.cols,
            );
        }
        let pane_frame_style = self.pane_frame_style;
        if let Some(pane) = self.panes.get_mut(&pane_id) {
            if covers_ui {
                pane.set_frame(false);
                pane.set_content_offset(Offset::default());
            } else if pane.borderless() {
                pane.set_frame(false);
                pane.set_content_offset(Offset::default());
            } else if pane_frame_style.draws_full_frames() {
                pane.set_frame(true);
                pane.set_content_offset(Offset::frame(1));
            } else if pane_frame_style.draws_titles() {
                pane.set_frame(false);
                pane.set_content_offset(Offset::shift_right_top_and_bottom(0, 1, 0));
            } else {
                pane.set_frame(false);
                pane.set_content_offset(Offset::default());
            }
        }
        let _ = resize_pty!(
            self.panes.get_mut(&pane_id).unwrap(),
            os_api,
            self.senders,
            self.character_cell_size
        );
        self.fullscreen_pane_id = Some(pane_id);
        self.floating_fullscreen_covers_ui = covers_ui;
        self.focus_pane_for_all_clients(pane_id);
        self.set_force_render();
    }
    pub fn unset_fullscreen(&mut self) {
        if let Some(fullscreen_pane_id) = self.fullscreen_pane_id.take() {
            if let Some(pane) = self.panes.get_mut(&fullscreen_pane_id) {
                pane.reset_size_and_position_override();
                if !pane.borderless() {
                    pane.set_frame(true);
                    pane.set_content_offset(Offset::frame(1));
                } else {
                    pane.set_content_offset(Offset::default());
                }
                let _ = resize_pty!(pane, os_api, self.senders, self.character_cell_size);
            }
            self.floating_fullscreen_covers_ui = false;
            self.set_force_render();
        }
    }
    pub fn toggle_pane_fullscreen(&mut self, pane_id: PaneId) {
        if self.fullscreen_pane_id.is_some() {
            let was_no_ui = self.floating_fullscreen_covers_ui;
            self.unset_fullscreen();
            if was_no_ui {
                self.set_fullscreen(pane_id, false);
            }
        } else {
            self.set_fullscreen(pane_id, false);
        }
    }
    pub fn toggle_pane_no_ui_fullscreen(&mut self, pane_id: PaneId) {
        if self.fullscreen_pane_id.is_some() {
            let was_no_ui = self.floating_fullscreen_covers_ui;
            self.unset_fullscreen();
            if !was_no_ui {
                self.set_fullscreen(pane_id, true);
            }
        } else {
            self.set_fullscreen(pane_id, true);
        }
    }
    pub fn toggle_active_pane_fullscreen(&mut self, client_id: ClientId) {
        if let Some(active_pane_id) = self
            .fullscreen_pane_id
            .or_else(|| self.get_active_pane_id(client_id))
        {
            self.toggle_pane_fullscreen(active_pane_id);
        }
    }
    pub fn toggle_active_pane_no_ui_fullscreen(&mut self, client_id: ClientId) {
        if let Some(active_pane_id) = self
            .fullscreen_pane_id
            .or_else(|| self.get_active_pane_id(client_id))
        {
            self.toggle_pane_no_ui_fullscreen(active_pane_id);
        }
    }
    pub fn focus_pane(&mut self, pane_id: PaneId, client_id: ClientId) {
        if let Some(focused_pane) = self.active_panes.get(&client_id) {
            if pane_id != *focused_pane {
                self.active_panes.set_last_pane(client_id, *focused_pane);
            }
        }
        let pane_is_selectable = self
            .panes
            .get(&pane_id)
            .map(|p| p.selectable())
            .unwrap_or(false);
        if !pane_is_selectable {
            log::error!("Cannot focus pane {:?} as it is not selectable!", pane_id);
            return;
        }
        self.active_panes
            .insert(client_id, pane_id, &mut self.panes);
        self.focus_pane_for_all_clients(pane_id);
    }
    pub fn focus_pane_if_client_not_focused(&mut self, pane_id: PaneId, client_id: ClientId) {
        match self.active_panes.get(&client_id) {
            Some(already_focused_pane_id) => self.focus_pane(*already_focused_pane_id, client_id),
            None => self.focus_pane(pane_id, client_id),
        }
    }
    pub fn focus_first_pane_if_client_not_focused(&mut self, client_id: ClientId) {
        match self.active_panes.get(&client_id) {
            Some(already_focused_pane_id) => self.focus_pane(*already_focused_pane_id, client_id),
            None => {
                if let Some(first_pane_id) = self.panes.iter().next().map(|(p_id, _)| *p_id) {
                    self.focus_pane(first_pane_id, client_id);
                }
            },
        }
    }
    pub fn defocus_pane(&mut self, client_id: ClientId) {
        self.active_panes.remove(&client_id, &mut self.panes);
        self.set_force_render();
    }
    pub fn get_pane(&self, pane_id: PaneId) -> Option<&Box<dyn Pane>> {
        self.panes.get(&pane_id)
    }
    pub fn get_pane_mut(&mut self, pane_id: PaneId) -> Option<&mut Box<dyn Pane>> {
        self.panes.get_mut(&pane_id)
    }
    pub fn get_pane_id_at(
        &self,
        point: &Position,
        search_selectable: bool,
    ) -> Result<Option<PaneId>> {
        let _err_context = || format!("failed to determine floating pane at point {point:?}");

        // TODO: better - loop through z-indices and check each one if it contains the point
        let mut panes: Vec<_> = if search_selectable {
            self.panes.iter().filter(|(_, p)| p.selectable()).collect()
        } else {
            self.panes.iter().collect()
        };
        panes.sort_by(|(a_id, _a_pane), (b_id, _b_pane)| {
            // TODO: continue
            Ord::cmp(
                &self
                    .z_indices
                    .iter()
                    .position(|id| id == *b_id)
                    .unwrap_or(0),
                &self
                    .z_indices
                    .iter()
                    .position(|id| id == *a_id)
                    .unwrap_or(0),
            )
        });
        Ok(panes
            .iter()
            .find(|(_, p)| p.contains(point))
            .map(|(&id, _)| id))
    }
    pub fn get_pinned_pane_id_at(
        &self,
        point: &Position,
        search_selectable: bool,
    ) -> Result<Option<PaneId>> {
        let _err_context = || format!("failed to determine floating pane at point {point:?}");

        let mut panes: Vec<_> = if search_selectable {
            self.panes
                .iter()
                .filter(|(_, p)| p.selectable())
                .filter(|(_, p)| p.current_geom().is_pinned)
                .collect()
        } else {
            self.panes
                .iter()
                .filter(|(_, p)| p.current_geom().is_pinned)
                .collect()
        };
        panes.sort_by(|(a_id, _a_pane), (b_id, _b_pane)| {
            // TODO: continue
            Ord::cmp(
                &self
                    .z_indices
                    .iter()
                    .position(|id| id == *b_id)
                    .unwrap_or(0),
                &self
                    .z_indices
                    .iter()
                    .position(|id| id == *a_id)
                    .unwrap_or(0),
            )
        });
        Ok(panes
            .iter()
            .find(|(_, p)| p.contains(point))
            .map(|(&id, _)| id))
    }
    pub fn has_pinned_pane_at(&self, point: &Position) -> bool {
        let mut panes: Vec<_> = self
            .panes
            .iter()
            .filter(|(_, p)| p.current_geom().is_pinned)
            .collect();

        panes.sort_by(|(a_id, _a_pane), (b_id, _b_pane)| {
            Ord::cmp(
                &self
                    .z_indices
                    .iter()
                    .position(|id| id == *b_id)
                    .unwrap_or(0),
                &self
                    .z_indices
                    .iter()
                    .position(|id| id == *a_id)
                    .unwrap_or(0),
            )
        });
        panes.iter().find(|(_, p)| p.contains(point)).is_some()
    }
    pub fn get_pane_at_mut(
        &mut self,
        position: &Position,
        search_selectable: bool,
    ) -> Option<&mut Box<dyn Pane>> {
        self.get_pane_id_at(position, search_selectable)
            .ok()?
            .and_then(|pane_id| self.panes.get_mut(&pane_id))
    }
    pub fn set_pane_being_moved_with_mouse(&mut self, pane_id: PaneId, last_position: Position) {
        if let Some((last_pane_id, initial_position)) = self
            .pane_being_moved_with_mouse
            .map(|(pane_id, initial_position, _)| (pane_id, initial_position))
        {
            if last_pane_id == pane_id {
                // preserve initial_position
                self.pane_being_moved_with_mouse = Some((pane_id, initial_position, last_position));
                return;
            }
        }
        self.pane_being_moved_with_mouse = Some((pane_id, last_position, last_position));
    }
    pub fn pane_is_being_moved_with_mouse(&self) -> bool {
        self.pane_being_moved_with_mouse.is_some()
    }
    pub fn move_pane_to_position(&mut self, click_position: &Position) -> bool {
        // true => changed position
        let display_area = *self.display_area.borrow();
        let viewport = *self.viewport.borrow();
        let Some((pane_id, _initial_position, previous_position)) =
            self.pane_being_moved_with_mouse
        else {
            return false;
        };
        if click_position == &previous_position {
            return false;
        }
        let move_x_by = click_position.column() as isize - previous_position.column() as isize;
        let move_y_by = click_position.line() as isize - previous_position.line() as isize;
        let mut floating_pane_grid = FloatingPaneGrid::new(
            &mut self.panes,
            &mut self.desired_pane_positions,
            display_area,
            viewport,
        );
        floating_pane_grid
            .move_pane_by(pane_id, move_x_by, move_y_by)
            .non_fatal();
        self.set_pane_being_moved_with_mouse(pane_id, *click_position);
        self.set_force_render();
        true
    }
    pub fn move_pane_with_mouse(&mut self, position: Position, search_selectable: bool) -> bool {
        // true => handled, false => not handled (eg. no pane at this position)
        if *self.fullscreen_covers_ui.borrow() || self.fullscreen_pane_id.is_some() {
            return false;
        }
        let show_panes = self.show_panes;
        if self.pane_being_moved_with_mouse.is_some() {
            if self.move_pane_to_position(&position) {
                // pane was moved to a new position
                self.set_force_render();
                return true;
            }
        } else if let Some(pane) = self.get_pane_at_mut(&position, search_selectable) {
            let clicked_on_frame = pane.position_is_on_frame(&position);
            if (show_panes || pane.position_and_size().is_pinned) && clicked_on_frame {
                let pid = pane.pid();
                if self.pane_being_moved_with_mouse.is_none() {
                    self.set_pane_being_moved_with_mouse(pid, position);
                }
                self.move_pane_to_position(&position);
                self.set_force_render();
                return true;
            }
        };
        false
    }
    pub fn stop_moving_pane_with_mouse(&mut self, position: Position) -> bool {
        // bool -> this
        // pane was never
        // moved (initial
        // position ==
        // last_position)
        let mut never_moved = false;
        if let Some((_pane_id, initial_position, _last_position)) =
            self.pane_being_moved_with_mouse.take()
        {
            if initial_position == position {
                never_moved = true;
            } else {
                self.move_pane_to_position(&position);
            }
            self.set_force_render();
        };
        self.pane_being_moved_with_mouse = None;
        never_moved
    }
    pub fn get_active_pane_id(&self, client_id: ClientId) -> Option<PaneId> {
        self.active_panes.get(&client_id).copied()
    }
    pub fn get_panes(&self) -> impl Iterator<Item = (&PaneId, &Box<dyn Pane>)> {
        self.panes.iter()
    }
    pub fn visible_panes_count(&self) -> usize {
        self.panes.len()
    }
    pub fn drain(&mut self) -> BTreeMap<PaneId, Box<dyn Pane>> {
        self.z_indices.clear();
        self.desired_pane_positions.clear();
        match self.panes.iter().next().map(|(pid, _p)| *pid) {
            Some(first_pid) => self.panes.split_off(&first_pid),
            None => BTreeMap::new(),
        }
    }
    fn move_clients_between_panes(&mut self, from_pane_id: PaneId, to_pane_id: PaneId) {
        let clients_in_pane: Vec<ClientId> = self
            .active_panes
            .iter()
            .filter(|(_cid, pid)| **pid == from_pane_id)
            .map(|(cid, _pid)| *cid)
            .collect();
        for client_id in clients_in_pane {
            self.active_panes.remove(&client_id, &mut self.panes);
            self.active_panes
                .insert(client_id, to_pane_id, &mut self.panes);
        }
    }
    pub fn reapply_pane_focus(&mut self) {
        if let Some(focused_pane) = self.first_active_floating_pane_id() {
            // floating pane focus is the same for all clients
            self.focus_pane_for_all_clients(focused_pane);
        }
    }
    pub fn switch_active_pane_with(&mut self, _os_api: &mut Box<dyn ServerOsApi>, pane_id: PaneId) {
        if let Some(active_pane_id) = self.first_active_floating_pane_id() {
            let Some(current_position) = self.panes.get(&active_pane_id) else {
                log::error!("Can't find current position");
                return;
            };
            let prev_geom = current_position.position_and_size();
            let prev_geom_override = current_position.geom_override();

            let Some(new_position) = self.panes.get_mut(&pane_id) else {
                log::error!("Can't find position");
                return;
            };
            let next_geom = new_position.position_and_size();
            let next_geom_override = new_position.geom_override();
            new_position.set_geom(prev_geom);
            if let Some(geom) = prev_geom_override {
                new_position.set_geom_override(geom);
            }
            resize_pty!(new_position, os_api, self.senders, self.character_cell_size).non_fatal();
            new_position.set_should_render(true);

            let Some(current_position) = self.panes.get_mut(&active_pane_id) else {
                log::error!("Can't find current position");
                return;
            };
            current_position.set_geom(next_geom);
            if let Some(geom) = next_geom_override {
                current_position.set_geom_override(geom);
            }
            resize_pty!(
                current_position,
                os_api,
                self.senders,
                self.character_cell_size
            )
            .non_fatal();
            current_position.set_should_render(true);
            self.focus_pane_for_all_clients(active_pane_id);
        }
    }
    pub fn get_plugin_pane_id(&self, run_plugin_or_alias: &RunPluginOrAlias) -> Option<PaneId> {
        self.panes
            .iter()
            .find(|(_id, pane)| run_plugin_or_alias.is_equivalent_to_run(pane.invoked_with()))
            .map(|(id, _)| *id)
    }
    pub fn focus_pane_if_exists(&mut self, pane_id: PaneId, client_id: ClientId) -> Result<()> {
        if self.panes.get(&pane_id).is_some() {
            self.focus_pane(pane_id, client_id);
            Ok(())
        } else {
            Err(anyhow!("Pane not found"))
        }
    }
    pub fn pane_info(&self, current_pane_group: &HashMap<ClientId, Vec<PaneId>>) -> Vec<PaneInfo> {
        let mut pane_infos = vec![];
        for (pane_id, pane) in self.panes.iter() {
            let mut pane_info_for_pane = pane_info_for_pane(pane_id, pane, current_pane_group);
            let is_focused = self.active_panes.pane_id_is_focused(pane_id);
            pane_info_for_pane.is_floating = true;
            pane_info_for_pane.is_suppressed = false;
            pane_info_for_pane.is_focused = is_focused;
            pane_info_for_pane.is_fullscreen = self.fullscreen_pane_id == Some(*pane_id);
            pane_infos.push(pane_info_for_pane);
        }
        pane_infos
    }
    pub fn set_geom_for_pane_with_run(&mut self, run: Option<Run>, geom: PaneGeom) {
        match self
            .panes
            .iter_mut()
            .find(|(_, p)| p.invoked_with() == &run)
        {
            Some((_, pane)) => {
                pane.set_geom(geom);
            },
            None => {
                log::error!("Failed to find pane with run: {:?}", run);
            },
        }
    }
    pub fn update_pane_themes(&mut self, theme: Styling) {
        self.style.colors = theme;
        for pane in self.panes.values_mut() {
            pane.update_theme(theme);
        }
    }
    pub fn update_pane_arrow_fonts(&mut self, should_support_arrow_fonts: bool) {
        for pane in self.panes.values_mut() {
            pane.update_arrow_fonts(should_support_arrow_fonts);
        }
    }
    pub fn update_pane_kitty_host_support(&mut self, supported: KittyHostSupport) {
        for pane in self.panes.values_mut() {
            pane.update_kitty_host_support(supported);
        }
    }
    pub fn update_pane_sixel_host_support(&mut self, supported: bool) {
        for pane in self.panes.values_mut() {
            pane.update_sixel_host_support(supported);
        }
    }
    pub fn update_pane_rounded_corners(&mut self, rounded_corners: bool) {
        self.style.rounded_corners = rounded_corners;
        for pane in self.panes.values_mut() {
            pane.update_rounded_corners(rounded_corners);
        }
    }
    pub fn next_selectable_pane_id_above(&mut self, pane_id: &PaneId) -> Option<PaneId> {
        let display_area = *self.display_area.borrow();
        let viewport = *self.viewport.borrow();
        let floating_pane_grid = FloatingPaneGrid::new(
            &mut self.panes,
            &mut self.desired_pane_positions,
            display_area,
            viewport,
        );
        floating_pane_grid.next_selectable_pane_id_above(&pane_id)
    }
    pub fn next_selectable_pane_id_below(&mut self, pane_id: &PaneId) -> Option<PaneId> {
        let display_area = *self.display_area.borrow();
        let viewport = *self.viewport.borrow();
        let floating_pane_grid = FloatingPaneGrid::new(
            &mut self.panes,
            &mut self.desired_pane_positions,
            display_area,
            viewport,
        );
        floating_pane_grid.next_selectable_pane_id_below(&pane_id)
    }
    pub fn next_selectable_pane_id_to_the_left(&mut self, pane_id: &PaneId) -> Option<PaneId> {
        let display_area = *self.display_area.borrow();
        let viewport = *self.viewport.borrow();
        let floating_pane_grid = FloatingPaneGrid::new(
            &mut self.panes,
            &mut self.desired_pane_positions,
            display_area,
            viewport,
        );
        floating_pane_grid.next_selectable_pane_id_to_the_left(&pane_id)
    }
    pub fn next_selectable_pane_id_to_the_right(&mut self, pane_id: &PaneId) -> Option<PaneId> {
        let display_area = *self.display_area.borrow();
        let viewport = *self.viewport.borrow();
        let floating_pane_grid = FloatingPaneGrid::new(
            &mut self.panes,
            &mut self.desired_pane_positions,
            display_area,
            viewport,
        );
        floating_pane_grid.next_selectable_pane_id_to_the_right(&pane_id)
    }
    fn make_sure_pinned_panes_are_on_top(&mut self) {
        let pinned_status: std::collections::HashMap<_, _> = self
            .z_indices
            .iter()
            .map(|id| (id.clone(), self.pane_id_is_pinned(id)))
            .collect();

        self.z_indices.sort_by(|a_id, b_id| {
            let a_is_pinned = pinned_status.get(a_id).copied().unwrap_or(false);
            let b_is_pinned = pinned_status.get(b_id).copied().unwrap_or(false);
            match (a_is_pinned, b_is_pinned) {
                (true, false) => Ordering::Greater,
                (false, true) => Ordering::Less,
                _ => Ordering::Equal,
            }
        });
    }
    fn pane_id_is_pinned(&self, pane_id: &PaneId) -> bool {
        self.panes
            .get(pane_id)
            .map(|p| p.position_and_size().is_pinned)
            .unwrap_or(false)
    }
}
