// xfwl4 -- Wayland compositor for the Xfce Desktop Environment
//
// Copyright (C) 2026 Brian Tarricone <brian@tarricone.org>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//
// Portions of this file are based on "anvil", an example compositor
// based on the smithay crate, and are licensed under the MIT license
// with the following terms:
//
// Copyright (C) Victor Berger <victor.berger@m4x.org>
// Copyright (C) Drakulix (Victoria Brekenfeld)
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

use std::{collections::HashSet, ffi::CString, os::fd::AsFd, sync::Arc};

use anyhow::anyhow;
use smithay::{
    backend::renderer::Texture,
    input::{Seat, SeatState, keyboard::XkbConfig, pointer::PointerHandle},
    reexports::{
        calloop::{
            Interest, LoopHandle, LoopSignal, Mode, PostAction, RegistrationToken,
            channel::{self, Sender},
            generic::Generic,
            timer::{TimeoutAction, Timer},
        },
        rustix::process::Pid,
        wayland_server::{
            Client, Display, DisplayHandle,
            backend::{ClientData, ClientId, DisconnectReason},
        },
    },
    utils::{Clock, Monotonic, Time},
    wayland::{
        alpha_modifier::AlphaModifierState,
        commit_timing::CommitTimingManagerState,
        compositor::{CompositorClientState, CompositorState},
        cursor_shape::CursorShapeManagerState,
        fifo::FifoManagerState,
        fixes::FixesState,
        fractional_scale::FractionalScaleManagerState,
        idle_inhibit::IdleInhibitManagerState,
        idle_notify::IdleNotifierState,
        image_copy_capture::ImageCopyCaptureState,
        input_method::InputMethodManagerState,
        keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitState,
        output::OutputManagerState,
        pointer_constraints::PointerConstraintsState,
        pointer_gestures::PointerGesturesState,
        presentation::PresentationState,
        relative_pointer::RelativePointerManagerState,
        security_context::{SecurityContext, SecurityContextState},
        selection::{data_device::DataDeviceState, primary_selection::PrimarySelectionState, wlr_data_control::DataControlState},
        shell::{
            wlr_layer::WlrLayerShellState,
            xdg::{XdgShellState, dialog::XdgDialogState},
        },
        shm::ShmState,
        single_pixel_buffer::SinglePixelBufferState,
        socket::ListeningSocketSource,
        tablet_manager::TabletManagerState,
        text_input::TextInputManagerState,
        viewporter::ViewporterState,
        virtual_keyboard::VirtualKeyboardManagerState,
        xdg_activation::XdgActivationState,
        xdg_foreign::XdgForeignState,
        xdg_toplevel_icon::XdgToplevelIconManager,
    },
};
use tracing::{error, info, warn};

use crate::{
    backend::{Backend, BackendType},
    core::{
        config::{
            DEFAULT_KEY_REPEAT_DELAY, DEFAULT_KEY_REPEAT_RATE, KeyboardConfig, OutputsConfig, ShortcutsState, UiSettings, Xfwl4Config,
        },
        cursor::{CursorState, CursorTheme},
        cycle::CyclingState,
        drawing::decorations::{DecorBackgroundState, DecorButtonName, DecorButtonState, DecorationResources, DecorationTheme},
        handlers::{
            DecorationState, ExtImageCaptureSourceState, ExtSessionLockState, ForeignToplevelState, ProtocolDelegates,
            xfwl4_compositor_ui::WindowMenuState,
        },
        input_handler::InputState,
        session::Session,
        shell::{GrabState, ShellState, ssd::DecorationInput},
        util::{ClientExt, FreedesktopIconsIconTheme, LaptopLidState, get_laptop_lid_state},
        workspaces::WorkspaceManager,
    },
    protocols::{
        output_management::OutputManagementState,
        wlr_screencopy::WlrScreencopyState,
        xfce_output::XfceOutputState,
        xfwl4_compositor_ui::{CompositorUiHandler, CompositorUiState},
    },
    ui::MainComms,
    util::io::{read_exact, write_all},
};

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum WindowClient {
    Wayland(ClientId),
    X11(u32),
}

#[derive(Debug)]
pub(crate) struct ClientState {
    pub compositor_state: CompositorClientState,
    pub security_context: Option<SecurityContext>,
    disconnect_tx: Sender<ClientId>,
}

impl ClientState {
    pub fn new(client_disconnect_tx: Sender<ClientId>) -> Self {
        Self {
            compositor_state: CompositorClientState::default(),
            security_context: None,
            disconnect_tx: client_disconnect_tx,
        }
    }

    pub fn with_security_context(client_disconnect_tx: Sender<ClientId>, security_context: SecurityContext) -> Self {
        Self {
            compositor_state: CompositorClientState::default(),
            security_context: Some(security_context),
            disconnect_tx: client_disconnect_tx,
        }
    }
}

impl ClientData for ClientState {
    fn initialized(&self, _client_id: ClientId) {}

    fn disconnected(&self, client_id: ClientId, _reason: DisconnectReason) {
        let _ = self.disconnect_tx.send(client_id);
    }
}

pub struct Xfwl4State<BackendData: Backend + 'static> {
    pub(crate) core: Xfwl4Core<BackendData>,
    pub(crate) backend: BackendData,
}

pub struct Xfwl4Core<BackendData: Backend + 'static> {
    is_running: bool,
    socket_name: Option<String>,
    pub(crate) display_handle: DisplayHandle,
    pub(in crate::core) loop_handle: LoopHandle<'static, Xfwl4State<BackendData>>,
    stop_signal: LoopSignal,
    clock: Clock<Monotonic>,

    pub(in crate::core) config: Xfwl4Config,
    pub(in crate::core) outputs_config: OutputsConfig<BackendData>,
    pub(in crate::core) keyboard_config: KeyboardConfig,
    pub(in crate::core) decorations_resources: DecorationResources,
    pub(in crate::core) ui_settings: UiSettings,
    laptop_lid_state: Option<LaptopLidState>,
    clients_with_windows: HashSet<WindowClient>,
    client_disconnect_tx: Sender<ClientId>,

    pub(in crate::core) workspace_manager: WorkspaceManager<BackendData>,
    session: Session,

    pub(in crate::core) input_state: InputState,
    pub(in crate::core) grab_state: GrabState,
    pub(in crate::core) seat: Seat<Xfwl4State<BackendData>>,
    pub(in crate::core) pointer: PointerHandle<Xfwl4State<BackendData>>,
    pub(in crate::core) cycling_state: CyclingState,
    window_id_counter: u32,
    pub(in crate::core) window_menu_state: WindowMenuState<BackendData>,

    pub(in crate::core) compositor_ui_state: CompositorUiState,
    pub(in crate::core) protocol_delegates: ProtocolDelegates<BackendData>,
    pub(in crate::core) shell_state: ShellState,
    pub(in crate::core) shortcuts_state: ShortcutsState,

    pub(in crate::core) cursor_state: CursorState,

    #[cfg(feature = "xwayland")]
    pub(in crate::core) xwayland_state: crate::core::x11_wm::XWaylandState,
}

impl<BackendData: Backend + 'static> Xfwl4State<BackendData> {
    pub fn init(
        display: Display<Xfwl4State<BackendData>>,
        handle: LoopHandle<'static, Xfwl4State<BackendData>>,
        stop_signal: LoopSignal,
        backend_data: BackendData,
        listen_on_socket: bool,
    ) -> Xfwl4State<BackendData> {
        let dh = display.handle();

        let clock = Clock::new();

        // init wayland clients
        let socket_name = if listen_on_socket {
            let source = ListeningSocketSource::new_auto().unwrap();
            let socket_name = source.socket_name().to_string_lossy().into_owned();
            handle
                .insert_source(source, |client_stream, _, state| {
                    if let Err(err) = state
                        .core
                        .display_handle
                        .insert_client(client_stream, Arc::new(ClientState::new(state.core.client_disconnect_tx.clone())))
                    {
                        warn!("Failed to get credentials for new client: {err}");
                    }
                })
                .expect("Failed to init wayland socket source");
            info!(name = socket_name, "Listening on wayland socket");
            Some(socket_name)
        } else {
            None
        };
        handle
            .insert_source(Generic::new(display, Interest::READ, Mode::Level), |_, display, data| {
                profiling::scope!("dispatch_clients");
                // Safety: we don't drop the display
                unsafe {
                    display.get_mut().dispatch_clients(data).unwrap();
                }
                Ok(PostAction::Continue)
            })
            .expect("Failed to init wayland server source");

        let (config, config_notifier) = Xfwl4Config::new(handle.clone()).expect("Failed to load initial config");
        handle
            .insert_source(config_notifier, |event, _, state| {
                if let channel::Event::Msg(property_name) = event {
                    if property_name == "theme" {
                        if let Err(err) = state.load_decoration_theme() {
                            tracing::warn!("Failed to load theme: {err}");
                        }
                    } else if state.core.config.is_decoration_setting(&property_name) {
                        state.update_window_decorations_properties();
                    } else if property_name == "cycle_tabwin_mode" || property_name == "cycle_preview" {
                        state.update_toplevel_icon_sizes();
                    }
                }
            })
            .expect("Failed to register xfconf xfwm4 source with event loop");

        // init globals
        let compositor_state = CompositorState::new::<Self>(&dh);
        let data_device_state = DataDeviceState::new::<Self>(&dh);
        let layer_shell_state = WlrLayerShellState::new::<Self>(&dh);
        let output_manager_state = OutputManagerState::new_with_xdg_output::<Self>(&dh);
        let primary_selection_state = PrimarySelectionState::new::<Self>(&dh);
        let data_control_state =
            DataControlState::new::<Self, _>(&dh, Some(&primary_selection_state), |client| !client.has_security_context());
        let mut seat_state = SeatState::new();
        let shm_state = ShmState::new::<Self>(&dh, vec![]);
        let viewporter_state = ViewporterState::new::<Self>(&dh);
        let xdg_activation_state = XdgActivationState::new::<Self>(&dh);
        let decoration_state = DecorationState::new::<BackendData>(&dh, handle.clone());
        let xdg_shell_state = XdgShellState::new::<Self>(&dh);
        let xdg_dialog_state = XdgDialogState::new::<Self>(&dh);
        let xdg_toplevel_icon_manager = XdgToplevelIconManager::new::<Self>(&dh);
        let presentation_state = PresentationState::new::<Self>(&dh, clock.id() as u32);
        let fractional_scale_manager_state = FractionalScaleManagerState::new::<Self>(&dh);
        let xdg_foreign_state = XdgForeignState::new::<Self>(&dh);
        let single_pixel_buffer_state = SinglePixelBufferState::new::<Self>(&dh);
        let fifo_manager_state = FifoManagerState::new::<Self>(&dh);
        let commit_timing_manager_state = CommitTimingManagerState::new::<Self>(&dh);
        let cursor_shape_manager_state = CursorShapeManagerState::new::<Self>(&dh);
        TextInputManagerState::new::<Self>(&dh);
        InputMethodManagerState::new::<Self, _>(&dh, |client| !client.has_security_context());
        VirtualKeyboardManagerState::new::<Self, _>(&dh, |client| !client.has_security_context());
        // Expose global only if backend supports relative motion events
        if BackendData::HAS_RELATIVE_MOTION {
            RelativePointerManagerState::new::<Self>(&dh);
        }
        PointerConstraintsState::new::<Self>(&dh);
        if BackendData::HAS_GESTURES {
            PointerGesturesState::new::<Self>(&dh);
        }
        TabletManagerState::new::<Self>(&dh);
        SecurityContextState::new::<Self, _>(&dh, |client| !client.has_security_context());
        FixesState::new::<Self>(&dh);
        AlphaModifierState::new::<Self>(&dh);

        // init input
        let seat_name = backend_data.seat_name();
        let mut seat = seat_state.new_wl_seat(&dh, seat_name.clone());

        let pointer = seat.add_pointer();

        seat.add_keyboard(XkbConfig::default(), DEFAULT_KEY_REPEAT_DELAY, DEFAULT_KEY_REPEAT_RATE)
            .expect("Failed to initialize the keyboard");
        let keyboard_config = KeyboardConfig::new(handle.clone());

        let keyboard_shortcuts_inhibit_state = KeyboardShortcutsInhibitState::new::<Self>(&dh);

        let ext_idle_notifier_state = IdleNotifierState::new(&dh, handle.clone());
        IdleInhibitManagerState::new::<Self>(&dh);

        let ext_session_lock_state = ExtSessionLockState::new::<BackendData>(&dh);

        let foreign_toplevel_state = ForeignToplevelState::<BackendData>::new(&dh);

        let ext_image_capture_source_state = ExtImageCaptureSourceState::new::<BackendData>(&dh);
        let image_copy_capture_state = ImageCopyCaptureState::new_with_filter::<Self, _>(&dh, |client| !client.has_security_context());
        let wlr_screencopy_state = WlrScreencopyState::new::<Self, _>(&dh, |client| !client.has_security_context());

        let output_management_state = OutputManagementState::new::<Self, _>(&dh, |client| !client.has_security_context());
        let xfce_output_state = XfceOutputState::new::<Self, _>(&dh, |client| !client.has_security_context());
        let outputs_config = OutputsConfig::new(handle.clone(), output_management_state, xfce_output_state);

        #[cfg(feature = "xwayland")]
        let xwayland_shell_state = smithay::wayland::xwayland_shell::XWaylandShellState::new::<Self>(&dh.clone());

        #[cfg(feature = "xwayland")]
        smithay::wayland::xwayland_keyboard_grab::XWaylandKeyboardGrabState::new::<Self>(&dh.clone());

        let workspace_manager = WorkspaceManager::new(&dh, &handle);

        let (cursor_theme, notifier) = CursorTheme::new(handle.clone());
        handle
            .insert_source(notifier, |_, _, _state| {
                #[cfg(feature = "xwayland")]
                {
                    _state.x11_update_scale();
                    _state.x11_update_xrm_xcursor();
                }
            })
            .unwrap();
        let cursor_state = CursorState::new(cursor_theme);

        let ui_settings = UiSettings::new(handle.clone());
        let icon_theme = FreedesktopIconsIconTheme::new(ui_settings.icon_theme_name());
        let font_options = {
            let mut options = gtk::cairo::FontOptions::new().expect("creating cairo FontOptions should not fail");
            options.set_hint_metrics(gtk::cairo::HintMetrics::On);
            options.set_hint_style(ui_settings.hint_style());
            options.set_subpixel_order(ui_settings.subpixel_order());
            options.set_antialias(ui_settings.antialias());
            options
        };
        let decorations_resources = DecorationResources::new(font_options, icon_theme);

        let laptop_lid_state = get_laptop_lid_state();

        let session = Session::new(handle.clone()).expect("failed to create session helper");

        let compositor_ui_state = CompositorUiState::new::<Self>(&dh);

        let (client_disconnect_tx, client_disconnect_rx) = channel::channel::<ClientId>();
        handle
            .insert_source(client_disconnect_rx, |event, _, state| {
                if let channel::Event::Msg(client_id) = event {
                    state.core.clients_with_windows.remove(&WindowClient::Wayland(client_id.clone()));
                    if state
                        .core
                        .grab_state
                        .wireframe()
                        .is_some_and(|wireframe| wireframe.is_owned_by(&client_id))
                    {
                        state.core.grab_state.clear_wireframe();
                    }
                    state.core.protocol_delegates.client_disconnected(client_id);
                }
            })
            .expect("Failed to insert client disconnect source");

        Xfwl4State {
            backend: backend_data,
            core: Xfwl4Core {
                is_running: true,
                display_handle: dh,
                socket_name,
                stop_signal,
                loop_handle: handle,
                clients_with_windows: HashSet::default(),
                client_disconnect_tx,
                config,
                outputs_config,
                workspace_manager,
                decorations_resources,
                ui_settings,
                laptop_lid_state,
                session,
                compositor_ui_state,
                window_id_counter: 0,
                cycling_state: CyclingState::default(),
                window_menu_state: WindowMenuState::default(),

                protocol_delegates: ProtocolDelegates::new(
                    commit_timing_manager_state,
                    cursor_shape_manager_state,
                    data_control_state,
                    data_device_state,
                    decoration_state,
                    ext_idle_notifier_state,
                    ext_image_capture_source_state,
                    ext_session_lock_state,
                    fifo_manager_state,
                    foreign_toplevel_state,
                    fractional_scale_manager_state,
                    image_copy_capture_state,
                    keyboard_shortcuts_inhibit_state,
                    output_manager_state,
                    presentation_state,
                    primary_selection_state,
                    seat_state,
                    shm_state,
                    single_pixel_buffer_state,
                    viewporter_state,
                    wlr_screencopy_state,
                    xdg_activation_state,
                    xdg_foreign_state,
                    xdg_toplevel_icon_manager,
                ),
                shell_state: ShellState::new(
                    compositor_state,
                    layer_shell_state,
                    xdg_dialog_state,
                    xdg_shell_state,
                    #[cfg(feature = "xwayland")]
                    xwayland_shell_state,
                ),

                cursor_state,

                input_state: InputState::default(),
                grab_state: GrabState::default(),
                seat,
                pointer,
                keyboard_config,
                shortcuts_state: ShortcutsState::default(),
                clock,

                #[cfg(feature = "xwayland")]
                xwayland_state: crate::core::x11_wm::XWaylandState::default(),
            },
        }
    }

    pub fn socket_name(&self) -> Option<&str> {
        self.core.socket_name.as_deref()
    }

    pub fn backend_type(&self) -> BackendType {
        self.backend.backend_type()
    }

    pub fn register_ui_comms(&self, main_comms: MainComms) {
        if let Some(socket_name) = self.socket_name()
            && let Ok(cstr) = CString::new(socket_name)
            && let Err(err) = write_all(&main_comms.to_supervisor, cstr.to_bytes_with_nul())
        {
            tracing::error!("Failed to write Wayland socket name to UI supervisor: {err}");
        }

        self.core
            .loop_handle
            .insert_source(
                Generic::new(main_comms.from_supervisor, Interest::READ, Mode::Level),
                move |_, fd, state| {
                    if let Err(err) = state.handle_ui_client_pid(&fd) {
                        tracing::error!("Failed to read UI client PID from pipe: {err}");
                    }

                    if let Err(err) = write_all(&main_comms.to_supervisor, b"\0") {
                        tracing::error!("Failed to write ACK to UI supervisor process: {err}");
                    }

                    Ok(PostAction::Continue)
                },
            )
            .expect("unable to insert UI supervisor thread comms FD into event loop");
    }

    fn handle_ui_client_pid<FD: AsFd>(&mut self, fd: FD) -> anyhow::Result<()> {
        let mut pid_bytes = [0u8; 4];
        read_exact(fd, &mut pid_bytes)?;
        if let Some(pid) = Pid::from_raw(libc::pid_t::from_ne_bytes(pid_bytes)) {
            if self.core.compositor_ui_state.set_ui_client_pid(Some(pid)) {
                self.tabwin_destroyed();
            }
            self.core.window_menu_state.reset();
            Ok(())
        } else {
            Err(anyhow!("UI process PID invalid"))
        }
    }

    pub fn load_decoration_theme(&mut self) -> anyhow::Result<DecorationTheme> {
        let theme_path = self.core.config.theme_path().ok_or_else(|| anyhow!("Unable to find theme path"))?;
        let renderer = self.backend.renderer(
            #[cfg(feature = "udev")]
            None,
        )?;
        let decoration_theme = DecorationTheme::load(renderer, theme_path, &self.core.config.resolved_theme_colors())?;
        self.core.decorations_resources.update_decoration_theme(decoration_theme.clone());

        self.update_window_decorations_theme(&decoration_theme);
        self.update_toplevel_icon_sizes();

        tracing::debug!("loaded decoration theme");

        Ok(decoration_theme)
    }

    fn update_toplevel_icon_sizes(&mut self) {
        const WANTED_ICON_SIZES: &[i32] = &[16, 32, 48, 64, 128, 256, 512];

        let mut icon_sizes = WANTED_ICON_SIZES.to_vec();
        if let Some(menu_button) = self
            .core
            .decorations_resources
            .decoration_theme()
            .and_then(|theme| theme.button_texture(DecorButtonName::Menu, DecorButtonState::Active, DecorBackgroundState::Active))
        {
            let icon_size = menu_button.size().w.min(menu_button.size().h);
            if !icon_sizes.contains(&icon_size) {
                icon_sizes.push(icon_size);
                icon_sizes.sort();
            }
        }

        self.core.replace_toplevel_icon_sizes(icon_sizes);
    }

    fn update_window_decorations_theme(&self, decoration_theme: &DecorationTheme) {
        for workspace in self.core.workspace_manager.workspaces() {
            for window in workspace.visible_windows() {
                if let Some(window_decorations) = window.decoration_state_mut().window_decorations_mut() {
                    window_decorations.update(DecorationInput::Theme(decoration_theme.clone()));
                }
                #[cfg(feature = "xwayland")]
                self.core.xwayland_state.update_window_frame_extents(window);
            }
        }
    }

    pub(in crate::core) fn update_window_decorations_icon_theme(&self) {
        for workspace in self.core.workspace_manager.workspaces() {
            for window in workspace.visible_windows() {
                if let Some(window_decorations) = window.decoration_state_mut().window_decorations_mut() {
                    window_decorations.update(DecorationInput::IconThemeReloaded);
                }
                #[cfg(feature = "xwayland")]
                self.core.xwayland_state.update_window_frame_extents(window);
            }
        }
    }

    pub(in crate::core) fn update_window_decorations_properties(&mut self) {
        for workspace in self.core.workspace_manager.workspaces() {
            for window in workspace.visible_windows() {
                if let Some(window_decorations) = window.decoration_state_mut().window_decorations_mut() {
                    window_decorations.update(DecorationInput::ThemePropertiesReloaded);
                }
                #[cfg(feature = "xwayland")]
                self.core.xwayland_state.update_window_frame_extents(window);
            }
        }

        let outputs: Vec<_> = self.core.workspace_manager.outputs().cloned().collect();
        for output in &outputs {
            self.reapply_anchored_layouts_on_output(output);
        }
    }

    pub(in crate::core) fn update_window_decorations_font_options(&self) {
        for workspace in self.core.workspace_manager.workspaces() {
            for window in workspace.visible_windows() {
                if let Some(window_decorations) = window.decoration_state_mut().window_decorations_mut() {
                    window_decorations.update(DecorationInput::FontOptions(self.core.decorations_resources.font_options().clone()));
                }
                #[cfg(feature = "xwayland")]
                self.core.xwayland_state.update_window_frame_extents(window);
            }
        }
    }

    pub fn refresh_and_flush_clients(&mut self) {
        profiling::scope!("refresh_and_flush_clients");
        self.core.workspace_manager.refresh_spaces();
        self.core.shell_state.popup_manager_mut().cleanup();
        self.refresh_pointer_focus();

        if let Err(err) = self.core.display_handle.flush_clients() {
            error!("Fatal error: Failed to flush Wayland clients: {err}");
            std::process::exit(1);
        }
    }

    pub fn shutdown(&mut self) {
        self.core.is_running = false;
        self.core.compositor_ui_state.send_quit();
        self.core.stop_signal.stop();
        self.core.stop_signal.wakeup();
    }
}

impl<BackendData: Backend + 'static> Xfwl4Core<BackendData> {
    pub(in crate::core) fn next_window_id(&mut self) -> u32 {
        let id = self.window_id_counter;
        self.window_id_counter += 1;
        id
    }

    pub(in crate::core) fn is_running(&self) -> bool {
        self.is_running
    }

    pub(crate) fn now(&self) -> Time<Monotonic> {
        self.clock.now()
    }

    pub(in crate::core) fn session_mut(&mut self) -> &mut Session {
        &mut self.session
    }

    pub(in crate::core) fn update_laptop_lid_state(&mut self, state: LaptopLidState) {
        self.laptop_lid_state = Some(state);
    }

    pub(in crate::core) fn new_client_state_with_security_context(&self, security_context: SecurityContext) -> ClientState {
        ClientState::with_security_context(self.client_disconnect_tx.clone(), security_context)
    }

    pub(in crate::core) fn add_window_client(&mut self, window_client: WindowClient) -> bool {
        self.clients_with_windows.insert(window_client)
    }

    pub(in crate::core) fn remove_window_client(&mut self, window_client: WindowClient) {
        self.clients_with_windows.remove(&window_client);
    }

    pub(in crate::core) fn client_is_ui_thread(&self, client: Option<Client>) -> bool {
        client
            .and_then(|client| client.get_credentials(&self.display_handle).ok())
            .is_some_and(|creds| {
                self.compositor_ui_state
                    .client_pid()
                    .as_ref()
                    .is_some_and(|pid| pid.as_raw_pid() == creds.pid)
            })
    }

    pub(in crate::core) fn is_laptop_lid_open(&self) -> bool {
        self.laptop_lid_state.as_ref().is_some_and(|state| *state == LaptopLidState::Open)
    }

    pub(crate) fn register_timer<F>(&self, timer: Timer, mut timer_fn: F) -> RegistrationToken
    where
        F: FnMut(&mut Xfwl4State<BackendData>) -> TimeoutAction + 'static,
    {
        self.loop_handle
            .insert_source(timer, move |_, _, state| timer_fn(state))
            .expect("Failed to register timer source with event loop")
    }

    pub(crate) fn unregister_timer(&self, token: RegistrationToken) {
        self.loop_handle.remove(token);
    }
}

smithay::delegate_dispatch2!(@<BackendData: Backend + 'static> Xfwl4State<BackendData>);
