//! Server-side decoration hit-testing and controls: title-bar / window-button
//! hit tests (Wayland and Xwayland), the resize-edge cursor, X11 occlusion
//! checks and applying a pressed SSD button. Operates on `Windows`.

use crate::windows::window::DecorationPolicy;

use super::*;

impl Windows {
    /// Hit-test the server-side decoration of any window at `point`,
    /// returning the target window reference and the action for drag
    /// interactions.
    pub fn ssd_window_hit_test(
        &self,
        point: smithay::utils::Point<f64, Logical>,
    ) -> Option<( Rc<RefCell<Window>>, crate::ssd::SsdAction )>
    {
        let scale = self.canvas.scale();
        let bar_h = crate::ssd::SsdDecoration::bar_height();

        // Desktop: topmost floating window first.
        if !self.canvas.is_mobile() {
            for layout in self.layouts.stacking_layouts() {
                for window_rc in layout.windows() {
                    let (rect, bar, surface) = {
                        let w = window_rc.borrow();
                        if w.minimized { continue; }
                        let bar = if w.suppress_ssd(&self.csd_apps) { 0 } else { bar_h };
                        (w.bounds(scale), bar, w.surface.surface().clone())
                    };
                    if let Some(action) = crate::ssd::hit_test(rect, point, bar) {
                        let this_z = self.z_index(&surface);
                        for entry in self.x11_windows.entries() {
                            if !entry.surface.is_mapped() || entry.minimized { continue; }
                            let Some(other_wl) = entry.surface.wl_surface() else { continue };
                            let geo = entry.surface.geometry();
                            if !crate::ssd::body_hit_test(geo, point, bar_h) { continue; }
                            match (self.z_index(&other_wl), this_z) {
                                (Some(a), Some(b)) if a > b => return None,
                                _ => {}
                            }
                        }
                        return Some((window_rc.clone(), action));
                    }
                    if crate::ssd::body_hit_test(rect, point, bar) {
                        return None;
                    }
                }
            }
            return None;
        }

        for window_rc in self.layouts.active().windows()
        {
            let (rect, bar) = {
                let w = window_rc.borrow();
                if w.minimized { continue; }
                let bar = if w.suppress_ssd(&self.csd_apps) { 0 } else { bar_h };
                (w.bounds(scale), bar)
            };
            if let Some( action ) = crate::ssd::hit_test( rect, point, bar )
            {
                return Some(( window_rc.clone(), action ));
            }
        }
        None
    }

    pub fn ssd_x11_hit_test(
        &self,
        point: Point<f64, Logical>,
    ) -> Option<(WlSurface, crate::ssd::SsdAction)> {
        if self.canvas.is_mobile() { return None; }
        let (wl, action) = self.x11_windows.ssd_hit_test(point, &self.toplevel_z_order)?;
        if self.x11_hit_occluded(&wl, point) { return None; }
        Some((wl, action))
    }

    pub fn apply_ssd_button_x11(
        &mut self,
        wl: &WlSurface,
        action: crate::ssd::SsdAction,
    ) -> bool {
        use crate::ssd::SsdAction;
        self.z_promote(wl);
        match action {
            SsdAction::Close => {
                if let Some(s) = self.x11_windows.find(wl) { let _ = s.close(); }
            }
            SsdAction::Maximize => {
                let avail = self.output.available();
                self.x11_windows.toggle_maximize(wl, avail);
            }
            SsdAction::Minimize => {
                self.x11_windows.set_minimized(wl, true);
            }
            SsdAction::Move => return false,
        }
        self.dirty = true;
        true
    }

    /// Set a toplevel's decoration policy. Called from the xdg-decoration
    /// and KDE server-decoration handlers as the client changes mode.
    pub fn set_decoration_policy(&mut self, surface: &WlSurface, policy: DecorationPolicy) {
        for layout in self.layouts.layouts() {
            for w in layout.windows() {
                if w.borrow().surface() == surface {
                    w.borrow_mut().decoration = policy;
                    self.dirty = true;
                    return;
                }
            }
        }
        tracing::info!(?policy, "decoration request for a surface with no toplevel window; dropped");
    }

    /// Check whether the SSD should be suppressed for `surface` based purely
    /// on its current `app_id` vs `self.csd_apps`, ignoring any explicit
    /// decoration policy. Used by the decoration `unset_mode` handler to pick
    /// the right fallback mode when the client says "don't care".
    pub fn should_suppress_ssd_lookup(&self, surface: &ToplevelSurface) -> bool {
        for layout in self.layouts.layouts() {
            for w in layout.windows() {
                let w = w.borrow();
                if &w.surface == surface {
                    return self.csd_apps.iter().any(|m| m.matches(w.app_id.as_ref()));
                }
            }
        }
        false
    }

    pub(super) fn x11_hit_occluded(&self, x11_wl: &WlSurface, point: Point<f64, Logical>) -> bool {
        let this_z = self.z_index(x11_wl);
        let scale = self.canvas.scale();
        let bar_h = crate::ssd::SsdDecoration::bar_height();

        for layout in self.layouts.stacking_layouts() {
            for window_rc in layout.windows() {
                let (rect, bar, surface) = {
                    let w = window_rc.borrow();
                    if w.minimized { continue; }
                    let bar = if w.suppress_ssd(&self.csd_apps) { 0 } else { bar_h };
                    (w.bounds(scale), bar, w.surface.surface().clone())
                };
                if !crate::ssd::body_hit_test(rect, point, bar) { continue; }
                if matches!((self.z_index(&surface), this_z), (Some(a), Some(b)) if a > b) {
                    return true;
                }
            }
        }

        for entry in self.x11_windows.entries() {
            if !entry.surface.is_mapped() || entry.minimized { continue; }
            let Some(other_wl) = entry.surface.wl_surface() else { continue };
            if &other_wl == x11_wl { continue; }
            let geo = entry.surface.geometry();
            if !crate::ssd::body_hit_test(geo, point, bar_h) { continue; }
            if matches!((self.z_index(&other_wl), this_z), (Some(a), Some(b)) if a > b) {
                return true;
            }
        }

        false
    }

    /// Reverse half of [`x11_hit_occluded`]: true if a mapped X11 window whose
    /// body covers `point` is stacked above `this_z` in the global order.
    pub(super) fn layout_hit_occluded_by_x11(&self, this_z: Option<usize>, point: Point<f64, Logical>) -> bool {
        let bar_h = crate::ssd::SsdDecoration::bar_height();
        for entry in self.x11_windows.entries() {
            if !entry.surface.is_mapped() || entry.minimized { continue; }
            let Some(other_wl) = entry.surface.wl_surface() else { continue };
            let geo = entry.surface.geometry();
            if !crate::ssd::body_hit_test(geo, point, bar_h) { continue; }
            if matches!((self.z_index(&other_wl), this_z), (Some(a), Some(b)) if a > b) {
                return true;
            }
        }
        false
    }

    /// Return the resize edge currently under `pointer`, if any. While a
    /// resize drag is active the drag's edge is returned even if the
    /// pointer has moved outside the grab band, so the cursor stays stable
    /// through the interaction.
    pub fn resize_cursor_edge(
        &self,
        pointer: Point<f64, Logical>,
    ) -> Option<crate::ssd::ResizeEdge> {
        if self.canvas.is_mobile() {
            return None;
        }
        if let Some(drag) = &self.resize_drag {
            return Some(drag.edge);
        }
        if let Some(drag) = &self.resize_drag_x11 {
            return Some(drag.edge);
        }
        if let Some((wl, edge, _)) = self.x11_windows.resize_hit_test(pointer, &self.toplevel_z_order) {
            if !self.x11_hit_occluded(&wl, pointer) {
                return Some(edge);
            }
        }

        // Walk stacking order topmost-first. If the pointer is inside a
        // window's visible footprint (body + SSD title bar) before we find a
        // resize edge, that window owns the pixel and any resize edges below
        // it are occluded — stop. Without this early-out, a 510 px wide
        // window A with a 8 px right-edge grab zone reaching into x=510..518
        // would still arm its Right resize when the pointer is at x=512
        // inside the body of window B that sits flush to its right.
        let scale = self.canvas.scale();
        let bar_h = crate::ssd::SsdDecoration::bar_height();
        for layout in self.layouts.stacking_layouts() {
            for window_rc in layout.windows() {
                let (rect, eligible, minimized, bar) = {
                    let w = window_rc.borrow();
                    let eligible = w.floating && !w.minimized && !w.is_maximized();
                    let bar = if w.suppress_ssd(&self.csd_apps) { 0 } else { bar_h };
                    (w.bounds(scale), eligible, w.minimized, bar)
                };
                if minimized { continue; }
                if crate::ssd::body_hit_test(rect, pointer, bar) {
                    return None;
                }
                if !eligible { continue; }
                if let Some(edge) = crate::ssd::resize_hit_test(rect, pointer, bar) {
                    let surface = window_rc.borrow().surface.surface().clone();
                    if self.layout_hit_occluded_by_x11(self.z_index(&surface), pointer) {
                        return None;
                    }
                    return Some(edge);
                }
            }
        }
        None
    }

    /// Handle a click on an SSD button of a specific window (desktop mode).
    /// Returns true if an action was taken. The target window is raised to
    /// top and made focused so subsequent key events go to it.
    pub fn apply_ssd_button(
        &mut self,
        window_rc: &Rc<RefCell<Window>>,
        action: crate::ssd::SsdAction,
    ) -> bool {
        use crate::ssd::SsdAction;

        let surface = window_rc.borrow().surface.surface().clone();
        self.raise_toplevel(&surface);

        match action {
            SsdAction::Close => {
                window_rc.borrow().surface.send_close();
            }
            SsdAction::Maximize => {
                let avail = self.output.available();
                let scale = self.output.scale();
                let bar_h = self.bar_h_for(&window_rc.borrow());
                window_rc.borrow_mut().toggle_maximize(scale, avail, bar_h);
                self.record_geometry(&mut window_rc.borrow_mut());
            }
            SsdAction::Minimize => {
                window_rc.borrow_mut().minimized = true;
            }
            SsdAction::Move => return false,
        }
        self.dirty = true;
        true
    }
}
