//! Minimal app/runtime glue for RUIN application experiments.
//!
//! This crate is intentionally low-level. It is the substrate that a future proc-macro-driven
//! component system can expand to, not the final ergonomic authoring API.

extern crate self as ruin_app;

use std::any::{Any, TypeId, type_name};
use std::cell::{Cell as StdCell, RefCell};
use std::collections::HashMap;
use std::error::Error;
use std::future::Future;
use std::iter;
use std::marker::PhantomData;
use std::rc::Rc;

use ruin_reactivity::effect;
use ruin_runtime::queue_future;
use ruin_ui::{
    Border, Color, CursorIcon, DisplayItem, Edges, Element, ElementId, HitTarget, InteractionTree,
    KeyboardEvent, KeyboardEventKind, KeyboardKey, LayoutSnapshot, PlatformEvent, PointerButton,
    PointerEvent, PointerEventKind, PointerRouter, Quad, RoutedPointerEvent,
    RoutedPointerEventKind, ScrollbarStyle, TextFontFamily, TextSpan, TextSpanWeight, TextStyle,
    TextSystem, TextWrap, UiSize, WindowController, WindowSpec, WindowUpdate,
    layout_snapshot_with_text_system,
};
use ruin_ui_platform_wayland::start_wayland_ui;

pub use ResourceState::{Pending, Ready};
pub use ruin_app_proc_macros::{component, context_provider, view};

pub type Result<T> = std::result::Result<T, Box<dyn Error>>;

#[derive(Clone, Debug)]
pub struct Window {
    spec: WindowSpec,
}

impl Window {
    pub fn new() -> Self {
        Self {
            spec: WindowSpec::new("RUIN App"),
        }
    }

    pub fn title(mut self, title: impl Into<String>) -> Self {
        self.spec.title = title.into();
        self
    }

    pub fn app_id(mut self, app_id: impl Into<String>) -> Self {
        self.spec = self.spec.app_id(app_id);
        self
    }

    pub fn size(mut self, width: f32, height: f32) -> Self {
        self.spec = self.spec.requested_inner_size(UiSize::new(width, height));
        self
    }
}

impl Default for Window {
    fn default() -> Self {
        Self::new()
    }
}

pub struct App {
    window: Window,
}

impl App {
    pub fn new() -> Self {
        Self {
            window: Window::new(),
        }
    }

    pub fn window(mut self, window: Window) -> Self {
        self.window = window;
        self
    }

    pub fn mount<M: Mountable>(self, root: M) -> MountedApp<M> {
        MountedApp {
            window: self.window,
            root: Rc::new(root),
            render_state: Rc::new(RenderState::default()),
        }
    }
}

impl Default for App {
    fn default() -> Self {
        Self::new()
    }
}

#[doc(hidden)]
pub fn __render_mountable_for_test<M: Mountable>(mountable: &M) -> View {
    render_with_context(Rc::new(RenderState::default()), || mountable.render()).view
}

pub trait Mountable: 'static {
    fn render(&self) -> View;
}

pub trait Component: 'static {
    type Builder;

    fn builder() -> Self::Builder;
    fn render(&self) -> View;
}

pub trait ContextKey: 'static {
    type Value: Clone + 'static;
}

impl<T: Component> Mountable for T {
    fn render(&self) -> View {
        Component::render(self)
    }
}

impl Mountable for View {
    fn render(&self) -> View {
        self.clone()
    }
}

impl Mountable for Element {
    fn render(&self) -> View {
        View::from_element(self.clone())
    }
}

pub struct MountedApp<M: Mountable> {
    window: Window,
    root: Rc<M>,
    render_state: Rc<RenderState>,
}

impl<M: Mountable> MountedApp<M> {
    pub async fn run(self) -> Result<()> {
        let MountedApp {
            window: app_window,
            root,
            render_state,
        } = self;

        let mut ui = start_wayland_ui();
        let window = ui.create_window(app_window.spec.clone())?;
        let initial_viewport = app_window
            .spec
            .requested_inner_size
            .unwrap_or(UiSize::new(960.0, 640.0));

        let viewport = ruin_reactivity::cell(initial_viewport);
        let scene_version = StdCell::new(0_u64);
        let text_system = Rc::new(RefCell::new(TextSystem::new()));
        let interaction_tree = Rc::new(RefCell::new(None::<InteractionTree>));
        let bindings = Rc::new(RefCell::new(EventBindings::default()));
        let shortcuts = Rc::new(RefCell::new(Vec::<ShortcutBinding>::new()));
        let current_title = Rc::new(RefCell::new(None::<String>));
        let mut input_state = InputState::new();
        let mut pointer_router = PointerRouter::new();

        let _scene_effect = effect({
            let window = window.clone();
            let viewport = viewport.clone();
            let text_system = Rc::clone(&text_system);
            let interaction_tree = Rc::clone(&interaction_tree);
            let bindings = Rc::clone(&bindings);
            let shortcuts = Rc::clone(&shortcuts);
            let current_title = Rc::clone(&current_title);
            let root = Rc::clone(&root);
            let render_state = Rc::clone(&render_state);
            let text_selection = Rc::clone(&input_state.text_selection);
            move || {
                let viewport = viewport.get();
                let version = scene_version.get().wrapping_add(1);
                scene_version.set(version);
                let _ = text_selection.version.get();

                let render_output = render_with_context(Rc::clone(&render_state), || root.render());

                if render_output.side_effects.window_title != *current_title.borrow() {
                    if let Some(title) = &render_output.side_effects.window_title {
                        window
                            .update(WindowUpdate::new().title(title.clone()))
                            .expect("window should remain alive while the app is running");
                    }
                    *current_title.borrow_mut() = render_output.side_effects.window_title.clone();
                }

                let LayoutSnapshot {
                    mut scene,
                    interaction_tree: next_interaction_tree,
                } = layout_snapshot_with_text_system(
                    version,
                    viewport,
                    render_output.view.element(),
                    &mut text_system.borrow_mut(),
                );

                apply_text_selection_overlay(&mut scene, *text_selection.selection.borrow());

                *interaction_tree.borrow_mut() = Some(next_interaction_tree);
                *bindings.borrow_mut() = render_output.view.bindings;
                *shortcuts.borrow_mut() = render_output.side_effects.shortcuts.clone();
                window
                    .replace_scene(scene)
                    .expect("window should remain alive while the app is running");
            }
        });

        loop {
            let Some(event) = ui.next_event().await else {
                break;
            };

            for event in iter::once(event).chain(ui.take_pending_events()) {
                match event {
                    PlatformEvent::Configured {
                        window_id,
                        configuration,
                    } if window_id == window.id() => {
                        let _ = viewport.set(configuration.actual_inner_size);
                    }
                    PlatformEvent::Pointer { window_id, event } if window_id == window.id() => {
                        Self::handle_pointer_event(
                            &window,
                            &interaction_tree,
                            &bindings,
                            &mut pointer_router,
                            &mut input_state,
                            event,
                        )?;
                    }
                    PlatformEvent::Keyboard { window_id, event } if window_id == window.id() => {
                        Self::handle_keyboard_event(
                            &interaction_tree,
                            &bindings,
                            &shortcuts,
                            &input_state,
                            event,
                        )?;
                    }
                    PlatformEvent::CloseRequested { window_id } if window_id == window.id() => {
                        let _ = window.update(WindowUpdate::new().open(false));
                    }
                    PlatformEvent::Closed { window_id } if window_id == window.id() => {
                        ui.shutdown()?;
                        return Ok(());
                    }
                    _ => {}
                }
            }
        }

        ui.shutdown()?;
        Ok(())
    }

    fn handle_pointer_event(
        window: &WindowController,
        interaction_tree: &RefCell<Option<InteractionTree>>,
        bindings: &RefCell<EventBindings>,
        pointer_router: &mut PointerRouter,
        input_state: &mut InputState,
        event: PointerEvent,
    ) -> Result<()> {
        if matches!(
            event.kind,
            PointerEventKind::Down {
                button: PointerButton::Primary | PointerButton::Middle,
            }
        ) {
            let interaction_tree = interaction_tree.borrow();
            if let Some(interaction_tree) = interaction_tree.as_ref() {
                input_state.focused_element = focused_element_for_pointer(interaction_tree, &event);
            }
        }

        let routed = {
            let interaction_tree = interaction_tree.borrow();
            let Some(interaction_tree) = interaction_tree.as_ref() else {
                return Ok(());
            };
            pointer_router.route(interaction_tree, event)
        };

        {
            let interaction_tree = interaction_tree.borrow();
            let Some(interaction_tree) = interaction_tree.as_ref() else {
                return Ok(());
            };
            let hovered_targets = pointer_router.hovered_targets();
            for routed_event in &routed {
                bindings
                    .borrow()
                    .dispatch(routed_event, interaction_tree, hovered_targets);
                Self::handle_text_selection_event(
                    window,
                    interaction_tree,
                    routed_event,
                    &input_state.text_selection,
                )?;
            }
        }

        let next_cursor = pointer_router
            .hovered_targets()
            .last()
            .map(|target| target.cursor)
            .unwrap_or(CursorIcon::Default);
        if next_cursor != input_state.current_cursor {
            input_state.current_cursor = next_cursor;
            window.set_cursor_icon(next_cursor)?;
        }

        Ok(())
    }

    fn handle_keyboard_event(
        interaction_tree: &RefCell<Option<InteractionTree>>,
        bindings: &RefCell<EventBindings>,
        shortcuts: &RefCell<Vec<ShortcutBinding>>,
        input_state: &InputState,
        event: KeyboardEvent,
    ) -> Result<()> {
        let interaction_tree = interaction_tree.borrow();
        let Some(interaction_tree) = interaction_tree.as_ref() else {
            return Ok(());
        };
        let shortcut_bindings = shortcuts.borrow().clone();
        let mut consumed = false;
        for shortcut in shortcut_bindings {
            if shortcut.matches(&event, input_state.focused_element, interaction_tree) {
                shortcut.trigger(interaction_tree);
                consumed = true;
            }
        }
        if consumed {
            return Ok(());
        }
        bindings
            .borrow()
            .dispatch_key(input_state.focused_element, &event, interaction_tree);
        Ok(())
    }

    fn handle_text_selection_event(
        window: &WindowController,
        interaction_tree: &InteractionTree,
        event: &RoutedPointerEvent,
        text_selection: &TextSelectionState,
    ) -> Result<()> {
        let mut selection_changed = false;

        match event.kind {
            RoutedPointerEventKind::Down {
                button: PointerButton::Primary,
            } => {
                if let Some(hit) = interaction_tree.text_hit_test(event.position)
                    && let Some(element_id) = hit.target.element_id
                {
                    let next = Some(TextSelection {
                        element_id,
                        anchor: hit.byte_offset,
                        focus: hit.byte_offset,
                    });
                    if *text_selection.selection.borrow() != next {
                        *text_selection.selection.borrow_mut() = next;
                        selection_changed = true;
                    }
                    *text_selection.drag.borrow_mut() = Some(TextSelectionDrag {
                        element_id,
                        anchor: hit.byte_offset,
                    });
                } else {
                    selection_changed = text_selection.selection.borrow_mut().take().is_some();
                    let _ = text_selection.drag.borrow_mut().take();
                }
            }
            RoutedPointerEventKind::Move => {
                let Some(drag) = *text_selection.drag.borrow() else {
                    return Ok(());
                };
                let Some(text) = interaction_tree.text_for_element(drag.element_id) else {
                    return Ok(());
                };
                let next = Some(TextSelection {
                    element_id: drag.element_id,
                    anchor: drag.anchor,
                    focus: text.byte_offset_for_position(event.position),
                });
                if *text_selection.selection.borrow() != next {
                    *text_selection.selection.borrow_mut() = next;
                    selection_changed = true;
                }
            }
            RoutedPointerEventKind::Up {
                button: PointerButton::Primary,
            } => {
                if text_selection.drag.borrow_mut().take().is_some() {
                    selection_changed = true;
                }
            }
            _ => {}
        }

        if selection_changed {
            text_selection.version.update(|value| *value += 1);
            sync_primary_selection(window, interaction_tree, *text_selection.selection.borrow())?;
        }

        Ok(())
    }
}

#[derive(Clone, Default)]
pub struct View {
    element: Element,
    bindings: EventBindings,
}

impl View {
    pub fn from_element(element: Element) -> Self {
        Self {
            element,
            bindings: EventBindings::default(),
        }
    }

    pub fn element(&self) -> &Element {
        &self.element
    }

    fn with_press_handler(mut self, element_id: ElementId, handler: PressHandler) -> Self {
        self.bindings.on_press.insert(element_id, handler);
        self
    }

    fn with_scroll_handler(mut self, element_id: ElementId, handler: ScrollHandler) -> Self {
        self.bindings.on_scroll.insert(element_id, handler);
        self
    }

    fn with_key_handler(mut self, element_id: ElementId, handler: KeyHandler) -> Self {
        self.bindings.on_key.insert(element_id, handler);
        self
    }

    fn from_container(element: Element, children: Vec<View>) -> Self {
        let mut composed = Self::from_element(element);
        let mut element = composed.element;

        for child in children {
            element = element.child(child.element);
            composed.bindings.extend(child.bindings);
        }

        composed.element = element;
        composed
    }
}

impl From<Element> for View {
    fn from(element: Element) -> Self {
        Self::from_element(element)
    }
}

pub trait IntoView {
    fn into_view(self) -> View;
}

impl IntoView for View {
    fn into_view(self) -> View {
        self
    }
}

impl IntoView for Element {
    fn into_view(self) -> View {
        View::from_element(self)
    }
}

impl<T: Component> IntoView for T {
    fn into_view(self) -> View {
        self.render()
    }
}

pub trait Children {
    fn into_views(self) -> Vec<View>;
}

#[derive(Clone, Default)]
pub struct ChildViews(Vec<View>);

impl ChildViews {
    pub fn from_children(children: impl Children) -> Self {
        Self(children.into_views())
    }

    pub fn into_vec(self) -> Vec<View> {
        self.0
    }
}

impl Children for () {
    fn into_views(self) -> Vec<View> {
        Vec::new()
    }
}

impl<T: IntoView> Children for T {
    fn into_views(self) -> Vec<View> {
        vec![self.into_view()]
    }
}

impl<T: IntoView> Children for Vec<T> {
    fn into_views(self) -> Vec<View> {
        self.into_iter().map(IntoView::into_view).collect()
    }
}

impl Children for ChildViews {
    fn into_views(self) -> Vec<View> {
        self.0
    }
}

macro_rules! impl_children_tuple {
    ($($name:ident),+ $(,)?) => {
        #[allow(non_camel_case_types)]
        impl<$($name: IntoView),+> Children for ($($name,)+) {
            fn into_views(self) -> Vec<View> {
                let ($($name,)+) = self;
                vec![$($name.into_view(),)+]
            }
        }
    };
}

impl_children_tuple!(a, b);
impl_children_tuple!(a, b, c);
impl_children_tuple!(a, b, c, d);
impl_children_tuple!(a, b, c, d, e);
impl_children_tuple!(a, b, c, d, e, f);
impl_children_tuple!(a, b, c, d, e, f, g);
impl_children_tuple!(a, b, c, d, e, f, g, h);

pub trait TextChildren {
    fn into_text(self) -> String;
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct TextValue(String);

impl TextValue {
    pub fn from_text(children: impl TextChildren) -> Self {
        Self(children.into_text())
    }

    pub fn into_string(self) -> String {
        self.0
    }
}

impl TextChildren for TextValue {
    fn into_text(self) -> String {
        self.0
    }
}

impl TextChildren for &'static str {
    fn into_text(self) -> String {
        self.to_string()
    }
}

impl TextChildren for String {
    fn into_text(self) -> String {
        self
    }
}

impl TextChildren for i32 {
    fn into_text(self) -> String {
        self.to_string()
    }
}

impl TextChildren for i64 {
    fn into_text(self) -> String {
        self.to_string()
    }
}

impl TextChildren for usize {
    fn into_text(self) -> String {
        self.to_string()
    }
}

impl TextChildren for u64 {
    fn into_text(self) -> String {
        self.to_string()
    }
}

impl TextChildren for f32 {
    fn into_text(self) -> String {
        self.to_string()
    }
}

impl<T: std::fmt::Display + 'static> TextChildren for Signal<T> {
    fn into_text(self) -> String {
        self.with(|value| value.to_string())
    }
}

impl<T: std::fmt::Display + 'static> TextChildren for Memo<T> {
    fn into_text(self) -> String {
        self.with(|value| value.to_string())
    }
}

macro_rules! impl_text_children_tuple {
    ($($name:ident),+ $(,)?) => {
        #[allow(non_camel_case_types)]
        impl<$($name: TextChildren),+> TextChildren for ($($name,)+) {
            fn into_text(self) -> String {
                let ($($name,)+) = self;
                let mut text = String::new();
                $(text.push_str(&$name.into_text());)+
                text
            }
        }
    };
}

impl_text_children_tuple!(a, b);
impl_text_children_tuple!(a, b, c);
impl_text_children_tuple!(a, b, c, d);
impl_text_children_tuple!(a, b, c, d, e);
impl_text_children_tuple!(a, b, c, d, e, f);

pub trait IntoEdges {
    fn into_edges(self) -> Edges;
}

impl IntoEdges for Edges {
    fn into_edges(self) -> Edges {
        self
    }
}

impl IntoEdges for f32 {
    fn into_edges(self) -> Edges {
        Edges::all(self)
    }
}

pub trait IntoBorder {
    fn into_border(self) -> Border;
}

impl IntoBorder for Border {
    fn into_border(self) -> Border {
        self
    }
}

impl IntoBorder for (f32, Color) {
    fn into_border(self) -> Border {
        Border::new(self.0, self.1)
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TextRole {
    Body,
    Heading(u8),
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum FontWeight {
    Normal,
    Medium,
    Semibold,
    Bold,
}

impl FontWeight {
    const fn to_text_span_weight(self) -> TextSpanWeight {
        match self {
            Self::Normal => TextSpanWeight::Normal,
            Self::Medium => TextSpanWeight::Medium,
            Self::Semibold => TextSpanWeight::Semibold,
            Self::Bold => TextSpanWeight::Bold,
        }
    }
}

pub mod colors {
    use ruin_ui::Color;

    pub const fn text() -> Color {
        Color::rgb(0xFF, 0xFF, 0xFF)
    }

    pub const fn muted() -> Color {
        Color::rgb(0xB6, 0xC2, 0xD9)
    }

    pub const fn danger() -> Color {
        Color::rgb(0xFF, 0x7B, 0x72)
    }
}

pub mod surfaces {
    use ruin_ui::Color;

    pub const fn canvas() -> Color {
        Color::rgb(0x0F, 0x16, 0x25)
    }

    pub const fn raised() -> Color {
        Color::rgb(0x1B, 0x26, 0x3D)
    }

    pub const fn interactive() -> Color {
        Color::rgb(0x2B, 0x3A, 0x67)
    }

    pub const fn interactive_muted() -> Color {
        Color::rgb(0x3D, 0x4B, 0x72)
    }
}

pub fn column() -> ContainerBuilder {
    ContainerBuilder {
        element: Element::column().background(surfaces::canvas()),
        widget_ref: None,
    }
}

pub fn row() -> ContainerBuilder {
    ContainerBuilder {
        element: Element::row(),
        widget_ref: None,
    }
}

pub fn block() -> ContainerBuilder {
    ContainerBuilder {
        element: Element::column(),
        widget_ref: None,
    }
}

pub fn text() -> TextBuilder {
    TextBuilder::default()
}

pub fn button() -> ButtonBuilder {
    ButtonBuilder::default()
}

pub fn scroll_box() -> ScrollBoxBuilder {
    ScrollBoxBuilder {
        element: Element::scroll_box(0.0),
        offset_y: None,
        drag: with_hook_slot(|| Signal::new(None::<ScrollbarDrag>), |drag| drag.clone()),
        widget_ref: None,
    }
}

pub struct ContainerBuilder {
    element: Element,
    widget_ref: Option<Signal<Option<ElementId>>>,
}

impl ContainerBuilder {
    pub fn gap(mut self, gap: f32) -> Self {
        self.element = self.element.gap(gap);
        self
    }

    pub fn padding(mut self, padding: impl IntoEdges) -> Self {
        self.element = self.element.padding(padding.into_edges());
        self
    }

    pub fn background(mut self, color: Color) -> Self {
        self.element = self.element.background(color);
        self
    }

    pub fn border(mut self, border: impl IntoBorder) -> Self {
        let border = border.into_border();
        self.element = self.element.border(border.width, border.color);
        self
    }

    pub fn border_radius(mut self, radius: f32) -> Self {
        self.element = self.element.corner_radius(radius);
        self
    }

    pub fn width(mut self, width: f32) -> Self {
        self.element = self.element.width(width);
        self
    }

    pub fn height(mut self, height: f32) -> Self {
        self.element = self.element.height(height);
        self
    }

    pub fn flex(mut self, flex: f32) -> Self {
        self.element = self.element.flex(flex);
        self
    }

    pub fn widget_ref<T>(mut self, widget_ref: WidgetRef<T>) -> Self {
        self.widget_ref = Some(widget_ref.element_id.clone());
        self
    }

    pub fn children(mut self, children: impl Children) -> View {
        if let Some(widget_ref) = &self.widget_ref {
            let element_id = allocate_element_id();
            self.element = self.element.id(element_id);
            let _ = widget_ref.set(Some(element_id));
        }
        View::from_container(self.element, children.into_views())
    }
}

pub struct ScrollBoxBuilder {
    element: Element,
    offset_y: Option<Signal<f32>>,
    drag: Signal<Option<ScrollbarDrag>>,
    widget_ref: Option<Signal<Option<ElementId>>>,
}

impl ScrollBoxBuilder {
    pub fn padding(mut self, padding: impl IntoEdges) -> Self {
        self.element = self.element.padding(padding.into_edges());
        self
    }

    pub fn background(mut self, color: Color) -> Self {
        self.element = self.element.background(color);
        self
    }

    pub fn border(mut self, border: impl IntoBorder) -> Self {
        let border = border.into_border();
        self.element = self.element.border(border.width, border.color);
        self
    }

    pub fn border_radius(mut self, radius: f32) -> Self {
        self.element = self.element.corner_radius(radius);
        self
    }

    pub fn width(mut self, width: f32) -> Self {
        self.element = self.element.width(width);
        self
    }

    pub fn height(mut self, height: f32) -> Self {
        self.element = self.element.height(height);
        self
    }

    pub fn flex(mut self, flex: f32) -> Self {
        self.element = self.element.flex(flex);
        self
    }

    pub fn scrollbar_style(mut self, style: ScrollbarStyle) -> Self {
        self.element = self.element.scrollbar_style(style);
        self
    }

    pub fn offset_y(mut self, offset_y: Signal<f32>) -> Self {
        self.element = self.element.scroll_offset(offset_y.get());
        self.offset_y = Some(offset_y);
        self
    }

    pub fn widget_ref<T>(mut self, widget_ref: WidgetRef<T>) -> Self {
        self.widget_ref = Some(widget_ref.element_id.clone());
        self
    }

    pub fn children(mut self, children: impl Children) -> View {
        let element_id = allocate_element_id();
        self.element = self.element.id(element_id);
        if let Some(widget_ref) = &self.widget_ref {
            let _ = widget_ref.set(Some(element_id));
        }

        let mut view = View::from_container(self.element, children.into_views());
        if let Some(offset_y) = self.offset_y {
            let drag = self.drag;
            let offset_y_for_pointer = offset_y.clone();
            let offset_y_for_keys = offset_y.clone();
            view = view.with_scroll_handler(
                element_id,
                Rc::new(move |event, interaction_tree| {
                    let Some(metrics) = interaction_tree.scroll_metrics_for_element(element_id)
                    else {
                        return;
                    };

                    match event.kind {
                        RoutedPointerEventKind::Scroll { delta } => {
                            offset_y_for_pointer.update(|value| {
                                *value =
                                    clamp_scroll_offset(*value + delta.y, metrics.max_offset_y);
                            });
                        }
                        RoutedPointerEventKind::Down {
                            button: PointerButton::Primary,
                        } => {
                            let Some(thumb_rect) = metrics.scrollbar_thumb else {
                                return;
                            };
                            if !thumb_rect.contains(event.position) {
                                return;
                            }
                            let _ = drag.set(Some(ScrollbarDrag {
                                start_pointer_y: event.position.y,
                                start_offset_y: offset_y_for_pointer.get(),
                            }));
                        }
                        RoutedPointerEventKind::Move => {
                            let Some(drag_state) = drag.get() else {
                                return;
                            };
                            let Some(track_rect) = metrics.scrollbar_track else {
                                return;
                            };
                            let Some(thumb_rect) = metrics.scrollbar_thumb else {
                                return;
                            };
                            let thumb_travel =
                                (track_rect.size.height - thumb_rect.size.height).max(0.0);
                            if thumb_travel <= 0.0 || metrics.max_offset_y <= 0.0 {
                                return;
                            }
                            let pointer_delta = event.position.y - drag_state.start_pointer_y;
                            let next_offset = drag_state.start_offset_y
                                + pointer_delta * (metrics.max_offset_y / thumb_travel);
                            offset_y_for_pointer.update(|value| {
                                *value = clamp_scroll_offset(next_offset, metrics.max_offset_y);
                            });
                        }
                        RoutedPointerEventKind::Up {
                            button: PointerButton::Primary,
                        } => {
                            let _ = drag.set(None);
                        }
                        _ => {}
                    }
                }),
            );
            view = view.with_key_handler(
                element_id,
                Rc::new(move |event, interaction_tree| {
                    if event.kind != KeyboardEventKind::Pressed
                        || event.modifiers.control
                        || event.modifiers.alt
                        || event.modifiers.super_key
                    {
                        return false;
                    }

                    let Some(metrics) = interaction_tree.scroll_metrics_for_element(element_id)
                    else {
                        return false;
                    };

                    let line_step = (metrics.viewport_rect.size.height * 0.12).clamp(28.0, 52.0);
                    let next_offset = match event.key {
                        KeyboardKey::ArrowUp => offset_y_for_keys.get() - line_step,
                        KeyboardKey::ArrowDown => offset_y_for_keys.get() + line_step,
                        KeyboardKey::Home => 0.0,
                        KeyboardKey::End => metrics.max_offset_y,
                        _ => return false,
                    };
                    let next_offset = clamp_scroll_offset(next_offset, metrics.max_offset_y);
                    let changed = (offset_y_for_keys.get() - next_offset).abs() > f32::EPSILON;
                    if changed {
                        let _ = offset_y_for_keys.set(next_offset);
                    }
                    changed
                }),
            );
        }
        view
    }
}

#[derive(Default)]
pub struct TextBuilder {
    role: Option<TextRole>,
    size: Option<f32>,
    weight: Option<FontWeight>,
    color: Option<Color>,
    font_family: Option<TextFontFamily>,
    wrap: Option<TextWrap>,
}

impl TextBuilder {
    pub fn role(mut self, role: TextRole) -> Self {
        self.role = Some(role);
        self
    }

    pub fn size(mut self, size: f32) -> Self {
        self.size = Some(size);
        self
    }

    pub fn weight(mut self, weight: FontWeight) -> Self {
        self.weight = Some(weight);
        self
    }

    pub fn color(mut self, color: Color) -> Self {
        self.color = Some(color);
        self
    }

    pub fn font_family(mut self, family: TextFontFamily) -> Self {
        self.font_family = Some(family);
        self
    }

    pub fn wrap(mut self, wrap: TextWrap) -> Self {
        self.wrap = Some(wrap);
        self
    }

    pub fn children(self, children: impl TextChildren) -> View {
        let size = self
            .size
            .unwrap_or_else(|| match self.role.unwrap_or(TextRole::Body) {
                TextRole::Body => 18.0,
                TextRole::Heading(1) => 32.0,
                TextRole::Heading(2) => 28.0,
                TextRole::Heading(_) => 24.0,
            });
        let color = self.color.unwrap_or(colors::text());
        let weight = self
            .weight
            .unwrap_or_else(|| match self.role.unwrap_or(TextRole::Body) {
                TextRole::Body => FontWeight::Normal,
                TextRole::Heading(_) => FontWeight::Semibold,
            });

        let mut style = TextStyle::new(size, color)
            .with_line_height(size * 1.2)
            .with_wrap(self.wrap.unwrap_or(TextWrap::None));
        if let Some(font_family) = self.font_family {
            style = style.with_font_family(font_family);
        }

        let span = TextSpan::new(children.into_text())
            .color(color)
            .weight(weight.to_text_span_weight());
        View::from_element(Element::spans([span], style).id(allocate_element_id()))
    }
}

#[derive(Default)]
pub struct ButtonBuilder {
    on_press: Option<PressHandler>,
}

impl ButtonBuilder {
    pub fn on_press(mut self, handler: impl Fn(&RoutedPointerEvent) + 'static) -> Self {
        self.on_press = Some(Rc::new(handler));
        self
    }

    pub fn children(self, children: impl TextChildren) -> View {
        let id = allocate_element_id();
        let label = children.into_text();
        let view = View::from_element(
            Element::column()
                .id(id)
                .padding(Edges::symmetric(14.0, 10.0))
                .background(surfaces::interactive())
                .corner_radius(10.0)
                .cursor(CursorIcon::Pointer)
                .focusable(true)
                .child(
                    Element::spans(
                        [TextSpan::new(label).weight(TextSpanWeight::Medium)],
                        TextStyle::new(18.0, colors::text()).with_line_height(21.6),
                    )
                    .pointer_events(false)
                    .focusable(false),
                ),
        );

        match self.on_press {
            Some(handler) => view.with_press_handler(id, handler),
            None => view,
        }
    }
}

pub struct Signal<T> {
    inner: Rc<SignalInner<T>>,
}

impl<T> Clone for Signal<T> {
    fn clone(&self) -> Self {
        Self {
            inner: Rc::clone(&self.inner),
        }
    }
}

impl<T: 'static> Signal<T> {
    fn new(initial: T) -> Self {
        Self {
            inner: Rc::new(SignalInner {
                cell: ruin_reactivity::cell(initial),
            }),
        }
    }

    pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
        self.inner.cell.with(f)
    }

    pub fn replace(&self, value: T) -> T {
        self.inner.cell.replace(value)
    }

    pub fn update<R>(&self, f: impl FnOnce(&mut T) -> R) -> R {
        self.inner.cell.update(f)
    }
}

impl<T: Clone + 'static> Signal<T> {
    pub fn get(&self) -> T {
        self.inner.cell.get()
    }
}

impl<T: PartialEq + 'static> Signal<T> {
    pub fn set(&self, value: T) -> Option<T> {
        self.inner.cell.set(value)
    }
}

pub struct Memo<T> {
    compute: Rc<dyn Fn() -> T>,
}

impl<T> Clone for Memo<T> {
    fn clone(&self) -> Self {
        Self {
            compute: Rc::clone(&self.compute),
        }
    }
}

impl<T> Memo<T> {
    pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
        let value = (self.compute)();
        f(&value)
    }
}

impl<T: Clone> Memo<T> {
    pub fn get(&self) -> T {
        (self.compute)()
    }
}

#[derive(Clone)]
struct ContextEntry {
    key: TypeId,
    value: Rc<dyn Any>,
}

impl ContextEntry {
    fn new<C: ContextKey>(value: C::Value) -> Self {
        Self {
            key: TypeId::of::<C>(),
            value: Rc::new(value),
        }
    }
}

pub fn use_signal<T: 'static>(initial: impl FnOnce() -> T) -> Signal<T> {
    with_hook_slot(|| Signal::new(initial()), |signal| signal.clone())
}

pub fn use_context<C: ContextKey>() -> C::Value {
    with_render_context_state(|context| {
        context
            .context_entries
            .iter()
            .rev()
            .find_map(|entry| {
                (entry.key == TypeId::of::<C>())
                    .then(|| entry.value.downcast_ref::<C::Value>())
                    .flatten()
                    .cloned()
            })
            .unwrap_or_else(|| {
                panic!(
                    "missing context provider for {} while rendering",
                    type_name::<C>()
                )
            })
    })
}

pub fn provide<C: ContextKey>(value: C::Value, render: impl FnOnce() -> View) -> View {
    with_render_context_state(|context| {
        let mut context_entries = (*context.context_entries).clone();
        context_entries.push(ContextEntry::new::<C>(value));
        with_render_context(
            context.with_context_entries(Rc::new(context_entries)),
            render,
        )
    })
}

pub fn use_memo<T: 'static>(compute: impl Fn() -> T + 'static) -> Memo<T> {
    let compute: Rc<RefCell<Box<dyn Fn() -> T>>> =
        Rc::new(RefCell::new(Box::new(compute) as Box<dyn Fn() -> T>));
    with_hook_slot(
        {
            let compute = Rc::clone(&compute);
            move || MemoSlot::new(compute)
        },
        |slot: &mut MemoSlot<T>| {
            slot.replace_compute(Rc::clone(&compute));
            slot.handle.clone()
        },
    )
}

pub fn use_effect(effect: impl Fn() + 'static) {
    with_hook_slot(|| ruin_reactivity::effect(effect), |_| ());
}

pub fn use_window_title(compute: impl FnOnce() -> String) {
    with_render_context_state(|context| {
        context.side_effects.borrow_mut().window_title = Some(compute());
    });
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Key {
    Character(char),
    Enter,
    ArrowUp,
    ArrowDown,
    Home,
    End,
}

impl Key {
    fn matches(&self, event: &KeyboardEvent) -> bool {
        match (self, &event.key) {
            (Self::Character(expected), KeyboardKey::Character(actual)) => actual
                .chars()
                .next()
                .is_some_and(|actual| actual.eq_ignore_ascii_case(expected)),
            (Self::Enter, KeyboardKey::Enter) => true,
            (Self::ArrowUp, KeyboardKey::ArrowUp) => true,
            (Self::ArrowDown, KeyboardKey::ArrowDown) => true,
            (Self::Home, KeyboardKey::Home) => true,
            (Self::End, KeyboardKey::End) => true,
            _ => false,
        }
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Shortcut {
    key: Key,
    control: bool,
    shift: bool,
    alt: bool,
    super_key: bool,
}

impl Shortcut {
    pub fn new(key: Key) -> Self {
        Self {
            key,
            control: false,
            shift: false,
            alt: false,
            super_key: false,
        }
    }

    pub fn with_ctrl(mut self) -> Self {
        self.control = true;
        self
    }

    pub fn with_shift(mut self) -> Self {
        self.shift = true;
        self
    }

    fn matches(&self, event: &KeyboardEvent) -> bool {
        event.kind == KeyboardEventKind::Pressed
            && self.key.matches(event)
            && event.modifiers.control == self.control
            && event.modifiers.shift == self.shift
            && event.modifiers.alt == self.alt
            && event.modifiers.super_key == self.super_key
    }
}

#[derive(Clone)]
pub struct FocusScope {
    element_id: Signal<Option<ElementId>>,
}

#[derive(Clone)]
pub enum ShortcutScope {
    Application,
    FocusedWithin(FocusScope),
}

impl ShortcutScope {
    fn matches(
        &self,
        focused_element: Option<ElementId>,
        interaction_tree: &InteractionTree,
    ) -> bool {
        match self {
            Self::Application => true,
            Self::FocusedWithin(scope) => {
                let Some(scope_element) = scope.element_id.get() else {
                    return false;
                };
                let Some(focused_element) = focused_element else {
                    return false;
                };
                element_contains_element(interaction_tree, scope_element, focused_element)
            }
        }
    }
}

pub struct WidgetRef<T> {
    element_id: Signal<Option<ElementId>>,
    _marker: PhantomData<fn() -> T>,
}

impl<T> Clone for WidgetRef<T> {
    fn clone(&self) -> Self {
        Self {
            element_id: self.element_id.clone(),
            _marker: PhantomData,
        }
    }
}

impl<T> WidgetRef<T> {
    fn new() -> Self {
        Self {
            element_id: Signal::new(None),
            _marker: PhantomData,
        }
    }

    pub fn element_id(&self) -> Option<ElementId> {
        self.element_id.get()
    }

    pub fn focus_scope(&self) -> FocusScope {
        FocusScope {
            element_id: self.element_id.clone(),
        }
    }
}

pub struct ScrollBoxWidget;
pub struct BlockWidget;

pub fn use_widget_ref<T: 'static>() -> WidgetRef<T> {
    with_hook_slot(WidgetRef::new, |widget_ref: &mut WidgetRef<T>| {
        widget_ref.clone()
    })
}

pub fn use_shortcut(shortcut: Shortcut, scope: ShortcutScope, action: impl Fn() + 'static) {
    use_shortcut_with_context(shortcut, scope, move |_| action());
}

pub fn use_shortcut_with_context(
    shortcut: Shortcut,
    scope: ShortcutScope,
    action: impl Fn(&InteractionTree) + 'static,
) {
    with_render_context_state(|context| {
        context
            .side_effects
            .borrow_mut()
            .shortcuts
            .push(ShortcutBinding {
                shortcut,
                scope,
                action: Rc::new(action),
            });
    });
}

#[derive(Clone)]
pub struct Resource<T, E> {
    state: Signal<ResourceState<T, E>>,
}

impl<T: Clone + 'static, E: Clone + 'static> Resource<T, E> {
    pub fn read(&self) -> ResourceState<T, E> {
        self.state.get()
    }
}

#[derive(Clone)]
pub enum ResourceState<T, E> {
    Pending,
    Ready(std::result::Result<T, E>),
}

pub fn use_resource<T, E, Fut, F>(factory: F) -> Resource<T, E>
where
    T: Clone + 'static,
    E: Clone + 'static,
    Fut: Future<Output = std::result::Result<T, E>> + 'static,
    F: Fn() -> Fut + 'static,
{
    with_hook_slot(
        || {
            let resource = Resource {
                state: Signal::new(ResourceState::Pending),
            };
            let generation = Rc::new(StdCell::new(0_u64));
            let _effect = ruin_reactivity::effect({
                let resource = resource.clone();
                let generation = Rc::clone(&generation);
                move || {
                    let next_generation = generation.get().wrapping_add(1);
                    generation.set(next_generation);
                    let _ = resource.state.replace(ResourceState::Pending);
                    let resource = resource.clone();
                    let future = factory();
                    let generation = Rc::clone(&generation);
                    std::mem::drop(queue_future(async move {
                        let result = future.await;
                        if generation.get() == next_generation {
                            let _ = resource.state.replace(ResourceState::Ready(result));
                        }
                    }));
                }
            });
            ResourceSlot { resource, _effect }
        },
        |slot: &mut ResourceSlot<T, E>| slot.resource.clone(),
    )
}

#[derive(Default)]
struct RenderState {
    hooks: RefCell<Vec<Box<dyn Any>>>,
    element_ids: RefCell<Vec<ElementId>>,
    next_element_id: StdCell<u64>,
}

#[derive(Clone)]
struct ShortcutBinding {
    shortcut: Shortcut,
    scope: ShortcutScope,
    action: Rc<dyn Fn(&InteractionTree)>,
}

impl ShortcutBinding {
    fn matches(
        &self,
        event: &KeyboardEvent,
        focused_element: Option<ElementId>,
        interaction_tree: &InteractionTree,
    ) -> bool {
        self.shortcut.matches(event) && self.scope.matches(focused_element, interaction_tree)
    }

    fn trigger(&self, interaction_tree: &InteractionTree) {
        (self.action)(interaction_tree);
    }
}

#[derive(Clone, Default)]
struct RenderSideEffects {
    window_title: Option<String>,
    shortcuts: Vec<ShortcutBinding>,
}

#[derive(Clone)]
struct RenderContext {
    state: Rc<RenderState>,
    hook_index: Rc<StdCell<usize>>,
    element_index: Rc<StdCell<usize>>,
    side_effects: Rc<RefCell<RenderSideEffects>>,
    context_entries: Rc<Vec<ContextEntry>>,
}

impl RenderContext {
    fn with_context_entries(&self, context_entries: Rc<Vec<ContextEntry>>) -> Self {
        Self {
            state: Rc::clone(&self.state),
            hook_index: Rc::clone(&self.hook_index),
            element_index: Rc::clone(&self.element_index),
            side_effects: Rc::clone(&self.side_effects),
            context_entries,
        }
    }
}

struct RenderOutput {
    view: View,
    side_effects: RenderSideEffects,
}

thread_local! {
    static CURRENT_RENDER_CONTEXT: RefCell<Option<RenderContext>> = const { RefCell::new(None) };
}

fn render_with_context(state: Rc<RenderState>, render: impl FnOnce() -> View) -> RenderOutput {
    let context = RenderContext {
        state,
        hook_index: Rc::new(StdCell::new(0)),
        element_index: Rc::new(StdCell::new(0)),
        side_effects: Rc::new(RefCell::new(RenderSideEffects::default())),
        context_entries: Rc::new(Vec::new()),
    };

    let view = with_render_context(context.clone(), render);
    let side_effects = context.side_effects.borrow().clone();
    RenderOutput { view, side_effects }
}

fn with_render_context(context: RenderContext, render: impl FnOnce() -> View) -> View {
    CURRENT_RENDER_CONTEXT.with(|slot| {
        let previous = slot.replace(Some(context.clone()));

        struct Guard<'a> {
            slot: &'a RefCell<Option<RenderContext>>,
            previous: Option<RenderContext>,
        }

        impl Drop for Guard<'_> {
            fn drop(&mut self) {
                let _ = self.slot.replace(self.previous.take());
            }
        }

        let _guard = Guard { slot, previous };
        render()
    })
}

fn with_render_context_state<R>(f: impl FnOnce(&RenderContext) -> R) -> R {
    CURRENT_RENDER_CONTEXT.with(|slot| {
        let context = slot
            .borrow()
            .clone()
            .expect("ruin_app hooks can only run while rendering a mounted component");
        f(&context)
    })
}

fn with_hook_slot<T: 'static, R>(init: impl FnOnce() -> T, f: impl FnOnce(&mut T) -> R) -> R {
    with_render_context_state(|context| {
        let index = context.hook_index.get();
        context.hook_index.set(index + 1);

        let mut hooks = context.state.hooks.borrow_mut();
        if hooks.len() == index {
            hooks.push(Box::new(init()));
        }

        let slot = hooks[index]
            .downcast_mut::<T>()
            .expect("ruin_app hook call order changed between renders");
        f(slot)
    })
}

fn allocate_element_id() -> ElementId {
    with_render_context_state(|context| {
        let index = context.element_index.get();
        context.element_index.set(index + 1);

        let mut element_ids = context.state.element_ids.borrow_mut();
        if element_ids.len() == index {
            let next = context.state.next_element_id.get().wrapping_add(1);
            context.state.next_element_id.set(next);
            element_ids.push(ElementId::new(next));
        }
        element_ids[index]
    })
}

struct MemoSlot<T> {
    compute: Rc<RefCell<Box<dyn Fn() -> T>>>,
    handle: Memo<T>,
}

struct ResourceSlot<T, E> {
    resource: Resource<T, E>,
    _effect: ruin_reactivity::EffectHandle,
}

impl<T: 'static> MemoSlot<T> {
    fn new(compute: Rc<RefCell<Box<dyn Fn() -> T>>>) -> Self {
        let handle = Memo {
            compute: Rc::new({
                let compute = Rc::clone(&compute);
                move || {
                    let compute = compute.borrow();
                    (compute.as_ref())()
                }
            }),
        };
        Self { compute, handle }
    }

    fn replace_compute(&mut self, compute: Rc<RefCell<Box<dyn Fn() -> T>>>) {
        self.compute = Rc::clone(&compute);
        self.handle.compute = Rc::new({
            let compute = Rc::clone(&compute);
            move || {
                let compute = compute.borrow();
                (compute.as_ref())()
            }
        });
    }
}

type PressHandler = Rc<dyn Fn(&RoutedPointerEvent) + 'static>;
type ScrollHandler = Rc<dyn Fn(&RoutedPointerEvent, &InteractionTree) + 'static>;
type KeyHandler = Rc<dyn Fn(&KeyboardEvent, &InteractionTree) -> bool + 'static>;

#[derive(Clone, Default)]
struct EventBindings {
    on_press: HashMap<ElementId, PressHandler>,
    on_scroll: HashMap<ElementId, ScrollHandler>,
    on_key: HashMap<ElementId, KeyHandler>,
}

impl EventBindings {
    fn extend(&mut self, other: EventBindings) {
        self.on_press.extend(other.on_press);
        self.on_scroll.extend(other.on_scroll);
        self.on_key.extend(other.on_key);
    }

    fn dispatch(
        &self,
        event: &RoutedPointerEvent,
        interaction_tree: &InteractionTree,
        hovered_targets: &[HitTarget],
    ) {
        match event.kind {
            RoutedPointerEventKind::Up {
                button: PointerButton::Primary,
            } => {
                if let Some(element_id) = event.target.element_id
                    && let Some(handler) = self.on_press.get(&element_id)
                {
                    handler(event);
                }
                if let Some(handler) = scroll_handler_for_event(
                    &self.on_scroll,
                    event.target.element_id,
                    hovered_targets,
                ) {
                    handler(event, interaction_tree);
                }
            }
            RoutedPointerEventKind::Down {
                button: PointerButton::Primary,
            }
            | RoutedPointerEventKind::Move
            | RoutedPointerEventKind::Scroll { .. }
            | RoutedPointerEventKind::Leave => {
                if let Some(handler) = scroll_handler_for_event(
                    &self.on_scroll,
                    event.target.element_id,
                    hovered_targets,
                ) {
                    handler(event, interaction_tree);
                }
            }
            _ => {}
        }
    }

    fn dispatch_key(
        &self,
        focused_element: Option<ElementId>,
        event: &KeyboardEvent,
        interaction_tree: &InteractionTree,
    ) {
        let Some(handler) = key_handler_for_focus(&self.on_key, focused_element, interaction_tree)
        else {
            return;
        };
        let _ = handler(event, interaction_tree);
    }
}

#[derive(Clone, Copy, PartialEq)]
struct ScrollbarDrag {
    start_pointer_y: f32,
    start_offset_y: f32,
}

#[derive(Clone, Copy, PartialEq)]
struct TextSelection {
    element_id: ElementId,
    anchor: usize,
    focus: usize,
}

#[derive(Clone, Copy, PartialEq)]
struct TextSelectionDrag {
    element_id: ElementId,
    anchor: usize,
}

struct TextSelectionState {
    selection: RefCell<Option<TextSelection>>,
    drag: RefCell<Option<TextSelectionDrag>>,
    version: Signal<u64>,
}

impl TextSelectionState {
    fn new() -> Self {
        Self {
            selection: RefCell::new(None),
            drag: RefCell::new(None),
            version: Signal::new(0),
        }
    }
}

struct InputState {
    current_cursor: CursorIcon,
    focused_element: Option<ElementId>,
    text_selection: Rc<TextSelectionState>,
}

impl InputState {
    fn new() -> Self {
        Self {
            current_cursor: CursorIcon::Default,
            focused_element: None,
            text_selection: Rc::new(TextSelectionState::new()),
        }
    }
}

fn clamp_scroll_offset(offset_y: f32, max_offset_y: f32) -> f32 {
    offset_y.clamp(0.0, max_offset_y.max(0.0))
}

fn apply_text_selection_overlay(
    scene: &mut ruin_ui::SceneSnapshot,
    selection: Option<TextSelection>,
) {
    let Some(selection) = selection else {
        return;
    };

    let mut next_items = Vec::with_capacity(scene.items.len());
    for item in scene.items.drain(..) {
        match item {
            DisplayItem::Text(mut text) if text.element_id == Some(selection.element_id) => {
                for rect in text.selection_rects(selection.anchor, selection.focus) {
                    next_items.push(DisplayItem::Quad(Quad::new(
                        rect,
                        text.selection_style.highlight_color,
                    )));
                }
                text.apply_selected_text_color(selection.anchor, selection.focus);
                next_items.push(DisplayItem::Text(text));
            }
            other => next_items.push(other),
        }
    }
    scene.items = next_items;
}

fn sync_primary_selection(
    window: &WindowController,
    interaction_tree: &InteractionTree,
    selection: Option<TextSelection>,
) -> Result<()> {
    let Some(selection) = selection else {
        window.set_primary_selection_text(String::new())?;
        return Ok(());
    };

    let Some(text) = interaction_tree.text_for_element(selection.element_id) else {
        return Ok(());
    };
    let copied = text
        .selected_text(selection.anchor, selection.focus)
        .unwrap_or_default()
        .to_owned();
    window.set_primary_selection_text(copied)?;
    Ok(())
}

fn scroll_handler_for_event<'a>(
    handlers: &'a HashMap<ElementId, ScrollHandler>,
    direct_target: Option<ElementId>,
    hovered_targets: &[HitTarget],
) -> Option<&'a ScrollHandler> {
    if let Some(element_id) = direct_target
        && let Some(handler) = handlers.get(&element_id)
    {
        return Some(handler);
    }

    hovered_targets
        .iter()
        .rev()
        .filter_map(|target| target.element_id)
        .find_map(|element_id| handlers.get(&element_id))
}

fn focused_element_for_pointer(
    interaction_tree: &InteractionTree,
    event: &PointerEvent,
) -> Option<ElementId> {
    let hit_path = interaction_tree.hit_path(event.position);
    hit_path
        .iter()
        .rev()
        .find_map(|target| target.focusable.then_some(target.element_id).flatten())
        .or_else(|| hit_path.iter().rev().find_map(|target| target.element_id))
}

fn element_contains_element(
    interaction_tree: &InteractionTree,
    ancestor: ElementId,
    descendant: ElementId,
) -> bool {
    fn contains_descendant(node: &ruin_ui::LayoutNode, descendant: ElementId) -> bool {
        if node.element_id == Some(descendant) {
            return true;
        }
        node.children
            .iter()
            .any(|child| contains_descendant(child, descendant))
    }

    fn ancestor_contains(
        node: &ruin_ui::LayoutNode,
        ancestor: ElementId,
        descendant: ElementId,
    ) -> bool {
        if node.element_id == Some(ancestor) {
            return contains_descendant(node, descendant);
        }
        node.children
            .iter()
            .any(|child| ancestor_contains(child, ancestor, descendant))
    }

    ancestor_contains(&interaction_tree.root, ancestor, descendant)
}

fn key_handler_for_focus<'a>(
    handlers: &'a HashMap<ElementId, KeyHandler>,
    focused_element: Option<ElementId>,
    interaction_tree: &InteractionTree,
) -> Option<&'a KeyHandler> {
    let focused_element = focused_element?;
    focused_ancestor_chain(&interaction_tree.root, focused_element)?
        .into_iter()
        .find_map(|element_id| handlers.get(&element_id))
}

fn focused_ancestor_chain(
    node: &ruin_ui::LayoutNode,
    focused_element: ElementId,
) -> Option<Vec<ElementId>> {
    let mut chain = Vec::new();
    if build_focused_ancestor_chain(node, focused_element, &mut chain) {
        Some(chain)
    } else {
        None
    }
}

fn build_focused_ancestor_chain(
    node: &ruin_ui::LayoutNode,
    focused_element: ElementId,
    chain: &mut Vec<ElementId>,
) -> bool {
    if node.element_id == Some(focused_element) {
        if let Some(element_id) = node.element_id {
            chain.push(element_id);
        }
        return true;
    }

    for child in &node.children {
        if build_focused_ancestor_chain(child, focused_element, chain) {
            if let Some(element_id) = node.element_id {
                chain.push(element_id);
            }
            return true;
        }
    }

    false
}

pub mod prelude {
    pub use crate::{
        App, BlockWidget, ButtonBuilder, ChildViews, Children, Component, ContainerBuilder,
        ContextKey, FocusScope, FontWeight, IntoBorder, IntoEdges, IntoView, Key, Memo, Mountable,
        Pending, Ready, Resource, ResourceState, Result, ScrollBoxBuilder, ScrollBoxWidget,
        Shortcut, ShortcutScope, Signal, TextBuilder, TextChildren, TextRole, TextValue, View,
        WidgetRef, Window, block, button, colors, column, component, context_provider, provide,
        row, scroll_box, surfaces, text, use_context, use_effect, use_memo, use_resource, use_shortcut,
        use_shortcut_with_context, use_signal, use_widget_ref, use_window_title, view,
    };
    pub use ruin_ui::{
        Border, Color, CursorIcon, Edges, Element, ElementId, InteractionTree, PointerButton,
        PointerEventKind, RoutedPointerEvent, RoutedPointerEventKind, ScrollbarStyle,
        TextFontFamily, TextStyle, TextWrap, UiSize,
    };
}

struct SignalInner<T> {
    cell: ruin_reactivity::Cell<T>,
}

#[cfg(test)]
mod tests {
    use super::*;
    use ruin_ui::{KeyboardModifiers, Point, UiRuntime, WindowSpec};

    #[derive(Clone, Debug, PartialEq, Eq)]
    struct NamedValue(&'static str);

    struct OuterContext;
    struct InnerContext;

    impl ContextKey for OuterContext {
        type Value = NamedValue;
    }

    impl ContextKey for InnerContext {
        type Value = NamedValue;
    }

    #[component]
    fn ContractProbe(label: TextValue, actions: ChildViews, children: ChildViews) -> impl IntoView {
        column().children((
            text().children(label),
            row().children(actions),
            block().children(children),
        ))
    }

    #[test]
    fn use_context_distinguishes_marker_types_for_same_value_type() {
        let seen_outer = Rc::new(RefCell::new(None::<NamedValue>));
        let seen_inner = Rc::new(RefCell::new(None::<NamedValue>));

        let _ = render_with_context(Rc::new(RenderState::default()), {
            let seen_outer = Rc::clone(&seen_outer);
            let seen_inner = Rc::clone(&seen_inner);
            move || {
                provide::<OuterContext>(NamedValue("outer"), || {
                    provide::<InnerContext>(NamedValue("inner"), || {
                        *seen_outer.borrow_mut() = Some(use_context::<OuterContext>());
                        *seen_inner.borrow_mut() = Some(use_context::<InnerContext>());
                        View::from_element(Element::column())
                    })
                })
            }
        });

        assert_eq!(*seen_outer.borrow(), Some(NamedValue("outer")));
        assert_eq!(*seen_inner.borrow(), Some(NamedValue("inner")));
    }

    #[test]
    fn nearer_provider_shadows_outer_provider_of_same_marker() {
        let seen_value = Rc::new(RefCell::new(None::<NamedValue>));

        let _ = render_with_context(Rc::new(RenderState::default()), {
            let seen_value = Rc::clone(&seen_value);
            move || {
                provide::<OuterContext>(NamedValue("outer"), || {
                    provide::<OuterContext>(NamedValue("inner"), || {
                        *seen_value.borrow_mut() = Some(use_context::<OuterContext>());
                        View::from_element(Element::column())
                    })
                })
            }
        });

        assert_eq!(*seen_value.borrow(), Some(NamedValue("inner")));
    }

    #[test]
    fn components_accept_child_contracts_and_child_like_slot_props() {
        let render = render_with_context(Rc::new(RenderState::default()), || {
            IntoView::into_view(view! {
                ContractProbe(
                    label = "slot label",
                    actions = (
                        text().children("action a"),
                        text().children("action b"),
                    )
                ) {
                    text() { "body child" }
                }
            })
        });
        let debug = format!("{:?}", render.view.element());

        assert!(debug.contains("slot label"), "{debug}");
        assert!(debug.contains("action a"), "{debug}");
        assert!(debug.contains("action b"), "{debug}");
        assert!(debug.contains("body child"), "{debug}");
    }

    #[test]
    fn key_dispatch_prefers_the_nearest_focused_ancestor_handler() {
        let outer_id = ElementId::new(41);
        let inner_id = ElementId::new(42);
        let root = Element::column().pointer_events(false).child(
            Element::column()
                .id(outer_id)
                .width(160.0)
                .height(120.0)
                .focusable(true)
                .child(
                    Element::column()
                        .id(inner_id)
                        .width(120.0)
                        .height(80.0)
                        .focusable(true),
                ),
        );
        let snapshot = ruin_ui::layout_snapshot(1, UiSize::new(200.0, 120.0), &root);
        let outer_hits = Rc::new(StdCell::new(0usize));
        let inner_hits = Rc::new(StdCell::new(0usize));

        let mut handlers = HashMap::<ElementId, KeyHandler>::new();
        handlers.insert(
            outer_id,
            Rc::new({
                let outer_hits = Rc::clone(&outer_hits);
                move |_, _| {
                    outer_hits.set(outer_hits.get() + 1);
                    true
                }
            }),
        );
        handlers.insert(
            inner_id,
            Rc::new({
                let inner_hits = Rc::clone(&inner_hits);
                move |_, _| {
                    inner_hits.set(inner_hits.get() + 1);
                    true
                }
            }),
        );

        let handler = key_handler_for_focus(&handlers, Some(inner_id), &snapshot.interaction_tree)
            .expect("focused element should resolve a key handler");
        let _ = handler(
            &KeyboardEvent::new(
                0,
                KeyboardEventKind::Pressed,
                KeyboardKey::ArrowDown,
                KeyboardModifiers::default(),
                None,
            ),
            &snapshot.interaction_tree,
        );

        assert_eq!(inner_hits.get(), 1);
        assert_eq!(outer_hits.get(), 0);
    }

    #[test]
    fn scroll_box_arrow_keys_work_after_clicking_text_content() {
        let offset_slot = Rc::new(RefCell::new(None::<Signal<f32>>));
        let render = render_with_context(Rc::new(RenderState::default()), {
            let offset_slot = Rc::clone(&offset_slot);
            move || {
                let offset = use_signal(|| 0.0_f32);
                *offset_slot.borrow_mut() = Some(offset.clone());
                scroll_box()
                    .height(120.0)
                    .offset_y(offset)
                    .children(text().children(
                        "line 01\nline 02\nline 03\nline 04\nline 05\nline 06\nline 07\nline 08\nline 09\nline 10\nline 11\nline 12",
                    ))
            }
        });
        let offset = offset_slot
            .borrow()
            .clone()
            .expect("scroll signal should have been captured");
        let scrollbox_id = render
            .view
            .element
            .id
            .expect("scroll box should receive an element id");
        let snapshot =
            ruin_ui::layout_snapshot(1, UiSize::new(260.0, 160.0), render.view.element());
        let focused = focused_element_for_pointer(
            &snapshot.interaction_tree,
            &PointerEvent::new(
                1,
                Point::new(12.0, 12.0),
                PointerEventKind::Down {
                    button: PointerButton::Primary,
                },
            ),
        );

        assert_eq!(focused, Some(scrollbox_id));

        render.view.bindings.dispatch_key(
            focused,
            &KeyboardEvent::new(
                0,
                KeyboardEventKind::Pressed,
                KeyboardKey::ArrowDown,
                KeyboardModifiers::default(),
                None,
            ),
            &snapshot.interaction_tree,
        );

        assert!(offset.get() > 0.0);
    }

    #[test]
    fn scroll_box_thumb_drag_updates_offset_signal() {
        let offset_slot = Rc::new(RefCell::new(None::<Signal<f32>>));
        let render = render_with_context(Rc::new(RenderState::default()), {
            let offset_slot = Rc::clone(&offset_slot);
            move || {
                let offset = use_signal(|| 0.0_f32);
                *offset_slot.borrow_mut() = Some(offset.clone());
                scroll_box()
                    .height(120.0)
                    .offset_y(offset)
                    .children(text().children(
                        "line 01\nline 02\nline 03\nline 04\nline 05\nline 06\nline 07\nline 08\nline 09\nline 10\nline 11\nline 12",
                    ))
            }
        });
        let offset = offset_slot
            .borrow()
            .clone()
            .expect("scroll signal should have been captured");
        let scrollbox_id = render
            .view
            .element
            .id
            .expect("scroll box should receive an element id");
        let snapshot =
            ruin_ui::layout_snapshot(1, UiSize::new(260.0, 160.0), render.view.element());
        let metrics = snapshot
            .interaction_tree
            .scroll_metrics_for_element(scrollbox_id)
            .expect("scroll metrics should exist for the scroll box");
        let thumb = metrics
            .scrollbar_thumb
            .expect("overflowing scroll box should expose a scrollbar thumb");
        let thumb_center = Point::new(
            thumb.origin.x + (thumb.size.width * 0.5),
            thumb.origin.y + (thumb.size.height * 0.5),
        );
        let hovered_targets = snapshot.interaction_tree.hit_path(thumb_center);
        let handler = scroll_handler_for_event(
            &render.view.bindings.on_scroll,
            Some(scrollbox_id),
            &hovered_targets,
        )
        .expect("scroll box should resolve its scroll handler")
        .clone();

        handler(
            &RoutedPointerEvent {
                kind: RoutedPointerEventKind::Down {
                    button: PointerButton::Primary,
                },
                target: hovered_targets
                    .last()
                    .cloned()
                    .expect("thumb center should hit the scroll box"),
                pointer_id: 1,
                position: thumb_center,
            },
            &snapshot.interaction_tree,
        );
        handler(
            &RoutedPointerEvent {
                kind: RoutedPointerEventKind::Move,
                target: hovered_targets
                    .last()
                    .cloned()
                    .expect("thumb center should hit the scroll box"),
                pointer_id: 1,
                position: Point::new(thumb_center.x, thumb_center.y + 24.0),
            },
            &snapshot.interaction_tree,
        );

        assert!(offset.get() > 0.0);
    }

    #[test]
    fn live_input_path_scrolls_a_scroll_box_rendered_inside_a_branch() {
        let offset_slot = Rc::new(RefCell::new(None::<Signal<f32>>));
        let render = render_with_context(Rc::new(RenderState::default()), {
            let offset_slot = Rc::clone(&offset_slot);
            move || match true {
                true => {
                    let offset = use_signal(|| 0.0_f32);
                    *offset_slot.borrow_mut() = Some(offset.clone());
                    column()
                        .background(surfaces::raised())
                        .gap(10.0)
                        .children((
                            text().children(("bytes = ", 4096)),
                            scroll_box()
                                .height(420.0)
                                .offset_y(offset.clone())
                                .padding(12.0)
                                .background(surfaces::canvas())
                                .border_radius(10.0)
                                .border((2.0, colors::muted()))
                                .children(
                                    text()
                                        .color(colors::muted())
                                        .font_family(TextFontFamily::Monospace)
                                        .children(
                                            "line 01\nline 02\nline 03\nline 04\nline 05\nline 06\nline 07\nline 08\nline 09\nline 10\nline 11\nline 12\nline 13\nline 14\nline 15\nline 16\nline 17\nline 18\nline 19\nline 20\nline 21\nline 22\nline 23\nline 24",
                                        ),
                                ),
                        ))
                }
                false => View::from_element(Element::column()),
            }
        });
        let offset = offset_slot
            .borrow()
            .clone()
            .expect("scroll signal should have been captured");
        let snapshot =
            ruin_ui::layout_snapshot(1, UiSize::new(1080.0, 760.0), render.view.element());
        let interaction_tree = RefCell::new(Some(snapshot.interaction_tree.clone()));
        let bindings = RefCell::new(render.view.bindings.clone());
        let mut pointer_router = PointerRouter::new();
        let mut input_state = InputState::new();
        let window = UiRuntime::headless()
            .create_window(WindowSpec::new("scrollbox-test"))
            .expect("headless window should be created");

        MountedApp::<View>::handle_pointer_event(
            &window,
            &interaction_tree,
            &bindings,
            &mut pointer_router,
            &mut input_state,
            PointerEvent::new(
                1,
                Point::new(24.0, 64.0),
                PointerEventKind::Down {
                    button: PointerButton::Primary,
                },
            ),
        )
        .expect("pointer down should succeed");
        MountedApp::<View>::handle_keyboard_event(
            &interaction_tree,
            &bindings,
            &RefCell::new(Vec::new()),
            &input_state,
            KeyboardEvent::new(
                0,
                KeyboardEventKind::Pressed,
                KeyboardKey::ArrowDown,
                KeyboardModifiers::default(),
                None,
            ),
        )
        .expect("keyboard event should succeed");

        assert!(offset.get() > 0.0);
    }

    #[test]
    fn scroll_box_stays_interactive_when_it_appears_on_a_later_render() {
        let state = Rc::new(RenderState::default());
        let ready_slot = Rc::new(RefCell::new(None::<Signal<bool>>));
        let offset_slot = Rc::new(RefCell::new(None::<Signal<f32>>));

        let render_once =
            |state: Rc<RenderState>,
             ready_slot: Rc<RefCell<Option<Signal<bool>>>>,
             offset_slot: Rc<RefCell<Option<Signal<f32>>>>| {
                render_with_context(state, move || {
                    let ready = use_signal(|| false);
                    let offset = use_signal(|| 0.0_f32);
                    *ready_slot.borrow_mut() = Some(ready.clone());
                    *offset_slot.borrow_mut() = Some(offset.clone());

                    if ready.get() {
                        column()
                        .background(surfaces::raised())
                        .gap(10.0)
                        .children((
                            text().children(("bytes = ", 4096)),
                            scroll_box()
                                .height(420.0)
                                .offset_y(offset.clone())
                                .padding(12.0)
                                .background(surfaces::canvas())
                                .border_radius(10.0)
                                .border((2.0, colors::muted()))
                                .children(
                                    text()
                                        .color(colors::muted())
                                        .font_family(TextFontFamily::Monospace)
                                        .children(
                                            "line 01\nline 02\nline 03\nline 04\nline 05\nline 06\nline 07\nline 08\nline 09\nline 10\nline 11\nline 12\nline 13\nline 14\nline 15\nline 16\nline 17\nline 18\nline 19\nline 20\nline 21\nline 22\nline 23\nline 24",
                                        ),
                                ),
                        ))
                    } else {
                        text().children("Loading file contents...")
                    }
                })
            };

        let _initial = render_once(
            Rc::clone(&state),
            Rc::clone(&ready_slot),
            Rc::clone(&offset_slot),
        );
        let ready = ready_slot
            .borrow()
            .clone()
            .expect("ready signal should have been captured");
        let offset = offset_slot
            .borrow()
            .clone()
            .expect("offset signal should have been captured");
        let _ = ready.set(true);

        let render = render_once(state, ready_slot, Rc::clone(&offset_slot));
        let snapshot =
            ruin_ui::layout_snapshot(1, UiSize::new(1080.0, 760.0), render.view.element());
        let interaction_tree = RefCell::new(Some(snapshot.interaction_tree.clone()));
        let bindings = RefCell::new(render.view.bindings.clone());
        let mut pointer_router = PointerRouter::new();
        let mut input_state = InputState::new();
        let window = UiRuntime::headless()
            .create_window(WindowSpec::new("scrollbox-transition-test"))
            .expect("headless window should be created");

        MountedApp::<View>::handle_pointer_event(
            &window,
            &interaction_tree,
            &bindings,
            &mut pointer_router,
            &mut input_state,
            PointerEvent::new(
                1,
                Point::new(24.0, 64.0),
                PointerEventKind::Down {
                    button: PointerButton::Primary,
                },
            ),
        )
        .expect("pointer down should succeed after branch switch");
        MountedApp::<View>::handle_keyboard_event(
            &interaction_tree,
            &bindings,
            &RefCell::new(Vec::new()),
            &input_state,
            KeyboardEvent::new(
                0,
                KeyboardEventKind::Pressed,
                KeyboardKey::ArrowDown,
                KeyboardModifiers::default(),
                None,
            ),
        )
        .expect("keyboard event should succeed after branch switch");

        assert!(offset.get() > 0.0);
    }

    #[test]
    fn live_input_path_scrolls_with_real_cargo_lock_contents() {
        let offset_slot = Rc::new(RefCell::new(None::<Signal<f32>>));
        let render = render_with_context(Rc::new(RenderState::default()), {
            let offset_slot = Rc::clone(&offset_slot);
            move || {
                let offset = use_signal(|| 0.0_f32);
                *offset_slot.borrow_mut() = Some(offset.clone());
                column().background(surfaces::raised()).gap(10.0).children((
                    text().children(("bytes = ", include_str!("../../../Cargo.lock").len())),
                    scroll_box()
                        .height(420.0)
                        .offset_y(offset.clone())
                        .padding(12.0)
                        .background(surfaces::canvas())
                        .border_radius(10.0)
                        .border((2.0, colors::muted()))
                        .children(
                            text()
                                .color(colors::muted())
                                .font_family(TextFontFamily::Monospace)
                                .children(include_str!("../../../Cargo.lock")),
                        ),
                ))
            }
        });
        let offset = offset_slot
            .borrow()
            .clone()
            .expect("scroll signal should have been captured");
        let snapshot =
            ruin_ui::layout_snapshot(1, UiSize::new(1080.0, 760.0), render.view.element());
        let interaction_tree = RefCell::new(Some(snapshot.interaction_tree.clone()));
        let bindings = RefCell::new(render.view.bindings.clone());
        let mut pointer_router = PointerRouter::new();
        let mut input_state = InputState::new();
        let window = UiRuntime::headless()
            .create_window(WindowSpec::new("scrollbox-cargo-lock-test"))
            .expect("headless window should be created");

        MountedApp::<View>::handle_pointer_event(
            &window,
            &interaction_tree,
            &bindings,
            &mut pointer_router,
            &mut input_state,
            PointerEvent::new(
                1,
                Point::new(24.0, 64.0),
                PointerEventKind::Scroll {
                    delta: Point::new(0.0, 48.0),
                },
            ),
        )
        .expect("wheel event should succeed");

        assert!(offset.get() > 0.0);
    }

    #[test]
    fn rerendered_scroll_box_element_carries_the_updated_offset() {
        let state = Rc::new(RenderState::default());
        let offset_slot = Rc::new(RefCell::new(None::<Signal<f32>>));
        let render_once =
            |state: Rc<RenderState>, offset_slot: Rc<RefCell<Option<Signal<f32>>>>| {
                render_with_context(state, move || {
                    let offset = use_signal(|| 0.0_f32);
                    *offset_slot.borrow_mut() = Some(offset.clone());
                    scroll_box()
                        .height(120.0)
                        .offset_y(offset.clone())
                        .children(
                            text().children("line 01\nline 02\nline 03\nline 04\nline 05\nline 06"),
                        )
                })
            };

        let _initial = render_once(Rc::clone(&state), Rc::clone(&offset_slot));
        let offset = offset_slot
            .borrow()
            .clone()
            .expect("offset signal should have been captured");
        let _ = offset.set(96.0);
        let render = render_once(state, offset_slot);
        let debug = format!("{:?}", render.view.element());

        assert!(debug.contains("offset_y: 96.0"), "{debug}");
    }
}
