//! Surface-kind-specific behaviour: xdg toplevels and layer surfaces.

use super::*;

impl Window {
    pub fn is_maximized(&self) -> bool {
        self.pre_max_rect.is_some()
    }

    /// The rect that should be persisted for geometry-restore purposes.
    /// For a maximized window this is the pre-maximize rect (the rect a
    /// future un-maximize would restore to), not the current full
    /// avail-minus-topbar rect — saving the latter would make the
    /// restored window start maximized *and* lose its prior floating
    /// size, which then permanently coincides with `avail`.
    pub fn persist_rect(&self) -> Rectangle<i32, Logical> {
        self.pre_max_rect.unwrap_or(self.output_rectangle)
    }

    /// Text for the SSD title bar: the toplevel's title, falling back to
    /// the app_id for clients that never set one.
    pub fn title(&self) -> String {
        compositor::with_states(self.surface.surface(), |states| {
            states
                .data_map
                .get::<Mutex<XdgToplevelSurfaceRoleAttributes>>()
                .and_then(|a| a.lock().ok())
                .and_then(|a| a.title.clone())
        })
        .filter(|t| !t.is_empty())
        .or_else(|| self.app_id.clone())
        .unwrap_or_else(|| "Untitled".into())
    }

    pub fn suppress_ssd(&self, csd_apps: &[AppIdMatcher]) -> bool {
        match self.decoration {
            DecorationPolicy::ClientSide => true,
            DecorationPolicy::ServerSide => false,
            DecorationPolicy::Auto => csd_apps.iter().any(|m| m.matches(self.app_id.as_ref())),
        }
    }

    /// Toggle the maximized state against `avail` (the output work area,
    /// already reduced by topbar and exclusive zones). The title bar lives
    /// above `output_rectangle.loc.y`, so the maximized rect is shrunk by
    /// the title bar height on top.
    pub fn toggle_maximize(
        &mut self,
        output_scale: f64,
        avail: Rectangle<i32, Logical>,
        bar_h: i32,
    ) {
        use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::State;

        if let Some(restore) = self.pre_max_rect.take() {
            self.surface.with_pending_state(|state| {
                state.states.unset(State::Maximized);
            });
            self.set_dimensions(output_scale, restore);
        } else {
            let prev = self.output_rectangle;
            let max_rect = Rectangle::<i32, Logical>::new(
                (avail.loc.x, avail.loc.y + bar_h).into(),
                (avail.size.w, (avail.size.h - bar_h).max(1)).into(),
            );
            self.pre_max_rect = Some(prev);
            self.surface.with_pending_state(|state| {
                state.states.set(State::Maximized);
            });
            self.set_dimensions(output_scale, max_rect);
        }
    }

    /// Re-fit against a changed work area: a maximized window re-derives its
    /// rect, a floating one is clamped fully inside. Client minimums win over
    /// the fit; fixed-size windows are left alone.
    pub fn clamp_to_available(
        &mut self,
        output_scale: f64,
        avail: Rectangle<i32, Logical>,
        bar_h: i32,
    ) {
        let area = Rectangle::new(
            Point::from((avail.loc.x, avail.loc.y + bar_h)),
            Size::from((avail.size.w, (avail.size.h - bar_h).max(1))),
        );

        if let Some(pre) = self.pre_max_rect.as_mut() {
            *pre = crate::geometry::clamp_rect(*pre, area);
            self.set_dimensions(output_scale, area);
            return;
        }

        if self.output_rectangle.size.w <= 0 || self.output_rectangle.size.h <= 0 {
            return;
        }

        let (min_size, max_size) = self.size_constraints();
        if min_size.w > 0 && min_size.h > 0 && min_size == max_size {
            return;
        }

        let mut rect = crate::geometry::clamp_rect(self.output_rectangle, area);
        rect.size.w = rect.size.w.max(min_size.w);
        rect.size.h = rect.size.h.max(min_size.h);
        self.set_dimensions(output_scale, rect);
    }

    /// Client-declared (min_size, max_size) in logical pixels. A zero
    /// component means "no constraint".
    pub fn size_constraints(&self) -> (Size<i32, Logical>, Size<i32, Logical>) {
        use smithay::wayland::shell::xdg::SurfaceCachedState;
        compositor::with_states(self.surface.surface(), |states| {
            let mut cached = states.cached_state.get::<SurfaceCachedState>();
            let cur = cached.current();
            (cur.min_size, cur.max_size)
        })
    }

    /// Un-maximize, forcing a restore even if the current rect differs.
    /// Used when a drag should implicitly "tear off" a maximized window.
    pub fn unmaximize(&mut self, output_scale: f64) {
        use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::State;
        let Some(restore) = self.pre_max_rect.take() else {
            return;
        };
        self.surface.with_pending_state(|state| {
            state.states.unset(State::Maximized);
        });
        self.set_dimensions(output_scale, restore);
    }

    pub fn set_resizing(&mut self, resizing: bool) {
        use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::State;
        self.surface.with_pending_state(|state| {
            if resizing {
                state.states.set(State::Resizing);
            } else {
                state.states.unset(State::Resizing);
            }
        });
        if self.surface.initial_configure_sent() {
            self.surface.send_configure();
        }
    }

    /// Snapshot the current `output_rectangle` so a later
    /// [`Self::exit_fullscreen`] can land the window back on its
    /// pre-fullscreen geometry instead of leaving it stuck at the
    /// fullscreen rectangle (which covers the topbar).
    ///
    /// Tiled windows still get this snapshot; the cost is one rect copy
    /// and `Layout::resize` will overwrite the rect on the way out anyway.
    pub fn enter_fullscreen(&mut self, avail: Rectangle<i32, Logical>) {
        // A repeat fullscreen request (clients re-assert the state, e.g. on
        // focus changes) must not overwrite the snapshot with the fullscreen
        // rect itself, or the eventual restore lands on the wrong geometry.
        if self.pre_fullscreen_rect.is_none() {
            self.pre_fullscreen_rect = Some(self.output_rectangle);
            self.pre_fullscreen_avail = Some(avail);
        }
    }

    pub fn take_pre_fullscreen_avail(&mut self) -> Option<Rectangle<i32, Logical>> {
        self.pre_fullscreen_avail.take()
    }

    pub fn is_fullscreen(&self) -> bool {
        self.pre_fullscreen_rect.is_some()
    }

    /// Pop the saved rect and resize back to it. No-op when there is no
    /// snapshot — calling this on a window that never went fullscreen
    /// leaves the geometry untouched.
    pub fn exit_fullscreen(&mut self, output_scale: f64) {
        if let Some(restore) = self.pre_fullscreen_rect.take() {
            self.set_dimensions(output_scale, restore);
        }
    }
}


impl Window<ForgeLayerSurface> {
    pub fn surface_commit(
        &mut self,
        window_scales: &[(AppIdMatcher, WindowScale)],
        output: &mut Output,
        fullscreen_active: bool,
        surface: &WlSurface,
    ) {
        self.update_layer_state(output);
        self.update_dimensions(output, fullscreen_active);
        self.surface_commit_common(output.scale(), output.available().size, window_scales, surface);
    }

    pub fn update_dimensions(&mut self, output: &mut Output, fullscreen_active: bool) {
        let state = compositor::with_states(self.surface.surface(), |states| {
            *states
                .cached_state
                .get::<LayerSurfaceCachedState>()
                .current()
        });

        let output_size = match state.layer {
            Layer::Overlay if fullscreen_active => output.size(),
            _ => output.wm_size(),
        };

        let output_scale = output.scale();
        let scale = self.window_scale(output_scale);
        let mut size = state.size.scale(scale / output_scale);

        let exclusive = match state.exclusive_zone {
            ExclusiveZone::Neutral => *output.exclusive(),
            _ => ExclusiveSpace::default(),
        };

        if size.w == 0 && state.anchor.contains(Anchor::LEFT | Anchor::RIGHT) {
            size.w = output_size.w - exclusive.left - exclusive.right;
            if state.anchor.contains(Anchor::RIGHT) {
                size.w -= state.margin.right;
            }
        }
        if size.h == 0 && state.anchor.contains(Anchor::TOP | Anchor::BOTTOM) {
            size.h = output_size.h - exclusive.top - exclusive.bottom;
            if state.anchor.contains(Anchor::BOTTOM) {
                size.h -= state.margin.bottom;
            }
        }

        let x = if state.anchor.contains(Anchor::LEFT) {
            state.margin.left + exclusive.left
        } else if state.anchor.contains(Anchor::RIGHT) {
            output_size.w - size.w - state.margin.right - exclusive.right
        } else {
            (output_size.w - size.w) / 2
        };
        let y = if state.anchor.contains(Anchor::TOP) {
            state.margin.top + exclusive.top
        } else if state.anchor.contains(Anchor::BOTTOM) {
            output_size.h - size.h - state.margin.bottom - exclusive.bottom
        } else {
            (output_size.h - size.h) / 2
        };

        self.set_dimensions(output.scale(), Rectangle::new((x, y).into(), size));
    }

    fn update_layer_state(&mut self, output: &mut Output) {
        let state = compositor::with_states(self.surface.surface(), |states| {
            *states
                .cached_state
                .get::<LayerSurfaceCachedState>()
                .current()
        });

        self.deny_focus = state.keyboard_interactivity == KeyboardInteractivity::None;

        let exclusive_zone = match state.exclusive_zone {
            ExclusiveZone::Exclusive(size) => {
                let output_scale = output.scale();
                let scale = self.window_scale(output_scale);
                ExclusiveZone::Exclusive((size as f64 * (scale / output_scale)).round() as u32)
            }
            ez => ez,
        };

        let old_exclusive = mem::replace(&mut self.surface.exclusive_zone, exclusive_zone);
        let old_anchor = mem::replace(&mut self.surface.anchor, state.anchor);
        output.exclusive().reset(old_anchor, old_exclusive);
        output
            .exclusive()
            .update(state.anchor, exclusive_zone, state.layer);
    }
}

