use super::*;

use crate::input::ctx::InputCtx;
use crate::protocol::wayland::portal;

use crate::backend::interface::{
    BackendView, DmabufImportBackend, RenderBackend, WinitBackendHandle,
};
use crate::compositor::exit_confirm::exit_confirm_controller;
use crate::compositor::interaction::PointerState;
use crate::compositor::monitor::camera::camera_controller;
use calloop::{Interest, Mode, PostAction, generic::Generic};
use halley_ipc::{LogicalOutputInfo, ModeInfo, OutputInfo, OutputStatus};

const CONFIG_RELOAD_SETTLE_MS: u64 = 100;

struct WinitOutputCaptureBackend {
    backend: Rc<RefCell<smithay::backend::winit::WinitGraphicsBackend<GlesRenderer>>>,
    pointer_state: Rc<RefCell<PointerState>>,
    dmabuf_formats: Vec<smithay::backend::allocator::Format>,
}

impl WinitOutputCaptureBackend {
    fn new(
        backend: Rc<RefCell<smithay::backend::winit::WinitGraphicsBackend<GlesRenderer>>>,
        pointer_state: Rc<RefCell<PointerState>>,
        dmabuf_formats: Vec<smithay::backend::allocator::Format>,
    ) -> Self {
        Self {
            backend,
            pointer_state,
            dmabuf_formats,
        }
    }
}

impl portal::OutputCaptureBackend for WinitOutputCaptureBackend {
    fn capture_dmabuf_formats(&self) -> Vec<smithay::backend::allocator::Format> {
        self.dmabuf_formats.clone()
    }

    fn capture_output_shm(
        &self,
        st: &mut Halley,
        output_name: &str,
        overlay_cursor: bool,
        logical_region: Option<smithay::utils::Rectangle<i32, smithay::utils::Logical>>,
    ) -> Result<portal::ShmCaptureFrame, Box<dyn Error>> {
        let mut backend = self
            .backend
            .try_borrow_mut()
            .map_err(|_| io::Error::other("winit renderer already borrowed during screencopy"))?;
        let size = backend.window_size();
        let physical_size: smithay::utils::Size<i32, smithay::utils::Physical> =
            (size.w.max(1), size.h.max(1)).into();
        let ps = self
            .pointer_state
            .try_borrow()
            .map_err(|_| io::Error::other("pointer state already borrowed during screencopy"))?;
        let now = Instant::now();
        let resize_preview = ps.resize;
        let (hover_node, preview_hover_node) = resolve_hover_targets(st, &ps, now);
        let cursor_screen = overlay_cursor.then_some(ps.screen);
        drop(ps);

        portal::capture_output_via_renderer(
            backend.renderer(),
            st,
            output_name,
            physical_size,
            st.output_transform_for(output_name),
            resize_preview,
            hover_node,
            preview_hover_node,
            cursor_screen,
            overlay_cursor,
            logical_region,
        )
    }

    fn capture_output_dmabuf(
        &self,
        st: &mut Halley,
        output_name: &str,
        overlay_cursor: bool,
        logical_region: Option<smithay::utils::Rectangle<i32, smithay::utils::Logical>>,
        dmabuf: &mut smithay::backend::allocator::dmabuf::Dmabuf,
    ) -> Result<crate::backend::interface::CaptureDmabufResult, Box<dyn Error>> {
        let mut backend = self.backend.try_borrow_mut().map_err(|_| {
            io::Error::other("winit renderer already borrowed during dma-buf screencopy")
        })?;
        let size = backend.window_size();
        let physical_size: smithay::utils::Size<i32, smithay::utils::Physical> =
            (size.w.max(1), size.h.max(1)).into();
        let ps = self.pointer_state.try_borrow().map_err(|_| {
            io::Error::other("pointer state already borrowed during dma-buf screencopy")
        })?;
        let now = Instant::now();
        let resize_preview = ps.resize;
        let (hover_node, preview_hover_node) = resolve_hover_targets(st, &ps, now);
        let cursor_screen = overlay_cursor.then_some(ps.screen);
        drop(ps);

        portal::capture_output_into_dmabuf_via_renderer(
            backend.renderer(),
            st,
            output_name,
            physical_size,
            st.output_transform_for(output_name),
            resize_preview,
            hover_node,
            preview_hover_node,
            cursor_screen,
            overlay_cursor,
            logical_region,
            dmabuf,
        )
    }

    fn capture_window_png(
        &self,
        st: &mut Halley,
        output_name: &str,
        node_id: halley_core::field::NodeId,
        output_path: &std::path::Path,
    ) -> Result<(), Box<dyn Error>> {
        let mut backend = self.backend.try_borrow_mut().map_err(|_| {
            io::Error::other("winit renderer already borrowed during window capture")
        })?;
        crate::window::capture_window_to_png_via_renderer(
            backend.renderer(),
            st,
            output_name,
            node_id,
            output_path,
        )
    }
}

fn apply_host_cursor(
    backend: &Rc<RefCell<smithay::backend::winit::WinitGraphicsBackend<GlesRenderer>>>,
    image: &smithay::input::pointer::CursorImageStatus,
) {
    let backend = backend.borrow();
    let window = backend.window();
    match image {
        smithay::input::pointer::CursorImageStatus::Hidden => {
            window.set_cursor_visible(false);
        }
        smithay::input::pointer::CursorImageStatus::Named(icon) => {
            window.set_cursor_visible(true);
            window.set_cursor(*icon);
        }
        _ => {
            window.set_cursor_visible(true);
            window.set_cursor(smithay::input::pointer::CursorIcon::Default);
        }
    }
}

fn publish_winit_output_snapshot(
    width: i32,
    height: i32,
    focused: bool,
    offset_x: i32,
    offset_y: i32,
) {
    let width = width.max(0) as u32;
    let height = height.max(0) as u32;

    publish_outputs(vec![OutputInfo {
        name: "winit-0".to_string(),
        status: OutputStatus::Connected,
        enabled: true,
        current_mode: Some(ModeInfo {
            width,
            height,
            refresh_hz: None,
            preferred: true,
            current: true,
        }),
        modes: vec![ModeInfo {
            width,
            height,
            refresh_hz: None,
            preferred: true,
            current: true,
        }],
        vrr_mode: None,
        vrr_support: None,
        direct_scanout_candidate_node: None,
        direct_scanout_active_node: None,
        direct_scanout_reason: None,
        logical: Some(LogicalOutputInfo {
            scale: 1.0,
            focused,
            offset_x,
            offset_y,
        }),
    }]);
}

fn apply_winit_reload(
    backend: &Rc<RefCell<smithay::backend::winit::WinitGraphicsBackend<GlesRenderer>>>,
    st: &mut Halley,
    mut next: RuntimeTuning,
    config_path: &str,
    wayland_display: &str,
    reason: &str,
) {
    let ws = backend.borrow().window_size();
    next.viewport_size = halley_core::field::Vec2 {
        x: ws.w.max(1) as f32,
        y: ws.h.max(1) as f32,
    };
    let live_camera = crate::bootstrap::capture_live_camera_state(st);
    st.apply_tuning(next);
    crate::bootstrap::restore_live_camera_state(st, live_camera);
    st.advertise_output(
        "winit-0",
        smithay::output::Mode {
            size: (ws.w.max(1), ws.h.max(1)).into(),
            refresh: 0,
        },
    );
    let reload_commands = st.runtime.tuning.autostart_on_reload.clone();
    run_autostart_commands(st, &reload_commands, wayland_display, "autostart");
    debug!(
        "{}: reloaded config from {} with viewport {}x{}",
        reason,
        config_path,
        ws.w.max(1),
        ws.h.max(1)
    );
}

pub(crate) fn run_winit_backend() -> Result<(), Box<dyn Error>> {
    scope!(
        "halley-wl",
        success = "compositor exited",
        failure = "compositor failed",
        aborted = "compositor aborted",
        {
            ensure_xdg_runtime_dir()?;
            ensure_dbus_session_bus_address();
            init_logging()?;
            let _host_backend_guard = ensure_host_display()?;

            let mut display: Display<Halley> = Display::new()?;
            let dh = display.handle();

            crate::bootstrap::ensure_default_user_config(None);
            let config_path = Rc::new(RuntimeTuning::config_path());
            let aperture_config_path = Rc::new(crate::aperture::default_aperture_config_path());
            let tuning = RuntimeTuning::load_from_path(config_path.as_str());
            let aperture_config =
                crate::aperture::load_aperture_config_from_path(aperture_config_path.as_path());
            tuning.apply_process_env();
            if !Path::new(config_path.as_str()).exists() {
                warn!(
                    "config file not found at {}; using built-in defaults",
                    config_path.as_str()
                );
            }
            info!("config path: {}", config_path.as_str());
            if !aperture_config_path.as_path().exists() {
                warn!(
                    "aperture config file not found at {}; using built-in defaults",
                    aperture_config_path.display()
                );
            }
            crate::aperture::log_aperture_config_startup(aperture_config_path.as_ref());
            debug!("keybind modifier: {}", tuning.keybinds.modifier_name());
            debug!("resolved keybinds: {}", tuning.keybinds_resolved_summary());
            debug!("resolved zoom: {}", tuning.zoom_resolved_summary());

            let (watch_rx, _watcher): (Option<mpsc::Receiver<()>>, Option<RecommendedWatcher>) = {
                let (watch_tx, watch_rx) = mpsc::channel::<()>();
                let config_watch_target = PathBuf::from(config_path.as_str());
                let aperture_watch_target = aperture_config_path.as_ref().clone();
                let mut watcher: RecommendedWatcher = notify::recommended_watcher(
                    move |result: Result<notify::Event, notify::Error>| {
                        if let Ok(event) = result {
                            let touches_config = if event.paths.is_empty() {
                                true
                            } else {
                                event.paths.iter().any(|path| {
                                    crate::aperture::aperture_config_matches_event_path(
                                        path,
                                        config_watch_target.as_path(),
                                        aperture_watch_target.as_path(),
                                    )
                                })
                            };
                            if touches_config {
                                match event.kind {
                                    EventKind::Modify(_)
                                    | EventKind::Create(_)
                                    | EventKind::Remove(_) => {
                                        let _ = watch_tx.send(());
                                    }
                                    _ => {}
                                }
                            }
                        }
                    },
                )?;
                for watch_root in crate::aperture::config_watch_roots(
                    Path::new(config_path.as_str()),
                    aperture_config_path.as_path(),
                ) {
                    if let Err(err) =
                        watcher.watch(watch_root.as_path(), RecursiveMode::NonRecursive)
                    {
                        warn!(
                            "config watch disabled for {}: {}",
                            watch_root.display(),
                            err
                        );
                    }
                }
                (Some(watch_rx), Some(watcher))
            };

            let listening = ListeningSocketSource::new_auto().map_err(|err| {
                let xdg_runtime_dir =
                    env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "<unset>".to_string());
                io::Error::other(format!(
                    "failed to create WAYLAND listening socket (XDG_RUNTIME_DIR={}): {}",
                    xdg_runtime_dir, err
                ))
            })?;
            let sock_name = listening.socket_name().to_string_lossy().to_string();
            info!("WAYLAND_DISPLAY={}", sock_name);

            let (backend, winit_source) = smithay_winit::init::<GlesRenderer>().map_err(|err| {
                let wayland_display =
                    env::var("WAYLAND_DISPLAY").unwrap_or_else(|_| "<unset>".to_string());
                let x11_display = env::var("DISPLAY").unwrap_or_else(|_| "<unset>".to_string());
                io::Error::other(format!(
                    "failed to initialize winit backend (WAYLAND_DISPLAY={}, DISPLAY={}): {}",
                    wayland_display, x11_display, err
                ))
            })?;
            let backend = Rc::new(RefCell::new(backend));
            let backend_handle = WinitBackendHandle::new(backend.clone());
            let mut ev: EventLoop<Halley> = EventLoop::try_new()?;
            let _signal = ev.get_signal();
            let mut state = Halley::new(&dh, ev.handle(), tuning.clone());
            state.apply_aperture_config(aperture_config);
            state.platform.seat.add_pointer();
            super::initialize_seat_keyboard(&mut state);
            let dmabuf_importer: Rc<dyn DmabufImportBackend> = Rc::new(backend_handle.clone());
            state.configure_dmabuf_importer(dmabuf_importer, None);
            let xwayland = Rc::new(RefCell::new(ensure_xwayland_satellite(sock_name.as_str())?));
            let (xwayland_request_tx, xwayland_request_rx) = mpsc::channel::<()>();
            register_xwayland_request_channel(xwayland_request_tx);
            let xwayland_request_rx = Rc::new(RefCell::new(xwayland_request_rx));
            let xwayland_for_timer = xwayland.clone();
            let xwayland_request_for_timer = xwayland_request_rx.clone();
            {
                let mut fresh = RuntimeTuning::load_from_path(config_path.as_str());
                let ws = backend.borrow().window_size();
                fresh.viewport_size = halley_core::field::Vec2 {
                    x: ws.w.max(1) as f32,
                    y: ws.h.max(1) as f32,
                };
                state.apply_tuning(fresh);
                state.model.zoom_ref_size = halley_core::field::Vec2 {
                    x: ws.w.max(1) as f32,
                    y: ws.h.max(1) as f32,
                };
                camera_controller(&mut state).snap_targets_to_live();
                state.advertise_output(
                    "winit-0",
                    smithay::output::Mode {
                        size: (ws.w.max(1), ws.h.max(1)).into(),
                        refresh: 0,
                    },
                );
            }
            let autostart_once = state.runtime.tuning.autostart_once.clone();
            run_autostart_commands(&mut state, &autostart_once, sock_name.as_str(), "autostart");
            apply_host_cursor(&backend, &state.effective_cursor_image_status());
            let backend_for_winit = backend.clone();
            let backend_for_timer = backend.clone();
            let backend_for_cursor_timer = backend.clone();
            let backend_for_output_timer = backend.clone();
            let backend_handle_for_winit = backend_handle.clone();
            let backend_handle_for_timer = backend_handle.clone();
            let config_path_for_winit = config_path.clone();
            let wayland_display_for_winit = sock_name.clone();
            let wayland_display_for_timer = sock_name.clone();
            let mod_state = Rc::new(RefCell::new(ModState::default()));
            let mod_state_for_winit = mod_state.clone();
            let pointer_state = Rc::new(RefCell::new(PointerState::default()));
            let capture_dmabuf_formats = {
                let mut backend_ref = backend.borrow_mut();
                <GlesRenderer as smithay::backend::renderer::Bind<
                    smithay::backend::allocator::dmabuf::Dmabuf,
                >>::supported_formats(backend_ref.renderer())
                .map(|formats| formats.iter().copied().collect())
                .unwrap_or_default()
            };
            portal::configure_output_capture_backend(
                &mut state,
                Rc::new(WinitOutputCaptureBackend::new(
                    backend.clone(),
                    pointer_state.clone(),
                    capture_dmabuf_formats,
                )),
            );
            let mod_state_for_timer = mod_state.clone();
            {
                let ws = backend.borrow().window_size();
                let mut ps = pointer_state.borrow_mut();
                ps.workspace_size = (ws.w.max(1), ws.h.max(1));
                ps.screen = ((ws.w as f32) * 0.5, (ws.h as f32) * 0.5);
            }
            let pointer_state_for_winit = pointer_state.clone();
            let pointer_state_for_timer = pointer_state.clone();
            let watch_rx = Rc::new(RefCell::new(watch_rx));
            let watch_rx_for_timer = watch_rx.clone();
            let pending_watch_reload_at = Rc::new(RefCell::new(None::<Instant>));
            let pending_watch_reload_at_for_timer = pending_watch_reload_at.clone();
            let config_path_for_timer = config_path.clone();
            let aperture_config_path_for_timer = aperture_config_path.clone();
            {
                let ws = backend.borrow().window_size();
                publish_winit_output_snapshot(ws.w, ws.h, true, 0, 0);
            }

            let mut dh_for_clients = dh.clone();
            ev.handle()
                .insert_source(listening, move |client_stream, _, _st| {
                    let _ =
                        dh_for_clients.insert_client(client_stream, Arc::new(ClientState::new()));
                })?;

            if let Some(listener) = xwayland.borrow().filesystem_listener_source()? {
                let xwayland_for_x11 = xwayland.clone();
                ev.handle().insert_source(
                    Generic::new(listener, Interest::READ, Mode::Level),
                    move |_readiness, _listener, _st| {
                        xwayland_for_x11.borrow_mut().request_start();
                        Ok(PostAction::Continue)
                    },
                )?;
            }
            if let Some(listener) = xwayland.borrow().abstract_listener_source()? {
                let xwayland_for_x11 = xwayland.clone();
                ev.handle().insert_source(
                    Generic::new(listener, Interest::READ, Mode::Level),
                    move |_readiness, _listener, _st| {
                        xwayland_for_x11.borrow_mut().request_start();
                        Ok(PostAction::Continue)
                    },
                )?;
            }

            ev.handle()
                .insert_source(winit_source, move |event, _, st| match event {
                    WinitEvent::Redraw => {
                        let ps = pointer_state_for_winit.borrow();
                        let now = Instant::now();
                        let resize_preview = ps.resize;
                        let (hover_node, preview_hover_node) = resolve_hover_targets(st, &ps, now);
                        if let Err(err) = backend_handle_for_winit.draw_frame(
                            st,
                            resize_preview,
                            hover_node,
                            preview_hover_node,
                        ) {
                            debug!("draw failed: {}", err);
                        } else {
                            crate::frame_loop::send_frame_callbacks(st, now);
                        }
                    }
                    WinitEvent::Resized { size, .. } => {
                        debug!("winit event: {:?}", event);
                        st.model.zoom_ref_size = halley_core::field::Vec2 {
                            x: size.w.max(1) as f32,
                            y: size.h.max(1) as f32,
                        };
                        camera_controller(&mut *st).snap_targets_to_live();
                        st.advertise_output(
                            "winit-0",
                            smithay::output::Mode {
                                size: (size.w.max(1), size.h.max(1)).into(),
                                refresh: 0,
                            },
                        );
                        {
                            let mut ps = pointer_state_for_winit.borrow_mut();
                            let (old_w, old_h) = ps.workspace_size;
                            let new_w = size.w.max(1);
                            let new_h = size.h.max(1);
                            if old_w > 0 && old_h > 0 {
                                let sx = ps.screen.0 * (new_w as f32) / (old_w as f32);
                                let sy = ps.screen.1 * (new_h as f32) / (old_h as f32);
                                let max_x = (new_w - 1) as f32;
                                let max_y = (new_h - 1) as f32;
                                ps.screen = (sx.clamp(0.0, max_x), sy.clamp(0.0, max_y));
                            }
                            ps.workspace_size = (new_w, new_h);
                        }
                        let ps = pointer_state_for_winit.borrow();
                        let now = Instant::now();
                        let resize_preview = ps.resize;
                        let (hover_node, preview_hover_node) = resolve_hover_targets(st, &ps, now);
                        if let Err(err) = backend_handle_for_winit.draw_frame(
                            st,
                            resize_preview,
                            hover_node,
                            preview_hover_node,
                        ) {
                            debug!("draw failed: {}", err);
                        } else {
                            crate::frame_loop::send_frame_callbacks(st, now);
                        }
                    }
                    WinitEvent::Focus(false) => {
                        debug!("winit event: {:?}", event);
                        *mod_state_for_winit.borrow_mut() = ModState::default();
                        let mut ps = pointer_state_for_winit.borrow_mut();
                        if ps.resize.is_none() {
                            crate::compositor::carry::system::set_drag_authority_node(st, None);
                            ps.drag = None;
                            ps.move_anim.clear();
                            ps.panning = false;
                        }
                        st.set_app_focused(false);
                    }
                    WinitEvent::Focus(true) => {
                        debug!("winit event: {:?}", event);
                        st.set_app_focused(true);
                        let now = Instant::now();
                        if let Some(id) = st.last_input_surface_node() {
                            st.set_interaction_focus(Some(id), 30_000, now);
                        }
                    }
                    WinitEvent::CloseRequested => {
                        debug!("winit event: {:?}", event);
                        st.request_exit();
                    }
                    WinitEvent::Input(InputEvent::Keyboard { event }) => {
                        let code: u32 = event.key_code().into();
                        let pressed = event.state() == KeyState::Pressed;
                        let input_ctx = InputCtx {
                            mod_state: &mod_state_for_winit,
                            pointer_state: &pointer_state_for_winit,
                            backend: &backend_handle_for_winit,
                            config_path: config_path_for_winit.as_str(),
                            wayland_display: wayland_display_for_winit.as_str(),
                        };
                        handle_backend_input_event(
                            st,
                            &input_ctx,
                            BackendInputEventData::Keyboard { code, pressed },
                        );
                    }
                    WinitEvent::Input(InputEvent::PointerMotionAbsolute { event }) => {
                        let ws = backend_for_winit.borrow().window_size();
                        let sx = event.x_transformed(ws.w) as f32;
                        let sy = event.y_transformed(ws.h) as f32;
                        let input_ctx = InputCtx {
                            mod_state: &mod_state_for_winit,
                            pointer_state: &pointer_state_for_winit,
                            backend: &backend_handle_for_winit,
                            config_path: config_path_for_winit.as_str(),
                            wayland_display: wayland_display_for_winit.as_str(),
                        };
                        handle_backend_input_event(
                            st,
                            &input_ctx,
                            BackendInputEventData::PointerMotionAbsolute {
                                ws_w: ws.w,
                                ws_h: ws.h,
                                sx,
                                sy,
                                delta_x: 0.0,
                                delta_y: 0.0,
                                delta_x_unaccel: 0.0,
                                delta_y_unaccel: 0.0,
                                time_usec: smithay::backend::input::Event::<
                                    smithay::backend::winit::WinitInput,
                                >::time(&event),
                            },
                        );
                    }
                    WinitEvent::Input(InputEvent::PointerMotion { event }) => {
                        let ws = backend_for_winit.borrow().window_size();
                        let (last_sx, last_sy) = pointer_state_for_winit.borrow().screen;
                        let sx = last_sx
                            + smithay::backend::input::PointerMotionEvent::<
                                smithay::backend::winit::WinitInput,
                            >::delta_x(&event) as f32;
                        let sy = last_sy
                            + smithay::backend::input::PointerMotionEvent::<
                                smithay::backend::winit::WinitInput,
                            >::delta_y(&event) as f32;
                        let input_ctx = InputCtx {
                            mod_state: &mod_state_for_winit,
                            pointer_state: &pointer_state_for_winit,
                            backend: &backend_handle_for_winit,
                            config_path: config_path_for_winit.as_str(),
                            wayland_display: wayland_display_for_winit.as_str(),
                        };
                        handle_backend_input_event(
                            st,
                            &input_ctx,
                            BackendInputEventData::PointerMotionAbsolute {
                                ws_w: ws.w,
                                ws_h: ws.h,
                                sx,
                                sy,
                                delta_x: smithay::backend::input::PointerMotionEvent::<
                                    smithay::backend::winit::WinitInput,
                                >::delta_x(&event),
                                delta_y: smithay::backend::input::PointerMotionEvent::<
                                    smithay::backend::winit::WinitInput,
                                >::delta_y(&event),
                                delta_x_unaccel: smithay::backend::input::PointerMotionEvent::<
                                    smithay::backend::winit::WinitInput,
                                >::delta_x_unaccel(
                                    &event
                                ),
                                delta_y_unaccel: smithay::backend::input::PointerMotionEvent::<
                                    smithay::backend::winit::WinitInput,
                                >::delta_y_unaccel(
                                    &event
                                ),
                                time_usec: smithay::backend::input::Event::<
                                    smithay::backend::winit::WinitInput,
                                >::time(&event),
                            },
                        );
                    }
                    WinitEvent::Input(InputEvent::PointerButton { event }) => {
                        let input_ctx = InputCtx {
                            mod_state: &mod_state_for_winit,
                            pointer_state: &pointer_state_for_winit,
                            backend: &backend_handle_for_winit,
                            config_path: config_path_for_winit.as_str(),
                            wayland_display: wayland_display_for_winit.as_str(),
                        };
                        handle_backend_input_event(
                            st,
                            &input_ctx,
                            BackendInputEventData::PointerButton {
                                button_code: event.button_code(),
                                state: event.state(),
                            },
                        );
                    }
                    WinitEvent::Input(InputEvent::PointerAxis { event }) => {
                        let input_ctx = InputCtx {
                            mod_state: &mod_state_for_winit,
                            pointer_state: &pointer_state_for_winit,
                            backend: &backend_handle_for_winit,
                            config_path: config_path_for_winit.as_str(),
                            wayland_display: wayland_display_for_winit.as_str(),
                        };
                        handle_backend_input_event(
                            st,
                            &input_ctx,
                            BackendInputEventData::PointerAxis {
                                source: event.source(),
                                amount_v120_horizontal: event.amount_v120(Axis::Horizontal),
                                amount_v120_vertical: event.amount_v120(Axis::Vertical),
                                amount_horizontal: event.amount(Axis::Horizontal),
                                amount_vertical: event.amount(Axis::Vertical),
                                relative_direction_horizontal: event
                                    .relative_direction(Axis::Horizontal),
                                relative_direction_vertical: event
                                    .relative_direction(Axis::Vertical),
                            },
                        );
                    }
                    _ => {}
                })?;

            let initial_frame_interval = frame_interval_for_refresh_hz(
                state
                    .runtime
                    .tuning
                    .tty_viewports
                    .first()
                    .and_then(|vp| vp.refresh_rate),
            );
            let timer = Timer::from_duration(initial_frame_interval);
            ev.handle().insert_source(timer, move |_tick, _, st| {
                if crate::compositor::interaction::state::take_input_state_reset_request(st) {
                    mod_state_for_timer.borrow_mut().clear_intercepts();
                    let mut ps = pointer_state_for_timer.borrow_mut();
                    ps.intercepted_buttons.clear();
                    ps.intercepted_binding_buttons.clear();
                    ps.intercepted_buttons.clear();
                    crate::compositor::carry::system::set_drag_authority_node(st, None);
                    ps.drag = None;
                    ps.move_anim.clear();
                    ps.panning = false;
                }
                if let Some((sx, sy)) = crate::compositor::interaction::state::take_pointer_screen_hint_request(st) {
                    let mut ps = pointer_state_for_timer.borrow_mut();
                    let (ws_w, ws_h) = ps.workspace_size;
                    ps.screen = (sx, sy);
                    ps.world = crate::spatial::screen_to_world(st, ws_w.max(1), ws_h.max(1), sx, sy);
                }
                let now = Instant::now();
                st.drain_drm_syncobj_blockers();

                st.runtime.spawned_children.retain_mut(|child| {
                    match child.try_wait() {
                        Ok(Some(status)) => {
                            debug!("reaped child pid={} status={}", child.id(), status);
                            false
                        }
                        Ok(None) => true,
                        Err(err) => {
                            warn!("try_wait failed for child pid={}: {}", child.id(), err);
                            false
                        }
                    }
                });

                drain_ipc_commands(|request| match request {
                    halley_ipc::Request::Compositor(halley_ipc::CompositorRequest::Quit) => {
                        info!("ipc: quit requested");
                        exit_confirm_controller(&mut *st).show();
                        halley_ipc::Response::Ok
                    }
                    halley_ipc::Request::Compositor(halley_ipc::CompositorRequest::Reload) => {
                        let _ = crate::aperture::reload_aperture_config(
                            st,
                            aperture_config_path_for_timer.as_path(),
                            "ipc",
                        );
                        if let Some(next) =
                            RuntimeTuning::try_load_from_path(config_path_for_timer.as_str())
                        {
                            if crate::bootstrap::viewport_section_changed(&st.runtime.tuning, &next) {
                                apply_winit_reload(
                                    &backend_for_timer,
                                    st,
                                    next,
                                    config_path_for_timer.as_str(),
                                    wayland_display_for_timer.as_str(),
                                    "ipc",
                                );
                            } else {
                                let next = crate::bootstrap::preserve_viewport_section(&st.runtime.tuning, next);
                                crate::bootstrap::apply_reloaded_tuning(
                                    st,
                                    next,
                                    config_path_for_timer.as_str(),
                                    wayland_display_for_timer.as_str(),
                                    "ipc",
                                );
                            }
                        } else {
                            warn!(
                                "ipc: reload skipped for {} because config parse/load failed",
                                config_path_for_timer.as_str()
                            );
                        }
                        debug!("resolved keybinds: {}", st.runtime.tuning.keybinds_resolved_summary());
                        debug!("resolved zoom: {}", st.runtime.tuning.zoom_resolved_summary());
                        halley_ipc::Response::Reloaded
                    }
                    halley_ipc::Request::Compositor(halley_ipc::CompositorRequest::Dpms {
                        command,
                        output,
                    }) => {
                        let target = output.unwrap_or_else(|| "all outputs".to_string());
                        warn!(
                            "ipc: ignoring tty-only dpms command on winit backend: {:?} ({})",
                            command, target
                        );
                        halley_ipc::Response::Error(halley_ipc::IpcError::Unsupported(
                            "dpms is only supported on the tty backend".into(),
                        ))
                    }
                    request => crate::ipc::handle_request(st, request),
                });

                {
                    let ws = backend_for_output_timer.borrow().window_size();
                    publish_winit_output_snapshot(ws.w, ws.h, true, 0, 0);
                }

                {
                    let rx = xwayland_request_for_timer.borrow_mut();
                    while rx.try_recv().is_ok() {
                        xwayland_for_timer.borrow_mut().request_start();
                    }
                }
                xwayland_for_timer.borrow_mut().tick();
                let resize_active = {
                    let ps = pointer_state_for_timer.borrow();
                    ps.resize.is_some()
                };
                crate::frame_loop::tick_frame_effects(st, now);
                crate::frame_loop::tick_animator_frame(st, now);
                st.tick_fullscreen_motion(now);
                crate::frame_loop::begin_render_frame(st, now);
                {
                    let mut ps = pointer_state_for_timer.borrow_mut();
                    let _ = advance_node_move_anim(st, &mut ps, now);
                    let _ = advance_resize_anim(st, &mut ps, now);
                }
                crate::frame_loop::tick_live_overlap(st);
                if !resize_active {
                    st.run_maintenance_if_needed(now);
                }

                let mut reloaded = false;
                let mut rx_ref = watch_rx_for_timer.borrow_mut();
                if let Some(rx) = rx_ref.as_mut() {
                    while rx.try_recv().is_ok() {
                        *pending_watch_reload_at_for_timer.borrow_mut() =
                            Some(now + Duration::from_millis(CONFIG_RELOAD_SETTLE_MS));
                    }
                }
                if pending_watch_reload_at_for_timer
                    .borrow()
                    .is_some_and(|deadline| now >= deadline)
                {
                    *pending_watch_reload_at_for_timer.borrow_mut() = None;
                    reloaded |= crate::aperture::reload_aperture_config(
                        st,
                        aperture_config_path_for_timer.as_path(),
                        "watch",
                    );
                    if let Some(next) =
                        RuntimeTuning::try_load_from_path(config_path_for_timer.as_str())
                    {
                        if crate::bootstrap::viewport_section_changed(&st.runtime.tuning, &next) {
                            apply_winit_reload(
                                &backend_for_timer,
                                st,
                                next,
                                config_path_for_timer.as_str(),
                                wayland_display_for_timer.as_str(),
                                "watch",
                            );
                        } else {
                            let next = crate::bootstrap::preserve_viewport_section(&st.runtime.tuning, next);
                            crate::bootstrap::apply_reloaded_tuning(
                                st,
                                next,
                                config_path_for_timer.as_str(),
                                wayland_display_for_timer.as_str(),
                                "watch",
                            );
                        }
                        reloaded = true;
                    } else {
                        warn!(
                            "watch: reload skipped for {} because config parse/load failed",
                            config_path_for_timer.as_str()
                        );
                    }
                }
                if reloaded {
                    debug!("resolved keybinds: {}", st.runtime.tuning.keybinds_resolved_summary());
                    debug!("resolved zoom: {}", st.runtime.tuning.zoom_resolved_summary());
                }

                {
                    let mut ps = pointer_state_for_timer.borrow_mut();
                    if let (Some(id), Some(until)) = (ps.resize_trace_node, ps.resize_trace_until) {
                        if now >= until {
                            ps.resize_trace_node = None;
                            ps.resize_trace_until = None;
                            ps.resize_trace_last_at = None;
                        } else {
                            let due = ps
                                .resize_trace_last_at
                                .is_none_or(|at| now.duration_since(at).as_millis() as u64 >= 120);
                            if due {
                                if let Some(n) = st.model.field.node(id) {
                                    let surf = current_surface_size_for_node(st, id);
                                    debug!(
                                        "resize-trace id={} pos=({:.1},{:.1}) intrinsic=({:.1},{:.1}) surface={:?} state={:?}",
                                        id.as_u64(),
                                        n.pos.x,
                                        n.pos.y,
                                        n.intrinsic_size.x,
                                        n.intrinsic_size.y,
                                        surf.map(|v| (v.x, v.y)),
                                        n.state,
                                    );
                                } else {
                                    debug!("resize-trace id={} missing-node", id.as_u64());
                                }
                                ps.resize_trace_last_at = Some(now);
                            }
                        }
                    }
                }

                apply_host_cursor(&backend_for_cursor_timer, &st.effective_cursor_image_status());
                backend_handle_for_timer.request_redraw();
                TimeoutAction::ToDuration(frame_interval_for_refresh_hz(
                    st.runtime.tuning
                        .tty_viewports
                        .first()
                        .and_then(|vp| vp.refresh_rate),
                ))
            })?;

            info!("entering main loop");

            loop {
                ev.dispatch(None, &mut state)?;

                if state.exit_requested() || crate::bootstrap::shutdown_requested() {
                    info!("exit requested, shutting down main loop");
                    break Ok(());
                }

                display.dispatch_clients(&mut state)?;
                display.flush_clients()?;
            }
        }
    )
}
