use std::{
    ffi::OsString,
    io::Write,
    os::unix::io::OwnedFd,
    process::Stdio,
    sync::mpsc::{self, Receiver, Sender},
};

use crate::{
    backend::render::cursor::{Cursor, load_cursor_env, load_cursor_theme},
    shell::{
        CosmicSurface, PendingWindow, Shell, focus::target::KeyboardFocusTarget, grabs::ReleaseMode,
    },
    state::State,
    utils::prelude::*,
    wayland::handlers::xdg_activation::ActivationContext,
};
use cosmic_comp_config::{EavesdroppingKeyboardMode, XwaylandDescaling};
use smithay::{
    backend::{
        allocator::Fourcc,
        drm::DrmNode,
        input::{ButtonState, KeyState, Keycode},
        renderer::{
            Bind, Frame, Offscreen, Renderer,
            element::{
                Kind,
                memory::{MemoryRenderBuffer, MemoryRenderBufferRenderElement},
            },
            pixman::{PixmanError, PixmanRenderer},
            utils::draw_render_elements,
        },
    },
    desktop::space::SpaceElement,
    input::{keyboard::ModifiersState, pointer::CursorIcon},
    reexports::{wayland_server::Client, x11rb::protocol::xproto::Window as X11Window},
    utils::{
        Buffer as BufferCoords, Logical, Point, Rectangle, SERIAL_COUNTER, Serial, Size, Transform,
    },
    wayland::{
        selection::{
            SelectionTarget,
            data_device::{
                clear_data_device_selection, current_data_device_selection_userdata,
                request_data_device_client_selection, set_data_device_selection,
            },
            primary_selection::{
                clear_primary_selection, current_primary_selection_userdata,
                request_primary_client_selection, set_primary_selection,
            },
        },
        xdg_activation::XdgActivationToken,
    },
    xwayland::{
        X11Surface, X11Wm, XWayland, XWaylandClientData, XWaylandEvent, XwmHandler,
        xwm::{Reorder, XwmId},
    },
};
use tracing::{error, trace, warn};
use xcursor::parser::Image;
use xkbcommon::xkb::Keysym;

#[derive(Debug)]
pub struct XWaylandState {
    pub client: Client,
    pub xwm: Option<X11Wm>,
    pub display: u32,
    pub pressed_keys: Vec<Keycode>,
    pub pressed_buttons: Vec<u32>,
    pub last_modifier_state: Option<ModifiersState>,
    pub clipboard_selection_dirty: Option<Vec<String>>,
    pub primary_selection_dirty: Option<Vec<String>>,
    pub xrdb_thread: Sender<(String, u32)>,
}

fn xrdb_thread(rx: Receiver<(String, u32)>, display: u32) {
    while let Ok((cursor_theme, cursor_size)) = rx.recv() {
        if let Ok(mut child) = std::process::Command::new("xrdb")
            .arg("-merge")
            .env("DISPLAY", format!(":{}", display))
            .stdin(Stdio::piped())
            .spawn()
        {
            let resources = format!(
                "Xcursor.theme: {}\nXcursor.size: {}\n",
                cursor_theme, cursor_size,
            );
            if let Some(mut stdin) = child.stdin.take()
                && let Err(err) = stdin.write_all(resources.as_bytes())
            {
                warn!("Failed to update xresources: {}", err);
            }
            match child.wait() {
                Ok(code) if code.success() => {}
                Ok(code) => {
                    warn!("xrdb failed with code: {}", code);
                }
                Err(err) => {
                    warn!("Failed to wait for child: {}", err);
                }
            }
        } else {
            warn!("`xrdb` not found, cannot update Xresources.");
        }
    }
}

impl State {
    pub fn launch_xwayland(&mut self, render_node: Option<DrmNode>) {
        if self.common.xwayland_state.is_some() {
            return;
        }

        let (xwayland, client) = match XWayland::spawn(
            &self.common.display_handle,
            None,
            std::iter::empty::<(OsString, OsString)>(),
            true,
            Stdio::null(),
            Stdio::null(),
            |user_data| {
                if let Some(node) = render_node {
                    user_data.insert_if_missing_threadsafe(|| node);
                }
            },
        ) {
            Ok((xwayland, client)) => (xwayland, client),
            Err(err) => {
                error!(?err, "Failed to start Xwayland.");
                self.notify_ready();
                return;
            }
        };

        match self
            .common
            .event_loop_handle
            .insert_source(xwayland, move |event, _, data| match event {
                XWaylandEvent::Ready {
                    x11_socket,
                    display_number,
                } => {
                    let (tx, rx) = mpsc::channel();
                    std::thread::spawn(move || xrdb_thread(rx, display_number));

                    data.common.xwayland_state = Some(XWaylandState {
                        client: client.clone(),
                        xwm: None,
                        display: display_number,
                        pressed_keys: Vec::new(),
                        pressed_buttons: Vec::new(),
                        last_modifier_state: None,
                        clipboard_selection_dirty: None,
                        primary_selection_dirty: None,
                        xrdb_thread: tx,
                    });

                    let wm = match X11Wm::start_wm(
                        data.common.event_loop_handle.clone(),
                        &data.common.display_handle,
                        x11_socket,
                        client.clone(),
                    ) {
                        Ok(wm) => wm,
                        Err(err) => {
                            error!(?err, "Failed to start Xwayland WM");
                            return;
                        }
                    };

                    let xwayland_state = data.common.xwayland_state.as_mut().unwrap();
                    xwayland_state.xwm = Some(wm);
                    xwayland_state.reload_cursor(1.);
                    data.notify_ready();

                    data.common.update_xwayland_settings();
                    data.common.update_xwayland_primary_output();
                }
                XWaylandEvent::Error => {
                    if let Some(mut xwayland_state) = data.common.xwayland_state.take() {
                        xwayland_state.xwm = None;
                    }
                    data.notify_ready();
                }
            }) {
            Ok(_token) => {}
            Err(err) => {
                error!(?err, "Failed to listen for Xwayland");
                self.notify_ready();
            }
        }
    }
}

fn scale_cursor(
    scale: f64,
    cursor_size: u32,
    image: &Image,
) -> Result<(Vec<u8>, Size<u16, Logical>, Point<u16, Logical>), PixmanError> {
    let mut renderer = PixmanRenderer::new()?;
    let image_scale = (image.size / cursor_size).max(1);
    let pixel_size = (cursor_size as f64 * scale).round() as i32;
    let buffer_size = Size::<i32, BufferCoords>::from((pixel_size, pixel_size));
    let output_size = buffer_size.to_logical(1, Transform::Normal).to_physical(1);

    let image_buffer = MemoryRenderBuffer::from_slice(
        &image.pixels_rgba,
        Fourcc::Abgr8888,
        (image.width as i32, image.height as i32),
        image_scale as i32,
        Transform::Normal,
        None,
    );
    let element = MemoryRenderBufferRenderElement::from_buffer(
        &mut renderer,
        (0., 0.),
        &image_buffer,
        None,
        None,
        None,
        Kind::Unspecified,
    )?;

    let mut buffer = renderer.create_buffer(Fourcc::Abgr8888, buffer_size)?;
    let mut fb = renderer.bind(&mut buffer)?;
    let mut frame = renderer.render(&mut fb, output_size, Transform::Normal)?;
    draw_render_elements(
        &mut frame,
        scale,
        &[element],
        &[Rectangle::new((0, 0).into(), output_size)],
    )?;
    let sync = frame.finish()?;
    while sync.wait().is_err() {}

    let len = (buffer_size.w * buffer_size.h * 4) as usize;
    let mut data = Vec::with_capacity(len);
    assert_eq!(buffer.stride(), (buffer_size.w * 4) as usize);
    data.extend_from_slice(unsafe { std::slice::from_raw_parts(buffer.data() as *mut u8, len) });

    let hotspot = Point::<i32, BufferCoords>::from((image.xhot as i32, image.yhot as i32))
        .to_f64()
        .to_logical(
            image_scale as f64,
            Transform::Normal,
            &Size::from((image.width as f64, image.height as f64)),
        )
        .to_buffer(
            scale,
            Transform::Normal,
            &output_size.to_logical(1).to_f64(),
        )
        .to_i32_round::<i32>();
    Ok((
        data,
        Size::from((buffer_size.w as u16, buffer_size.h as u16)),
        Point::from((hotspot.x as u16, hotspot.y as u16)),
    ))
}

impl XWaylandState {
    pub fn reload_cursor(&mut self, scale: f64) {
        if let Some(wm) = self.xwm.as_mut() {
            let (theme, size) = load_cursor_theme();
            let cursor = Cursor::load(&theme, CursorIcon::Default, size);
            let image = cursor.get_image(scale.ceil() as u32, 0);

            let (pixels_rgba, size, hotspot) = match scale_cursor(scale, size, &image) {
                Ok(x) => x,
                Err(err) => {
                    warn!("Failed to scale Xwayland cursor image: {}", err);
                    (
                        image.pixels_rgba,
                        Size::from((image.width as u16, image.height as u16)),
                        Point::from((image.xhot as u16, image.yhot as u16)),
                    )
                }
            };

            if let Err(err) = wm.set_cursor(&pixels_rgba, size, hotspot) {
                warn!(
                    id = ?wm.id(),
                    ?err,
                    "Failed to set default cursor for Xwayland WM",
                );
            }
        }
    }
}

impl Common {
    pub fn has_x_keyboard_focus(&self, xwmid: XwmId) -> bool {
        let keyboard = self
            .shell
            .read()
            .seats
            .last_active()
            .get_keyboard()
            .unwrap();

        keyboard
            .current_focus()
            .is_some_and(|target| target.is_xwm(xwmid))
    }

    fn has_x_pointer_focus(&self, xwmid: XwmId) -> bool {
        let pointer = self.shell.read().seats.last_active().get_pointer().unwrap();

        if let Some(x_client) = self.xwayland_state.as_ref().and_then(|xstate| {
            xstate
                .xwm
                .as_ref()
                .is_some_and(|xwm| xwm.id() == xwmid)
                .then_some(&xstate.client)
        }) {
            pointer
                .current_focus()
                .is_some_and(|target| target.is_client(x_client))
        } else {
            false
        }
    }

    pub fn xwayland_notify_focus_change(
        &mut self,
        target: Option<KeyboardFocusTarget>,
        serial: Serial,
    ) {
        if let Some(xwm_id) = self
            .xwayland_state
            .as_ref()
            .and_then(|xstate| xstate.xwm.as_ref())
            .map(|xwm| xwm.id())
        {
            if target
                .as_ref()
                .is_some_and(|target| matches!(target, KeyboardFocusTarget::LockSurface(_)))
            {
                self.xwayland_reset_eavesdropping(serial);
                return;
            }

            if !self.has_x_keyboard_focus(xwm_id)
                && target.as_ref().is_some_and(|target| target.is_xwm(xwm_id))
            {
                self.xwayland_reset_eavesdropping(serial);

                let xstate = self.xwayland_state.as_mut().unwrap();
                if let Some(mime_types) = xstate.clipboard_selection_dirty.take()
                    && let Err(err) = xstate
                        .xwm
                        .as_mut()
                        .unwrap()
                        .new_selection(SelectionTarget::Clipboard, Some(mime_types))
                {
                    warn!(?err, "Failed to set Xwayland clipboard selection.");
                }
                if let Some(mime_types) = xstate.primary_selection_dirty.take()
                    && let Err(err) = xstate
                        .xwm
                        .as_mut()
                        .unwrap()
                        .new_selection(SelectionTarget::Primary, Some(mime_types))
                {
                    warn!(?err, "Failed to set Xwayland clipboard selection.");
                }
            }
        }
    }

    pub fn xwayland_reset_eavesdropping(&mut self, serial: Serial) {
        let seat = self.shell.read().seats.last_active().clone();
        let keyboard = seat.get_keyboard().unwrap();
        let pointer = seat.get_pointer().unwrap();

        let xstate = self.xwayland_state.as_mut().unwrap();
        xstate.last_modifier_state.take();
        for key in xstate.pressed_keys.drain(..).rev() {
            for wl_keyboard in keyboard.client_keyboards(&xstate.client) {
                wl_keyboard.key(serial.into(), 0, key.raw() - 8, KeyState::Released.into());
            }
        }
        for button in xstate.pressed_buttons.drain(..).rev() {
            for wl_pointer in pointer.client_pointers(&xstate.client) {
                wl_pointer.button(serial.into(), 0, button, ButtonState::Released.into());
            }
        }
    }

    #[profiling::function]
    pub fn xwayland_notify_key_event(
        &mut self,
        sym: Keysym,
        code: Keycode,
        state: KeyState,
        serial: Serial,
        time: u32,
    ) {
        let config = self.config.cosmic_conf.xwayland_eavesdropping.keyboard;
        if config == EavesdroppingKeyboardMode::None {
            return;
        }

        if self.xwayland_state.as_ref().is_none_or(|xstate| {
            xstate
                .xwm
                .as_ref()
                .is_none_or(|xwm| self.has_x_keyboard_focus(xwm.id()))
        }) {
            return;
        }

        let keyboard = self
            .shell
            .read()
            .seats
            .last_active()
            .get_keyboard()
            .unwrap();
        let modifiers = keyboard.modifier_state();
        let is_modifier = sym.is_modifier_key();

        let xstate = self.xwayland_state.as_mut().unwrap();
        if state == KeyState::Pressed {
            match config {
                EavesdroppingKeyboardMode::Modifiers => {
                    if !is_modifier {
                        return;
                    }
                }
                EavesdroppingKeyboardMode::Combinations => {
                    // don't forward alpha-numeric keys, just because shift is held, but forward shift itself
                    if !(is_modifier || modifiers.alt || modifiers.ctrl || modifiers.logo) {
                        return;
                    }
                }
                _ => {}
            }

            xstate.pressed_keys.push(code);
        } else {
            let mut removed = false;
            xstate.pressed_keys.retain(|c| {
                if *c == code {
                    removed = true;
                    false
                } else {
                    true
                }
            });

            if !removed {
                // Don't forward released events, we don't have a record off.
                return;
            }
        }

        tracing::trace!("Forwaring key {} {:?} to xwayland", code.raw() - 8, state);
        for wl_keyboard in keyboard.client_keyboards(&xstate.client) {
            wl_keyboard.key(serial.into(), time, code.raw() - 8, state.into());
            if xstate.last_modifier_state != Some(modifiers) {
                xstate.last_modifier_state = Some(modifiers);
                wl_keyboard.modifiers(
                    serial.into(),
                    modifiers.serialized.depressed,
                    modifiers.serialized.latched,
                    modifiers.serialized.locked,
                    modifiers.serialized.layout_effective,
                );
            }
        }
    }

    #[profiling::function]
    pub fn xwayland_notify_pointer_button_event(
        &mut self,
        button: u32,
        state: ButtonState,
        serial: Serial,
        time: u32,
    ) {
        if !self.config.cosmic_conf.xwayland_eavesdropping.pointer {
            return;
        }

        let pointer = self.shell.read().seats.last_active().get_pointer().unwrap();

        if self.xwayland_state.as_ref().is_none_or(|xstate| {
            xstate
                .xwm
                .as_ref()
                .is_none_or(|xwm| self.has_x_pointer_focus(xwm.id()))
        }) {
            return;
        }

        let xstate = self.xwayland_state.as_mut().unwrap();
        if state == ButtonState::Pressed {
            xstate.pressed_buttons.push(button);
        } else {
            let mut removed = false;
            xstate.pressed_buttons.retain(|b| {
                if *b == button {
                    removed = true;
                    false
                } else {
                    true
                }
            });

            if !removed {
                // Don't forward released events, we don't have a record off.
                // This can happen if `xwayland_reset_eavesdropping` was called in between
                return;
            }
        }

        tracing::trace!("Forwaring ptr button {} {:?} to Xwayland", button, state);
        for wl_pointer in pointer.client_pointers(&xstate.client) {
            wl_pointer.button(serial.into(), time, button, state.into());
        }
    }

    #[profiling::function]
    pub fn update_x11_stacking_order(&mut self) {
        let shell = self.shell.read();
        let seat = shell.seats.last_active();
        let active_output = seat.active_output();

        if let Some(xwm) = self
            .xwayland_state
            .as_mut()
            .and_then(|state| state.xwm.as_mut())
        {
            // front to back, given that is how the workspace enumerates
            let order = shell
                .workspaces
                .sets
                .iter()
                .filter(|(output, _)| *output == &active_output)
                .chain(
                    shell
                        .workspaces
                        .sets
                        .iter()
                        .filter(|(output, _)| *output != &active_output),
                )
                .flat_map(|(_, set)| {
                    set.sticky_layer
                        .mapped()
                        .flat_map(|mapped| {
                            let active = mapped.active_window();
                            std::iter::once(active.clone()).chain(
                                mapped
                                    .is_stack()
                                    .then(move || {
                                        mapped
                                            .windows()
                                            .map(|(s, _)| s)
                                            .filter(move |s| s != &active)
                                    })
                                    .into_iter()
                                    .flatten(),
                            )
                        })
                        .chain(
                            set.workspaces
                                .iter()
                                .enumerate()
                                .filter(|(i, _)| *i == set.active)
                                .chain(
                                    set.workspaces
                                        .iter()
                                        .enumerate()
                                        .filter(|(i, _)| *i != set.active),
                                )
                                .flat_map(|(_, workspace)| {
                                    let focus_last =
                                        workspace.focus_stack.get(seat).last().cloned();
                                    workspace
                                        .get_fullscreen_surfaces()
                                        .filter(|f| {
                                            focus_last.as_ref().is_some_and(|t| t == &f.surface)
                                        })
                                        .map(|f| f.surface.clone())
                                        .collect::<Vec<_>>()
                                        .into_iter()
                                        .chain(workspace.mapped().flat_map(|mapped| {
                                            let active = mapped.active_window();
                                            std::iter::once(active.clone()).chain(
                                                mapped
                                                    .is_stack()
                                                    .then(move || {
                                                        mapped
                                                            .windows()
                                                            .map(|(s, _)| s)
                                                            .filter(move |s| s != &active)
                                                    })
                                                    .into_iter()
                                                    .flatten(),
                                            )
                                        }))
                                        .chain(
                                            workspace
                                                .get_fullscreen_surfaces()
                                                .filter(|f| {
                                                    focus_last
                                                        .as_ref()
                                                        .is_none_or(|t| t != &f.surface)
                                                })
                                                .map(|f| f.surface.clone())
                                                .collect::<Vec<_>>()
                                                .into_iter(),
                                        )
                                        .chain(
                                            workspace
                                                .minimized_windows
                                                .iter()
                                                .flat_map(|m| m.windows()),
                                        )
                                }),
                        )
                })
                .collect::<Vec<_>>();

            // we don't include the popup elements, which contain the OR windows, because we are not supposed to restack them.
            // Which is also why we match upwards, to not disturb elements at the top.
            //
            // But this also means we need to match across all outputs and workspaces at once, to be sure nothing that shouldn't be on top of us is.
            if let Err(err) = xwm.update_stacking_order_upwards(order.iter().rev()) {
                warn!(wm_id = ?xwm.id(), ?err, "Failed to update Xwm stacking order.");
            }
        }
    }

    pub fn update_xwayland_settings(&mut self) {
        let new_scale = match self.config.cosmic_conf.descale_xwayland {
            XwaylandDescaling::Disabled => 1.,
            XwaylandDescaling::Enabled => {
                let shell = self.shell.read();
                shell
                    .outputs()
                    .map(|o| o.current_scale().integer_scale())
                    .max()
                    .unwrap_or(1) as f64
            }
            XwaylandDescaling::Fractional => {
                let shell = self.shell.read();

                if let Some(output) = shell.outputs().find(|o| o.config().xwayland_primary) {
                    output.current_scale().fractional_scale().max(1f64)
                } else {
                    shell
                        .outputs()
                        .map(|o| o.current_scale().fractional_scale())
                        .fold(1f64, |acc, val| acc.max(val))
                }
            }
        };
        let (_, cursor_size) = load_cursor_env();

        // compare with current scale
        if let Some(xwayland) = self.xwayland_state.as_mut() {
            let geometries = if Some(new_scale) != self.xwayland_scale {
                // backup geometries
                let geometries = self
                    .shell
                    .read()
                    .mapped()
                    .flat_map(|m| m.windows().map(|(s, _)| s))
                    .filter_map(|s| s.0.x11_surface().map(|x| (x.clone(), x.geometry())))
                    .collect::<Vec<_>>();

                // update xorg dpi
                if let Some(xwm) = xwayland.xwm.as_mut() {
                    let base = 96. * 1024.;
                    let dpi = new_scale * base;
                    let fractional = new_scale.fract();
                    let integer = (new_scale - fractional).max(1.0);
                    let unscaled_dpi = base * (1.0 + fractional / integer);
                    if let Err(err) = xwm.set_xsettings(
                        [
                            ("Xft/DPI".into(), (dpi.round() as i32).into()),
                            (
                                "Gdk/UnscaledDPI".into(),
                                (unscaled_dpi.round() as i32).into(),
                            ),
                            (
                                "Gdk/WindowScalingFactor".into(),
                                (new_scale.floor() as i32).into(),
                            ),
                            (
                                "Gtk/CursorThemeSize".into(),
                                ((new_scale * cursor_size as f64).round() as i32).into(),
                            ),
                        ]
                        .into_iter(),
                    ) {
                        warn!(wm_id = ?xwm.id(), ?err, "Failed to update XSETTINGS.");
                    }
                }

                Some(geometries)
            } else {
                None
            };

            if xwayland
                .xrdb_thread
                .send((
                    cosmic::icon_theme::default(),
                    (new_scale * cursor_size as f64).round() as u32,
                ))
                .is_err()
            {
                warn!("xrdb thread died");
            }

            // update cursor
            xwayland.reload_cursor(new_scale);

            if let Some(geometries) = geometries {
                // update client scale
                xwayland
                    .client
                    .get_data::<XWaylandClientData>()
                    .unwrap()
                    .compositor_state
                    .set_client_scale(new_scale);

                // update wl/xdg_outputs
                for output in self.shell.read().outputs() {
                    output.change_current_state(None, None, None, None);
                }

                // update geometries
                for (surface, geometry) in geometries.iter() {
                    if let Err(err) = surface.configure(*geometry) {
                        warn!(?err, surface = ?surface.window_id(), "Failed to update geometry after scale change");
                    }
                }
                self.update_x11_stacking_order();

                self.xwayland_scale = Some(new_scale);
            }
        }
    }

    pub fn update_xwayland_primary_output(&mut self) {
        let mut xwayland_primary_output = None;
        for output in self.output_configuration_state.outputs() {
            if output.config().xwayland_primary {
                xwayland_primary_output = Some(output);
                break;
            }
        }

        if let Some(xstate) = self.xwayland_state.as_mut()
            && let Some(xwm) = xstate.xwm.as_mut()
            && let Err(err) = xwm.set_randr_primary_output(xwayland_primary_output.as_ref())
        {
            warn!("Failed to set xwayland primary output: {}", err);
            return;
        };

        self.output_configuration_state.update();
    }
}

impl XwmHandler for State {
    fn xwm_state(&mut self, _xwm: XwmId) -> &mut X11Wm {
        self.common
            .xwayland_state
            .as_mut()
            .and_then(|state| state.xwm.as_mut())
            .unwrap()
    }

    fn new_window(&mut self, _xwm: XwmId, _window: X11Surface) {}
    fn new_override_redirect_window(&mut self, _xwm: XwmId, _window: X11Surface) {}
    fn destroyed_window(&mut self, _xwm: XwmId, _window: X11Surface) {}

    fn map_window_request(&mut self, _xwm: XwmId, window: X11Surface) {
        if let Err(err) = window.set_mapped(true) {
            warn!(?window, ?err, "Failed to send Xwayland Mapped-Event",);
        }

        let mut shell = self.common.shell.write();
        let startup_id = window.startup_id();
        if shell.is_surface_mapped(&window) {
            warn!(
                ?window,
                "Got map_request for already mapped window? Ignoring"
            );
            return;
        }

        let seat = shell.seats.last_active().clone();
        if let Some(context) = startup_id
            .map(XdgActivationToken::from)
            .and_then(|token| self.common.xdg_activation_state.data_for_token(&token))
            .and_then(|data| data.user_data.get::<ActivationContext>())
        {
            shell.pending_activations.insert(
                crate::shell::ActivationKey::X11(window.window_id()),
                *context,
            );
        }
        if !shell.pending_windows.iter().any(|w| w.surface == window) {
            let fullscreen = window.is_fullscreen().then(|| seat.active_output());
            let maximized = window.is_maximized();
            let surface = CosmicSurface::from(window);
            shell.pending_windows.push(PendingWindow {
                surface,
                seat,
                fullscreen,
                maximized,
                sticky: false,
            })
        }
    }

    fn map_window_notify(&mut self, _xwm: XwmId, surface: X11Surface) {
        let mut shell = self.common.shell.write();
        if let Some(window) = shell
            .pending_windows
            .iter()
            .find(|pending| pending.surface.x11_surface() == Some(&surface))
            .map(|pending| pending.surface.clone())
        {
            if let std::collections::hash_map::Entry::Vacant(e) = shell
                .pending_activations
                .entry(crate::shell::ActivationKey::X11(surface.window_id()))
                && let Some(startup_id) = window.x11_surface().and_then(|x| x.startup_id())
                && let Some(context) = self
                    .common
                    .xdg_activation_state
                    .data_for_token(&XdgActivationToken::from(startup_id))
                    .and_then(|data| data.user_data.get::<ActivationContext>())
            {
                e.insert(*context);
            }
            let res = shell.map_window(
                &window,
                &mut self.common.toplevel_info_state,
                &mut self.common.workspace_state,
                &self.common.event_loop_handle,
            );
            if let Some(target) = res {
                let seat = shell.seats.last_active().clone();
                std::mem::drop(shell);
                Shell::set_focus(self, Some(&target), &seat, None, false);
            }
        }
    }

    fn mapped_override_redirect_window(&mut self, _xwm: XwmId, window: X11Surface) {
        let mut shell = self.common.shell.write();
        if shell
            .override_redirect_windows
            .iter()
            .any(|or| or == &window)
        {
            return;
        }
        shell.map_override_redirect(window)
    }

    fn unmapped_window(&mut self, _xwm: XwmId, window: X11Surface) {
        let mut shell = self.common.shell.write();
        if window.is_override_redirect() {
            shell.override_redirect_windows.retain(|or| or != &window);
        } else {
            let seat = shell.seats.last_active().clone();
            if let Some(pending) =
                shell.unmap_surface(&window, &seat, &mut self.common.toplevel_info_state)
                && !shell
                    .pending_windows
                    .iter()
                    .any(|w| w.surface == pending.surface)
            {
                shell.pending_windows.push(pending);
            }
        }

        let outputs = if let Some(wl_surface) = window.wl_surface() {
            shell
                .visible_output_for_surface(&wl_surface)
                .into_iter()
                .cloned()
                .collect::<Vec<_>>()
        } else {
            shell.outputs().cloned().collect::<Vec<_>>()
        };
        for output in outputs.iter() {
            shell.refresh_active_space(output);
        }

        for output in outputs.into_iter() {
            self.backend.schedule_render(&output);
        }
    }

    fn configure_request(
        &mut self,
        _xwm: XwmId,
        window: X11Surface,
        x: Option<i32>,
        y: Option<i32>,
        w: Option<u32>,
        h: Option<u32>,
        _reorder: Option<Reorder>,
    ) {
        // We only allow floating X11 windows to resize themselves. Nothing else
        let shell = self.common.shell.read();

        if window.is_fullscreen() {
            let _ = window.configure(None);
            return;
        }

        if let Some(mapped) = shell
            .element_for_surface(&window)
            .filter(|mapped| !mapped.is_minimized())
        {
            let current_geo = if let Some(workspace) = shell.space_for(mapped) {
                workspace
                    .element_geometry(mapped)
                    .filter(|_| workspace.is_floating(&window))
                    .map(|geo| geo.to_global(workspace.output()))
            } else if let Some((output, set)) = shell
                .workspaces
                .sets
                .iter()
                .find(|(_, set)| set.sticky_layer.mapped().any(|m| m == mapped))
            {
                Some(
                    set.sticky_layer
                        .element_geometry(mapped)
                        .unwrap()
                        .to_global(output),
                )
            } else {
                None
            };

            if let Some(current_geo) = current_geo {
                let ssd_height = mapped.ssd_height(false).unwrap_or(0);
                mapped.set_geometry(Rectangle::new(
                    current_geo.loc,
                    (
                        w.map(|w| w as i32).unwrap_or(current_geo.size.w),
                        h.map(|h| h as i32 + ssd_height)
                            .unwrap_or(current_geo.size.h),
                    )
                        .into(),
                ))
            } else {
                let _ = window.configure(None); // ack and force old state
            }
        } else {
            let mut current_geo = window.geometry();
            if let Some(x) = x {
                current_geo.loc.x = x;
            }
            if let Some(y) = y {
                current_geo.loc.y = y;
            }
            if let Some(w) = w {
                current_geo.size.w = w as i32;
            }
            if let Some(h) = h {
                current_geo.size.h = h as i32;
            }
            // the window is not yet mapped. Lets give it what it wants
            let _ = window.configure(current_geo);
        }
    }

    fn configure_notify(
        &mut self,
        _xwm: XwmId,
        window: X11Surface,
        _geometry: Rectangle<i32, Logical>,
        above: Option<X11Window>,
    ) {
        if window.is_override_redirect() {
            let mut shell = self.common.shell.write();
            if let Some(id) = above {
                let or_windows = &mut shell.override_redirect_windows;
                if let Some(own_pos) = or_windows.iter().position(|or| or == &window) {
                    let compare_pos = or_windows
                        .iter()
                        .position(|or| or.window_id() == id)
                        .unwrap_or(0);
                    if compare_pos > own_pos {
                        let this = or_windows.remove(own_pos);
                        or_windows.insert(compare_pos, this);
                    }
                }
            }

            let geo = window.geometry().as_global();
            for (output, overlap) in shell.outputs().cloned().map(|o| {
                let intersection = o.geometry().intersection(geo);
                (o, intersection)
            }) {
                if let Some(overlap) = overlap {
                    window.output_enter(&output, overlap.as_logical());
                } else {
                    window.output_leave(&output);
                }
            }
        }
    }

    fn resize_request(
        &mut self,
        _xwm: XwmId,
        window: X11Surface,
        _button: u32,
        resize_edge: smithay::xwayland::xwm::ResizeEdge,
    ) {
        if let Some(wl_surface) = window.wl_surface() {
            let mut shell = self.common.shell.write();
            let seat = shell.seats.last_active().clone();
            if let Some((grab, focus)) = shell.resize_request(
                &wl_surface,
                &seat,
                None,
                resize_edge.into(),
                self.common.config.cosmic_conf.edge_snap_threshold,
                true,
            ) {
                std::mem::drop(shell);
                if grab.is_touch_grab() {
                    seat.get_touch()
                        .unwrap()
                        .set_grab(self, grab, SERIAL_COUNTER.next_serial())
                } else {
                    seat.get_pointer().unwrap().set_grab(
                        self,
                        grab,
                        SERIAL_COUNTER.next_serial(),
                        focus,
                    )
                }
            }
        }
    }

    fn move_request(&mut self, _xwm: XwmId, window: X11Surface, _button: u32) {
        if let Some(wl_surface) = window.wl_surface() {
            let mut shell = self.common.shell.write();
            let seat = shell.seats.last_active().clone();
            if let Some((grab, focus)) = shell.move_request(
                &wl_surface,
                &seat,
                None,
                ReleaseMode::NoMouseButtons,
                false,
                &self.common.config,
                &self.common.event_loop_handle,
                true,
            ) {
                std::mem::drop(shell);
                if grab.is_touch_grab() {
                    seat.get_touch()
                        .unwrap()
                        .set_grab(self, grab, SERIAL_COUNTER.next_serial())
                } else {
                    seat.get_pointer().unwrap().set_grab(
                        self,
                        grab,
                        SERIAL_COUNTER.next_serial(),
                        focus,
                    )
                }
            }
        }
    }

    fn maximize_request(&mut self, _xwm: XwmId, window: X11Surface) {
        let mut shell = self.common.shell.write();
        if let Some(mapped) = shell.element_for_surface(&window).cloned() {
            let seat = shell.seats.last_active().clone();
            shell.maximize_request(&mapped, &seat, true, &self.common.event_loop_handle);
        } else if let Some(pending) = shell
            .pending_windows
            .iter_mut()
            .find(|pending| pending.surface.x11_surface() == Some(&window))
        {
            pending.maximized = true;
        }
    }

    fn unmaximize_request(&mut self, _xwm: XwmId, window: X11Surface) {
        let mut shell = self.common.shell.write();
        if let Some(mapped) = shell.element_for_surface(&window).cloned() {
            shell.unmaximize_request(&mapped);
        } else if let Some(pending) = shell
            .pending_windows
            .iter_mut()
            .find(|pending| pending.surface.x11_surface() == Some(&window))
        {
            pending.maximized = false;
        }
    }

    fn minimize_request(&mut self, _xwm: XwmId, window: X11Surface) {
        let mut shell = self.common.shell.write();
        shell.minimize_request(&window);
    }

    fn unminimize_request(&mut self, _xwm: XwmId, window: X11Surface) {
        let mut shell = self.common.shell.write();
        let seat = shell.seats.last_active().clone();
        shell.unminimize_request(&window, &seat, &self.common.event_loop_handle);
    }

    fn fullscreen_request(&mut self, _xwm: XwmId, window: X11Surface) {
        let mut shell = self.common.shell.write();
        let seat = shell.seats.last_active().clone();
        let output = window
            .wl_surface()
            .and_then(|surface| shell.visible_output_for_surface(&surface).cloned())
            .unwrap_or_else(|| seat.focused_or_active_output());

        match shell.fullscreen_request(&window, output.clone(), &self.common.event_loop_handle) {
            Some(target) => {
                std::mem::drop(shell);
                Shell::set_focus(self, Some(&target), &seat, None, true);
            }
            None => {
                if let Some(pending) = shell
                    .pending_windows
                    .iter_mut()
                    .find(|pending| pending.surface.x11_surface() == Some(&window))
                {
                    pending.fullscreen = Some(output);
                }
            }
        }
    }

    fn unfullscreen_request(&mut self, _xwm: XwmId, window: X11Surface) {
        let mut shell = self.common.shell.write();
        let seat = shell.seats.last_active().clone();
        let should_focus = seat
            .get_keyboard()
            .unwrap()
            .current_focus()
            .is_some_and(|target| {
                if let KeyboardFocusTarget::Fullscreen(s) = target {
                    s == window
                } else {
                    false
                }
            });

        if let Some(target) = shell.unfullscreen_request(&window, &self.common.event_loop_handle) {
            std::mem::drop(shell);
            if should_focus {
                Shell::set_focus(self, Some(&target), &seat, None, true);
            }
        } else if let Some(pending) = shell
            .pending_windows
            .iter_mut()
            .find(|pending| pending.surface.x11_surface() == Some(&window))
        {
            pending.fullscreen.take();
        }
    }

    fn stick_request(&mut self, _xwm: XwmId, window: X11Surface) {
        let mut shell = self.common.shell.write();
        if let Some(pending) = shell
            .pending_windows
            .iter_mut()
            .find(|pending| pending.surface.x11_surface() == Some(&window))
        {
            pending.sticky = true;
        }
    }

    fn unstick_request(&mut self, _xwm: XwmId, window: X11Surface) {
        let mut shell = self.common.shell.write();
        if let Some(pending) = shell
            .pending_windows
            .iter_mut()
            .find(|pending| pending.surface.x11_surface() == Some(&window))
        {
            pending.sticky = false;
        }
    }

    fn active_window_request(
        &mut self,
        _xwm: XwmId,
        window: X11Surface,
        _timestamp: u32,
        _currently_active_window: Option<X11Surface>,
    ) {
        if let Some(surface) = window.wl_surface() {
            self.activate_surface(&surface, None);
        }
    }

    fn send_selection(
        &mut self,
        _xwm: XwmId,
        selection: SelectionTarget,
        mime_type: String,
        fd: OwnedFd,
    ) {
        let seat = self.common.shell.read().seats.last_active().clone();
        match selection {
            SelectionTarget::Clipboard => {
                if let Err(err) = request_data_device_client_selection(&seat, mime_type, fd) {
                    error!(
                        ?err,
                        "Failed to request current wayland clipboard for Xwayland.",
                    );
                }
            }
            SelectionTarget::Primary => {
                if let Err(err) = request_primary_client_selection(&seat, mime_type, fd) {
                    error!(
                        ?err,
                        "Failed to request current wayland primary selection for Xwayland.",
                    );
                }
            }
        }
    }

    fn allow_selection_access(&mut self, xwm: XwmId, _selection: SelectionTarget) -> bool {
        self.common.has_x_keyboard_focus(xwm)
    }

    fn new_selection(&mut self, xwm: XwmId, selection: SelectionTarget, mime_types: Vec<String>) {
        trace!(?selection, ?mime_types, "Got Selection from Xwayland",);

        let seat = self.common.shell.read().seats.last_active().clone();
        match selection {
            SelectionTarget::Clipboard => {
                set_data_device_selection(&self.common.display_handle, &seat, mime_types, xwm)
            }
            SelectionTarget::Primary => {
                set_primary_selection(&self.common.display_handle, &seat, mime_types, xwm)
            }
        }
    }

    fn cleared_selection(&mut self, xwm: XwmId, selection: SelectionTarget) {
        let shell = self.common.shell.read();
        for seat in shell.seats.iter() {
            match selection {
                SelectionTarget::Clipboard => {
                    if current_data_device_selection_userdata(seat).as_deref() == Some(&xwm) {
                        clear_data_device_selection(&self.common.display_handle, seat)
                    }
                }
                SelectionTarget::Primary => {
                    if current_primary_selection_userdata(seat).as_deref() == Some(&xwm) {
                        clear_primary_selection(&self.common.display_handle, seat)
                    }
                }
            }
        }
    }

    fn randr_primary_output_change(&mut self, _xwm: XwmId, output_name: Option<String>) {
        for output in self.common.output_configuration_state.outputs() {
            output.config_mut().xwayland_primary =
                output_name.as_deref().is_some_and(|o| o == output.name());
        }
        self.common.output_configuration_state.update();
        self.common.update_xwayland_settings();
        self.common
            .config
            .write_outputs(self.common.output_configuration_state.outputs());
    }

    fn disconnected(&mut self, _xwm: XwmId) {
        let xwayland_state = self.common.xwayland_state.as_mut().unwrap();
        xwayland_state.xwm = None;
    }
}
