//! Headless platform/render worker for the explicit-construction prototype.

use std::cell::RefCell;
use std::collections::BTreeMap;
use std::fmt;
use std::rc::Rc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex, OnceLock};

use ruin_runtime::channel::mpsc;
use ruin_runtime::{WorkerHandle, queue_future, queue_microtask, spawn_worker};
use tracing::{debug, info};

use crate::interaction::PointerEvent;
use crate::keyboard::KeyboardEvent;
use crate::scene::{SceneSnapshot, UiSize};
use crate::trace_targets;
use crate::tree::CursorIcon;
use crate::window::{WindowConfigured, WindowId, WindowLifecycle, WindowSpec, WindowUpdate};

type CommandWakeHook = Arc<dyn Fn() + Send + Sync + 'static>;

fn command_wake_hook() -> &'static Mutex<Option<CommandWakeHook>> {
    static HOOK: OnceLock<Mutex<Option<CommandWakeHook>>> = OnceLock::new();
    HOOK.get_or_init(|| Mutex::new(None))
}

#[derive(Clone)]
pub struct PlatformProxy {
    command_tx: mpsc::UnboundedSender<PlatformRequest>,
    next_window_id: Arc<AtomicU64>,
}

/// Low-level platform/runtime host for the explicit prototype.
///
/// The headless backend currently co-locates logical platform and rendering work on one runtime
/// worker thread, but the event/snapshot API is intentionally shaped so future backends can split
/// them if needed.
pub struct PlatformRuntime {
    proxy: PlatformProxy,
    events: mpsc::Receiver<PlatformEvent>,
    worker: Option<WorkerHandle>,
}

pub struct PlatformEndpoint {
    pub commands: mpsc::Receiver<PlatformRequest>,
    pub events: mpsc::UnboundedSender<PlatformEvent>,
}

#[derive(Clone, Debug, PartialEq)]
pub enum PlatformEvent {
    Opened {
        window_id: WindowId,
    },
    Closed {
        window_id: WindowId,
    },
    VisibilityChanged {
        window_id: WindowId,
        visible: bool,
    },
    Configured {
        window_id: WindowId,
        configuration: WindowConfigured,
    },
    FramePresented {
        window_id: WindowId,
        scene_version: u64,
        item_count: usize,
    },
    Pointer {
        window_id: WindowId,
        event: PointerEvent,
    },
    Keyboard {
        window_id: WindowId,
        event: KeyboardEvent,
    },
    ClipboardText {
        window_id: WindowId,
        text: String,
    },
    #[cfg(target_os = "linux")]
    PrimarySelectionText {
        window_id: WindowId,
        text: String,
    },
    Wake {
        window_id: WindowId,
        token: u64,
    },
    CloseRequested {
        window_id: WindowId,
    },
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct PlatformClosed;

impl fmt::Display for PlatformClosed {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str("platform worker is closed")
    }
}

impl std::error::Error for PlatformClosed {}

#[derive(Clone, Debug)]
pub enum PlatformRequest {
    CreateWindow {
        window_id: WindowId,
        spec: WindowSpec,
    },
    UpdateWindow {
        window_id: WindowId,
        update: WindowUpdate,
    },
    ReplaceScene {
        window_id: WindowId,
        scene: SceneSnapshot,
    },
    SetClipboardText {
        window_id: WindowId,
        text: String,
    },
    RequestClipboardText {
        window_id: WindowId,
    },
    #[cfg(target_os = "linux")]
    SetPrimarySelectionText {
        window_id: WindowId,
        text: String,
    },
    #[cfg(target_os = "linux")]
    RequestPrimarySelectionText {
        window_id: WindowId,
    },
    SetCursorIcon {
        window_id: WindowId,
        cursor: CursorIcon,
    },
    EmitCloseRequested {
        window_id: WindowId,
    },
    EmitPointerEvent {
        window_id: WindowId,
        event: PointerEvent,
    },
    EmitKeyboardEvent {
        window_id: WindowId,
        event: KeyboardEvent,
    },
    EmitWake {
        window_id: WindowId,
        token: u64,
    },
    Shutdown,
}

#[derive(Clone)]
struct HeadlessState {
    events: mpsc::UnboundedSender<PlatformEvent>,
    windows: BTreeMap<WindowId, HeadlessWindow>,
}

#[derive(Clone)]
struct HeadlessWindow {
    spec: WindowSpec,
    lifecycle: WindowLifecycle,
    configuration: WindowConfigured,
    pending_scene: Option<SceneSnapshot>,
    render_scheduled: bool,
}

impl HeadlessWindow {
    fn new(spec: WindowSpec) -> Self {
        let actual_inner_size = spec
            .requested_inner_size
            .unwrap_or_else(|| UiSize::new(800.0, 600.0));
        Self {
            spec,
            lifecycle: WindowLifecycle::LogicalClosed,
            configuration: WindowConfigured {
                actual_inner_size,
                scale_factor: 1.0,
                visible: false,
                maximized: false,
                fullscreen: false,
            },
            pending_scene: None,
            render_scheduled: false,
        }
    }
}

impl PlatformRuntime {
    pub fn headless() -> Self {
        start_headless()
    }

    pub fn custom(start: impl FnOnce(PlatformEndpoint) -> WorkerHandle) -> Self {
        let (command_tx, command_rx) = mpsc::unbounded_channel::<PlatformRequest>();
        let (event_tx, event_rx) = mpsc::unbounded_channel::<PlatformEvent>();
        let proxy = PlatformProxy {
            command_tx,
            next_window_id: Arc::new(AtomicU64::new(1)),
        };
        let worker = start(PlatformEndpoint {
            commands: command_rx,
            events: event_tx,
        });

        Self {
            proxy,
            events: event_rx,
            worker: Some(worker),
        }
    }

    /// Creates a platform runtime hosted by the current runtime thread.
    ///
    /// This is useful for backends that must run on a specific host thread
    /// (for example, AppKit on macOS main thread).
    pub fn custom_local(start: impl FnOnce(PlatformEndpoint)) -> Self {
        let (command_tx, command_rx) = mpsc::unbounded_channel::<PlatformRequest>();
        let (event_tx, event_rx) = mpsc::unbounded_channel::<PlatformEvent>();
        let proxy = PlatformProxy {
            command_tx,
            next_window_id: Arc::new(AtomicU64::new(1)),
        };
        start(PlatformEndpoint {
            commands: command_rx,
            events: event_tx,
        });

        Self {
            proxy,
            events: event_rx,
            worker: None,
        }
    }

    pub fn proxy(&self) -> PlatformProxy {
        self.proxy.clone()
    }

    pub async fn next_event(&mut self) -> Option<PlatformEvent> {
        self.events.recv().await
    }

    pub fn take_pending_events(&mut self) -> Vec<PlatformEvent> {
        let _ = self.worker.as_ref();
        let mut events = Vec::new();
        while let Ok(event) = self.events.try_recv() {
            events.push(event);
        }
        events
    }
}

impl PlatformProxy {
    pub fn create_window(&self, spec: WindowSpec) -> Result<WindowId, PlatformClosed> {
        let window_id = WindowId::from_raw(self.next_window_id.fetch_add(1, Ordering::Relaxed));
        self.send(PlatformRequest::CreateWindow { window_id, spec })?;
        Ok(window_id)
    }

    pub fn update_window(
        &self,
        window_id: WindowId,
        update: WindowUpdate,
    ) -> Result<(), PlatformClosed> {
        self.send(PlatformRequest::UpdateWindow { window_id, update })
    }

    pub fn replace_scene(
        &self,
        window_id: WindowId,
        scene: SceneSnapshot,
    ) -> Result<(), PlatformClosed> {
        self.send(PlatformRequest::ReplaceScene { window_id, scene })
    }

    #[cfg(target_os = "linux")]
    pub fn set_primary_selection_text(
        &self,
        window_id: WindowId,
        text: impl Into<String>,
    ) -> Result<(), PlatformClosed> {
        self.send(PlatformRequest::SetPrimarySelectionText {
            window_id,
            text: text.into(),
        })
    }

    #[cfg(target_os = "linux")]
    pub fn request_primary_selection_text(
        &self,
        window_id: WindowId,
    ) -> Result<(), PlatformClosed> {
        self.send(PlatformRequest::RequestPrimarySelectionText { window_id })
    }

    pub fn set_clipboard_text(
        &self,
        window_id: WindowId,
        text: impl Into<String>,
    ) -> Result<(), PlatformClosed> {
        self.send(PlatformRequest::SetClipboardText {
            window_id,
            text: text.into(),
        })
    }

    pub fn request_clipboard_text(&self, window_id: WindowId) -> Result<(), PlatformClosed> {
        self.send(PlatformRequest::RequestClipboardText { window_id })
    }

    pub fn set_cursor_icon(
        &self,
        window_id: WindowId,
        cursor: CursorIcon,
    ) -> Result<(), PlatformClosed> {
        self.send(PlatformRequest::SetCursorIcon { window_id, cursor })
    }

    pub fn emit_close_requested(&self, window_id: WindowId) -> Result<(), PlatformClosed> {
        self.send(PlatformRequest::EmitCloseRequested { window_id })
    }

    pub fn emit_pointer_event(
        &self,
        window_id: WindowId,
        event: PointerEvent,
    ) -> Result<(), PlatformClosed> {
        self.send(PlatformRequest::EmitPointerEvent { window_id, event })
    }

    pub fn emit_keyboard_event(
        &self,
        window_id: WindowId,
        event: KeyboardEvent,
    ) -> Result<(), PlatformClosed> {
        self.send(PlatformRequest::EmitKeyboardEvent { window_id, event })
    }

    pub fn emit_wake(&self, window_id: WindowId, token: u64) -> Result<(), PlatformClosed> {
        self.send(PlatformRequest::EmitWake { window_id, token })
    }

    pub fn shutdown(&self) -> Result<(), PlatformClosed> {
        self.send(PlatformRequest::Shutdown)
    }

    fn send(&self, command: PlatformRequest) -> Result<(), PlatformClosed> {
        self.command_tx.send(command).map_err(|_| PlatformClosed)?;
        if let Ok(hook) = command_wake_hook().lock()
            && let Some(hook) = hook.as_ref().cloned()
        {
            hook();
        }
        Ok(())
    }
}

#[doc(hidden)]
pub fn set_command_wake_hook(hook: Option<CommandWakeHook>) {
    if let Ok(mut slot) = command_wake_hook().lock() {
        *slot = hook;
    }
}

/// Starts the headless prototype backend.
///
/// Policy note: when a window becomes visible again, the backend may re-present the latest retained
/// scene for that window even if the scene version itself did not change. Visibility restoration is
/// treated as a presentation-affecting state change.
pub fn start_headless() -> PlatformRuntime {
    PlatformRuntime::custom(|mut endpoint| {
        spawn_worker(
            move || {
                let state = Rc::new(RefCell::new(HeadlessState {
                    events: endpoint.events.clone(),
                    windows: BTreeMap::new(),
                }));

                queue_future(async move {
                    debug!(
                        target: trace_targets::PLATFORM,
                        event = "worker_started",
                        backend = "headless",
                        "starting headless platform worker"
                    );

                    while let Some(command) = endpoint.commands.recv().await {
                        match command {
                            PlatformRequest::CreateWindow { window_id, spec } => {
                                handle_create_window(&state, window_id, spec);
                            }
                            PlatformRequest::UpdateWindow { window_id, update } => {
                                handle_update_window(&state, window_id, update);
                            }
                            PlatformRequest::ReplaceScene { window_id, scene } => {
                                handle_replace_scene(&state, window_id, scene);
                            }
                            PlatformRequest::SetClipboardText { .. } => {}
                            PlatformRequest::RequestClipboardText { .. } => {}
                            #[cfg(target_os = "linux")]
                            PlatformRequest::SetPrimarySelectionText { .. } => {}
                            #[cfg(target_os = "linux")]
                            PlatformRequest::RequestPrimarySelectionText { .. } => {}
                            PlatformRequest::SetCursorIcon { .. } => {}
                            PlatformRequest::EmitCloseRequested { window_id } => {
                                let sender = state.borrow().events.clone();
                                let _ = sender.send(PlatformEvent::CloseRequested { window_id });
                            }
                            PlatformRequest::EmitPointerEvent { window_id, event } => {
                                handle_emit_pointer_event(&state, window_id, event);
                            }
                            PlatformRequest::EmitKeyboardEvent { window_id, event } => {
                                let sender = state.borrow().events.clone();
                                let _ = sender.send(PlatformEvent::Keyboard { window_id, event });
                            }
                            PlatformRequest::EmitWake { window_id, token } => {
                                let sender = state.borrow().events.clone();
                                let _ = sender.send(PlatformEvent::Wake { window_id, token });
                            }
                            PlatformRequest::Shutdown => {
                                debug!(
                                    target: trace_targets::PLATFORM,
                                    event = "worker_shutdown_requested",
                                    "shutting down headless platform worker"
                                );
                                break;
                            }
                        }
                    }
                });
            },
            || {
                debug!(
                    target: trace_targets::PLATFORM,
                    event = "worker_exited",
                    backend = "headless",
                    "headless platform worker exited"
                );
            },
        )
    })
}

fn handle_create_window(state: &Rc<RefCell<HeadlessState>>, window_id: WindowId, spec: WindowSpec) {
    debug!(
        target: trace_targets::PLATFORM,
        event = "create_window",
        window_id = window_id.raw(),
        title = spec.title.as_str(),
        open = spec.open,
        visible = spec.visible,
        "creating logical window"
    );

    state
        .borrow_mut()
        .windows
        .insert(window_id, HeadlessWindow::new(spec.clone()));

    if spec.open {
        begin_open_transition(Rc::clone(state), window_id);
    }
}

fn handle_update_window(
    state: &Rc<RefCell<HeadlessState>>,
    window_id: WindowId,
    update: WindowUpdate,
) {
    let mut action = UpdateAction::None;
    {
        let mut state_ref = state.borrow_mut();
        let Some(window) = state_ref.windows.get_mut(&window_id) else {
            return;
        };
        let was_open = window.spec.open;
        let was_visible = window.spec.visible;
        update.apply_to(&mut window.spec);

        if let Some(requested_inner_size) = window.spec.requested_inner_size {
            window.configuration.actual_inner_size = requested_inner_size;
        }
        window.configuration.maximized = window.spec.maximized;
        window.configuration.fullscreen = window.spec.fullscreen;

        match (was_open, window.spec.open) {
            (false, true) => action = UpdateAction::Open,
            (true, false) => action = UpdateAction::Close,
            _ => {}
        }

        if was_open == window.spec.open && was_visible != window.spec.visible {
            action = UpdateAction::Visibility(window.spec.visible);
        }
    }

    match action {
        UpdateAction::None => {
            maybe_schedule_render(Rc::clone(state), window_id);
        }
        UpdateAction::Open => begin_open_transition(Rc::clone(state), window_id),
        UpdateAction::Close => close_window(state, window_id),
        UpdateAction::Visibility(visible) => set_visibility(state, window_id, visible),
    }
}

fn handle_replace_scene(
    state: &Rc<RefCell<HeadlessState>>,
    window_id: WindowId,
    scene: SceneSnapshot,
) {
    #[cfg(debug_assertions)]
    tracing::trace!(
        target: trace_targets::PLATFORM,
        event = "replace_scene",
        window_id = window_id.raw(),
        version = scene.version,
        items = scene.item_count(),
        "received scene snapshot"
    );

    if let Some(window) = state.borrow_mut().windows.get_mut(&window_id) {
        window.pending_scene = Some(scene);
    } else {
        return;
    }

    maybe_schedule_render(Rc::clone(state), window_id);
}

fn maybe_schedule_render(state: Rc<RefCell<HeadlessState>>, window_id: WindowId) {
    let should_schedule = {
        let mut state_ref = state.borrow_mut();
        let Some(window) = state_ref.windows.get_mut(&window_id) else {
            return;
        };
        if window.render_scheduled {
            false
        } else {
            window.render_scheduled = true;
            true
        }
    };

    if should_schedule {
        queue_microtask(move || present_latest_scene(&state, window_id));
    }
}

fn begin_open_transition(state: Rc<RefCell<HeadlessState>>, window_id: WindowId) {
    {
        let mut state_ref = state.borrow_mut();
        let Some(window) = state_ref.windows.get_mut(&window_id) else {
            return;
        };
        window.lifecycle = WindowLifecycle::Opening;
    }

    queue_microtask(move || {
        {
            let mut state_ref = state.borrow_mut();
            let Some(window) = state_ref.windows.get_mut(&window_id) else {
                return;
            };
            window.lifecycle = WindowLifecycle::AwaitingInitialConfigure;
            window.configuration.visible = window.spec.visible;
            window.configuration.maximized = window.spec.maximized;
            window.configuration.fullscreen = window.spec.fullscreen;
            window.lifecycle = if window.spec.visible {
                WindowLifecycle::OpenVisible
            } else {
                WindowLifecycle::OpenHidden
            };
        }

        emit_event(
            &state,
            PlatformEvent::Configured {
                window_id,
                configuration: state
                    .borrow()
                    .windows
                    .get(&window_id)
                    .expect("window should exist during configure")
                    .configuration,
            },
        );
        emit_event(&state, PlatformEvent::Opened { window_id });
        maybe_schedule_render(state, window_id);
    });
}

fn close_window(state: &Rc<RefCell<HeadlessState>>, window_id: WindowId) {
    let should_emit = {
        let mut state_ref = state.borrow_mut();
        let Some(window) = state_ref.windows.get_mut(&window_id) else {
            return;
        };
        if window.lifecycle == WindowLifecycle::LogicalClosed {
            false
        } else {
            window.lifecycle = WindowLifecycle::Closing;
            window.render_scheduled = false;
            window.configuration.visible = false;
            window.lifecycle = WindowLifecycle::LogicalClosed;
            true
        }
    };

    if should_emit {
        emit_event(state, PlatformEvent::Closed { window_id });
    }
}

fn set_visibility(state: &Rc<RefCell<HeadlessState>>, window_id: WindowId, visible: bool) {
    let should_emit = {
        let mut state_ref = state.borrow_mut();
        let Some(window) = state_ref.windows.get_mut(&window_id) else {
            return;
        };
        if !window.spec.open {
            false
        } else {
            window.configuration.visible = visible;
            window.lifecycle = if visible {
                WindowLifecycle::OpenVisible
            } else {
                WindowLifecycle::OpenHidden
            };
            true
        }
    };

    if should_emit {
        emit_event(
            state,
            PlatformEvent::VisibilityChanged { window_id, visible },
        );
        maybe_schedule_render(Rc::clone(state), window_id);
    }
}

fn handle_emit_pointer_event(
    state: &Rc<RefCell<HeadlessState>>,
    window_id: WindowId,
    event: PointerEvent,
) {
    let should_emit = matches!(
        state.borrow().windows.get(&window_id),
        Some(window) if window.lifecycle == WindowLifecycle::OpenVisible
    );
    if should_emit {
        emit_event(state, PlatformEvent::Pointer { window_id, event });
    }
}

fn present_latest_scene(state: &Rc<RefCell<HeadlessState>>, window_id: WindowId) {
    let presentation = {
        let mut state_ref = state.borrow_mut();
        let Some(window) = state_ref.windows.get_mut(&window_id) else {
            return;
        };
        window.render_scheduled = false;
        if window.lifecycle != WindowLifecycle::OpenVisible {
            return;
        }
        let Some(scene) = window.pending_scene.clone() else {
            return;
        };
        Some((scene.version, scene.item_count(), window.spec.title.clone()))
    };

    let Some((scene_version, item_count, title)) = presentation else {
        return;
    };

    info!(
        target: trace_targets::PLATFORM,
        event = "present_scene",
        backend = "headless",
        window_id = window_id.raw(),
        title = title.as_str(),
        scene_version,
        item_count,
        "presenting latest scene snapshot"
    );
    emit_event(
        state,
        PlatformEvent::FramePresented {
            window_id,
            scene_version,
            item_count,
        },
    );
}

fn emit_event(state: &Rc<RefCell<HeadlessState>>, event: PlatformEvent) {
    let sender = state.borrow().events.clone();
    let _ = sender.send(event);
}

enum UpdateAction {
    None,
    Open,
    Close,
    Visibility(bool),
}

#[cfg(test)]
mod tests {
    use super::{PlatformEvent, start_headless};
    use crate::scene::{Color, Point, PreparedText, Rect, SceneSnapshot, UiSize};
    use crate::window::{WindowSpec, WindowUpdate};
    use ruin_runtime::{current_thread_handle, queue_future, run};
    use std::sync::{Arc, Mutex};

    #[test]
    fn headless_platform_coalesces_latest_scene_per_window() {
        let observed = Arc::new(Mutex::new(Vec::<PlatformEvent>::new()));
        let done = Arc::new(Mutex::new(false));
        let main = current_thread_handle();

        queue_future({
            let observed = Arc::clone(&observed);
            let done = Arc::clone(&done);
            async move {
                let mut platform = start_headless();
                let proxy = platform.proxy();
                let window_id = proxy
                    .create_window(
                        WindowSpec::new("coalesce").requested_inner_size(UiSize::new(320.0, 180.0)),
                    )
                    .expect("window should be created");

                let mut one = SceneSnapshot::new(1, UiSize::new(320.0, 180.0));
                one.push_quad(
                    Rect::new(0.0, 0.0, 320.0, 180.0),
                    Color::rgb(0x22, 0x22, 0x33),
                );
                let mut two = SceneSnapshot::new(2, UiSize::new(320.0, 180.0));
                two.push_quad(
                    Rect::new(0.0, 0.0, 320.0, 180.0),
                    Color::rgb(0x33, 0x22, 0x22),
                );
                let mut three = SceneSnapshot::new(3, UiSize::new(320.0, 180.0));
                three
                    .push_quad(
                        Rect::new(0.0, 0.0, 320.0, 180.0),
                        Color::rgb(0x22, 0x33, 0x22),
                    )
                    .push_text(PreparedText::monospace(
                        "v3",
                        Point::new(16.0, 28.0),
                        16.0,
                        8.0,
                        Color::rgb(0xFF, 0xFF, 0xFF),
                    ));

                proxy
                    .replace_scene(window_id, one)
                    .expect("scene v1 should queue");
                proxy
                    .replace_scene(window_id, two)
                    .expect("scene v2 should queue");
                proxy
                    .replace_scene(window_id, three)
                    .expect("scene v3 should queue");

                while let Some(event) = platform.next_event().await {
                    observed.lock().unwrap().push(event.clone());
                    if let PlatformEvent::FramePresented {
                        window_id: presented,
                        scene_version,
                        ..
                    } = event
                    {
                        assert_eq!(presented, window_id);
                        assert_eq!(scene_version, 3);
                        proxy.shutdown().expect("shutdown should queue");
                        break;
                    }
                }

                while platform.next_event().await.is_some() {}
                *done.lock().unwrap() = true;
                let _ = main.queue_task(|| {});
            }
        });

        run();
        assert!(*done.lock().unwrap());
    }

    #[test]
    fn open_false_closes_and_reopen_reconfigures() {
        let counts = Arc::new(Mutex::new((0usize, 0usize, 0usize)));
        let done = Arc::new(Mutex::new(false));
        let main = current_thread_handle();

        queue_future({
            let counts = Arc::clone(&counts);
            let done = Arc::clone(&done);
            async move {
                let mut platform = start_headless();
                let proxy = platform.proxy();
                let window_id = proxy
                    .create_window(
                        WindowSpec::new("reopen").requested_inner_size(UiSize::new(640.0, 360.0)),
                    )
                    .expect("window should be created");

                let mut saw_initial_open = false;
                while let Some(event) = platform.next_event().await {
                    match event {
                        PlatformEvent::Configured { window_id: id, .. } if id == window_id => {
                            counts.lock().unwrap().0 += 1;
                        }
                        PlatformEvent::Opened { window_id: id } if id == window_id => {
                            counts.lock().unwrap().1 += 1;
                            if !saw_initial_open {
                                saw_initial_open = true;
                                proxy
                                    .update_window(window_id, WindowUpdate::new().open(false))
                                    .expect("close should queue");
                                proxy
                                    .update_window(window_id, WindowUpdate::new().open(true))
                                    .expect("reopen should queue");
                            }
                        }
                        PlatformEvent::Closed { window_id: id } if id == window_id => {
                            counts.lock().unwrap().2 += 1;
                        }
                        _ => {}
                    }

                    let (configured, opened, closed) = *counts.lock().unwrap();
                    if configured >= 2 && opened >= 2 && closed >= 1 {
                        proxy.shutdown().expect("shutdown should queue");
                        break;
                    }
                }

                while platform.next_event().await.is_some() {}
                *done.lock().unwrap() = true;
                let _ = main.queue_task(|| {});
            }
        });

        run();
        let (configured, opened, closed) = *counts.lock().unwrap();
        assert_eq!(configured, 2);
        assert_eq!(opened, 2);
        assert_eq!(closed, 1);
        assert!(*done.lock().unwrap());
    }
}
