#![cfg_attr(doc, doc = include_str!("../README.md"))]
//!
//! ## Feature flags
#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
#![expect(clippy::unwrap_used)] // TODO(emilk): avoid unwraps

mod builder;
#[cfg(feature = "snapshot")]
mod snapshot;

#[cfg(feature = "snapshot")]
pub use crate::snapshot::*;

mod app_kind;
mod config;
mod node;
mod renderer;
#[cfg(feature = "wgpu")]
mod texture_to_image;
#[cfg(feature = "wgpu")]
pub mod wgpu;

// re-exports:
pub use {
    self::{builder::*, node::*, renderer::*},
    kittest,
};

use core::{
    fmt::{Debug, Display, Formatter},
    time::Duration,
};

use egui::{
    Color32, Key, Modifiers, PointerButton, Pos2, Rect, RepaintCause, Shape, Vec2, ViewportId,
    epaint::{ClippedShape, RectShape},
    style::ScrollAnimation,
};
use kittest::Queryable;

use crate::{app_kind::AppKind, config::config};

#[derive(Debug, Clone)]
pub struct ExceededMaxStepsError {
    pub max_steps: u64,

    /// How many steps the ui would have needed to settle.
    ///
    /// `None` if it did not settle within `diagnostic_max_steps` (see `kittest.toml`) further
    /// steps either, i.e. it just keeps repainting.
    pub steps_to_settle: Option<u64>,

    /// How far past [`Self::max_steps`] we kept stepping to find [`Self::steps_to_settle`].
    pub diagnostic_max_steps: u64,

    pub repaint_causes: Vec<RepaintCause>,
}

impl Display for ExceededMaxStepsError {
    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
        write!(f, "Harness::run exceeded max_steps ({}). ", self.max_steps)?;

        match self.steps_to_settle {
            Some(steps) => write!(f, "It would have settled after {steps} steps. ")?,
            None => write!(
                f,
                "It did not settle within {} further steps either. ",
                self.diagnostic_max_steps
            )?,
        }

        write!(
            f,
            "If you expect your ui to keep repainting \
            (e.g. when showing a spinner) call Harness::step or Harness::run_steps instead.\
            \nRepaint causes: {:#?}",
            self.repaint_causes,
        )
    }
}

/// The test Harness. This contains everything needed to run the test.
///
/// Create a new Harness using [`Harness::new_ui`] or [`Harness::builder`].
///
/// The [Harness] has a optional generic state that can be used to pass data to the app / ui closure.
/// In _most cases_ it should be fine to just store the state in the closure itself.
/// The state functions are useful if you need to access the state after the harness has been created.
///
/// Some egui style options are changed from the defaults:
/// - The cursor blinking is disabled
/// - The scroll animation is disabled
pub struct Harness<'a, State = ()> {
    pub ctx: egui::Context,
    input: egui::RawInput,
    kittest: kittest::State,
    output: egui::FullOutput,
    app: AppKind<'a, State>,
    response: Option<egui::Response>,
    state: State,
    renderer: Box<dyn TestRenderer>,
    max_steps: u64,
    step_dt: f32,
    wait_for_pending_images: bool,
    queued_events: EventQueue,

    #[cfg(feature = "snapshot")]
    default_snapshot_options: SnapshotOptions,
    #[cfg(feature = "snapshot")]
    snapshot_results: SnapshotResults,
}

impl<State> Debug for Harness<'_, State> {
    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
        self.kittest.fmt(f)
    }
}

impl<'a, State> Harness<'a, State> {
    #[track_caller]
    pub(crate) fn from_builder(
        builder: HarnessBuilder<State>,
        mut app: AppKind<'a, State>,
        mut state: State,
        ctx: Option<egui::Context>,
    ) -> Self {
        let HarnessBuilder {
            screen_rect,
            pixels_per_point,
            theme,
            os,
            max_steps,
            step_dt,
            state: _,
            mut renderer,
            wait_for_pending_images,

            #[cfg(feature = "snapshot")]
            default_snapshot_options,

            // rustfmt adds this weird indentation below.
            // See: https://github.com/rust-lang/rustfmt/issues/5920
            #[cfg(feature = "wgpu")]
                render_options: _,
        } = builder;
        let ctx = ctx.unwrap_or_default();
        ctx.set_theme(theme);
        ctx.set_os(os);
        ctx.enable_accesskit();
        ctx.all_styles_mut(|style| {
            // Disable cursor blinking so it doesn't interfere with snapshots
            style.visuals.text_cursor.blink = false;
            style.scroll_animation = ScrollAnimation::none();
            style.animation_time = 0.0;
        });
        let mut input = egui::RawInput {
            screen_rect: Some(screen_rect),
            ..Default::default()
        };
        let viewport = input.viewports.get_mut(&ViewportId::ROOT).unwrap();
        viewport.native_pixels_per_point = Some(pixels_per_point);

        let mut response = None;

        // We need to run egui for a single frame so that the AccessKit state can be initialized
        // and users can immediately start querying for widgets.
        let mut output = ctx.run_ui(input.clone(), |ui| {
            response = app.run(ui, &mut state, false);
        });

        renderer.handle_delta(&mut output.textures_delta);

        let mut harness = Self {
            app,
            ctx,
            input,
            kittest: kittest::State::new(
                output
                    .platform_output
                    .accesskit_update
                    .take()
                    .expect("AccessKit was disabled"),
            ),
            output,
            response,
            state,
            renderer,
            max_steps,
            step_dt,
            wait_for_pending_images,
            queued_events: Default::default(),

            #[cfg(feature = "snapshot")]
            default_snapshot_options,

            #[cfg(feature = "snapshot")]
            snapshot_results: SnapshotResults::default(),
        };
        // Handle any viewport commands (e.g. a screenshot or resize) requested during the initial
        // frame above (which didn't go through `_step`).
        harness.handle_viewport_commands();

        // Run the harness until it is stable, ensuring that all Areas are shown and animations are done
        harness.run_ok();
        harness
    }

    /// Create a [`Harness`] via a [`HarnessBuilder`].
    pub fn builder() -> HarnessBuilder<State> {
        HarnessBuilder::default()
    }

    /// Create a new Harness with the given ui closure and a state.
    ///
    /// The ui closure will immediately be called once to create the initial ui.
    ///
    /// If you e.g. want to customize the size of the ui, you can use [`Harness::builder`].
    ///
    /// # Example
    /// ```rust
    /// # use egui_kittest::{Harness, kittest::Queryable};
    /// let mut checked = false;
    /// let mut harness = Harness::new_ui_state(|ui, checked| {
    ///     ui.checkbox(checked, "Check me!");
    /// }, checked);
    ///
    /// harness.get_by_label("Check me!").click();
    /// harness.run();
    ///
    /// assert_eq!(*harness.state(), true);
    /// ```
    #[track_caller]
    pub fn new_ui_state(app: impl FnMut(&mut egui::Ui, &mut State) + 'a, state: State) -> Self {
        Self::builder().build_ui_state(app, state)
    }

    /// Create a new [Harness] from the given eframe creation closure.
    #[cfg(feature = "eframe")]
    #[track_caller]
    pub fn new_eframe(builder: impl FnOnce(&mut eframe::CreationContext<'a>) -> State) -> Self
    where
        State: eframe::App + 'static,
    {
        Self::builder().build_eframe(builder)
    }

    /// Set the size of the window.
    /// Note: If you only want to set the size once at the beginning,
    /// prefer using [`HarnessBuilder::with_size`].
    #[inline]
    pub fn set_size(&mut self, size: Vec2) -> &mut Self {
        self.input.screen_rect = Some(Rect::from_min_size(Pos2::ZERO, size));
        self
    }

    /// Set the `pixels_per_point` of the window.
    /// Note: If you only want to set the `pixels_per_point` once at the beginning,
    /// prefer using [`HarnessBuilder::with_pixels_per_point`].
    #[inline]
    pub fn set_pixels_per_point(&mut self, pixels_per_point: f32) -> &mut Self {
        self.ctx.set_pixels_per_point(pixels_per_point);
        self
    }

    /// Run a frame for each queued event (or a single frame if there are no events).
    /// This will call the app closure with each queued event and
    /// update the Harness.
    pub fn step(&mut self) {
        let events = core::mem::take(&mut *self.queued_events.lock());
        if events.is_empty() {
            self.step_impl(false);
        }
        for event in events {
            self.input.events.push(event);
            self.step_impl(false);
        }
    }

    /// Run a single step. This will not process any events.
    fn step_impl(&mut self, sizing_pass: bool) {
        self.input.predicted_dt = self.step_dt;

        let mut output = self.ctx.run_ui(self.input.take(), |ui| {
            self.response = self.app.run(ui, &mut self.state, sizing_pass);
        });
        self.kittest.update(
            output
                .platform_output
                .accesskit_update
                .take()
                .expect("AccessKit was disabled"),
        );
        self.renderer.handle_delta(&mut output.textures_delta);
        self.output = output;

        self.handle_viewport_commands();
    }

    /// Calculate the rect that includes all popups and tooltips.
    fn compute_total_rect_with_popups(&self) -> Option<Rect> {
        // Start with the standard response rect
        let mut used = self.response.as_ref()?.rect;

        // Add all visible areas from other orders (popups, tooltips, etc.)
        self.ctx.memory(|mem| {
            mem.areas()
                .visible_layer_ids()
                .into_iter()
                .filter(|layer_id| layer_id.order != egui::Order::Background)
                .filter_map(|layer_id| mem.area_rect(layer_id.id))
                .for_each(|area_rect| used |= area_rect);
        });

        Some(used)
    }

    /// Resize the test harness to fit the contents. This only works when creating the Harness via
    /// [`Harness::new_ui`] / [`Harness::new_ui_state`] or
    /// [`HarnessBuilder::build_ui`] / [`HarnessBuilder::build_ui_state`].
    pub fn fit_contents(&mut self) {
        self.step_impl(true);

        // Calculate size including all content (main UI + popups + tooltips)
        if let Some(rect) = self.compute_total_rect_with_popups() {
            self.set_size(rect.size());
        }

        self.run_ok();
    }

    /// Run until
    /// - all animations are done
    /// - no more repaints are requested
    ///
    /// Returns the number of frames that were run.
    ///
    /// # Panics
    /// Panics if the number of steps exceeds the maximum number of steps set
    /// in [`HarnessBuilder::with_max_steps`].
    ///
    /// See also:
    /// - [`Harness::try_run`].
    /// - [`Harness::try_run_realtime`].
    /// - [`Harness::run_ok`].
    /// - [`Harness::step`].
    /// - [`Harness::run_steps`].
    #[track_caller]
    pub fn run(&mut self) -> u64 {
        match self.try_run() {
            Ok(steps) => steps,
            Err(err) => {
                panic!("{err}");
            }
        }
    }

    /// When `sleep` is true, each step sleeps for `self.step_dt`.
    /// When `diagnostic` is true, we run extra steps to find [`ExceededMaxStepsError::steps_to_settle`].
    fn try_run_impl(
        &mut self,
        sleep: bool,
        diagnostic: bool,
    ) -> Result<u64, ExceededMaxStepsError> {
        // Once the budget is blown we keep going for a while, purely to find out how many steps
        // would have been needed. The repaint causes are the ones from the moment we blew it.
        let diagnostic_max_steps = if diagnostic {
            config().diagnostic_max_steps()
        } else {
            0
        };
        let last_diagnostic_step = self.max_steps.saturating_add(diagnostic_max_steps);
        let mut repaint_causes_at_max_steps = None;

        let mut steps = 0;
        loop {
            steps += 1;
            self.step();

            let wait_for_images = self.wait_for_pending_images && self.ctx.has_pending_images();

            // We only care about immediate repaints
            if self.root_viewport_output().repaint_delay != Duration::ZERO && !wait_for_images {
                if let Some(repaint_causes) = repaint_causes_at_max_steps {
                    return Err(ExceededMaxStepsError {
                        max_steps: self.max_steps,
                        steps_to_settle: Some(steps),
                        diagnostic_max_steps,
                        repaint_causes,
                    });
                }
                break;
            } else if sleep || wait_for_images {
                std::thread::sleep(Duration::from_secs_f32(self.step_dt));
            }
            if steps > self.max_steps && repaint_causes_at_max_steps.is_none() {
                repaint_causes_at_max_steps = Some(self.ctx.repaint_causes());
            }
            if steps > last_diagnostic_step {
                return Err(ExceededMaxStepsError {
                    max_steps: self.max_steps,
                    steps_to_settle: None,
                    diagnostic_max_steps,
                    repaint_causes: repaint_causes_at_max_steps
                        .unwrap_or_else(|| self.ctx.repaint_causes()),
                });
            }
        }
        Ok(steps)
    }

    /// Run until
    /// - all animations are done
    /// - no more repaints are requested
    /// - the maximum number of steps is reached (See [`HarnessBuilder::with_max_steps`])
    ///
    /// Returns the number of steps that were run.
    ///
    /// # Errors
    /// Returns an error if the maximum number of steps is exceeded.
    ///
    /// See also:
    /// - [`Harness::run`].
    /// - [`Harness::run_ok`].
    /// - [`Harness::step`].
    /// - [`Harness::run_steps`].
    /// - [`Harness::try_run_realtime`].
    pub fn try_run(&mut self) -> Result<u64, ExceededMaxStepsError> {
        self.try_run_impl(false, true)
    }

    /// Run until
    /// - all animations are done
    /// - no more repaints are requested
    /// - the maximum number of steps is reached (See [`HarnessBuilder::with_max_steps`])
    ///
    /// Returns the number of steps that were run, or None if the maximum number of steps was exceeded.
    ///
    /// Unlike [`Harness::run`], this never steps past `max_steps`.
    ///
    /// See also:
    /// - [`Harness::run`].
    /// - [`Harness::try_run`].
    /// - [`Harness::step`].
    /// - [`Harness::run_steps`].
    /// - [`Harness::try_run_realtime`].
    pub fn run_ok(&mut self) -> Option<u64> {
        self.try_run_impl(false, false).ok()
    }

    /// Run multiple frames, sleeping for [`HarnessBuilder::with_step_dt`] between frames.
    ///
    /// This is useful to e.g. wait for an async operation to complete (e.g. loading of images).
    /// Runs until
    /// - all animations are done
    /// - no more repaints are requested
    /// - the maximum number of steps is reached (See [`HarnessBuilder::with_max_steps`])
    ///
    /// Returns the number of steps that were run.
    ///
    /// # Errors
    /// Returns an error if the maximum number of steps is exceeded.
    ///
    /// See also:
    /// - [`Harness::run`].
    /// - [`Harness::run_ok`].
    /// - [`Harness::step`].
    /// - [`Harness::run_steps`].
    /// - [`Harness::try_run`].
    pub fn try_run_realtime(&mut self) -> Result<u64, ExceededMaxStepsError> {
        self.try_run_impl(true, true)
    }

    /// Run a number of steps.
    /// Equivalent to calling [`Harness::step`] x times.
    pub fn run_steps(&mut self, steps: usize) {
        for _ in 0..steps {
            self.step();
        }
    }

    /// Access the [`egui::RawInput`] for the next frame.
    pub fn input(&self) -> &egui::RawInput {
        &self.input
    }

    /// Access the [`egui::RawInput`] for the next frame mutably.
    pub fn input_mut(&mut self) -> &mut egui::RawInput {
        &mut self.input
    }

    /// Access the [`egui::FullOutput`] for the last frame.
    pub fn output(&self) -> &egui::FullOutput {
        &self.output
    }

    /// Access the [`kittest::State`].
    pub fn kittest_state(&self) -> &kittest::State {
        &self.kittest
    }

    /// Access the state.
    pub fn state(&self) -> &State {
        &self.state
    }

    /// Access the state mutably.
    pub fn state_mut(&mut self) -> &mut State {
        &mut self.state
    }

    /// Consume the harness and return the state.
    pub fn into_state(self) -> State {
        self.state
    }

    /// Queue an event to be processed in the next frame.
    pub fn event(&self, event: egui::Event) {
        self.queued_events.lock().push(event);
    }

    /// Queue an event with modifiers.
    ///
    /// Queues the modifiers to be pressed, then the event, then the modifiers to be released.
    pub fn event_modifiers(&self, event: egui::Event, modifiers: Modifiers) {
        let mut queue = self.queued_events.lock();
        queue.push(egui::Event::ModifiersChanged(modifiers));
        queue.push(event);
        queue.push(egui::Event::ModifiersChanged(Modifiers::default()));
    }

    fn modifiers(&self, modifiers: Modifiers) {
        self.queued_events
            .lock()
            .push(egui::Event::ModifiersChanged(modifiers));
    }

    pub fn key_down(&self, key: egui::Key) {
        self.event(egui::Event::Key {
            key,
            pressed: true,
            modifiers: Modifiers::default(),
            repeat: false,
            physical_key: None,
        });
    }

    pub fn key_down_modifiers(&self, modifiers: Modifiers, key: egui::Key) {
        self.event_modifiers(
            egui::Event::Key {
                key,
                pressed: true,
                modifiers,
                repeat: false,
                physical_key: None,
            },
            modifiers,
        );
    }

    pub fn key_up(&self, key: egui::Key) {
        self.event(egui::Event::Key {
            key,
            pressed: false,
            modifiers: Modifiers::default(),
            repeat: false,
            physical_key: None,
        });
    }

    pub fn key_up_modifiers(&self, modifiers: Modifiers, key: egui::Key) {
        self.event_modifiers(
            egui::Event::Key {
                key,
                pressed: false,
                modifiers,
                repeat: false,
                physical_key: None,
            },
            modifiers,
        );
    }

    /// Press the given keys in combination.
    ///
    /// For e.g. [`Key::A`] + [`Key::B`] this would generate:
    /// - Press [`Key::A`]
    /// - Press [`Key::B`]
    /// - Release [`Key::B`]
    /// - Release [`Key::A`]
    pub fn key_combination(&self, keys: &[Key]) {
        for key in keys {
            self.key_down(*key);
        }
        for key in keys.iter().rev() {
            self.key_up(*key);
        }
    }

    /// Press the given keys in combination, with modifiers.
    ///
    /// For e.g. [`Modifiers::COMMAND`] + [`Key::A`] + [`Key::B`] this would generate:
    /// - Press [`Modifiers::COMMAND`]
    /// - Press [`Key::A`]
    /// - Press [`Key::B`]
    /// - Release [`Key::B`]
    /// - Release [`Key::A`]
    /// - Release [`Modifiers::COMMAND`]
    pub fn key_combination_modifiers(&self, modifiers: Modifiers, keys: &[Key]) {
        self.modifiers(modifiers);

        for pressed in [true, false] {
            for key in keys {
                self.event(egui::Event::Key {
                    key: *key,
                    pressed,
                    modifiers,
                    repeat: false,
                    physical_key: None,
                });
            }
        }

        self.modifiers(Modifiers::default());
    }

    /// Press a key.
    ///
    /// This will create a key down event and a key up event.
    pub fn key_press(&self, key: egui::Key) {
        self.key_combination(&[key]);
    }

    /// Press a key with modifiers.
    ///
    /// This will
    /// - set the modifiers
    /// - create a key down event
    /// - create a key up event
    /// - reset the modifiers
    pub fn key_press_modifiers(&self, modifiers: Modifiers, key: egui::Key) {
        self.key_combination_modifiers(modifiers, &[key]);
    }

    /// Move mouse cursor to this position.
    pub fn hover_at(&self, pos: egui::Pos2) {
        self.event(egui::Event::PointerMoved(pos));
    }

    /// Start dragging from a position.
    pub fn drag_at(&self, pos: egui::Pos2) {
        self.event(egui::Event::PointerButton {
            pos,
            button: PointerButton::Primary,
            pressed: true,
            modifiers: Modifiers::NONE,
        });
    }

    /// Stop dragging and remove cursor.
    pub fn drop_at(&self, pos: egui::Pos2) {
        self.event(egui::Event::PointerButton {
            pos,
            button: PointerButton::Primary,
            pressed: false,
            modifiers: Modifiers::NONE,
        });
        self.remove_cursor();
    }

    /// Remove the cursor from the screen.
    ///
    /// Will fire a [`egui::Event::PointerGone`] event.
    ///
    /// If you click a button and then take a snapshot, the button will be shown as hovered.
    /// If you don't want that, you can call this method after clicking.
    pub fn remove_cursor(&self) {
        self.event(egui::Event::PointerGone);
    }

    /// Mask something. Useful for snapshot tests.
    ///
    /// Call this _after_ [`Self::run`] and before [`Self::snapshot`].
    /// This will add a [`RectShape`] to the output shapes, for the current frame.
    /// Will be overwritten on the next call to [`Self::run`].
    pub fn mask(&mut self, rect: Rect) {
        self.output.shapes.push(ClippedShape {
            clip_rect: Rect::EVERYTHING,
            shape: Shape::Rect(RectShape::filled(rect, 0.0, Color32::MAGENTA)),
        });
    }

    /// Render the last output to an image.
    ///
    /// # Errors
    /// Returns an error if the rendering fails.
    #[cfg(any(feature = "wgpu", feature = "snapshot"))]
    pub fn render(&mut self) -> Result<image::RgbaImage, String> {
        let mut output = self.output.clone();

        if let Some(mouse_pos) = self.ctx.input(|i| i.pointer.hover_pos()) {
            // Paint a mouse cursor:
            let triangle = vec![
                mouse_pos,
                mouse_pos + egui::vec2(16.0, 8.0),
                mouse_pos + egui::vec2(8.0, 16.0),
            ];

            output.shapes.push(ClippedShape {
                clip_rect: self.ctx.content_rect(),
                shape: egui::epaint::PathShape::convex_polygon(
                    triangle,
                    Color32::WHITE,
                    egui::Stroke::new(1.0, Color32::BLACK),
                )
                .into(),
            });
        }

        self.renderer.render(&self.ctx, &output)
    }

    /// Apply the [`egui::ViewportCommand`]s the app emitted during the last frame.
    fn handle_viewport_commands(&mut self) {
        self.handle_inner_size();

        #[cfg(any(feature = "wgpu", feature = "snapshot"))]
        self.handle_screenshots();
    }

    /// Resize the harness to the last [`egui::ViewportCommand::InnerSize`] requested by the app
    /// during the last frame, if any.
    fn handle_inner_size(&mut self) {
        let new_inner_size =
            self.root_viewport_output()
                .commands
                .iter()
                .rev()
                .find_map(|command| {
                    if let egui::ViewportCommand::InnerSize(size) = command {
                        Some(*size)
                    } else {
                        None
                    }
                });

        if let Some(size) = new_inner_size {
            self.set_size(size);
            self.ctx.request_repaint();
        }
    }

    /// Fulfill any [`egui::ViewportCommand::Screenshot`] requests made by the app during the
    /// last frame.
    ///
    /// If a screenshot was requested and no renderer is available, an error will be logged.
    #[cfg(any(feature = "wgpu", feature = "snapshot"))]
    fn handle_screenshots(&mut self) {
        // Collect all screenshot requests from this frame's viewport output.
        let requests: Vec<(ViewportId, egui::UserData)> = self
            .output
            .viewport_output
            .iter()
            .flat_map(|(id, viewport)| {
                viewport.commands.iter().filter_map(move |command| {
                    if let egui::ViewportCommand::Screenshot(user_data) = command {
                        Some((*id, user_data.clone()))
                    } else {
                        None
                    }
                })
            })
            .collect();

        if requests.is_empty() {
            return;
        }

        // Render the frame once and reuse it for every request. We render without the synthetic
        // mouse cursor since a real screenshot wouldn't include the OS cursor either.
        let image = match self.renderer.render(&self.ctx, &self.output) {
            Ok(image) => image,
            Err(err) => {
                log::error!("Failed to render screenshot requested via ViewportCommand: {err}");
                return;
            }
        };
        let image = std::sync::Arc::new(rgba_image_to_color_image(&image));

        for (viewport_id, user_data) in requests {
            self.input.events.push(egui::Event::Screenshot {
                viewport_id,
                user_data,
                image: std::sync::Arc::clone(&image),
            });
        }

        // Make sure the run loop runs at least one more frame so the app actually receives the
        // queued screenshot event.
        self.ctx.request_repaint();
    }

    /// Get the root viewport output
    fn root_viewport_output(&self) -> &egui::ViewportOutput {
        self.output
            .viewport_output
            .get(&ViewportId::ROOT)
            .expect("Missing root viewport")
    }

    /// The root node of the test harness.
    pub fn root(&self) -> Node<'_> {
        Node::new(
            self.kittest.root(),
            &self.queued_events,
            self.ctx.pixels_per_point(),
        )
    }

    /// Spawn a real native eframe window running this harness's app, reusing its [`egui::Context`].
    ///
    /// Blocks until the window is closed.
    ///
    /// Useful for interactively debugging a failing test: add a call to this before the failing
    /// assertion to poke at the UI yourself.
    ///
    /// # macOS: must be called on the main thread
    /// `AppKit` requires UI work to happen on the main thread, but by default cargo's test harness
    /// runs each test on a spawned worker thread, so this function will panic on macOS unless
    /// you opt out of the default harness.
    ///
    /// To fix this, disable the default libtest harness for your test target and run tests on
    /// the main thread yourself. In `Cargo.toml`:
    ///
    /// ```toml
    /// [[test]]
    /// name = "your_test"
    /// harness = false
    /// ```
    ///
    /// Then write a `fn main()` in the test file that invokes your test directly.
    ///
    /// See also: <https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-harness-field>
    #[cfg(all(feature = "eframe", not(target_arch = "wasm32")))]
    #[deprecated = "Only for debugging, don't commit this."]
    pub fn spawn_eframe_app(self)
    where
        'a: 'static,
        State: 'static,
    {
        #[cfg(target_os = "macos")]
        {
            // AppKit requires UI work to happen on the main thread, but by default cargo's
            // test harness runs each test on a spawned worker thread.
            #[expect(unsafe_code)]
            // SAFETY: `pthread_main_np` is a thread-safe libc query with no arguments.
            let is_main_thread = unsafe {
                unsafe extern "C" {
                    fn pthread_main_np() -> core::ffi::c_int;
                }
                pthread_main_np() != 0
            };
            assert!(
                is_main_thread,
                "spawn_eframe_app must be called on the main thread on macOS, \
                 but the default `cargo test` harness runs each test on a worker thread.\n\
                 \n\
                 To fix this, disable the default libtest harness for your test target and run \
                 tests on the main thread yourself. In Cargo.toml:\n\
                 \n\
                     [[test]]\n\
                     name = \"your_test\"\n\
                     harness = false\n\
                 \n\
                 Then write a `fn main()` in the test file that invokes your test directly.\n\
                 \n\
                 See: https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-harness-field"
            );
        }

        struct UiApp {
            f: Box<dyn FnMut(&mut egui::Ui)>,
        }

        impl eframe::App for UiApp {
            fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
                (self.f)(ui);
            }
        }

        struct UiStateApp<State> {
            f: Box<dyn FnMut(&mut egui::Ui, &mut State)>,
            state: State,
        }

        impl<State: 'static> eframe::App for UiStateApp<State> {
            fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
                let Self { f, state } = self;
                f(ui, state);
            }
        }

        use crate::app_kind::AppKindEframe;

        let Self {
            ctx, state, app, ..
        } = self;

        let eframe_app: Box<dyn eframe::App> = match app {
            AppKind::Ui(f) => Box::new(UiApp { f }),
            AppKind::UiState(f) => Box::new(UiStateApp { f, state }),
            AppKind::Eframe(AppKindEframe { take_app, .. }) => take_app(state),
        };

        eframe::run_native_ext(
            "egui_kittest",
            eframe::NativeOptions::default(),
            Some(ctx),
            Box::new(|_cc| Ok(eframe_app)),
        )
        .unwrap();
    }
}

/// Utilities for stateless harnesses.
impl<'a> Harness<'a> {
    /// Create a new Harness with the given ui closure.
    /// Use the [`Harness::run`], [`Harness::step`], etc... methods to run the app.
    ///
    /// The ui closure will immediately be called once to create the initial ui.
    ///
    /// If you e.g. want to customize the size of the ui, you can use [`Harness::builder`].
    ///
    /// # Example
    /// ```rust
    /// # use egui_kittest::Harness;
    /// let mut harness = Harness::new_ui(|ui| {
    ///     ui.label("Hello, world!");
    /// });
    /// ```
    #[track_caller]
    pub fn new_ui(app: impl FnMut(&mut egui::Ui) + 'a) -> Self {
        Self::builder().build_ui(app)
    }
}

/// Convert a rendered [`image::RgbaImage`] (premultiplied alpha, as produced by the renderer)
/// into an [`egui::ColorImage`] suitable for [`egui::Event::Screenshot`].
#[cfg(any(feature = "wgpu", feature = "snapshot"))]
fn rgba_image_to_color_image(image: &image::RgbaImage) -> egui::ColorImage {
    let size = [image.width() as usize, image.height() as usize];
    let pixels = image
        .pixels()
        .map(|p| Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3]))
        .collect();
    egui::ColorImage::new(size, pixels)
}

impl<'tree, 'node, State> Queryable<'tree, 'node, Node<'tree>> for Harness<'_, State>
where
    'node: 'tree,
{
    fn queryable_node(&'node self) -> Node<'tree> {
        self.root()
    }
}
