use crate::ecs::text::components::TextVertex;
use crate::ecs::world::World;
use crate::render::wgpu::rendergraph::{PassExecutionContext, PassNode};
use nalgebra_glm::{Mat4, Vec4};

pub struct TextPass {
    pub render_pipeline: wgpu::RenderPipeline,
    pub texture_bind_group_layout: wgpu::BindGroupLayout,
    pub text_bind_group_layout: wgpu::BindGroupLayout,

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

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

    pub text_instances: Vec<TextInstance>,

    vertex_buffer_capacity: usize,
    index_buffer_capacity: usize,
    uniform_buffer_instances: usize,
    character_color_buffer_capacity: usize,

    cached_font_texture_views: Vec<wgpu::TextureView>,
}

#[derive(Clone)]
pub struct TextInstance {
    pub vertex_offset: u32,
    pub index_offset: u32,
    pub index_count: u32,
    pub transform: Mat4,
    pub color: Vec4,
    pub outline_color: Vec4,
    pub outline_width: f32,
    pub smoothing: f32,
    pub font_index: usize,
    pub billboard: bool,
}

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct TextUniforms {
    pub model: Mat4,
    pub color: Vec4,
    pub outline_color: Vec4,
    pub outline_width: f32,
    pub smoothing: f32,
    pub _padding: [f32; 2],
}

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct SceneUniforms {
    pub view: Mat4,
    pub projection: Mat4,
    pub camera_position: Vec4,
}

const INITIAL_CHARACTER_COLOR_CAPACITY: usize = 10000;

impl TextPass {
    pub fn new(
        device: &wgpu::Device,
        color_format: wgpu::TextureFormat,
        depth_format: wgpu::TextureFormat,
    ) -> Self {
        let uniform_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Text Uniform Bind Group Layout"),
                entries: &[wgpu::BindGroupLayoutEntry {
                    binding: 0,
                    visibility: wgpu::ShaderStages::VERTEX,
                    ty: wgpu::BindingType::Buffer {
                        ty: wgpu::BufferBindingType::Uniform,
                        has_dynamic_offset: false,
                        min_binding_size: None,
                    },
                    count: None,
                }],
            });

        let text_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Text Instance Bind Group Layout"),
                entries: &[wgpu::BindGroupLayoutEntry {
                    binding: 0,
                    visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
                    ty: wgpu::BindingType::Buffer {
                        ty: wgpu::BufferBindingType::Uniform,
                        has_dynamic_offset: true,
                        min_binding_size: wgpu::BufferSize::new(
                            std::mem::size_of::<TextUniforms>() as u64,
                        ),
                    },
                    count: None,
                }],
            });

        let texture_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Text 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,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 2,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: true },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                ],
            });

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

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

        let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: Some("Text 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::Uint32,
                        },
                    ],
                }],
                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: depth_format,
                depth_write_enabled: true,
                depth_compare: wgpu::CompareFunction::Greater,
                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("Text 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("Text 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("Text Scene Uniform Buffer"),
            size: std::mem::size_of::<SceneUniforms>() as u64,
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let uniform_size = std::mem::size_of::<TextUniforms>();
        let alignment = device.limits().min_uniform_buffer_offset_alignment as usize;
        let aligned_size = (uniform_size + alignment - 1) & !(alignment - 1);
        let text_uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Text Instance Uniform Buffer"),
            size: (aligned_size * 100) as u64,
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let character_color_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Text Character Color Buffer"),
            size: (std::mem::size_of::<[f32; 4]>() * INITIAL_CHARACTER_COLOR_CAPACITY) 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("Text Uniform Bind Group"),
            layout: &uniform_bind_group_layout,
            entries: &[wgpu::BindGroupEntry {
                binding: 0,
                resource: uniform_buffer.as_entire_binding(),
            }],
        });

        let text_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("Text Instance Bind Group"),
            layout: &text_bind_group_layout,
            entries: &[wgpu::BindGroupEntry {
                binding: 0,
                resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
                    buffer: &text_uniform_buffer,
                    offset: 0,
                    size: wgpu::BufferSize::new(aligned_size as u64),
                }),
            }],
        });

        Self {
            render_pipeline,
            texture_bind_group_layout,
            text_bind_group_layout,
            vertex_buffer,
            index_buffer,
            uniform_buffer,
            text_uniform_buffer,
            character_color_buffer,
            uniform_bind_group,
            text_bind_groups: vec![text_bind_group],
            texture_bind_groups: Vec::new(),
            text_instances: Vec::new(),
            vertex_buffer_capacity: 10000,
            index_buffer_capacity: 30000,
            uniform_buffer_instances: 100,
            character_color_buffer_capacity: INITIAL_CHARACTER_COLOR_CAPACITY,
            cached_font_texture_views: Vec::new(),
        }
    }

    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("Text 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("Text 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;
        }
    }

    fn ensure_uniform_buffer_capacity(&mut self, device: &wgpu::Device, required: usize) {
        if required > self.uniform_buffer_instances {
            let uniform_size = std::mem::size_of::<TextUniforms>();
            let alignment = device.limits().min_uniform_buffer_offset_alignment as usize;
            let aligned_size = (uniform_size + alignment - 1) & !(alignment - 1);
            let new_capacity = (required * 2).max(10);

            self.text_uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                label: Some("Text Instance Uniform Buffer (Resized)"),
                size: (aligned_size * new_capacity) as u64,
                usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
                mapped_at_creation: false,
            });

            let text_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
                label: Some("Text Instance Bind Group (Resized)"),
                layout: &self.text_bind_group_layout,
                entries: &[wgpu::BindGroupEntry {
                    binding: 0,
                    resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
                        buffer: &self.text_uniform_buffer,
                        offset: 0,
                        size: wgpu::BufferSize::new(aligned_size as u64),
                    }),
                }],
            });

            self.text_bind_groups = vec![text_bind_group];
            self.uniform_buffer_instances = new_capacity;
        }
    }

    fn ensure_character_color_buffer_capacity(&mut self, device: &wgpu::Device, required: usize) {
        if required > self.character_color_buffer_capacity {
            let new_capacity = (required * 2).max(1000);
            self.character_color_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                label: Some("Text Character Color Buffer (Resized)"),
                size: (std::mem::size_of::<[f32; 4]>() * new_capacity) as u64,
                usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
                mapped_at_creation: false,
            });
            self.character_color_buffer_capacity = new_capacity;
            self.rebuild_texture_bind_groups(device);
        }
    }

    pub fn update_texture_bind_groups(
        &mut self,
        device: &wgpu::Device,
        font_textures: &[(wgpu::Texture, wgpu::TextureView)],
    ) {
        self.cached_font_texture_views.clear();
        for (_texture, texture_view) in font_textures {
            self.cached_font_texture_views.push(texture_view.clone());
        }
        self.rebuild_texture_bind_groups(device);
    }

    fn rebuild_texture_bind_groups(&mut self, device: &wgpu::Device) {
        self.texture_bind_groups.clear();

        for texture_view in &self.cached_font_texture_views {
            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("Text 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),
                    },
                    wgpu::BindGroupEntry {
                        binding: 2,
                        resource: self.character_color_buffer.as_entire_binding(),
                    },
                ],
            });

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

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

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

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

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

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

        let mut all_vertices = Vec::new();
        let mut all_indices = Vec::new();
        let mut all_character_colors: Vec<[f32; 4]> = Vec::new();
        let mut character_color_offset = 0u32;

        let view_matrix = if let Some(camera_matrices) =
            crate::ecs::camera::queries::query_active_camera_matrices(world)
        {
            let camera_position = nalgebra_glm::vec4(0.0, 0.0, 0.0, 1.0);
            let scene_uniforms = SceneUniforms {
                view: camera_matrices.view,
                projection: camera_matrices.projection,
                camera_position,
            };
            queue.write_buffer(
                &self.uniform_buffer,
                0,
                bytemuck::cast_slice(&[scene_uniforms]),
            );
            Some(camera_matrices.view)
        } else {
            None
        };

        let entities = world.query_entities(
            crate::ecs::world::TEXT
                | crate::ecs::world::VISIBILITY
                | crate::ecs::world::GLOBAL_TRANSFORM,
        );

        for entity in entities {
            let visible = world
                .get_visibility(entity)
                .map(|v| v.visible)
                .unwrap_or(false);
            if !visible {
                continue;
            }

            let text = world.get_text(entity);
            let global_transform = world.get_global_transform(entity);
            let character_colors = world.get_text_character_colors(entity);

            if let (Some(text), Some(global_transform)) = (text, global_transform)
                && let Some(mesh) = &text.cached_mesh
            {
                let vertex_offset = all_vertices.len() as u32;
                let index_offset = all_indices.len() as u32;

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

                let text_content = world
                    .resources
                    .text_cache
                    .get_text(text.text_index)
                    .map(|s| s.to_string())
                    .unwrap_or_default();
                let char_count = text_content.chars().filter(|c| *c != '\n').count();

                for vertex in &mesh.vertices {
                    let adjusted_vertex = TextVertex {
                        position: vertex.position,
                        tex_coords: vertex.tex_coords,
                        character_index: vertex.character_index + character_color_offset,
                        _padding: 0,
                    };
                    all_vertices.push(adjusted_vertex);
                }

                for character_index in 0..char_count {
                    let color = if let Some(colors) = character_colors {
                        colors
                            .colors
                            .get(character_index)
                            .and_then(|c| *c)
                            .map(|c| [c.x, c.y, c.z, c.w])
                            .unwrap_or([0.0, 0.0, 0.0, 0.0])
                    } else {
                        [0.0, 0.0, 0.0, 0.0]
                    };
                    all_character_colors.push(color);
                }

                character_color_offset += char_count as u32;

                self.text_instances.push(TextInstance {
                    vertex_offset,
                    index_offset,
                    index_count: mesh.indices.len() as u32,
                    transform: global_transform.0,
                    color: text.properties.color,
                    outline_color: text.properties.outline_color,
                    outline_width: text.properties.outline_width,
                    smoothing: text.properties.smoothing,
                    font_index: text.font_index,
                    billboard: text.billboard,
                });
            }
        }

        if all_character_colors.is_empty() {
            all_character_colors.push([0.0, 0.0, 0.0, 0.0]);
        }

        self.ensure_character_color_buffer_capacity(device, all_character_colors.len());
        queue.write_buffer(
            &self.character_color_buffer,
            0,
            bytemuck::cast_slice(&all_character_colors),
        );

        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 uniform_size = std::mem::size_of::<TextUniforms>();
        let alignment = device.limits().min_uniform_buffer_offset_alignment as usize;
        let aligned_size = (uniform_size + alignment - 1) & !(alignment - 1);

        self.ensure_uniform_buffer_capacity(device, self.text_instances.len());

        for (i, instance) in self.text_instances.iter().enumerate() {
            if i >= self.uniform_buffer_instances {
                break;
            }

            let model = if instance.billboard {
                if let Some(view) = view_matrix {
                    let position = nalgebra_glm::vec3(
                        instance.transform[(0, 3)],
                        instance.transform[(1, 3)],
                        instance.transform[(2, 3)],
                    );

                    let scale_x = nalgebra_glm::length(&nalgebra_glm::vec3(
                        instance.transform[(0, 0)],
                        instance.transform[(1, 0)],
                        instance.transform[(2, 0)],
                    ));
                    let scale_y = nalgebra_glm::length(&nalgebra_glm::vec3(
                        instance.transform[(0, 1)],
                        instance.transform[(1, 1)],
                        instance.transform[(2, 1)],
                    ));
                    let scale_z = nalgebra_glm::length(&nalgebra_glm::vec3(
                        instance.transform[(0, 2)],
                        instance.transform[(1, 2)],
                        instance.transform[(2, 2)],
                    ));

                    let right = nalgebra_glm::vec3(view[(0, 0)], view[(0, 1)], view[(0, 2)]);
                    let up = nalgebra_glm::vec3(view[(1, 0)], view[(1, 1)], view[(1, 2)]);
                    let forward = nalgebra_glm::vec3(-view[(2, 0)], -view[(2, 1)], -view[(2, 2)]);

                    #[rustfmt::skip]
                    let billboard_transform = Mat4::new(
                        right.x * scale_x, up.x * scale_y, forward.x * scale_z, position.x,
                        right.y * scale_x, up.y * scale_y, forward.y * scale_z, position.y,
                        right.z * scale_x, up.z * scale_y, forward.z * scale_z, position.z,
                        0.0, 0.0, 0.0, 1.0,
                    );

                    billboard_transform
                } else {
                    instance.transform
                }
            } else {
                instance.transform
            };

            let text_uniforms = TextUniforms {
                model,
                color: instance.color,
                outline_color: instance.outline_color,
                outline_width: instance.outline_width,
                smoothing: instance.smoothing,
                _padding: [0.0; 2],
            };

            let offset = i * aligned_size;
            queue.write_buffer(
                &self.text_uniform_buffer,
                offset as u64,
                bytemuck::cast_slice(&[text_uniforms]),
            );
        }
    }

    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 mut render_pass = context
            .encoder
            .begin_render_pass(&wgpu::RenderPassDescriptor {
                label: Some("Text 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.text_instances.is_empty() {
            render_pass.set_pipeline(&self.render_pipeline);
            render_pass.set_bind_group(0, &self.uniform_bind_group, &[]);
            render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
            render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);

            for (i, instance) in self.text_instances.iter().enumerate() {
                if i >= self.uniform_buffer_instances {
                    tracing::warn!(
                        "Skipping text instance {} - exceeds uniform buffer capacity of {}",
                        i,
                        self.uniform_buffer_instances
                    );
                    break;
                }

                if instance.font_index >= self.texture_bind_groups.len() {
                    tracing::warn!(
                        "Skipping text instance with invalid font index: {}",
                        instance.font_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 text instance with out-of-bounds indices");
                    continue;
                }

                let uniform_size = std::mem::size_of::<TextUniforms>();
                let alignment =
                    context.device.limits().min_uniform_buffer_offset_alignment as usize;
                let aligned_size = (uniform_size + alignment - 1) & !(alignment - 1);
                let offset = (i as u32) * (aligned_size as u32);
                render_pass.set_bind_group(1, &self.text_bind_groups[0], &[offset]);

                render_pass.set_bind_group(2, &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())
    }
}
