//! OpenXR virtual reality support for head-mounted displays.
//!
//! Run applications in VR with full 6DOF tracking and controller input:
//!
//! - [`XrContext`]: OpenXR session management and device access
//! - [`XrInput`]: Controller state including thumbsticks, triggers, and hand poses
//! - [`XrResources`]: World resource for VR state and locomotion settings
//! - [`XrRenderer`]: Stereo rendering pipeline for VR
//! - `launch_xr`: Entry point to run an application in VR mode
//!
//! Requires the `openxr` feature and a connected VR headset with OpenXR runtime.
//!
//! # Quick Start
//!
//! Launch your application in VR mode:
//!
//! ```ignore
//! use nightshade::xr::launch_xr;
//!
//! fn main() -> Result<(), Box<dyn std::error::Error>> {
//!     let state = MyGameState::new();
//!     launch_xr(state)
//! }
//! ```
//!
//! # Reading Controller Input
//!
//! Access VR controller state through [`XrResources`]:
//!
//! ```ignore
//! fn run_systems(&mut self, world: &mut World) {
//!     if let Some(input) = &world.resources.xr.input {
//!         // Thumbstick movement (left controller)
//!         let move_x = input.thumbstick.x;
//!         let move_y = input.thumbstick.y;
//!
//!         // Trigger and grip (0.0 - 1.0)
//!         if input.left_trigger_pressed() {
//!             // Left trigger held
//!         }
//!         if input.right_grip_pressed() {
//!             // Right grip held
//!         }
//!
//!         // Face buttons
//!         if input.a_button_pressed() {
//!             // A button pressed
//!         }
//!
//!         // Hand tracking
//!         if let Some(left_pos) = input.left_hand_position() {
//!             // World-space left hand position
//!         }
//!         if let Some(left_rot) = input.left_hand_rotation() {
//!             // Left hand orientation as quaternion
//!         }
//!
//!         // Head tracking
//!         let head_pos = input.head_position;
//!         let head_rot = input.head_orientation;
//!     }
//! }
//! ```
//!
//! # Locomotion
//!
//! Enable thumbstick-based movement:
//!
//! ```ignore
//! fn initialize(&mut self, world: &mut World) {
//!     // Enable smooth locomotion with left thumbstick
//!     world.resources.xr.locomotion_enabled = true;
//!     world.resources.xr.locomotion_speed = 3.0;  // Units per second
//!
//!     // Set initial player position
//!     world.resources.xr.initial_player_position = Some(Vec3::new(0.0, 0.0, 5.0));
//!     world.resources.xr.initial_player_yaw = Some(0.0);
//! }
//! ```
//!
//! # Syncing with Character Controller
//!
//! If your scene has a character controller, the VR player position automatically
//! syncs to it. The headset tracks relative to the character's floor position.
//!
//! # Input Properties
//!
//! | Property | Type | Description |
//! |----------|------|-------------|
//! | `thumbstick` | Vec2 | Left thumbstick X/Y (-1 to 1) |
//! | `right_thumbstick` | Vec2 | Right thumbstick X/Y (-1 to 1) |
//! | `left_trigger` | f32 | Left trigger (0 to 1) |
//! | `right_trigger` | f32 | Right trigger (0 to 1) |
//! | `left_grip` | f32 | Left grip (0 to 1) |
//! | `right_grip` | f32 | Right grip (0 to 1) |
//! | `a_button` | bool | A button state |
//! | `b_button` | bool | B button state |
//! | `left_hand_pose` | Option | Left controller pose |
//! | `right_hand_pose` | Option | Right controller pose |
//! | `head_position` | Vec3 | World-space head position |
//! | `head_orientation` | Quat | Head rotation |
//! | `player_yaw` | f32 | Player body rotation |
//!
//! # Supported Controllers
//!
//! Currently configured for Oculus Touch controllers. The input mapping:
//!
//! | Input | Binding |
//! |-------|---------|
//! | Movement | Left thumbstick |
//! | Turn | Right thumbstick |
//! | Left trigger | Left index trigger |
//! | Right trigger | Right index trigger |
//! | Left grip | Left squeeze |
//! | Right grip | Right squeeze |
//! | A button | Right controller A |
//! | B button | Right controller B |
//!
//! # Render Pipeline
//!
//! The VR renderer uses the same passes as the desktop renderer but renders
//! to each eye separately at the headset's native resolution. HDR rendering,
//! bloom, and tone mapping are all supported.

use crate::ecs::world::World;
use crate::render::wgpu::brdf_lut;
use crate::render::wgpu::passes;
use crate::render::wgpu::rendergraph::RenderGraph;
use ash::vk::{self, Handle};
use openxr as xr;

#[derive(Clone, Default)]
pub struct XrResources {
    pub input: Option<XrInput>,
    pub locomotion_enabled: bool,
    pub locomotion_speed: f32,
    pub initial_player_yaw: Option<f32>,
    pub initial_player_position: Option<nalgebra_glm::Vec3>,
    pub camera_override: Option<crate::ecs::camera::queries::CameraMatrices>,
    pub sync_player_position: Option<nalgebra_glm::Vec3>,
}

#[derive(Clone)]
pub struct XrInput {
    pub thumbstick: nalgebra_glm::Vec2,
    pub right_thumbstick: nalgebra_glm::Vec2,
    pub left_trigger: f32,
    pub right_trigger: f32,
    pub left_grip: f32,
    pub right_grip: f32,
    pub a_button: bool,
    pub b_button: bool,
    pub left_thumbstick_click: bool,
    pub left_hand_pose: Option<xr::Posef>,
    pub right_hand_pose: Option<xr::Posef>,
    pub player_position: nalgebra_glm::Vec3,
    pub player_yaw: f32,
    pub head_yaw: f32,
    pub head_position: nalgebra_glm::Vec3,
    pub head_orientation: nalgebra_glm::Quat,
}

impl Default for XrInput {
    fn default() -> Self {
        Self {
            thumbstick: nalgebra_glm::vec2(0.0, 0.0),
            right_thumbstick: nalgebra_glm::vec2(0.0, 0.0),
            left_trigger: 0.0,
            right_trigger: 0.0,
            left_grip: 0.0,
            right_grip: 0.0,
            a_button: false,
            b_button: false,
            left_thumbstick_click: false,
            left_hand_pose: None,
            right_hand_pose: None,
            player_position: nalgebra_glm::vec3(0.0, 0.0, 0.0),
            player_yaw: 0.0,
            head_yaw: 0.0,
            head_position: nalgebra_glm::vec3(0.0, 0.0, 0.0),
            head_orientation: nalgebra_glm::quat_identity(),
        }
    }
}

impl XrInput {
    pub fn left_trigger_pressed(&self) -> bool {
        self.left_trigger > 0.5
    }

    pub fn right_trigger_pressed(&self) -> bool {
        self.right_trigger > 0.5
    }

    pub fn left_grip_pressed(&self) -> bool {
        self.left_grip > 0.5
    }

    pub fn right_grip_pressed(&self) -> bool {
        self.right_grip > 0.5
    }

    pub fn a_button_pressed(&self) -> bool {
        self.a_button
    }

    pub fn b_button_pressed(&self) -> bool {
        self.b_button
    }

    pub fn left_hand_position(&self) -> Option<nalgebra_glm::Vec3> {
        self.left_hand_pose.map(|pose| {
            let local_x = -pose.position.x;
            let local_z = -pose.position.z;
            let rotated_x = local_x * self.player_yaw.cos() + local_z * self.player_yaw.sin();
            let rotated_z = -local_x * self.player_yaw.sin() + local_z * self.player_yaw.cos();
            nalgebra_glm::vec3(
                rotated_x + self.player_position.x,
                pose.position.y + self.player_position.y,
                rotated_z + self.player_position.z,
            )
        })
    }

    pub fn right_hand_position(&self) -> Option<nalgebra_glm::Vec3> {
        self.right_hand_pose.map(|pose| {
            let local_x = -pose.position.x;
            let local_z = -pose.position.z;
            let rotated_x = local_x * self.player_yaw.cos() + local_z * self.player_yaw.sin();
            let rotated_z = -local_x * self.player_yaw.sin() + local_z * self.player_yaw.cos();
            nalgebra_glm::vec3(
                rotated_x + self.player_position.x,
                pose.position.y + self.player_position.y,
                rotated_z + self.player_position.z,
            )
        })
    }

    pub fn left_hand_rotation(&self) -> Option<nalgebra_glm::Quat> {
        self.left_hand_pose.map(|pose| {
            let openxr_orientation = pose.orientation;
            let openxr_quat = nalgebra_glm::quat(
                openxr_orientation.w,
                openxr_orientation.z,
                openxr_orientation.y,
                openxr_orientation.x,
            );
            let flip_x = nalgebra_glm::quat_angle_axis(
                180.0_f32.to_radians(),
                &nalgebra_glm::vec3(1.0, 0.0, 0.0),
            );
            let player_rotation =
                nalgebra_glm::quat_angle_axis(self.player_yaw, &nalgebra_glm::vec3(0.0, 1.0, 0.0));
            player_rotation * flip_x * openxr_quat
        })
    }

    pub fn right_hand_rotation(&self) -> Option<nalgebra_glm::Quat> {
        self.right_hand_pose.map(|pose| {
            let openxr_orientation = pose.orientation;
            let openxr_quat = nalgebra_glm::quat(
                openxr_orientation.w,
                openxr_orientation.z,
                openxr_orientation.y,
                openxr_orientation.x,
            );
            let flip_x = nalgebra_glm::quat_angle_axis(
                180.0_f32.to_radians(),
                &nalgebra_glm::vec3(1.0, 0.0, 0.0),
            );
            let player_rotation =
                nalgebra_glm::quat_angle_axis(self.player_yaw, &nalgebra_glm::vec3(0.0, 1.0, 0.0));
            player_rotation * flip_x * openxr_quat
        })
    }
}
use std::ffi::{CString, c_void};
use web_time::Instant;

const VK_TARGET_VERSION: xr::Version = xr::Version::new(1, 1, 0);
const VK_TARGET_VERSION_ASH: u32 = vk::make_api_version(
    0,
    VK_TARGET_VERSION.major() as u32,
    VK_TARGET_VERSION.minor() as u32,
    VK_TARGET_VERSION.patch(),
);

pub struct XrContext {
    _vk_entry: ash::Entry,
    _vk_instance: ash::Instance,
    instance: xr::Instance,
    _system: xr::SystemId,
    session: xr::Session<xr::Vulkan>,
    frame_wait: xr::FrameWaiter,
    frame_stream: xr::FrameStream<xr::Vulkan>,
    stage: xr::Space,
    swapchain: xr::Swapchain<xr::Vulkan>,
    swapchain_buffers: Vec<wgpu::Texture>,
    resolution: (u32, u32),
    _views: Vec<xr::ViewConfigurationView>,
    action_set: xr::ActionSet,
    move_action: xr::Action<xr::Vector2f>,
    turn_action: xr::Action<xr::Vector2f>,
    _left_hand_action: xr::Action<xr::Posef>,
    _right_hand_action: xr::Action<xr::Posef>,
    left_trigger_action: xr::Action<f32>,
    right_trigger_action: xr::Action<f32>,
    left_grip_action: xr::Action<f32>,
    right_grip_action: xr::Action<f32>,
    a_button_action: xr::Action<bool>,
    b_button_action: xr::Action<bool>,
    left_thumbstick_click_action: xr::Action<bool>,
    left_hand_space: xr::Space,
    right_hand_space: xr::Space,
    player_position: nalgebra_glm::Vec3,
    player_yaw: f32,
    session_running: bool,
    device: wgpu::Device,
    queue: wgpu::Queue,
}

impl XrContext {
    pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
        let xr_entry = xr::Entry::linked();

        let mut required_extensions = xr::ExtensionSet::default();
        required_extensions.khr_vulkan_enable2 = true;

        let xr_instance = xr_entry.create_instance(
            &xr::ApplicationInfo {
                application_name: "nightshade-xr",
                application_version: 1,
                engine_name: "nightshade",
                engine_version: 1,
                api_version: xr::Version::new(1, 0, 0),
            },
            &required_extensions,
            &[],
        )?;

        let system = xr_instance.system(xr::FormFactor::HEAD_MOUNTED_DISPLAY)?;

        let views = xr_instance.enumerate_view_configuration_views(
            system,
            xr::ViewConfigurationType::PRIMARY_STEREO,
        )?;

        let resolution = (
            views[0].recommended_image_rect_width,
            views[0].recommended_image_rect_height,
        );

        let reqs = xr_instance.graphics_requirements::<xr::Vulkan>(system)?;
        if VK_TARGET_VERSION < reqs.min_api_version_supported
            || VK_TARGET_VERSION.major() > reqs.max_api_version_supported.major()
        {
            return Err(format!(
                "OpenXR runtime requires Vulkan version > {}, < {}.0.0",
                reqs.min_api_version_supported,
                reqs.max_api_version_supported.major() + 1
            )
            .into());
        }

        let vk_entry = unsafe { ash::Entry::load()? };
        let flags = wgpu::InstanceFlags::default();
        let instance_exts = <wgpu_hal::vulkan::Api as wgpu_hal::Api>::Instance::desired_extensions(
            &vk_entry,
            VK_TARGET_VERSION_ASH,
            flags,
        )?;

        let vk_instance = unsafe {
            let extensions_cchar: Vec<_> = instance_exts.iter().map(|s| s.as_ptr()).collect();

            let app_name = CString::new("nightshade-xr")?;
            let vk_app_info = vk::ApplicationInfo::default()
                .application_name(&app_name)
                .application_version(1)
                .engine_name(&app_name)
                .engine_version(1)
                .api_version(VK_TARGET_VERSION_ASH);

            let get_instance_proc_addr: unsafe extern "system" fn(
                *const c_void,
                *const i8,
            ) -> Option<
                unsafe extern "system" fn(),
            > = std::mem::transmute(vk_entry.static_fn().get_instance_proc_addr);

            let vk_instance = xr_instance
                .create_vulkan_instance(
                    system,
                    get_instance_proc_addr,
                    &vk::InstanceCreateInfo::default()
                        .application_info(&vk_app_info)
                        .enabled_extension_names(&extensions_cchar) as *const _
                        as *const _,
                )?
                .map_err(vk::Result::from_raw)?;

            ash::Instance::load(
                vk_entry.static_fn(),
                vk::Instance::from_raw(vk_instance as _),
            )
        };

        let vk_physical_device = vk::PhysicalDevice::from_raw(unsafe {
            xr_instance.vulkan_graphics_device(system, vk_instance.handle().as_raw() as _)? as _
        });

        let vk_instance_ptr = vk_instance.handle().as_raw() as *const c_void;
        let vk_physical_device_ptr = vk_physical_device.as_raw() as *const c_void;

        let vk_device_properties =
            unsafe { vk_instance.get_physical_device_properties(vk_physical_device) };

        if vk_device_properties.api_version < VK_TARGET_VERSION_ASH {
            return Err("Vulkan physical device doesn't support version 1.1".into());
        }

        let wgpu_vk_instance = unsafe {
            <wgpu_hal::vulkan::Api as wgpu_hal::Api>::Instance::from_raw(
                vk_entry.clone(),
                vk_instance.clone(),
                vk_device_properties.api_version,
                0,
                None,
                instance_exts,
                flags,
                wgpu::MemoryBudgetThresholds::default(),
                false,
                None,
            )?
        };

        let wgpu_exposed_adapter = wgpu_vk_instance
            .expose_adapter(vk_physical_device)
            .ok_or("Failed to expose adapter")?;

        let adapter_features = wgpu_exposed_adapter.features;
        let desired_features =
            wgpu::Features::INDIRECT_FIRST_INSTANCE | wgpu::Features::MULTI_DRAW_INDIRECT_COUNT;
        let wgpu_features = adapter_features & desired_features;
        let mut enabled_extensions = wgpu_exposed_adapter
            .adapter
            .required_device_extensions(wgpu_features);
        if !enabled_extensions.contains(&ash::khr::swapchain::NAME) {
            enabled_extensions.push(ash::khr::swapchain::NAME);
        }

        let (wgpu_open_device, vk_device_ptr, queue_family_index) = {
            let extensions_cchar: Vec<_> = enabled_extensions.iter().map(|s| s.as_ptr()).collect();
            let mut enabled_phd_features = wgpu_exposed_adapter
                .adapter
                .physical_device_features(&enabled_extensions, wgpu_features);
            let family_index = 0;
            let queue_priorities = [1.0_f32];
            let family_info = vk::DeviceQueueCreateInfo::default()
                .queue_family_index(family_index)
                .queue_priorities(&queue_priorities);
            let family_infos = [family_info];
            let info = enabled_phd_features
                .add_to_device_create(
                    vk::DeviceCreateInfo::default().queue_create_infos(&family_infos),
                )
                .enabled_extension_names(&extensions_cchar);

            let vk_device = unsafe {
                let get_instance_proc_addr: unsafe extern "system" fn(
                    *const c_void,
                    *const i8,
                ) -> Option<
                    unsafe extern "system" fn(),
                > = std::mem::transmute(vk_entry.static_fn().get_instance_proc_addr);

                let vk_device = xr_instance
                    .create_vulkan_device(
                        system,
                        get_instance_proc_addr,
                        vk_physical_device.as_raw() as _,
                        &info as *const _ as *const _,
                    )?
                    .map_err(vk::Result::from_raw)?;

                ash::Device::load(vk_instance.fp_v1_0(), vk::Device::from_raw(vk_device as _))
            };
            let vk_device_ptr = vk_device.handle().as_raw() as *const c_void;

            let wgpu_open_device = unsafe {
                wgpu_exposed_adapter.adapter.device_from_raw(
                    vk_device,
                    None,
                    &enabled_extensions,
                    wgpu_features,
                    &wgpu::MemoryHints::Performance,
                    family_info.queue_family_index,
                    0,
                )
            }?;

            (
                wgpu_open_device,
                vk_device_ptr,
                family_info.queue_family_index,
            )
        };

        let wgpu_instance =
            unsafe { wgpu::Instance::from_hal::<wgpu_hal::api::Vulkan>(wgpu_vk_instance) };
        let wgpu_adapter = unsafe { wgpu_instance.create_adapter_from_hal(wgpu_exposed_adapter) };
        let adapter_limits = wgpu_adapter.limits();
        let limits = adapter_limits;
        let (wgpu_device, wgpu_queue) = unsafe {
            wgpu_adapter.create_device_from_hal(
                wgpu_open_device,
                &wgpu::DeviceDescriptor {
                    label: None,
                    required_features: wgpu_features,
                    required_limits: limits,
                    memory_hints: wgpu::MemoryHints::Performance,
                    experimental_features: wgpu::ExperimentalFeatures::disabled(),
                    trace: wgpu::Trace::Off,
                },
            )
        }?;

        let (session, frame_wait, frame_stream) = unsafe {
            xr_instance.create_session::<xr::Vulkan>(
                system,
                &xr::vulkan::SessionCreateInfo {
                    instance: vk_instance_ptr,
                    physical_device: vk_physical_device_ptr,
                    device: vk_device_ptr,
                    queue_family_index,
                    queue_index: 0,
                },
            )
        }?;

        let action_set = xr_instance.create_action_set("gameplay", "Gameplay Actions", 0)?;
        let move_action = action_set.create_action::<xr::Vector2f>("move", "Move", &[])?;
        let turn_action = action_set.create_action::<xr::Vector2f>("turn", "Turn", &[])?;

        let left_hand_action =
            action_set.create_action::<xr::Posef>("left_hand_pose", "Left Hand Pose", &[])?;

        let right_hand_action =
            action_set.create_action::<xr::Posef>("right_hand_pose", "Right Hand Pose", &[])?;

        let left_trigger_action =
            action_set.create_action::<f32>("left_trigger", "Left Trigger", &[])?;

        let right_trigger_action =
            action_set.create_action::<f32>("right_trigger", "Right Trigger", &[])?;

        let left_grip_action = action_set.create_action::<f32>("left_grip", "Left Grip", &[])?;

        let right_grip_action = action_set.create_action::<f32>("right_grip", "Right Grip", &[])?;

        let a_button_action = action_set.create_action::<bool>("a_button", "A Button", &[])?;

        let b_button_action = action_set.create_action::<bool>("b_button", "B Button", &[])?;

        let left_thumbstick_click_action = action_set.create_action::<bool>(
            "left_thumbstick_click",
            "Left Thumbstick Click",
            &[],
        )?;

        xr_instance.suggest_interaction_profile_bindings(
            xr_instance.string_to_path("/interaction_profiles/oculus/touch_controller")?,
            &[
                xr::Binding::new(
                    &move_action,
                    xr_instance.string_to_path("/user/hand/left/input/thumbstick")?,
                ),
                xr::Binding::new(
                    &turn_action,
                    xr_instance.string_to_path("/user/hand/right/input/thumbstick")?,
                ),
                xr::Binding::new(
                    &left_hand_action,
                    xr_instance.string_to_path("/user/hand/left/input/grip/pose")?,
                ),
                xr::Binding::new(
                    &right_hand_action,
                    xr_instance.string_to_path("/user/hand/right/input/grip/pose")?,
                ),
                xr::Binding::new(
                    &left_trigger_action,
                    xr_instance.string_to_path("/user/hand/left/input/trigger/value")?,
                ),
                xr::Binding::new(
                    &right_trigger_action,
                    xr_instance.string_to_path("/user/hand/right/input/trigger/value")?,
                ),
                xr::Binding::new(
                    &left_grip_action,
                    xr_instance.string_to_path("/user/hand/left/input/squeeze/value")?,
                ),
                xr::Binding::new(
                    &right_grip_action,
                    xr_instance.string_to_path("/user/hand/right/input/squeeze/value")?,
                ),
                xr::Binding::new(
                    &a_button_action,
                    xr_instance.string_to_path("/user/hand/right/input/a/click")?,
                ),
                xr::Binding::new(
                    &b_button_action,
                    xr_instance.string_to_path("/user/hand/right/input/b/click")?,
                ),
                xr::Binding::new(
                    &left_thumbstick_click_action,
                    xr_instance.string_to_path("/user/hand/left/input/thumbstick/click")?,
                ),
            ],
        )?;

        session.attach_action_sets(&[&action_set])?;

        let left_hand_space =
            left_hand_action.create_space(&session, xr::Path::NULL, xr::Posef::IDENTITY)?;

        let right_hand_space =
            right_hand_action.create_space(&session, xr::Path::NULL, xr::Posef::IDENTITY)?;

        let stage =
            session.create_reference_space(xr::ReferenceSpaceType::STAGE, xr::Posef::IDENTITY)?;

        let swapchain = session.create_swapchain(&xr::SwapchainCreateInfo {
            create_flags: xr::SwapchainCreateFlags::EMPTY,
            usage_flags: xr::SwapchainUsageFlags::COLOR_ATTACHMENT
                | xr::SwapchainUsageFlags::SAMPLED,
            format: vk::Format::R8G8B8A8_SRGB.as_raw() as _,
            sample_count: 1,
            width: resolution.0,
            height: resolution.1,
            face_count: 1,
            array_size: 2,
            mip_count: 1,
        })?;

        let swapchain_images = swapchain.enumerate_images()?;
        let swapchain_buffers: Vec<wgpu::Texture> = swapchain_images
            .into_iter()
            .map(|color_image| {
                let color_image = vk::Image::from_raw(color_image);
                let wgpu_hal_texture = unsafe {
                    let hal_dev = wgpu_device
                        .as_hal::<wgpu_hal::vulkan::Api>()
                        .ok_or("Failed to get HAL device")?;
                    hal_dev.texture_from_raw(
                        color_image,
                        &wgpu_hal::TextureDescriptor {
                            label: Some("VR Swapchain"),
                            size: wgpu::Extent3d {
                                width: resolution.0,
                                height: resolution.1,
                                depth_or_array_layers: 2,
                            },
                            mip_level_count: 1,
                            sample_count: 1,
                            dimension: wgpu::TextureDimension::D2,
                            format: wgpu::TextureFormat::Rgba8UnormSrgb,
                            usage: wgpu::TextureUses::COLOR_TARGET | wgpu::TextureUses::COPY_DST,
                            memory_flags: wgpu_hal::MemoryFlags::empty(),
                            view_formats: vec![],
                        },
                        None,
                    )
                };
                let texture = unsafe {
                    wgpu_device.create_texture_from_hal::<wgpu_hal::vulkan::Api>(
                        wgpu_hal_texture,
                        &wgpu::TextureDescriptor {
                            label: Some("VR Swapchain"),
                            size: wgpu::Extent3d {
                                width: resolution.0,
                                height: resolution.1,
                                depth_or_array_layers: 2,
                            },
                            mip_level_count: 1,
                            sample_count: 1,
                            dimension: wgpu::TextureDimension::D2,
                            format: wgpu::TextureFormat::Rgba8UnormSrgb,
                            usage: wgpu::TextureUsages::RENDER_ATTACHMENT
                                | wgpu::TextureUsages::COPY_DST,
                            view_formats: &[],
                        },
                    )
                };
                Ok(texture)
            })
            .collect::<Result<Vec<_>, Box<dyn std::error::Error>>>()?;

        tracing::info!(
            "OpenXR session created successfully with resolution {}x{}",
            resolution.0,
            resolution.1
        );

        Ok(Self {
            _vk_entry: vk_entry,
            _vk_instance: vk_instance,
            instance: xr_instance,
            _system: system,
            session,
            frame_wait,
            frame_stream,
            stage,
            swapchain,
            swapchain_buffers,
            resolution,
            _views: views,
            action_set,
            move_action,
            turn_action,
            _left_hand_action: left_hand_action,
            _right_hand_action: right_hand_action,
            left_trigger_action,
            right_trigger_action,
            left_grip_action,
            right_grip_action,
            a_button_action,
            b_button_action,
            left_thumbstick_click_action,
            left_hand_space,
            right_hand_space,
            player_position: nalgebra_glm::vec3(0.0, 0.0, 0.0),
            player_yaw: 0.0,
            session_running: false,
            device: wgpu_device,
            queue: wgpu_queue,
        })
    }

    pub fn device(&self) -> &wgpu::Device {
        &self.device
    }

    pub fn queue(&self) -> &wgpu::Queue {
        &self.queue
    }

    pub fn resolution(&self) -> (u32, u32) {
        self.resolution
    }

    pub fn surface_format(&self) -> wgpu::TextureFormat {
        wgpu::TextureFormat::Rgba8UnormSrgb
    }

    pub fn set_player_yaw(&mut self, yaw: f32) {
        self.player_yaw = yaw;
    }

    pub fn set_player_position(&mut self, position: nalgebra_glm::Vec3) {
        self.player_position = position;
    }

    pub fn poll_events(&mut self) -> Result<bool, Box<dyn std::error::Error>> {
        let mut event_buffer = xr::EventDataBuffer::new();
        while let Some(event) = self.instance.poll_event(&mut event_buffer)? {
            match event {
                xr::Event::SessionStateChanged(state_change) => {
                    tracing::info!("XR Session state changed to: {:?}", state_change.state());
                    match state_change.state() {
                        xr::SessionState::READY => {
                            self.session
                                .begin(xr::ViewConfigurationType::PRIMARY_STEREO)?;
                            self.session_running = true;
                            tracing::info!("XR Session started");
                        }
                        xr::SessionState::STOPPING => {
                            self.session.end()?;
                            self.session_running = false;
                            tracing::info!("XR Session ended");
                        }
                        xr::SessionState::EXITING | xr::SessionState::LOSS_PENDING => {
                            tracing::info!("XR Session exiting");
                            return Ok(false);
                        }
                        _ => {}
                    }
                }
                xr::Event::InstanceLossPending(_) => {
                    tracing::info!("XR Instance loss pending");
                    return Ok(false);
                }
                _ => {}
            }
        }
        Ok(true)
    }

    pub fn wait_frame(&mut self) -> Result<Option<xr::FrameState>, Box<dyn std::error::Error>> {
        if !self.session_running {
            return Ok(None);
        }
        Ok(Some(self.frame_wait.wait()?))
    }

    pub fn update_input(
        &mut self,
        delta_time: f32,
        predicted_display_time: xr::Time,
        locomotion_enabled: bool,
        locomotion_speed: f32,
    ) -> Result<XrInput, Box<dyn std::error::Error>> {
        self.session.sync_actions(&[(&self.action_set).into()])?;

        let move_state = self.move_action.state(&self.session, xr::Path::NULL)?;
        let turn_state = self.turn_action.state(&self.session, xr::Path::NULL)?;

        let (_, views) = self.session.locate_views(
            xr::ViewConfigurationType::PRIMARY_STEREO,
            predicted_display_time,
            &self.stage,
        )?;

        let (head_yaw, openxr_quat, head_pose_position) = if !views.is_empty() {
            let head_pose = &views[0].pose;
            let quat = nalgebra_glm::quat(
                head_pose.orientation.w,
                head_pose.orientation.z,
                head_pose.orientation.y,
                head_pose.orientation.x,
            );
            let head_forward =
                nalgebra_glm::quat_rotate_vec3(&quat, &nalgebra_glm::vec3(0.0, 0.0, -1.0));
            let yaw = (-head_forward.x).atan2(-head_forward.z);
            (yaw, Some(quat), Some(head_pose.position))
        } else {
            (0.0, None, None)
        };

        if turn_state.current_state.x.abs() > 0.1 {
            let turn_speed = 2.0;
            self.player_yaw -= turn_state.current_state.x * turn_speed * delta_time;
        }

        if locomotion_enabled {
            let move_speed = if locomotion_speed > 0.0 {
                locomotion_speed
            } else {
                2.0
            };

            if move_state.current_state.x.abs() > 0.1 || move_state.current_state.y.abs() > 0.1 {
                let combined_yaw = head_yaw + self.player_yaw;

                let move_x = move_state.current_state.x;
                let move_z = -move_state.current_state.y;

                let rotated_x = move_x * combined_yaw.cos() - move_z * combined_yaw.sin();
                let rotated_z = move_x * combined_yaw.sin() + move_z * combined_yaw.cos();

                self.player_position.x += rotated_x * move_speed * delta_time;
                self.player_position.z += rotated_z * move_speed * delta_time;
            }

            let left_trigger_value = self
                .left_trigger_action
                .state(&self.session, xr::Path::NULL)
                .map(|s| s.current_state)
                .unwrap_or(0.0);
            let right_trigger_value = self
                .right_trigger_action
                .state(&self.session, xr::Path::NULL)
                .map(|s| s.current_state)
                .unwrap_or(0.0);

            let vertical_input = right_trigger_value - left_trigger_value;
            if vertical_input.abs() > 0.1 {
                self.player_position.y += vertical_input * move_speed * delta_time;
            }
        }

        let (head_position, head_orientation) = if let (Some(quat), Some(pos)) =
            (openxr_quat, head_pose_position)
        {
            let local_x = -pos.x;
            let local_z = -pos.z;
            let rotated_x = local_x * self.player_yaw.cos() + local_z * self.player_yaw.sin();
            let rotated_z = -local_x * self.player_yaw.sin() + local_z * self.player_yaw.cos();
            let world_head_pos =
                self.player_position + nalgebra_glm::vec3(rotated_x, pos.y, rotated_z);

            let player_rotation =
                nalgebra_glm::quat_angle_axis(self.player_yaw, &nalgebra_glm::vec3(0.0, 1.0, 0.0));
            let flip_x = nalgebra_glm::quat_angle_axis(
                std::f32::consts::PI,
                &nalgebra_glm::vec3(1.0, 0.0, 0.0),
            );
            let world_head_orientation = player_rotation * flip_x * quat;

            (world_head_pos, world_head_orientation)
        } else {
            (self.player_position, nalgebra_glm::quat_identity())
        };

        let left_trigger = self
            .left_trigger_action
            .state(&self.session, xr::Path::NULL)
            .map(|s| s.current_state)
            .unwrap_or(0.0);

        let right_trigger = self
            .right_trigger_action
            .state(&self.session, xr::Path::NULL)
            .map(|s| s.current_state)
            .unwrap_or(0.0);

        let left_grip = self
            .left_grip_action
            .state(&self.session, xr::Path::NULL)
            .map(|s| s.current_state)
            .unwrap_or(0.0);

        let right_grip = self
            .right_grip_action
            .state(&self.session, xr::Path::NULL)
            .map(|s| s.current_state)
            .unwrap_or(0.0);

        let a_button = self
            .a_button_action
            .state(&self.session, xr::Path::NULL)
            .map(|s| s.current_state)
            .unwrap_or(false);

        let b_button = self
            .b_button_action
            .state(&self.session, xr::Path::NULL)
            .map(|s| s.current_state)
            .unwrap_or(false);

        let left_thumbstick_click = self
            .left_thumbstick_click_action
            .state(&self.session, xr::Path::NULL)
            .map(|s| s.current_state)
            .unwrap_or(false);

        let left_hand_pose = self
            .left_hand_space
            .locate(&self.stage, predicted_display_time)
            .ok()
            .filter(|loc| {
                loc.location_flags.contains(
                    xr::SpaceLocationFlags::POSITION_VALID
                        | xr::SpaceLocationFlags::ORIENTATION_VALID,
                )
            })
            .map(|loc| loc.pose);

        let right_hand_pose = self
            .right_hand_space
            .locate(&self.stage, predicted_display_time)
            .ok()
            .filter(|loc| {
                loc.location_flags.contains(
                    xr::SpaceLocationFlags::POSITION_VALID
                        | xr::SpaceLocationFlags::ORIENTATION_VALID,
                )
            })
            .map(|loc| loc.pose);

        Ok(XrInput {
            thumbstick: nalgebra_glm::vec2(move_state.current_state.x, move_state.current_state.y),
            right_thumbstick: nalgebra_glm::vec2(
                turn_state.current_state.x,
                turn_state.current_state.y,
            ),
            left_trigger,
            right_trigger,
            left_grip,
            right_grip,
            a_button,
            b_button,
            left_thumbstick_click,
            left_hand_pose,
            right_hand_pose,
            player_position: self.player_position,
            player_yaw: self.player_yaw,
            head_yaw,
            head_position,
            head_orientation,
        })
    }

    pub fn begin_frame(&mut self) -> Result<Option<XrFrameContext>, Box<dyn std::error::Error>> {
        let Some(frame_state) = self.wait_frame()? else {
            return Ok(None);
        };

        self.frame_stream.begin()?;

        if !frame_state.should_render {
            self.frame_stream.end(
                frame_state.predicted_display_time,
                xr::EnvironmentBlendMode::OPAQUE,
                &[],
            )?;
            return Ok(None);
        }

        let (view_state_flags, views) = self.session.locate_views(
            xr::ViewConfigurationType::PRIMARY_STEREO,
            frame_state.predicted_display_time,
            &self.stage,
        )?;

        if !view_state_flags
            .contains(xr::ViewStateFlags::POSITION_VALID | xr::ViewStateFlags::ORIENTATION_VALID)
        {
            self.frame_stream.end(
                frame_state.predicted_display_time,
                xr::EnvironmentBlendMode::OPAQUE,
                &[],
            )?;
            return Ok(None);
        }

        let image_index = self.swapchain.acquire_image()?;
        self.swapchain.wait_image(xr::Duration::INFINITE)?;

        Ok(Some(XrFrameContext {
            frame_state,
            views,
            image_index,
            player_position: self.player_position,
            player_yaw: self.player_yaw,
        }))
    }

    pub fn get_eye_texture_view(
        &self,
        frame_ctx: &XrFrameContext,
        eye_index: usize,
    ) -> wgpu::TextureView {
        let swapchain_texture = &self.swapchain_buffers[frame_ctx.image_index as usize];
        swapchain_texture.create_view(&wgpu::TextureViewDescriptor {
            label: Some(&format!("XR Eye {}", eye_index)),
            format: Some(wgpu::TextureFormat::Rgba8UnormSrgb),
            dimension: Some(wgpu::TextureViewDimension::D2),
            aspect: wgpu::TextureAspect::All,
            base_mip_level: 0,
            mip_level_count: None,
            base_array_layer: eye_index as u32,
            array_layer_count: Some(1),
            usage: None,
        })
    }

    pub fn end_frame(
        &mut self,
        frame_ctx: XrFrameContext,
    ) -> Result<(), Box<dyn std::error::Error>> {
        self.swapchain.release_image()?;

        let rect = xr::Rect2Di {
            offset: xr::Offset2Di { x: 0, y: 0 },
            extent: xr::Extent2Di {
                width: self.resolution.0 as i32,
                height: self.resolution.1 as i32,
            },
        };

        let sub_images: Vec<_> = frame_ctx
            .views
            .iter()
            .enumerate()
            .map(|(view_index, view)| {
                xr::CompositionLayerProjectionView::new()
                    .pose(view.pose)
                    .fov(view.fov)
                    .sub_image(
                        xr::SwapchainSubImage::new()
                            .swapchain(&self.swapchain)
                            .image_array_index(view_index as u32)
                            .image_rect(rect),
                    )
            })
            .collect();

        let projection_layer = xr::CompositionLayerProjection::new()
            .space(&self.stage)
            .views(&sub_images);

        self.frame_stream.end(
            frame_ctx.frame_state.predicted_display_time,
            xr::EnvironmentBlendMode::OPAQUE,
            &[&projection_layer],
        )?;

        Ok(())
    }
}

pub struct XrFrameContext {
    pub frame_state: xr::FrameState,
    pub views: Vec<xr::View>,
    pub image_index: u32,
    pub player_position: nalgebra_glm::Vec3,
    pub player_yaw: f32,
}

impl XrFrameContext {
    pub fn view_matrix(&self, eye_index: usize) -> nalgebra_glm::Mat4 {
        let view = &self.views[eye_index];
        let pose = &view.pose;

        let rotation = {
            let orientation = pose.orientation;
            let flip_x = nalgebra_glm::quat_angle_axis(
                180.0_f32.to_radians(),
                &nalgebra_glm::vec3(1.0, 0.0, 0.0),
            );
            let openxr_quat =
                nalgebra_glm::quat(orientation.w, orientation.z, orientation.y, orientation.x);
            let player_rotation =
                nalgebra_glm::quat_angle_axis(self.player_yaw, &nalgebra_glm::vec3(0.0, 1.0, 0.0));
            player_rotation * flip_x * openxr_quat
        };

        let local_x = -pose.position.x;
        let local_z = -pose.position.z;
        let rotated_x = local_x * self.player_yaw.cos() + local_z * self.player_yaw.sin();
        let rotated_z = -local_x * self.player_yaw.sin() + local_z * self.player_yaw.cos();
        let translation = nalgebra_glm::vec3(rotated_x, pose.position.y, rotated_z);
        let eye = translation + self.player_position;
        let target =
            eye + nalgebra_glm::quat_rotate_vec3(&rotation, &nalgebra_glm::vec3(0.0, 0.0, 1.0));
        let up = nalgebra_glm::quat_rotate_vec3(&rotation, &nalgebra_glm::vec3(0.0, 1.0, 0.0));

        nalgebra_glm::look_at_rh(&eye, &target, &up)
    }

    pub fn projection_matrix(&self, eye_index: usize) -> nalgebra_glm::Mat4 {
        let view = &self.views[eye_index];
        let fov = &view.fov;

        let tan_left = fov.angle_left.tan();
        let tan_right = fov.angle_right.tan();
        let tan_up = fov.angle_up.tan();
        let tan_down = fov.angle_down.tan();

        let near = 0.1_f32;

        let tan_width = tan_right - tan_left;
        let tan_height = tan_up - tan_down;

        let a11 = 2.0 / tan_width;
        let a22 = 2.0 / tan_height;
        let a31 = (tan_right + tan_left) / tan_width;
        let a32 = (tan_up + tan_down) / tan_height;

        let mut proj = nalgebra_glm::Mat4::zeros();
        proj[(0, 0)] = a11;
        proj[(1, 1)] = a22;
        proj[(0, 2)] = a31;
        proj[(1, 2)] = a32;
        proj[(2, 2)] = 0.0;
        proj[(2, 3)] = near;
        proj[(3, 2)] = -1.0;

        proj
    }

    pub fn camera_position(&self, eye_index: usize) -> nalgebra_glm::Vec3 {
        let view = &self.views[eye_index];
        let pose = &view.pose;
        let local_x = -pose.position.x;
        let local_z = -pose.position.z;
        let rotated_x = local_x * self.player_yaw.cos() + local_z * self.player_yaw.sin();
        let rotated_z = -local_x * self.player_yaw.sin() + local_z * self.player_yaw.cos();
        let translation = nalgebra_glm::vec3(rotated_x, pose.position.y, rotated_z);
        translation + self.player_position
    }

    pub fn eye_count(&self) -> usize {
        self.views.len()
    }
}

pub struct XrRenderer {
    graph: RenderGraph<World>,
    _depth_id: crate::render::wgpu::rendergraph::ResourceId,
    _scene_color_id: crate::render::wgpu::rendergraph::ResourceId,
    swapchain_id: crate::render::wgpu::rendergraph::ResourceId,
    _brdf_lut_texture: wgpu::Texture,
    brdf_lut_view: wgpu::TextureView,
}

impl XrRenderer {
    pub fn new(
        device: &wgpu::Device,
        queue: &wgpu::Queue,
        resolution: (u32, u32),
        surface_format: wgpu::TextureFormat,
    ) -> Result<Self, Box<dyn std::error::Error>> {
        let depth_format = wgpu::TextureFormat::Depth32Float;
        let hdr_format = wgpu::TextureFormat::Rgba16Float;

        let clear_pass = passes::ClearPass::new();
        let sky_pass = passes::SkyPass::new(device, hdr_format, depth_format);
        let grid_pass = passes::GridPass::new(device, hdr_format, depth_format);
        let scene_pass = passes::ScenePass::new(device, hdr_format, depth_format);
        let shadow_depth_pass = passes::ShadowDepthPass::new(device, queue);
        let mesh_pass = passes::MeshPass::new(device, queue, hdr_format, depth_format, resolution);
        let skinned_mesh_pass =
            passes::SkinnedMeshPass::new(device, queue, hdr_format, depth_format);
        let lines_pass = passes::LinesPass::new(device, hdr_format);
        let sprite_pass = passes::SpritePass::new(device, hdr_format, queue);
        let particle_pass = passes::ParticlePass::new(device, hdr_format);

        let mut graph = RenderGraph::new();

        let depth_id = graph
            .add_depth_texture("depth")
            .size(resolution.0.max(1), resolution.1.max(1))
            .clear_depth(0.0)
            .transient();

        let scene_color_id = graph
            .add_color_texture("scene_color")
            .format(hdr_format)
            .size(resolution.0.max(1), resolution.1.max(1))
            .clear_color(wgpu::Color::BLACK)
            .transient();

        let swapchain_id = graph
            .add_color_texture("swapchain")
            .format(surface_format)
            .external();

        let shadow_depth_id = graph
            .add_depth_texture("shadow_depth")
            .size(8192, 8192)
            .format(wgpu::TextureFormat::Depth32Float)
            .clear_depth(0.0)
            .transient();

        let spotlight_shadow_atlas_id = graph
            .add_depth_texture("spotlight_shadow_atlas")
            .size(4096, 4096)
            .format(wgpu::TextureFormat::Depth32Float)
            .clear_depth(0.0)
            .transient();

        graph.add_pass(
            Box::new(clear_pass),
            &[("color", scene_color_id), ("depth", depth_id)],
        )?;

        graph.add_pass(Box::new(sky_pass), &[("color", scene_color_id)])?;

        graph.add_pass(
            Box::new(scene_pass),
            &[("color", scene_color_id), ("depth", depth_id)],
        )?;

        graph.add_pass(
            Box::new(shadow_depth_pass),
            &[
                ("shadow_depth", shadow_depth_id),
                ("spotlight_shadow_atlas", spotlight_shadow_atlas_id),
            ],
        )?;

        graph.add_pass(
            Box::new(mesh_pass),
            &[
                ("color", scene_color_id),
                ("depth", depth_id),
                ("shadow_depth", shadow_depth_id),
                ("spotlight_shadow_atlas", spotlight_shadow_atlas_id),
            ],
        )?;

        graph.add_pass(
            Box::new(skinned_mesh_pass),
            &[
                ("color", scene_color_id),
                ("depth", depth_id),
                ("shadow_depth", shadow_depth_id),
                ("spotlight_shadow_atlas", spotlight_shadow_atlas_id),
            ],
        )?;

        graph.add_pass(
            Box::new(grid_pass),
            &[("color", scene_color_id), ("depth", depth_id)],
        )?;

        graph.add_pass(
            Box::new(lines_pass),
            &[("color", scene_color_id), ("depth", depth_id)],
        )?;

        graph.add_pass(Box::new(sprite_pass), &[("color", scene_color_id)])?;

        graph.add_pass(
            Box::new(particle_pass),
            &[("color", scene_color_id), ("depth", depth_id)],
        )?;

        let bloom_width = resolution.0 / 2;
        let bloom_height = resolution.1 / 2;

        let bloom_texture_id = graph
            .add_color_texture("bloom")
            .format(hdr_format)
            .size(bloom_width.max(1), bloom_height.max(1))
            .clear_color(wgpu::Color::BLACK)
            .transient();

        let bloom_pass = passes::BloomPass::new(device, resolution.0, resolution.1);
        graph.add_pass(
            Box::new(bloom_pass),
            &[("hdr", scene_color_id), ("bloom", bloom_texture_id)],
        )?;

        let postprocess_pass = passes::PostProcessPass::new(device, surface_format, 0.1);
        graph.add_pass(
            Box::new(postprocess_pass),
            &[
                ("hdr", scene_color_id),
                ("bloom", bloom_texture_id),
                ("output", swapchain_id),
            ],
        )?;

        graph.compile()?;

        let (brdf_lut_texture, brdf_lut_view) = brdf_lut::generate_brdf_lut(device, queue);

        Ok(Self {
            graph,
            _depth_id: depth_id,
            _scene_color_id: scene_color_id,
            swapchain_id,
            _brdf_lut_texture: brdf_lut_texture,
            brdf_lut_view,
        })
    }

    pub fn render_eye(
        &mut self,
        device: &wgpu::Device,
        queue: &wgpu::Queue,
        world: &mut World,
        eye_texture_view: wgpu::TextureView,
        resolution: (u32, u32),
    ) -> Result<(), Box<dyn std::error::Error>> {
        self.process_commands(device, queue, world);

        self.graph.set_external_texture(
            self.swapchain_id,
            eye_texture_view,
            resolution.0,
            resolution.1,
        );

        let command_buffers = self.graph.execute(device, queue, world)?;
        queue.submit(command_buffers);

        Ok(())
    }

    fn process_commands(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, world: &mut World) {
        let mut texture_load_commands = Vec::new();
        let mut hdr_skybox_commands = Vec::new();

        for cmd in world.resources.command_queue.iter() {
            match cmd {
                crate::ecs::world::WorldCommand::LoadTexture {
                    name,
                    rgba_data,
                    width,
                    height,
                } => {
                    texture_load_commands.push((name.clone(), rgba_data.clone(), *width, *height));
                }
                crate::ecs::world::WorldCommand::LoadHdrSkybox { hdr_data } => {
                    hdr_skybox_commands.push(hdr_data.clone());
                }
                _ => {}
            }
        }

        if !texture_load_commands.is_empty() {
            for (name, rgba_data, width, height) in texture_load_commands {
                if let Err(e) = world.resources.texture_cache.load_texture_from_raw_rgba(
                    device,
                    queue,
                    name.clone(),
                    &rgba_data,
                    (width, height),
                ) {
                    tracing::error!("Failed to load texture: {}", e);
                    continue;
                }

                if let Some(mesh_pass) = self.graph.get_pass_mut("mesh_pass")
                    && let Some(mesh_pass) =
                        (mesh_pass as &mut dyn std::any::Any).downcast_mut::<passes::MeshPass>()
                    && let Some(texture_entry) = world.resources.texture_cache.get(&name)
                {
                    mesh_pass.register_texture(
                        name.clone(),
                        texture_entry.view.clone(),
                        texture_entry.sampler.clone(),
                    );
                }

                if let Some(skinned_mesh_pass) = self.graph.get_pass_mut("skinned_mesh_pass")
                    && let Some(skinned_mesh_pass) = (skinned_mesh_pass as &mut dyn std::any::Any)
                        .downcast_mut::<passes::SkinnedMeshPass>()
                    && let Some(texture_entry) = world.resources.texture_cache.get(&name)
                {
                    skinned_mesh_pass.register_texture(
                        name,
                        texture_entry.view.clone(),
                        texture_entry.sampler.clone(),
                    );
                }
            }

            world
                .resources
                .command_queue
                .retain(|cmd| !matches!(cmd, crate::ecs::world::WorldCommand::LoadTexture { .. }));
        }

        if !hdr_skybox_commands.is_empty() {
            let mut ibl_views = None;

            if let Some(sky_pass) = self.graph.get_pass_mut("sky_pass")
                && let Some(sky_pass) =
                    (sky_pass as &mut dyn std::any::Any).downcast_mut::<passes::geometry::SkyPass>()
            {
                for hdr_data in hdr_skybox_commands {
                    ibl_views = Some(sky_pass.load_hdr_skybox(device, queue, &hdr_data));
                }
            }

            if let Some((irradiance_view, prefiltered_view)) = ibl_views {
                if let Some(mesh_pass) = self.graph.get_pass_mut("mesh_pass")
                    && let Some(mesh_pass) =
                        (mesh_pass as &mut dyn std::any::Any).downcast_mut::<passes::MeshPass>()
                {
                    mesh_pass.update_ibl_textures(
                        self.brdf_lut_view.clone(),
                        irradiance_view.clone(),
                        prefiltered_view.clone(),
                    );
                }

                if let Some(skinned_mesh_pass) = self.graph.get_pass_mut("skinned_mesh_pass")
                    && let Some(skinned_mesh_pass) = (skinned_mesh_pass as &mut dyn std::any::Any)
                        .downcast_mut::<passes::geometry::SkinnedMeshPass>(
                    )
                {
                    skinned_mesh_pass.set_ibl_textures(
                        self.brdf_lut_view.clone(),
                        irradiance_view,
                        prefiltered_view,
                    );
                }
            }

            world.resources.command_queue.retain(|cmd| {
                !matches!(cmd, crate::ecs::world::WorldCommand::LoadHdrSkybox { .. })
            });
        }

        if let Some(lines_pass) = self.graph.get_pass_mut("lines_pass")
            && let Some(lines_pass) =
                (lines_pass as &mut dyn std::any::Any).downcast_mut::<passes::LinesPass>()
        {
            passes::sync_lines_data(lines_pass, device, queue, world);
        }
    }

    pub fn graph_mut(&mut self) -> &mut RenderGraph<World> {
        &mut self.graph
    }
}

pub fn launch_xr<S: crate::run::State + 'static>(
    mut state: S,
) -> Result<(), Box<dyn std::error::Error>> {
    #[cfg(not(feature = "tracing"))]
    tracing_subscriber::fmt::init();

    #[cfg(all(feature = "tracing", not(feature = "tracy"), not(feature = "chrome")))]
    let _guard = {
        use tracing_subscriber::EnvFilter;
        use tracing_subscriber::layer::SubscriberExt;
        use tracing_subscriber::util::SubscriberInitExt;

        let file_appender = tracing_appender::rolling::daily("logs", "nightshade.log");
        let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);

        let env_filter =
            EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));

        tracing_subscriber::registry()
            .with(env_filter)
            .with(tracing_subscriber::fmt::layer())
            .with(
                tracing_subscriber::fmt::layer()
                    .with_writer(non_blocking)
                    .with_ansi(false),
            )
            .init();

        guard
    };

    #[cfg(all(feature = "tracy", not(feature = "chrome")))]
    let _guard = {
        use tracing_subscriber::EnvFilter;
        use tracing_subscriber::layer::SubscriberExt;
        use tracing_subscriber::util::SubscriberInitExt;

        let file_appender = tracing_appender::rolling::daily("logs", "nightshade.log");
        let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);

        let env_filter =
            EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));

        tracing_subscriber::registry()
            .with(env_filter)
            .with(tracing_subscriber::fmt::layer())
            .with(
                tracing_subscriber::fmt::layer()
                    .with_writer(non_blocking)
                    .with_ansi(false),
            )
            .with(tracing_tracy::TracyLayer::default())
            .init();

        guard
    };

    #[cfg(all(feature = "chrome", not(feature = "tracy")))]
    let _guard = {
        use tracing_subscriber::EnvFilter;
        use tracing_subscriber::layer::SubscriberExt;
        use tracing_subscriber::util::SubscriberInitExt;

        let file_appender = tracing_appender::rolling::daily("logs", "nightshade.log");
        let (non_blocking, file_guard) = tracing_appender::non_blocking(file_appender);

        let env_filter =
            EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));

        let (chrome_layer, chrome_guard) = tracing_chrome::ChromeLayerBuilder::new()
            .file("logs/trace.json")
            .build();

        tracing_subscriber::registry()
            .with(env_filter)
            .with(tracing_subscriber::fmt::layer())
            .with(
                tracing_subscriber::fmt::layer()
                    .with_writer(non_blocking)
                    .with_ansi(false),
            )
            .with(chrome_layer)
            .init();

        (file_guard, chrome_guard)
    };

    #[cfg(all(feature = "tracy", feature = "chrome"))]
    let _guard = {
        use tracing_subscriber::EnvFilter;
        use tracing_subscriber::layer::SubscriberExt;
        use tracing_subscriber::util::SubscriberInitExt;

        let file_appender = tracing_appender::rolling::daily("logs", "nightshade.log");
        let (non_blocking, file_guard) = tracing_appender::non_blocking(file_appender);

        let env_filter =
            EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));

        let (chrome_layer, chrome_guard) = tracing_chrome::ChromeLayerBuilder::new()
            .file("logs/trace.json")
            .build();

        tracing_subscriber::registry()
            .with(env_filter)
            .with(tracing_subscriber::fmt::layer())
            .with(
                tracing_subscriber::fmt::layer()
                    .with_writer(non_blocking)
                    .with_ansi(false),
            )
            .with(tracing_tracy::TracyLayer::default())
            .with(chrome_layer)
            .init();

        (file_guard, chrome_guard)
    };

    tracing::info!("Initializing OpenXR mode: {}", state.title());

    let mut xr_context = XrContext::new()?;
    let mut world = World::default();

    let resolution = xr_context.resolution();
    let surface_format = xr_context.surface_format();

    let mut renderer = XrRenderer::new(
        xr_context.device(),
        xr_context.queue(),
        resolution,
        surface_format,
    )?;

    world.resources.xr.locomotion_enabled = true;
    state.initialize(&mut world);

    if let Some(initial_yaw) = world.resources.xr.initial_player_yaw {
        xr_context.set_player_yaw(initial_yaw);
    }

    if let Some(initial_position) = world.resources.xr.initial_player_position {
        xr_context.set_player_position(initial_position);
    } else {
        let character_controllers: Vec<_> = world
            .query_entities(
                crate::ecs::world::CHARACTER_CONTROLLER | crate::ecs::world::LOCAL_TRANSFORM,
            )
            .collect();
        if let Some(entity) = character_controllers.first()
            && let Some(transform) = world.get_local_transform(*entity)
        {
            let floor_offset = if let Some(cc) = world.get_character_controller(*entity) {
                match &cc.shape {
                    crate::ecs::physics::ColliderShape::Capsule {
                        half_height,
                        radius,
                    } => half_height + radius,
                    crate::ecs::physics::ColliderShape::Cylinder { half_height, .. } => {
                        *half_height
                    }
                    crate::ecs::physics::ColliderShape::Cuboid { hy, .. } => *hy,
                    crate::ecs::physics::ColliderShape::Ball { radius } => *radius,
                    _ => 1.0,
                }
            } else {
                1.0
            };
            let floor_position = nalgebra_glm::vec3(
                transform.translation.x,
                transform.translation.y - floor_offset,
                transform.translation.z,
            );
            xr_context.set_player_position(floor_position);
        }
    }

    let mut last_render_time = Instant::now();
    let initial_time = Instant::now();

    tracing::info!("Starting XR render loop");

    loop {
        if !xr_context.poll_events()? {
            tracing::info!("XR session ended, exiting");
            break;
        }

        let Some(mut frame_ctx) = xr_context.begin_frame()? else {
            std::thread::sleep(std::time::Duration::from_millis(10));
            continue;
        };

        let now = Instant::now();
        let delta_time = (now - last_render_time).as_secs_f32();
        last_render_time = now;

        world.resources.window.timing.delta_time = delta_time;
        world.resources.window.timing.raw_delta_time = delta_time;
        world.resources.window.timing.uptime_milliseconds = (now - initial_time).as_millis() as u64;

        let locomotion_enabled = world.resources.xr.locomotion_enabled;
        let locomotion_speed = world.resources.xr.locomotion_speed;
        let xr_input = xr_context.update_input(
            delta_time,
            frame_ctx.frame_state.predicted_display_time,
            locomotion_enabled,
            locomotion_speed,
        )?;
        frame_ctx.player_yaw = xr_input.player_yaw;
        frame_ctx.player_position = xr_input.player_position;
        world.resources.xr.input = Some(xr_input);

        state.run_systems(&mut world);

        crate::run::run_frame_systems(&mut world);

        let character_controllers: Vec<_> = world
            .query_entities(
                crate::ecs::world::CHARACTER_CONTROLLER | crate::ecs::world::LOCAL_TRANSFORM,
            )
            .collect();
        if let Some(entity) = character_controllers.first()
            && let Some(transform) = world.get_local_transform(*entity)
        {
            let floor_offset = if let Some(cc) = world.get_character_controller(*entity) {
                match &cc.shape {
                    crate::ecs::physics::ColliderShape::Capsule {
                        half_height,
                        radius,
                    } => half_height + radius,
                    crate::ecs::physics::ColliderShape::Cylinder { half_height, .. } => {
                        *half_height
                    }
                    crate::ecs::physics::ColliderShape::Cuboid { hy, .. } => *hy,
                    crate::ecs::physics::ColliderShape::Ball { radius } => *radius,
                    _ => 1.0,
                }
            } else {
                1.0
            };
            let floor_position = nalgebra_glm::vec3(
                transform.translation.x,
                transform.translation.y - floor_offset,
                transform.translation.z,
            );
            xr_context.set_player_position(floor_position);
        }

        for eye_index in 0..frame_ctx.eye_count() {
            let view_matrix = frame_ctx.view_matrix(eye_index);
            let projection_matrix = frame_ctx.projection_matrix(eye_index);
            let camera_position = frame_ctx.camera_position(eye_index);

            world.resources.xr.camera_override =
                Some(crate::ecs::camera::queries::CameraMatrices {
                    camera_position,
                    projection: projection_matrix,
                    view: view_matrix,
                });

            let eye_texture_view = xr_context.get_eye_texture_view(&frame_ctx, eye_index);

            renderer.render_eye(
                xr_context.device(),
                xr_context.queue(),
                &mut world,
                eye_texture_view,
                resolution,
            )?;
        }

        world.resources.xr.camera_override = None;
        world.resources.xr.input = None;

        xr_context.end_frame(frame_ctx)?;
    }

    Ok(())
}
