use crate::ecs::camera::queries::query_active_camera_matrices;
use crate::ecs::sprite::components::{SpriteBlendMode, SpriteGradient, SpriteStencilMode};
use crate::ecs::sprite_text::components::SpriteTextAlignment;
use crate::ecs::world::World;
use crate::render::wgpu::rendergraph::{PassExecutionContext, PassNode};
use crate::render::wgpu::sprite_font_atlas::{SpriteFontAtlas, rasterize_sprite_font};
use crate::render::wgpu::sprite_texture_atlas::SpriteTextureAtlas;
use nalgebra_glm::{Mat4, Vec2, Vec4};
use std::collections::HashMap;
use wgpu::util::DeviceExt;

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct SpriteInstance {
    pub position: Vec2,
    pub size: Vec2,
    pub uv_min: Vec2,
    pub uv_max: Vec2,
    pub color: Vec4,
    pub rotation_scale: Vec4,
    pub anchor: Vec2,
    pub depth: f32,
    pub texture_slot: u32,
    pub texture_slot2: u32,
    pub blend_factor: f32,
    pub gradient_type: u32,
    pub gradient_param_a: f32,
    pub gradient_param_b: f32,
    pub advanced_blend_mode: u32,
    pub _padding: [f32; 2],
}

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct SpriteVertex {
    pub offset: Vec2,
    pub uv: Vec2,
}

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct GlobalUniforms {
    pub view_projection: Mat4,
    pub screen_size: Vec2,
    pub atlas_slots_per_row: f32,
    pub atlas_slot_uv_size: f32,
}

struct SpriteDrawBatch {
    blend_mode: SpriteBlendMode,
    stencil_mode: SpriteStencilMode,
    stencil_reference: u32,
    clip_rect: Option<[f32; 4]>,
    instance_start: u32,
    instance_count: u32,
}

pub struct SpritePass {
    pipelines_normal: [wgpu::RenderPipeline; 4],
    pipelines_stencil_write: [wgpu::RenderPipeline; 4],
    pipelines_stencil_test: [wgpu::RenderPipeline; 4],
    pipeline_advanced_normal: wgpu::RenderPipeline,
    pipeline_advanced_stencil_test: wgpu::RenderPipeline,
    global_uniform_buffer: wgpu::Buffer,
    instance_buffer: wgpu::Buffer,
    vertex_buffer: wgpu::Buffer,
    index_buffer: wgpu::Buffer,
    uniform_bind_group: wgpu::BindGroup,
    texture_bind_group_layout: wgpu::BindGroupLayout,
    texture_bind_group: Option<wgpu::BindGroup>,
    background_bind_group_layout: wgpu::BindGroupLayout,
    background_texture: wgpu::Texture,
    background_view: wgpu::TextureView,
    background_sampler: wgpu::Sampler,
    background_bind_group: Option<wgpu::BindGroup>,
    background_texture_size: (u32, u32),
    max_instances: usize,
    instance_data: Vec<SpriteInstance>,
    blend_modes: Vec<SpriteBlendMode>,
    stencil_modes: Vec<SpriteStencilMode>,
    stencil_references: Vec<u8>,
    clip_rects: Vec<Option<[f32; 4]>>,
    draw_batches: Vec<SpriteDrawBatch>,
    atlas: SpriteTextureAtlas,
    stencil_texture: wgpu::Texture,
    stencil_view: wgpu::TextureView,
    stencil_texture_size: (u32, u32),
    cached_view_projection: Option<Mat4>,
    cached_surface_width: u32,
    cached_surface_height: u32,
    sort_indices: Vec<usize>,
    sorted_instances_scratch: Vec<SpriteInstance>,
    sorted_blend_modes_scratch: Vec<SpriteBlendMode>,
    sorted_stencil_modes_scratch: Vec<SpriteStencilMode>,
    sorted_stencil_references_scratch: Vec<u8>,
    sorted_clip_rects_scratch: Vec<Option<[f32; 4]>>,
    sprite_font_atlas: SpriteFontAtlas,
    sprite_text_cache: HashMap<freecs::Entity, CachedSpriteText>,
}

struct CachedSpriteText {
    instances: Vec<SpriteInstance>,
    text: String,
    position: Vec2,
    depth: f32,
    font_size: f32,
    color: [f32; 4],
    alignment: SpriteTextAlignment,
}

struct SpritePipelineConfig<'a> {
    layout: &'a wgpu::PipelineLayout,
    shader: &'a wgpu::ShaderModule,
    color_format: wgpu::TextureFormat,
    blend_state: wgpu::BlendState,
    depth_stencil: Option<wgpu::DepthStencilState>,
    color_write_mask: wgpu::ColorWrites,
    label: &'a str,
    fragment_entry: &'a str,
}

fn create_sprite_pipeline(
    device: &wgpu::Device,
    config: &SpritePipelineConfig,
) -> wgpu::RenderPipeline {
    device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
        label: Some(config.label),
        layout: Some(config.layout),
        vertex: wgpu::VertexState {
            module: config.shader,
            entry_point: Some("vs_main"),
            buffers: &[wgpu::VertexBufferLayout {
                array_stride: std::mem::size_of::<SpriteVertex>() as wgpu::BufferAddress,
                step_mode: wgpu::VertexStepMode::Vertex,
                attributes: &[
                    wgpu::VertexAttribute {
                        offset: 0,
                        shader_location: 0,
                        format: wgpu::VertexFormat::Float32x2,
                    },
                    wgpu::VertexAttribute {
                        offset: std::mem::size_of::<Vec2>() as wgpu::BufferAddress,
                        shader_location: 1,
                        format: wgpu::VertexFormat::Float32x2,
                    },
                ],
            }],
            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: config.depth_stencil.clone(),
        multisample: wgpu::MultisampleState {
            count: 1,
            mask: !0,
            alpha_to_coverage_enabled: false,
        },
        fragment: Some(wgpu::FragmentState {
            module: config.shader,
            entry_point: Some(config.fragment_entry),
            targets: &[Some(wgpu::ColorTargetState {
                format: config.color_format,
                blend: Some(config.blend_state),
                write_mask: config.color_write_mask,
            })],
            compilation_options: wgpu::PipelineCompilationOptions::default(),
        }),
        multiview: None,
        cache: None,
    })
}

fn stencil_depth_state_normal() -> wgpu::DepthStencilState {
    wgpu::DepthStencilState {
        format: wgpu::TextureFormat::Depth24PlusStencil8,
        depth_write_enabled: false,
        depth_compare: wgpu::CompareFunction::Always,
        stencil: wgpu::StencilState {
            front: wgpu::StencilFaceState {
                compare: wgpu::CompareFunction::Always,
                fail_op: wgpu::StencilOperation::Keep,
                depth_fail_op: wgpu::StencilOperation::Keep,
                pass_op: wgpu::StencilOperation::Keep,
            },
            back: wgpu::StencilFaceState {
                compare: wgpu::CompareFunction::Always,
                fail_op: wgpu::StencilOperation::Keep,
                depth_fail_op: wgpu::StencilOperation::Keep,
                pass_op: wgpu::StencilOperation::Keep,
            },
            read_mask: 0,
            write_mask: 0,
        },
        bias: wgpu::DepthBiasState::default(),
    }
}

fn stencil_depth_state_write() -> wgpu::DepthStencilState {
    wgpu::DepthStencilState {
        format: wgpu::TextureFormat::Depth24PlusStencil8,
        depth_write_enabled: false,
        depth_compare: wgpu::CompareFunction::Always,
        stencil: wgpu::StencilState {
            front: wgpu::StencilFaceState {
                compare: wgpu::CompareFunction::Always,
                fail_op: wgpu::StencilOperation::Keep,
                depth_fail_op: wgpu::StencilOperation::Keep,
                pass_op: wgpu::StencilOperation::Replace,
            },
            back: wgpu::StencilFaceState {
                compare: wgpu::CompareFunction::Always,
                fail_op: wgpu::StencilOperation::Keep,
                depth_fail_op: wgpu::StencilOperation::Keep,
                pass_op: wgpu::StencilOperation::Replace,
            },
            read_mask: 0xFF,
            write_mask: 0xFF,
        },
        bias: wgpu::DepthBiasState::default(),
    }
}

fn stencil_depth_state_test() -> wgpu::DepthStencilState {
    wgpu::DepthStencilState {
        format: wgpu::TextureFormat::Depth24PlusStencil8,
        depth_write_enabled: false,
        depth_compare: wgpu::CompareFunction::Always,
        stencil: wgpu::StencilState {
            front: wgpu::StencilFaceState {
                compare: wgpu::CompareFunction::Equal,
                fail_op: wgpu::StencilOperation::Keep,
                depth_fail_op: wgpu::StencilOperation::Keep,
                pass_op: wgpu::StencilOperation::Keep,
            },
            back: wgpu::StencilFaceState {
                compare: wgpu::CompareFunction::Equal,
                fail_op: wgpu::StencilOperation::Keep,
                depth_fail_op: wgpu::StencilOperation::Keep,
                pass_op: wgpu::StencilOperation::Keep,
            },
            read_mask: 0xFF,
            write_mask: 0,
        },
        bias: wgpu::DepthBiasState::default(),
    }
}

impl SpritePass {
    pub fn new(
        device: &wgpu::Device,
        color_format: wgpu::TextureFormat,
        queue: &wgpu::Queue,
    ) -> Self {
        let max_instances = 1_000_000;

        let mut atlas = SpriteTextureAtlas::new(
            device,
            crate::render::wgpu::sprite_texture_atlas::SPRITE_ATLAS_TOTAL_SLOTS,
            crate::render::wgpu::sprite_texture_atlas::SPRITE_ATLAS_SLOT_SIZE,
        );

        let total_pixels = (atlas.atlas_size.0 * atlas.atlas_size.1 * 4) as usize;
        let white_texture = vec![255u8; total_pixels];
        queue.write_texture(
            atlas.texture.as_image_copy(),
            &white_texture,
            wgpu::TexelCopyBufferLayout {
                offset: 0,
                bytes_per_row: Some(atlas.atlas_size.0 * 4),
                rows_per_image: Some(atlas.atlas_size.1),
            },
            wgpu::Extent3d {
                width: atlas.atlas_size.0,
                height: atlas.atlas_size.1,
                depth_or_array_layers: 1,
            },
        );

        let uniform_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Sprite 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: wgpu::BufferSize::new(std::mem::size_of::<
                                GlobalUniforms,
                            >()
                                as u64),
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 1,
                        visibility: wgpu::ShaderStages::VERTEX,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: true },
                            has_dynamic_offset: false,
                            min_binding_size: wgpu::BufferSize::new(std::mem::size_of::<
                                SpriteInstance,
                            >()
                                as u64),
                        },
                        count: None,
                    },
                ],
            });

        let texture_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Sprite 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 background_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Sprite Background 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 standard_pipeline_layout =
            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: Some("Sprite Pipeline Layout"),
                bind_group_layouts: &[&uniform_bind_group_layout, &texture_bind_group_layout],
                push_constant_ranges: &[],
            });

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

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

        let vertices = [
            SpriteVertex {
                offset: Vec2::new(-0.5, -0.5),
                uv: Vec2::new(0.0, 1.0),
            },
            SpriteVertex {
                offset: Vec2::new(0.5, -0.5),
                uv: Vec2::new(1.0, 1.0),
            },
            SpriteVertex {
                offset: Vec2::new(0.5, 0.5),
                uv: Vec2::new(1.0, 0.0),
            },
            SpriteVertex {
                offset: Vec2::new(-0.5, 0.5),
                uv: Vec2::new(0.0, 0.0),
            },
        ];
        let indices: [u16; 6] = [0, 1, 2, 0, 2, 3];

        let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
            label: Some("Sprite Vertex Buffer"),
            contents: bytemuck::cast_slice(&vertices),
            usage: wgpu::BufferUsages::VERTEX,
        });

        let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
            label: Some("Sprite Index Buffer"),
            contents: bytemuck::cast_slice(&indices),
            usage: wgpu::BufferUsages::INDEX,
        });

        let additive_blend = wgpu::BlendState {
            color: wgpu::BlendComponent {
                src_factor: wgpu::BlendFactor::SrcAlpha,
                dst_factor: wgpu::BlendFactor::One,
                operation: wgpu::BlendOperation::Add,
            },
            alpha: wgpu::BlendComponent {
                src_factor: wgpu::BlendFactor::One,
                dst_factor: wgpu::BlendFactor::One,
                operation: wgpu::BlendOperation::Add,
            },
        };

        let multiply_blend = wgpu::BlendState {
            color: wgpu::BlendComponent {
                src_factor: wgpu::BlendFactor::Dst,
                dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
                operation: wgpu::BlendOperation::Add,
            },
            alpha: wgpu::BlendComponent {
                src_factor: wgpu::BlendFactor::One,
                dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
                operation: wgpu::BlendOperation::Add,
            },
        };

        let screen_blend = wgpu::BlendState {
            color: wgpu::BlendComponent {
                src_factor: wgpu::BlendFactor::One,
                dst_factor: wgpu::BlendFactor::OneMinusSrc,
                operation: wgpu::BlendOperation::Add,
            },
            alpha: wgpu::BlendComponent {
                src_factor: wgpu::BlendFactor::One,
                dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
                operation: wgpu::BlendOperation::Add,
            },
        };

        let blend_states = [
            wgpu::BlendState::ALPHA_BLENDING,
            additive_blend,
            multiply_blend,
            screen_blend,
        ];
        let blend_labels = ["Alpha", "Additive", "Multiply", "Screen"];

        let pipelines_normal = std::array::from_fn(|blend_index| {
            create_sprite_pipeline(
                device,
                &SpritePipelineConfig {
                    layout: &standard_pipeline_layout,
                    shader: &shader,
                    color_format,
                    blend_state: blend_states[blend_index],
                    depth_stencil: Some(stencil_depth_state_normal()),
                    color_write_mask: wgpu::ColorWrites::ALL,
                    label: &format!("Sprite Pipeline Normal ({})", blend_labels[blend_index]),
                    fragment_entry: "fs_main",
                },
            )
        });

        let pipelines_stencil_write = std::array::from_fn(|blend_index| {
            create_sprite_pipeline(
                device,
                &SpritePipelineConfig {
                    layout: &standard_pipeline_layout,
                    shader: &shader,
                    color_format,
                    blend_state: blend_states[blend_index],
                    depth_stencil: Some(stencil_depth_state_write()),
                    color_write_mask: wgpu::ColorWrites::empty(),
                    label: &format!(
                        "Sprite Pipeline Stencil Write ({})",
                        blend_labels[blend_index]
                    ),
                    fragment_entry: "fs_main",
                },
            )
        });

        let pipelines_stencil_test = std::array::from_fn(|blend_index| {
            create_sprite_pipeline(
                device,
                &SpritePipelineConfig {
                    layout: &standard_pipeline_layout,
                    shader: &shader,
                    color_format,
                    blend_state: blend_states[blend_index],
                    depth_stencil: Some(stencil_depth_state_test()),
                    color_write_mask: wgpu::ColorWrites::ALL,
                    label: &format!(
                        "Sprite Pipeline Stencil Test ({})",
                        blend_labels[blend_index]
                    ),
                    fragment_entry: "fs_main",
                },
            )
        });

        let pipeline_advanced_normal = create_sprite_pipeline(
            device,
            &SpritePipelineConfig {
                layout: &advanced_pipeline_layout,
                shader: &shader,
                color_format,
                blend_state: wgpu::BlendState::REPLACE,
                depth_stencil: Some(stencil_depth_state_normal()),
                color_write_mask: wgpu::ColorWrites::ALL,
                label: "Sprite Pipeline Advanced Normal",
                fragment_entry: "fs_main_advanced",
            },
        );

        let pipeline_advanced_stencil_test = create_sprite_pipeline(
            device,
            &SpritePipelineConfig {
                layout: &advanced_pipeline_layout,
                shader: &shader,
                color_format,
                blend_state: wgpu::BlendState::REPLACE,
                depth_stencil: Some(stencil_depth_state_test()),
                color_write_mask: wgpu::ColorWrites::ALL,
                label: "Sprite Pipeline Advanced Stencil Test",
                fragment_entry: "fs_main_advanced",
            },
        );

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

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

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

        let initial_stencil_size = (1u32, 1u32);
        let stencil_texture = device.create_texture(&wgpu::TextureDescriptor {
            label: Some("Sprite Stencil Texture"),
            size: wgpu::Extent3d {
                width: initial_stencil_size.0,
                height: initial_stencil_size.1,
                depth_or_array_layers: 1,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: wgpu::TextureFormat::Depth24PlusStencil8,
            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
            view_formats: &[],
        });
        let stencil_view = stencil_texture.create_view(&wgpu::TextureViewDescriptor::default());

        let initial_bg_size = (1u32, 1u32);
        let background_texture = device.create_texture(&wgpu::TextureDescriptor {
            label: Some("Sprite Background Texture"),
            size: wgpu::Extent3d {
                width: initial_bg_size.0,
                height: initial_bg_size.1,
                depth_or_array_layers: 1,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: wgpu::TextureFormat::Rgba16Float,
            usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING,
            view_formats: &[],
        });
        let background_view =
            background_texture.create_view(&wgpu::TextureViewDescriptor::default());

        let background_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
            label: Some("Sprite Background Sampler"),
            mag_filter: wgpu::FilterMode::Linear,
            min_filter: wgpu::FilterMode::Linear,
            ..Default::default()
        });

        let sprite_font_atlas = rasterize_sprite_font(queue, &mut atlas);
        crate::render::wgpu::sprite_shapes::upload_builtin_shapes(queue, &mut atlas);

        let mut sprite_pass = Self {
            pipelines_normal,
            pipelines_stencil_write,
            pipelines_stencil_test,
            pipeline_advanced_normal,
            pipeline_advanced_stencil_test,
            global_uniform_buffer,
            instance_buffer,
            vertex_buffer,
            index_buffer,
            uniform_bind_group,
            texture_bind_group_layout,
            texture_bind_group: None,
            background_bind_group_layout,
            background_texture,
            background_view,
            background_sampler,
            background_bind_group: None,
            background_texture_size: initial_bg_size,
            max_instances,
            instance_data: Vec::new(),
            blend_modes: Vec::new(),
            stencil_modes: Vec::new(),
            stencil_references: Vec::new(),
            clip_rects: Vec::new(),
            draw_batches: Vec::new(),
            atlas,
            stencil_texture,
            stencil_view,
            stencil_texture_size: initial_stencil_size,
            cached_view_projection: None,
            cached_surface_width: 0,
            cached_surface_height: 0,
            sort_indices: Vec::new(),
            sorted_instances_scratch: Vec::new(),
            sorted_blend_modes_scratch: Vec::new(),
            sorted_stencil_modes_scratch: Vec::new(),
            sorted_stencil_references_scratch: Vec::new(),
            sorted_clip_rects_scratch: Vec::new(),
            sprite_font_atlas,
            sprite_text_cache: HashMap::new(),
        };

        sprite_pass.update_texture_bind_group(device);
        sprite_pass
    }

    fn update_texture_bind_group(&mut self, device: &wgpu::Device) {
        self.texture_bind_group = Some(device.create_bind_group(&wgpu::BindGroupDescriptor {
            layout: &self.texture_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: wgpu::BindingResource::TextureView(&self.atlas.view),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: wgpu::BindingResource::Sampler(&self.atlas.sampler),
                },
            ],
            label: Some("Sprite Texture Atlas Bind Group"),
        }));
    }

    fn update_background_bind_group(&mut self, device: &wgpu::Device) {
        self.background_bind_group = Some(device.create_bind_group(&wgpu::BindGroupDescriptor {
            layout: &self.background_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: wgpu::BindingResource::TextureView(&self.background_view),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: wgpu::BindingResource::Sampler(&self.background_sampler),
                },
            ],
            label: Some("Sprite Background Bind Group"),
        }));
    }

    pub fn atlas_view(&self) -> &wgpu::TextureView {
        &self.atlas.view
    }

    pub fn atlas_sampler(&self) -> &wgpu::Sampler {
        &self.atlas.sampler
    }

    pub fn atlas_slots_per_row(&self) -> u32 {
        self.atlas.slots_per_row
    }

    pub fn upload_texture_to_atlas(
        &mut self,
        queue: &wgpu::Queue,
        slot: u32,
        rgba_data: &[u8],
        width: u32,
        height: u32,
    ) {
        self.atlas
            .upload_texture(queue, slot, rgba_data, width, height);
    }

    fn pipeline_for_batch(
        &self,
        blend_mode: SpriteBlendMode,
        stencil_mode: SpriteStencilMode,
    ) -> &wgpu::RenderPipeline {
        if blend_mode.is_advanced() {
            return match stencil_mode {
                SpriteStencilMode::Test => &self.pipeline_advanced_stencil_test,
                _ => &self.pipeline_advanced_normal,
            };
        }

        let blend_index = match blend_mode {
            SpriteBlendMode::Alpha => 0,
            SpriteBlendMode::Additive => 1,
            SpriteBlendMode::Multiply => 2,
            SpriteBlendMode::Screen => 3,
            _ => 0,
        };
        match stencil_mode {
            SpriteStencilMode::None => &self.pipelines_normal[blend_index],
            SpriteStencilMode::Write => &self.pipelines_stencil_write[blend_index],
            SpriteStencilMode::Test => &self.pipelines_stencil_test[blend_index],
        }
    }

    fn gradient_instance_fields(gradient: &SpriteGradient) -> (u32, f32, f32) {
        match gradient {
            SpriteGradient::None => (0, 0.0, 0.0),
            SpriteGradient::Linear { angle } => (1, angle.sin(), angle.cos()),
            SpriteGradient::Radial { center } => (2, center.x, center.y),
        }
    }

    fn emit_sprite_instance(&mut self, sprite: &crate::ecs::sprite::components::Sprite) {
        let (sin_r, cos_r) = if sprite.rotation != 0.0 {
            (sprite.rotation.sin(), sprite.rotation.cos())
        } else {
            (0.0, 1.0)
        };

        let scale_x = sprite.scale.x * if sprite.flip_x { -1.0 } else { 1.0 };
        let scale_y = sprite.scale.y * if sprite.flip_y { -1.0 } else { 1.0 };

        let (gradient_type, gradient_param_a, gradient_param_b) =
            Self::gradient_instance_fields(&sprite.gradient);
        let advanced_blend_mode = sprite.blend_mode.advanced_blend_index();

        if let Some(ref nine_slice) = sprite.nine_slice {
            self.emit_nine_slice_instances(
                sprite,
                nine_slice,
                cos_r,
                sin_r,
                scale_x,
                scale_y,
                gradient_type,
                gradient_param_a,
                gradient_param_b,
                advanced_blend_mode,
            );
        } else {
            self.instance_data.push(SpriteInstance {
                position: sprite.position,
                size: sprite.size,
                uv_min: sprite.uv_min,
                uv_max: sprite.uv_max,
                color: Vec4::from_column_slice(&sprite.color),
                rotation_scale: Vec4::new(cos_r, sin_r, scale_x, scale_y),
                anchor: sprite.anchor,
                depth: sprite.depth,
                texture_slot: sprite.texture_index,
                texture_slot2: sprite.texture_index2,
                blend_factor: sprite.blend_factor,
                gradient_type,
                gradient_param_a,
                gradient_param_b,
                advanced_blend_mode,
                _padding: [0.0; 2],
            });
            self.blend_modes.push(sprite.blend_mode);
            self.stencil_modes.push(sprite.stencil_mode);
            self.stencil_references.push(sprite.stencil_reference);
            self.clip_rects.push(sprite.clip_rect);
        }
    }

    #[allow(clippy::too_many_arguments)]
    fn emit_nine_slice_instances(
        &mut self,
        sprite: &crate::ecs::sprite::components::Sprite,
        nine_slice: &crate::ecs::sprite::components::NineSlice,
        cos_r: f32,
        sin_r: f32,
        scale_x: f32,
        scale_y: f32,
        gradient_type: u32,
        gradient_param_a: f32,
        gradient_param_b: f32,
        advanced_blend_mode: u32,
    ) {
        let total_width = sprite.size.x;
        let total_height = sprite.size.y;
        let center_width = (total_width - nine_slice.left - nine_slice.right).max(0.0);
        let center_height = (total_height - nine_slice.top - nine_slice.bottom).max(0.0);

        let uv_range = sprite.uv_max - sprite.uv_min;
        let left_uv_frac = if total_width > 0.0 {
            nine_slice.left / total_width
        } else {
            0.0
        };
        let right_uv_frac = if total_width > 0.0 {
            nine_slice.right / total_width
        } else {
            0.0
        };
        let bottom_uv_frac = if total_height > 0.0 {
            nine_slice.bottom / total_height
        } else {
            0.0
        };
        let top_uv_frac = if total_height > 0.0 {
            nine_slice.top / total_height
        } else {
            0.0
        };

        let uv_cols = [
            sprite.uv_min.x,
            sprite.uv_min.x + uv_range.x * left_uv_frac,
            sprite.uv_max.x - uv_range.x * right_uv_frac,
            sprite.uv_max.x,
        ];
        let uv_rows = [
            sprite.uv_min.y,
            sprite.uv_min.y + uv_range.y * bottom_uv_frac,
            sprite.uv_max.y - uv_range.y * top_uv_frac,
            sprite.uv_max.y,
        ];

        let widths = [nine_slice.left, center_width, nine_slice.right];
        let heights = [nine_slice.bottom, center_height, nine_slice.top];

        let anchor_offset_x = sprite.anchor.x * total_width * scale_x;
        let anchor_offset_y = sprite.anchor.y * total_height * scale_y;

        let base_x = -total_width * 0.5;
        let base_y = -total_height * 0.5;

        let color = Vec4::from_column_slice(&sprite.color);

        let mut accumulated_y = 0.0f32;
        for row in 0..3 {
            let piece_height = heights[row];
            if piece_height <= 0.0 {
                continue;
            }
            let mut accumulated_x = 0.0f32;
            for col in 0..3 {
                let piece_width = widths[col];
                if piece_width <= 0.0 {
                    continue;
                }

                let local_center_x = base_x + accumulated_x + piece_width * 0.5;
                let local_center_y = base_y + accumulated_y + piece_height * 0.5;

                let scaled_cx = local_center_x * scale_x - anchor_offset_x;
                let scaled_cy = local_center_y * scale_y - anchor_offset_y;

                let rotated_x = scaled_cx * cos_r - scaled_cy * sin_r;
                let rotated_y = scaled_cx * sin_r + scaled_cy * cos_r;

                let position =
                    Vec2::new(sprite.position.x + rotated_x, sprite.position.y + rotated_y);

                let size = Vec2::new(piece_width, piece_height);

                let piece_uv_min = Vec2::new(uv_cols[col], uv_rows[row]);
                let piece_uv_max = Vec2::new(uv_cols[col + 1], uv_rows[row + 1]);

                self.instance_data.push(SpriteInstance {
                    position,
                    size,
                    uv_min: piece_uv_min,
                    uv_max: piece_uv_max,
                    color,
                    rotation_scale: Vec4::new(cos_r, sin_r, scale_x, scale_y),
                    anchor: Vec2::new(0.0, 0.0),
                    depth: sprite.depth,
                    texture_slot: sprite.texture_index,
                    texture_slot2: sprite.texture_index2,
                    blend_factor: sprite.blend_factor,
                    gradient_type,
                    gradient_param_a,
                    gradient_param_b,
                    advanced_blend_mode,
                    _padding: [0.0; 2],
                });
                self.blend_modes.push(sprite.blend_mode);
                self.stencil_modes.push(sprite.stencil_mode);
                self.stencil_references.push(sprite.stencil_reference);
                self.clip_rects.push(sprite.clip_rect);

                accumulated_x += piece_width;
            }
            accumulated_y += piece_height;
        }
    }

    fn prepare_sprites(&mut self, world: &World, surface_width: u32, surface_height: u32) {
        self.instance_data.clear();
        self.blend_modes.clear();
        self.stencil_modes.clear();
        self.stencil_references.clear();
        self.clip_rects.clear();
        self.draw_batches.clear();
        self.cached_view_projection = None;
        self.cached_surface_width = surface_width;
        self.cached_surface_height = surface_height;

        let width = surface_width as f32;
        let height = surface_height as f32;

        let camera_matrices = query_active_camera_matrices(world);

        let view_projection = if let Some(ref matrices) = camera_matrices {
            let vp = matrices.projection * matrices.view;
            self.cached_view_projection = Some(vp);
            vp
        } else {
            let vp = nalgebra_glm::ortho_rh_zo(0.0, width, 0.0, height, -1.0, 1.0);
            self.cached_view_projection = Some(vp);
            vp
        };

        let cull_bounds = camera_matrices.as_ref().map(|matrices| {
            let camera_pos = matrices.camera_position;
            let half_extent = world
                .resources
                .active_camera
                .and_then(|entity| world.get_camera(entity))
                .map(|camera| match &camera.projection {
                    crate::ecs::camera::components::Projection::Orthographic(ortho) => {
                        Vec2::new(ortho.x_mag, ortho.y_mag)
                    }
                    crate::ecs::camera::components::Projection::Perspective(persp) => {
                        let distance = camera_pos.z.abs();
                        let half_height = distance * (persp.y_fov_rad / 2.0).tan();
                        let aspect = persp.aspect_ratio.unwrap_or(width / height);
                        let half_width = half_height * aspect;
                        Vec2::new(half_width, half_height)
                    }
                })
                .unwrap_or_else(|| Vec2::new(width * 0.5, height * 0.5));

            let min_x = camera_pos.x - half_extent.x;
            let max_x = camera_pos.x + half_extent.x;
            let min_y = camera_pos.y - half_extent.y;
            let max_y = camera_pos.y + half_extent.y;
            (min_x, max_x, min_y, max_y)
        });

        let sprite_entities: Vec<_> = world
            .query_entities(crate::ecs::SPRITE | crate::ecs::VISIBILITY)
            .collect();

        for entity in sprite_entities {
            let Some(sprite) = world.get_sprite(entity) else {
                continue;
            };
            let Some(visibility) = world.get_visibility(entity) else {
                continue;
            };
            if !visibility.visible {
                continue;
            }

            let render_layer = world
                .get_render_layer(entity)
                .map(|layer| layer.0)
                .unwrap_or(crate::ecs::render_layer::components::RenderLayer::WORLD);

            let should_render = match render_layer {
                crate::ecs::render_layer::components::RenderLayer::WORLD => {
                    world.resources.graphics.render_layer_world_enabled
                }
                crate::ecs::render_layer::components::RenderLayer::OVERLAY => {
                    world.resources.graphics.render_layer_overlay_enabled
                }
                _ => true,
            };
            if !should_render {
                continue;
            }

            if let Some((min_x, max_x, min_y, max_y)) = cull_bounds {
                let scaled_size = sprite.size.component_mul(&sprite.scale);
                let half_size = scaled_size * 0.5;

                if sprite.position.x + half_size.x < min_x
                    || sprite.position.x - half_size.x > max_x
                    || sprite.position.y + half_size.y < min_y
                    || sprite.position.y - half_size.y > max_y
                {
                    continue;
                }
            }

            if self.instance_data.len() >= self.max_instances {
                break;
            }

            self.emit_sprite_instance(sprite);
        }

        self.prepare_tilemaps(world, cull_bounds);
        self.prepare_sprite_text(world, cull_bounds);

        self.sort_and_build_batches();

        let _ = view_projection;
    }

    fn prepare_tilemaps(&mut self, world: &World, cull_bounds: Option<(f32, f32, f32, f32)>) {
        let tilemap_entities: Vec<_> = world
            .query_entities(crate::ecs::TILEMAP | crate::ecs::VISIBILITY)
            .collect();

        for entity in tilemap_entities {
            let Some(tilemap) = world.get_tilemap(entity) else {
                continue;
            };
            let Some(visibility) = world.get_visibility(entity) else {
                continue;
            };
            if !visibility.visible {
                continue;
            }

            let render_layer = world
                .get_render_layer(entity)
                .map(|layer| layer.0)
                .unwrap_or(crate::ecs::render_layer::components::RenderLayer::WORLD);

            let should_render = match render_layer {
                crate::ecs::render_layer::components::RenderLayer::WORLD => {
                    world.resources.graphics.render_layer_world_enabled
                }
                crate::ecs::render_layer::components::RenderLayer::OVERLAY => {
                    world.resources.graphics.render_layer_overlay_enabled
                }
                _ => true,
            };
            if !should_render {
                continue;
            }

            let (col_start, col_end, row_start, row_end) =
                if let Some((min_x, max_x, min_y, max_y)) = cull_bounds {
                    let cs = ((min_x - tilemap.position.x) / tilemap.tile_size.x)
                        .floor()
                        .max(0.0) as u32;
                    let ce = ((max_x - tilemap.position.x) / tilemap.tile_size.x)
                        .ceil()
                        .min(tilemap.grid_width as f32) as u32;
                    let rs = ((min_y - tilemap.position.y) / tilemap.tile_size.y)
                        .floor()
                        .max(0.0) as u32;
                    let re = ((max_y - tilemap.position.y) / tilemap.tile_size.y)
                        .ceil()
                        .min(tilemap.grid_height as f32) as u32;
                    (cs, ce, rs, re)
                } else {
                    (0, tilemap.grid_width, 0, tilemap.grid_height)
                };

            let sheet_tile_uv_width = if tilemap.sheet_columns > 0 {
                tilemap.uv_max.x / tilemap.sheet_columns as f32
            } else {
                tilemap.uv_max.x
            };
            let sheet_tile_uv_height = if tilemap.sheet_rows > 0 {
                tilemap.uv_max.y / tilemap.sheet_rows as f32
            } else {
                tilemap.uv_max.y
            };

            let color = Vec4::from_column_slice(&tilemap.color);

            for row in row_start..row_end {
                for col in col_start..col_end {
                    let tile_index = (row * tilemap.grid_width + col) as usize;
                    if tile_index >= tilemap.tiles.len() {
                        continue;
                    }

                    let Some(ref tile_data) = tilemap.tiles[tile_index] else {
                        continue;
                    };

                    if self.instance_data.len() >= self.max_instances {
                        return;
                    }

                    let sheet_col = tile_data.tile_id % tilemap.sheet_columns;
                    let sheet_row = tile_data.tile_id / tilemap.sheet_columns;

                    let half_texel_x = sheet_tile_uv_width * 0.001;
                    let half_texel_y = sheet_tile_uv_height * 0.001;

                    let uv_min = Vec2::new(
                        sheet_col as f32 * sheet_tile_uv_width + half_texel_x,
                        sheet_row as f32 * sheet_tile_uv_height + half_texel_y,
                    );
                    let uv_max = Vec2::new(
                        (sheet_col + 1) as f32 * sheet_tile_uv_width - half_texel_x,
                        (sheet_row + 1) as f32 * sheet_tile_uv_height - half_texel_y,
                    );

                    let position = Vec2::new(
                        tilemap.position.x
                            + col as f32 * tilemap.tile_size.x
                            + tilemap.tile_size.x * 0.5,
                        tilemap.position.y
                            + row as f32 * tilemap.tile_size.y
                            + tilemap.tile_size.y * 0.5,
                    );

                    let flip_scale_x = if tile_data.flip_x { -1.0 } else { 1.0 };
                    let flip_scale_y = if tile_data.flip_y { -1.0 } else { 1.0 };

                    self.instance_data.push(SpriteInstance {
                        position,
                        size: tilemap.tile_size,
                        uv_min,
                        uv_max,
                        color,
                        rotation_scale: Vec4::new(1.0, 0.0, flip_scale_x, flip_scale_y),
                        anchor: Vec2::new(0.0, 0.0),
                        depth: tilemap.depth,
                        texture_slot: tilemap.texture_index,
                        texture_slot2: tilemap.texture_index,
                        blend_factor: 0.0,
                        gradient_type: 0,
                        gradient_param_a: 0.0,
                        gradient_param_b: 0.0,
                        advanced_blend_mode: 0,
                        _padding: [0.0; 2],
                    });
                    self.blend_modes.push(SpriteBlendMode::Alpha);
                    self.stencil_modes.push(SpriteStencilMode::None);
                    self.stencil_references.push(1);
                    self.clip_rects.push(None);
                }
            }
        }
    }

    fn prepare_sprite_text(&mut self, world: &World, cull_bounds: Option<(f32, f32, f32, f32)>) {
        let sprite_text_entities: Vec<_> = world
            .query_entities(crate::ecs::SPRITE_TEXT | crate::ecs::VISIBILITY)
            .collect();

        let mut active_entities = std::collections::HashSet::new();

        for entity in &sprite_text_entities {
            active_entities.insert(*entity);

            let Some(sprite_text) = world.get_sprite_text(*entity) else {
                continue;
            };
            let Some(visibility) = world.get_visibility(*entity) else {
                continue;
            };
            if !visibility.visible {
                continue;
            }

            let render_layer = world
                .get_render_layer(*entity)
                .map(|layer| layer.0)
                .unwrap_or(crate::ecs::render_layer::components::RenderLayer::WORLD);

            let should_render = match render_layer {
                crate::ecs::render_layer::components::RenderLayer::WORLD => {
                    world.resources.graphics.render_layer_world_enabled
                }
                crate::ecs::render_layer::components::RenderLayer::OVERLAY => {
                    world.resources.graphics.render_layer_overlay_enabled
                }
                _ => true,
            };
            if !should_render {
                continue;
            }

            let needs_rebuild = match self.sprite_text_cache.get(entity) {
                None => true,
                Some(cached) => {
                    cached.text != sprite_text.text
                        || cached.position != sprite_text.position
                        || cached.depth != sprite_text.depth
                        || cached.font_size != sprite_text.font_size
                        || cached.color != sprite_text.color
                        || cached.alignment != sprite_text.alignment
                }
            };

            if needs_rebuild {
                let instances = self.layout_sprite_text(sprite_text);
                self.sprite_text_cache.insert(
                    *entity,
                    CachedSpriteText {
                        instances,
                        text: sprite_text.text.clone(),
                        position: sprite_text.position,
                        depth: sprite_text.depth,
                        font_size: sprite_text.font_size,
                        color: sprite_text.color,
                        alignment: sprite_text.alignment,
                    },
                );
            }

            if let Some(cached) = self.sprite_text_cache.get(entity) {
                if cached.instances.is_empty() {
                    continue;
                }

                if let Some((min_x, max_x, min_y, max_y)) = cull_bounds {
                    let scale = sprite_text.font_size / self.sprite_font_atlas.rasterization_scale;
                    let line_height = self.sprite_font_atlas.line_height * scale;
                    let total_width = self.compute_text_width(sprite_text);
                    let half_width = total_width * 0.5;
                    let half_height = line_height * 0.5;

                    let center_x = match sprite_text.alignment {
                        SpriteTextAlignment::Left => sprite_text.position.x + half_width,
                        SpriteTextAlignment::Center => sprite_text.position.x,
                        SpriteTextAlignment::Right => sprite_text.position.x - half_width,
                    };
                    let center_y = sprite_text.position.y;

                    if center_x + half_width < min_x
                        || center_x - half_width > max_x
                        || center_y + half_height < min_y
                        || center_y - half_height > max_y
                    {
                        continue;
                    }
                }

                for instance in &cached.instances {
                    if self.instance_data.len() >= self.max_instances {
                        return;
                    }
                    self.instance_data.push(*instance);
                    self.blend_modes.push(SpriteBlendMode::Alpha);
                    self.stencil_modes.push(SpriteStencilMode::None);
                    self.stencil_references.push(1);
                    self.clip_rects.push(None);
                }
            }
        }

        self.sprite_text_cache
            .retain(|entity, _| active_entities.contains(entity));
    }

    fn compute_text_width(
        &self,
        sprite_text: &crate::ecs::sprite_text::components::SpriteText,
    ) -> f32 {
        let scale = sprite_text.font_size / self.sprite_font_atlas.rasterization_scale;
        let mut width = 0.0f32;
        for character in sprite_text.text.chars() {
            if let Some(glyph) = self.sprite_font_atlas.glyphs.get(&character) {
                width += glyph.advance * scale;
            }
        }
        width
    }

    fn layout_sprite_text(
        &self,
        sprite_text: &crate::ecs::sprite_text::components::SpriteText,
    ) -> Vec<SpriteInstance> {
        let mut instances = Vec::new();
        let scale = sprite_text.font_size / self.sprite_font_atlas.rasterization_scale;

        let total_width = self.compute_text_width(sprite_text);

        let start_x = match sprite_text.alignment {
            SpriteTextAlignment::Left => sprite_text.position.x,
            SpriteTextAlignment::Center => sprite_text.position.x - total_width * 0.5,
            SpriteTextAlignment::Right => sprite_text.position.x - total_width,
        };

        let color = Vec4::from_column_slice(&sprite_text.color);
        let mut cursor_x = start_x;

        for character in sprite_text.text.chars() {
            let Some(glyph) = self.sprite_font_atlas.glyphs.get(&character) else {
                continue;
            };

            if glyph.size.x > 0.0 && glyph.size.y > 0.0 {
                let glyph_width = glyph.size.x * scale;
                let glyph_height = glyph.size.y * scale;

                let glyph_x = cursor_x + glyph.bearing.x * scale + glyph_width * 0.5;
                let glyph_y = sprite_text.position.y + glyph.bearing.y * scale + glyph_height * 0.5;

                instances.push(SpriteInstance {
                    position: Vec2::new(glyph_x, glyph_y),
                    size: Vec2::new(glyph_width, glyph_height),
                    uv_min: glyph.uv_min,
                    uv_max: glyph.uv_max,
                    color,
                    rotation_scale: Vec4::new(1.0, 0.0, 1.0, 1.0),
                    anchor: Vec2::new(0.0, 0.0),
                    depth: sprite_text.depth,
                    texture_slot: self.sprite_font_atlas.atlas_slot,
                    texture_slot2: self.sprite_font_atlas.atlas_slot,
                    blend_factor: 0.0,
                    gradient_type: 0,
                    gradient_param_a: 0.0,
                    gradient_param_b: 0.0,
                    advanced_blend_mode: 0,
                    _padding: [0.0; 2],
                });
            }

            cursor_x += glyph.advance * scale;
        }

        instances
    }

    fn stencil_sort_priority(mode: SpriteStencilMode) -> u8 {
        match mode {
            SpriteStencilMode::Write => 0,
            SpriteStencilMode::Test => 1,
            SpriteStencilMode::None => 1,
        }
    }

    fn sort_and_build_batches(&mut self) {
        if self.instance_data.is_empty() {
            return;
        }

        self.sort_indices.clear();
        self.sort_indices.extend(0..self.instance_data.len());
        self.sort_indices.sort_unstable_by(|&a, &b| {
            let stencil_a = Self::stencil_sort_priority(self.stencil_modes[a]);
            let stencil_b = Self::stencil_sort_priority(self.stencil_modes[b]);
            stencil_a
                .cmp(&stencil_b)
                .then_with(|| self.stencil_references[a].cmp(&self.stencil_references[b]))
                .then_with(|| {
                    self.instance_data[a]
                        .depth
                        .total_cmp(&self.instance_data[b].depth)
                })
        });

        self.sorted_instances_scratch.clear();
        self.sorted_blend_modes_scratch.clear();
        self.sorted_stencil_modes_scratch.clear();
        self.sorted_stencil_references_scratch.clear();
        self.sorted_clip_rects_scratch.clear();
        for &index in &self.sort_indices {
            self.sorted_instances_scratch
                .push(self.instance_data[index]);
            self.sorted_blend_modes_scratch
                .push(self.blend_modes[index]);
            self.sorted_stencil_modes_scratch
                .push(self.stencil_modes[index]);
            self.sorted_stencil_references_scratch
                .push(self.stencil_references[index]);
            self.sorted_clip_rects_scratch.push(self.clip_rects[index]);
        }
        std::mem::swap(&mut self.instance_data, &mut self.sorted_instances_scratch);
        std::mem::swap(&mut self.blend_modes, &mut self.sorted_blend_modes_scratch);
        std::mem::swap(
            &mut self.stencil_modes,
            &mut self.sorted_stencil_modes_scratch,
        );
        std::mem::swap(
            &mut self.stencil_references,
            &mut self.sorted_stencil_references_scratch,
        );
        std::mem::swap(&mut self.clip_rects, &mut self.sorted_clip_rects_scratch);

        let mut current_blend = self.blend_modes[0];
        let mut current_stencil = self.stencil_modes[0];
        let mut current_stencil_ref = self.stencil_references[0];
        let mut current_clip = self.clip_rects[0];
        let mut batch_start = 0u32;

        for index in 1..self.blend_modes.len() {
            if self.blend_modes[index] != current_blend
                || self.stencil_modes[index] != current_stencil
                || self.stencil_references[index] != current_stencil_ref
                || self.clip_rects[index] != current_clip
            {
                self.draw_batches.push(SpriteDrawBatch {
                    blend_mode: current_blend,
                    stencil_mode: current_stencil,
                    stencil_reference: current_stencil_ref as u32,
                    clip_rect: current_clip,
                    instance_start: batch_start,
                    instance_count: index as u32 - batch_start,
                });
                current_blend = self.blend_modes[index];
                current_stencil = self.stencil_modes[index];
                current_stencil_ref = self.stencil_references[index];
                current_clip = self.clip_rects[index];
                batch_start = index as u32;
            }
        }

        self.draw_batches.push(SpriteDrawBatch {
            blend_mode: current_blend,
            stencil_mode: current_stencil,
            stencil_reference: current_stencil_ref as u32,
            clip_rect: current_clip,
            instance_start: batch_start,
            instance_count: self.blend_modes.len() as u32 - batch_start,
        });
    }

    fn write_buffers(&mut self, queue: &wgpu::Queue, surface_width: u32, surface_height: u32) {
        if self.instance_data.is_empty() {
            return;
        }

        let view_projection = self.cached_view_projection.unwrap_or_else(|| {
            nalgebra_glm::ortho_rh_zo(
                0.0,
                surface_width as f32,
                0.0,
                surface_height as f32,
                -1.0,
                1.0,
            )
        });

        let globals = GlobalUniforms {
            view_projection,
            screen_size: Vec2::new(surface_width as f32, surface_height as f32),
            atlas_slots_per_row: self.atlas.slots_per_row as f32,
            atlas_slot_uv_size: 1.0 / self.atlas.slots_per_row as f32,
        };
        queue.write_buffer(
            &self.global_uniform_buffer,
            0,
            bytemuck::cast_slice(&[globals]),
        );

        queue.write_buffer(
            &self.instance_buffer,
            0,
            bytemuck::cast_slice(&self.instance_data),
        );
    }

    fn ensure_stencil_texture(&mut self, device: &wgpu::Device, width: u32, height: u32) {
        if self.stencil_texture_size.0 == width && self.stencil_texture_size.1 == height {
            return;
        }
        self.stencil_texture = device.create_texture(&wgpu::TextureDescriptor {
            label: Some("Sprite Stencil Texture"),
            size: wgpu::Extent3d {
                width,
                height,
                depth_or_array_layers: 1,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: wgpu::TextureFormat::Depth24PlusStencil8,
            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
            view_formats: &[],
        });
        self.stencil_view = self
            .stencil_texture
            .create_view(&wgpu::TextureViewDescriptor::default());
        self.stencil_texture_size = (width, height);
    }

    fn ensure_background_texture(&mut self, device: &wgpu::Device, width: u32, height: u32) {
        if self.background_texture_size.0 == width && self.background_texture_size.1 == height {
            return;
        }
        self.background_texture = device.create_texture(&wgpu::TextureDescriptor {
            label: Some("Sprite Background Texture"),
            size: wgpu::Extent3d {
                width,
                height,
                depth_or_array_layers: 1,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: wgpu::TextureFormat::Rgba16Float,
            usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING,
            view_formats: &[],
        });
        self.background_view = self
            .background_texture
            .create_view(&wgpu::TextureViewDescriptor::default());
        self.background_texture_size = (width, height);
        self.background_bind_group = None;
    }

    fn has_advanced_blend_batches(&self) -> bool {
        self.draw_batches
            .iter()
            .any(|batch| batch.blend_mode.is_advanced())
    }

    fn render_sprites(
        &mut self,
        encoder: &mut wgpu::CommandEncoder,
        color_view: &wgpu::TextureView,
        color_texture: Option<&wgpu::Texture>,
        device: &wgpu::Device,
        surface_width: u32,
        surface_height: u32,
    ) {
        if self.instance_data.is_empty() {
            return;
        }

        let has_advanced = self.has_advanced_blend_batches();

        if has_advanced {
            self.ensure_background_texture(device, surface_width, surface_height);
            if self.background_bind_group.is_none() {
                self.update_background_bind_group(device);
            }
        }

        let has_stencil_sprites = self
            .stencil_modes
            .iter()
            .any(|mode| *mode != SpriteStencilMode::None);

        let depth_stencil_attachment = if has_stencil_sprites {
            Some(wgpu::RenderPassDepthStencilAttachment {
                view: &self.stencil_view,
                depth_ops: None,
                stencil_ops: Some(wgpu::Operations {
                    load: wgpu::LoadOp::Clear(0),
                    store: wgpu::StoreOp::Store,
                }),
            })
        } else {
            Some(wgpu::RenderPassDepthStencilAttachment {
                view: &self.stencil_view,
                depth_ops: None,
                stencil_ops: Some(wgpu::Operations {
                    load: wgpu::LoadOp::Clear(0),
                    store: wgpu::StoreOp::Discard,
                }),
            })
        };

        let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
            label: Some("Sprite Render Pass"),
            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                view: color_view,
                resolve_target: None,
                ops: wgpu::Operations {
                    load: wgpu::LoadOp::Load,
                    store: wgpu::StoreOp::Store,
                },
                depth_slice: None,
            })],
            depth_stencil_attachment: depth_stencil_attachment.clone(),
            occlusion_query_set: None,
            timestamp_writes: None,
        });

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

        if let Some(ref bind_group) = self.texture_bind_group {
            render_pass.set_bind_group(1, bind_group, &[]);
            render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
            render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16);

            for batch_index in 0..self.draw_batches.len() {
                let batch = &self.draw_batches[batch_index];

                if batch.blend_mode.is_advanced() {
                    drop(render_pass);

                    if let Some(source_texture) = color_texture {
                        encoder.copy_texture_to_texture(
                            source_texture.as_image_copy(),
                            self.background_texture.as_image_copy(),
                            wgpu::Extent3d {
                                width: surface_width,
                                height: surface_height,
                                depth_or_array_layers: 1,
                            },
                        );
                    }

                    render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
                        label: Some("Sprite Render Pass (Advanced)"),
                        color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                            view: color_view,
                            resolve_target: None,
                            ops: wgpu::Operations {
                                load: wgpu::LoadOp::Load,
                                store: wgpu::StoreOp::Store,
                            },
                            depth_slice: None,
                        })],
                        depth_stencil_attachment: depth_stencil_attachment.clone(),
                        occlusion_query_set: None,
                        timestamp_writes: None,
                    });

                    render_pass.set_bind_group(0, &self.uniform_bind_group, &[]);
                    if let Some(ref tex_bind_group) = self.texture_bind_group {
                        render_pass.set_bind_group(1, tex_bind_group, &[]);
                    }
                    if let Some(ref bg_bind_group) = self.background_bind_group {
                        render_pass.set_bind_group(2, bg_bind_group, &[]);
                    }
                    render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
                    render_pass
                        .set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
                }

                let batch = &self.draw_batches[batch_index];

                if let Some(clip) = batch.clip_rect {
                    let x = (clip[0] as u32).min(surface_width);
                    let y = (clip[1] as u32).min(surface_height);
                    let max_x = (clip[2] as u32).min(surface_width);
                    let max_y = (clip[3] as u32).min(surface_height);
                    let width = max_x.saturating_sub(x);
                    let height = max_y.saturating_sub(y);
                    if width == 0 || height == 0 {
                        continue;
                    }
                    render_pass.set_scissor_rect(x, y, width, height);
                } else {
                    render_pass.set_scissor_rect(0, 0, surface_width, surface_height);
                }

                let pipeline = self.pipeline_for_batch(batch.blend_mode, batch.stencil_mode);
                render_pass.set_pipeline(pipeline);

                if batch.stencil_mode == SpriteStencilMode::Write
                    || batch.stencil_mode == SpriteStencilMode::Test
                {
                    render_pass.set_stencil_reference(batch.stencil_reference);
                }

                render_pass.draw_indexed(
                    0..6,
                    0,
                    batch.instance_start..batch.instance_start + batch.instance_count,
                );
            }
        }
    }
}

impl PassNode<World> for SpritePass {
    fn name(&self) -> &str {
        "sprite_pass"
    }

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

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

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

    fn prepare(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, configs: &World) {
        if let Some((width, height)) = configs.resources.window.cached_viewport_size {
            self.ensure_stencil_texture(device, width, height);
            self.prepare_sprites(configs, width, height);
            self.write_buffers(queue, width, height);
        }
    }

    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 = context.get_texture_view("color")?;
        let color_texture = context.get_texture("color").ok();

        self.render_sprites(
            context.encoder,
            color_view,
            color_texture,
            context.device,
            self.cached_surface_width,
            self.cached_surface_height,
        );

        Ok(vec![])
    }
}
