use crate::ecs::text::components::{HudAnchor, TextAlignment, TextVertex};
use crate::ecs::ui::types::Rect;
use crate::ecs::world::World;
use crate::render::wgpu::rendergraph::{PassExecutionContext, PassNode};
use nalgebra_glm::{Mat4, Vec2, Vec4};

pub struct HudPass {
    pub render_pipeline: wgpu::RenderPipeline,
    pub texture_bind_group_layout: wgpu::BindGroupLayout,
    pub uniform_bind_group_layout: wgpu::BindGroupLayout,

    pub vertex_buffer: wgpu::Buffer,
    pub index_buffer: wgpu::Buffer,
    pub uniform_buffer: wgpu::Buffer,

    pub uniform_bind_group: wgpu::BindGroup,
    pub instance_uniform_bind_groups: Vec<wgpu::BindGroup>,
    pub texture_bind_groups: Vec<wgpu::BindGroup>,

    pub hud_instances: Vec<HudInstance>,

    vertex_buffer_capacity: usize,
    index_buffer_capacity: usize,

    screen_width: f32,
    screen_height: f32,
}

use crate::render::wgpu::passes::geometry::UiLayer;

#[derive(Clone)]
pub struct HudInstance {
    pub vertex_offset: u32,
    pub index_offset: u32,
    pub index_count: u32,
    pub color: Vec4,
    pub outline_color: Vec4,
    pub outline_width: f32,
    pub font_index: usize,
    pub clip_rect: Option<Rect>,
    pub layer: UiLayer,
    pub z_index: i32,
}

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct HudUniforms {
    pub projection: Mat4,
    pub color: Vec4,
    pub outline_color: Vec4,
    pub outline_width: f32,
    pub depth: f32,
    pub _padding: Vec2,
}

fn calculate_screen_position(
    position: Vec2,
    bounds: Vec2,
    anchor: HudAnchor,
    text_alignment: TextAlignment,
    screen_width: f32,
    screen_height: f32,
) -> Vec2 {
    const SCALE: f32 = 100.0;
    let text_width = bounds.x * SCALE;
    let text_height = bounds.y * SCALE;

    let base_position = match (anchor, text_alignment) {
        (HudAnchor::TopLeft, _) => nalgebra_glm::vec2(0.0, 0.0),
        (HudAnchor::TopCenter, TextAlignment::Center) => {
            nalgebra_glm::vec2(screen_width * 0.5, 0.0)
        }
        (HudAnchor::TopCenter, TextAlignment::Left) => {
            nalgebra_glm::vec2(screen_width * 0.5 - text_width * 0.5, 0.0)
        }
        (HudAnchor::TopCenter, TextAlignment::Right) => {
            nalgebra_glm::vec2(screen_width * 0.5 + text_width * 0.5, 0.0)
        }
        (HudAnchor::TopRight, _) => nalgebra_glm::vec2(screen_width - text_width, 0.0),
        (HudAnchor::CenterLeft, _) => {
            nalgebra_glm::vec2(0.0, screen_height * 0.5 - text_height * 0.5)
        }
        (HudAnchor::Center, TextAlignment::Center) => {
            nalgebra_glm::vec2(screen_width * 0.5, screen_height * 0.5 - text_height * 0.5)
        }
        (HudAnchor::Center, TextAlignment::Left) => nalgebra_glm::vec2(
            screen_width * 0.5 - text_width * 0.5,
            screen_height * 0.5 - text_height * 0.5,
        ),
        (HudAnchor::Center, TextAlignment::Right) => nalgebra_glm::vec2(
            screen_width * 0.5 + text_width * 0.5,
            screen_height * 0.5 - text_height * 0.5,
        ),
        (HudAnchor::CenterRight, _) => nalgebra_glm::vec2(
            screen_width - text_width,
            screen_height * 0.5 - text_height * 0.5,
        ),
        (HudAnchor::BottomLeft, _) => nalgebra_glm::vec2(0.0, screen_height - text_height),
        (HudAnchor::BottomCenter, TextAlignment::Center) => {
            nalgebra_glm::vec2(screen_width * 0.5, screen_height - text_height)
        }
        (HudAnchor::BottomCenter, TextAlignment::Left) => nalgebra_glm::vec2(
            screen_width * 0.5 - text_width * 0.5,
            screen_height - text_height,
        ),
        (HudAnchor::BottomCenter, TextAlignment::Right) => nalgebra_glm::vec2(
            screen_width * 0.5 + text_width * 0.5,
            screen_height - text_height,
        ),
        (HudAnchor::BottomRight, _) => {
            nalgebra_glm::vec2(screen_width - text_width, screen_height - text_height)
        }
    };

    base_position + position
}

impl HudPass {
    pub fn new(device: &wgpu::Device, color_format: wgpu::TextureFormat) -> Self {
        let uniform_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("HUD Uniform Bind Group Layout"),
                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 texture_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("HUD Texture Bind Group Layout"),
                entries: &[
                    wgpu::BindGroupLayoutEntry {
                        binding: 0,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Texture {
                            multisampled: false,
                            view_dimension: wgpu::TextureViewDimension::D2,
                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 1,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
                        count: None,
                    },
                ],
            });

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

        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
            label: Some("HUD Shader"),
            source: wgpu::ShaderSource::Wgsl(include_str!("../../shaders/hud.wgsl").into()),
        });

        let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: Some("HUD Render Pipeline"),
            layout: Some(&pipeline_layout),
            vertex: wgpu::VertexState {
                module: &shader,
                entry_point: Some("vs_main"),
                buffers: &[wgpu::VertexBufferLayout {
                    array_stride: std::mem::size_of::<TextVertex>() as wgpu::BufferAddress,
                    step_mode: wgpu::VertexStepMode::Vertex,
                    attributes: &[
                        wgpu::VertexAttribute {
                            offset: 0,
                            shader_location: 0,
                            format: wgpu::VertexFormat::Float32x3,
                        },
                        wgpu::VertexAttribute {
                            offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
                            shader_location: 1,
                            format: wgpu::VertexFormat::Float32x2,
                        },
                        wgpu::VertexAttribute {
                            offset: std::mem::size_of::<[f32; 5]>() as wgpu::BufferAddress,
                            shader_location: 2,
                            format: wgpu::VertexFormat::Float32x4,
                        },
                    ],
                }],
                compilation_options: wgpu::PipelineCompilationOptions::default(),
            },
            primitive: wgpu::PrimitiveState {
                topology: wgpu::PrimitiveTopology::TriangleList,
                strip_index_format: None,
                front_face: wgpu::FrontFace::Ccw,
                cull_mode: None,
                polygon_mode: wgpu::PolygonMode::Fill,
                unclipped_depth: false,
                conservative: false,
            },
            depth_stencil: Some(wgpu::DepthStencilState {
                format: wgpu::TextureFormat::Depth32Float,
                depth_write_enabled: false,
                depth_compare: wgpu::CompareFunction::GreaterEqual,
                stencil: wgpu::StencilState::default(),
                bias: wgpu::DepthBiasState::default(),
            }),
            multisample: wgpu::MultisampleState {
                count: 1,
                mask: !0,
                alpha_to_coverage_enabled: false,
            },
            fragment: Some(wgpu::FragmentState {
                module: &shader,
                entry_point: Some("fs_main"),
                targets: &[Some(wgpu::ColorTargetState {
                    format: color_format,
                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
                    write_mask: wgpu::ColorWrites::ALL,
                })],
                compilation_options: wgpu::PipelineCompilationOptions::default(),
            }),
            multiview: None,
            cache: None,
        });

        let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("HUD Vertex Buffer"),
            size: (std::mem::size_of::<TextVertex>() * 10000) as u64,
            usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let index_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("HUD Index Buffer"),
            size: (std::mem::size_of::<u32>() * 30000) as u64,
            usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

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

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

        Self {
            render_pipeline,
            texture_bind_group_layout,
            uniform_bind_group_layout,
            vertex_buffer,
            index_buffer,
            uniform_buffer,
            uniform_bind_group,
            instance_uniform_bind_groups: Vec::new(),
            texture_bind_groups: Vec::new(),
            hud_instances: Vec::new(),
            vertex_buffer_capacity: 10000,
            index_buffer_capacity: 30000,
            screen_width: 800.0,
            screen_height: 600.0,
        }
    }

    fn ensure_vertex_buffer_capacity(&mut self, device: &wgpu::Device, required: usize) {
        if required > self.vertex_buffer_capacity {
            let new_capacity = (required * 2).max(1000);
            self.vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                label: Some("HUD Vertex Buffer (Resized)"),
                size: (std::mem::size_of::<TextVertex>() * new_capacity) as u64,
                usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
                mapped_at_creation: false,
            });
            self.vertex_buffer_capacity = new_capacity;
        }
    }

    fn ensure_index_buffer_capacity(&mut self, device: &wgpu::Device, required: usize) {
        if required > self.index_buffer_capacity {
            let new_capacity = (required * 2).max(3000);
            self.index_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                label: Some("HUD Index Buffer (Resized)"),
                size: (std::mem::size_of::<u32>() * new_capacity) as u64,
                usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
                mapped_at_creation: false,
            });
            self.index_buffer_capacity = new_capacity;
        }
    }

    pub fn update_texture_bind_groups(
        &mut self,
        device: &wgpu::Device,
        font_textures: &[(wgpu::Texture, wgpu::TextureView)],
    ) {
        self.texture_bind_groups.clear();

        for (_texture, texture_view) in font_textures {
            let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
                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 bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
                label: Some("HUD Texture Bind Group"),
                layout: &self.texture_bind_group_layout,
                entries: &[
                    wgpu::BindGroupEntry {
                        binding: 0,
                        resource: wgpu::BindingResource::TextureView(texture_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 1,
                        resource: wgpu::BindingResource::Sampler(&sampler),
                    },
                ],
            });

            self.texture_bind_groups.push(bind_group);
        }
    }
}

impl PassNode<World> for HudPass {
    fn name(&self) -> &'static str {
        "hud_pass"
    }

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

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

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

    fn prepare(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, world: &World) {
        self.hud_instances.clear();
        self.instance_uniform_bind_groups.clear();

        let mut all_vertices = Vec::new();
        let mut all_indices = Vec::new();

        if let Some(window_handle) = &world.resources.window.handle {
            let size = window_handle.inner_size();
            self.screen_width = size.width as f32;
            self.screen_height = size.height as f32;
        }

        let text_entities = world.query_entities(crate::ecs::world::HUD_TEXT);

        for entity in text_entities {
            let hud_text = world.get_hud_text(entity);

            if let Some(hud_text) = hud_text
                && let Some(mesh) = &hud_text.cached_mesh
            {
                const SCALE: f32 = 100.0;

                let screen_position = calculate_screen_position(
                    hud_text.position,
                    mesh.bounds,
                    hud_text.anchor,
                    hud_text.properties.alignment,
                    self.screen_width,
                    self.screen_height,
                );

                let vertex_offset = all_vertices.len() as u32;
                let index_offset = all_indices.len() as u32;

                for vertex in &mesh.vertices {
                    all_vertices.push(TextVertex {
                        position: nalgebra_glm::vec3(
                            vertex.position.x * SCALE + screen_position.x,
                            -vertex.position.y * SCALE + screen_position.y,
                            0.0,
                        ),
                        tex_coords: vertex.tex_coords,
                        color: vertex.color,
                    });
                }

                for &index in &mesh.indices {
                    all_indices.push(index + vertex_offset);
                }

                self.hud_instances.push(HudInstance {
                    vertex_offset,
                    index_offset,
                    index_count: mesh.indices.len() as u32,
                    color: hud_text.properties.color,
                    outline_color: hud_text.properties.outline_color,
                    outline_width: hud_text.properties.outline_width,
                    font_index: hud_text.font_index,
                    clip_rect: None,
                    layer: UiLayer::Background,
                    z_index: 0,
                });
            }
        }

        let mut sorted_text: Vec<_> = world
            .resources
            .immediate_ui
            .frame_text_meshes
            .iter()
            .collect();
        sorted_text.sort_by(|a, b| match a.layer.cmp(&b.layer) {
            std::cmp::Ordering::Equal => a.z_index.cmp(&b.z_index),
            other => other,
        });

        for text_instance in sorted_text {
            const SCALE: f32 = 100.0;

            let vertex_offset = all_vertices.len() as u32;
            let index_offset = all_indices.len() as u32;

            for vertex in &text_instance.mesh.vertices {
                all_vertices.push(TextVertex {
                    position: nalgebra_glm::vec3(
                        vertex.position.x * SCALE + text_instance.position.x,
                        -vertex.position.y * SCALE + text_instance.position.y,
                        0.0,
                    ),
                    tex_coords: vertex.tex_coords,
                    color: vertex.color,
                });
            }

            for &index in &text_instance.mesh.indices {
                all_indices.push(index + vertex_offset);
            }

            self.hud_instances.push(HudInstance {
                vertex_offset,
                index_offset,
                index_count: text_instance.mesh.indices.len() as u32,
                color: text_instance.color,
                outline_color: text_instance.outline_color,
                outline_width: text_instance.outline_width,
                font_index: text_instance.font_index,
                clip_rect: text_instance.clip_rect,
                layer: text_instance.layer,
                z_index: text_instance.z_index,
            });
        }

        if !all_vertices.is_empty() {
            self.ensure_vertex_buffer_capacity(device, all_vertices.len());
            queue.write_buffer(&self.vertex_buffer, 0, bytemuck::cast_slice(&all_vertices));
        }

        if !all_indices.is_empty() {
            self.ensure_index_buffer_capacity(device, all_indices.len());
            queue.write_buffer(&self.index_buffer, 0, bytemuck::cast_slice(&all_indices));
        }

        let projection =
            nalgebra_glm::ortho(0.0, self.screen_width, self.screen_height, 0.0, -1.0, 1.0);

        for instance in &self.hud_instances {
            let combined_z = (instance.layer as i32) * 100000 + instance.z_index;
            let depth = 0.1 + (combined_z as f32 / 10000000.0) * 0.8;
            let uniforms = HudUniforms {
                projection,
                color: instance.color,
                outline_color: instance.outline_color,
                outline_width: instance.outline_width,
                depth,
                _padding: Vec2::new(0.0, 0.0),
            };

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

            queue.write_buffer(&instance_buffer, 0, bytemuck::cast_slice(&[uniforms]));

            let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
                label: Some("HUD Instance Uniform Bind Group"),
                layout: &self.uniform_bind_group_layout,
                entries: &[wgpu::BindGroupEntry {
                    binding: 0,
                    resource: instance_buffer.as_entire_binding(),
                }],
            });

            self.instance_uniform_bind_groups.push(bind_group);
        }
    }

    fn execute<'r, 'e>(
        &mut self,
        context: PassExecutionContext<'r, 'e, World>,
    ) -> Result<
        Vec<crate::render::wgpu::rendergraph::SubGraphRunCommand<'r>>,
        crate::render::wgpu::rendergraph::RenderGraphError,
    > {
        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 (render_target_width, render_target_height) = context.get_texture_size("color")?;

        let mut render_pass = context
            .encoder
            .begin_render_pass(&wgpu::RenderPassDescriptor {
                label: Some("HUD Render Pass"),
                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                    view: color_view,
                    resolve_target: None,
                    depth_slice: None,
                    ops: wgpu::Operations {
                        load: color_load,
                        store: color_store,
                    },
                })],
                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,
            });

        if !self.hud_instances.is_empty() {
            render_pass.set_pipeline(&self.render_pipeline);
            render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
            render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);

            for (instance_index, instance) in self.hud_instances.iter().enumerate() {
                if instance.font_index >= self.texture_bind_groups.len() {
                    tracing::warn!(
                        "Skipping HUD text instance with invalid font index: {}",
                        instance.font_index
                    );
                    continue;
                }

                if instance_index >= self.instance_uniform_bind_groups.len() {
                    tracing::warn!(
                        "Skipping HUD instance with missing uniform bind group: {}",
                        instance_index
                    );
                    continue;
                }

                let index_end = instance.index_offset.saturating_add(instance.index_count);
                if instance.vertex_offset >= self.vertex_buffer_capacity as u32
                    || index_end > self.index_buffer_capacity as u32
                {
                    tracing::warn!("Skipping HUD instance with out-of-bounds indices");
                    continue;
                }

                let screen_w = render_target_width;
                let screen_h = render_target_height;
                if let Some(clip_rect) = instance.clip_rect {
                    let x = (clip_rect.min.x.max(0.0) as u32).min(screen_w);
                    let y = (clip_rect.min.y.max(0.0) as u32).min(screen_h);
                    let max_width = screen_w.saturating_sub(x);
                    let max_height = screen_h.saturating_sub(y);
                    let width = (clip_rect.width().max(0.0) as u32).min(max_width);
                    let height = (clip_rect.height().max(0.0) as u32).min(max_height);
                    if width > 0 && height > 0 {
                        render_pass.set_scissor_rect(x, y, width, height);
                    } else {
                        continue;
                    }
                } else {
                    render_pass.set_scissor_rect(0, 0, screen_w, screen_h);
                }

                render_pass.set_bind_group(
                    0,
                    &self.instance_uniform_bind_groups[instance_index],
                    &[],
                );

                render_pass.set_bind_group(1, &self.texture_bind_groups[instance.font_index], &[]);

                render_pass.draw_indexed(instance.index_offset..index_end, 0, 0..1);
            }
        }

        drop(render_pass);

        Ok(context.into_sub_graph_commands())
    }
}
