use crate::animation::{
    default_theme_transition, AnimationCoordinator, AnimationEngine, AnimationKey, Transition,
    WindowProperty,
};
use crate::application::{
    ApplicationConfig, ThemeSelection, WindowClosePolicy, WindowRole, WindowSetFactory,
};
use crate::dialog::{async_dialog_channel, AsyncDialogDispatcher, AsyncDialogReceiver, Dialogs};
use crate::foundation::binding::{Binding, InvalidationSignal};
use crate::foundation::color::Color;
use crate::foundation::error::TguiError;
use crate::foundation::event::InputTrigger;
use crate::foundation::view_model::{Command, CommandContext, ValueCommand, ViewModel};
use crate::media::{MediaManager, VideoPlaybackStatus};
#[cfg(all(target_os = "android", feature = "android"))]
use crate::platform::android::activity::ndk::configuration::UiModeNight;
#[cfg(all(target_os = "android", feature = "android"))]
use crate::platform::android::activity::AndroidApp;
use crate::platform::backend::application::ApplicationHandler;
use crate::platform::backend::event_loop::{ActiveEventLoop, ControlFlow};
use crate::platform::backend::window::Window;
use crate::platform::backend::EventLoop;
use crate::platform::cursor::{Cursor, CursorIcon};
use crate::platform::dpi::{PhysicalPosition, PhysicalSize};
use crate::platform::event::{
    ElementState, Ime, KeyEvent, MouseButton, MouseScrollDelta, WindowEvent,
};
use crate::platform::keyboard::{KeyCode, ModifiersState, PhysicalKey};
#[cfg(all(target_env = "ohos", feature = "ohos"))]
use crate::platform::ohos::OhosApp;
use crate::platform::window::{
    ImeCapabilities, ImeEnableRequest, ImeHint, ImePurpose, ImeRequest, ImeRequestData,
    Theme as WindowTheme, WindowAttributes, WindowId,
};
use crate::rendering::renderer::{RenderStatus, Renderer};
use crate::text::font::FontManager;
use crate::ui::theme::{Theme, ThemeMode};
use crate::ui::widget::{
    video_status_transition_phase, HitInteraction, InputEditState, InputSnapshot, MediaEventPhase,
    MediaEventState, Point, Rect, RenderedWidgetScene, ScenePrimitives, ScrollbarAxis,
    ScrollbarHandle, WidgetId, WidgetTree,
};
use image::GenericImageView;
#[cfg(all(target_os = "android", feature = "android"))]
use jni::{jni_sig, jni_str, objects::JObject, JValue, JavaVM};
use std::collections::{HashMap, HashSet};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use unicode_segmentation::UnicodeSegmentation;
use winit_core::icon::{Icon, RgbaIcon};

const DOUBLE_CLICK_THRESHOLD: Duration = Duration::from_millis(300);
const CARET_BLINK_INTERVAL: Duration = Duration::from_millis(500);
#[cfg(all(target_os = "android", feature = "android"))]
const ANDROID_SYSTEM_THEME_POLL_INTERVAL: Duration = Duration::from_millis(500);

#[cfg(all(target_os = "android", feature = "android"))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct SystemBarStyle {
    color: Color,
    use_dark_icons: bool,
}

#[cfg(all(target_os = "android", feature = "android"))]
impl SystemBarStyle {
    fn from_theme(theme: &Theme) -> Self {
        let color = theme.palette.window_background;
        Self {
            color,
            use_dark_icons: is_light_color(color),
        }
    }
}

pub struct Runtime {
    event_loop: EventLoop,
    config: ApplicationConfig,
    #[cfg(all(target_os = "android", feature = "android"))]
    android_app: Option<AndroidApp>,
}

impl Runtime {
    pub fn new(config: ApplicationConfig) -> Result<Self, TguiError> {
        let event_loop = build_event_loop(ControlFlow::Poll)?;
        Ok(Self {
            event_loop,
            config,
            #[cfg(all(target_os = "android", feature = "android"))]
            android_app: None,
        })
    }

    #[cfg(all(target_os = "android", feature = "android"))]
    pub fn new_android(config: ApplicationConfig, app: AndroidApp) -> Result<Self, TguiError> {
        let event_loop = build_event_loop_with_android_app(ControlFlow::Poll, app.clone())?;
        Ok(Self {
            event_loop,
            config,
            android_app: Some(app),
        })
    }

    #[cfg(all(target_env = "ohos", feature = "ohos"))]
    pub fn new_ohos(config: ApplicationConfig, app: OhosApp) -> Result<Self, TguiError> {
        let event_loop = build_event_loop_with_ohos_app(ControlFlow::Poll, app)?;
        Ok(Self {
            event_loop,
            config,
            #[cfg(all(target_os = "android", feature = "android"))]
            android_app: None,
        })
    }

    pub fn run(self) -> Result<(), TguiError> {
        let (mut event_loop, mut handler) = self.into_parts();
        event_loop.run_app_on_demand(&mut handler)?;

        if let Some(error) = handler.error {
            return Err(error);
        }

        Ok(())
    }

    #[cfg(all(target_env = "ohos", feature = "ohos"))]
    pub(crate) fn handler(config: ApplicationConfig) -> RuntimeHandler {
        RuntimeHandler::new(
            config,
            #[cfg(all(target_os = "android", feature = "android"))]
            None,
        )
    }

    fn into_parts(self) -> (EventLoop, RuntimeHandler) {
        let handler = RuntimeHandler::new(
            self.config,
            #[cfg(all(target_os = "android", feature = "android"))]
            self.android_app,
        );
        (self.event_loop, handler)
    }
}

pub struct BoundRuntime<VM> {
    event_loop: EventLoop,
    config: ApplicationConfig,
    view_model: Arc<Mutex<VM>>,
    windows: Option<WindowSetFactory<VM>>,
    single_window: Option<SingleWindowSetup<VM>>,
    invalidation: InvalidationSignal,
    animations: AnimationCoordinator,
    #[cfg(all(target_os = "android", feature = "android"))]
    android_app: Option<AndroidApp>,
}

struct SingleWindowSetup<VM> {
    key: String,
    window_bindings: WindowBindings,
    widget_tree: Option<WidgetTree<VM>>,
    commands: Vec<WindowCommand<VM>>,
}

impl<VM: ViewModel> BoundRuntime<VM> {
    pub fn new(
        config: ApplicationConfig,
        view_model: VM,
        windows: WindowSetFactory<VM>,
        invalidation: InvalidationSignal,
        animations: AnimationCoordinator,
    ) -> Result<Self, TguiError> {
        let event_loop = build_event_loop(ControlFlow::Wait)?;
        Ok(Self {
            event_loop,
            config,
            view_model: Arc::new(Mutex::new(view_model)),
            windows: Some(windows),
            single_window: None,
            invalidation: invalidation.clone(),
            animations,
            #[cfg(all(target_os = "android", feature = "android"))]
            android_app: None,
        })
    }

    #[cfg(all(target_os = "android", feature = "android"))]
    pub fn new_android(
        config: ApplicationConfig,
        view_model: VM,
        window_bindings: WindowBindings,
        widget_tree: Option<WidgetTree<VM>>,
        commands: Vec<WindowCommand<VM>>,
        invalidation: InvalidationSignal,
        animations: AnimationCoordinator,
        app: AndroidApp,
    ) -> Result<Self, TguiError> {
        let event_loop = build_event_loop_with_android_app(ControlFlow::Wait, app.clone())?;
        Ok(Self {
            event_loop,
            config,
            view_model: Arc::new(Mutex::new(view_model)),
            windows: None,
            single_window: Some(SingleWindowSetup {
                key: "main".to_string(),
                window_bindings,
                widget_tree,
                commands,
            }),
            invalidation: invalidation.clone(),
            animations,
            android_app: Some(app),
        })
    }

    #[cfg(all(target_env = "ohos", feature = "ohos"))]
    pub fn new_ohos(
        config: ApplicationConfig,
        view_model: VM,
        window_bindings: WindowBindings,
        widget_tree: Option<WidgetTree<VM>>,
        commands: Vec<WindowCommand<VM>>,
        invalidation: InvalidationSignal,
        animations: AnimationCoordinator,
        app: OhosApp,
    ) -> Result<Self, TguiError> {
        let event_loop = build_event_loop_with_ohos_app(ControlFlow::Wait, app)?;
        Ok(Self {
            event_loop,
            config,
            view_model: Arc::new(Mutex::new(view_model)),
            windows: None,
            single_window: Some(SingleWindowSetup {
                key: "main".to_string(),
                window_bindings,
                widget_tree,
                commands,
            }),
            invalidation: invalidation.clone(),
            animations,
            #[cfg(all(target_os = "android", feature = "android"))]
            android_app: None,
        })
    }

    pub fn run(self) -> Result<(), TguiError> {
        if self.windows.is_some() {
            let (mut event_loop, mut handler) = self.into_parts();
            event_loop.run_app_on_demand(&mut handler)?;

            if let Some(error) = handler.error {
                return Err(error);
            }
        } else {
            let (mut event_loop, mut handler) = self.into_single_window_parts();
            event_loop.run_app_on_demand(&mut handler)?;

            if let Some(error) = handler.error {
                return Err(error);
            }
        }

        Ok(())
    }

    #[cfg(all(target_env = "ohos", feature = "ohos"))]
    pub(crate) fn handler(
        config: ApplicationConfig,
        view_model: VM,
        window_bindings: WindowBindings,
        widget_tree: Option<WidgetTree<VM>>,
        commands: Vec<WindowCommand<VM>>,
        invalidation: InvalidationSignal,
        animations: AnimationCoordinator,
    ) -> BoundRuntimeHandler<VM> {
        let (dialog_dispatcher, dialog_receiver) = async_dialog_channel();
        BoundRuntimeHandler::new(
            "main".to_string(),
            1,
            WindowRole::Main,
            config,
            Arc::new(Mutex::new(view_model)),
            window_bindings,
            widget_tree,
            commands,
            invalidation,
            animations,
            dialog_dispatcher,
            Some(dialog_receiver),
            #[cfg(all(target_os = "android", feature = "android"))]
            None,
        )
    }

    fn into_parts(self) -> (EventLoop, MultiWindowHandler<VM>) {
        let handler = MultiWindowHandler::new(
            self.config,
            self.view_model,
            self.windows
                .expect("desktop runtime requires a window factory"),
            self.invalidation,
            self.animations,
        );
        (self.event_loop, handler)
    }

    fn into_single_window_parts(self) -> (EventLoop, BoundRuntimeHandler<VM>) {
        let single_window = self
            .single_window
            .expect("single-window runtime requires a window definition");
        let (dialog_dispatcher, dialog_receiver) = async_dialog_channel();
        let handler = BoundRuntimeHandler::new(
            single_window.key,
            1,
            WindowRole::Main,
            self.config,
            self.view_model,
            single_window.window_bindings,
            single_window.widget_tree,
            single_window.commands,
            self.invalidation,
            self.animations,
            dialog_dispatcher,
            Some(dialog_receiver),
            #[cfg(all(target_os = "android", feature = "android"))]
            self.android_app,
        );
        (self.event_loop, handler)
    }
}

fn build_event_loop(control_flow: ControlFlow) -> Result<EventLoop, TguiError> {
    let event_loop = EventLoop::new()?;
    event_loop.set_control_flow(control_flow);
    Ok(event_loop)
}

#[cfg(all(target_os = "android", feature = "android"))]
fn build_event_loop_with_android_app(
    control_flow: ControlFlow,
    app: AndroidApp,
) -> Result<EventLoop, TguiError> {
    let event_loop = EventLoop::with_android_app(app)?;
    event_loop.set_control_flow(control_flow);
    Ok(event_loop)
}

#[cfg(all(target_env = "ohos", feature = "ohos"))]
fn build_event_loop_with_ohos_app(
    control_flow: ControlFlow,
    app: OhosApp,
) -> Result<EventLoop, TguiError> {
    let event_loop = EventLoop::with_ohos_app(app)?;
    event_loop.set_control_flow(control_flow);
    Ok(event_loop)
}

#[derive(Clone, Default)]
pub struct WindowBindings {
    pub(crate) title: Option<Binding<String>>,
    pub(crate) clear_color: Option<Binding<Color>>,
    pub(crate) theme_mode: Option<Binding<ThemeMode>>,
}

pub struct WindowCommand<VM> {
    pub(crate) trigger: InputTrigger,
    pub(crate) command: Command<VM>,
}

impl<VM> Clone for WindowCommand<VM> {
    fn clone(&self) -> Self {
        Self {
            trigger: self.trigger,
            command: self.command.clone(),
        }
    }
}

#[doc(hidden)]
pub struct RuntimeHandler {
    config: ApplicationConfig,
    window: Option<Arc<dyn Window>>,
    renderer: Option<Renderer>,
    window_id: Option<WindowId>,
    error: Option<TguiError>,
    #[cfg(all(target_os = "android", feature = "android"))]
    android_app: Option<AndroidApp>,
    #[cfg(all(target_os = "android", feature = "android"))]
    system_bar_style: Option<SystemBarStyle>,
}

impl RuntimeHandler {
    fn new(
        config: ApplicationConfig,
        #[cfg(all(target_os = "android", feature = "android"))] android_app: Option<AndroidApp>,
    ) -> Self {
        Self {
            config,
            window: None,
            renderer: None,
            window_id: None,
            error: None,
            #[cfg(all(target_os = "android", feature = "android"))]
            android_app,
            #[cfg(all(target_os = "android", feature = "android"))]
            system_bar_style: None,
        }
    }

    fn fail(&mut self, event_loop: &dyn ActiveEventLoop, error: TguiError) {
        eprintln!("tgui runtime failed: {error}");
        self.error = Some(error);
        event_loop.exit();
    }

    fn resolved_theme(&self, window: &dyn Window) -> Theme {
        resolve_theme(
            &self.config.theme,
            resolve_window_theme(
                Some(window),
                #[cfg(all(target_os = "android", feature = "android"))]
                self.android_app.as_ref(),
            ),
        )
    }

    #[cfg(all(target_os = "android", feature = "android"))]
    fn sync_system_bar_style(&mut self, theme: &Theme) {
        let Some(app) = self.android_app.as_ref() else {
            return;
        };
        let style = SystemBarStyle::from_theme(theme);
        if self.system_bar_style == Some(style) {
            return;
        }

        if let Err(error) = apply_android_system_bar_style(app, style) {
            eprintln!("failed to sync Android system bar style: {error}");
            return;
        }

        self.system_bar_style = Some(style);
    }

    fn render_hidden_frame(&mut self, event_loop: &dyn ActiveEventLoop) -> bool {
        #[cfg(all(target_env = "ohos", feature = "ohos"))]
        {
            let _ = event_loop;
            return true;
        }

        #[cfg(not(all(target_env = "ohos", feature = "ohos")))]
        let Some(renderer) = self.renderer.as_mut() else {
            return true;
        };

        #[cfg(not(all(target_env = "ohos", feature = "ohos")))]
        match renderer.render(&ScenePrimitives::default()) {
            Ok(RenderStatus::Rendered | RenderStatus::SkipFrame) => true,
            Ok(RenderStatus::ReconfigureSurface) => {
                renderer.reconfigure();
                match renderer.render(&ScenePrimitives::default()) {
                    Ok(RenderStatus::Rendered | RenderStatus::SkipFrame) => true,
                    Ok(RenderStatus::ReconfigureSurface) => true,
                    Err(error) => {
                        self.fail(event_loop, error);
                        false
                    }
                }
            }
            Err(error) => {
                self.fail(event_loop, error);
                false
            }
        }
    }

    fn resume_existing_window(&mut self, event_loop: &dyn ActiveEventLoop) {
        let Some(window) = self.window.clone() else {
            return;
        };

        let clear_color = if self.config.clear_color_overridden {
            self.config.clear_color
        } else {
            self.resolved_theme(window.as_ref())
                .palette
                .window_background
        };

        match Renderer::new(window.clone(), clear_color, &self.config.fonts) {
            Ok(renderer) => self.renderer = Some(renderer),
            Err(error) => {
                self.fail(event_loop, error);
                return;
            }
        }

        #[cfg(all(target_os = "android", feature = "android"))]
        {
            let theme = self.resolved_theme(window.as_ref());
            self.sync_system_bar_style(&theme);
        }

        if !self.render_hidden_frame(event_loop) {
            return;
        }

        window.request_redraw();
        window.set_visible(true);
    }

    fn suspend(&mut self) {
        self.renderer = None;
        #[cfg(all(target_os = "android", feature = "android"))]
        {
            self.system_bar_style = None;
        }
    }

    fn handle_runtime_redraw(&mut self, event_loop: &dyn ActiveEventLoop) {
        let Some(renderer) = self.renderer.as_mut() else {
            return;
        };

        match renderer.render(&ScenePrimitives::default()) {
            Ok(RenderStatus::Rendered | RenderStatus::SkipFrame) => {}
            Ok(RenderStatus::ReconfigureSurface) => {
                renderer.reconfigure();
                match renderer.render(&ScenePrimitives::default()) {
                    Ok(RenderStatus::Rendered | RenderStatus::SkipFrame) => {}
                    Ok(RenderStatus::ReconfigureSurface) => {}
                    Err(error) => self.fail(event_loop, error),
                }
            }
            Err(error) => self.fail(event_loop, error),
        }
    }
}

#[doc(hidden)]
pub struct BoundRuntimeHandler<VM> {
    window_key: String,
    window_instance_id: u64,
    role: WindowRole,
    config: ApplicationConfig,
    font_manager: FontManager,
    theme: Theme,
    view_model: Arc<Mutex<VM>>,
    window_bindings: WindowBindings,
    widget_tree: Option<WidgetTree<VM>>,
    commands: Vec<WindowCommand<VM>>,
    close_policy: WindowClosePolicy,
    invalidation: InvalidationSignal,
    last_invalidation_revision: u64,
    animations: AnimationCoordinator,
    animation_engine: AnimationEngine,
    animation_epoch: u64,
    caret_blink_started_at: Option<Instant>,
    cursor_position: Option<Point>,
    modifiers: ModifiersState,
    hovered_widgets: Vec<HoveredWidget<VM>>,
    hovered_scrollbar: Option<ScrollbarHandle>,
    active_scrollbar_drag: Option<ScrollbarDrag>,
    pending_click: Option<PendingClick<VM>>,
    focused_widget: Option<FocusedWidget<VM>>,
    focused_input: Option<WidgetId>,
    input_states: HashMap<WidgetId, InputEditState>,
    clipboard: ClipboardService,
    cached_scene: Option<CachedScene>,
    scroll_states: HashMap<WidgetId, Point>,
    scroll_epoch: u64,
    media_event_states: HashMap<WidgetId, DispatchedMediaState>,
    media_manager: MediaManager,
    window: Option<Arc<dyn Window>>,
    renderer: Option<Renderer>,
    window_id: Option<WindowId>,
    error: Option<TguiError>,
    dialog_dispatcher: AsyncDialogDispatcher<VM>,
    dialog_receiver: Option<AsyncDialogReceiver<VM>>,
    #[cfg(all(target_os = "android", feature = "android"))]
    android_app: Option<AndroidApp>,
    #[cfg(all(target_os = "android", feature = "android"))]
    system_bar_style: Option<SystemBarStyle>,
}

struct CachedScene {
    viewport: Rect,
    focused_input: Option<WidgetId>,
    caret_visible: bool,
    animation_epoch: u64,
    scroll_epoch: u64,
    rendered: RenderedWidgetScene,
}

struct PendingClick<VM> {
    widget_id: WidgetId,
    deadline: Instant,
    command: Option<Command<VM>>,
}

struct FocusedWidget<VM> {
    widget_id: WidgetId,
    on_blur: Option<Command<VM>>,
}

struct HoveredWidget<VM> {
    widget_id: WidgetId,
    cursor_style: Option<crate::ui::widget::CursorStyle>,
    on_mouse_enter: Option<Command<VM>>,
    on_mouse_leave: Option<Command<VM>>,
    on_mouse_move: Option<crate::foundation::view_model::ValueCommand<VM, Point>>,
}

#[derive(Clone, Copy)]
struct ScrollbarDrag {
    handle: ScrollbarHandle,
    start_cursor: Point,
    start_scroll_offset: Point,
    track: Rect,
    thumb: Rect,
    max_offset: f32,
}

enum PendingMediaEvent<VM> {
    Command(Command<VM>),
    Error(ValueCommand<VM, String>, String),
}

#[derive(Clone, Default)]
struct DispatchedMediaState {
    phase: Option<MediaEventPhase>,
    video_status: Option<VideoPlaybackStatus>,
    seek_generation: Option<u64>,
}

fn collect_pending_media_event<VM>(
    state: &MediaEventState<VM>,
    previous: Option<&DispatchedMediaState>,
    pending: &mut Vec<PendingMediaEvent<VM>>,
) {
    if previous.and_then(|value| value.phase.as_ref()) != state.media_phase.as_ref() {
        match state.media_phase.as_ref() {
            Some(MediaEventPhase::Loading) => {
                if let Some(command) = state.handlers.on_loading.clone() {
                    pending.push(PendingMediaEvent::Command(command));
                }
            }
            Some(MediaEventPhase::Success) => {
                if let Some(command) = state.handlers.on_success.clone() {
                    pending.push(PendingMediaEvent::Command(command));
                }
            }
            Some(MediaEventPhase::Error(error)) => {
                if let Some(command) = state.handlers.on_error.clone() {
                    pending.push(PendingMediaEvent::Error(command, error.clone()));
                }
            }
            _ => {}
        }
    }

    if let Some(status) = state.video_status.as_ref() {
        if let Some(phase) = video_status_transition_phase(
            previous.and_then(|value| value.video_status.as_ref()),
            status,
        ) {
            match phase {
                MediaEventPhase::Play => {
                    if let Some(command) = state.handlers.on_play.clone() {
                        pending.push(PendingMediaEvent::Command(command));
                    }
                }
                MediaEventPhase::Pause => {
                    if let Some(command) = state.handlers.on_pause.clone() {
                        pending.push(PendingMediaEvent::Command(command));
                    }
                }
                MediaEventPhase::Resume => {
                    if let Some(command) = state.handlers.on_resume.clone() {
                        pending.push(PendingMediaEvent::Command(command));
                    }
                }
                MediaEventPhase::End => {
                    if let Some(command) = state.handlers.on_end.clone() {
                        pending.push(PendingMediaEvent::Command(command));
                    }
                }
                _ => {}
            }
        }
    }

    if previous.and_then(|value| value.seek_generation) != state.seek_generation {
        if previous.is_some() {
            if let Some(command) = state.handlers.on_seek.clone() {
                pending.push(PendingMediaEvent::Command(command));
            }
        }
    }
}

#[derive(Default)]
struct ClipboardService {
    #[cfg(any(
        target_os = "windows",
        target_os = "macos",
        all(target_os = "linux", not(target_env = "ohos"))
    ))]
    inner: Option<arboard::Clipboard>,
}

impl ClipboardService {
    fn get_text(&mut self) -> Option<String> {
        #[cfg(any(
            target_os = "windows",
            target_os = "macos",
            all(target_os = "linux", not(target_env = "ohos"))
        ))]
        {
            if self.inner.is_none() {
                self.inner = arboard::Clipboard::new().ok();
            }
            return self
                .inner
                .as_mut()
                .and_then(|clipboard| clipboard.get_text().ok());
        }

        #[allow(unreachable_code)]
        None
    }

    fn set_text(&mut self, _text: String) {
        #[cfg(any(
            target_os = "windows",
            target_os = "macos",
            all(target_os = "linux", not(target_env = "ohos"))
        ))]
        {
            if self.inner.is_none() {
                self.inner = arboard::Clipboard::new().ok();
            }
            if let Some(clipboard) = self.inner.as_mut() {
                let _ = clipboard.set_text(_text);
            }
        }
    }
}

impl<VM: 'static> BoundRuntimeHandler<VM> {
    fn new(
        window_key: String,
        window_instance_id: u64,
        role: WindowRole,
        config: ApplicationConfig,
        view_model: Arc<Mutex<VM>>,
        window_bindings: WindowBindings,
        widget_tree: Option<WidgetTree<VM>>,
        commands: Vec<WindowCommand<VM>>,
        invalidation: InvalidationSignal,
        animations: AnimationCoordinator,
        dialog_dispatcher: AsyncDialogDispatcher<VM>,
        dialog_receiver: Option<AsyncDialogReceiver<VM>>,
        #[cfg(all(target_os = "android", feature = "android"))] android_app: Option<AndroidApp>,
    ) -> Self {
        let font_manager = FontManager::new(&config.fonts);
        let theme = match &config.theme {
            ThemeSelection::Fixed(theme) => theme.clone(),
            ThemeSelection::System => Theme::default(),
        };
        Self {
            window_key,
            window_instance_id,
            role,
            config,
            font_manager,
            theme,
            view_model,
            window_bindings,
            widget_tree,
            commands,
            close_policy: WindowClosePolicy::Close,
            invalidation: invalidation.clone(),
            last_invalidation_revision: 0,
            animations,
            animation_engine: AnimationEngine::default(),
            animation_epoch: 0,
            caret_blink_started_at: None,
            cursor_position: None,
            modifiers: ModifiersState::default(),
            hovered_widgets: Vec::new(),
            hovered_scrollbar: None,
            active_scrollbar_drag: None,
            pending_click: None,
            focused_widget: None,
            focused_input: None,
            input_states: HashMap::new(),
            clipboard: ClipboardService::default(),
            cached_scene: None,
            scroll_states: HashMap::new(),
            scroll_epoch: 0,
            media_event_states: HashMap::new(),
            media_manager: MediaManager::new(invalidation.clone()),
            window: None,
            renderer: None,
            window_id: None,
            error: None,
            dialog_dispatcher,
            dialog_receiver,
            #[cfg(all(target_os = "android", feature = "android"))]
            android_app,
            #[cfg(all(target_os = "android", feature = "android"))]
            system_bar_style: None,
        }
    }

    fn with_view_model<R>(&self, f: impl FnOnce(&mut VM) -> R) -> R {
        let mut view_model = self.view_model.lock().expect("view model lock poisoned");
        f(&mut view_model)
    }

    fn command_context(&self) -> CommandContext<VM> {
        CommandContext::new(Dialogs::from_runtime(
            self.window_key.clone(),
            self.window_instance_id,
            self.window.as_ref(),
            self.dialog_dispatcher.clone(),
        ))
    }

    fn set_dialog_proxy(&self, event_loop: &dyn ActiveEventLoop) {
        self.dialog_dispatcher.set_proxy(event_loop.create_proxy());
    }

    fn execute_command(&mut self, command: &Command<VM>) {
        let context = self.command_context();
        self.with_view_model(|view_model| command.execute_with_context(view_model, &context));
        self.invalidate_scene();
        self.invalidation.mark_dirty();
    }

    fn execute_value_command<V>(&mut self, command: &ValueCommand<VM, V>, value: V) {
        let context = self.command_context();
        self.with_view_model(|view_model| {
            command.execute_with_context(view_model, value, &context)
        });
        self.invalidate_scene();
        self.invalidation.mark_dirty();
    }

    fn drain_dialog_completions(&mut self) -> bool {
        let completions: Vec<_> = self
            .dialog_receiver
            .as_ref()
            .map(|receiver| receiver.try_iter().collect())
            .unwrap_or_default();

        if completions.is_empty() {
            return false;
        }

        for completion in completions {
            if completion.window_instance_id != self.window_instance_id {
                continue;
            }
            let context = self.command_context();
            self.with_view_model(|view_model| (completion.callback)(view_model, &context));
            self.invalidate_scene();
            self.invalidation.mark_dirty();
        }

        if let Some(window) = self.window.as_ref() {
            window.request_redraw();
        }

        true
    }

    fn set_definition(
        &mut self,
        role: WindowRole,
        config: ApplicationConfig,
        window_bindings: WindowBindings,
        commands: Vec<WindowCommand<VM>>,
        close_policy: WindowClosePolicy,
    ) {
        self.role = role;
        let font_manager = FontManager::new(&config.fonts);
        self.config = config;
        self.font_manager = font_manager;
        self.window_bindings = window_bindings;
        self.commands = commands;
        self.close_policy = close_policy;
        self.media_event_states.clear();
        self.invalidate_scene();
    }

    fn close_policy(&self) -> WindowClosePolicy {
        self.close_policy
    }

    fn is_main_window(&self) -> bool {
        matches!(self.role, WindowRole::Main)
    }

    fn blocks_main_window(&self) -> bool {
        matches!(
            self.role,
            WindowRole::Child {
                blocks_main_window: true
            }
        )
    }

    fn fail(&mut self, event_loop: &dyn ActiveEventLoop, error: TguiError) {
        eprintln!("tgui bound runtime failed: {error}");
        self.error = Some(error);
        event_loop.exit();
    }

    #[cfg(all(target_os = "android", feature = "android"))]
    fn sync_system_bar_style(&mut self, theme: &Theme) {
        let Some(app) = self.android_app.as_ref() else {
            return;
        };
        let style = SystemBarStyle::from_theme(theme);
        if self.system_bar_style == Some(style) {
            return;
        }

        if let Err(error) = apply_android_system_bar_style(app, style) {
            eprintln!("failed to sync Android system bar style: {error}");
            return;
        }

        self.system_bar_style = Some(style);
    }

    fn uses_system_theme(&self) -> bool {
        matches!(self.active_theme_selection(), ThemeSelection::System)
    }

    fn apply_theme(&mut self, theme: Theme) {
        self.theme = theme;
        self.invalidate_scene();
    }

    fn apply_window_theme(&mut self, window_theme: Option<WindowTheme>) {
        if self.uses_system_theme() {
            self.apply_theme(resolve_theme(
                &self.active_theme_selection(),
                resolve_window_theme(
                    self.window.as_deref(),
                    #[cfg(all(target_os = "android", feature = "android"))]
                    self.android_app.as_ref(),
                )
                .or(window_theme),
            ));
        }
    }

    fn active_theme_selection(&self) -> ThemeSelection {
        self.window_bindings
            .theme_mode
            .as_ref()
            .map(|binding| ThemeSelection::from_mode(binding.get()))
            .unwrap_or_else(|| self.config.theme.clone())
    }

    fn sync_theme_binding(&mut self) {
        let resolved_theme = resolve_theme(
            &self.active_theme_selection(),
            resolve_window_theme(
                self.window.as_deref(),
                #[cfg(all(target_os = "android", feature = "android"))]
                self.android_app.as_ref(),
            ),
        );
        if self.theme != resolved_theme {
            self.apply_theme(resolved_theme);
        }
    }

    fn refresh_platform_theme(&mut self) -> bool {
        let previous_theme = self.theme.clone();
        self.sync_theme_binding();
        self.theme != previous_theme
    }

    fn sync_bindings(&mut self, now: Instant) {
        self.sync_theme_binding();
        #[cfg(all(target_os = "android", feature = "android"))]
        {
            let theme = self.theme.clone();
            self.sync_system_bar_style(&theme);
        }

        if let Some(window) = self.window.as_ref() {
            if let Some(binding) = self.window_bindings.title.as_ref() {
                window.set_title(&binding.get());
            }
        }

        let theme = self.animated_theme(now);
        if let Some(renderer) = self.renderer.as_mut() {
            if let Some(binding) = self.window_bindings.clear_color.as_ref() {
                renderer.set_clear_color(self.animation_engine.resolve_color(
                    AnimationKey::Window(WindowProperty::ClearColor),
                    binding.get(),
                    binding.transition(),
                    now,
                ));
            } else if !self.config.clear_color_overridden {
                renderer.set_clear_color(theme.palette.window_background);
            }
        }
    }

    fn request_redraw_if_dirty(&mut self, now: Instant) {
        let revision = self.invalidation.revision();
        if revision != self.last_invalidation_revision {
            self.last_invalidation_revision = revision;
            self.invalidate_scene();
            self.sync_bindings(now);

            if let Some(window) = self.window.as_ref() {
                window.request_redraw();
            }
        }
    }

    fn render_primitives(&mut self) -> RenderedWidgetScene {
        let viewport = self.viewport_rect();
        let caret_visible = self.input_caret_visible(Instant::now());
        let focused_input_state = self
            .focused_input
            .and_then(|id| self.focused_input_state(id))
            .cloned();
        if !self.media_manager.has_active_video() {
            if let Some(cached) = self.cached_scene.as_ref() {
                if cached.viewport == viewport
                    && cached.focused_input == self.focused_input
                    && cached.caret_visible == caret_visible
                    && cached.animation_epoch == self.animation_epoch
                    && cached.scroll_epoch == self.scroll_epoch
                {
                    return cached.rendered.clone();
                }
            }
        }

        let theme = self.animated_theme(Instant::now());
        let rendered = match self.widget_tree.as_ref() {
            Some(tree) => tree.render_output(
                &self.font_manager,
                &theme,
                &self.media_manager,
                &mut self.animation_engine,
                self.hovered_scrollbar,
                self.active_scrollbar_drag.map(|drag| drag.handle),
                &self.scroll_states,
                viewport,
                self.focused_input,
                focused_input_state.as_ref(),
                caret_visible,
            ),
            None => RenderedWidgetScene::default(),
        };
        self.cached_scene = Some(CachedScene {
            viewport,
            focused_input: self.focused_input,
            caret_visible,
            animation_epoch: self.animation_epoch,
            scroll_epoch: self.scroll_epoch,
            rendered: rendered.clone(),
        });
        rendered
    }

    fn dispatch_media_events(&mut self) {
        let Some(tree) = self.widget_tree.as_ref() else {
            self.media_event_states.clear();
            return;
        };

        let states = tree.media_event_states(&self.media_manager);
        let current_ids: HashSet<_> = states.iter().map(|state| state.widget_id).collect();
        self.media_event_states
            .retain(|widget_id, _| current_ids.contains(widget_id));

        let mut pending = Vec::new();
        for state in states {
            let previous = self.media_event_states.get(&state.widget_id);
            collect_pending_media_event(&state, previous, &mut pending);
            self.media_event_states.insert(
                state.widget_id,
                DispatchedMediaState {
                    phase: state.media_phase.clone(),
                    video_status: state.video_status.clone(),
                    seek_generation: state.seek_generation,
                },
            );
        }

        if pending.is_empty() {
            return;
        }

        for event in pending {
            match event {
                PendingMediaEvent::Command(command) => self.execute_command(&command),
                PendingMediaEvent::Error(command, error) => {
                    self.execute_value_command(&command, error);
                }
            }
        }

        if let Some(window) = self.window.as_ref() {
            window.request_redraw();
        }
    }

    fn viewport_rect(&self) -> Rect {
        let size = self
            .window
            .as_ref()
            .map(|window| window.surface_size())
            .unwrap_or(self.config.size.to_physical::<u32>(1.0));
        Rect::new(0.0, 0.0, size.width as f32, size.height as f32)
    }

    fn invalidate_scene(&mut self) {
        self.cached_scene = None;
    }

    fn should_dispatch_widget_event(event: &WindowEvent) -> bool {
        match event {
            WindowEvent::PointerMoved { .. } | WindowEvent::MouseWheel { .. } => true,
            WindowEvent::PointerButton {
                state: ElementState::Pressed,
                button,
                ..
            } => button.clone().mouse_button() == Some(MouseButton::Left),
            WindowEvent::KeyboardInput { .. } => true,
            _ => false,
        }
    }

    fn render_current_frame(&mut self) -> Result<RenderStatus, TguiError> {
        // Android can deliver a redraw before a replacement surface is ready.
        // In that case we simply skip the frame and wait for the next resume/redraw.
        if self.renderer.is_none() {
            return Ok(RenderStatus::SkipFrame);
        }

        self.sync_bindings(Instant::now());
        let rendered = self.render_primitives();
        self.dispatch_media_events();
        if let (Some(window), Some(caret_rect)) = (self.window.as_ref(), rendered.ime_cursor_area) {
            let _ = window.request_ime_update(ImeRequest::Update(Self::ime_cursor_request_data(
                caret_rect,
            )));
        }
        let renderer = self
            .renderer
            .as_mut()
            .expect("renderer should exist before drawing");
        renderer.render(&rendered.primitives)
    }

    #[cfg(all(target_os = "android", feature = "android"))]
    fn render_immediately(&mut self, event_loop: &dyn ActiveEventLoop) {
        if self.window.is_none() || self.renderer.is_none() {
            return;
        }

        match self.render_current_frame() {
            Ok(RenderStatus::Rendered | RenderStatus::SkipFrame) => {}
            Ok(RenderStatus::ReconfigureSurface) => {
                if let Some(renderer) = self.renderer.as_mut() {
                    renderer.reconfigure();
                }
                match self.render_current_frame() {
                    Ok(RenderStatus::Rendered | RenderStatus::SkipFrame) => {}
                    Ok(RenderStatus::ReconfigureSurface) => {}
                    Err(error) => self.fail(event_loop, error),
                }
            }
            Err(error) => self.fail(event_loop, error),
        }
    }

    fn set_pointer_position(&mut self, position: PhysicalPosition<f64>) {
        self.cursor_position = Some(Point {
            x: position.x as f32,
            y: position.y as f32,
        });
    }

    fn clear_pointer_position(&mut self) {
        self.cursor_position = None;
        for hovered in std::mem::take(&mut self.hovered_widgets).into_iter().rev() {
            if let Some(command) = hovered.on_mouse_leave {
                self.execute_command(&command);
            }
        }
        self.hovered_scrollbar = self.active_scrollbar_drag.map(|drag| drag.handle);
        self.update_cursor_icon();
    }

    fn drive_animations(&mut self, event_loop: &dyn ActiveEventLoop, now: Instant) -> bool {
        self.flush_pending_click_if_due(now);

        let mut frame_advanced = false;
        if self.animations.refresh(now) {
            frame_advanced = true;
            self.animation_epoch = self.animation_epoch.wrapping_add(1);
            self.invalidate_scene();
            if let Some(window) = self.window.as_ref() {
                window.request_redraw();
            }
        }

        if self.animation_engine.refresh(now) {
            frame_advanced = true;
            self.animation_epoch = self.animation_epoch.wrapping_add(1);
            self.invalidate_scene();
            if let Some(window) = self.window.as_ref() {
                window.request_redraw();
            }
        }

        if let Some(deadline) = self.next_deadline(now) {
            event_loop.set_control_flow(ControlFlow::WaitUntil(deadline));
            if let Some(window) = self.window.as_ref() {
                window.request_redraw();
            }
        } else {
            #[cfg(all(target_os = "android", feature = "android"))]
            if self.uses_system_theme() {
                event_loop.set_control_flow(ControlFlow::WaitUntil(
                    now + ANDROID_SYSTEM_THEME_POLL_INTERVAL,
                ));
                return frame_advanced;
            }

            event_loop.set_control_flow(ControlFlow::Wait);
        }

        frame_advanced
    }

    fn render_hidden_frame(&mut self, event_loop: &dyn ActiveEventLoop) -> bool {
        #[cfg(all(target_env = "ohos", feature = "ohos"))]
        {
            let _ = event_loop;
            return true;
        }

        #[cfg(not(all(target_env = "ohos", feature = "ohos")))]
        let status = match self.render_current_frame() {
            Ok(status) => status,
            Err(error) => {
                self.fail(event_loop, error);
                return false;
            }
        };

        #[cfg(not(all(target_env = "ohos", feature = "ohos")))]
        if matches!(status, RenderStatus::ReconfigureSurface) {
            if let Some(renderer) = self.renderer.as_mut() {
                renderer.reconfigure();
            }

            if let Err(error) = self.render_current_frame() {
                self.fail(event_loop, error);
                return false;
            }
        }

        #[cfg(not(all(target_env = "ohos", feature = "ohos")))]
        true
    }

    fn resume_existing_window(&mut self, event_loop: &dyn ActiveEventLoop) {
        let Some(window) = self.window.clone() else {
            return;
        };

        self.sync_theme_binding();
        self.invalidate_scene();
        let clear_color =
            if self.window_bindings.clear_color.is_some() || self.config.clear_color_overridden {
                self.config.clear_color
            } else {
                self.theme.palette.window_background
            };

        match Renderer::new(window.clone(), clear_color, &self.config.fonts) {
            Ok(renderer) => self.renderer = Some(renderer),
            Err(error) => {
                self.fail(event_loop, error);
                return;
            }
        }

        #[cfg(all(target_os = "android", feature = "android"))]
        {
            let theme = self.theme.clone();
            self.sync_system_bar_style(&theme);
        }

        if !self.render_hidden_frame(event_loop) {
            return;
        }

        window.request_redraw();
        window.set_visible(true);
    }

    fn suspend(&mut self) {
        self.renderer = None;
        self.cached_scene = None;
        self.media_event_states.clear();
        #[cfg(all(target_os = "android", feature = "android"))]
        {
            self.system_bar_style = None;
        }
    }

    fn animated_theme(&mut self, now: Instant) -> Theme {
        let transition = Some(default_theme_transition());
        let mut theme = self.theme.clone();
        theme.palette.window_background = self.resolve_theme_color(
            WindowProperty::ThemeWindowBackground,
            theme.palette.window_background,
            transition,
            now,
        );
        theme.palette.surface = self.resolve_theme_color(
            WindowProperty::ThemeSurface,
            theme.palette.surface,
            transition,
            now,
        );
        theme.palette.surface_muted = self.resolve_theme_color(
            WindowProperty::ThemeSurfaceMuted,
            theme.palette.surface_muted,
            transition,
            now,
        );
        theme.palette.accent = self.resolve_theme_color(
            WindowProperty::ThemeAccent,
            theme.palette.accent,
            transition,
            now,
        );
        theme.palette.text = self.resolve_theme_color(
            WindowProperty::ThemeText,
            theme.palette.text,
            transition,
            now,
        );
        theme.palette.text_muted = self.resolve_theme_color(
            WindowProperty::ThemeTextMuted,
            theme.palette.text_muted,
            transition,
            now,
        );
        theme.palette.input_background = self.resolve_theme_color(
            WindowProperty::ThemeInputBackground,
            theme.palette.input_background,
            transition,
            now,
        );
        theme
    }

    fn resolve_theme_color(
        &mut self,
        property: WindowProperty,
        target: Color,
        transition: Option<Transition>,
        now: Instant,
    ) -> Color {
        self.animation_engine
            .resolve_color(AnimationKey::Window(property), target, transition, now)
    }

    fn next_deadline(&self, now: Instant) -> Option<Instant> {
        let animation_deadline = self.animation_engine.next_frame_deadline(now);
        let controller_deadline = self.animations.next_frame_deadline(now);
        let caret_deadline = self.next_caret_blink_deadline(now);
        let click_deadline = self.pending_click.as_ref().map(|pending| pending.deadline);
        let media_deadline = self.media_manager.next_frame_deadline(now);
        [
            animation_deadline,
            controller_deadline,
            caret_deadline,
            click_deadline,
            media_deadline,
        ]
        .into_iter()
        .flatten()
        .min()
    }

    fn reset_caret_blink(&mut self, now: Instant) {
        if self.focused_input.is_some() {
            self.caret_blink_started_at = Some(now);
            self.invalidate_scene();
        }
    }

    fn input_caret_visible(&self, now: Instant) -> bool {
        let Some(_) = self.focused_input else {
            return false;
        };
        let Some(started_at) = self.caret_blink_started_at else {
            return true;
        };
        let phase = now.saturating_duration_since(started_at).as_millis()
            / CARET_BLINK_INTERVAL.as_millis().max(1);
        phase % 2 == 0
    }

    fn next_caret_blink_deadline(&self, now: Instant) -> Option<Instant> {
        let Some(_) = self.focused_input else {
            return None;
        };
        let Some(started_at) = self.caret_blink_started_at else {
            return Some(now + CARET_BLINK_INTERVAL);
        };
        let interval_ms = CARET_BLINK_INTERVAL.as_millis().max(1);
        let elapsed_ms = now.saturating_duration_since(started_at).as_millis();
        let next_boundary_ms = ((elapsed_ms / interval_ms) + 1) * interval_ms;
        Some(started_at + Duration::from_millis(next_boundary_ms as u64))
    }

    fn flush_pending_click_if_due(&mut self, now: Instant) {
        let should_flush = self
            .pending_click
            .as_ref()
            .map(|pending| pending.deadline <= now)
            .unwrap_or(false);
        if !should_flush {
            return;
        }

        if let Some(pending) = self.pending_click.take() {
            if let Some(command) = pending.command {
                self.execute_command(&command);
            }
        }
    }

    fn focused_input_state(&self, id: WidgetId) -> Option<&InputEditState> {
        self.input_states.get(&id)
    }

    fn focused_input_snapshot(&self) -> Option<InputSnapshot<VM>> {
        let id = self.focused_input?;
        self.widget_tree.as_ref()?.input_snapshot(id)
    }

    fn ime_cursor_request_data(caret_rect: Rect) -> ImeRequestData {
        ImeRequestData::default().with_cursor_area(
            PhysicalPosition::new(caret_rect.x as i32, caret_rect.y as i32).into(),
            PhysicalSize::new(
                caret_rect.width.ceil().max(1.0) as u32,
                caret_rect.height.ceil().max(1.0) as u32,
            )
            .into(),
        )
    }

    fn ime_enable_request(&mut self) -> Option<ImeEnableRequest> {
        let request_data = Self::ime_cursor_request_data(self.render_primitives().ime_cursor_area?)
            .with_hint_and_purpose(ImeHint::NONE, ImePurpose::Normal);
        ImeEnableRequest::new(
            ImeCapabilities::new()
                .with_cursor_area()
                .with_hint_and_purpose(),
            request_data,
        )
    }

    fn sync_ime_allowed(&mut self) {
        let request = if self.focused_input.is_some() {
            self.ime_enable_request().map(ImeRequest::Enable)
        } else {
            Some(ImeRequest::Disable)
        };

        if let (Some(window), Some(request)) = (self.window.as_ref(), request) {
            let _ = window.request_ime_update(request);
        }
    }

    fn ensure_input_state(&mut self, widget_id: WidgetId, text: &str) -> &mut InputEditState {
        self.input_states
            .entry(widget_id)
            .and_modify(|state| *state = state.clone().clamped_to(text))
            .or_insert_with(|| InputEditState::caret_at(text))
    }

    fn update_input_state(
        &mut self,
        widget_id: WidgetId,
        text: &str,
        update: impl FnOnce(&mut InputEditState),
    ) {
        let state = self.ensure_input_state(widget_id, text);
        update(state);
        *state = state.clone().clamped_to(text);
        if self.focused_input == Some(widget_id) {
            self.reset_caret_blink(Instant::now());
        }
        self.invalidate_scene();
    }

    fn set_input_focus_state(&mut self, widget_id: WidgetId, text: &str) {
        let state = self.ensure_input_state(widget_id, text);
        *state = InputEditState::caret_at(text);
        self.caret_blink_started_at = Some(Instant::now());
        self.invalidate_scene();
    }

    fn clear_input_composition(&mut self, widget_id: WidgetId, text: &str) {
        if let Some(state) = self.input_states.get_mut(&widget_id) {
            state.composition = None;
            *state = state.clone().clamped_to(text);
            if self.focused_input == Some(widget_id) {
                self.reset_caret_blink(Instant::now());
            }
            self.invalidate_scene();
        }
    }

    fn apply_input_text_change(
        &mut self,
        snapshot: &InputSnapshot<VM>,
        new_text: String,
        new_cursor: usize,
    ) {
        {
            let state = self.ensure_input_state(snapshot.id, &new_text);
            state.cursor = new_cursor.min(new_text.len());
            state.anchor = state.cursor;
            state.composition = None;
        }
        self.reset_caret_blink(Instant::now());

        if let Some(command) = snapshot.on_change.clone() {
            self.execute_value_command(&command, new_text);
        }

        self.invalidate_scene();
        if let Some(window) = self.window.as_ref() {
            window.request_redraw();
        }
    }

    fn insert_input_text(&mut self, snapshot: &InputSnapshot<VM>, inserted: &str) {
        if snapshot.on_change.is_none() || inserted.is_empty() {
            return;
        }

        let sanitized = normalize_single_line_text(inserted);
        if sanitized.is_empty() {
            return;
        }

        let state = self
            .focused_input_state(snapshot.id)
            .cloned()
            .unwrap_or_else(|| InputEditState::caret_at(&snapshot.text))
            .clamped_to(&snapshot.text);
        let replace_range = state
            .selection_range()
            .unwrap_or((state.cursor, state.cursor));
        let mut next_text = snapshot.text.clone();
        next_text.replace_range(replace_range.0..replace_range.1, &sanitized);
        self.apply_input_text_change(snapshot, next_text, replace_range.0 + sanitized.len());
    }

    fn handle_input_ime(&mut self, ime: Ime) {
        let Some(snapshot) = self.focused_input_snapshot() else {
            return;
        };
        let current_text = snapshot.text.clone();
        let state = self
            .focused_input_state(snapshot.id)
            .cloned()
            .unwrap_or_else(|| InputEditState::caret_at(&current_text))
            .clamped_to(&current_text);

        match ime {
            Ime::Enabled => {
                self.sync_ime_allowed();
            }
            Ime::Preedit(text, cursor) => {
                let replace_range = state
                    .composition
                    .as_ref()
                    .map(|composition| composition.replace_range)
                    .unwrap_or_else(|| {
                        state
                            .selection_range()
                            .unwrap_or((state.cursor, state.cursor))
                    });
                self.update_input_state(snapshot.id, &current_text, |edit| {
                    if text.is_empty() {
                        edit.composition = None;
                    } else {
                        edit.composition = Some(crate::ui::widget::CompositionState {
                            replace_range,
                            text,
                            cursor,
                        });
                    }
                });
                if let Some(window) = self.window.as_ref() {
                    window.request_redraw();
                }
            }
            Ime::Commit(text) => {
                let replace_range = state
                    .composition
                    .as_ref()
                    .map(|composition| composition.replace_range)
                    .unwrap_or_else(|| {
                        state
                            .selection_range()
                            .unwrap_or((state.cursor, state.cursor))
                    });
                let sanitized = normalize_single_line_text(&text);
                let mut next_text = current_text.clone();
                next_text.replace_range(replace_range.0..replace_range.1, &sanitized);
                self.apply_input_text_change(
                    &snapshot,
                    next_text,
                    replace_range.0 + sanitized.len(),
                );
            }
            Ime::Disabled => {
                self.clear_input_composition(snapshot.id, &current_text);
            }
            Ime::DeleteSurrounding { .. } => {}
        }
    }

    fn handle_input_keyboard_event(&mut self, event: &KeyEvent) {
        let Some(snapshot) = self.focused_input_snapshot() else {
            return;
        };

        if event.state != ElementState::Pressed {
            return;
        }

        self.reset_caret_blink(Instant::now());

        let text = snapshot.text.clone();
        let mut state = self
            .focused_input_state(snapshot.id)
            .cloned()
            .unwrap_or_else(|| InputEditState::caret_at(&text))
            .clamped_to(&text);
        let extend_selection = self.modifiers.shift_key();

        if is_primary_shortcut_modifier(self.modifiers) {
            match event.physical_key {
                PhysicalKey::Code(KeyCode::KeyA) => {
                    state.cursor = text.len();
                    state.anchor = 0;
                    self.input_states.insert(snapshot.id, state);
                    self.invalidate_scene();
                }
                PhysicalKey::Code(KeyCode::KeyC) => {
                    if let Some((start, end)) = state.selection_range() {
                        self.clipboard.set_text(text[start..end].to_string());
                    }
                }
                PhysicalKey::Code(KeyCode::KeyX) => {
                    if let Some((start, end)) = state.selection_range() {
                        self.clipboard.set_text(text[start..end].to_string());
                        if snapshot.on_change.is_some() {
                            let mut next_text = text.clone();
                            next_text.replace_range(start..end, "");
                            self.apply_input_text_change(&snapshot, next_text, start);
                        }
                    }
                }
                PhysicalKey::Code(KeyCode::KeyV) => {
                    if let Some(clipboard_text) = self.clipboard.get_text() {
                        self.insert_input_text(&snapshot, &clipboard_text);
                    }
                }
                _ => {}
            }
            return;
        }

        match event.physical_key {
            PhysicalKey::Code(KeyCode::ArrowLeft) => {
                let next = if !extend_selection {
                    state.selection_range().map(|(start, _)| start)
                } else {
                    None
                }
                .unwrap_or_else(|| previous_grapheme_boundary(&text, state.cursor));
                move_cursor(&mut state, next, extend_selection);
            }
            PhysicalKey::Code(KeyCode::ArrowRight) => {
                let next = if !extend_selection {
                    state.selection_range().map(|(_, end)| end)
                } else {
                    None
                }
                .unwrap_or_else(|| next_grapheme_boundary(&text, state.cursor));
                move_cursor(&mut state, next, extend_selection);
            }
            PhysicalKey::Code(KeyCode::Home) => move_cursor(&mut state, 0, extend_selection),
            PhysicalKey::Code(KeyCode::End) => {
                move_cursor(&mut state, text.len(), extend_selection)
            }
            PhysicalKey::Code(KeyCode::Backspace) => {
                if snapshot.on_change.is_none() {
                    return;
                }
                let (start, end) = state.selection_range().unwrap_or_else(|| {
                    let previous = previous_grapheme_boundary(&text, state.cursor);
                    (previous, state.cursor)
                });
                if start == end {
                    return;
                }
                let mut next_text = text.clone();
                next_text.replace_range(start..end, "");
                self.apply_input_text_change(&snapshot, next_text, start);
                return;
            }
            PhysicalKey::Code(KeyCode::Delete) => {
                if snapshot.on_change.is_none() {
                    return;
                }
                let (start, end) = state.selection_range().unwrap_or_else(|| {
                    let next = next_grapheme_boundary(&text, state.cursor);
                    (state.cursor, next)
                });
                if start == end {
                    return;
                }
                let mut next_text = text.clone();
                next_text.replace_range(start..end, "");
                self.apply_input_text_change(&snapshot, next_text, start);
                return;
            }
            _ => {
                if let Some(input) = event.text.as_ref() {
                    self.insert_input_text(&snapshot, input);
                    return;
                }
                return;
            }
        }

        self.input_states.insert(snapshot.id, state);
        self.invalidate_scene();
        if let Some(window) = self.window.as_ref() {
            window.request_redraw();
        }
    }

    fn hit_test(&mut self, viewport: Rect) -> Option<HitInteraction<VM>> {
        self.widget_tree.as_ref()?.hit_test(
            &self.font_manager,
            &self.theme,
            &self.media_manager,
            &mut self.animation_engine,
            self.hovered_scrollbar,
            self.active_scrollbar_drag.map(|drag| drag.handle),
            &self.scroll_states,
            viewport,
            self.cursor_position,
            self.focused_input,
        )
    }

    fn hover_path(&mut self, viewport: Rect) -> Vec<HoveredWidget<VM>> {
        self.widget_tree
            .as_ref()
            .map(|tree| {
                tree.hit_path(
                    &self.font_manager,
                    &self.theme,
                    &self.media_manager,
                    &mut self.animation_engine,
                    self.hovered_scrollbar,
                    self.active_scrollbar_drag.map(|drag| drag.handle),
                    &self.scroll_states,
                    viewport,
                    self.cursor_position,
                    self.focused_input,
                )
                .into_iter()
                .map(|interaction| match interaction {
                    HitInteraction::Widget {
                        id, interactions, ..
                    } => HoveredWidget {
                        widget_id: id,
                        cursor_style: interactions.cursor_style,
                        on_mouse_enter: interactions.on_mouse_enter,
                        on_mouse_leave: interactions.on_mouse_leave,
                        on_mouse_move: interactions.on_mouse_move,
                    },
                    HitInteraction::FocusInput {
                        id, interactions, ..
                    } => HoveredWidget {
                        widget_id: id,
                        cursor_style: interactions
                            .cursor_style
                            .or(Some(crate::ui::widget::CursorStyle::Text)),
                        on_mouse_enter: interactions.on_mouse_enter,
                        on_mouse_leave: interactions.on_mouse_leave,
                        on_mouse_move: interactions.on_mouse_move,
                    },
                })
                .collect()
            })
            .unwrap_or_default()
    }

    fn handle_hover(&mut self, viewport: Rect) {
        let cursor_position = self.cursor_position;
        let next_hovered = self.hover_path(viewport);
        let mut prefix_len = 0usize;
        while prefix_len < self.hovered_widgets.len()
            && prefix_len < next_hovered.len()
            && self.hovered_widgets[prefix_len].widget_id == next_hovered[prefix_len].widget_id
        {
            prefix_len += 1;
        }

        let previous_hovered = std::mem::take(&mut self.hovered_widgets);
        for previous in previous_hovered[prefix_len..].iter().rev() {
            if let Some(command) = previous.on_mouse_leave.clone() {
                self.execute_command(&command);
            }
        }

        for hovered in next_hovered[prefix_len..].iter() {
            if let Some(command) = hovered.on_mouse_enter.clone() {
                self.execute_command(&command);
            }
        }

        if let Some(position) = cursor_position {
            for hovered in &next_hovered {
                if let Some(command) = hovered.on_mouse_move.clone() {
                    self.execute_value_command(&command, position);
                }
            }
        }

        self.hovered_widgets = next_hovered;
        self.sync_scrollbar_hover();
        self.update_cursor_icon();
    }

    fn handle_mouse_wheel(&mut self, delta: MouseScrollDelta) -> bool {
        let Some(cursor_position) = self.cursor_position else {
            return false;
        };

        let mut scroll_delta = mouse_scroll_delta(delta);
        if scroll_delta.x.abs() <= f32::EPSILON && self.modifiers.shift_key() {
            scroll_delta.x = scroll_delta.y;
            scroll_delta.y = 0.0;
        }
        if scroll_delta.x.abs() <= f32::EPSILON && scroll_delta.y.abs() <= f32::EPSILON {
            return false;
        }

        let rendered = self.render_primitives();
        for region in rendered.scroll_regions.iter().rev().copied() {
            if region.visible_frame.is_empty() || !region.visible_frame.contains(cursor_position) {
                continue;
            }

            let max_offset = region.max_offset();
            let mut next_offset = region.scroll_offset;
            if region.can_scroll_x() {
                next_offset.x = (next_offset.x - scroll_delta.x).clamp(0.0, max_offset.x);
            }
            if region.can_scroll_y() {
                next_offset.y = (next_offset.y - scroll_delta.y).clamp(0.0, max_offset.y);
            }

            if (next_offset.x - region.scroll_offset.x).abs() > 0.01
                || (next_offset.y - region.scroll_offset.y).abs() > 0.01
            {
                self.set_scroll_offset(region.id, next_offset);
                return true;
            }
        }

        false
    }

    fn sync_scrollbar_hover(&mut self) {
        let next_hovered = if let Some(drag) = self.active_scrollbar_drag {
            Some(drag.handle)
        } else {
            self.scrollbar_thumb_hit()
        };

        if self.hovered_scrollbar != next_hovered {
            self.hovered_scrollbar = next_hovered;
            self.invalidate_scene();
        }
    }

    fn scrollbar_thumb_hit(&mut self) -> Option<ScrollbarHandle> {
        let cursor_position = self.cursor_position?;
        let rendered = self.render_primitives();
        rendered.scroll_regions.iter().rev().find_map(|region| {
            if region.visible_frame.is_empty() || !region.visible_frame.contains(cursor_position) {
                return None;
            }
            if region
                .vertical_thumb
                .map(|thumb| thumb.contains(cursor_position))
                .unwrap_or(false)
            {
                return Some(ScrollbarHandle {
                    id: region.id,
                    axis: ScrollbarAxis::Vertical,
                });
            }
            if region
                .horizontal_thumb
                .map(|thumb| thumb.contains(cursor_position))
                .unwrap_or(false)
            {
                return Some(ScrollbarHandle {
                    id: region.id,
                    axis: ScrollbarAxis::Horizontal,
                });
            }
            None
        })
    }

    fn begin_scrollbar_drag(&mut self) -> bool {
        let Some(handle) = self.scrollbar_thumb_hit() else {
            return false;
        };
        let Some(cursor_position) = self.cursor_position else {
            return false;
        };
        let rendered = self.render_primitives();
        let Some(region) = rendered
            .scroll_regions
            .iter()
            .copied()
            .find(|region| region.id == handle.id)
        else {
            return false;
        };

        let (track, thumb, max_offset) = match handle.axis {
            ScrollbarAxis::Horizontal => (
                region.horizontal_track,
                region.horizontal_thumb,
                region.max_offset().x,
            ),
            ScrollbarAxis::Vertical => (
                region.vertical_track,
                region.vertical_thumb,
                region.max_offset().y,
            ),
        };
        let (Some(track), Some(thumb)) = (track, thumb) else {
            return false;
        };

        self.active_scrollbar_drag = Some(ScrollbarDrag {
            handle,
            start_cursor: cursor_position,
            start_scroll_offset: region.scroll_offset,
            track,
            thumb,
            max_offset,
        });
        self.hovered_scrollbar = Some(handle);
        self.invalidate_scene();
        true
    }

    fn handle_scrollbar_drag(&mut self) -> bool {
        let Some(drag) = self.active_scrollbar_drag else {
            return false;
        };
        let Some(cursor_position) = self.cursor_position else {
            return false;
        };

        let (travel, delta) = match drag.handle.axis {
            ScrollbarAxis::Horizontal => (
                (drag.track.width - drag.thumb.width).max(0.0),
                cursor_position.x - drag.start_cursor.x,
            ),
            ScrollbarAxis::Vertical => (
                (drag.track.height - drag.thumb.height).max(0.0),
                cursor_position.y - drag.start_cursor.y,
            ),
        };

        let mut next_offset = drag.start_scroll_offset;
        let axis_offset = if travel <= 0.0 || drag.max_offset <= 0.0 {
            0.0
        } else {
            (delta / travel) * drag.max_offset
        };

        match drag.handle.axis {
            ScrollbarAxis::Horizontal => {
                next_offset.x =
                    (drag.start_scroll_offset.x + axis_offset).clamp(0.0, drag.max_offset)
            }
            ScrollbarAxis::Vertical => {
                next_offset.y =
                    (drag.start_scroll_offset.y + axis_offset).clamp(0.0, drag.max_offset)
            }
        }

        let previous = self
            .scroll_states
            .get(&drag.handle.id)
            .copied()
            .unwrap_or(Point::ZERO);
        if (previous.x - next_offset.x).abs() > 0.01 || (previous.y - next_offset.y).abs() > 0.01 {
            self.set_scroll_offset(drag.handle.id, next_offset);
            return true;
        }

        false
    }

    fn end_scrollbar_drag(&mut self) -> bool {
        if self.active_scrollbar_drag.take().is_none() {
            return false;
        }
        self.sync_scrollbar_hover();
        self.invalidate_scene();
        true
    }

    fn update_cursor_icon(&self) {
        if let Some(window) = self.window.as_ref() {
            let cursor = if self.active_scrollbar_drag.is_some() {
                Cursor::Icon(CursorIcon::Pointer)
            } else if self.hovered_scrollbar.is_some() {
                Cursor::Icon(CursorIcon::Pointer)
            } else if let Some(cursor_style) = self
                .hovered_widgets
                .last()
                .and_then(|hovered| hovered.cursor_style)
            {
                Cursor::Icon(cursor_icon(cursor_style))
            } else {
                Cursor::Icon(CursorIcon::Default)
            };
            window.set_cursor(cursor);
        }
    }

    fn set_scroll_offset(&mut self, widget_id: WidgetId, offset: Point) {
        if offset.x.abs() <= 0.01 && offset.y.abs() <= 0.01 {
            self.scroll_states.remove(&widget_id);
        } else {
            self.scroll_states.insert(widget_id, offset);
        }
        self.scroll_epoch = self.scroll_epoch.wrapping_add(1);
        self.invalidate_scene();
        self.invalidation.mark_dirty();
    }

    fn update_focus(
        &mut self,
        next_widget: Option<FocusedWidget<VM>>,
        next_input: Option<WidgetId>,
        on_focus: Option<Command<VM>>,
        input_text: Option<&str>,
    ) {
        let current_id = self
            .focused_widget
            .as_ref()
            .map(|focused| focused.widget_id);
        let next_id = next_widget.as_ref().map(|focused| focused.widget_id);

        if current_id == next_id {
            self.focused_input = next_input;
            self.caret_blink_started_at = next_input.map(|_| Instant::now());
            self.sync_ime_allowed();
            return;
        }

        if let Some(previous_input) = self.focused_input {
            if let Some(snapshot) = self
                .widget_tree
                .as_ref()
                .and_then(|tree| tree.input_snapshot(previous_input))
            {
                self.clear_input_composition(previous_input, &snapshot.text);
            }
        }

        let mut fired_handler = false;
        if let Some(previous) = self.focused_widget.take() {
            if let Some(command) = previous.on_blur {
                self.execute_command(&command);
                fired_handler = true;
            }
        }

        self.focused_widget = next_widget;
        self.focused_input = next_input;
        self.caret_blink_started_at = next_input.map(|_| Instant::now());
        if let (Some(input_id), Some(text)) = (next_input, input_text) {
            self.set_input_focus_state(input_id, text);
        }
        self.sync_ime_allowed();

        if let Some(command) = on_focus {
            if next_id.is_some() {
                self.execute_command(&command);
                fired_handler = true;
            }
        }

        if fired_handler {
            self.invalidate_scene();
        }
    }

    fn handle_mouse_press(&mut self, viewport: Rect, now: Instant) {
        self.flush_pending_click_if_due(now);

        let hit = self.hit_test(viewport);
        let Some(hit) = hit else {
            self.update_focus(None, None, None, None);
            self.pending_click = None;
            return;
        };

        let (widget_id, interactions, focus_target, focus_input, focus_command, input_text) =
            match hit {
                HitInteraction::Widget {
                    id,
                    interactions,
                    focusable,
                } => (
                    id,
                    interactions.clone(),
                    focusable.then_some(id),
                    None,
                    focusable.then_some(interactions.on_focus.clone()).flatten(),
                    None,
                ),
                HitInteraction::FocusInput {
                    id,
                    interactions,
                    text,
                    ..
                } => (
                    id,
                    interactions.clone(),
                    Some(id),
                    Some(id),
                    interactions.on_focus.clone(),
                    Some(text),
                ),
            };

        self.update_focus(
            focus_target.map(|id| FocusedWidget {
                widget_id: id,
                on_blur: interactions.on_blur.clone(),
            }),
            focus_input,
            focus_command,
            input_text.as_deref(),
        );

        let is_double_click = self
            .pending_click
            .as_ref()
            .map(|pending| pending.widget_id == widget_id && pending.deadline > now)
            .unwrap_or(false);

        if is_double_click {
            self.pending_click = None;
            if let Some(command) = interactions.on_double_click.or(interactions.on_click) {
                self.execute_command(&command);
            }
            return;
        }

        if interactions.on_double_click.is_some() {
            self.pending_click = Some(PendingClick {
                widget_id,
                deadline: now + DOUBLE_CLICK_THRESHOLD,
                command: interactions.on_click,
            });
        } else if let Some(command) = interactions.on_click {
            self.execute_command(&command);
        } else {
            self.pending_click = None;
        }
    }

    fn window_id(&self) -> Option<WindowId> {
        self.window_id
    }

    fn create_or_resume_surface(&mut self, event_loop: &dyn ActiveEventLoop) {
        self.set_dialog_proxy(event_loop);

        if self.window.is_some() && self.renderer.is_some() {
            return;
        }

        if self.window.is_some() {
            self.resume_existing_window(event_loop);
            return;
        }

        let mut attributes = WindowAttributes::default()
            .with_transparent(!cfg!(all(target_env = "ohos", feature = "ohos")))
            .with_title(self.config.title.clone())
            .with_surface_size(self.config.size)
            .with_visible(false);


        if let Some(icon_bytes) = self.config.window_icon {
            match image::load_from_memory(icon_bytes) {
                Ok(image) => {
                    let (w, h) = image.dimensions();
                    let rgba = image.into_rgba8().into_raw();

                    match RgbaIcon::new(rgba, w, h) {
                        Ok(ok) => {
                            let icon = Icon::from(ok);
                            attributes = attributes.with_window_icon(Some(icon));
                        }
                        Err(err) => {
                            self.fail(event_loop, TguiError::Icon(err.to_string()));
                        }
                    }
                }
                Err(err) => {
                    self.fail(event_loop, TguiError::Icon(err.to_string()));
                }
            }
        }


        let window: Arc<dyn Window> = match event_loop.create_window(attributes) {
            Ok(window) => Arc::from(window),
            Err(error) => {
                self.fail(event_loop, error.into());
                return;
            }
        };

        self.theme = resolve_theme(
            &self.active_theme_selection(),
            resolve_window_theme(
                Some(window.as_ref()),
                #[cfg(all(target_os = "android", feature = "android"))]
                self.android_app.as_ref(),
            ),
        );
        #[cfg(all(target_os = "android", feature = "android"))]
        {
            let theme = self.theme.clone();
            self.sync_system_bar_style(&theme);
        }
        let clear_color =
            if self.window_bindings.clear_color.is_some() || self.config.clear_color_overridden {
                self.config.clear_color
            } else {
                self.theme.palette.window_background
            };

        let renderer = match Renderer::new(window.clone(), clear_color, &self.config.fonts) {
            Ok(renderer) => renderer,
            Err(error) => {
                self.fail(event_loop, error);
                return;
            }
        };

        self.window_id = Some(window.id());
        self.renderer = Some(renderer);
        self.window = Some(window);

        if !self.render_hidden_frame(event_loop) {
            return;
        }

        if let Some(window) = self.window.as_ref() {
            window.request_redraw();
            window.set_visible(true);
        }
    }

    fn handle_bound_window_event(
        &mut self,
        event_loop: &dyn ActiveEventLoop,
        event: WindowEvent,
    ) -> bool {
        if let WindowEvent::PointerMoved { position, .. } = &event {
            self.set_pointer_position(*position);
        }

        if let WindowEvent::ModifiersChanged(modifiers) = &event {
            self.modifiers = modifiers.state();
        }

        if matches!(event, WindowEvent::PointerLeft { .. }) {
            self.clear_pointer_position();
        }

        if Self::should_dispatch_widget_event(&event) {
            let viewport = self.viewport_rect();
            let previous_focus = self.focused_input;

            match &event {
                WindowEvent::PointerMoved { .. } => {
                    if self.active_scrollbar_drag.is_some() {
                        self.handle_scrollbar_drag();
                        self.sync_scrollbar_hover();
                        self.update_cursor_icon();
                    } else {
                        self.handle_hover(viewport);
                    }
                }
                WindowEvent::MouseWheel { delta, .. } => {
                    self.handle_mouse_wheel(*delta);
                }
                WindowEvent::PointerButton {
                    state: ElementState::Pressed,
                    position,
                    button,
                    ..
                } => {
                    if button.clone().mouse_button() == Some(MouseButton::Left) {
                        self.set_pointer_position(*position);
                        if !self.begin_scrollbar_drag() {
                            self.handle_mouse_press(viewport, Instant::now());
                        } else {
                            self.update_cursor_icon();
                        }
                    }
                }
                WindowEvent::KeyboardInput { event, .. } => {
                    self.handle_input_keyboard_event(event);
                }
                _ => {}
            }

            if self.focused_input != previous_focus {
                self.invalidate_scene();
            }

            if let Some(window) = self.window.as_ref() {
                window.request_redraw();
            }
        }

        if let Some(window_command) = self
            .commands
            .iter()
            .find(|entry| entry.trigger.matches(&event))
            .cloned()
        {
            self.execute_command(&window_command.command);
        }

        match event {
            WindowEvent::CloseRequested => return self.close_policy() == WindowClosePolicy::Close,
            WindowEvent::Focused(false) => {
                self.end_scrollbar_drag();
                self.update_focus(None, None, None, None);
                if let Some(window) = self.window.as_ref() {
                    window.request_redraw();
                }
            }
            WindowEvent::ThemeChanged(theme) => {
                self.apply_window_theme(Some(theme));
                self.sync_bindings(Instant::now());
                if let Some(window) = self.window.as_ref() {
                    window.request_redraw();
                }
            }
            WindowEvent::ScaleFactorChanged { .. } => {
                self.apply_window_theme(None);
                self.invalidate_scene();
                if let Some(window) = self.window.as_ref() {
                    if let Some(renderer) = self.renderer.as_mut() {
                        renderer.resize(window.surface_size());
                    }
                    window.request_redraw();
                }
            }
            WindowEvent::Ime(ime) => {
                self.handle_input_ime(ime);
            }
            WindowEvent::PointerButton {
                state: ElementState::Released,
                position,
                button,
                ..
            } => {
                if button.clone().mouse_button() == Some(MouseButton::Left) {
                    self.set_pointer_position(position);
                    self.end_scrollbar_drag();
                    if let Some(window) = self.window.as_ref() {
                        window.request_redraw();
                    }
                }
            }
            WindowEvent::SurfaceResized(size) => {
                self.invalidate_scene();
                if let Some(renderer) = self.renderer.as_mut() {
                    renderer.resize(size);
                }

                if let Some(window) = self.window.as_ref() {
                    window.request_redraw();
                }
            }
            WindowEvent::RedrawRequested => match self.render_current_frame() {
                Ok(RenderStatus::Rendered | RenderStatus::SkipFrame) => {}
                Ok(RenderStatus::ReconfigureSurface) => {
                    if let Some(renderer) = self.renderer.as_mut() {
                        renderer.reconfigure();
                    }
                    match self.render_current_frame() {
                        Ok(RenderStatus::Rendered | RenderStatus::SkipFrame) => {}
                        Ok(RenderStatus::ReconfigureSurface) => {}
                        Err(error) => self.fail(event_loop, error),
                    }
                }
                Err(error) => self.fail(event_loop, error),
            },
            _ => {}
        }

        false
    }

    fn handle_bound_about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) {
        let now = Instant::now();
        let theme_changed = self.refresh_platform_theme();
        if theme_changed {
            self.sync_bindings(now);
            if let Some(window) = self.window.as_ref() {
                window.request_redraw();
            }
        }
        self.request_redraw_if_dirty(now);
        #[cfg(all(target_os = "android", feature = "android"))]
        let animation_frame_advanced = self.drive_animations(event_loop, now);
        #[cfg(not(all(target_os = "android", feature = "android")))]
        self.drive_animations(event_loop, now);
        if self.media_manager.has_active_video() {
            self.invalidate_scene();
            if let Some(window) = self.window.as_ref() {
                window.request_redraw();
            }
        }
        #[cfg(all(target_os = "android", feature = "android"))]
        if theme_changed || animation_frame_advanced {
            self.render_immediately(event_loop);
        }
    }
}

struct ResolvedWindowSpec<VM> {
    key: String,
    role: WindowRole,
    config: ApplicationConfig,
    window_bindings: WindowBindings,
    widget_tree: Option<WidgetTree<VM>>,
    commands: Vec<WindowCommand<VM>>,
    close_policy: WindowClosePolicy,
}

struct MultiWindowHandler<VM> {
    config: ApplicationConfig,
    view_model: Arc<Mutex<VM>>,
    windows: WindowSetFactory<VM>,
    invalidation: InvalidationSignal,
    animations: AnimationCoordinator,
    dialog_dispatcher: AsyncDialogDispatcher<VM>,
    dialog_receiver: AsyncDialogReceiver<VM>,
    next_window_instance_id: u64,
    windows_by_key: HashMap<String, BoundRuntimeHandler<VM>>,
    window_keys_by_id: HashMap<WindowId, String>,
    closed_window_keys: HashSet<String>,
    last_window_sync_revision: u64,
    windows_need_sync: bool,
    shutting_down: bool,
    error: Option<TguiError>,
}

impl<VM: ViewModel> MultiWindowHandler<VM> {
    fn new(
        config: ApplicationConfig,
        view_model: Arc<Mutex<VM>>,
        windows: WindowSetFactory<VM>,
        invalidation: InvalidationSignal,
        animations: AnimationCoordinator,
    ) -> Self {
        let (dialog_dispatcher, dialog_receiver) = async_dialog_channel();
        Self {
            config,
            view_model,
            windows,
            invalidation,
            animations,
            dialog_dispatcher,
            dialog_receiver,
            next_window_instance_id: 1,
            windows_by_key: HashMap::new(),
            window_keys_by_id: HashMap::new(),
            closed_window_keys: HashSet::new(),
            last_window_sync_revision: 0,
            windows_need_sync: true,
            shutting_down: false,
            error: None,
        }
    }

    fn fail(&mut self, event_loop: &dyn ActiveEventLoop, error: TguiError) {
        eprintln!("tgui multi-window runtime failed: {error}");
        self.error = Some(error);
        event_loop.exit();
    }

    fn next_window_instance_id(&mut self) -> u64 {
        let next = self.next_window_instance_id;
        self.next_window_instance_id = self.next_window_instance_id.wrapping_add(1);
        next
    }

    fn set_dialog_proxy(&self, event_loop: &dyn ActiveEventLoop) {
        self.dialog_dispatcher.set_proxy(event_loop.create_proxy());
    }

    fn drain_dialog_completions(&mut self) {
        let completions: Vec<_> = self.dialog_receiver.try_iter().collect();
        for completion in completions {
            let Some(window) = self.windows_by_key.get_mut(&completion.window_key) else {
                continue;
            };
            if completion.window_instance_id != window.window_instance_id {
                continue;
            }

            let context = window.command_context();
            window.with_view_model(|view_model| (completion.callback)(view_model, &context));
            window.invalidate_scene();
            self.invalidation.mark_dirty();
            if let Some(native_window) = window.window.as_ref() {
                native_window.request_redraw();
            }
        }
    }

    fn resolve_windows(&self) -> Result<Vec<ResolvedWindowSpec<VM>>, TguiError> {
        let view_model = self.view_model.lock().expect("view model lock poisoned");
        let specs = (self.windows.factory)(&view_model);
        let mut keys = HashSet::new();
        let mut main_window_count = 0usize;
        let mut resolved = Vec::with_capacity(specs.len());

        for spec in specs {
            let key = spec.key.clone();
            if !keys.insert(key.clone()) {
                return Err(TguiError::Unsupported(format!(
                    "window factory returned a duplicate window key: {key}"
                )));
            }

            if matches!(spec.role, WindowRole::Main) {
                main_window_count += 1;
            }

            let widget_tree = if self.windows_by_key.contains_key(&key) {
                None
            } else {
                spec.build_widget_tree(&view_model)
            };

            resolved.push(ResolvedWindowSpec {
                key,
                role: spec.role,
                config: spec.resolved_config(&self.config),
                window_bindings: spec.build_window_bindings(&view_model),
                widget_tree,
                commands: spec.commands,
                close_policy: spec.close_policy,
            });
        }

        if resolved.is_empty() {
            return Ok(resolved);
        }

        if main_window_count != 1 {
            return Err(TguiError::Unsupported(format!(
                "multi-window applications must declare exactly one main window, found {main_window_count}"
            )));
        }

        Ok(resolved)
    }

    fn main_window_is_blocked(&self) -> bool {
        self.windows_by_key
            .values()
            .any(BoundRuntimeHandler::blocks_main_window)
    }

    fn should_gate_main_window_event(event: &WindowEvent) -> bool {
        matches!(
            event,
            WindowEvent::PointerMoved { .. }
                | WindowEvent::PointerLeft { .. }
                | WindowEvent::PointerButton { .. }
                | WindowEvent::MouseWheel { .. }
                | WindowEvent::KeyboardInput { .. }
                | WindowEvent::Ime(_)
                | WindowEvent::ModifiersChanged(_)
        )
    }

    fn sync_windows(&mut self, event_loop: &dyn ActiveEventLoop, force: bool) {
        if self.shutting_down {
            return;
        }

        let revision = self.invalidation.revision();
        if !force
            && !self.windows_need_sync
            && !self.windows_by_key.is_empty()
            && revision == self.last_window_sync_revision
        {
            return;
        }

        let resolved = match self.resolve_windows() {
            Ok(resolved) => resolved,
            Err(error) => {
                self.fail(event_loop, error);
                return;
            }
        };

        let desired_keys: HashSet<String> =
            resolved.iter().map(|window| window.key.clone()).collect();
        self.closed_window_keys
            .retain(|key| desired_keys.contains(key));

        for resolved_window in resolved {
            if self.closed_window_keys.contains(&resolved_window.key) {
                continue;
            }

            let key = resolved_window.key.clone();
            if let Some(window) = self.windows_by_key.get_mut(&key) {
                window.set_definition(
                    resolved_window.role,
                    resolved_window.config,
                    resolved_window.window_bindings,
                    resolved_window.commands,
                    resolved_window.close_policy,
                );
                window.create_or_resume_surface(event_loop);
                if let Some(error) = window.error.take() {
                    self.fail(event_loop, error);
                    return;
                }
                self.window_keys_by_id
                    .retain(|_, existing_key| existing_key != &key);
                if let Some(window_id) = window.window_id() {
                    self.window_keys_by_id.insert(window_id, key);
                }
            } else {
                let mut window = BoundRuntimeHandler::new(
                    key.clone(),
                    self.next_window_instance_id(),
                    resolved_window.role,
                    resolved_window.config,
                    self.view_model.clone(),
                    resolved_window.window_bindings,
                    resolved_window.widget_tree,
                    resolved_window.commands,
                    self.invalidation.clone(),
                    self.animations.clone(),
                    self.dialog_dispatcher.clone(),
                    None,
                    #[cfg(all(target_os = "android", feature = "android"))]
                    None,
                );
                window.close_policy = resolved_window.close_policy;
                window.create_or_resume_surface(event_loop);
                if let Some(error) = window.error.take() {
                    self.fail(event_loop, error);
                    return;
                }
                if let Some(window_id) = window.window_id() {
                    self.window_keys_by_id.insert(window_id, key.clone());
                }
                self.windows_by_key.insert(key, window);
            }
        }

        let stale_keys: Vec<String> = self
            .windows_by_key
            .keys()
            .filter(|key| {
                !desired_keys.contains(*key) || self.closed_window_keys.contains(key.as_str())
            })
            .cloned()
            .collect();

        for key in stale_keys {
            self.remove_window(&key);
        }

        if self.windows_by_key.is_empty() {
            event_loop.exit();
        }

        self.last_window_sync_revision = revision;
        self.windows_need_sync = false;
    }

    fn remove_window(&mut self, key: &str) {
        if let Some(window) = self.windows_by_key.remove(key) {
            if let Some(window_id) = window.window_id() {
                self.window_keys_by_id.remove(&window_id);
            }
        }
    }
}

impl<VM: ViewModel> ApplicationHandler for MultiWindowHandler<VM> {
    fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
        self.set_dialog_proxy(event_loop);
        self.sync_windows(event_loop, true);
    }

    fn proxy_wake_up(&mut self, _event_loop: &dyn ActiveEventLoop) {
        self.drain_dialog_completions();
    }

    fn window_event(
        &mut self,
        event_loop: &dyn ActiveEventLoop,
        window_id: WindowId,
        event: WindowEvent,
    ) {
        let Some(key) = self.window_keys_by_id.get(&window_id).cloned() else {
            return;
        };

        let is_main_window = self
            .windows_by_key
            .get(&key)
            .map(BoundRuntimeHandler::is_main_window)
            .unwrap_or(false);

        if is_main_window
            && self.main_window_is_blocked()
            && Self::should_gate_main_window_event(&event)
        {
            return;
        }

        let close_requested = self
            .windows_by_key
            .get_mut(&key)
            .map(|window| window.handle_bound_window_event(event_loop, event))
            .unwrap_or(false);

        if let Some(window) = self.windows_by_key.get_mut(&key) {
            if let Some(error) = window.error.take() {
                self.fail(event_loop, error);
                return;
            }
        }

        if close_requested {
            if is_main_window && self.config.close_children_with_main {
                self.shutting_down = true;
                self.windows_by_key.clear();
                self.window_keys_by_id.clear();
                event_loop.exit();
                return;
            }

            self.closed_window_keys.insert(key.clone());
            self.remove_window(&key);
            if self.windows_by_key.is_empty() {
                event_loop.exit();
            }
        }
    }

    fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) {
        if self.shutting_down {
            event_loop.exit();
            return;
        }

        self.drain_dialog_completions();
        self.sync_windows(event_loop, false);
        if self.error.is_some() {
            return;
        }

        let keys: Vec<String> = self.windows_by_key.keys().cloned().collect();
        for key in keys {
            if let Some(window) = self.windows_by_key.get_mut(&key) {
                window.handle_bound_about_to_wait(event_loop);
                if let Some(error) = window.error.take() {
                    self.fail(event_loop, error);
                    return;
                }
                self.window_keys_by_id
                    .retain(|_, existing_key| existing_key != &key);
                if let Some(window_id) = window.window_id() {
                    self.window_keys_by_id.insert(window_id, key.clone());
                }
            }
        }
    }

    fn suspended(&mut self, _event_loop: &dyn ActiveEventLoop) {
        for window in self.windows_by_key.values_mut() {
            window.suspend();
        }
    }
}

impl ApplicationHandler for RuntimeHandler {
    fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
        if self.window.is_some() {
            self.resume_existing_window(event_loop);
            return;
        }

        let attributes = WindowAttributes::default()
            .with_transparent(!cfg!(all(target_env = "ohos", feature = "ohos")))
            .with_title(self.config.title.clone())
            .with_surface_size(self.config.size)
            .with_visible(false);

        let window: Arc<dyn Window> = match event_loop.create_window(attributes) {
            Ok(window) => Arc::from(window),
            Err(error) => {
                self.fail(event_loop, error.into());
                return;
            }
        };

        let clear_color = if self.config.clear_color_overridden {
            self.config.clear_color
        } else {
            self.resolved_theme(window.as_ref())
                .palette
                .window_background
        };
        #[cfg(all(target_os = "android", feature = "android"))]
        {
            let theme = self.resolved_theme(window.as_ref());
            self.sync_system_bar_style(&theme);
        }

        let renderer = match Renderer::new(window.clone(), clear_color, &self.config.fonts) {
            Ok(renderer) => renderer,
            Err(error) => {
                self.fail(event_loop, error);
                return;
            }
        };

        self.window_id = Some(window.id());
        self.renderer = Some(renderer);
        self.window = Some(window);

        if !self.render_hidden_frame(event_loop) {
            return;
        }

        if let Some(window) = self.window.as_ref() {
            window.request_redraw();
            window.set_visible(true);
        }
    }

    fn window_event(
        &mut self,
        event_loop: &dyn ActiveEventLoop,
        window_id: WindowId,
        event: WindowEvent,
    ) {
        if Some(window_id) != self.window_id {
            return;
        }

        match event {
            WindowEvent::CloseRequested => event_loop.exit(),
            WindowEvent::ThemeChanged(_) => {
                if !self.config.clear_color_overridden {
                    let resolved_theme = self
                        .window
                        .as_ref()
                        .map(|window| self.resolved_theme(window.as_ref()));
                    let clear_color = resolved_theme
                        .as_ref()
                        .map(|theme| theme.palette.window_background)
                        .unwrap_or(self.config.clear_color);
                    if let Some(renderer) = self.renderer.as_mut() {
                        renderer.set_clear_color(clear_color);
                    }
                    #[cfg(all(target_os = "android", feature = "android"))]
                    if let Some(theme) = resolved_theme.as_ref() {
                        self.sync_system_bar_style(theme);
                    }
                }
                if let Some(window) = self.window.as_ref() {
                    window.request_redraw();
                }
            }
            WindowEvent::ScaleFactorChanged { .. } => {
                if let Some(window) = self.window.clone() {
                    if !self.config.clear_color_overridden {
                        let theme = self.resolved_theme(window.as_ref());
                        if let Some(renderer) = self.renderer.as_mut() {
                            renderer.set_clear_color(theme.palette.window_background);
                            renderer.resize(window.surface_size());
                        }
                        #[cfg(all(target_os = "android", feature = "android"))]
                        self.sync_system_bar_style(&theme);
                    } else if let Some(renderer) = self.renderer.as_mut() {
                        renderer.resize(window.surface_size());
                    }
                    window.request_redraw();
                }
            }
            WindowEvent::SurfaceResized(size) => {
                if let Some(renderer) = self.renderer.as_mut() {
                    renderer.resize(size);
                }
            }
            WindowEvent::RedrawRequested => self.handle_runtime_redraw(event_loop),
            _ => {}
        }
    }

    fn about_to_wait(&mut self, _event_loop: &dyn ActiveEventLoop) {
        if let Some(window) = self.window.as_ref() {
            window.request_redraw();
        }
    }

    fn suspended(&mut self, _event_loop: &dyn ActiveEventLoop) {
        self.suspend();
    }
}

impl<VM: ViewModel> ApplicationHandler for BoundRuntimeHandler<VM> {
    fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
        self.create_or_resume_surface(event_loop);
    }

    fn proxy_wake_up(&mut self, _event_loop: &dyn ActiveEventLoop) {
        self.drain_dialog_completions();
    }

    fn window_event(
        &mut self,
        event_loop: &dyn ActiveEventLoop,
        window_id: WindowId,
        event: WindowEvent,
    ) {
        if Some(window_id) != self.window_id {
            return;
        }

        if self.handle_bound_window_event(event_loop, event) {
            event_loop.exit();
        }
    }

    fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) {
        self.drain_dialog_completions();
        self.handle_bound_about_to_wait(event_loop);
    }

    fn suspended(&mut self, _event_loop: &dyn ActiveEventLoop) {
        self.suspend();
    }
}

fn is_primary_shortcut_modifier(modifiers: ModifiersState) -> bool {
    #[cfg(target_os = "macos")]
    {
        modifiers.super_key()
    }

    #[cfg(not(target_os = "macos"))]
    {
        modifiers.control_key()
    }
}

fn normalize_single_line_text(text: &str) -> String {
    text.chars()
        .map(|ch| match ch {
            '\r' | '\n' => ' ',
            other => other,
        })
        .filter(|ch| !ch.is_control() || *ch == ' ')
        .collect()
}

fn mouse_scroll_delta(delta: MouseScrollDelta) -> Point {
    const LINE_SCROLL_STEP: f32 = 40.0;

    match delta {
        MouseScrollDelta::LineDelta(x, y) => Point {
            x: x * LINE_SCROLL_STEP,
            y: y * LINE_SCROLL_STEP,
        },
        MouseScrollDelta::PixelDelta(position) => Point {
            x: position.x as f32,
            y: position.y as f32,
        },
    }
}

fn cursor_icon(cursor_style: crate::ui::widget::CursorStyle) -> CursorIcon {
    match cursor_style {
        crate::ui::widget::CursorStyle::Default => CursorIcon::Default,
        crate::ui::widget::CursorStyle::Pointer => CursorIcon::Pointer,
        crate::ui::widget::CursorStyle::Text => CursorIcon::Text,
        crate::ui::widget::CursorStyle::Crosshair => CursorIcon::Crosshair,
        crate::ui::widget::CursorStyle::Move => CursorIcon::Move,
        crate::ui::widget::CursorStyle::NotAllowed => CursorIcon::NotAllowed,
        crate::ui::widget::CursorStyle::Grab => CursorIcon::Grab,
        crate::ui::widget::CursorStyle::Grabbing => CursorIcon::Grabbing,
        crate::ui::widget::CursorStyle::EwResize => CursorIcon::EwResize,
        crate::ui::widget::CursorStyle::NsResize => CursorIcon::NsResize,
        crate::ui::widget::CursorStyle::NeswResize => CursorIcon::NeswResize,
        crate::ui::widget::CursorStyle::NwseResize => CursorIcon::NwseResize,
    }
}

fn move_cursor(state: &mut InputEditState, next: usize, extend_selection: bool) {
    state.cursor = next;
    if !extend_selection {
        state.anchor = next;
    }
    state.composition = None;
}

fn grapheme_boundaries(text: &str) -> Vec<usize> {
    let mut boundaries = vec![0];
    for (index, grapheme) in text.grapheme_indices(true) {
        boundaries.push(index + grapheme.len());
    }
    boundaries
}

fn previous_grapheme_boundary(text: &str, cursor: usize) -> usize {
    grapheme_boundaries(text)
        .into_iter()
        .take_while(|boundary| *boundary < cursor)
        .last()
        .unwrap_or(0)
}

fn next_grapheme_boundary(text: &str, cursor: usize) -> usize {
    grapheme_boundaries(text)
        .into_iter()
        .find(|boundary| *boundary > cursor)
        .unwrap_or(text.len())
}

fn resolve_theme(selection: &ThemeSelection, window_theme: Option<WindowTheme>) -> Theme {
    match selection {
        ThemeSelection::System => Theme::from_window_theme(window_theme),
        ThemeSelection::Fixed(theme) => theme.clone(),
    }
}

fn resolve_window_theme(
    window: Option<&dyn Window>,
    #[cfg(all(target_os = "android", feature = "android"))] android_app: Option<&AndroidApp>,
) -> Option<WindowTheme> {
    #[cfg(all(target_os = "android", feature = "android"))]
    if let Some(app) = android_app {
        if let Some(theme) = resolve_android_window_theme(app) {
            return Some(theme);
        }
    }

    window.and_then(|window| window.theme())
}

#[cfg(all(target_os = "android", feature = "android"))]
fn resolve_android_window_theme(app: &AndroidApp) -> Option<WindowTheme> {
    resolve_android_window_theme_from_java(app).or_else(|| match app.config().ui_mode_night() {
        UiModeNight::No => Some(WindowTheme::Light),
        UiModeNight::Yes => Some(WindowTheme::Dark),
        UiModeNight::Any | UiModeNight::__Unknown(_) => None,
        _ => None,
    })
}

#[cfg(all(target_os = "android", feature = "android"))]
fn resolve_android_window_theme_from_java(app: &AndroidApp) -> Option<WindowTheme> {
    let vm = unsafe { JavaVM::from_raw(app.vm_as_ptr().cast()) };
    let activity_raw = app.activity_as_ptr() as jni::sys::jobject;

    vm.attach_current_thread(|env| -> jni::errors::Result<Option<WindowTheme>> {
        let activity = unsafe { env.as_cast_raw::<JObject>(&activity_raw)? };
        let ui_mode_service = env
            .get_static_field(
                jni_str!("android/content/Context"),
                jni_str!("UI_MODE_SERVICE"),
                jni_sig!("Ljava/lang/String;"),
            )?
            .l()?;
        let ui_mode_manager = env
            .call_method(
                &activity,
                jni_str!("getSystemService"),
                jni_sig!("(Ljava/lang/String;)Ljava/lang/Object;"),
                &[JValue::Object(&ui_mode_service)],
            )?
            .l()?;

        if !ui_mode_manager.is_null() {
            let night_mode = env
                .call_method(
                    &ui_mode_manager,
                    jni_str!("getNightMode"),
                    jni_sig!("()I"),
                    &[],
                )?
                .i()?;
            let light = env
                .get_static_field(
                    jni_str!("android/app/UiModeManager"),
                    jni_str!("MODE_NIGHT_NO"),
                    jni_sig!("I"),
                )?
                .i()?;
            let dark = env
                .get_static_field(
                    jni_str!("android/app/UiModeManager"),
                    jni_str!("MODE_NIGHT_YES"),
                    jni_sig!("I"),
                )?
                .i()?;

            match night_mode {
                mode if mode == light => return Ok(Some(WindowTheme::Light)),
                mode if mode == dark => return Ok(Some(WindowTheme::Dark)),
                _ => {}
            }
        }

        let resources = env
            .call_method(
                &activity,
                jni_str!("getResources"),
                jni_sig!("()Landroid/content/res/Resources;"),
                &[],
            )?
            .l()?;
        let configuration = env
            .call_method(
                &resources,
                jni_str!("getConfiguration"),
                jni_sig!("()Landroid/content/res/Configuration;"),
                &[],
            )?
            .l()?;

        let ui_mode = env
            .get_field(&configuration, jni_str!("uiMode"), jni_sig!("I"))?
            .i()?;
        let mask = env
            .get_static_field(
                jni_str!("android/content/res/Configuration"),
                jni_str!("UI_MODE_NIGHT_MASK"),
                jni_sig!("I"),
            )?
            .i()?;
        let light = env
            .get_static_field(
                jni_str!("android/content/res/Configuration"),
                jni_str!("UI_MODE_NIGHT_NO"),
                jni_sig!("I"),
            )?
            .i()?;
        let dark = env
            .get_static_field(
                jni_str!("android/content/res/Configuration"),
                jni_str!("UI_MODE_NIGHT_YES"),
                jni_sig!("I"),
            )?
            .i()?;

        Ok(match ui_mode & mask {
            mode if mode == light => Some(WindowTheme::Light),
            mode if mode == dark => Some(WindowTheme::Dark),
            _ => None,
        })
    })
    .ok()
    .flatten()
}

#[cfg(all(target_os = "android", feature = "android"))]
fn apply_android_system_bar_style(app: &AndroidApp, style: SystemBarStyle) -> Result<(), String> {
    let scheduler_app = app.clone();
    let callback_app = scheduler_app.clone();
    scheduler_app.run_on_java_main_thread(Box::new(move || {
        if let Err(error) = apply_android_system_bar_style_on_main_thread(&callback_app, style) {
            eprintln!("failed to sync Android system bars: {error}");
        }
    }));

    Ok(())
}

#[cfg(all(target_os = "android", feature = "android"))]
fn apply_android_system_bar_style_on_main_thread(
    app: &AndroidApp,
    style: SystemBarStyle,
) -> Result<(), String> {
    let vm = unsafe { JavaVM::from_raw(app.vm_as_ptr().cast()) };
    let activity_raw = app.activity_as_ptr() as jni::sys::jobject;

    vm.attach_current_thread(|env| -> jni::errors::Result<()> {
        let activity = unsafe { env.as_cast_raw::<JObject>(&activity_raw)? };
        let window = env
            .call_method(
                &activity,
                jni_str!("getWindow"),
                jni_sig!("()Landroid/view/Window;"),
                &[],
            )?
            .l()?;

        let bar_color = color_to_android_argb(style.color);
        env.call_method(
            &window,
            jni_str!("setStatusBarColor"),
            jni_sig!("(I)V"),
            &[JValue::Int(bar_color)],
        )?;
        env.call_method(
            &window,
            jni_str!("setNavigationBarColor"),
            jni_sig!("(I)V"),
            &[JValue::Int(bar_color)],
        )?;

        let sdk_int = env
            .get_static_field(
                jni_str!("android/os/Build$VERSION"),
                jni_str!("SDK_INT"),
                jni_sig!("I"),
            )?
            .i()?;

        if sdk_int >= 30 {
            let controller = env
                .call_method(
                    &window,
                    jni_str!("getInsetsController"),
                    jni_sig!("()Landroid/view/WindowInsetsController;"),
                    &[],
                )?
                .l()?;

            if !controller.is_null() {
                let light_status = env
                    .get_static_field(
                        jni_str!("android/view/WindowInsetsController"),
                        jni_str!("APPEARANCE_LIGHT_STATUS_BARS"),
                        jni_sig!("I"),
                    )?
                    .i()?;
                let light_navigation = env
                    .get_static_field(
                        jni_str!("android/view/WindowInsetsController"),
                        jni_str!("APPEARANCE_LIGHT_NAVIGATION_BARS"),
                        jni_sig!("I"),
                    )?
                    .i()?;
                let mask = light_status | light_navigation;
                let appearance = if style.use_dark_icons { mask } else { 0 };
                env.call_method(
                    &controller,
                    jni_str!("setSystemBarsAppearance"),
                    jni_sig!("(II)V"),
                    &[JValue::Int(appearance), JValue::Int(mask)],
                )?;
            }
        } else {
            let decor_view = env
                .call_method(
                    &window,
                    jni_str!("getDecorView"),
                    jni_sig!("()Landroid/view/View;"),
                    &[],
                )?
                .l()?;
            let mut visibility = env
                .call_method(
                    &decor_view,
                    jni_str!("getSystemUiVisibility"),
                    jni_sig!("()I"),
                    &[],
                )?
                .i()?;

            let light_status = if sdk_int >= 23 {
                env.get_static_field(
                    jni_str!("android/view/View"),
                    jni_str!("SYSTEM_UI_FLAG_LIGHT_STATUS_BAR"),
                    jni_sig!("I"),
                )?
                .i()?
            } else {
                0
            };
            let light_navigation = if sdk_int >= 26 {
                env.get_static_field(
                    jni_str!("android/view/View"),
                    jni_str!("SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR"),
                    jni_sig!("I"),
                )?
                .i()?
            } else {
                0
            };

            let flags = light_status | light_navigation;
            if style.use_dark_icons {
                visibility |= flags;
            } else {
                visibility &= !flags;
            }

            env.call_method(
                &decor_view,
                jni_str!("setSystemUiVisibility"),
                jni_sig!("(I)V"),
                &[JValue::Int(visibility)],
            )?;
        }

        Ok(())
    })
    .map_err(|error| format!("failed to sync Android system bars: {error}"))?;

    Ok(())
}

#[cfg(all(target_os = "android", feature = "android"))]
fn color_to_android_argb(color: Color) -> i32 {
    ((color.a as i32) << 24) | ((color.r as i32) << 16) | ((color.g as i32) << 8) | color.b as i32
}

#[cfg(all(target_os = "android", feature = "android"))]
fn is_light_color(color: Color) -> bool {
    let to_linear = |channel: u8| {
        let value = channel as f32 / 255.0;
        if value <= 0.03928 {
            value / 12.92
        } else {
            ((value + 0.055) / 1.055).powf(2.4)
        }
    };

    let luminance =
        0.2126 * to_linear(color.r) + 0.7152 * to_linear(color.g) + 0.0722 * to_linear(color.b);
    luminance > 0.5
}

#[cfg(test)]
mod tests {
    use super::{next_grapheme_boundary, normalize_single_line_text, previous_grapheme_boundary};

    #[test]
    fn grapheme_navigation_keeps_emoji_cluster_intact() {
        let text = "A👨‍👩‍👧‍👦中";
        let emoji_start = previous_grapheme_boundary(text, text.len());
        let end = next_grapheme_boundary(text, 1);

        assert_eq!(&text[1..end], "👨‍👩‍👧‍👦");
        assert_eq!(emoji_start, end);
    }

    #[test]
    fn normalize_single_line_text_replaces_newlines_with_spaces() {
        assert_eq!(
            normalize_single_line_text("hello\r\nworld\t!"),
            "hello  world!"
        );
    }
}
