#[cfg(target_arch = "wasm32")]
use super::async_model_loading::fetch_model_data;
#[cfg(not(target_arch = "wasm32"))]
use super::glb_parser::parse_glb;
use super::{
    camera::{CameraUniform, Projection},
    light::{BiasUniform, LightUniform},
    mesh::Mesh,
    offset::ModelTransform,
    vertex::Vertex,
};
use buttery_engine::{
    camera::Camera,
    game::ButteryGame,
    renderer::ButteryRenderer,
    ui::{ButteryColor, ButteryUIElement, ButteryUIModel, ButteryUIWindowRelativePosition},
    world_model::ButteryWorldModel,
};
use bytemuck::bytes_of;
use cgmath::{Deg, Matrix4};
use egui::{Align2, Color32, Frame, Id, Stroke, Ui, vec2};
#[cfg(not(target_arch = "wasm32"))]
use std::io::Read;
use std::{any::Any, collections::HashMap, sync::Arc};
use std::{cell::RefCell, rc::Rc};
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_futures::spawn_local;
use wgpu::{
    BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BufferBindingType, ShaderStages,
    util::DeviceExt,
};
use winit::window::Window;

pub enum MeshState {
    Loading,
    Ready(Vec<Arc<Mesh>>),
    Error(String),
}

pub struct MeshStateHandle {
    state: MeshState,
}

pub struct RenderableObject {
    mesh: Arc<Mesh>,
    transform: ModelTransform,
    transform_buffer: wgpu::Buffer,
    transform_bind_group: wgpu::BindGroup,
}

pub struct SlipperyRenderer<G: ButteryGame> {
    surface: wgpu::Surface<'static>,
    config: wgpu::SurfaceConfiguration,
    is_surface_configured: bool,
    device: wgpu::Device,
    queue: wgpu::Queue,
    pub window: Arc<Window>,
    msaa_samples: u32,
    msaa_texture: wgpu::TextureView,
    render_pipeline: wgpu::RenderPipeline,

    pub objects: Vec<RenderableObject>,
    pub mesh_cache: HashMap<String, Rc<RefCell<MeshStateHandle>>>,
    depth_format: wgpu::TextureFormat,

    projection: Projection,

    camera_uniform: CameraUniform,
    camera_buffer: wgpu::Buffer,
    camera_light_bind_group: wgpu::BindGroup,

    light: LightUniform,
    light_buffer: wgpu::Buffer,
    shadow_bind_group: wgpu::BindGroup,
    shadow_view: wgpu::TextureView,
    light_camera: Camera,
    light_camera_uniform: CameraUniform,
    light_projection: Projection,
    light_render_pipeline: wgpu::RenderPipeline,

    pub egui_ctx: egui::Context,
    pub egui_state: egui_winit::State,
    pub egui_renderer: egui_wgpu::Renderer,
    ui_model: Option<ButteryUIModel<G>>,

    show_light_view: bool,
    texture_bind_group_layout: wgpu::BindGroupLayout,
    transform_bind_group_layout: wgpu::BindGroupLayout,

    background_color: ButteryColor,
}

impl<G: ButteryGame> SlipperyRenderer<G> {
    pub async fn new(
        window: Arc<Window>,
        background_color: Option<ButteryColor>,
    ) -> anyhow::Result<Self> {
        let size = window.inner_size();

        let msaa_samples = 4;

        let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
            #[cfg(not(target_arch = "wasm32"))]
            backends: wgpu::Backends::PRIMARY,
            #[cfg(target_arch = "wasm32")]
            backends: wgpu::Backends::GL,
            ..Default::default()
        });

        let surface = instance.create_surface(window.clone()).unwrap();

        let adapter = instance
            .request_adapter(&wgpu::RequestAdapterOptions {
                power_preference: wgpu::PowerPreference::default(),
                compatible_surface: Some(&surface),
                force_fallback_adapter: false,
            })
            .await?;

        let (device, queue) = adapter
            .request_device(&wgpu::DeviceDescriptor {
                label: None,
                required_features: wgpu::Features::empty(),
                experimental_features: wgpu::ExperimentalFeatures::disabled(),
                // WebGL doesn't support all of wgpu's features, so if
                // we're building for the web we'll have to disable some.
                required_limits: if cfg!(target_arch = "wasm32") {
                    wgpu::Limits::downlevel_webgl2_defaults()
                } else {
                    wgpu::Limits::default()
                },
                memory_hints: Default::default(),
                trace: wgpu::Trace::Off,
            })
            .await?;

        let surface_caps = surface.get_capabilities(&adapter);
        let surface_format = surface_caps
            .formats
            .iter()
            .find(|f| f.is_srgb())
            .copied()
            .unwrap_or(surface_caps.formats[0]);

        let width = if size.width < 1 { 1 } else { size.width };
        let height = if size.height < 1 { 1 } else { size.height };
        let config = wgpu::SurfaceConfiguration {
            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
            format: surface_format,
            width: width,
            height: height,
            present_mode: surface_caps.present_modes[0],
            alpha_mode: surface_caps.alpha_modes[0],
            view_formats: vec![],
            desired_maximum_frame_latency: 2,
        };

        let texture_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("texture_bind_group_layout"),
                entries: &[
                    wgpu::BindGroupLayoutEntry {
                        binding: 0,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Texture {
                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
                            view_dimension: wgpu::TextureViewDimension::D2,
                            multisampled: false,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 1,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
                        count: None,
                    },
                ],
            });

        let camera = Camera::new((0.0, 4.0, 6.0), Deg(-90.0), Deg(-35.0));
        let projection =
            Projection::new(config.width, config.height, cgmath::Deg(45.0), 0.1, 100.0);

        let mut camera_uniform = CameraUniform::new();
        camera_uniform.update_view_proj(&camera, &projection);

        let camera_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
            label: Some("Camera Buffer"),
            contents: bytemuck::cast_slice(&[camera_uniform]),
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
        });

        let camera_light_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                entries: &[
                    wgpu::BindGroupLayoutEntry {
                        binding: 0,
                        visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Uniform,
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 1,
                        visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Uniform,
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 2,
                        visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Uniform,
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                ],
                label: Some("camera_bind_group_layout"),
            });

        let light_camera = Camera::new((30.0, 28.0, 0.0), Deg(-180.0), Deg(-35.0));
        let light_projection =
            Projection::new(config.width, config.height, cgmath::Deg(45.0), 0.1, 100.0);
        let mut light_camera_uniform = CameraUniform::new();
        light_camera_uniform.update_view_proj(&light_camera, &light_projection);
        let mut light = LightUniform::new();
        light.update_view_proj(&light_camera, &light_projection);

        let light_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
            label: Some("Light Buffer"),
            contents: bytemuck::cast_slice(&[light]),
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
        });

        #[cfg(not(target_arch = "wasm32"))]
        let bias_uniform = BiasUniform { bias: [0.00001; 4] };
        #[cfg(target_arch = "wasm32")]
        let bias_uniform = BiasUniform { bias: [0.0001; 4] };

        let bias_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
            label: Some("Bias Buffer"),
            contents: bytemuck::cast_slice(&[bias_uniform]),
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
        });

        let camera_light_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            layout: &camera_light_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: camera_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: light_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 2,
                    resource: bias_buffer.as_entire_binding(),
                },
            ],
            label: Some("camera_light_bind_group"),
        });

        let transform_bind_group_layout =
            device.create_bind_group_layout(&BindGroupLayoutDescriptor {
                label: Some("Offset BindGroup Layout"),
                entries: &[BindGroupLayoutEntry {
                    binding: 0,
                    visibility: ShaderStages::VERTEX,
                    count: None,
                    ty: BindingType::Buffer {
                        ty: BufferBindingType::Uniform,
                        has_dynamic_offset: false,
                        min_binding_size: None,
                    },
                }],
            });

        let shadow_texture = device.create_texture(&wgpu::TextureDescriptor {
            label: Some("Shadow Resolved Depth"),
            #[cfg(not(target_arch = "wasm32"))]
            size: wgpu::Extent3d {
                width: 8192,
                height: 8192,
                depth_or_array_layers: 1,
            },
            #[cfg(target_arch = "wasm32")]
            size: wgpu::Extent3d {
                width: 2048,
                height: 2048,
                depth_or_array_layers: 1,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: wgpu::TextureFormat::Depth24Plus,
            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
            view_formats: &[],
        });
        let shadow_view = shadow_texture.create_view(&Default::default());

        let shadow_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
            compare: Some(wgpu::CompareFunction::LessEqual),
            mag_filter: wgpu::FilterMode::Linear,
            min_filter: wgpu::FilterMode::Linear,
            ..Default::default()
        });

        let shadow_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("shadow_bind_group_layout"),
                entries: &[
                    // depth texture
                    wgpu::BindGroupLayoutEntry {
                        binding: 0,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Texture {
                            sample_type: wgpu::TextureSampleType::Depth,
                            view_dimension: wgpu::TextureViewDimension::D2,
                            multisampled: false,
                        },
                        count: None,
                    },
                    // sampler
                    wgpu::BindGroupLayoutEntry {
                        binding: 1,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
                        count: None,
                    },
                ],
            });

        let shadow_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            layout: &shadow_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: wgpu::BindingResource::TextureView(&shadow_view),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: wgpu::BindingResource::Sampler(&shadow_sampler),
                },
            ],
            label: Some("shadow_bind_group"),
        });

        // More Bindgroup stuff here...

        let shader = device.create_shader_module(wgpu::include_wgsl!("shader/shader.wgsl"));

        let depth_format = wgpu::TextureFormat::Depth24Plus;

        let render_pipeline_layout =
            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: Some("Render Pipeline Layout"),
                bind_group_layouts: &[
                    &texture_bind_group_layout,
                    &camera_light_bind_group_layout,
                    &transform_bind_group_layout,
                    &shadow_bind_group_layout,
                ],
                push_constant_ranges: &[],
            });

        let msaa_texture = device
            .create_texture(&wgpu::TextureDescriptor {
                label: Some("MSAA Texture"),
                size: wgpu::Extent3d {
                    width: config.width,
                    height: config.height,
                    depth_or_array_layers: 1,
                },
                mip_level_count: 1,
                sample_count: msaa_samples,
                dimension: wgpu::TextureDimension::D2,
                format: config.format,
                usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
                view_formats: &[],
            })
            .create_view(&wgpu::TextureViewDescriptor::default());

        let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: Some("Render Pipeline"),
            layout: Some(&render_pipeline_layout),
            vertex: wgpu::VertexState {
                module: &shader,
                entry_point: Some("vs_main"),
                buffers: &[Vertex::desc()],
                compilation_options: wgpu::PipelineCompilationOptions::default(),
            },
            fragment: Some(wgpu::FragmentState {
                module: &shader,
                entry_point: Some("fs_main"),
                targets: &[Some(wgpu::ColorTargetState {
                    format: config.format,
                    blend: Some(wgpu::BlendState::REPLACE),
                    write_mask: wgpu::ColorWrites::ALL,
                })],
                compilation_options: wgpu::PipelineCompilationOptions::default(),
            }),
            primitive: wgpu::PrimitiveState {
                topology: wgpu::PrimitiveTopology::TriangleList,
                strip_index_format: None,
                front_face: wgpu::FrontFace::Ccw,
                cull_mode: Some(wgpu::Face::Back),
                polygon_mode: wgpu::PolygonMode::Fill,
                unclipped_depth: false,
                conservative: false,
            },
            depth_stencil: Some(wgpu::DepthStencilState {
                format: depth_format,
                depth_write_enabled: true,
                depth_compare: wgpu::CompareFunction::Less,
                stencil: wgpu::StencilState::default(),
                bias: wgpu::DepthBiasState::default(),
            }),
            multisample: wgpu::MultisampleState {
                count: msaa_samples,
                mask: !0,
                alpha_to_coverage_enabled: false,
            },
            multiview: None,
            cache: None,
        });

        let light_render_pipeline = {
            let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: Some("Light Pipeline Layout"),
                bind_group_layouts: &[
                    &camera_light_bind_group_layout,
                    &transform_bind_group_layout,
                ],
                push_constant_ranges: &[],
            });
            let shader = device.create_shader_module(wgpu::include_wgsl!("shader/light.wgsl"));
            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
                label: Some("Render Pipeline"),
                layout: Some(&layout),
                vertex: wgpu::VertexState {
                    module: &shader,
                    entry_point: Some("vs_main"),
                    buffers: &[Vertex::desc()],
                    compilation_options: wgpu::PipelineCompilationOptions::default(),
                },
                fragment: None,
                primitive: wgpu::PrimitiveState {
                    topology: wgpu::PrimitiveTopology::TriangleList,
                    strip_index_format: None,
                    front_face: wgpu::FrontFace::Ccw,
                    cull_mode: Some(wgpu::Face::Back),
                    polygon_mode: wgpu::PolygonMode::Fill,
                    unclipped_depth: false,
                    conservative: false,
                },
                depth_stencil: Some(wgpu::DepthStencilState {
                    format: depth_format,
                    depth_write_enabled: true,
                    depth_compare: wgpu::CompareFunction::Less,
                    stencil: wgpu::StencilState::default(),
                    bias: wgpu::DepthBiasState::default(),
                }),
                multisample: wgpu::MultisampleState {
                    count: 1,
                    mask: !0,
                    alpha_to_coverage_enabled: false,
                },
                multiview: None,
                cache: None,
            })
        };

        let egui_ctx = egui::Context::default();
        let egui_state = egui_winit::State::new(
            egui_ctx.clone(),
            egui::ViewportId::ROOT,
            &window,
            None,
            None,
            None,
        );
        let egui_renderer = egui_wgpu::Renderer::new(
            &device,
            #[cfg(target_arch = "wasm32")]
            wgpu::TextureFormat::Rgba8UnormSrgb,
            #[cfg(not(target_arch = "wasm32"))]
            wgpu::TextureFormat::Bgra8UnormSrgb,
            egui_wgpu::RendererOptions::PREDICTABLE,
        );

        Ok(Self {
            surface,
            config,
            is_surface_configured: false,
            device,
            queue,
            window,
            msaa_samples,
            msaa_texture,
            render_pipeline,

            objects: Vec::new(),
            mesh_cache: HashMap::new(),
            depth_format,

            projection,

            camera_light_bind_group,
            camera_buffer,
            camera_uniform,

            light,
            light_buffer,
            shadow_bind_group,
            shadow_view,
            light_projection,
            light_camera,
            light_camera_uniform,
            light_render_pipeline,

            egui_ctx,
            egui_state,
            egui_renderer,
            ui_model: None,

            show_light_view: false,

            texture_bind_group_layout,
            transform_bind_group_layout,

            background_color: background_color.unwrap_or_default(),
        })
    }

    pub fn ui_model_to_ui(ui: &mut Ui, element: &ButteryUIElement<G>, game: &mut G) {
        match element {
            ButteryUIElement::Default => {
                ui.label("".to_string());
            }
            ButteryUIElement::Text(text) => {
                let label = egui::Label::new(format!("{}", text.text));
                if let Some(size) = &text.size {
                    ui.add_sized(vec2(size.x, size.y), label);
                } else {
                    ui.add(label);
                }
            }
            ButteryUIElement::Column(column) => {
                if column.centered {
                    ui.vertical_centered(|mut ui| {
                        for child in &column.children {
                            Self::ui_model_to_ui(&mut ui, child, game);
                        }
                    });
                } else {
                    ui.vertical(|mut ui| {
                        for child in &column.children {
                            Self::ui_model_to_ui(&mut ui, child, game);
                        }
                    });
                }
            }
            ButteryUIElement::Row(row) => {
                if row.centered {
                    ui.horizontal_centered(|mut ui| {
                        for child in &row.children {
                            Self::ui_model_to_ui(&mut ui, child, game);
                        }
                    });
                } else {
                    ui.horizontal(|mut ui| {
                        for child in &row.children {
                            Self::ui_model_to_ui(&mut ui, child, game);
                        }
                    });
                }
            }
            ButteryUIElement::Button(btn) => {
                let widget = egui::Button::new(btn.label.clone())
                    .min_size(egui::Vec2 {
                        x: btn.width,
                        y: btn.height,
                    })
                    .corner_radius(btn.corner_radius);

                let response = ui.add(widget);
                if response.clicked() {
                    (btn.on_click)(game);
                }
            }
            ButteryUIElement::Input(input) => {
                let mut output = input.current_value.clone();

                let color = if let Some(color) = &input.background_color {
                    Color32::from_rgba_unmultiplied(color.r, color.g, color.b, color.a)
                } else {
                    Color32::TRANSPARENT
                };

                let mut text_edit = egui::TextEdit::singleline(&mut output)
                    .background_color(color)
                    .frame(false);
                if let Some(size) = &input.size {
                    text_edit = text_edit.min_size(vec2(size.x, size.y));
                }

                if ui.add(text_edit).changed() {
                    (input.on_changed)(output, game);
                }
            }
            ButteryUIElement::Container(container) => {
                let stroke = if let Some(outline) = &container.outline {
                    Stroke::new(
                        outline.width,
                        Color32::from_rgba_unmultiplied(
                            outline.color.r,
                            outline.color.g,
                            outline.color.b,
                            outline.color.a,
                        ),
                    )
                } else {
                    Stroke::new(0.0, Color32::TRANSPARENT)
                };

                let frame = Frame::new()
                    .corner_radius(container.corner_radius)
                    .fill(Color32::from_rgb(
                        container.color.r,
                        container.color.g,
                        container.color.b,
                        // container.color.a,
                    ))
                    .stroke(stroke);

                if let Some(size) = &container.size {
                    ui.scope(|ui| {
                        frame
                            .inner_margin(vec2(size.x / 2.0, size.y / 2.0))
                            .show(ui, |mut ui| {
                                for child in &container.children {
                                    Self::ui_model_to_ui(&mut ui, child, game);
                                }
                            });
                    });
                } else {
                    frame.show(ui, |mut ui| {
                        for child in &container.children {
                            Self::ui_model_to_ui(&mut ui, child, game);
                        }
                    });
                }
            }
        }
    }
}

impl<G: ButteryGame> ButteryRenderer<G> for SlipperyRenderer<G> {
    fn as_any(&self) -> &dyn Any {
        self
    }

    fn as_any_mut(&mut self) -> &mut dyn Any {
        self
    }

    fn set_background_color(&mut self, color: ButteryColor) {
        self.background_color = color;
    }

    fn load_model(&mut self, path: &str) {
        #[cfg(not(target_arch = "wasm32"))]
        {
            let Ok(mut file) = std::fs::File::open(format!("./src/{path}")) else {
                let state = Rc::new(RefCell::new(MeshStateHandle {
                    state: MeshState::Error("File does not exist".into()),
                }));
                self.mesh_cache.insert(path.to_string(), state);
                return;
            };

            let mut buffer = Vec::new();
            if let Err(_) = file.read_to_end(&mut buffer) {
                let state = Rc::new(RefCell::new(MeshStateHandle {
                    state: MeshState::Error("Failed to read file".into()),
                }));
                self.mesh_cache.insert(path.to_string(), state);
                return;
            }

            let glb_meshes = match parse_glb(
                buffer,
                &self.device,
                &self.queue,
                &self.texture_bind_group_layout,
            ) {
                Ok(res) => res,
                Err(e) => {
                    let state = Rc::new(RefCell::new(MeshStateHandle {
                        state: MeshState::Error(e.to_string()),
                    }));
                    self.mesh_cache.insert(path.to_string(), state);
                    return;
                }
            };

            let state = Rc::new(RefCell::new(MeshStateHandle {
                state: MeshState::Ready(
                    glb_meshes.into_iter().map(|mesh| Arc::new(mesh)).collect(),
                ),
            }));
            self.mesh_cache.insert(path.to_string(), state);
        }

        #[cfg(target_arch = "wasm32")]
        {
            let entry = Rc::new(RefCell::new(MeshStateHandle {
                state: MeshState::Loading,
            }));
            self.mesh_cache.insert(path.into(), entry.clone());

            let device = self.device.clone();
            let queue = self.queue.clone();
            let texture = self.texture_bind_group_layout.clone();
            let path = path.to_string();

            spawn_local(async move {
                let result = fetch_model_data(&path, &device, &queue, &texture).await;

                let new_state = match result {
                    Ok(meshes) => {
                        MeshState::Ready(meshes.into_iter().map(|mesh| Arc::new(mesh)).collect())
                    }
                    Err(e) => MeshState::Error(e),
                };

                entry.borrow_mut().state = new_state;
            });
        }
    }

    fn unload_model(&mut self, path: &str) {
        self.mesh_cache.remove(path);
    }

    fn on_update(&mut self, world_model: &ButteryWorldModel) {
        // Update Camera
        {
            self.camera_uniform
                .update_view_proj(&world_model.camera, &self.projection);

            let camera_uniform = if self.show_light_view {
                self.light_camera_uniform
            } else {
                self.camera_uniform
            };

            self.queue.write_buffer(
                &self.camera_buffer,
                0,
                bytemuck::cast_slice(&[camera_uniform]),
            );
        }

        // Update Light
        {
            self.light_camera_uniform
                .update_view_proj(&world_model.light, &self.projection);

            self.queue.write_buffer(
                &self.light_buffer,
                0,
                bytemuck::cast_slice(&[self.light_camera_uniform]),
            );
        }

        // Update Meshes/Offsets
        {
            self.objects.clear();

            for (_, object) in &world_model.objects {
                let mesh_state =
                    if let Some(mesh_state) = self.mesh_cache.get(&object.model_path) {
                        mesh_state
                    } else {
                        self.load_model(&object.model_path);
                        let Some(mesh_state) = self.mesh_cache.get(&object.model_path) else {
                            panic!("Missing MeshState {}", object.model_path);
                        };
                        mesh_state
                    }
                    .borrow();

                let meshes = match &*&mesh_state.state {
                    MeshState::Error(e) => {
                        #[cfg(not(target_arch = "wasm32"))]
                        println!("Failed to load mesh: {e}");
                        #[cfg(target_arch = "wasm32")]
                        web_sys::console::log_1(&format!("Failed to load mesh {e}").into());
                        continue;
                    }
                    MeshState::Loading => {
                        println!("Mesh loading...");
                        continue;
                    }
                    MeshState::Ready(meshes) => meshes,
                };

                for mesh in meshes {
                    // Offset
                    let offset = [
                        object.data.position[0],
                        object.data.position[1],
                        object.data.position[2],
                        0.0,
                    ];

                    let rotation = Matrix4::from_angle_y(object.data.rotation[1])
                        * Matrix4::from_angle_x(object.data.rotation[0])
                        * Matrix4::from_angle_z(object.data.rotation[2]);

                    let transform = ModelTransform {
                        offset,
                        rotation: rotation.into(),
                    };

                    let transform_buffer =
                        self.device
                            .create_buffer_init(&wgpu::util::BufferInitDescriptor {
                                label: Some("Bias Buffer"),
                                contents: bytemuck::cast_slice(&[transform]),
                                usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
                            });

                    let transform_bind_group =
                        self.device.create_bind_group(&wgpu::BindGroupDescriptor {
                            label: Some("Transform Bind Group"),
                            layout: &self.transform_bind_group_layout,
                            entries: &[wgpu::BindGroupEntry {
                                binding: 0,
                                resource: transform_buffer.as_entire_binding(),
                            }],
                        });

                    self.objects.push(RenderableObject {
                        mesh: mesh.clone(),
                        transform,
                        transform_buffer,
                        transform_bind_group,
                    });
                }
            }
        }
    }

    fn render(&mut self, game: &mut G) {
        let depth_texture = self.device.create_texture(&wgpu::TextureDescriptor {
            label: Some("Depth Texture"),
            size: wgpu::Extent3d {
                width: self.config.width,
                height: self.config.height,
                depth_or_array_layers: 1,
            },
            mip_level_count: 1,
            sample_count: self.msaa_samples,
            dimension: wgpu::TextureDimension::D2,
            format: self.depth_format,
            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
            view_formats: &[],
        });
        let depth_texture_view = depth_texture.create_view(&wgpu::TextureViewDescriptor::default());

        self.window.request_redraw();

        // We can't render unless the surface is configured
        if !self.is_surface_configured {
            return;
        }

        let output = self.surface.get_current_texture().unwrap();

        let view = output
            .texture
            .create_view(&wgpu::TextureViewDescriptor::default());

        let mut encoder = self
            .device
            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
                label: Some("Render Encoder"),
            });

        // Shadow Render Pass
        {
            let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
                label: Some("Shadow Pass"),
                color_attachments: &[],
                depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
                    view: &self.shadow_view,
                    depth_ops: Some(wgpu::Operations {
                        load: wgpu::LoadOp::Clear(1.0),
                        store: wgpu::StoreOp::Store,
                    }),
                    stencil_ops: None,
                }),
                occlusion_query_set: None,
                timestamp_writes: None,
            });

            pass.set_pipeline(&self.light_render_pipeline);

            for object in &self.objects {
                self.queue
                    .write_buffer(&object.transform_buffer, 0, bytes_of(&object.transform));

                pass.set_bind_group(0, &self.camera_light_bind_group, &[]);
                pass.set_bind_group(1, &object.transform_bind_group, &[]);
                pass.set_vertex_buffer(0, object.mesh.vertex_buffer.slice(..));
                pass.set_index_buffer(object.mesh.index_buffer.slice(..), object.mesh.index_format);
                pass.draw_indexed(0..object.mesh.num_indices, 0, 0..1);
            }
        }

        // Main Render Pass
        {
            let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
                label: Some("Render Pass"),
                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                    view: &self.msaa_texture,
                    resolve_target: Some(&view),
                    ops: wgpu::Operations {
                        load: wgpu::LoadOp::Clear(wgpu::Color {
                            r: (self.background_color.r as f64 / 255.0),
                            g: (self.background_color.g as f64 / 255.0),
                            b: (self.background_color.b as f64 / 255.0),
                            a: (self.background_color.a as f64 / 255.0),
                        }),
                        store: wgpu::StoreOp::Store,
                    },
                    depth_slice: None,
                })],
                depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
                    view: &depth_texture_view,
                    depth_ops: Some(wgpu::Operations {
                        load: wgpu::LoadOp::Clear(1.0),
                        store: wgpu::StoreOp::Store,
                    }),
                    stencil_ops: None,
                }),
                occlusion_query_set: None,
                timestamp_writes: None,
            });

            render_pass.set_pipeline(&self.render_pipeline);

            for object in &self.objects {
                self.queue
                    .write_buffer(&object.transform_buffer, 0, bytes_of(&object.transform));

                render_pass.set_bind_group(0, &object.mesh.texture_bind_group, &[]);
                render_pass.set_bind_group(1, &self.camera_light_bind_group, &[]);
                render_pass.set_bind_group(2, &object.transform_bind_group, &[]);
                render_pass.set_bind_group(3, &self.shadow_bind_group, &[]);
                render_pass.set_vertex_buffer(0, object.mesh.vertex_buffer.slice(..));
                render_pass
                    .set_index_buffer(object.mesh.index_buffer.slice(..), object.mesh.index_format);
                render_pass.draw_indexed(0..object.mesh.num_indices, 0, 0..1);
            }
        }

        // Start UI-Setup
        let raw_input = self.egui_state.take_egui_input(&self.window);

        let mut style = (*self.egui_ctx.style()).clone();
        // Example: rounder corners and more spacing
        style.visuals.window_corner_radius = 20.0.into();
        style.visuals.window_fill = egui::Color32::from_rgb(0, 0, 255);
        style.visuals.collapsing_header_frame = false;
        self.egui_ctx.set_style(style);

        self.egui_ctx.begin_pass(raw_input);

        if let Some(ui_model) = &self.ui_model {
            for window in &ui_model.windows {
                egui::Area::new("panel".into())
                    .id(Id::new(&window.id))
                    .anchor(
                        buttery_ui_window_relative_position_to_align_2(
                            window.relative_position.clone(),
                        ),
                        egui::vec2(window.offset.x, window.offset.y),
                    )
                    .show(&self.egui_ctx, |ui| {
                        // Constrain the whole area
                        ui.set_max_width(window.max_width);
                        ui.set_max_height(window.max_height);
                        egui::Frame::NONE
                            .fill(egui::Color32::from_rgba_unmultiplied(
                                window.background_color.r,
                                window.background_color.g,
                                window.background_color.b,
                                window.background_color.a,
                            ))
                            .corner_radius(window.corner_radius)
                            .inner_margin(egui::Margin::same(window.padding))
                            .show(ui, |mut ui| {
                                Self::ui_model_to_ui(&mut ui, &window.child, game);
                            });
                    });
            }
        }

        // End egui frame
        let full_output = self.egui_ctx.end_pass();
        let paint_jobs = self
            .egui_ctx
            .tessellate(full_output.shapes, full_output.pixels_per_point);

        // Apply window commands (cursor, IME, etc.)
        let platform_output = full_output.platform_output;
        self.egui_state
            .handle_platform_output(&self.window, platform_output);

        // Upload textures
        for (id, image_delta) in &full_output.textures_delta.set {
            self.egui_renderer
                .update_texture(&self.device, &self.queue, *id, image_delta);
        }
        // End UI-Setup

        // Render UI over 3D-Meshes
        let screen_desc = egui_wgpu::ScreenDescriptor {
            size_in_pixels: [self.config.width, self.config.height],
            pixels_per_point: self.window.scale_factor() as f32,
        };
        self.egui_renderer.update_buffers(
            &self.device,
            &self.queue,
            &mut encoder,
            &paint_jobs,
            &screen_desc,
        );

        let ui_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
            label: Some("egui UI pass"),
            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                view: &view,
                resolve_target: None,
                ops: wgpu::Operations {
                    load: wgpu::LoadOp::Load,
                    store: wgpu::StoreOp::Store,
                },
                depth_slice: None,
            })],
            depth_stencil_attachment: None,
            occlusion_query_set: None,
            timestamp_writes: None,
        });
        let mut ui_pass = ui_pass.forget_lifetime();
        self.egui_renderer
            .render(&mut ui_pass, &paint_jobs, &screen_desc);
        drop(ui_pass);

        // Free textures that egui wants to remove
        for id in &full_output.textures_delta.free {
            self.egui_renderer.free_texture(id);
        }

        // submit will accept anything that implements IntoIter
        self.queue.submit(std::iter::once(encoder.finish()));
        output.present();
    }

    fn resize(&mut self, width: u32, height: u32) {
        if width > 0 && height > 0 {
            #[cfg(target_arch = "wasm32")]
            let (width, height) = {
                let width = if width > 2048 { 2048 } else { width };
                let height = if height > 2048 { 2048 } else { height };
                (width, height)
            };
            self.projection.resize(width, height);

            self.light_projection.resize(width, height);
            self.light
                .update_view_proj(&self.light_camera, &self.light_projection);
            self.light_camera_uniform
                .update_view_proj(&self.light_camera, &self.light_projection);
            self.queue
                .write_buffer(&self.light_buffer, 0, bytemuck::cast_slice(&[self.light]));

            self.config.width = width;
            self.config.height = height;
            self.surface.configure(&self.device, &self.config);
            self.msaa_texture = self
                .device
                .create_texture(&wgpu::TextureDescriptor {
                    label: Some("MSAA Texture"),
                    size: wgpu::Extent3d {
                        width,
                        height,
                        depth_or_array_layers: 1,
                    },
                    mip_level_count: 1,
                    sample_count: self.msaa_samples,
                    dimension: wgpu::TextureDimension::D2,
                    format: self.config.format,
                    usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
                    view_formats: &[],
                })
                .create_view(&wgpu::TextureViewDescriptor::default());
            self.is_surface_configured = true;
        }
    }

    fn update_ui_model(&mut self, ui_model: Option<ButteryUIModel<G>>) {
        self.ui_model = ui_model;
    }
}

fn buttery_ui_window_relative_position_to_align_2(
    value: ButteryUIWindowRelativePosition,
) -> Align2 {
    match value {
        ButteryUIWindowRelativePosition::TopLeft => Align2::LEFT_TOP,
        ButteryUIWindowRelativePosition::TopCenter => Align2::CENTER_TOP,
        ButteryUIWindowRelativePosition::TopRight => Align2::RIGHT_TOP,
        ButteryUIWindowRelativePosition::CenterLeft => Align2::LEFT_CENTER,
        ButteryUIWindowRelativePosition::Centered => Align2::CENTER_CENTER,
        ButteryUIWindowRelativePosition::CenterRight => Align2::RIGHT_CENTER,
        ButteryUIWindowRelativePosition::BottomLeft => Align2::LEFT_BOTTOM,
        ButteryUIWindowRelativePosition::BottomCenter => Align2::CENTER_BOTTOM,
        ButteryUIWindowRelativePosition::BottomRight => Align2::RIGHT_BOTTOM,
    }
}
