use nalgebra_glm::{Vec2, Vec4};
use winit::keyboard::KeyCode;

use crate::ecs::input::resources::MouseState;
use crate::ecs::ui::resources::UiEvent;
use crate::ecs::world::World;

use super::driver::UiTestDriver;
use super::types::{
    ActionRecord, AssertKind, EventMatch, ReportSummary, ResultRecord, ScheduledAction, TestAction,
    TestReport, UiTestResult,
};

use crate::prelude::*;
impl UiTestDriver {
    fn clear_frame_input(&self, world: &mut World) {
        world.resources.input.mouse.state = MouseState::empty();
        world.resources.input.mouse.position_delta = Vec2::zeros();
        world.resources.input.mouse.wheel_delta = Vec2::zeros();
        world.resources.input.keyboard.just_pressed_keys.clear();
        world.resources.input.keyboard.just_released_keys.clear();
        world.resources.input.keyboard.frame_keys.clear();
        world.resources.input.keyboard.frame_chars.clear();
    }

    pub fn step(&mut self, world: &mut World) {
        if !self.active {
            return;
        }

        self.captured_events
            .extend(world.resources.retained_ui.frame.events.iter().cloned());

        self.clear_frame_input(world);

        if self.pending_release {
            self.pending_release = false;
            world
                .resources
                .input
                .mouse
                .state
                .insert(MouseState::LEFT_JUST_RELEASED);
        }

        if self.pending_right_release {
            self.pending_right_release = false;
            world
                .resources
                .input
                .mouse
                .state
                .insert(MouseState::RIGHT_JUST_RELEASED);
        }

        if !self.pending_key_releases.is_empty() {
            let keys = std::mem::take(&mut self.pending_key_releases);
            for key in &keys {
                world
                    .resources
                    .input
                    .keyboard
                    .keystates
                    .insert(*key, winit::event::ElementState::Released);
                world
                    .resources
                    .input
                    .keyboard
                    .just_released_keys
                    .insert(*key);
                world
                    .resources
                    .input
                    .keyboard
                    .frame_keys
                    .push((*key, false));
            }
        }

        self.process_frame_actions(world);
        self.frame += 1;

        if self.cursor >= self.actions.len()
            && !self.pending_release
            && !self.pending_right_release
            && self.pending_key_releases.is_empty()
        {
            self.active = false;
            self.print_results();
            if self.exit_on_complete {
                let code = if self.all_passed() { 0 } else { 1 };
                std::process::exit(code);
            }
        }
    }

    fn process_frame_actions(&mut self, world: &mut World) {
        while self.cursor < self.actions.len() && self.actions[self.cursor].frame == self.frame {
            let action = self.actions[self.cursor].action.clone();
            self.execute_action(world, action);
            self.cursor += 1;
        }
    }

    pub(super) fn resolve_entity_center(&self, world: &World, test_id: &str) -> Option<Vec2> {
        let entity = *world
            .resources
            .retained_ui
            .accessibility
            .test_id_map
            .get(test_id)?;
        let node = world.ui.get_ui_layout_node(entity)?;
        Some(node.computed_rect.center())
    }

    fn resolve_raw_entity_center(&self, world: &World, entity: freecs::Entity) -> Option<Vec2> {
        let node = world.ui.get_ui_layout_node(entity)?;
        Some(node.computed_rect.center())
    }

    fn inject_click_at(&mut self, world: &mut World, position: Vec2) {
        let delta = position - world.resources.input.mouse.position;
        world.resources.input.mouse.position = position;
        world.resources.input.mouse.position_delta = delta;
        world
            .resources
            .input
            .mouse
            .state
            .insert(MouseState::MOVED | MouseState::LEFT_JUST_PRESSED | MouseState::LEFT_CLICKED);
        self.pending_release = true;
    }

    fn build_parent_map(
        world: &World,
    ) -> std::collections::HashMap<freecs::Entity, freecs::Entity> {
        let mut map = std::collections::HashMap::new();
        for (parent, children) in world.resources.transform_state.children_cache.iter() {
            for &child in children {
                map.insert(child, *parent);
            }
        }
        map
    }

    fn scroll_into_view(&mut self, world: &mut World, test_id: &str) -> bool {
        let entity = match world
            .resources
            .retained_ui
            .accessibility
            .test_id_map
            .get(test_id)
            .copied()
        {
            Some(e) => e,
            None => return false,
        };

        let target_center = match world.ui.get_ui_layout_node(entity) {
            Some(node) => node.computed_rect.center(),
            None => return false,
        };

        let parents = Self::build_parent_map(world);
        let mut current = entity;
        let mut outermost_scroll: Option<freecs::Entity> = None;

        while let Some(&parent) = parents.get(&current) {
            if world.ui.get_ui_scroll_area(parent).is_some() {
                outermost_scroll = Some(parent);
            }
            current = parent;
        }

        let scroll_entity = match outermost_scroll {
            Some(e) => e,
            None => return false,
        };

        let scroll_rect = match world.ui.get_ui_layout_node(scroll_entity) {
            Some(node) => node.computed_rect,
            None => return false,
        };

        if scroll_rect.contains(target_center) {
            return false;
        }

        let dpi_scale = world.resources.window.cached_scale_factor.max(1.0);
        let (current_offset, content_entity) = match world.ui.get_ui_scroll_area(scroll_entity) {
            Some(data) => (data.scroll_offset, data.content_entity),
            None => return false,
        };

        let scroll_center_y = scroll_rect.center().y;
        let delta_pixels = target_center.y - scroll_center_y;
        let delta_units = delta_pixels / dpi_scale;
        let new_offset = (current_offset + delta_units).max(0.0);

        if (new_offset - current_offset).abs() < 0.5 {
            return false;
        }

        if let Some(data_mut) = world.ui.get_ui_scroll_area_mut(scroll_entity) {
            data_mut.scroll_offset = new_offset;
        }
        if let Some(content_node) = world.ui.get_ui_layout_node_mut(content_entity) {
            content_node.scroll_offset = Vec2::new(0.0, new_offset);
        }

        true
    }

    fn execute_action(&mut self, world: &mut World, action: TestAction) {
        self.record_action(&action, world);
        match action {
            TestAction::MoveMouse(position) => {
                let delta = position - world.resources.input.mouse.position;
                world.resources.input.mouse.position = position;
                world.resources.input.mouse.position_delta = delta;
                world.resources.input.mouse.state.insert(MouseState::MOVED);
            }
            TestAction::MoveMouseToEntity(test_id) => {
                if let Some(center) = self.resolve_entity_center(world, &test_id) {
                    let delta = center - world.resources.input.mouse.position;
                    world.resources.input.mouse.position = center;
                    world.resources.input.mouse.position_delta = delta;
                    world.resources.input.mouse.state.insert(MouseState::MOVED);
                } else {
                    eprintln!("[UI_TEST] WARNING: entity '{test_id}' not found for move_mouse_to");
                }
            }
            TestAction::MousePress => {
                world
                    .resources
                    .input
                    .mouse
                    .state
                    .insert(MouseState::LEFT_JUST_PRESSED | MouseState::LEFT_CLICKED);
            }
            TestAction::MouseRelease => {
                world
                    .resources
                    .input
                    .mouse
                    .state
                    .insert(MouseState::LEFT_JUST_RELEASED);
            }
            TestAction::RightMousePress => {
                world
                    .resources
                    .input
                    .mouse
                    .state
                    .insert(MouseState::RIGHT_JUST_PRESSED | MouseState::RIGHT_CLICKED);
            }
            TestAction::RightMouseRelease => {
                world
                    .resources
                    .input
                    .mouse
                    .state
                    .insert(MouseState::RIGHT_JUST_RELEASED);
            }
            TestAction::ClickAt(position) => {
                self.inject_click_at(world, position);
            }
            TestAction::ClickAtEntity(test_id) => {
                if self.scroll_into_view(world, &test_id) {
                    self.actions.push(ScheduledAction {
                        frame: self.frame + 1,
                        action: TestAction::ClickAtEntity(test_id),
                    });
                    self.actions.sort_by_key(|a| a.frame);
                } else if let Some(center) = self.resolve_entity_center(world, &test_id) {
                    self.inject_click_at(world, center);
                } else {
                    eprintln!("[UI_TEST] WARNING: entity '{test_id}' not found for click_entity");
                }
            }
            TestAction::RightClickAt(position) => {
                let delta = position - world.resources.input.mouse.position;
                world.resources.input.mouse.position = position;
                world.resources.input.mouse.position_delta = delta;
                world.resources.input.mouse.state.insert(
                    MouseState::MOVED | MouseState::RIGHT_JUST_PRESSED | MouseState::RIGHT_CLICKED,
                );
                self.pending_right_release = true;
            }
            TestAction::RightClickAtEntity(test_id) => {
                if let Some(center) = self.resolve_entity_center(world, &test_id) {
                    let delta = center - world.resources.input.mouse.position;
                    world.resources.input.mouse.position = center;
                    world.resources.input.mouse.position_delta = delta;
                    world.resources.input.mouse.state.insert(
                        MouseState::MOVED
                            | MouseState::RIGHT_JUST_PRESSED
                            | MouseState::RIGHT_CLICKED,
                    );
                    self.pending_right_release = true;
                } else {
                    eprintln!(
                        "[UI_TEST] WARNING: entity '{test_id}' not found for right_click_entity"
                    );
                }
            }
            TestAction::DragEntity(test_id, end_position) => {
                if let Some(center) = self.resolve_entity_center(world, &test_id) {
                    let delta = center - world.resources.input.mouse.position;
                    world.resources.input.mouse.position = center;
                    world.resources.input.mouse.position_delta = delta;
                    world.resources.input.mouse.state.insert(
                        MouseState::MOVED
                            | MouseState::LEFT_JUST_PRESSED
                            | MouseState::LEFT_CLICKED,
                    );
                    let release_frame = self.frame + 1;
                    self.actions.push(ScheduledAction {
                        frame: release_frame,
                        action: TestAction::MoveMouse(end_position),
                    });
                    self.actions.push(ScheduledAction {
                        frame: release_frame,
                        action: TestAction::MouseRelease,
                    });
                    self.actions.sort_by_key(|a| a.frame);
                } else {
                    eprintln!("[UI_TEST] WARNING: entity '{test_id}' not found for drag_entity");
                }
            }
            TestAction::SetSliderValue(test_id, target_value) => {
                if self.scroll_into_view(world, &test_id) {
                    self.actions.push(ScheduledAction {
                        frame: self.frame + 1,
                        action: TestAction::SetSliderValue(test_id, target_value),
                    });
                    self.actions.sort_by_key(|a| a.frame);
                    return;
                }
                let entity = world
                    .resources
                    .retained_ui
                    .accessibility
                    .test_id_map
                    .get(&test_id)
                    .copied();
                if let Some(entity) = entity {
                    let slider_info = if let Some(data) = world.ui.get_ui_slider(entity) {
                        let rect = world
                            .ui
                            .get_ui_layout_node(entity)
                            .map(|node| node.computed_rect);
                        rect.map(|rect| {
                            let range = data.max - data.min;
                            let normalized = if data.logarithmic && data.min > 0.0 && range > 0.0 {
                                ((target_value / data.min).ln() / (data.max / data.min).ln())
                                    .clamp(0.0, 1.0)
                            } else if range.abs() > f32::EPSILON {
                                ((target_value - data.min) / range).clamp(0.0, 1.0)
                            } else {
                                0.0
                            };
                            let target_x = rect.min.x + normalized * rect.width();
                            let target_y = rect.center().y;
                            Vec2::new(target_x, target_y)
                        })
                    } else {
                        None
                    };
                    if let Some(position) = slider_info {
                        self.inject_click_at(world, position);
                    } else {
                        eprintln!(
                            "[UI_TEST] WARNING: '{test_id}' is not a slider or has no layout"
                        );
                    }
                } else {
                    eprintln!(
                        "[UI_TEST] WARNING: entity '{test_id}' not found for set_slider_value"
                    );
                }
            }
            TestAction::SelectDropdownOption(test_id, option_index) => {
                if self.scroll_into_view(world, &test_id) {
                    self.actions.push(ScheduledAction {
                        frame: self.frame + 1,
                        action: TestAction::SelectDropdownOption(test_id, option_index),
                    });
                    self.actions.sort_by_key(|a| a.frame);
                    return;
                }
                let entity = world
                    .resources
                    .retained_ui
                    .accessibility
                    .test_id_map
                    .get(&test_id)
                    .copied();
                if let Some(entity) = entity {
                    let option_count = if let Some(data) = world.ui.get_ui_dropdown(entity) {
                        data.options.len()
                    } else {
                        0
                    };
                    if option_count > 0 {
                        if let Some(center) = self.resolve_entity_center(world, &test_id) {
                            self.inject_click_at(world, center);
                        }
                        let nav_start = self.frame + 2;
                        let arrow_count = if option_index == 0 {
                            option_count
                        } else {
                            option_index
                        };
                        for step in 0..arrow_count {
                            self.actions.push(ScheduledAction {
                                frame: nav_start + step as u64,
                                action: TestAction::PressAndReleaseKey(KeyCode::ArrowDown),
                            });
                        }
                        self.actions.push(ScheduledAction {
                            frame: nav_start + arrow_count as u64,
                            action: TestAction::PressAndReleaseKey(KeyCode::Enter),
                        });
                        self.actions.sort_by_key(|a| a.frame);
                    } else {
                        eprintln!(
                            "[UI_TEST] WARNING: '{test_id}' is not a dropdown or has no options"
                        );
                    }
                } else {
                    eprintln!(
                        "[UI_TEST] WARNING: entity '{test_id}' not found for select_dropdown_option"
                    );
                }
            }
            TestAction::SelectTab(test_id, tab_index) => {
                if self.scroll_into_view(world, &test_id) {
                    self.actions.push(ScheduledAction {
                        frame: self.frame + 1,
                        action: TestAction::SelectTab(test_id, tab_index),
                    });
                    self.actions.sort_by_key(|a| a.frame);
                    return;
                }
                let entity = world
                    .resources
                    .retained_ui
                    .accessibility
                    .test_id_map
                    .get(&test_id)
                    .copied();
                if let Some(entity) = entity {
                    let tab_entity = if let Some(data) = world.ui.get_ui_tab_bar(entity) {
                        data.tab_entities.get(tab_index).copied()
                    } else {
                        None
                    };
                    if let Some(tab_entity) = tab_entity {
                        if let Some(center) = self.resolve_raw_entity_center(world, tab_entity) {
                            self.inject_click_at(world, center);
                        } else {
                            eprintln!(
                                "[UI_TEST] WARNING: tab {tab_index} in '{test_id}' has no layout"
                            );
                        }
                    } else {
                        eprintln!(
                            "[UI_TEST] WARNING: '{test_id}' is not a tab bar or index {tab_index} out of range"
                        );
                    }
                } else {
                    eprintln!("[UI_TEST] WARNING: entity '{test_id}' not found for select_tab");
                }
            }
            TestAction::TypeChars(chars) => {
                for character in &chars {
                    world.resources.input.keyboard.frame_chars.push(*character);
                }
            }
            TestAction::PressAndReleaseKey(key) => {
                world
                    .resources
                    .input
                    .keyboard
                    .keystates
                    .insert(key, winit::event::ElementState::Pressed);
                world.resources.input.keyboard.just_pressed_keys.insert(key);
                world.resources.input.keyboard.frame_keys.push((key, true));
                self.pending_key_releases.push(key);
            }
            TestAction::KeyDown(key) => {
                world
                    .resources
                    .input
                    .keyboard
                    .keystates
                    .insert(key, winit::event::ElementState::Pressed);
                world.resources.input.keyboard.just_pressed_keys.insert(key);
                world.resources.input.keyboard.frame_keys.push((key, true));
            }
            TestAction::KeyUp(key) => {
                world
                    .resources
                    .input
                    .keyboard
                    .keystates
                    .insert(key, winit::event::ElementState::Released);
                world
                    .resources
                    .input
                    .keyboard
                    .just_released_keys
                    .insert(key);
                world.resources.input.keyboard.frame_keys.push((key, false));
            }
            TestAction::Scroll(delta) => {
                world.resources.input.mouse.wheel_delta = delta;
                world
                    .resources
                    .input
                    .mouse
                    .state
                    .insert(MouseState::SCROLLED);
            }
            TestAction::Screenshot(_path) => {
                #[cfg(not(target_arch = "wasm32"))]
                {
                    crate::ecs::world::commands::capture_screenshot_to_path(world, &_path);
                    self.screenshots.push(_path);
                }
            }
            TestAction::Assert(name, kind) => {
                let result = self.evaluate_assertion(world, &name, &kind);
                self.result_frames.push(self.frame);
                self.results.push(result);
            }
            TestAction::Log(message) => {
                eprintln!("[UI_TEST] {message}");
            }
            TestAction::Exit(success) => {
                self.active = false;
                self.print_results();
                let code = if success { 0 } else { 1 };
                std::process::exit(code);
            }
        }
    }

    fn evaluate_assertion(&self, world: &World, name: &str, kind: &AssertKind) -> UiTestResult {
        match kind {
            AssertKind::Visible(test_id) => {
                let entity = world
                    .resources
                    .retained_ui
                    .accessibility
                    .test_id_map
                    .get(test_id);
                let visible = entity
                    .and_then(|entity| world.ui.get_ui_layout_node(*entity))
                    .is_some_and(|node| node.visible);
                UiTestResult {
                    name: name.to_string(),
                    passed: visible,
                    message: if visible {
                        format!("'{test_id}' is visible")
                    } else {
                        format!("'{test_id}' expected visible but was not")
                    },
                }
            }
            AssertKind::NotVisible(test_id) => {
                let entity = world
                    .resources
                    .retained_ui
                    .accessibility
                    .test_id_map
                    .get(test_id);
                let visible = entity
                    .and_then(|entity| world.ui.get_ui_layout_node(*entity))
                    .is_some_and(|node| node.visible);
                UiTestResult {
                    name: name.to_string(),
                    passed: !visible,
                    message: if !visible {
                        format!("'{test_id}' is not visible")
                    } else {
                        format!("'{test_id}' expected not visible but was visible")
                    },
                }
            }
            AssertKind::Focused(test_id) => {
                let entity = world
                    .resources
                    .retained_ui
                    .accessibility
                    .test_id_map
                    .get(test_id);
                let focused = entity.is_some_and(|entity| {
                    world
                        .resources
                        .retained_ui
                        .interaction_for_active()
                        .focused_entity
                        == Some(*entity)
                });
                UiTestResult {
                    name: name.to_string(),
                    passed: focused,
                    message: if focused {
                        format!("'{test_id}' is focused")
                    } else {
                        format!("'{test_id}' expected focused but was not")
                    },
                }
            }
            AssertKind::NotFocused(test_id) => {
                let entity = world
                    .resources
                    .retained_ui
                    .accessibility
                    .test_id_map
                    .get(test_id);
                let focused = entity.is_some_and(|entity| {
                    world
                        .resources
                        .retained_ui
                        .interaction_for_active()
                        .focused_entity
                        == Some(*entity)
                });
                UiTestResult {
                    name: name.to_string(),
                    passed: !focused,
                    message: if !focused {
                        format!("'{test_id}' is not focused")
                    } else {
                        format!("'{test_id}' expected not focused but was focused")
                    },
                }
            }
            AssertKind::Hovered(test_id) => {
                let entity = world
                    .resources
                    .retained_ui
                    .accessibility
                    .test_id_map
                    .get(test_id);
                let hovered = entity
                    .and_then(|entity| world.ui.get_ui_node_interaction(*entity))
                    .is_some_and(|interaction| interaction.hovered);
                UiTestResult {
                    name: name.to_string(),
                    passed: hovered,
                    message: if hovered {
                        format!("'{test_id}' is hovered")
                    } else {
                        format!("'{test_id}' expected hovered but was not")
                    },
                }
            }
            AssertKind::NotHovered(test_id) => {
                let entity = world
                    .resources
                    .retained_ui
                    .accessibility
                    .test_id_map
                    .get(test_id);
                let hovered = entity
                    .and_then(|entity| world.ui.get_ui_node_interaction(*entity))
                    .is_some_and(|interaction| interaction.hovered);
                UiTestResult {
                    name: name.to_string(),
                    passed: !hovered,
                    message: if !hovered {
                        format!("'{test_id}' is not hovered")
                    } else {
                        format!("'{test_id}' expected not hovered but was hovered")
                    },
                }
            }
            AssertKind::Disabled(test_id) => {
                let entity = world
                    .resources
                    .retained_ui
                    .accessibility
                    .test_id_map
                    .get(test_id);
                let disabled = entity
                    .and_then(|entity| world.ui.get_ui_node_interaction(*entity))
                    .is_some_and(|interaction| interaction.disabled);
                UiTestResult {
                    name: name.to_string(),
                    passed: disabled,
                    message: if disabled {
                        format!("'{test_id}' is disabled")
                    } else {
                        format!("'{test_id}' expected disabled but was not")
                    },
                }
            }
            AssertKind::NotDisabled(test_id) => {
                let entity = world
                    .resources
                    .retained_ui
                    .accessibility
                    .test_id_map
                    .get(test_id);
                let disabled = entity
                    .and_then(|entity| world.ui.get_ui_node_interaction(*entity))
                    .is_some_and(|interaction| interaction.disabled);
                UiTestResult {
                    name: name.to_string(),
                    passed: !disabled,
                    message: if !disabled {
                        format!("'{test_id}' is enabled")
                    } else {
                        format!("'{test_id}' expected enabled but was disabled")
                    },
                }
            }
            AssertKind::TextEquals(test_id, expected) => {
                let actual = self.get_text_content(world, test_id);
                let passed = actual.as_deref() == Some(expected.as_str());
                UiTestResult {
                    name: name.to_string(),
                    passed,
                    message: if passed {
                        format!("'{test_id}' text equals '{expected}'")
                    } else {
                        format!(
                            "'{test_id}' expected text '{expected}', got '{}'",
                            actual.as_deref().unwrap_or("<none>")
                        )
                    },
                }
            }
            AssertKind::WidgetValueF32(test_id, expected, tolerance) => {
                match read_widget_f32(world, test_id) {
                    Some(actual) => {
                        let passed = (actual - expected).abs() <= *tolerance;
                        UiTestResult {
                            name: name.to_string(),
                            passed,
                            message: if passed {
                                format!("'{test_id}' value {actual} matches expected {expected}")
                            } else {
                                format!(
                                    "'{test_id}' expected {expected} (tolerance {tolerance}), got {actual}"
                                )
                            },
                        }
                    }
                    None => UiTestResult {
                        name: name.to_string(),
                        passed: false,
                        message: format!("'{test_id}' has no f32 widget value"),
                    },
                }
            }
            AssertKind::WidgetValueBool(test_id, expected) => {
                match read_widget_bool(world, test_id) {
                    Some(actual) => {
                        let passed = actual == *expected;
                        UiTestResult {
                            name: name.to_string(),
                            passed,
                            message: if passed {
                                format!("'{test_id}' is {expected}")
                            } else {
                                format!("'{test_id}' expected {expected}, got {actual}")
                            },
                        }
                    }
                    None => UiTestResult {
                        name: name.to_string(),
                        passed: false,
                        message: format!("'{test_id}' has no bool widget value"),
                    },
                }
            }
            AssertKind::WidgetValueUsize(test_id, expected) => {
                match read_widget_usize(world, test_id) {
                    Some(actual) => {
                        let passed = actual == *expected;
                        UiTestResult {
                            name: name.to_string(),
                            passed,
                            message: if passed {
                                format!("'{test_id}' is {expected}")
                            } else {
                                format!("'{test_id}' expected {expected}, got {actual}")
                            },
                        }
                    }
                    None => UiTestResult {
                        name: name.to_string(),
                        passed: false,
                        message: format!("'{test_id}' has no usize widget value"),
                    },
                }
            }
            AssertKind::WidgetValueString(test_id, expected) => {
                match read_widget_string(world, test_id) {
                    Some(actual) => {
                        let passed = actual == *expected;
                        UiTestResult {
                            name: name.to_string(),
                            passed,
                            message: if passed {
                                format!("'{test_id}' equals '{expected}'")
                            } else {
                                format!("'{test_id}' expected '{expected}', got '{actual}'")
                            },
                        }
                    }
                    None => UiTestResult {
                        name: name.to_string(),
                        passed: false,
                        message: format!("'{test_id}' has no string widget value"),
                    },
                }
            }
            AssertKind::WidgetValueVec4(test_id, expected, tolerance) => {
                match read_widget_vec4(world, test_id) {
                    Some(actual) => {
                        let diff = (actual - expected).norm();
                        let passed = diff <= *tolerance;
                        UiTestResult {
                            name: name.to_string(),
                            passed,
                            message: if passed {
                                format!("'{test_id}' matches expected value")
                            } else {
                                format!(
                                    "'{test_id}' expected [{:.3}, {:.3}, {:.3}, {:.3}], got [{:.3}, {:.3}, {:.3}, {:.3}]",
                                    expected.x,
                                    expected.y,
                                    expected.z,
                                    expected.w,
                                    actual.x,
                                    actual.y,
                                    actual.z,
                                    actual.w,
                                )
                            },
                        }
                    }
                    None => UiTestResult {
                        name: name.to_string(),
                        passed: false,
                        message: format!("'{test_id}' has no Vec4 widget value"),
                    },
                }
            }
            AssertKind::EventFired(event_match) => {
                let found = self.captured_events.iter().any(|event| match event_match {
                    EventMatch::ButtonClicked => matches!(event, UiEvent::ButtonClicked(_)),
                    EventMatch::SliderChanged => matches!(event, UiEvent::SliderChanged { .. }),
                    EventMatch::ToggleChanged => matches!(event, UiEvent::ToggleChanged { .. }),
                    EventMatch::TextInputSubmitted => {
                        matches!(event, UiEvent::TextInputSubmitted { .. })
                    }
                    EventMatch::TextInputChanged => {
                        matches!(event, UiEvent::TextInputChanged { .. })
                    }
                    EventMatch::CheckboxChanged => {
                        matches!(event, UiEvent::CheckboxChanged { .. })
                    }
                    EventMatch::DropdownChanged => {
                        matches!(event, UiEvent::DropdownChanged { .. })
                    }
                    EventMatch::RadioChanged => matches!(event, UiEvent::RadioChanged { .. }),
                    EventMatch::TabChanged => matches!(event, UiEvent::TabChanged { .. }),
                    EventMatch::DragValueChanged => {
                        matches!(event, UiEvent::DragValueChanged { .. })
                    }
                    EventMatch::MenuItemClicked => {
                        matches!(event, UiEvent::MenuItemClicked { .. })
                    }
                    EventMatch::ColorPickerChanged => {
                        matches!(event, UiEvent::ColorPickerChanged { .. })
                    }
                    EventMatch::SelectableLabelClicked => {
                        matches!(event, UiEvent::SelectableLabelClicked { .. })
                    }
                    EventMatch::ContextMenuItemClicked => {
                        matches!(event, UiEvent::ContextMenuItemClicked { .. })
                    }
                    EventMatch::TreeNodeSelected => {
                        matches!(event, UiEvent::TreeNodeSelected { .. })
                    }
                    EventMatch::TreeNodeToggled => {
                        matches!(event, UiEvent::TreeNodeToggled { .. })
                    }
                    EventMatch::ModalClosed => matches!(event, UiEvent::ModalClosed { .. }),
                    EventMatch::CommandPaletteExecuted => {
                        matches!(event, UiEvent::CommandPaletteExecuted { .. })
                    }
                    EventMatch::TileTabActivated => {
                        matches!(event, UiEvent::TileTabActivated { .. })
                    }
                    EventMatch::TileTabClosed => matches!(event, UiEvent::TileTabClosed { .. }),
                    EventMatch::TileSplitterMoved => {
                        matches!(event, UiEvent::TileSplitterMoved { .. })
                    }
                    EventMatch::CanvasClicked => matches!(event, UiEvent::CanvasClicked { .. }),
                    EventMatch::VirtualListItemClicked => {
                        matches!(event, UiEvent::VirtualListItemClicked { .. })
                    }
                    EventMatch::TextAreaChanged => {
                        matches!(event, UiEvent::TextAreaChanged { .. })
                    }
                    EventMatch::RichTextEditorChanged => {
                        matches!(event, UiEvent::RichTextEditorChanged { .. })
                    }
                    EventMatch::DataGridFilterChanged => {
                        matches!(event, UiEvent::DataGridFilterChanged { .. })
                    }
                    EventMatch::RangeSliderChanged => {
                        matches!(event, UiEvent::RangeSliderChanged { .. })
                    }
                    EventMatch::DataGridCellEdited => {
                        matches!(event, UiEvent::DataGridCellEdited { .. })
                    }
                    EventMatch::BreadcrumbClicked => {
                        matches!(event, UiEvent::BreadcrumbClicked { .. })
                    }
                    EventMatch::SplitterChanged => {
                        matches!(event, UiEvent::SplitterChanged { .. })
                    }
                    EventMatch::MultiSelectChanged => {
                        matches!(event, UiEvent::MultiSelectChanged { .. })
                    }
                    EventMatch::DatePickerChanged => {
                        matches!(event, UiEvent::DatePickerChanged { .. })
                    }
                    EventMatch::DragStarted => matches!(event, UiEvent::DragStarted { .. }),
                    EventMatch::DragDropped => matches!(event, UiEvent::DragDropped { .. }),
                    EventMatch::DragCancelled => matches!(event, UiEvent::DragCancelled { .. }),
                });
                UiTestResult {
                    name: name.to_string(),
                    passed: found,
                    message: if found {
                        format!("Event {event_match:?} was fired")
                    } else {
                        format!("Event {event_match:?} expected but not fired")
                    },
                }
            }
        }
    }

    fn get_text_content(&self, world: &World, test_id: &str) -> Option<String> {
        let entity = *world
            .resources
            .retained_ui
            .accessibility
            .test_id_map
            .get(test_id)?;
        let text_slot = ui_text_slot_for_entity(world, entity)?;
        world
            .resources
            .text
            .cache
            .get_text(text_slot)
            .map(|s| s.to_string())
    }

    fn record_action(&mut self, action: &TestAction, world: &World) {
        let (kind, target, position) = match action {
            TestAction::MoveMouse(p) => ("move_mouse", None, Some(*p)),
            TestAction::MoveMouseToEntity(id) => (
                "move_mouse_to_entity",
                Some(id.clone()),
                self.resolve_entity_center(world, id),
            ),
            TestAction::MousePress => ("mouse_press", None, None),
            TestAction::MouseRelease => ("mouse_release", None, None),
            TestAction::RightMousePress => ("right_mouse_press", None, None),
            TestAction::RightMouseRelease => ("right_mouse_release", None, None),
            TestAction::ClickAt(p) => ("click_at", None, Some(*p)),
            TestAction::ClickAtEntity(id) => (
                "click_at_entity",
                Some(id.clone()),
                self.resolve_entity_center(world, id),
            ),
            TestAction::RightClickAt(p) => ("right_click_at", None, Some(*p)),
            TestAction::RightClickAtEntity(id) => (
                "right_click_at_entity",
                Some(id.clone()),
                self.resolve_entity_center(world, id),
            ),
            TestAction::DragEntity(id, _) => ("drag_entity", Some(id.clone()), None),
            TestAction::SetSliderValue(id, _) => ("set_slider_value", Some(id.clone()), None),
            TestAction::SelectDropdownOption(id, _) => {
                ("select_dropdown_option", Some(id.clone()), None)
            }
            TestAction::SelectTab(id, _) => ("select_tab", Some(id.clone()), None),
            TestAction::TypeChars(_) => ("type_chars", None, None),
            TestAction::PressAndReleaseKey(_) => ("press_and_release_key", None, None),
            TestAction::KeyDown(_) => ("key_down", None, None),
            TestAction::KeyUp(_) => ("key_up", None, None),
            TestAction::Scroll(p) => ("scroll", None, Some(*p)),
            TestAction::Screenshot(path) => ("screenshot", Some(path.clone()), None),
            TestAction::Assert(name, _) => ("assert", Some(name.clone()), None),
            TestAction::Log(msg) => ("log", Some(msg.clone()), None),
            TestAction::Exit(_) => ("exit", None, None),
        };
        self.action_records.push(ActionRecord {
            frame: self.frame,
            kind: kind.to_string(),
            target,
            position: position.map(|p| [p.x, p.y]),
        });
    }

    pub(super) fn write_json_report(&self) {
        let Some(path) = &self.json_output_path else {
            return;
        };
        let report = TestReport {
            test_name: self.test_name.clone(),
            passed: self.all_passed(),
            summary: ReportSummary {
                total: self.results.len(),
                passed: self.results.iter().filter(|r| r.passed).count(),
                failed: self.results.iter().filter(|r| !r.passed).count(),
            },
            results: self
                .results
                .iter()
                .enumerate()
                .map(|(index, result)| ResultRecord {
                    name: result.name.clone(),
                    passed: result.passed,
                    message: result.message.clone(),
                    frame: self.result_frames.get(index).copied().unwrap_or(0),
                })
                .collect(),
            actions: self.action_records.clone(),
            screenshots: self.screenshots.clone(),
        };
        match serde_json::to_string_pretty(&report) {
            Ok(json) => {
                if let Err(error) = std::fs::write(path, &json) {
                    eprintln!("[UI_TEST] failed to write JSON report to {path}: {error}");
                }
            }
            Err(error) => {
                eprintln!("[UI_TEST] failed to serialize JSON report: {error}");
            }
        }
    }

    pub(super) fn print_results(&self) {
        self.write_json_report();
        eprintln!();
        eprintln!("=== UI Test: {} ===", self.test_name);
        let mut pass_count = 0;
        let mut fail_count = 0;
        for result in &self.results {
            if result.passed {
                pass_count += 1;
                eprintln!("  PASS: {} - {}", result.name, result.message);
            } else {
                fail_count += 1;
                eprintln!("  FAIL: {} - {}", result.name, result.message);
            }
        }
        eprintln!();
        eprintln!(
            "Results: {} passed, {} failed, {} total",
            pass_count,
            fail_count,
            self.results.len()
        );
        if fail_count == 0 {
            eprintln!("ALL TESTS PASSED");
        } else {
            eprintln!("TESTS FAILED");
        }
        eprintln!("========================");
        eprintln!();
    }
}

fn entity_for_test_id(world: &World, test_id: &str) -> Option<freecs::Entity> {
    world
        .resources
        .retained_ui
        .accessibility
        .test_id_map
        .get(test_id)
        .copied()
}

fn read_widget_f32(world: &World, test_id: &str) -> Option<f32> {
    let entity = entity_for_test_id(world, test_id)?;
    if let Some(data) = world.ui.get_ui_slider(entity) {
        return Some(data.value);
    }
    if let Some(data) = world.ui.get_ui_drag_value(entity) {
        return Some(data.value);
    }
    if let Some(data) = world.ui.get_ui_progress_bar(entity) {
        return Some(data.value);
    }
    None
}

fn read_widget_bool(world: &World, test_id: &str) -> Option<bool> {
    let entity = entity_for_test_id(world, test_id)?;
    if let Some(data) = world.ui.get_ui_toggle(entity) {
        return Some(data.value);
    }
    if let Some(data) = world.ui.get_ui_checkbox(entity) {
        return Some(data.value);
    }
    if let Some(data) = world.ui.get_ui_selectable_label(entity) {
        return Some(data.selected);
    }
    if let Some(data) = world.ui.get_ui_radio(entity) {
        return Some(data.selected);
    }
    if let Some(data) = world.ui.get_ui_collapsing_header(entity) {
        return Some(data.open);
    }
    None
}

fn read_widget_usize(world: &World, test_id: &str) -> Option<usize> {
    let entity = entity_for_test_id(world, test_id)?;
    if let Some(data) = world.ui.get_ui_dropdown(entity) {
        return Some(data.selected_index);
    }
    if let Some(data) = world.ui.get_ui_tab_bar(entity) {
        return Some(data.selected_tab);
    }
    None
}

fn read_widget_string(world: &World, test_id: &str) -> Option<String> {
    let entity = entity_for_test_id(world, test_id)?;
    if let Some(data) = world.ui.get_ui_text_input(entity) {
        return Some(data.text.clone());
    }
    if let Some(data) = world.ui.get_ui_text_area(entity) {
        return Some(data.text.clone());
    }
    if let Some(data) = world.ui.get_ui_rich_text_editor(entity) {
        return Some(data.text.clone());
    }
    None
}

fn read_widget_vec4(world: &World, test_id: &str) -> Option<Vec4> {
    let entity = entity_for_test_id(world, test_id)?;
    if let Some(data) = world.ui.get_ui_color_picker(entity) {
        return Some(data.color);
    }
    None
}
