use crate::render::wgpu::rendergraph::{PassExecutionContext, PassNode};
use std::sync::{Arc, RwLock};

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct InteriorMappingUniforms {
    pub view_proj: [[f32; 4]; 4],
    pub wall_freq_and_threshold: [f32; 4],
    pub disp_and_alpha: [f32; 4],
    pub uv_and_pad: [f32; 4],
    pub sun_direction_and_intensity: [f32; 4],
    pub sun_color: [f32; 4],
    pub ambient_color: [f32; 4],
}

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct GpuInstanceData {
    pub model: [[f32; 4]; 4],
    pub camera_pos_object: [f32; 4],
}

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct InteriorMappingVertex {
    pub position: [f32; 3],
    pub normal: [f32; 3],
    pub texcoord: [f32; 2],
}

impl InteriorMappingVertex {
    const ATTRIBS: [wgpu::VertexAttribute; 3] = wgpu::vertex_attr_array![
        0 => Float32x3,
        1 => Float32x3,
        2 => Float32x2,
    ];

    pub fn layout() -> wgpu::VertexBufferLayout<'static> {
        wgpu::VertexBufferLayout {
            array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
            step_mode: wgpu::VertexStepMode::Vertex,
            attributes: &Self::ATTRIBS,
        }
    }
}

pub struct InteriorMappingMeshData {
    pub vertices: Vec<InteriorMappingVertex>,
    pub indices: Vec<u32>,
}

pub struct InteriorMappingInstance {
    pub model_matrix: crate::ecs::world::Mat4,
}

pub struct InteriorMappingState {
    pub wall_frequencies: [f32; 3],
    pub light_threshold: f32,
    pub alpha_plane_distance: f32,
    pub displacement_strengths: [f32; 3],
    pub uv_multiplier: f32,
    pub instances: Vec<InteriorMappingInstance>,
    pub mesh_data: Option<InteriorMappingMeshData>,
    pub mesh_dirty: bool,
    pub enabled: bool,
    pub sun_direction: [f32; 3],
    pub sun_intensity: f32,
    pub sun_color: [f32; 3],
    pub ambient_color: [f32; 4],
}

impl Default for InteriorMappingState {
    fn default() -> Self {
        Self {
            wall_frequencies: [2.99, 7.99, 2.99],
            light_threshold: 0.6,
            alpha_plane_distance: 0.1,
            displacement_strengths: [1.0, 0.0, 1.0],
            uv_multiplier: 2.0,
            instances: Vec::new(),
            mesh_data: None,
            mesh_dirty: true,
            enabled: true,
            sun_direction: [0.5, 0.33166, 0.8],
            sun_intensity: 1.0,
            sun_color: [1.0, 1.0, 0.9],
            ambient_color: [0.3, 0.3, 0.4, 1.0],
        }
    }
}

pub type InteriorMappingStateHandle = Arc<RwLock<InteriorMappingState>>;

pub fn create_interior_mapping_state() -> InteriorMappingStateHandle {
    Arc::new(RwLock::new(InteriorMappingState::default()))
}

#[derive(Clone)]
pub struct InteriorMappingTextures {
    pub ceiling: Vec<u8>,
    pub ceiling_width: u32,
    pub ceiling_height: u32,
    pub floor: Vec<u8>,
    pub floor_width: u32,
    pub floor_height: u32,
    pub wall_xy: Vec<u8>,
    pub wall_xy_width: u32,
    pub wall_xy_height: u32,
    pub wall_zy: Vec<u8>,
    pub wall_zy_width: u32,
    pub wall_zy_height: u32,
    pub noise: Vec<u8>,
    pub noise_width: u32,
    pub noise_height: u32,
    pub furniture: Vec<u8>,
    pub furniture_width: u32,
    pub furniture_height: u32,
    pub exterior: Vec<u8>,
    pub exterior_width: u32,
    pub exterior_height: u32,
    pub cubemap_faces: [Vec<u8>; 6],
    pub cubemap_face_size: u32,
}

pub fn generate_box_mesh(
    half_width: f32,
    half_height: f32,
    half_depth: f32,
) -> InteriorMappingMeshData {
    let hw = half_width;
    let hh = half_height;
    let hd = half_depth;

    let positions: [[f32; 3]; 24] = [
        [-hw, -hh, hd],
        [hw, -hh, hd],
        [hw, hh, hd],
        [-hw, hh, hd],
        [hw, -hh, -hd],
        [-hw, -hh, -hd],
        [-hw, hh, -hd],
        [hw, hh, -hd],
        [-hw, -hh, -hd],
        [-hw, -hh, hd],
        [-hw, hh, hd],
        [-hw, hh, -hd],
        [hw, -hh, hd],
        [hw, -hh, -hd],
        [hw, hh, -hd],
        [hw, hh, hd],
        [-hw, hh, hd],
        [hw, hh, hd],
        [hw, hh, -hd],
        [-hw, hh, -hd],
        [-hw, -hh, -hd],
        [hw, -hh, -hd],
        [hw, -hh, hd],
        [-hw, -hh, hd],
    ];

    let normals: [[f32; 3]; 6] = [
        [0.0, 0.0, 1.0],
        [0.0, 0.0, -1.0],
        [-1.0, 0.0, 0.0],
        [1.0, 0.0, 0.0],
        [0.0, 1.0, 0.0],
        [0.0, -1.0, 0.0],
    ];

    let uvs: [[f32; 2]; 4] = [[0.0, 1.0], [1.0, 1.0], [1.0, 0.0], [0.0, 0.0]];

    let mut vertices = Vec::with_capacity(24);
    for (face, normal) in normals.iter().enumerate() {
        for (vertex, uv) in uvs.iter().enumerate() {
            let index = face * 4 + vertex;
            vertices.push(InteriorMappingVertex {
                position: positions[index],
                normal: *normal,
                texcoord: *uv,
            });
        }
    }

    let mut indices = Vec::with_capacity(36);
    for face in 0..6u32 {
        let base = face * 4;
        indices.extend_from_slice(&[base, base + 1, base + 2, base, base + 2, base + 3]);
    }

    InteriorMappingMeshData { vertices, indices }
}

pub fn generate_cube_mesh() -> InteriorMappingMeshData {
    generate_box_mesh(0.5, 0.5, 0.5)
}

pub fn generate_cylinder_mesh(segments: u32, rings: u32) -> InteriorMappingMeshData {
    let mut vertices = Vec::new();
    let mut indices = Vec::new();

    for ring in 0..=rings {
        let v = ring as f32 / rings as f32;
        let y = v - 0.5;
        for segment in 0..=segments {
            let u = segment as f32 / segments as f32;
            let angle = u * std::f32::consts::TAU;
            let x = angle.cos() * 0.5;
            let z = angle.sin() * 0.5;
            let normal = [angle.cos(), 0.0, angle.sin()];
            vertices.push(InteriorMappingVertex {
                position: [x, y, z],
                normal,
                texcoord: [u, 1.0 - v],
            });
        }
    }

    for ring in 0..rings {
        for segment in 0..segments {
            let current = ring * (segments + 1) + segment;
            let next = current + segments + 1;
            indices.extend_from_slice(&[current, next, current + 1, current + 1, next, next + 1]);
        }
    }

    InteriorMappingMeshData { vertices, indices }
}

pub fn generate_sphere_mesh(slices: u32, stacks: u32) -> InteriorMappingMeshData {
    let mut vertices = Vec::new();
    let mut indices = Vec::new();

    for stack in 0..=stacks {
        let v = stack as f32 / stacks as f32;
        let phi = v * std::f32::consts::PI;
        for slice in 0..=slices {
            let u = slice as f32 / slices as f32;
            let theta = u * std::f32::consts::TAU;
            let x = phi.sin() * theta.cos();
            let y = phi.cos();
            let z = phi.sin() * theta.sin();
            vertices.push(InteriorMappingVertex {
                position: [x * 0.5, y * 0.5, z * 0.5],
                normal: [x, y, z],
                texcoord: [u, 1.0 - v],
            });
        }
    }

    for stack in 0..stacks {
        for slice in 0..slices {
            let current = stack * (slices + 1) + slice;
            let next = current + slices + 1;
            indices.extend_from_slice(&[current, next, current + 1, current + 1, next, next + 1]);
        }
    }

    InteriorMappingMeshData { vertices, indices }
}

fn upload_texture_2d(
    device: &wgpu::Device,
    queue: &wgpu::Queue,
    data: &[u8],
    width: u32,
    height: u32,
    label: &str,
) -> (wgpu::Texture, wgpu::TextureView) {
    let texture = device.create_texture(&wgpu::TextureDescriptor {
        label: Some(label),
        size: wgpu::Extent3d {
            width,
            height,
            depth_or_array_layers: 1,
        },
        mip_level_count: 1,
        sample_count: 1,
        dimension: wgpu::TextureDimension::D2,
        format: wgpu::TextureFormat::Rgba8UnormSrgb,
        usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
        view_formats: &[],
    });
    queue.write_texture(
        wgpu::TexelCopyTextureInfo {
            texture: &texture,
            mip_level: 0,
            origin: wgpu::Origin3d::ZERO,
            aspect: wgpu::TextureAspect::All,
        },
        data,
        wgpu::TexelCopyBufferLayout {
            offset: 0,
            bytes_per_row: Some(4 * width),
            rows_per_image: Some(height),
        },
        wgpu::Extent3d {
            width,
            height,
            depth_or_array_layers: 1,
        },
    );
    let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
    (texture, view)
}

fn upload_cubemap(
    device: &wgpu::Device,
    queue: &wgpu::Queue,
    faces: &[Vec<u8>; 6],
    face_size: u32,
    label: &str,
) -> (wgpu::Texture, wgpu::TextureView) {
    let texture = device.create_texture(&wgpu::TextureDescriptor {
        label: Some(label),
        size: wgpu::Extent3d {
            width: face_size,
            height: face_size,
            depth_or_array_layers: 6,
        },
        mip_level_count: 1,
        sample_count: 1,
        dimension: wgpu::TextureDimension::D2,
        format: wgpu::TextureFormat::Rgba8UnormSrgb,
        usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
        view_formats: &[],
    });
    for (face_index, face_data) in faces.iter().enumerate() {
        queue.write_texture(
            wgpu::TexelCopyTextureInfo {
                texture: &texture,
                mip_level: 0,
                origin: wgpu::Origin3d {
                    x: 0,
                    y: 0,
                    z: face_index as u32,
                },
                aspect: wgpu::TextureAspect::All,
            },
            face_data,
            wgpu::TexelCopyBufferLayout {
                offset: 0,
                bytes_per_row: Some(4 * face_size),
                rows_per_image: Some(face_size),
            },
            wgpu::Extent3d {
                width: face_size,
                height: face_size,
                depth_or_array_layers: 1,
            },
        );
    }
    let view = texture.create_view(&wgpu::TextureViewDescriptor {
        dimension: Some(wgpu::TextureViewDimension::Cube),
        ..Default::default()
    });
    (texture, view)
}

fn tex_bgl_entry(
    binding: u32,
    dimension: wgpu::TextureViewDimension,
) -> wgpu::BindGroupLayoutEntry {
    wgpu::BindGroupLayoutEntry {
        binding,
        visibility: wgpu::ShaderStages::FRAGMENT,
        ty: wgpu::BindingType::Texture {
            sample_type: wgpu::TextureSampleType::Float { filterable: true },
            view_dimension: dimension,
            multisampled: false,
        },
        count: None,
    }
}

fn sampler_bgl_entry(binding: u32) -> wgpu::BindGroupLayoutEntry {
    wgpu::BindGroupLayoutEntry {
        binding,
        visibility: wgpu::ShaderStages::FRAGMENT,
        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
        count: None,
    }
}

struct GpuResources {
    pipeline: wgpu::RenderPipeline,
    uniform_buffer: wgpu::Buffer,
    uniform_bind_group: wgpu::BindGroup,
    texture_bind_group: wgpu::BindGroup,
    instance_buffer: wgpu::Buffer,
    instance_bind_group_layout: wgpu::BindGroupLayout,
    instance_bind_group: wgpu::BindGroup,
    instance_capacity: usize,
    vertex_buffer: Option<wgpu::Buffer>,
    index_buffer: Option<wgpu::Buffer>,
    index_count: u32,
    _textures: Vec<wgpu::Texture>,
}

pub struct InteriorMappingPass {
    state: InteriorMappingStateHandle,
    color_format: wgpu::TextureFormat,
    depth_format: wgpu::TextureFormat,
    pending_textures: Option<InteriorMappingTextures>,
    gpu: Option<GpuResources>,
}

impl InteriorMappingPass {
    pub fn new(
        color_format: wgpu::TextureFormat,
        depth_format: wgpu::TextureFormat,
        textures: InteriorMappingTextures,
        state: InteriorMappingStateHandle,
    ) -> Self {
        Self {
            state,
            color_format,
            depth_format,
            pending_textures: Some(textures),
            gpu: None,
        }
    }

    fn initialize_gpu(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) {
        let textures = self.pending_textures.take().unwrap();

        let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Interior Mapping Uniform Buffer"),
            size: std::mem::size_of::<InteriorMappingUniforms>() as u64,
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let uniform_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Interior Mapping Uniform BGL"),
                entries: &[wgpu::BindGroupLayoutEntry {
                    binding: 0,
                    visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
                    ty: wgpu::BindingType::Buffer {
                        ty: wgpu::BufferBindingType::Uniform,
                        has_dynamic_offset: false,
                        min_binding_size: None,
                    },
                    count: None,
                }],
            });

        let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("Interior Mapping Uniform BG"),
            layout: &uniform_bind_group_layout,
            entries: &[wgpu::BindGroupEntry {
                binding: 0,
                resource: uniform_buffer.as_entire_binding(),
            }],
        });

        let (ceiling_tex, ceiling_view) = upload_texture_2d(
            device,
            queue,
            &textures.ceiling,
            textures.ceiling_width,
            textures.ceiling_height,
            "IM Ceiling",
        );
        let (floor_tex, floor_view) = upload_texture_2d(
            device,
            queue,
            &textures.floor,
            textures.floor_width,
            textures.floor_height,
            "IM Floor",
        );
        let (wall_xy_tex, wall_xy_view) = upload_texture_2d(
            device,
            queue,
            &textures.wall_xy,
            textures.wall_xy_width,
            textures.wall_xy_height,
            "IM Wall XY",
        );
        let (wall_zy_tex, wall_zy_view) = upload_texture_2d(
            device,
            queue,
            &textures.wall_zy,
            textures.wall_zy_width,
            textures.wall_zy_height,
            "IM Wall ZY",
        );
        let (noise_tex, noise_view) = upload_texture_2d(
            device,
            queue,
            &textures.noise,
            textures.noise_width,
            textures.noise_height,
            "IM Noise",
        );
        let (furniture_tex, furniture_view) = upload_texture_2d(
            device,
            queue,
            &textures.furniture,
            textures.furniture_width,
            textures.furniture_height,
            "IM Furniture",
        );
        let (exterior_tex, exterior_view) = upload_texture_2d(
            device,
            queue,
            &textures.exterior,
            textures.exterior_width,
            textures.exterior_height,
            "IM Exterior",
        );
        let (cubemap_tex, cubemap_view) = upload_cubemap(
            device,
            queue,
            &textures.cubemap_faces,
            textures.cubemap_face_size,
            "IM Cubemap",
        );

        let repeat_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
            label: Some("IM Repeat Sampler"),
            address_mode_u: wgpu::AddressMode::Repeat,
            address_mode_v: wgpu::AddressMode::Repeat,
            address_mode_w: wgpu::AddressMode::Repeat,
            mag_filter: wgpu::FilterMode::Linear,
            min_filter: wgpu::FilterMode::Linear,
            mipmap_filter: wgpu::FilterMode::Linear,
            ..Default::default()
        });

        let cubemap_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
            label: Some("IM Cubemap Sampler"),
            address_mode_u: wgpu::AddressMode::ClampToEdge,
            address_mode_v: wgpu::AddressMode::ClampToEdge,
            address_mode_w: wgpu::AddressMode::ClampToEdge,
            mag_filter: wgpu::FilterMode::Linear,
            min_filter: wgpu::FilterMode::Linear,
            mipmap_filter: wgpu::FilterMode::Linear,
            ..Default::default()
        });

        let texture_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Interior Mapping Texture BGL"),
                entries: &[
                    tex_bgl_entry(0, wgpu::TextureViewDimension::D2),
                    sampler_bgl_entry(1),
                    tex_bgl_entry(2, wgpu::TextureViewDimension::D2),
                    sampler_bgl_entry(3),
                    tex_bgl_entry(4, wgpu::TextureViewDimension::D2),
                    sampler_bgl_entry(5),
                    tex_bgl_entry(6, wgpu::TextureViewDimension::D2),
                    sampler_bgl_entry(7),
                    tex_bgl_entry(8, wgpu::TextureViewDimension::D2),
                    sampler_bgl_entry(9),
                    tex_bgl_entry(10, wgpu::TextureViewDimension::D2),
                    sampler_bgl_entry(11),
                    tex_bgl_entry(12, wgpu::TextureViewDimension::D2),
                    sampler_bgl_entry(13),
                    tex_bgl_entry(14, wgpu::TextureViewDimension::Cube),
                    sampler_bgl_entry(15),
                ],
            });

        let texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("Interior Mapping Texture BG"),
            layout: &texture_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: wgpu::BindingResource::TextureView(&ceiling_view),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: wgpu::BindingResource::Sampler(&repeat_sampler),
                },
                wgpu::BindGroupEntry {
                    binding: 2,
                    resource: wgpu::BindingResource::TextureView(&floor_view),
                },
                wgpu::BindGroupEntry {
                    binding: 3,
                    resource: wgpu::BindingResource::Sampler(&repeat_sampler),
                },
                wgpu::BindGroupEntry {
                    binding: 4,
                    resource: wgpu::BindingResource::TextureView(&wall_xy_view),
                },
                wgpu::BindGroupEntry {
                    binding: 5,
                    resource: wgpu::BindingResource::Sampler(&repeat_sampler),
                },
                wgpu::BindGroupEntry {
                    binding: 6,
                    resource: wgpu::BindingResource::TextureView(&wall_zy_view),
                },
                wgpu::BindGroupEntry {
                    binding: 7,
                    resource: wgpu::BindingResource::Sampler(&repeat_sampler),
                },
                wgpu::BindGroupEntry {
                    binding: 8,
                    resource: wgpu::BindingResource::TextureView(&noise_view),
                },
                wgpu::BindGroupEntry {
                    binding: 9,
                    resource: wgpu::BindingResource::Sampler(&repeat_sampler),
                },
                wgpu::BindGroupEntry {
                    binding: 10,
                    resource: wgpu::BindingResource::TextureView(&furniture_view),
                },
                wgpu::BindGroupEntry {
                    binding: 11,
                    resource: wgpu::BindingResource::Sampler(&repeat_sampler),
                },
                wgpu::BindGroupEntry {
                    binding: 12,
                    resource: wgpu::BindingResource::TextureView(&exterior_view),
                },
                wgpu::BindGroupEntry {
                    binding: 13,
                    resource: wgpu::BindingResource::Sampler(&repeat_sampler),
                },
                wgpu::BindGroupEntry {
                    binding: 14,
                    resource: wgpu::BindingResource::TextureView(&cubemap_view),
                },
                wgpu::BindGroupEntry {
                    binding: 15,
                    resource: wgpu::BindingResource::Sampler(&cubemap_sampler),
                },
            ],
        });

        let initial_instance_capacity = 64;
        let instance_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Interior Mapping Instance Buffer"),
            size: (initial_instance_capacity * std::mem::size_of::<GpuInstanceData>()) as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let instance_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Interior Mapping Instance BGL"),
                entries: &[wgpu::BindGroupLayoutEntry {
                    binding: 0,
                    visibility: wgpu::ShaderStages::VERTEX,
                    ty: wgpu::BindingType::Buffer {
                        ty: wgpu::BufferBindingType::Storage { read_only: true },
                        has_dynamic_offset: false,
                        min_binding_size: None,
                    },
                    count: None,
                }],
            });

        let instance_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("Interior Mapping Instance BG"),
            layout: &instance_bind_group_layout,
            entries: &[wgpu::BindGroupEntry {
                binding: 0,
                resource: instance_buffer.as_entire_binding(),
            }],
        });

        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
            label: Some("Interior Mapping Shader"),
            source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!(
                "../../shaders/interior_mapping.wgsl"
            ))),
        });

        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
            label: Some("Interior Mapping Pipeline Layout"),
            bind_group_layouts: &[
                &uniform_bind_group_layout,
                &texture_bind_group_layout,
                &instance_bind_group_layout,
            ],
            push_constant_ranges: &[],
        });

        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: Some("Interior Mapping Pipeline"),
            layout: Some(&pipeline_layout),
            vertex: wgpu::VertexState {
                module: &shader,
                entry_point: Some("vertex_main"),
                buffers: &[InteriorMappingVertex::layout()],
                compilation_options: Default::default(),
            },
            fragment: Some(wgpu::FragmentState {
                module: &shader,
                entry_point: Some("fragment_main"),
                targets: &[Some(wgpu::ColorTargetState {
                    format: self.color_format,
                    blend: Some(wgpu::BlendState::REPLACE),
                    write_mask: wgpu::ColorWrites::ALL,
                })],
                compilation_options: Default::default(),
            }),
            primitive: wgpu::PrimitiveState {
                topology: wgpu::PrimitiveTopology::TriangleList,
                strip_index_format: None,
                front_face: wgpu::FrontFace::Ccw,
                cull_mode: Some(wgpu::Face::Back),
                unclipped_depth: false,
                polygon_mode: wgpu::PolygonMode::Fill,
                conservative: false,
            },
            depth_stencil: Some(wgpu::DepthStencilState {
                format: self.depth_format,
                depth_write_enabled: true,
                depth_compare: wgpu::CompareFunction::GreaterEqual,
                stencil: wgpu::StencilState::default(),
                bias: wgpu::DepthBiasState::default(),
            }),
            multisample: wgpu::MultisampleState::default(),
            multiview: None,
            cache: None,
        });

        self.gpu = Some(GpuResources {
            pipeline,
            uniform_buffer,
            uniform_bind_group,
            texture_bind_group,
            instance_buffer,
            instance_bind_group_layout,
            instance_bind_group,
            instance_capacity: initial_instance_capacity,
            vertex_buffer: None,
            index_buffer: None,
            index_count: 0,
            _textures: vec![
                ceiling_tex,
                floor_tex,
                wall_xy_tex,
                wall_zy_tex,
                noise_tex,
                furniture_tex,
                exterior_tex,
                cubemap_tex,
            ],
        });
    }
}

impl PassNode<crate::ecs::world::World> for InteriorMappingPass {
    fn name(&self) -> &str {
        "interior_mapping_pass"
    }

    fn reads(&self) -> Vec<&str> {
        vec![]
    }

    fn writes(&self) -> Vec<&str> {
        vec![]
    }

    fn reads_writes(&self) -> Vec<&str> {
        vec!["color", "depth"]
    }

    fn prepare(
        &mut self,
        device: &wgpu::Device,
        queue: &wgpu::Queue,
        world: &crate::ecs::world::World,
    ) {
        if self.pending_textures.is_some() {
            self.initialize_gpu(device, queue);
        }

        let Some(gpu) = &mut self.gpu else {
            return;
        };

        let Ok(mut state) = self.state.write() else {
            return;
        };

        if !state.enabled {
            return;
        }

        let Some(camera_matrices) =
            crate::ecs::camera::queries::query_active_camera_matrices(world)
        else {
            return;
        };

        let view_proj: [[f32; 4]; 4] = (camera_matrices.projection * camera_matrices.view).into();

        let uniform = InteriorMappingUniforms {
            view_proj,
            wall_freq_and_threshold: [
                state.wall_frequencies[0],
                state.wall_frequencies[1],
                state.wall_frequencies[2],
                state.light_threshold,
            ],
            disp_and_alpha: [
                state.displacement_strengths[0],
                state.displacement_strengths[1],
                state.displacement_strengths[2],
                state.alpha_plane_distance,
            ],
            uv_and_pad: [state.uv_multiplier, 0.0, 0.0, 0.0],
            sun_direction_and_intensity: [
                state.sun_direction[0],
                state.sun_direction[1],
                state.sun_direction[2],
                state.sun_intensity,
            ],
            sun_color: [
                state.sun_color[0],
                state.sun_color[1],
                state.sun_color[2],
                1.0,
            ],
            ambient_color: state.ambient_color,
        };
        queue.write_buffer(&gpu.uniform_buffer, 0, bytemuck::bytes_of(&uniform));

        if state.mesh_dirty {
            if let Some(mesh_data) = &state.mesh_data {
                let vertex_data = bytemuck::cast_slice(&mesh_data.vertices);
                let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                    label: Some("IM Vertex Buffer"),
                    size: vertex_data.len() as u64,
                    usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
                    mapped_at_creation: false,
                });
                queue.write_buffer(&vertex_buffer, 0, vertex_data);
                gpu.vertex_buffer = Some(vertex_buffer);

                let index_data = bytemuck::cast_slice(&mesh_data.indices);
                let index_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                    label: Some("IM Index Buffer"),
                    size: index_data.len() as u64,
                    usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
                    mapped_at_creation: false,
                });
                queue.write_buffer(&index_buffer, 0, index_data);
                gpu.index_buffer = Some(index_buffer);
                gpu.index_count = mesh_data.indices.len() as u32;
            }
            state.mesh_dirty = false;
        }

        if !state.instances.is_empty() {
            let camera_world_pos = camera_matrices.camera_position;

            let gpu_instances: Vec<GpuInstanceData> = state
                .instances
                .iter()
                .map(|instance| {
                    let model = instance.model_matrix;
                    let model_inv = nalgebra_glm::inverse(&model);
                    let camera_obj = model_inv
                        * nalgebra_glm::Vec4::new(
                            camera_world_pos.x,
                            camera_world_pos.y,
                            camera_world_pos.z,
                            1.0,
                        );
                    GpuInstanceData {
                        model: model.into(),
                        camera_pos_object: [camera_obj.x, camera_obj.y, camera_obj.z, 1.0],
                    }
                })
                .collect();

            let required_size = gpu_instances.len() * std::mem::size_of::<GpuInstanceData>();

            if gpu_instances.len() > gpu.instance_capacity {
                let new_capacity = gpu_instances.len().next_power_of_two();
                gpu.instance_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                    label: Some("IM Instance Buffer"),
                    size: (new_capacity * std::mem::size_of::<GpuInstanceData>()) as u64,
                    usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
                    mapped_at_creation: false,
                });
                gpu.instance_capacity = new_capacity;
                gpu.instance_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
                    label: Some("IM Instance BG"),
                    layout: &gpu.instance_bind_group_layout,
                    entries: &[wgpu::BindGroupEntry {
                        binding: 0,
                        resource: gpu.instance_buffer.as_entire_binding(),
                    }],
                });
            }

            queue.write_buffer(
                &gpu.instance_buffer,
                0,
                &bytemuck::cast_slice(&gpu_instances)[..required_size],
            );
        }
    }

    fn execute<'r, 'e>(
        &mut self,
        context: PassExecutionContext<'r, 'e, crate::ecs::world::World>,
    ) -> crate::render::wgpu::rendergraph::Result<
        Vec<crate::render::wgpu::rendergraph::SubGraphRunCommand<'r>>,
    > {
        let Some(gpu) = &self.gpu else {
            return Ok(context.into_sub_graph_commands());
        };

        let (enabled, instance_count) = self
            .state
            .read()
            .map(|s| (s.enabled, s.instances.len()))
            .unwrap_or((false, 0));

        if !enabled || instance_count == 0 || gpu.vertex_buffer.is_none() {
            return Ok(context.into_sub_graph_commands());
        }

        let (color_view, color_load, color_store) = context.get_color_attachment("color")?;
        let (depth_view, depth_load, depth_store) = context.get_depth_attachment("depth")?;

        let mut render_pass = context
            .encoder
            .begin_render_pass(&wgpu::RenderPassDescriptor {
                label: Some("Interior Mapping Pass"),
                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                    view: color_view,
                    resolve_target: None,
                    ops: wgpu::Operations {
                        load: color_load,
                        store: color_store,
                    },
                    depth_slice: None,
                })],
                depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
                    view: depth_view,
                    depth_ops: Some(wgpu::Operations {
                        load: depth_load,
                        store: depth_store,
                    }),
                    stencil_ops: None,
                }),
                timestamp_writes: None,
                occlusion_query_set: None,
            });

        render_pass.set_pipeline(&gpu.pipeline);
        render_pass.set_bind_group(0, &gpu.uniform_bind_group, &[]);
        render_pass.set_bind_group(1, &gpu.texture_bind_group, &[]);
        render_pass.set_bind_group(2, &gpu.instance_bind_group, &[]);
        render_pass.set_vertex_buffer(0, gpu.vertex_buffer.as_ref().unwrap().slice(..));
        render_pass.set_index_buffer(
            gpu.index_buffer.as_ref().unwrap().slice(..),
            wgpu::IndexFormat::Uint32,
        );
        render_pass.draw_indexed(0..gpu.index_count, 0, 0..instance_count as u32);

        drop(render_pass);

        Ok(context.into_sub_graph_commands())
    }
}
