//! Wayland protocol handler implementations for `Forge`: the trait impls
//! and their `delegate_*!` glue that make `Forge` the compositor state every
//! smithay protocol delegates to.

use smithay::{
    delegate_alpha_modifier, delegate_compositor, delegate_cursor_shape, delegate_data_control,
    delegate_data_device, delegate_dmabuf, delegate_foreign_toplevel_list,
    delegate_fractional_scale, delegate_idle_inhibit, delegate_idle_notify,
    delegate_input_method_manager, delegate_kde_decoration, delegate_keyboard_shortcuts_inhibit,
    delegate_layer_shell, delegate_output, delegate_pointer_constraints, delegate_pointer_gestures,
    delegate_presentation, delegate_primary_selection, delegate_relative_pointer, delegate_seat,
    delegate_security_context, delegate_session_lock, delegate_shm, delegate_single_pixel_buffer,
    delegate_tablet_manager, delegate_text_input_manager, delegate_viewporter,
    delegate_virtual_keyboard_manager, delegate_xdg_activation, delegate_xdg_decoration,
    delegate_xdg_shell,
};

use crate::{
    delegate_export_dmabuf, delegate_foreign_toplevel, delegate_gamma_control,
    delegate_image_copy_capture, delegate_input_inhibitor, delegate_output_management,
    delegate_output_power, delegate_screencopy, delegate_session_management,
    delegate_virtual_pointer,
};
use crate::protocols::session_management::{
    AttachError, SessionManagementHandler, SessionManagerState, WindowSession,
};
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::XdgToplevel;

use super::*;

impl CompositorHandler for Forge {
    fn compositor_state(&mut self) -> &mut CompositorState {
        &mut self.protocol.compositor_state
    }

    fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {
        // XWayland's own client carries `XWaylandClientData` instead of our `ClientState`.
        if let Some(state) = client.get_data::<ClientState>() {
            return &state.compositor_state;
        }
        if let Some(state) = client.get_data::<smithay::xwayland::XWaylandClientData>() {
            return &state.compositor_state;
        }
        panic!("client state");
    }

    fn commit(&mut self, surface: &WlSurface) {
        if !compositor::is_sync_subsurface(surface) {
            self.popups.commit(surface);
            self.windows.surface_commit(surface);
            self.unstall();
        }
    }
}
delegate_compositor!(Forge);

impl ShmHandler for Forge {
    fn shm_state(&self) -> &ShmState {
        &self.protocol.shm_state
    }
}
delegate_shm!(Forge);

impl DmabufHandler for Forge {
    fn dmabuf_state(&mut self) -> &mut DmabufState {
        &mut self.protocol.dmabuf_state
    }

    fn dmabuf_imported(&mut self, _: &DmabufGlobal, buffer: Dmabuf, notifier: ImportNotifier) {
        match self.backend.renderer().and_then(
            |r: &mut smithay::backend::renderer::gles::GlesRenderer| {
                r.import_dmabuf(&buffer, None).ok()
            },
        ) {
            Some(_) => drop(notifier.successful::<Self>()),
            None => {
                notifier.failed();
                error!("dmabuf import failed");
            }
        }
    }
}
delegate_dmabuf!(Forge);

impl XdgShellHandler for Forge {
    fn xdg_shell_state(&mut self) -> &mut XdgShellState {
        &mut self.protocol.xdg_shell_state
    }

    fn new_toplevel(&mut self, surface: ToplevelSurface) {
        let handle = self
            .protocol
            .ext_foreign_toplevel_list_state
            .new_toplevel::<Self>("", "");
        self.windows.add(surface, Some(handle));
    }

    fn ack_configure(&mut self, surface: WlSurface, _: Configure) {
        let runtime = self.windows.runtime();
        if let Some(w) = self.windows.find_xdg(&surface) {
            w.request_frame(runtime);
        }
    }

    fn fullscreen_request(&mut self, surface: ToplevelSurface, _: Option<WlOutput>) {
        self.windows.fullscreen(&surface);
    }

    fn move_request(&mut self, surface: ToplevelSurface, _seat: WlSeat, _serial: Serial) {
        let pointer = self.input.touch_state.cursor_pos();
        self.windows.start_move_drag_for(&surface, pointer);
    }

    fn resize_request(
        &mut self,
        surface: ToplevelSurface,
        _seat: WlSeat,
        _serial: Serial,
        edges: smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::ResizeEdge,
    ) {
        use crate::ssd::ResizeEdge as Edge;
        use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::ResizeEdge as Xdg;
        let edge = match edges {
            Xdg::Top => Edge::N,
            Xdg::Bottom => Edge::S,
            Xdg::Left => Edge::W,
            Xdg::Right => Edge::E,
            Xdg::TopLeft => Edge::NW,
            Xdg::TopRight => Edge::NE,
            Xdg::BottomLeft => Edge::SW,
            Xdg::BottomRight => Edge::SE,
            // `None` means the client did not pick an edge — there is no
            // meaningful drag to arm, so silently ignore.
            _ => return,
        };
        let pointer = self.input.touch_state.cursor_pos();
        self.windows.start_resize_drag_for(&surface, edge, pointer);
    }

    fn maximize_request(&mut self, surface: ToplevelSurface) {
        self.windows.set_maximized(&surface);
        surface.send_configure();
    }

    fn unmaximize_request(&mut self, surface: ToplevelSurface) {
        self.windows.unset_maximized(&surface);
        surface.send_configure();
    }

    fn minimize_request(&mut self, surface: ToplevelSurface) {
        self.windows.set_minimized(&surface);
    }

    fn unfullscreen_request(&mut self, surface: ToplevelSurface) {
        self.windows.unfullscreen(&surface);
    }

    fn toplevel_destroyed(&mut self, surface: ToplevelSurface) {
        if let Some(window) = self.windows.find_xdg(surface.surface()) {
            if let Some(ref handle) = window.foreign_handle {
                handle.send_closed();
            }
        }
        self.protocol.session_manager_state.toplevel_destroyed(surface.wl_surface());
        self.windows.reap_xdg(&surface);
        if self.windows.has_no_windows() {
            Self::send_to_crustace(r#"{"action":"show_homescreen"}"#);
        }
        self.unstall();
    }

    fn new_popup(&mut self, surface: PopupSurface, positioner: PositionerState) {
        let sid = surface.wl_surface().id().protocol_id();
        surface.with_pending_state(|s| s.positioner = positioner);
        if let Err(e) = self.popups.track_popup(PopupKind::Xdg(surface.clone())) {
            warn!("track_popup failed: surface={sid} err={e}");
        }
        self.windows.add_popup(surface);
    }

    fn reposition_request(
        &mut self,
        surface: PopupSurface,
        positioner: PositionerState,
        token: u32,
    ) {
        surface.with_pending_state(|s| {
            s.geometry = positioner.get_geometry();
            s.positioner = positioner;
        });
        self.windows.reposition_popup(&surface, token);
        surface.send_repositioned(token);
    }

    fn popup_destroyed(&mut self, _: PopupSurface) {
        self.popups.cleanup();
        self.windows.refresh_popups();
        self.unstall();
    }

    fn grab(&mut self, surface: PopupSurface, _: WlSeat, serial: Serial) {
        let popup = surface.clone();
        let sid = popup.wl_surface().id().protocol_id();
        let kind = PopupKind::Xdg(surface);
        let Ok(root) = smithay::desktop::find_popup_root_surface(&kind) else {
            warn!("popup grab rejected: surface={sid} reason=find_popup_root_surface failed");
            popup.send_popup_done();
            return;
        };

        let seat = self.seat.clone();
        let mut grab = match self.popups.grab_popup(root, kind, &seat, serial) {
            Ok(g) => g,
            Err(e) => {
                warn!("popup grab rejected: surface={sid} reason=grab_popup err={e:?}");
                popup.send_popup_done();
                return;
            }
        };

        if let Some(keyboard) = seat.get_keyboard() {
            if keyboard.is_grabbed()
                && !(keyboard.has_grab(serial)
                    || keyboard.has_grab(grab.previous_serial().unwrap_or(serial)))
            {
                warn!(
                    "popup grab rejected: surface={sid} reason=keyboard serial mismatch \
                     serial={serial:?} prev={:?}",
                    grab.previous_serial()
                );
                grab.ungrab(PopupUngrabStrategy::All);
                popup.send_popup_done();
                return;
            }
            keyboard.set_focus(self, grab.current_grab(), serial);
            keyboard.set_grab(self, PopupKeyboardGrab::new(&grab), serial);
        }

        if let Some(pointer) = seat.get_pointer() {
            if pointer.is_grabbed()
                && !(pointer.has_grab(serial)
                    || pointer.has_grab(grab.previous_serial().unwrap_or_else(|| grab.serial())))
            {
                warn!(
                    "popup grab rejected: surface={sid} reason=pointer serial mismatch \
                     serial={serial:?} prev={:?}",
                    grab.previous_serial()
                );
                grab.ungrab(PopupUngrabStrategy::All);
                popup.send_popup_done();
                return;
            }
            pointer.set_grab(self, PopupPointerGrab::new(&grab), serial, Focus::Keep);
        }
    }
}
delegate_xdg_shell!(Forge);

impl WlrLayerShellHandler for Forge {
    fn shell_state(&mut self) -> &mut WlrLayerShellState {
        &mut self.protocol.layer_shell_state
    }

    fn new_layer_surface(
        &mut self,
        surface: LayerSurface,
        output: Option<WlOutput>,
        layer: Layer,
        namespace: String,
    ) {
        self.windows.add_layer(layer, surface, namespace, output.as_ref());
    }

    fn layer_destroyed(&mut self, surface: LayerSurface) {
        self.windows.reap_layer(&surface);
        self.unstall();
    }
}
delegate_layer_shell!(Forge);

impl SessionLockHandler for Forge {
    fn lock_state(&mut self) -> &mut SessionLockManagerState {
        &mut self.protocol.lock_state
    }

    fn lock(&mut self, confirmation: SessionLocker) {
        // A second locker while the current one is alive must not take over:
        // accepting it resets the lock view to `Lock(None)`, which drops the
        // mapped lock surface and the keyboard focus until the new client maps
        // its own — dead input for however long that takes. Dropping the
        // confirmation sends `finished` to the client.
        if self.session.active_lock.as_ref().is_some_and(|l| l.is_alive()) {
            info!("refusing session lock: a live client already holds the lock");
            return;
        }
        self.session.active_lock = Some(confirmation.ext_session_lock().clone());
        self.session.locker = Some(confirmation);
        self.windows.lock();
        self.shell.app_switcher.cancel();
        self.session.osk_visible = false;
        Self::set_osk_visible(false);
        // Nothing is shown with the display off: confirm without a frame.
        if !self.session.display_on {
            if let Some(locker) = self.session.locker.take() {
                locker.lock();
            }
        }
        self.report_session_state();
        self.unstall();
    }

    fn unlock(&mut self) {
        self.session.active_lock = None;
        self.windows.unlock();
        self.report_session_state();
    }

    fn new_surface(&mut self, surface: LockSurface, output: WlOutput) {
        // A refused locker may still request a lock surface before it processes
        // `finished`; it must not displace the holder's surface.
        let owner = self.session.active_lock.as_ref().and_then(|l| l.client()).map(|c| c.id());
        let client = surface.wl_surface().client().map(|c| c.id());
        if owner != client {
            info!("ignoring lock surface from a client that does not hold the lock");
            return;
        }
        self.windows.set_lock_surface(surface, &output);
    }
}
delegate_session_lock!(Forge);

impl SeatHandler for Forge {
    type KeyboardFocus = WlSurface;
    type PointerFocus = WlSurface;
    type TouchFocus = WlSurface;

    fn seat_state(&mut self) -> &mut SeatState<Self> {
        &mut self.protocol.seat_state
    }

    fn focus_changed(&mut self, seat: &Seat<Self>, surface: Option<&Self::KeyboardFocus>) {
        let client = surface.and_then(|s| self.display_handle.get_client(s.id()).ok());
        data_device::set_data_device_focus(&self.display_handle, seat, client.clone());
        primary_selection::set_primary_focus(&self.display_handle, &self.seat, client);
    }

    fn cursor_image(&mut self, _seat: &Seat<Self>, image: CursorImageStatus) {
        if self.input.cursor_status != image {
            let surface = match &image {
                CursorImageStatus::Surface(surface) => Some(surface.clone()),
                _ => None,
            };
            self.input.cursor_status = image;
            self.windows.set_cursor_surface(surface);
            self.windows.set_dirty();
        }
    }

    fn led_state_changed(&mut self, _seat: &Seat<Self>, led_state: LedState) {
        match &mut self.backend {
            Backend::Udev(udev) => udev.update_keyboard_leds(led_state),
            #[cfg(feature = "winit")]
            Backend::Winit(_) => {}
        }
    }
}
delegate_seat!(Forge);

impl InputMethodHandler for Forge {
    fn new_popup(&mut self, _: ImeSurface) {}
    fn dismiss_popup(&mut self, _: ImeSurface) {}
    fn popup_repositioned(&mut self, _: ImeSurface) {}

    fn parent_geometry(&self, parent: &WlSurface) -> Rectangle<i32, Logical> {
        self.windows.parent_geometry(parent)
    }
}

impl VirtualKeyboardHandler for Forge {
    fn on_keyboard_event(
        &mut self,
        keycode: Keycode,
        state: KeyState,
        time: u32,
        keyboard: KeyboardHandle<Self>,
    ) {
        keyboard.input(
            self,
            keycode,
            state,
            SERIAL_COUNTER.next_serial(),
            time,
            |_, _, _| FilterResult::<bool>::Forward,
        );
    }

    fn on_keyboard_modifiers(
        &mut self,
        _: ModMask,
        _: ModMask,
        _: ModMask,
        _: KeyboardHandle<Self>,
    ) {
    }
}
delegate_virtual_keyboard_manager!(Forge);

delegate_input_method_manager!(Forge);
delegate_text_input_manager!(Forge);

impl OutputHandler for Forge {}
delegate_output!(Forge);

impl XdgDecorationHandler for Forge {
    fn new_decoration(&mut self, toplevel: ToplevelSurface) {
        let app_id = compositor::with_states(toplevel.wl_surface(), |states| {
            states
                .data_map
                .get::<Mutex<XdgToplevelSurfaceRoleAttributes>>()
                .and_then(|a| a.lock().ok())
                .and_then(|a| a.app_id.clone())
        });
        // Initial bind: no explicit client request yet, so leave the policy
        // on Auto (the csd_apps allowlist) and seed the first configure from
        // it. An explicit request_mode/unset_mode below overrides this.
        let suppress = self
            .windows
            .csd_apps
            .iter()
            .any(|m| m.matches(app_id.as_ref()));
        let mode = if suppress {
            DecorationMode::ClientSide
        } else {
            DecorationMode::ServerSide
        };
        toplevel.set_state(|s| s.decoration_mode = Some(mode));
        self.windows
            .set_decoration_policy(toplevel.wl_surface(), DecorationPolicy::Auto);
    }

    fn request_mode(&mut self, toplevel: ToplevelSurface, mode: DecorationMode) {
        // Honour the client's explicit request. This takes precedence over
        // the csd_apps allowlist: a client that asks for ServerSide gets
        // forge's SSD even when its app_id matches the allowlist (which
        // exists only for clients that never bind this protocol).
        toplevel.set_state(|s| s.decoration_mode = Some(mode));
        let policy = if mode == DecorationMode::ClientSide {
            DecorationPolicy::ClientSide
        } else {
            DecorationPolicy::ServerSide
        };
        self.windows.set_decoration_policy(toplevel.wl_surface(), policy);
    }

    fn unset_mode(&mut self, toplevel: ToplevelSurface) {
        // "Don't care" from the client — fall back to the csd_apps list.
        let suppress = self.windows.should_suppress_ssd_lookup(&toplevel);
        let mode = if suppress {
            DecorationMode::ClientSide
        } else {
            DecorationMode::ServerSide
        };
        toplevel.set_state(|s| s.decoration_mode = Some(mode));
        self.windows
            .set_decoration_policy(toplevel.wl_surface(), DecorationPolicy::Auto);
    }
}
delegate_xdg_decoration!(Forge);

impl KdeDecorationHandler for Forge {
    fn kde_decoration_state(&self) -> &KdeDecorationState {
        &self.protocol.kde_decoration_state
    }

    fn request_mode(
        &mut self,
        surface: &WlSurface,
        decoration: &OrgKdeKwinServerDecoration,
        mode: WEnum<KdeMode>,
    ) {
        let WEnum::Value(mode) = mode else { return };
        let policy = match mode {
            KdeMode::Client | KdeMode::None => DecorationPolicy::ClientSide,
            KdeMode::Server => DecorationPolicy::ServerSide,
            _ => return,
        };
        self.windows.set_decoration_policy(surface, policy);
        decoration.mode(mode);
    }

    fn release(&mut self, _decoration: &OrgKdeKwinServerDecoration, surface: &WlSurface) {
        self.windows.set_decoration_policy(surface, DecorationPolicy::Auto);
    }
}
delegate_kde_decoration!(Forge);

impl BufferHandler for Forge {
    fn buffer_destroyed(&mut self, _: &WlBuffer) {}
}

impl ScreencopyHandler for Forge {
    fn capture_source(&mut self, output: &WlOutput) -> (Canvas, Option<String>) {
        self.windows.capture_source(output)
    }

    fn frame(&mut self, screencopy: Screencopy) {
        self.backend.request_screencopy(screencopy);
        self.request_redraw();
    }
}
delegate_screencopy!(Forge);

impl ExportDmabufHandler for Forge {
    fn export_dmabuf_frame(&mut self, capture: ExportDmabufCapture) {
        self.backend.request_export_dmabuf(capture);
        self.request_redraw();
    }
}

impl ImageCopyCaptureHandler for Forge {
    fn image_copy_frame(&mut self, capture: ImageCopyCapture) {
        self.backend.request_image_copy(capture);
        self.request_redraw();
    }
}

impl ForeignToplevelHandler for Forge {
    fn foreign_toplevel_state(&mut self) -> &mut ForeignToplevelState {
        &mut self.protocol.foreign_toplevel_state
    }

    fn activate_toplevel(&mut self, app_id: &str) {
        self.windows.activate_window_by_app_id(app_id);
        self.unstall();
    }

    fn close_toplevel(&mut self, app_id: &str) {
        self.windows.close_by_app_id(app_id);
    }

    fn fullscreen_toplevel(&mut self, _app_id: &str) {
        self.windows.toggle_fullscreen();
    }

    fn unfullscreen_toplevel(&mut self, _app_id: &str) {
        self.windows.toggle_fullscreen();
    }
}
delegate_foreign_toplevel!(Forge);

impl ForeignToplevelListHandler for Forge {
    fn foreign_toplevel_list_state(&mut self) -> &mut ForeignToplevelListState {
        &mut self.protocol.ext_foreign_toplevel_list_state
    }
}
delegate_foreign_toplevel_list!(Forge);

impl OutputPowerHandler for Forge {
    fn output_power_state(&mut self) -> &mut OutputPowerManagerState {
        &mut self.protocol.output_power_state
    }

    fn set_output_power(&mut self, on: bool) {
        info!("output-power client request: on={on}");
        if !on {
            self.lock_session();
        }
        self.set_display_status(on);
        if on {
            self.force_redraw(true);
        }
    }
}
delegate_output_power!(Forge);

impl SessionManagementHandler for Forge {
    fn session_management_state(&mut self) -> &mut SessionManagerState {
        &mut self.protocol.session_manager_state
    }

    fn session_store(&mut self) -> &mut crate::session_store::SessionStore {
        self.windows.session_store_mut()
    }

    fn session_toplevel(&mut self, toplevel: &XdgToplevel) -> Option<ToplevelSurface> {
        self.protocol.xdg_shell_state.get_toplevel(toplevel)
    }

    fn session_attach(
        &mut self,
        toplevel: &ToplevelSurface,
        ws: WindowSession,
    ) -> Result<(), AttachError> {
        self.windows.attach_session(toplevel, ws)
    }

    fn session_detach(&mut self, surface: &WlSurface) {
        self.windows.detach_session(surface);
    }

    fn session_detach_all(&mut self, session_id: &str) {
        self.windows.detach_session_all(session_id);
    }

    fn session_rename(&mut self, surface: &WlSurface, name: &str) {
        self.windows.rename_session(surface, name);
    }
}
delegate_session_management!(Forge);
delegate_image_copy_capture!(Forge);
delegate_gamma_control!(Forge);
delegate_output_management!(Forge);
delegate_export_dmabuf!(Forge);
delegate_input_inhibitor!(Forge);
delegate_virtual_pointer!(Forge);

impl IdleInhibitHandler for Forge {
    fn inhibit(&mut self, surface: WlSurface) {
        self.session.idle_inhibitors.push(surface);
    }

    fn uninhibit(&mut self, surface: WlSurface) {
        self.session.idle_inhibitors
            .retain(|s| s.is_alive() && s != &surface);
    }
}
delegate_idle_inhibit!(Forge);

impl IdleNotifierHandler for Forge {
    fn idle_notifier_state(&mut self) -> &mut IdleNotifierState<Self> {
        &mut self.protocol.idle_notifier_state
    }
}
delegate_idle_notify!(Forge);

impl FractionalScaleHandler for Forge {
    fn new_fractional_scale(&mut self, surface: WlSurface) {
        compositor::with_states(&surface, |states| {
            fractional_scale::with_fractional_scale(states, |fs| {
                if let Some(data) = states.data_map.get::<RefCell<ForgeSurfaceData>>() {
                    fs.set_preferred_scale(data.borrow().preferred_fractional_scale);
                }
            });
        });
    }
}
delegate_fractional_scale!(Forge);

impl XdgActivationHandler for Forge {
    fn activation_state(&mut self) -> &mut XdgActivationState {
        &mut self.protocol.xdg_activation_state
    }

    fn token_created(&mut self, _: XdgActivationToken, _data: XdgActivationTokenData) -> bool {
        // Permissive: the shell launches apps by touch, with no keyboard
        // serial to validate against, so the stricter focus-steal gate
        // would reject every launch token. crustace correlates the splash
        // by the token itself in `request_activation` below.
        true
    }

    fn request_activation(
        &mut self,
        token: XdgActivationToken,
        data: XdgActivationTokenData,
        surface: WlSurface,
    ) {
        if data.timestamp.elapsed() >= ACTIVATION_TIMEOUT {
            return;
        }

        if data.surface == self.last_focus {
            self.windows.raise(&surface);
        } else if Some(&surface) != self.last_focus.as_ref() {
            self.windows.set_urgent(&surface, true);
        }

        let app_id = self
            .windows
            .find_xdg(&surface)
            .and_then(|w| w.app_id.clone())
            .unwrap_or_default();
        let msg = serde_json::json!({
            "action": "app_activated",
            "token": &*token,
            "app_id": app_id,
        })
        .to_string();
        Self::send_to_crustace(&msg);

        self.request_redraw();
    }
}
delegate_xdg_activation!(Forge);

impl SelectionHandler for Forge {
    type SelectionUserData = ();

    fn new_selection(
        &mut self,
        ty: SelectionTarget,
        source: Option<SelectionSource>,
        _seat: Seat<Self>,
    ) {
        // A client took the clipboard, so drop any screenshot we were serving.
        if ty == SelectionTarget::Clipboard {
            self.render.clipboard_png = None;
        }
        if let Some(xwm) = self.xwm.as_mut() {
            if let Err(err) = xwm.new_selection(ty, source.map(|s| s.mime_types())) {
                warn!("failed to set Xwayland selection: {err}");
            }
        }
    }

    fn send_selection(
        &mut self,
        ty: SelectionTarget,
        mime_type: String,
        fd: OwnedFd,
        _seat: Seat<Self>,
        _user_data: &(),
    ) {
        if ty == SelectionTarget::Clipboard && mime_type == "image/png" {
            if let Some(bytes) = self.render.clipboard_png.clone() {
                serve_clipboard_bytes(fd, bytes);
                return;
            }
        }
        if let Some(xwm) = self.xwm.as_mut() {
            if let Err(err) = xwm.send_selection(ty, mime_type, fd) {
                warn!("failed to send selection to Xwayland: {err}");
            }
        }
    }
}

impl PrimarySelectionHandler for Forge {
    fn primary_selection_state(&mut self) -> &mut PrimarySelectionState {
        &mut self.protocol.primary_selection_state
    }
}
delegate_primary_selection!(Forge);

impl DataDeviceHandler for Forge {
    fn data_device_state(&mut self) -> &mut DataDeviceState {
        &mut self.protocol.data_device_state
    }
}
impl WaylandDndGrabHandler for Forge {}
delegate_data_device!(Forge);

impl DataControlHandler for Forge {
    fn data_control_state(&mut self) -> &mut DataControlState {
        &mut self.protocol.data_control_state
    }
}
delegate_data_control!(Forge);

impl KeyboardShortcutsInhibitHandler for Forge {
    fn keyboard_shortcuts_inhibit_state(&mut self) -> &mut KeyboardShortcutsInhibitState {
        &mut self.protocol.keyboard_shortcuts_inhibit_state
    }

    fn new_inhibitor(&mut self, inhibitor: KeyboardShortcutsInhibitor) {
        compositor::with_states(inhibitor.wl_surface(), |states| {
            states
                .data_map
                .get_or_insert(|| RefCell::new(ForgeSurfaceData::new()))
                .borrow_mut()
                .inhibits_shortcuts = true;
        });
        inhibitor.activate();
    }

    fn inhibitor_destroyed(&mut self, inhibitor: KeyboardShortcutsInhibitor) {
        compositor::with_states(inhibitor.wl_surface(), |states| {
            states
                .data_map
                .get_or_insert(|| RefCell::new(ForgeSurfaceData::new()))
                .borrow_mut()
                .inhibits_shortcuts = false;
        });
    }
}
delegate_keyboard_shortcuts_inhibit!(Forge);

delegate_presentation!(Forge);
delegate_viewporter!(Forge);
delegate_single_pixel_buffer!(Forge);
delegate_cursor_shape!(Forge);
delegate_alpha_modifier!(Forge);
smithay::delegate_content_type!(Forge);
impl smithay::wayland::tablet_manager::TabletSeatHandler for Forge {}
delegate_tablet_manager!(Forge);

impl PointerConstraintsHandler for Forge {
    fn new_constraint(
        &mut self,
        _surface: &WlSurface,
        pointer: &smithay::input::pointer::PointerHandle<Self>,
    ) {
        // Auto-activate constraints when the surface has pointer focus
        if let Some(focus) = pointer.current_focus() {
            let dominated = self.last_focus.as_ref() == Some(&focus);
            if dominated {
                smithay::wayland::pointer_constraints::with_pointer_constraint(
                    &focus,
                    pointer,
                    |constraint| {
                        if let Some(constraint) = constraint {
                            if !constraint.is_active() {
                                constraint.activate();
                            }
                        }
                    },
                );
            }
        }
    }

    fn cursor_position_hint(
        &mut self,
        _surface: &WlSurface,
        _pointer: &smithay::input::pointer::PointerHandle<Self>,
        _location: Point<f64, Logical>,
    ) {
        // The cursor position hint is advisory - we don't need to warp
    }
}
delegate_pointer_constraints!(Forge);
delegate_relative_pointer!(Forge);
delegate_pointer_gestures!(Forge);

impl SecurityContextHandler for Forge {
    fn context_created(&mut self, source: SecurityContextListenerSource, context: SecurityContext) {
        // Accept all security contexts — clients in sandboxes can connect
        let _ = self
            .event_loop
            .insert_source(source, move |client_stream, _, forge| {
                if let Ok(_client) = forge
                    .display_handle
                    .insert_client(client_stream, Arc::new(ClientState::default()))
                {
                    tracing::debug!("Security context client connected: {:?}", context.app_id);
                }
            });
    }
}
delegate_security_context!(Forge);
