par-term 0.48.0

Cross-platform GPU-accelerated terminal emulator with inline graphics support (Sixel, iTerm2, Kitty)
//! Window arrangement save, restore, and management for the window manager.
//!
//! This module handles saving the current window layout as a named arrangement,
//! restoring arrangements by ID or name, and CRUD operations on stored arrangements.

use std::sync::Arc;
use winit::event_loop::ActiveEventLoop;
use winit::window::WindowId;

use crate::arrangements::ArrangementId;

use super::WindowManager;

impl WindowManager {
    /// Save the current window layout as an arrangement
    pub fn save_arrangement(&mut self, name: String, event_loop: &ActiveEventLoop) {
        // Remove existing arrangement with the same name (case-insensitive) to allow overwrite
        if let Some(existing) = self.arrangement_manager.find_by_name(&name) {
            let existing_id = existing.id;
            self.arrangement_manager.remove(&existing_id);
            log::info!("Overwriting existing arrangement '{}'", name);
        }

        let arrangement = crate::arrangements::capture::capture_arrangement(
            name.clone(),
            &self.windows,
            event_loop,
        );
        log::info!(
            "Saved arrangement '{}' with {} windows",
            name,
            arrangement.windows.len()
        );
        self.arrangement_manager.add(arrangement);
        if let Err(e) = crate::arrangements::storage::save_arrangements(&self.arrangement_manager) {
            log::error!("Failed to save arrangements: {}", e);
        }
        self.sync_arrangements_to_settings();
    }

    /// Replace the content of an existing arrangement with the current window layout.
    ///
    /// Preserves the arrangement's ID, name, and display order.
    pub fn replace_arrangement(&mut self, id: ArrangementId, event_loop: &ActiveEventLoop) {
        let name = match self.arrangement_manager.get(&id) {
            Some(a) => a.name.clone(),
            None => {
                log::error!("replace_arrangement: arrangement not found: {}", id);
                return;
            }
        };

        let original_order = self
            .arrangement_manager
            .get(&id)
            .map(|a| a.order)
            .unwrap_or(0);

        let mut new_arrangement = crate::arrangements::capture::capture_arrangement(
            name.clone(),
            &self.windows,
            event_loop,
        );
        new_arrangement.id = id;
        new_arrangement.order = original_order;

        log::info!(
            "Replaced arrangement '{}' with current layout ({} windows)",
            name,
            new_arrangement.windows.len()
        );

        self.arrangement_manager.update(new_arrangement);
        if let Err(e) = crate::arrangements::storage::save_arrangements(&self.arrangement_manager) {
            log::error!("Failed to save arrangements after replace: {}", e);
        }
        self.sync_arrangements_to_settings();
    }

    /// Restore a saved arrangement by ID.
    ///
    /// Closes all existing windows and creates new ones according to the arrangement.
    pub fn restore_arrangement(&mut self, id: ArrangementId, event_loop: &ActiveEventLoop) {
        let arrangement = match self.arrangement_manager.get(&id) {
            Some(a) => a.clone(),
            None => {
                log::error!("Arrangement not found: {}", id);
                return;
            }
        };

        log::info!(
            "Restoring arrangement '{}' ({} windows)",
            arrangement.name,
            arrangement.windows.len()
        );

        // Close all existing windows
        let window_ids: Vec<WindowId> = self.windows.keys().copied().collect();
        for window_id in window_ids {
            if let Some(window_state) = self.windows.remove(&window_id) {
                drop(window_state);
            }
        }

        // Build monitor mapping
        let available_monitors: Vec<_> = event_loop.available_monitors().collect();
        let monitor_mapping = crate::arrangements::restore::build_monitor_mapping(
            &arrangement.monitor_layout,
            &available_monitors,
        );

        // Create windows from arrangement
        for (i, window_snapshot) in arrangement.windows.iter().enumerate() {
            let Some((x, y, w, h)) = crate::arrangements::restore::compute_restore_position(
                window_snapshot,
                &monitor_mapping,
                &available_monitors,
            ) else {
                log::warn!("Could not compute position for window {} in arrangement", i);
                continue;
            };

            // When a tmux session is saved, the visible tabs are tmux display tabs that
            // will be re-created by the tmux session on reconnect.  Pass only a single
            // empty tab CWD so create_window_with_overrides spawns just the gateway
            // shell; the real tmux tabs arrive via layout-change notifications.
            // A par-mux window is the same shape (tabs arrive via %window-add).
            let tab_cwds: Vec<Option<String>> = if window_snapshot.tmux_session_name.is_some()
                || window_snapshot.mux_session_name.is_some()
            {
                vec![None]
            } else {
                crate::arrangements::restore::tab_cwds(&arrangement, i)
            };

            let created_window_id = self.create_window_with_overrides(
                event_loop,
                (x, y),
                (w, h),
                &tab_cwds,
                window_snapshot.active_tab_index,
                None,
            );

            if let Some(window_id) = created_window_id
                && let Some(window_state) = self.windows.get_mut(&window_id)
            {
                // Reattach a par-mux session saved with this window — never
                // through the tmux branch, which would spawn a real
                // `tmux -CC` session of the same name.
                if let Some(ref mux_name) = window_snapshot.mux_session_name
                    && !mux_name.is_empty()
                {
                    // The single `vec![None]` tab spawned above is a
                    // placeholder so a failed attach never leaves the
                    // window empty; the first daemon window tab closes it
                    // (handle_tmux_window_add).
                    if let Some(tab) = window_state.tab_manager.tabs().first() {
                        window_state.tmux_state.mux_restore_placeholder_tab = Some(tab.id);
                    }
                    #[cfg(feature = "mux")]
                    window_state.begin_mux_session_attach(mux_name);
                    #[cfg(not(feature = "mux"))]
                    log::warn!(
                        "Arrangement restore: par-mux session '{mux_name}' skipped — \
                         this build has no mux support"
                    );
                } else if let Some(ref session_name) = window_snapshot.tmux_session_name
                    && window_state.config.load().tmux.tmux_enabled
                    && !session_name.is_empty()
                {
                    if let Err(e) = window_state.initiate_tmux_gateway(Some(session_name)) {
                        log::warn!("Arrangement restore: tmux auto-connect failed: {}", e);
                    }
                } else {
                    // Non-tmux window: restore pane layouts (PN11), user
                    // titles, custom colors, and icons
                    let tabs = window_state.tab_manager.tabs_mut();
                    for (tab_idx, snapshot) in window_snapshot.tabs.iter().enumerate() {
                        if let Some(tab) = tabs.get_mut(tab_idx) {
                            crate::session::restore::apply_tab_snapshot(
                                tab,
                                snapshot,
                                &self.config.load(),
                                Arc::clone(&self.runtime),
                            );
                        }
                    }
                    // Restored panes need refresh tasks to redraw on output.
                    if let Some(win) = &window_state.window {
                        for tab in window_state.tab_manager.tabs_mut() {
                            tab.start_pane_refresh_tasks(
                                Arc::clone(&self.runtime),
                                Arc::clone(win),
                                self.config.load().rendering.max_fps,
                                self.config.load().power.inactive_tab_fps,
                            );
                        }
                    }
                }
            }
        }

        // If no windows were created (e.g., empty arrangement), create one default window
        if self.windows.is_empty() {
            log::warn!("Arrangement had no restorable windows, creating default window");
            self.create_window(event_loop);
        }
    }

    /// Restore an arrangement by name (for auto-restore and keybinding actions)
    pub fn restore_arrangement_by_name(
        &mut self,
        name: &str,
        event_loop: &ActiveEventLoop,
    ) -> bool {
        if let Some(arrangement) = self.arrangement_manager.find_by_name(name) {
            let id = arrangement.id;
            self.restore_arrangement(id, event_loop);
            true
        } else {
            log::warn!("Arrangement not found by name: {}", name);
            false
        }
    }

    /// Delete an arrangement by ID
    pub fn delete_arrangement(&mut self, id: ArrangementId) {
        if let Some(removed) = self.arrangement_manager.remove(&id) {
            log::info!("Deleted arrangement '{}'", removed.name);
            if let Err(e) =
                crate::arrangements::storage::save_arrangements(&self.arrangement_manager)
            {
                log::error!("Failed to save arrangements after delete: {}", e);
            }
            self.sync_arrangements_to_settings();
        }
    }

    /// Rename an arrangement by ID
    pub fn rename_arrangement(&mut self, id: ArrangementId, new_name: String) {
        if let Some(arrangement) = self.arrangement_manager.get_mut(&id) {
            log::info!(
                "Renamed arrangement '{}' -> '{}'",
                arrangement.name,
                new_name
            );
            arrangement.name = new_name;
            if let Err(e) =
                crate::arrangements::storage::save_arrangements(&self.arrangement_manager)
            {
                log::error!("Failed to save arrangements after rename: {}", e);
            }
            self.sync_arrangements_to_settings();
        }
    }

    /// Move an arrangement up in the order
    pub fn move_arrangement_up(&mut self, id: ArrangementId) {
        self.arrangement_manager.move_up(&id);
        if let Err(e) = crate::arrangements::storage::save_arrangements(&self.arrangement_manager) {
            log::error!("Failed to save arrangements after reorder: {}", e);
        }
        self.sync_arrangements_to_settings();
    }

    /// Move an arrangement down in the order
    pub fn move_arrangement_down(&mut self, id: ArrangementId) {
        self.arrangement_manager.move_down(&id);
        if let Err(e) = crate::arrangements::storage::save_arrangements(&self.arrangement_manager) {
            log::error!("Failed to save arrangements after reorder: {}", e);
        }
        self.sync_arrangements_to_settings();
    }

    /// Sync arrangement manager data to the settings window (for UI display)
    pub fn sync_arrangements_to_settings(&mut self) {
        if let Some(sw) = &mut self.settings_window {
            sw.settings_ui.arrangement_manager = self.arrangement_manager.clone();
        }
    }
}