use {
    crate::{
        client::{Client, ClientCaps},
        ifs::{
            color_management::wp_color_manager_v1::WpColorManagerV1Global,
            ext_foreign_toplevel_image_capture_source_manager_v1::ExtForeignToplevelImageCaptureSourceManagerV1Global,
            ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1Global,
            ext_idle_notifier_v1::ExtIdleNotifierV1Global,
            ext_image_copy::ext_image_copy_capture_manager_v1::ExtImageCopyCaptureManagerV1Global,
            ext_output_image_capture_source_manager_v1::ExtOutputImageCaptureSourceManagerV1Global,
            ext_session_lock_manager_v1::ExtSessionLockManagerV1Global,
            head_management::jay_head_manager_v1::JayHeadManagerV1Global,
            hyprland_focus_grab_manager_v1::HyprlandFocusGrabManagerV1Global,
            ipc::{
                data_control::{
                    ext_data_control_manager_v1::ExtDataControlManagerV1Global,
                    zwlr_data_control_manager_v1::ZwlrDataControlManagerV1Global,
                },
                wl_data_device_manager::WlDataDeviceManagerGlobal,
                zwp_primary_selection_device_manager_v1::ZwpPrimarySelectionDeviceManagerV1Global,
            },
            jay_compositor::JayCompositorGlobal,
            jay_damage_tracking::JayDamageTrackingGlobal,
            jay_popup_ext_manager_v1::JayPopupExtManagerV1Global,
            org_kde_kwin_server_decoration_manager::OrgKdeKwinServerDecorationManagerGlobal,
            wl_compositor::WlCompositorGlobal,
            wl_drm::WlDrmGlobal,
            wl_fixes::WlFixesGlobal,
            wl_output::WlOutputGlobal,
            wl_registry::WlRegistry,
            wl_seat::{
                ext_transient_seat_manager_v1::ExtTransientSeatManagerV1Global,
                tablet::zwp_tablet_manager_v2::ZwpTabletManagerV2Global,
                text_input::{
                    zwp_input_method_manager_v2::ZwpInputMethodManagerV2Global,
                    zwp_text_input_manager_v3::ZwpTextInputManagerV3Global,
                },
                wp_pointer_warp_v1::WpPointerWarpV1Global,
                zwp_pointer_constraints_v1::ZwpPointerConstraintsV1Global,
                zwp_pointer_gestures_v1::ZwpPointerGesturesV1Global,
                zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1Global,
                zwp_virtual_keyboard_manager_v1::ZwpVirtualKeyboardManagerV1Global,
                WlSeatGlobal,
            },
            wl_shm::WlShmGlobal,
            wl_subcompositor::WlSubcompositorGlobal,
            wl_surface::xwayland_shell_v1::XwaylandShellV1Global,
            wlr_output_manager::zwlr_output_manager_v1::ZwlrOutputManagerV1Global,
            workspace_manager::ext_workspace_manager_v1::ExtWorkspaceManagerV1Global,
            wp_alpha_modifier_v1::WpAlphaModifierV1Global,
            wp_color_representation_manager_v1::WpColorRepresentationManagerV1Global,
            wp_commit_timing_manager_v1::WpCommitTimingManagerV1Global,
            wp_content_type_manager_v1::WpContentTypeManagerV1Global,
            wp_cursor_shape_manager_v1::WpCursorShapeManagerV1Global,
            wp_fifo_manager_v1::WpFifoManagerV1Global,
            wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1Global,
            wp_linux_drm_syncobj_manager_v1::WpLinuxDrmSyncobjManagerV1Global,
            wp_presentation::WpPresentationGlobal,
            wp_security_context_manager_v1::WpSecurityContextManagerV1Global,
            wp_single_pixel_buffer_manager_v1::WpSinglePixelBufferManagerV1Global,
            wp_tearing_control_manager_v1::WpTearingControlManagerV1Global,
            wp_viewporter::WpViewporterGlobal,
            xdg_activation_v1::XdgActivationV1Global,
            xdg_toplevel_drag_manager_v1::XdgToplevelDragManagerV1Global,
            xdg_toplevel_tag_manager_v1::XdgToplevelTagManagerV1Global,
            xdg_wm_base::XdgWmBaseGlobal,
            xdg_wm_dialog_v1::XdgWmDialogV1Global,
            xx_foreign_toplevel_geometry_tracking_manager_v1::XxForeignToplevelGeometryTrackingManagerV1Global,
            zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1Global,
            zwlr_gamma_control_manager_v1::ZwlrGammaControlManagerV1Global,
            zwlr_layer_shell_v1::ZwlrLayerShellV1Global,
            zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1Global,
            zwlr_virtual_pointer_manager_v1::ZwlrVirtualPointerManagerV1Global,
            zwp_idle_inhibit_manager_v1::ZwpIdleInhibitManagerV1Global,
            zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1Global,
            zxdg_decoration_manager_v1::ZxdgDecorationManagerV1Global,
            zxdg_output_manager_v1::ZxdgOutputManagerV1Global,
        },
        object::{Interface, ObjectId, Version},
        state::State,
        utils::{
            copyhashmap::{CopyHashMap, Locked},
            numcell::NumCell,
        },
    },
    arrayvec::ArrayVec,
    linearize::{Linearize, StaticMap},
    std::{
        cell::Cell,
        error::Error,
        fmt::{Display, Formatter},
        rc::Rc,
    },
    thiserror::Error,
};

#[derive(Debug, Error)]
pub enum GlobalsError {
    #[error("The requested global {0} does not exist")]
    GlobalDoesNotExist(GlobalName),
    #[error("The output with id {0} does not exist")]
    OutputDoesNotExist(GlobalName),
    #[error(transparent)]
    GlobalError(GlobalError),
}

#[derive(Debug, Error)]
#[error("An error occurred in a `{}` global", .interface.name())]
pub struct GlobalError {
    pub interface: Interface,
    #[source]
    pub error: Box<dyn Error>,
}

#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct GlobalName(u32);

impl GlobalName {
    pub fn from_raw(id: u32) -> Self {
        Self(id)
    }

    pub fn raw(self) -> u32 {
        self.0
    }
}

impl Display for GlobalName {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        Display::fmt(&self.0, f)
    }
}

pub trait GlobalBase {
    fn name(&self) -> GlobalName;
    fn bind<'a>(
        self: Rc<Self>,
        client: &'a Rc<Client>,
        id: ObjectId,
        version: Version,
    ) -> Result<(), GlobalsError>;
    fn interface(&self) -> Interface;
    fn singleton(&self) -> Option<Singleton>;
}

pub trait Global: GlobalBase {
    fn version(&self) -> u32;
    fn required_caps(&self) -> ClientCaps {
        ClientCaps::none()
    }
    fn xwayland_only(&self) -> bool {
        false
    }
    fn exposed(&self, state: &State) -> bool {
        let _ = state;
        true
    }
    fn permitted(&self, caps: ClientCaps, xwayland: bool) -> bool {
        caps.contains(self.required_caps()) && (xwayland || !self.xwayland_only())
    }
    fn not_permitted(&self, caps: ClientCaps, xwayland: bool) -> bool {
        !self.permitted(caps, xwayland)
    }
}

macro_rules! singletons {
    ($($name:ident,)*) => {
        #[derive(Copy, Clone, Debug, Linearize)]
        pub enum Singleton {
            $($name,)*
        }

        fn add_singletons(globals: &mut Globals) {
            $(
                let name = globals.name();
                with_builtin_macros::with_eager_expansions! {
                    globals.add_global_no_broadcast(&Rc::new(#{concat_idents!($name, Global)}::new(name)));
                }
                globals.singletons[Singleton::$name] = name;
            )*
        }

        #[expect(non_upper_case_globals)]
        pub mod interface_singletons {
            pub use crate::wire::interface_singletons::*;

            $(
                pub const $name: Option<crate::globals::Singleton> = Some(crate::globals::Singleton::$name);
            )*
        }
    };
}

singletons! {
    WlCompositor,
    WlShm,
    WlSubcompositor,
    XdgWmBase,
    WlDataDeviceManager,
    ZxdgDecorationManagerV1,
    OrgKdeKwinServerDecorationManager,
    ZwpPrimarySelectionDeviceManagerV1,
    ZwlrLayerShellV1,
    ZwlrOutputManagerV1,
    ZxdgOutputManagerV1,
    JayCompositor,
    ZwlrScreencopyManagerV1,
    ZwpRelativePointerManagerV1,
    ExtSessionLockManagerV1,
    HyprlandFocusGrabManagerV1,
    WpViewporter,
    WpFractionalScaleManagerV1,
    ZwpPointerConstraintsV1,
    XwaylandShellV1,
    WpTearingControlManagerV1,
    WpSinglePixelBufferManagerV1,
    WpCursorShapeManagerV1,
    WpContentTypeManagerV1,
    XdgActivationV1,
    ExtForeignToplevelListV1,
    ZwpIdleInhibitManagerV1,
    ExtIdleNotifierV1,
    XdgToplevelDragManagerV1,
    ZwlrForeignToplevelManagerV1,
    ZwlrDataControlManagerV1,
    WpAlphaModifierV1,
    ZwpVirtualKeyboardManagerV1,
    ZwpInputMethodManagerV2,
    ZwpTextInputManagerV3,
    WpSecurityContextManagerV1,
    XdgWmDialogV1,
    ExtTransientSeatManagerV1,
    ZwpPointerGesturesV1,
    ZwpTabletManagerV2,
    JayDamageTracking,
    ExtOutputImageCaptureSourceManagerV1,
    ExtForeignToplevelImageCaptureSourceManagerV1,
    ExtImageCopyCaptureManagerV1,
    WpFifoManagerV1,
    WpCommitTimingManagerV1,
    ExtDataControlManagerV1,
    WlFixes,
    ExtWorkspaceManagerV1,
    WpColorManagerV1,
    XdgToplevelTagManagerV1,
    JayHeadManagerV1,
    WpPointerWarpV1,
    JayPopupExtManagerV1,
    ZwlrGammaControlManagerV1,
    WpColorRepresentationManagerV1,
    WlDrm,
    ZwpLinuxDmabufV1,
    WpLinuxDrmSyncobjManagerV1,
    WpPresentation,
    ZwlrVirtualPointerManagerV1,
    XxForeignToplevelGeometryTrackingManagerV1,
}

pub struct Globals {
    next_name: NumCell<u32>,
    registry: CopyHashMap<GlobalName, Rc<dyn Global>>,
    removed: CopyHashMap<GlobalName, Rc<dyn Global>>,
    pub outputs: CopyHashMap<GlobalName, Rc<WlOutputGlobal>>,
    pub seats: CopyHashMap<GlobalName, Rc<WlSeatGlobal>>,
    pub singletons: StaticMap<Singleton, GlobalName>,
    exposed: StaticMap<Singleton, Cell<bool>>,
}

impl Globals {
    pub fn new() -> Self {
        let mut slf = Self {
            next_name: NumCell::new(1),
            registry: CopyHashMap::new(),
            removed: CopyHashMap::new(),
            outputs: Default::default(),
            seats: Default::default(),
            singletons: StaticMap::from_fn(|_| GlobalName(0)),
            exposed: Default::default(),
        };
        add_singletons(&mut slf);
        slf
    }

    pub fn clear(&self) {
        self.registry.clear();
        self.outputs.clear();
        self.seats.clear();
    }

    pub fn name(&self) -> GlobalName {
        let id = self.next_name.fetch_add(1);
        if id == 0 {
            panic!("Global names overflowed");
        }
        GlobalName(id)
    }

    fn insert_no_broadcast<'a>(&'a self, global: Rc<dyn Global>) {
        self.insert_no_broadcast_(&global);
    }

    fn insert_no_broadcast_<'a>(&'a self, global: &Rc<dyn Global>) {
        self.registry.set(global.name(), global.clone());
    }

    fn insert(&self, state: &State, global: Rc<dyn Global>) {
        self.insert_no_broadcast_(&global);
        self.broadcast(state, global.required_caps(), global.xwayland_only(), |r| {
            r.handle_global(&global)
        });
    }

    pub fn get(
        &self,
        name: GlobalName,
        client_caps: ClientCaps,
        allow_xwayland_only: bool,
    ) -> Result<Rc<dyn Global>, GlobalsError> {
        let global = self.take(name, false)?;
        if global.not_permitted(client_caps, allow_xwayland_only) {
            return Err(GlobalsError::GlobalDoesNotExist(name));
        }
        Ok(global)
    }

    pub fn remove<T: RemovableWaylandGlobal>(
        &self,
        state: &State,
        global: &Rc<T>,
    ) -> Result<(), GlobalsError> {
        let _global = self.take(global.name(), true)?;
        global.remove(self);
        let replacement = global.clone().create_replacement();
        assert_eq!(global.name(), replacement.name());
        assert_eq!(global.interface().0, replacement.interface().0);
        self.removed.set(global.name(), replacement);
        self.broadcast(state, global.required_caps(), global.xwayland_only(), |r| {
            r.handle_global_removed(&**global)
        });
        Ok(())
    }

    pub fn lock_seats(&self) -> Locked<'_, GlobalName, Rc<WlSeatGlobal>> {
        self.seats.lock()
    }

    pub fn notify_all(&self, registry: &Rc<WlRegistry>) {
        let caps = registry.client.effective_caps.get();
        let xwayland = registry.client.is_xwayland;
        let globals = self.registry.lock();
        macro_rules! emit {
            ($singleton:expr) => {
                for global in globals.values() {
                    if global.singleton().is_some() == $singleton {
                        if global.exposed(&registry.client.state)
                            && global.permitted(caps, xwayland)
                        {
                            registry.handle_global(global);
                        }
                    }
                }
            };
        }
        emit!(true);
        emit!(false);
    }

    fn broadcast<F: Fn(&Rc<WlRegistry>)>(
        &self,
        state: &State,
        required_caps: ClientCaps,
        xwayland_only: bool,
        f: F,
    ) {
        state.clients.broadcast(required_caps, xwayland_only, |c| {
            let registries = c.lock_registries();
            for registry in registries.values() {
                f(registry);
            }
            // c.flush();
        });
    }

    fn take(&self, name: GlobalName, remove: bool) -> Result<Rc<dyn Global>, GlobalsError> {
        let res = if remove {
            self.registry.remove(&name)
        } else {
            match self.registry.get(&name) {
                Some(res) => Some(res),
                _ => self.removed.get(&name),
            }
        };
        match res {
            Some(g) => Ok(g),
            None => Err(GlobalsError::GlobalDoesNotExist(name)),
        }
    }

    #[expect(dead_code)]
    pub fn get_output(&self, output: GlobalName) -> Result<Rc<WlOutputGlobal>, GlobalsError> {
        match self.outputs.get(&output) {
            Some(o) => Ok(o),
            _ => Err(GlobalsError::OutputDoesNotExist(output)),
        }
    }

    pub fn add_global<T: WaylandGlobal>(&self, state: &State, global: &Rc<T>) {
        global.clone().add(self);
        self.insert(state, global.clone())
    }

    pub fn add_global_no_broadcast<T: WaylandGlobal>(&self, global: &Rc<T>) {
        global.clone().add(self);
        self.insert_no_broadcast(global.clone());
    }

    pub fn expose_new_singletons(&self, state: &State) {
        let mut singletons = ArrayVec::<_, { Singleton::LENGTH }>::new();
        for (singleton, name) in self.singletons.iter() {
            if let Some(global) = self.registry.get(name) {
                let exposed = global.exposed(state);
                if self.exposed[singleton].replace(exposed) != exposed && exposed {
                    singletons.push(global);
                }
            }
        }
        if singletons.is_empty() {
            return;
        }
        for client in state.clients.clients.borrow().values() {
            let client = &client.data;
            let caps = client.effective_caps.get();
            let xwayland = client.is_xwayland;
            for global in &singletons {
                if global.permitted(caps, xwayland) {
                    for registry in client.objects.registries.lock().values() {
                        registry.handle_global(global);
                    }
                }
            }
        }
    }
}

pub trait WaylandGlobal: Global + 'static {
    fn add(self: Rc<Self>, globals: &Globals) {
        let _ = globals;
    }
    fn remove(&self, globals: &Globals) {
        let _ = globals;
    }
}

pub trait RemovableWaylandGlobal: WaylandGlobal {
    fn create_replacement(self: Rc<Self>) -> Rc<dyn Global>;
}
