//! Positional input hit-testing on `Windows`: resolving the pointer/touch
//! position to the surface that should receive it, honouring layer-shell
//! foreground/overlay/background order, the fullscreen and lock views, and
//! Wayland-vs-Xwayland stacking. Operates on `Windows`.

use super::*;
use crate::windows::surface::InputSurfaceKind;

impl Windows {
    /// True when the foreground layer surface *under `position`* takes keyboard
    /// focus — the open launcher, or a fullscreen modal greeter / lock surface.
    /// The swipe gestures must not arm over it: its scrolling and dismissal are
    /// that surface's own (ltk) gestures, and a fullscreen lock owns all input.
    /// Checked at the press point so an unrelated partial surface elsewhere
    /// (e.g. the on-screen keyboard at the opposite edge) doesn't block a
    /// gesture that starts on the bare homescreen.
    pub fn has_exclusive_foreground_at(&self, position: Point<f64, Logical>) -> bool {
        let scale = self.canvas.scale();
        self.layers
            .foreground_surface_at(scale, position)
            .is_some_and(|(w, _)| !w.deny_focus)
    }

    /// True when a dismissable crustace panel (Quick Settings, calendar,
    /// pickers…) is open — a focus-denying overlay surface. A bottom
    /// swipe-up then dismisses it instead of revealing the launcher.
    pub fn has_dismissable_panel(&self) -> bool {
        self.layers.overlay().any(|w| w.deny_focus)
    }

    pub fn foreground_layer_at(&self, position: Point<f64, Logical>) -> bool {
        let scale = self.canvas.scale();
        self.layers.foreground_surface_at(scale, position).is_some()
    }

    /// Pointer focus restricted to foreground layer surfaces (dock, topbar).
    /// Used while the app switcher is up so those surfaces keep getting
    /// hover/motion — and thus tooltips — without leaking motion to the
    /// client windows that sit under the carousel thumbnails.
    pub fn foreground_layer_surface_at(
        &self,
        position: Point<f64, Logical>,
    ) -> Option<InputSurface> {
        let scale = self.canvas.scale();
        let (w, mut surface) = self.layers.foreground_surface_at(scale, position)?;
        if !w.deny_focus {
            let wl_surface = w.surface().clone();
            let app_id = w.app_id.clone();
            surface.toplevel = Some(InputSurfaceKind::Layer((wl_surface, app_id)));
        }
        Some(surface)
    }

    pub fn surface_at(&mut self, position: Point<f64, Logical>) -> Option<InputSurface> {
        let scale = self.canvas.scale();

        macro_rules! focus_layer_surface {
            ($window:expr, $surface:expr) => {{
                if !$window.deny_focus {
                    let wl_surface = $window.surface().clone();
                    let app_id = $window.app_id.clone();
                    $surface.toplevel = Some(InputSurfaceKind::Layer((wl_surface, app_id)));
                }
            }};
        }

        match &self.view {
            View::Workspace => (),
            View::Fullscreen(window) => {
                if let Some((w, mut surface)) = self.layers.overlay_surface_at(scale, position) {
                    focus_layer_surface!(w, surface);
                    return Some(surface);
                }

                let window_ref = window.borrow();
                let mut surface = window_ref.surface_at(scale, position)?;

                let app_id = window_ref.app_id.clone();
                let weak = Rc::downgrade(window);
                surface.toplevel = Some(InputSurfaceKind::Layout((weak, app_id)));

                return Some(surface);
            },
            View::Lock(window_opt) => {
                // Touch over the OSK goes to squeekboard; the rest of the
                // locked screen goes to the lock surface. Other foreground
                // layer surfaces stay non-interactive while locked.
                if let Some((w, mut surface)) = self.layers.foreground_surface_at(scale, position) {
                    if w.app_id.as_deref() == Some("osk") {
                        focus_layer_surface!(w, surface);
                        return Some(surface);
                    }
                }
                return window_opt.as_ref().and_then(|w| w.surface_at(scale, position));
            },
        };

        if let Some((w, mut surface)) = self.layers.foreground_surface_at(scale, position) {
            focus_layer_surface!(w, surface);
            return Some(surface);
        }

        let desktop = !self.canvas.is_mobile();
        let layouts_hit = self.layouts.touch_surface_at(scale, position, desktop);
        let x11_hit = self.x11_windows.surface_at(position, &self.toplevel_z_order).map(|(wl, origin)| {
            let mut s = InputSurface::new(wl.clone(), origin, 1.0);
            s.toplevel = Some(InputSurfaceKind::X11(wl));
            s
        });
        match (layouts_hit, x11_hit) {
            (Some(l), Some(x)) => {
                // Compare the owning toplevel's root z, not `l.surface`: a
                // popup or subsurface hit carries the child surface, which is
                // not tracked in `toplevel_z_order` (z_index would be `None`
                // and the X window would wrongly win underneath the popup).
                let l_root = match &l.toplevel {
                    Some(InputSurfaceKind::Layout((weak, _))) => {
                        weak.upgrade().map(|w| w.borrow().surface.surface().clone())
                    },
                    _ => Some(l.surface.clone()),
                };
                let l_z = l_root.as_ref().and_then(|s| self.z_index(s));
                let x_z = self.z_index(&x.surface);
                let pick = match (l_z, x_z) {
                    (Some(a), Some(b)) => if a > b { l } else { x },
                    (Some(_), None) => l,
                    (None, Some(_)) => x,
                    (None, None) => x,
                };
                return Some(pick);
            },
            (Some(l), None) => return Some(l),
            (None, Some(x)) => return Some(x),
            (None, None)    => (),
        }

        if let Some((w, mut surface)) = self.layers.background_surface_at(scale, position) {
            focus_layer_surface!(w, surface);
            return Some(surface);
        }

        None
    }
}
