//! Pointer motion on `Forge`: forwarding absolute motion to clients unless a
//! compositor gesture (move/resize drag, topbar swipe) has captured the stream.

use super::*;

impl Forge {
    /// True while forge is mid-gesture on the pointer stream and must
    /// not forward motion to Wayland clients: window move-drag, edge
    /// resize-drag, or an active topbar swipe-down. Armed-but-inactive
    /// topbar-swipe state is intentionally not captured here — the
    /// press hasn't escalated past the threshold so letting a micro-tap
    /// on the topbar fall through to client pointer events is correct.
    pub(super) fn pointer_captured(&self) -> bool {
        self.windows.is_move_dragging()
            || self.windows.is_resize_dragging()
            || self.input.topbar_swipe.as_ref().is_some_and(|s| s.active)
            || self.input.bottom_swipe.as_ref().is_some_and(|s| s.active)
    }

    pub(super) fn on_pointer_motion(&mut self, pos: Point<f64, Logical>, time: u32) {
        if self.shell.screenshot.is_active() {
            self.shell.screenshot.on_motion(pos);
            self.request_redraw();
            return;
        }
        let pointer = self.seat.get_pointer().unwrap();
        let serial = SERIAL_COUNTER.next_serial();

        // While the app switcher is up the panel covers everything, so a
        // pointer motion must NOT leak to whatever client window happens
        // to sit under the cursor in screen coordinates — otherwise that
        // client repaints its hover state and the live thumbnail shows
        // the change inside the carousel tile. Foreground layer surfaces
        // (the dock, the topbar) never appear in a thumbnail, so they are
        // exempt: they stay reachable so their hover and tooltips keep
        // working with the switcher open.
        let surface = if self.shell.app_switcher.is_active() {
            self.windows.foreground_layer_surface_at(pos)
        } else {
            self.windows.surface_at(pos)
        };
        let mut under = surface.map(|s| (s.surface, (s.surface_offset.x as f64, s.surface_offset.y as f64).into()));

        // An SSD title bar / resize band lives above or outside its window's
        // bounds, so `surface_at` falls through to whatever client sits under
        // that strip (a lower window). Drop the hit so the pointer never enters
        // that client there — otherwise it receives stray hover and its cursor
        // shows over our own decoration. The arrow (or, at render time, the
        // resize shape) is shown instead. Mirrors the press path's `on_ssd`,
        // including its layer exemption: a top/overlay layer surface (dock,
        // calendar) covering the point stacks above the SSD chrome, and
        // `surface_at` already resolved to it.
        if !self.shell.app_switcher.is_active()
            && !self.windows.foreground_layer_at(pos)
            && (self.windows.ssd_window_hit_test(pos).is_some()
                || self.windows.ssd_x11_hit_test(pos).is_some()
                || self.windows.resize_cursor_edge(pos).is_some())
        {
            under = None;
        }

        // Over the compositor background no client owns the pointer, so it
        // never gets the chance to set a shape; fall back to the arrow
        // rather than keep a stale shape (e.g. the I-beam from a text field).
        if under.is_none() {
            self.input.cursor_status = smithay::input::pointer::CursorImageStatus::default_named();
            self.windows.set_cursor_surface(None);
        }

        pointer.motion(
            self,
            under,
            &PointerMotionData {
                location: pos,
                serial,
                time,
            },
        );
        pointer.frame(self);
        // Feed the pointer position into the SSD hover machinery so
        // the close button can swap to its red surface, etc. This
        // also conditionally marks the compositor dirty on hover
        // transitions, so plain pointer travel across the desktop
        // does not thrash the render loop.
        self.windows.pointer_moved(pos);

        if self.shell.app_switcher.is_active() && !self.windows.canvas().is_mobile() {
            // A grabbed tile follows the pointer; hover-select would fight it
            // for the highlight, so it only runs while nothing is in flight.
            if self.shell.app_switcher.tile_drag_motion(pos) {
                self.windows.set_dirty();
            } else if self.shell.app_switcher.hover_select(pos) {
                self.windows.set_dirty();
            }
        }
        self.unstall();
    }
}
