//! xdg-session-management binding of toplevels to persisted WM state.

use super::*;
use crate::geometry_store::{OutputRef, SavedGeo};
use crate::protocols::session_management::{AttachError, WindowSession};

impl Windows {
    /// Bind `toplevel` to a session. On the restore path the surface must not
    /// have been committed yet; forge sends the first configure only from
    /// `surface_commit_common`, so `initial_configure_sent` is that test.
    pub fn attach_session(
        &mut self,
        toplevel: &ToplevelSurface,
        mut ws: WindowSession,
    ) -> Result<(), AttachError> {
        let Some(window_rc) = self.find_toplevel(toplevel) else {
            return Err(AttachError::NoWindow);
        };
        let mut w = window_rc.borrow_mut();
        if ws.pending_restore.is_some() && w.surface.initial_configure_sent() {
            return Err(AttachError::AlreadyMapped);
        }
        if self.canvas.is_mobile() {
            ws.pending_restore = None;
        }
        w.session = Some(ws);
        Ok(())
    }

    pub fn detach_session(&mut self, surface: &WlSurface) {
        for mut w in self.layouts.windows_mut() {
            if w.surface.surface() == surface {
                w.session = None;
                return;
            }
        }
    }

    pub fn detach_session_all(&mut self, session_id: &str) {
        for mut w in self.layouts.windows_mut() {
            if w.session.as_ref().is_some_and(|s| s.session_id == session_id) {
                w.session = None;
            }
        }
    }

    pub fn rename_session(&mut self, surface: &WlSurface, name: &str) {
        for mut w in self.layouts.windows_mut() {
            if w.surface.surface() == surface {
                if let Some(s) = w.session.as_mut() {
                    s.name = name.to_string();
                }
                return;
            }
        }
    }

    /// The extend screen a saved geometry names: by EDID identity first, by
    /// connector name otherwise. `None` for the primary or an absent screen.
    pub(super) fn resolve_saved_output<'a>(
        extra_outputs: &'a [Output],
        saved: &OutputRef,
    ) -> Option<&'a Output> {
        saved
            .identity
            .as_deref()
            .and_then(|id| extra_outputs.iter().find(|o| o.identity() == Some(id)))
            .or_else(|| {
                let name = saved.connector.as_deref()?;
                extra_outputs.iter().find(|o| o.smithay_output().name() == name)
            })
    }

    /// Work area and clamped rect for a saved geometry: the screen the window
    /// was last on when still connected, the primary's work area otherwise.
    /// The flag says whether the saved screen was found (always true for a
    /// rect saved on the primary). Takes field refs rather than `&self` so it
    /// can run while a window is mutably borrowed out of `layouts`.
    pub(super) fn saved_rect_target(
        extra_outputs: &[Output],
        primary: &Output,
        saved: &SavedGeo,
        min_size: Size<i32, Logical>,
        max_size: Size<i32, Logical>,
    ) -> (Rectangle<i32, Logical>, Rectangle<i32, Logical>, bool) {
        let target = saved.output_ref();
        let found = Self::resolve_saved_output(extra_outputs, &target);
        let resolved = target.is_primary() || found.is_some();
        let avail = found
            .map(|o| o.logical_rect())
            .unwrap_or_else(|| primary.available());
        let mut rect = saved.to_rect_clamped(avail);
        rect.size.w = rect.size.w.max(min_size.w);
        rect.size.h = rect.size.h.max(min_size.h);
        if max_size.w > 0 { rect.size.w = rect.size.w.min(max_size.w); }
        if max_size.h > 0 { rect.size.h = rect.size.h.min(max_size.h); }
        (avail, rect, resolved)
    }

    /// Re-apply the saved geometry of every window whose screen has just
    /// appeared under `name`.
    pub(super) fn place_deferred_on(&mut self, name: &str) {
        let scale = self.output.scale();
        let mut placed = false;
        for mut w in self.layouts.windows_mut() {
            let Some(saved) = w.deferred_geo.clone() else { continue };
            let on_new = Self::resolve_saved_output(&self.extra_outputs, &saved.output_ref())
                .is_some_and(|o| o.smithay_output().name() == name);
            if !on_new { continue; }
            let (min_size, max_size) = w.size_constraints();
            let (avail, rect, _) = Self::saved_rect_target(
                &self.extra_outputs, &self.output, &saved, min_size, max_size,
            );
            if w.is_maximized() {
                w.unmaximize(scale);
            }
            w.set_dimensions(scale, rect);
            if saved.maximized {
                let bar_h = if w.suppress_ssd(&self.csd_apps) { 0 }
                            else { crate::ssd::SsdDecoration::bar_height() };
                w.toggle_maximize(scale, avail, bar_h);
            }
            w.deferred_geo = None;
            placed = true;
        }
        if placed {
            self.refresh_output_overlaps();
            self.dirty = true;
        }
    }
}
