use std::collections::{HashMap, HashSet};

pub(super) use super::super::projection::{LightData, MAX_SPOTLIGHT_SHADOWS};
pub(super) use crate::render::wgpu::passes::shadow_depth::{
    MAX_POINT_LIGHT_SHADOWS, PointLightShadowData,
};

use freecs::Entity;

pub(super) const INITIAL_INSTANCES: usize = 1_000;
pub const MAX_INSTANCES: usize = 1_000_000;
pub(super) const INITIAL_VERTEX_BUFFER_SIZE: usize = 10_000;
pub(super) const INITIAL_INDEX_BUFFER_SIZE: usize = 30_000;
pub(super) const INITIAL_MATERIALS: usize = 100;
pub(super) const INITIAL_LIGHTS: usize = 32;
pub(super) const INITIAL_LOCAL_TRANSFORM_BUFFER_SIZE: usize = 1_000;
pub(super) const MAX_HIERARCHY_DEPTH: u32 = 32;
pub(super) const MAX_LIGHTS: usize = 1024;
pub(super) const BUFFER_GROWTH_FACTOR: f32 = 2.0;
pub(super) const BUFFER_SHRINK_THRESHOLD: f32 = 0.25;
pub(super) const COMPACTION_MIN_CHECK_INTERVAL: u64 = 60;
pub(super) const COMPACTION_UTILIZATION_DROP_THRESHOLD: f32 = 0.1;

pub const CLUSTER_GRID_X: u32 = 16;
pub const CLUSTER_GRID_Y: u32 = 9;
pub const CLUSTER_GRID_Z: u32 = 24;
pub const MAX_LIGHTS_PER_CLUSTER: u32 = 256;
pub const TOTAL_CLUSTERS: u32 = CLUSTER_GRID_X * CLUSTER_GRID_Y * CLUSTER_GRID_Z;

#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct ClusterUniforms {
    pub inverse_projection: [[f32; 4]; 4],
    pub screen_size: [f32; 2],
    pub z_near: f32,
    pub z_far: f32,
    pub cluster_count: [u32; 4],
    pub tile_size: [f32; 2],
    pub num_lights: u32,
    pub num_directional_lights: u32,
}

#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct ClusterBounds {
    pub min_point: [f32; 4],
    pub max_point: [f32; 4],
}

#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct LightGrid {
    pub offset: u32,
    pub count: u32,
}

#[repr(C, align(256))]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub(super) struct MeshUniforms {
    pub(super) view: [[f32; 4]; 4],
    pub(super) projection: [[f32; 4]; 4],
    pub(super) camera_position: [f32; 4],
    pub(super) num_lights: [u32; 4],
    pub(super) ambient_light: [f32; 4],
    pub(super) light_view_projection: [[f32; 4]; 4],
    pub(super) shadow_bias: f32,
    pub(super) shadows_enabled: f32,
    pub(super) global_unlit: f32,
    pub(super) shadow_normal_bias: f32,
    pub(super) snap_resolution: [f32; 2],
    pub(super) snap_enabled: u32,
    pub(super) affine_enabled: u32,
    pub(super) fog_color: [f32; 3],
    pub(super) fog_enabled: u32,
    pub(super) fog_start: f32,
    pub(super) fog_end: f32,
    pub(super) cascade_count: u32,
    pub(super) directional_light_size: f32,
    pub(super) cascade_view_projections:
        [[[f32; 4]; 4]; crate::render::wgpu::passes::NUM_SHADOW_CASCADES],
    pub(super) cascade_split_distances: [f32; crate::render::wgpu::passes::NUM_SHADOW_CASCADES],
    pub(super) cascade_atlas_offsets: [[f32; 4]; crate::render::wgpu::passes::NUM_SHADOW_CASCADES],
    pub(super) cascade_atlas_scale: [f32; 4],
    pub(super) time: f32,
    pub(super) pbr_debug_mode: u32,
    pub(super) texture_debug_stripes: u32,
    pub(super) texture_debug_stripes_speed: f32,
    pub(super) directional_light_direction: [f32; 4],
    pub(super) _padding3: [f32; 20],
}

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

#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct GpuLocalTransform {
    pub translation: [f32; 3],
    pub parent_index: i32,
    pub rotation: [f32; 4],
    pub scale: [f32; 3],
    pub depth: u32,
}

#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct TransformComputeUniforms {
    pub entity_count: u32,
    pub current_depth: u32,
    pub _pad: [u32; 2],
}

#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub(super) struct ObjectData {
    pub(super) transform_index: u32,
    pub(super) mesh_id: u32,
    pub(super) material_id: u32,
    pub(super) batch_id: u32,
    pub(super) morph_weights: [f32; 8],
    pub(super) morph_target_count: u32,
    pub(super) morph_displacement_offset: u32,
    pub(super) mesh_vertex_offset: u32,
    pub(super) mesh_vertex_count: u32,
    pub(super) entity_id: u32,
    pub(super) is_overlay: u32,
    pub(super) _entity_padding: [u32; 2],
}

#[repr(C, align(16))]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub(super) struct CullingUniforms {
    pub(super) frustum_planes: [[f32; 4]; 6],
    pub(super) view_projection: [[f32; 4]; 4],
    pub(super) screen_size: [f32; 2],
    pub(super) hiz_mip_count: f32,
    pub(super) occlusion_enabled: u32,
    pub(super) object_count: u32,
    pub(super) _padding: [u32; 3],
}

#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct MaterialData {
    pub base_color: [f32; 4],
    pub emissive_factor: [f32; 3],
    pub alpha_mode: u32,
    pub alpha_cutoff: f32,
    pub has_base_texture: u32,
    pub has_emissive_texture: u32,
    pub has_normal_texture: u32,
    pub has_metallic_roughness_texture: u32,
    pub has_occlusion_texture: u32,
    pub normal_scale: f32,
    pub occlusion_strength: f32,
    pub roughness: f32,
    pub metallic: f32,
    pub unlit: u32,
    pub normal_map_flags: u32,
    pub uv_scale: [f32; 2],
    pub transmission_factor: f32,
    pub has_transmission_texture: u32,
    pub thickness: f32,
    pub has_thickness_texture: u32,
    pub _align_attenuation: [u32; 2],
    pub attenuation_color: [f32; 3],
    pub attenuation_distance: f32,
    pub ior: f32,
    pub specular_factor: f32,
    pub _align_specular: [u32; 2],
    pub specular_color_factor: [f32; 3],
    pub has_specular_texture: u32,
    pub has_specular_color_texture: u32,
    pub emissive_strength: f32,
    pub uv_set_indices: u32,
    pub _pad_end: f32,
}

pub const NORMAL_MAP_FLIP_Y: u32 = 1;
pub const NORMAL_MAP_TWO_COMPONENT: u32 = 2;

pub fn pack_uv_set_indices(material: &crate::ecs::material::components::Material) -> u32 {
    (material.base_texture_uv_set & 1)
        | ((material.emissive_texture_uv_set & 1) << 1)
        | ((material.normal_texture_uv_set & 1) << 2)
        | ((material.metallic_roughness_texture_uv_set & 1) << 3)
        | ((material.occlusion_texture_uv_set & 1) << 4)
        | ((material.transmission_texture_uv_set & 1) << 5)
        | ((material.thickness_texture_uv_set & 1) << 6)
        | ((material.specular_texture_uv_set & 1) << 7)
        | ((material.specular_color_texture_uv_set & 1) << 8)
}

#[derive(Clone, PartialEq, Default)]
pub struct MaterialTextures {
    pub base_texture: Option<String>,
    pub emissive_texture: Option<String>,
    pub normal_texture: Option<String>,
    pub metallic_roughness_texture: Option<String>,
    pub occlusion_texture: Option<String>,
    pub transmission_texture: Option<String>,
    pub thickness_texture: Option<String>,
    pub specular_texture: Option<String>,
    pub specular_color_texture: Option<String>,
}

impl MaterialTextures {
    pub(super) fn contains_any(&self, names: &HashSet<String>) -> bool {
        self.base_texture
            .as_ref()
            .is_some_and(|name| names.contains(name))
            || self
                .emissive_texture
                .as_ref()
                .is_some_and(|name| names.contains(name))
            || self
                .normal_texture
                .as_ref()
                .is_some_and(|name| names.contains(name))
            || self
                .metallic_roughness_texture
                .as_ref()
                .is_some_and(|name| names.contains(name))
            || self
                .occlusion_texture
                .as_ref()
                .is_some_and(|name| names.contains(name))
            || self
                .transmission_texture
                .as_ref()
                .is_some_and(|name| names.contains(name))
            || self
                .thickness_texture
                .as_ref()
                .is_some_and(|name| names.contains(name))
            || self
                .specular_texture
                .as_ref()
                .is_some_and(|name| names.contains(name))
            || self
                .specular_color_texture
                .as_ref()
                .is_some_and(|name| names.contains(name))
    }
}

#[derive(Debug, Clone)]
pub(super) struct MeshData {
    pub(super) vertex_offset: u32,
    pub(super) vertex_count: u32,
    pub(super) index_offset: u32,
    pub(super) index_count: u32,
    pub(super) morph_displacement_offset: u32,
    pub(super) morph_target_count: u32,
}

#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub(super) struct MeshBoundsData {
    pub(super) center: [f32; 3],
    pub(super) radius: f32,
}

#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub(super) struct MeshBoundsAABB {
    pub(super) min: [f32; 3],
    pub(super) _pad0: f32,
    pub(super) max: [f32; 3],
    pub(super) _pad1: f32,
}

#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub(super) struct MorphDisplacement {
    pub(super) position: [f32; 3],
    pub(super) _pad0: f32,
    pub(super) normal: [f32; 3],
    pub(super) _pad1: f32,
    pub(super) tangent: [f32; 3],
    pub(super) _pad2: f32,
}

#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub(super) struct DrawIndexedIndirect {
    pub(super) index_count: u32,
    pub(super) instance_count: u32,
    pub(super) first_index: u32,
    pub(super) base_vertex: i32,
    pub(super) first_instance: u32,
}

pub(super) fn compute_normal_matrix(model_matrix: &nalgebra_glm::Mat4) -> [[f32; 4]; 3] {
    let mat3 = nalgebra_glm::mat4_to_mat3(model_matrix);

    let scale_x_sq =
        mat3[(0, 0)] * mat3[(0, 0)] + mat3[(1, 0)] * mat3[(1, 0)] + mat3[(2, 0)] * mat3[(2, 0)];
    let scale_y_sq =
        mat3[(0, 1)] * mat3[(0, 1)] + mat3[(1, 1)] * mat3[(1, 1)] + mat3[(2, 1)] * mat3[(2, 1)];
    let scale_z_sq =
        mat3[(0, 2)] * mat3[(0, 2)] + mat3[(1, 2)] * mat3[(1, 2)] + mat3[(2, 2)] * mat3[(2, 2)];

    let is_uniform_scale =
        (scale_x_sq - scale_y_sq).abs() < 0.001 && (scale_y_sq - scale_z_sq).abs() < 0.001;

    let normal_mat3 = if is_uniform_scale {
        let inv_scale = 1.0 / scale_x_sq.sqrt();
        nalgebra_glm::Mat3::new(
            mat3[(0, 0)] * inv_scale,
            mat3[(0, 1)] * inv_scale,
            mat3[(0, 2)] * inv_scale,
            mat3[(1, 0)] * inv_scale,
            mat3[(1, 1)] * inv_scale,
            mat3[(1, 2)] * inv_scale,
            mat3[(2, 0)] * inv_scale,
            mat3[(2, 1)] * inv_scale,
            mat3[(2, 2)] * inv_scale,
        )
    } else {
        match mat3.try_inverse() {
            Some(inv) => inv.transpose(),
            None => nalgebra_glm::Mat3::identity(),
        }
    };

    [
        [
            normal_mat3[(0, 0)],
            normal_mat3[(1, 0)],
            normal_mat3[(2, 0)],
            0.0,
        ],
        [
            normal_mat3[(0, 1)],
            normal_mat3[(1, 1)],
            normal_mat3[(2, 1)],
            0.0,
        ],
        [
            normal_mat3[(0, 2)],
            normal_mat3[(1, 2)],
            normal_mat3[(2, 2)],
            0.0,
        ],
    ]
}

#[derive(Debug)]
pub struct GpuEntityRegistry {
    pub entity_to_slot: HashMap<Entity, u32>,
    pub slot_to_entity: Vec<Option<Entity>>,
    pub free_slots: Vec<u32>,
    pub slot_count: u32,
}

impl Default for GpuEntityRegistry {
    fn default() -> Self {
        Self::new()
    }
}

impl GpuEntityRegistry {
    pub fn new() -> Self {
        Self {
            entity_to_slot: HashMap::new(),
            slot_to_entity: Vec::new(),
            free_slots: Vec::new(),
            slot_count: 0,
        }
    }

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

    pub fn clear(&mut self) {
        self.entity_to_slot.clear();
        self.slot_to_entity.clear();
        self.free_slots.clear();
        self.slot_count = 0;
    }
}

use std::cell::RefCell;

#[derive(Debug, Default)]
pub struct MeshRenderState {
    pub(crate) inner: RefCell<MeshRenderStateInner>,
}

#[derive(Debug, Default)]
pub struct MeshRenderStateInner {
    pub transform_dirty: HashSet<Entity>,
    pub material_dirty: HashSet<Entity>,
    pub visibility_dirty: HashSet<Entity>,
    pub entities_added: Vec<Entity>,
    pub entities_removed: Vec<Entity>,
    pub batches_invalidated: bool,
    pub full_rebuild_needed: bool,
    pub frame_initialized: bool,
}

impl MeshRenderState {
    pub fn new() -> Self {
        Self {
            inner: RefCell::new(MeshRenderStateInner {
                transform_dirty: HashSet::new(),
                material_dirty: HashSet::new(),
                visibility_dirty: HashSet::new(),
                entities_added: Vec::new(),
                entities_removed: Vec::new(),
                batches_invalidated: false,
                full_rebuild_needed: true,
                frame_initialized: false,
            }),
        }
    }

    pub fn mark_transform_dirty(&self, entity: Entity) {
        self.inner.borrow_mut().transform_dirty.insert(entity);
    }

    pub fn mark_material_dirty(&self, entity: Entity) {
        let mut inner = self.inner.borrow_mut();
        inner.material_dirty.insert(entity);
        inner.batches_invalidated = true;
    }

    pub fn mark_visibility_dirty(&self, entity: Entity) {
        let mut inner = self.inner.borrow_mut();
        inner.visibility_dirty.insert(entity);
        inner.batches_invalidated = true;
    }

    pub fn mark_entity_added(&self, entity: Entity) {
        let mut inner = self.inner.borrow_mut();
        inner.entities_added.push(entity);
        inner.batches_invalidated = true;
    }

    pub fn mark_entity_removed(&self, entity: Entity) {
        let mut inner = self.inner.borrow_mut();
        inner.entities_removed.push(entity);
        inner.batches_invalidated = true;
        inner.transform_dirty.remove(&entity);
        inner.material_dirty.remove(&entity);
        inner.visibility_dirty.remove(&entity);
    }

    pub fn request_full_rebuild(&self) {
        self.inner.borrow_mut().full_rebuild_needed = true;
    }

    pub fn clear_frame(&self) {
        let mut inner = self.inner.borrow_mut();
        inner.transform_dirty.clear();
        inner.material_dirty.clear();
        inner.visibility_dirty.clear();
        inner.entities_added.clear();
        inner.entities_removed.clear();
        inner.batches_invalidated = false;
        inner.full_rebuild_needed = false;
        inner.frame_initialized = true;
    }

    pub fn has_any_changes(&self) -> bool {
        let inner = self.inner.borrow();
        !inner.transform_dirty.is_empty()
            || !inner.material_dirty.is_empty()
            || !inner.visibility_dirty.is_empty()
            || !inner.entities_added.is_empty()
            || !inner.entities_removed.is_empty()
            || inner.batches_invalidated
            || inner.full_rebuild_needed
    }

    pub fn dirty_transform_count(&self) -> usize {
        self.inner.borrow().transform_dirty.len()
    }

    pub fn frame_initialized(&self) -> bool {
        self.inner.borrow().frame_initialized
    }

    pub fn full_rebuild_needed(&self) -> bool {
        self.inner.borrow().full_rebuild_needed
    }

    pub fn batches_invalidated(&self) -> bool {
        self.inner.borrow().batches_invalidated
    }

    pub fn entities_added_is_empty(&self) -> bool {
        self.inner.borrow().entities_added.is_empty()
    }

    pub fn entities_removed_is_empty(&self) -> bool {
        self.inner.borrow().entities_removed.is_empty()
    }

    pub fn material_dirty_is_empty(&self) -> bool {
        self.inner.borrow().material_dirty.is_empty()
    }

    pub fn visibility_dirty_is_empty(&self) -> bool {
        self.inner.borrow().visibility_dirty.is_empty()
    }

    pub fn transform_dirty_is_empty(&self) -> bool {
        self.inner.borrow().transform_dirty.is_empty()
    }

    pub fn take_transform_dirty(&self) -> HashSet<Entity> {
        std::mem::take(&mut self.inner.borrow_mut().transform_dirty)
    }
}
