#[cfg(feature = "raw_handle")]
use std::ptr::NonNull;
#[cfg(feature = "gtk4_wayland")]
use std::sync::Arc;

#[cfg(feature = "gtk4_wayland")]
use futures_util::lock::Mutex;
use gdk::Backend;
#[cfg(feature = "raw_handle")]
use glib::translate::ToGlibPtr;
use gtk4::{gdk, glib, prelude::*};
#[cfg(feature = "raw_handle")]
use raw_window_handle::{
    DisplayHandle, RawDisplayHandle, RawWindowHandle, WaylandDisplayHandle, WaylandWindowHandle,
    WindowHandle, XlibDisplayHandle, XlibWindowHandle,
};

use super::WindowIdentifierType;

#[cfg(feature = "gtk4_wayland")]
const WINDOW_HANDLE_KEY: &str = "ashpd-wayland-gtk4-window-handle";

pub struct Gtk4WindowIdentifier {
    #[allow(dead_code)]
    native: gtk4::Native,
    type_: WindowIdentifierType,
    exported: bool,
}

impl Gtk4WindowIdentifier {
    pub async fn new(native: &impl glib::prelude::IsA<gtk4::Native>) -> Option<Self> {
        let surface = native.surface()?;
        match surface.display().backend() {
            #[cfg(feature = "gtk4_wayland")]
            Backend::Wayland => {
                let top_level = surface
                    .downcast_ref::<gdk4wayland::WaylandToplevel>()
                    .unwrap();
                let handle = unsafe {
                    if let Some(mut handle) = top_level.data(WINDOW_HANDLE_KEY) {
                        let (handle, ref_count): &mut (Option<String>, u8) = handle.as_mut();
                        *ref_count += 1;
                        handle.clone()
                    } else {
                        let (sender, receiver) =
                            futures_channel::oneshot::channel::<Option<String>>();
                        let sender = Arc::new(Mutex::new(Some(sender)));

                        let result = top_level.export_handle(glib::clone!(#[strong] sender, move |_, handle| {
                            let handle = handle.map(ToOwned::to_owned);
                            glib::spawn_future_local(glib::clone!(#[strong] sender, #[strong] handle, async move {
                                if let Some(m) = sender.lock().await.take() {
                                    match handle {
                                        Ok(h) => {
                                            let _ = m.send(Some(h.to_string()));
                                        },
                                        Err(_err) => {
                                            let _ = m.send(None);
                                            #[cfg(feature = "tracing")]
                                            tracing::warn!("Failed to export window identifier. The compositor doesn't support xdg-foreign protocol. {_err}");
                                        }
                                    }
                                }
                            }));
                        }));

                        if !result {
                            return None;
                        }
                        let handle = receiver.await.ok().flatten();
                        top_level.set_data(WINDOW_HANDLE_KEY, (handle.clone(), 1));
                        handle
                    }
                };
                Some(Gtk4WindowIdentifier {
                    native: native.clone().upcast(),
                    exported: handle.is_some(),
                    type_: WindowIdentifierType::Wayland(handle.unwrap_or_default()),
                })
            }
            #[cfg(feature = "gtk4_x11")]
            Backend::X11 => {
                let xid = surface
                    .downcast_ref::<gdk4x11::X11Surface>()
                    .map(|w| w.xid())?;
                Some(Gtk4WindowIdentifier {
                    native: native.clone().upcast(),
                    exported: false,
                    type_: WindowIdentifierType::X11(xid),
                })
            }
            _ => None,
        }
    }

    #[cfg(feature = "raw_handle")]
    pub fn as_raw_window_handle(&self) -> WindowHandle<'_> {
        unsafe {
            let raw_handle = match self.type_ {
                #[cfg(feature = "gtk4_wayland")]
                WindowIdentifierType::Wayland(_) => {
                    let surface = self.native.surface().unwrap();
                    RawWindowHandle::Wayland(WaylandWindowHandle::new(
                        NonNull::new(gdk4wayland::ffi::gdk_wayland_surface_get_wl_surface(
                            surface
                                .downcast_ref::<gdk4wayland::WaylandSurface>()
                                .unwrap()
                                .to_glib_none()
                                .0,
                        ))
                        .expect("Identifier must be attached to a wl_surface"),
                    ))
                }
                #[cfg(feature = "gtk4_x11")]
                WindowIdentifierType::X11(xid) => RawWindowHandle::Xlib(XlibWindowHandle::new(xid)),
            };
            WindowHandle::borrow_raw(raw_handle)
        }
    }

    #[cfg(feature = "raw_handle")]
    pub fn as_raw_display_handle(&self) -> DisplayHandle<'_> {
        let surface = self.native.surface().unwrap();
        let display = surface.display();
        unsafe {
            let raw_handle = match self.type_ {
                #[cfg(feature = "gtk4_wayland")]
                WindowIdentifierType::Wayland(_) => {
                    RawDisplayHandle::Wayland(WaylandDisplayHandle::new(
                        NonNull::new(gdk4wayland::ffi::gdk_wayland_display_get_wl_display(
                            display
                                .downcast_ref::<gdk4wayland::WaylandDisplay>()
                                .unwrap()
                                .to_glib_none()
                                .0,
                        ))
                        .expect("Identifier must be attached to a wl_display"),
                    ))
                }
                #[cfg(feature = "gtk4_x11")]
                WindowIdentifierType::X11(_xid) => RawDisplayHandle::Xlib(XlibDisplayHandle::new(
                    NonNull::new(gdk4x11::ffi::gdk_x11_display_get_xdisplay(
                        display
                            .downcast_ref::<gdk4x11::X11Display>()
                            .unwrap()
                            .to_glib_none()
                            .0,
                    )),
                    display
                        .downcast_ref::<gdk4x11::X11Display>()
                        .unwrap()
                        .screen()
                        .screen_number(),
                )),
            };
            DisplayHandle::borrow_raw(raw_handle)
        }
    }
}

impl std::fmt::Display for Gtk4WindowIdentifier {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(&format!("{}", self.type_))
    }
}

impl Drop for Gtk4WindowIdentifier {
    fn drop(&mut self) {
        if !self.exported {
            return;
        }
        match self.type_ {
            #[cfg(feature = "gtk4_wayland")]
            WindowIdentifierType::Wayland(_) => {
                let surface = self.native.surface().unwrap();
                let top_level = surface
                    .downcast_ref::<gdk4wayland::WaylandToplevel>()
                    .unwrap();
                unsafe {
                    let (_handle, ref_count): &mut (Option<String>, u8) =
                        top_level.data(WINDOW_HANDLE_KEY).unwrap().as_mut();
                    if ref_count > &mut 1 {
                        *ref_count -= 1;
                        return;
                    }
                    top_level.unexport_handle();
                    #[cfg(feature = "tracing")]
                    tracing::debug!("Unexporting handle: {_handle:?}");
                    let _ = top_level.steal_data::<(Option<String>, u8)>(WINDOW_HANDLE_KEY);
                }
            }
            _ => (),
        }
    }
}
