use crate::Context;
use crate::drawable::DrawCommand3D;
use crate::graphics::model_raw::MaterialBindGroupKey;
use crate::graphics::model_raw::{MeshData, ModelRenderer};
use crate::image::ImageEntry;

use super::core::Graphics;
use super::image_ops::resolve_image_uv;
use crate::model::SkinData;

pub(super) struct Render3DConfig<'a> {
    pub model_pipeline: &'a wgpu::RenderPipeline,
    pub transparent_model_pipeline: &'a wgpu::RenderPipeline,
    pub instanced_model_pipeline: &'a wgpu::RenderPipeline,
    pub transparent_instanced_model_pipeline: &'a wgpu::RenderPipeline,
    pub shadow_pipeline: &'a wgpu::RenderPipeline,
    pub instanced_shadow_pipeline: &'a wgpu::RenderPipeline,
    pub model_pipelines: &'a std::collections::HashMap<u32, wgpu::RenderPipeline>,
    pub transparent_model_pipelines: &'a std::collections::HashMap<u32, wgpu::RenderPipeline>,
    pub instanced_model_pipelines: &'a std::collections::HashMap<u32, wgpu::RenderPipeline>,
    pub transparent_instanced_model_pipelines:
        &'a std::collections::HashMap<u32, wgpu::RenderPipeline>,
    pub white_image_id: u32,
    pub black_image_id: u32,
    pub normal_image_id: u32,
    pub environment_bind_group: &'a wgpu::BindGroup,
    pub width: u32,
    pub height: u32,
}

type MaterialTextureBinding<'a> = (u32, [f32; 4], &'a wgpu::TextureView);
type MaterialTextureSet<'a> = (
    MaterialTextureBinding<'a>,
    MaterialTextureBinding<'a>,
    MaterialTextureBinding<'a>,
    MaterialTextureBinding<'a>,
    MaterialTextureBinding<'a>,
);

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
struct DrawCommand3DSortKey {
    instanced: bool,
    shader_id: u32,
    skin_id: u32,
    mesh_id: u32,
    albedo_id: u32,
    pbr_id: u32,
    normal_id: u32,
    ao_id: u32,
    emissive_id: u32,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
struct ShadowDrawCommand3DSortKey {
    instanced: bool,
    skin_id: u32,
    mesh_id: u32,
}

fn normalize3(v: [f32; 3]) -> [f32; 3] {
    let len = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt().max(0.0001);
    [v[0] / len, v[1] / len, v[2] / len]
}

fn cross3(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
    [
        a[1] * b[2] - a[2] * b[1],
        a[2] * b[0] - a[0] * b[2],
        a[0] * b[1] - a[1] * b[0],
    ]
}

fn resolve_material_texture<'a>(
    images: &[Option<ImageEntry>],
    textures: &'a [Option<crate::graphics::texture::TextureEntry>],
    img_id: Option<u32>,
    fallback_id: u32,
) -> Option<(u32, [f32; 4], &'a wgpu::TextureView)> {
    let id = img_id
        .filter(|&id| images.get(id as usize).and_then(|v| v.as_ref()).is_some())
        .unwrap_or(fallback_id);
    let entry = images.get(id as usize).and_then(|v| v.as_ref())?;
    let texture_entry = textures
        .get(entry.texture_id as usize)
        .and_then(|v| v.as_ref())?;
    let uv_rect = resolve_image_uv(entry, texture_entry);
    let view = &texture_entry.runtime.gpu_texture.as_ref()?.0.view;
    Some((entry.texture_id, uv_rect, view))
}

fn expect_default_material_texture<'a>(
    images: &[Option<ImageEntry>],
    textures: &'a [Option<crate::graphics::texture::TextureEntry>],
    image_id: u32,
    label: &str,
) -> MaterialTextureBinding<'a> {
    resolve_material_texture(images, textures, Some(image_id), image_id).unwrap_or_else(|| {
        panic!(
            "[spot][render] default {} texture {} is unavailable",
            label, image_id
        )
    })
}

fn resolve_material_textures<'a>(
    images: &[Option<ImageEntry>],
    textures: &'a [Option<crate::graphics::texture::TextureEntry>],
    part: &crate::model::ModelPart,
    white_image_id: u32,
    black_image_id: u32,
    normal_image_id: u32,
) -> MaterialTextureSet<'a> {
    let white = expect_default_material_texture(images, textures, white_image_id, "white");
    let black = expect_default_material_texture(images, textures, black_image_id, "black");
    let normal_default =
        expect_default_material_texture(images, textures, normal_image_id, "normal");

    let albedo = resolve_material_texture(images, textures, part.material.albedo, white_image_id)
        .unwrap_or(white);
    let pbr = resolve_material_texture(images, textures, part.material.pbr, black_image_id)
        .unwrap_or(black);
    let normal = resolve_material_texture(images, textures, part.material.normal, normal_image_id)
        .unwrap_or(normal_default);
    let ao = resolve_material_texture(images, textures, part.material.occlusion, white_image_id)
        .unwrap_or(white);
    let emissive =
        resolve_material_texture(images, textures, part.material.emissive, black_image_id)
            .unwrap_or(black);

    (albedo, pbr, normal, ao, emissive)
}

fn draw_command_3d_is_transparent(command: &DrawCommand3D) -> bool {
    match command {
        DrawCommand3D::Model(_, _, opts, ..) | DrawCommand3D::ModelInstanced(_, _, opts, ..) => {
            opts.opacity < 1.0
        }
    }
}

fn draw_command_3d_sort_key(command: &DrawCommand3D) -> DrawCommand3DSortKey {
    match command {
        DrawCommand3D::Model(_, model, _, shader_id, _, skin_id) => DrawCommand3DSortKey {
            instanced: false,
            shader_id: *shader_id,
            skin_id: skin_id.unwrap_or(0),
            mesh_id: model.first_id(),
            albedo_id: model
                .parts
                .first()
                .and_then(|part| part.material.albedo)
                .unwrap_or(0),
            pbr_id: model
                .parts
                .first()
                .and_then(|part| part.material.pbr)
                .unwrap_or(0),
            normal_id: model
                .parts
                .first()
                .and_then(|part| part.material.normal)
                .unwrap_or(0),
            ao_id: model
                .parts
                .first()
                .and_then(|part| part.material.occlusion)
                .unwrap_or(0),
            emissive_id: model
                .parts
                .first()
                .and_then(|part| part.material.emissive)
                .unwrap_or(0),
        },
        DrawCommand3D::ModelInstanced(_, model, _, shader_id, _, skin_id, _) => {
            DrawCommand3DSortKey {
                instanced: true,
                shader_id: *shader_id,
                skin_id: skin_id.unwrap_or(0),
                mesh_id: model.first_id(),
                albedo_id: model
                    .parts
                    .first()
                    .and_then(|part| part.material.albedo)
                    .unwrap_or(0),
                pbr_id: model
                    .parts
                    .first()
                    .and_then(|part| part.material.pbr)
                    .unwrap_or(0),
                normal_id: model
                    .parts
                    .first()
                    .and_then(|part| part.material.normal)
                    .unwrap_or(0),
                ao_id: model
                    .parts
                    .first()
                    .and_then(|part| part.material.occlusion)
                    .unwrap_or(0),
                emissive_id: model
                    .parts
                    .first()
                    .and_then(|part| part.material.emissive)
                    .unwrap_or(0),
            }
        }
    }
}

fn draw_command_3d_position(command: &DrawCommand3D) -> [f32; 3] {
    match command {
        DrawCommand3D::Model(_, _, opts, ..) | DrawCommand3D::ModelInstanced(_, _, opts, ..) => {
            opts.position
        }
    }
}

fn shadow_draw_command_3d_sort_key(command: &DrawCommand3D) -> ShadowDrawCommand3DSortKey {
    match command {
        DrawCommand3D::Model(_, model, _, _, _, skin_id)
        | DrawCommand3D::ModelInstanced(_, model, _, _, _, skin_id, _) => {
            ShadowDrawCommand3DSortKey {
                instanced: matches!(command, DrawCommand3D::ModelInstanced(..)),
                skin_id: skin_id.unwrap_or(0),
                mesh_id: model.first_id(),
            }
        }
    }
}

fn distance_squared(a: [f32; 3], b: [f32; 3]) -> f32 {
    let dx = a[0] - b[0];
    let dy = a[1] - b[1];
    let dz = a[2] - b[2];
    dx * dx + dy * dy + dz * dz
}

fn draw_option_3d_model_matrix(opts: &crate::DrawOption3D) -> [[f32; 4]; 4] {
    let model_mat = crate::math::mat4::from_translation(opts.position);
    let rot_mat = crate::math::mat4::from_rotation(opts.rotation);
    let scale_mat = crate::math::mat4::from_scale(opts.scale);
    crate::math::mat4::multiply(model_mat, crate::math::mat4::multiply(rot_mat, scale_mat))
}

impl Graphics {
    pub(super) fn clear_3d_command_order(&mut self) {
        if let Some(model_3d) = self.model_3d_mut() {
            model_3d.opaque_draw_indices_3d.clear();
            model_3d.transparent_draw_indices_3d.clear();
            model_3d.shadow_draw_indices_3d.clear();
        }
    }

    #[allow(clippy::too_many_arguments)]
    fn render_shadow_3d_internal<'pass>(
        model_renderer: &mut ModelRenderer,
        queue: &wgpu::Queue,
        models: &[Option<MeshData>],
        skins: &[Option<SkinData>],
        pipeline: &wgpu::RenderPipeline,
        rpass: &mut wgpu::RenderPass<'pass>,
        ctx: &Context,
        shadow_draw_indices: &[usize],
        target_texture_id: u32,
        lvp: [[f32; 4]; 4],
    ) {
        let mut current_mesh_id: Option<u32> = None;
        let mut current_bone_offset: Option<u32> = None;

        rpass.set_pipeline(pipeline);

        let mut index = 0;
        while index < shadow_draw_indices.len() {
            let Some(command) = ctx
                .runtime
                .model_3d
                .draw_list
                .get(shadow_draw_indices[index])
            else {
                index += 1;
                continue;
            };

            match command {
                DrawCommand3D::Model(command_target_texture_id, model, opts, _, _, skin_id_cmd) => {
                    if *command_target_texture_id != target_texture_id {
                        index += 1;
                        continue;
                    }

                    let mut transforms = Vec::with_capacity(8);
                    transforms.push(draw_option_3d_model_matrix(opts));

                    let mut next_index = index + 1;
                    while next_index < shadow_draw_indices.len() {
                        match ctx
                            .runtime
                            .model_3d
                            .draw_list
                            .get(shadow_draw_indices[next_index])
                        {
                            Some(DrawCommand3D::Model(
                                next_target_texture_id,
                                next_model,
                                next_opts,
                                _,
                                _,
                                next_skin_id,
                            )) if *next_target_texture_id == target_texture_id
                                && next_skin_id == skin_id_cmd
                                && next_model == model =>
                            {
                                transforms.push(draw_option_3d_model_matrix(next_opts));
                                next_index += 1;
                            }
                            Some(_) | None => break,
                        }
                    }

                    if let Err(e) = model_renderer.upload_instances(queue, &transforms) {
                        eprintln!("[spot][render] Failed to upload shadow instances: {}", e);
                        index = next_index;
                        continue;
                    }

                    let mut bone_offset = 0;
                    if let Some(skin_id) = skin_id_cmd
                        && let Some(Some(skin)) = skins.get(*skin_id as usize)
                        && let Ok(off) = model_renderer.bone_offset_for_skin(
                            queue,
                            *skin_id,
                            &skin.bone_matrices,
                        )
                    {
                        bone_offset = off;
                    }

                    let globals = crate::graphics::model_raw::ModelGlobals {
                        mvp: lvp,
                        model: crate::math::mat4::identity(),
                        extra: [1.0, 0.0, 0.0, 0.0],
                        ..Default::default()
                    };

                    if let Ok(offset) = model_renderer.upload_globals(queue, &globals) {
                        rpass.set_bind_group(0, &model_renderer.globals_bind_group, &[offset, 0]);
                        if current_bone_offset != Some(bone_offset) {
                            rpass.set_bind_group(
                                1,
                                &model_renderer.bone_matrices_bind_group,
                                &[bone_offset],
                            );
                            current_bone_offset = Some(bone_offset);
                        }

                        for part in model.parts.iter() {
                            if let Some(Some(mesh)) = models.get(part.id as usize) {
                                if current_mesh_id != Some(part.id) {
                                    rpass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
                                    rpass.set_vertex_buffer(
                                        1,
                                        model_renderer.instance_buffer.slice(..),
                                    );
                                    rpass.set_index_buffer(
                                        mesh.index_buffer.slice(..),
                                        wgpu::IndexFormat::Uint32,
                                    );
                                    current_mesh_id = Some(part.id);
                                }
                                rpass.draw_indexed(
                                    0..mesh.index_count,
                                    0,
                                    0..transforms.len() as u32,
                                );
                            }
                        }
                    }

                    index = next_index;
                }
                DrawCommand3D::ModelInstanced(
                    command_target_texture_id,
                    model,
                    opts,
                    _,
                    _,
                    skin_id_cmd,
                    transforms,
                ) => {
                    if *command_target_texture_id != target_texture_id {
                        index += 1;
                        continue;
                    }

                    if let Err(e) = model_renderer.upload_instances(queue, transforms.as_ref()) {
                        eprintln!("[spot][render] Failed to upload instances: {}", e);
                        index += 1;
                        continue;
                    }

                    let mut bone_offset = 0;
                    if let Some(skin_id) = skin_id_cmd
                        && let Some(Some(skin)) = skins.get(*skin_id as usize)
                        && let Ok(off) = model_renderer.bone_offset_for_skin(
                            queue,
                            *skin_id,
                            &skin.bone_matrices,
                        )
                    {
                        bone_offset = off;
                    }

                    let globals = crate::graphics::model_raw::ModelGlobals {
                        mvp: crate::math::mat4::multiply(lvp, draw_option_3d_model_matrix(opts)),
                        model: crate::math::mat4::identity(),
                        extra: [1.0, 0.0, 0.0, 0.0],
                        ..Default::default()
                    };

                    if let Ok(offset) = model_renderer.upload_globals(queue, &globals) {
                        rpass.set_bind_group(0, &model_renderer.globals_bind_group, &[offset, 0]);
                        if current_bone_offset != Some(bone_offset) {
                            rpass.set_bind_group(
                                1,
                                &model_renderer.bone_matrices_bind_group,
                                &[bone_offset],
                            );
                            current_bone_offset = Some(bone_offset);
                        }

                        for part in model.parts.iter() {
                            if let Some(Some(mesh)) = models.get(part.id as usize) {
                                if current_mesh_id != Some(part.id) {
                                    rpass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
                                    rpass.set_vertex_buffer(
                                        1,
                                        model_renderer.instance_buffer.slice(..),
                                    );
                                    rpass.set_index_buffer(
                                        mesh.index_buffer.slice(..),
                                        wgpu::IndexFormat::Uint32,
                                    );
                                    current_mesh_id = Some(part.id);
                                }
                                rpass.draw_indexed(
                                    0..mesh.index_count,
                                    0,
                                    0..transforms.len() as u32,
                                );
                            }
                        }
                    }

                    index += 1;
                }
            }
        }
    }

    pub(super) fn prepare_3d_command_order(&mut self, ctx: &Context, target_texture_id: u32) {
        let model_3d = self.ensure_model_3d();
        model_3d.opaque_draw_indices_3d.clear();
        model_3d.transparent_draw_indices_3d.clear();
        model_3d.shadow_draw_indices_3d.clear();

        model_3d
            .opaque_draw_indices_3d
            .reserve(ctx.runtime.model_3d.draw_list.len());
        model_3d
            .transparent_draw_indices_3d
            .reserve(ctx.runtime.model_3d.draw_list.len());
        model_3d
            .shadow_draw_indices_3d
            .reserve(ctx.runtime.model_3d.draw_list.len());

        for (index, command) in ctx.runtime.model_3d.draw_list.iter().enumerate() {
            let command_target = match command {
                DrawCommand3D::Model(target, ..) | DrawCommand3D::ModelInstanced(target, ..) => {
                    *target
                }
            };
            if command_target != target_texture_id {
                continue;
            }
            model_3d.shadow_draw_indices_3d.push(index);
            if draw_command_3d_is_transparent(command) {
                model_3d.transparent_draw_indices_3d.push(index);
            } else {
                model_3d.opaque_draw_indices_3d.push(index);
            }
        }

        model_3d
            .opaque_draw_indices_3d
            .sort_by_key(|&index| draw_command_3d_sort_key(&ctx.runtime.model_3d.draw_list[index]));
        let camera_pos = ctx.runtime.model_3d.camera.eye;
        model_3d.transparent_draw_indices_3d.sort_by(|&a, &b| {
            let command_a = &ctx.runtime.model_3d.draw_list[a];
            let command_b = &ctx.runtime.model_3d.draw_list[b];
            let distance_a = distance_squared(camera_pos, draw_command_3d_position(command_a));
            let distance_b = distance_squared(camera_pos, draw_command_3d_position(command_b));

            distance_b
                .partial_cmp(&distance_a)
                .unwrap_or(std::cmp::Ordering::Equal)
                .then_with(|| {
                    draw_command_3d_sort_key(command_a).cmp(&draw_command_3d_sort_key(command_b))
                })
        });
        model_3d.shadow_draw_indices_3d.sort_by_key(|&index| {
            shadow_draw_command_3d_sort_key(&ctx.runtime.model_3d.draw_list[index])
        });
    }

    pub(super) fn render_shadow_pass(
        &mut self,
        encoder: &mut wgpu::CommandEncoder,
        ctx: &mut Context,
        width: u32,
        height: u32,
        target_texture_id: u32,
    ) {
        self.ensure_model_3d();
        {
            let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
                label: Some("shadow_pass"),
                color_attachments: &[],
                depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
                    view: &self.model_3d().expect("ensured").shadow_view,
                    depth_ops: Some(wgpu::Operations {
                        load: wgpu::LoadOp::Clear(1.0),
                        store: wgpu::StoreOp::Store,
                    }),
                    stencil_ops: None,
                }),
                timestamp_writes: None,
                occlusion_query_set: None,
                multiview_mask: None,
            });

            let queue = &self.queue;
            let device = &self.device;
            let model_3d = self.model_3d.as_mut().expect("ensured");
            Self::render_3d_internal(
                &mut model_3d.model_renderer,
                queue,
                device,
                &mut model_3d.scene_globals,
                &model_3d.gpu_models,
                &model_3d.gpu_skins,
                &ctx.registry.images,
                &ctx.registry.textures,
                Render3DConfig {
                    model_pipeline: &model_3d.model_pipeline,
                    transparent_model_pipeline: &model_3d.transparent_model_pipeline,
                    instanced_model_pipeline: &model_3d.instanced_model_pipeline,
                    transparent_instanced_model_pipeline: &model_3d
                        .transparent_instanced_model_pipeline,
                    shadow_pipeline: &model_3d.shadow_pipeline,
                    instanced_shadow_pipeline: &model_3d.instanced_shadow_pipeline,
                    model_pipelines: &model_3d.model_pipelines,
                    transparent_model_pipelines: &model_3d.transparent_model_pipelines,
                    instanced_model_pipelines: &model_3d.instanced_model_pipelines,
                    transparent_instanced_model_pipelines: &model_3d
                        .transparent_instanced_model_pipelines,
                    white_image_id: model_3d.white_image_id,
                    black_image_id: model_3d.black_image_id,
                    normal_image_id: model_3d.normal_image_id,
                    environment_bind_group: &model_3d.environment_bind_group,
                    width,
                    height,
                },
                &mut rpass,
                ctx,
                &model_3d.shadow_draw_indices_3d,
                &[],
                true,
                target_texture_id,
            );
        }
    }

    pub(super) fn render_main_3d_pass<'pass>(
        &mut self,
        ctx: &mut Context,
        width: u32,
        height: u32,
        rpass: &mut wgpu::RenderPass<'pass>,
        target_texture_id: u32,
    ) {
        let queue = &self.queue;
        let device = &self.device;
        let model_3d = self.model_3d.as_mut().expect("ensured");
        Self::render_3d_internal(
            &mut model_3d.model_renderer,
            queue,
            device,
            &mut model_3d.scene_globals,
            &model_3d.gpu_models,
            &model_3d.gpu_skins,
            &ctx.registry.images,
            &ctx.registry.textures,
            Render3DConfig {
                model_pipeline: &model_3d.model_pipeline,
                transparent_model_pipeline: &model_3d.transparent_model_pipeline,
                instanced_model_pipeline: &model_3d.instanced_model_pipeline,
                transparent_instanced_model_pipeline: &model_3d
                    .transparent_instanced_model_pipeline,
                shadow_pipeline: &model_3d.shadow_pipeline,
                instanced_shadow_pipeline: &model_3d.instanced_shadow_pipeline,
                model_pipelines: &model_3d.model_pipelines,
                transparent_model_pipelines: &model_3d.transparent_model_pipelines,
                instanced_model_pipelines: &model_3d.instanced_model_pipelines,
                transparent_instanced_model_pipelines: &model_3d
                    .transparent_instanced_model_pipelines,
                white_image_id: model_3d.white_image_id,
                black_image_id: model_3d.black_image_id,
                normal_image_id: model_3d.normal_image_id,
                environment_bind_group: &model_3d.environment_bind_group,
                width,
                height,
            },
            rpass,
            ctx,
            &model_3d.opaque_draw_indices_3d,
            &model_3d.transparent_draw_indices_3d,
            false,
            target_texture_id,
        );
    }

    #[allow(clippy::too_many_arguments)]
    pub(super) fn render_3d_internal<'pass, 'cfg>(
        model_renderer: &mut ModelRenderer,
        queue: &wgpu::Queue,
        device: &wgpu::Device,
        scene_globals: &mut crate::model::SceneGlobals,
        models: &[Option<MeshData>],
        skins: &[Option<SkinData>],
        images: &[Option<ImageEntry>],
        textures: &[Option<crate::graphics::texture::TextureEntry>],
        config: Render3DConfig<'cfg>,
        rpass: &mut wgpu::RenderPass<'pass>,
        ctx: &Context,
        opaque_draw_indices: &[usize],
        transparent_draw_indices: &[usize],
        is_shadow_pass: bool,
        target_texture_id: u32,
    ) {
        let mut camera = ctx.runtime.model_3d.camera;
        camera.aspect = config.width as f32 / config.height as f32;
        let proj = camera.projection_matrix();
        let view_mat = camera.view_matrix();
        let forward = normalize3([
            camera.target[0] - camera.eye[0],
            camera.target[1] - camera.eye[1],
            camera.target[2] - camera.eye[2],
        ]);
        let right = normalize3(cross3(camera.up, forward));
        let up = cross3(forward, right);

        scene_globals.camera_pos = [camera.eye[0], camera.eye[1], camera.eye[2], 1.0];
        scene_globals.camera_right = [right[0], right[1], right[2], 0.0];
        scene_globals.camera_up = [up[0], up[1], up[2], 0.0];
        scene_globals.camera_forward = [forward[0], forward[1], forward[2], 0.0];
        scene_globals.projection_params = [proj[0][0], proj[1][1], camera.znear, camera.zfar];

        scene_globals.light_view_proj = [
            [0.1, 0.0, 0.0, 0.0],
            [0.0, 0.1, 0.0, 0.0],
            [0.0, 0.0, 0.05, 0.0],
            [0.0, 0.0, 0.5, 1.0],
        ];

        if !is_shadow_pass {
            model_renderer.upload_scene_globals(queue, scene_globals);
        }

        let lvp = scene_globals.light_view_proj;

        if is_shadow_pass {
            Self::render_shadow_3d_internal(
                model_renderer,
                queue,
                models,
                skins,
                config.instanced_shadow_pipeline,
                rpass,
                ctx,
                opaque_draw_indices,
                target_texture_id,
                lvp,
            );
            return;
        }

        let mut current_pipeline: Option<*const wgpu::RenderPipeline> = None;
        let mut current_mesh_binding: Option<(u32, bool)> = None;
        let mut current_material_key: Option<MaterialBindGroupKey> = None;
        let mut current_shader_opts_bytes: Option<[u8; ModelRenderer::USER_SHADER_OPTS_SIZE]> =
            None;
        let mut current_shader_opts_offset: u32 = 0;
        let mut current_bone_offset: Option<u32> = None;
        let mut environment_bound = false;

        for command in opaque_draw_indices
            .iter()
            .chain(transparent_draw_indices.iter())
            .filter_map(|&index| ctx.runtime.model_3d.draw_list.get(index))
        {
            match command {
                DrawCommand3D::Model(
                    command_target_texture_id,
                    model,
                    opts,
                    shader_id,
                    shader_opts,
                    skin_id_cmd,
                ) => {
                    if *command_target_texture_id != target_texture_id {
                        continue;
                    }
                    let model_mat = crate::math::mat4::from_translation(opts.position);
                    let rot_mat = crate::math::mat4::from_rotation(opts.rotation);
                    let scale_mat = crate::math::mat4::from_scale(opts.scale);
                    let model_mat_all = crate::math::mat4::multiply(
                        model_mat,
                        crate::math::mat4::multiply(rot_mat, scale_mat),
                    );

                    let mvp = if is_shadow_pass {
                        crate::math::mat4::multiply(lvp, model_mat_all)
                    } else {
                        crate::math::mat4::multiply(
                            proj,
                            crate::math::mat4::multiply(view_mat, model_mat_all),
                        )
                    };

                    let base_globals = crate::graphics::model_raw::ModelGlobals {
                        mvp,
                        model: model_mat_all,
                        extra: [opts.opacity, 0.0, 0.0, 0.0],
                        ..Default::default()
                    };
                    let is_transparent = opts.opacity < 1.0;
                    let pipeline = if is_shadow_pass {
                        config.shadow_pipeline
                    } else if is_transparent && *shader_id == 0 {
                        config.transparent_model_pipeline
                    } else if is_transparent {
                        config
                            .transparent_model_pipelines
                            .get(shader_id)
                            .unwrap_or(config.transparent_model_pipeline)
                    } else if *shader_id == 0 {
                        config.model_pipeline
                    } else {
                        config
                            .model_pipelines
                            .get(shader_id)
                            .unwrap_or(config.model_pipeline)
                    };
                    let mut bone_offset = 0;
                    if let Some(skin_id) = skin_id_cmd
                        && let Some(Some(skin)) = skins.get(*skin_id as usize)
                        && let Ok(off) = model_renderer.bone_offset_for_skin(
                            queue,
                            *skin_id,
                            &skin.bone_matrices,
                        )
                    {
                        bone_offset = off;
                    }
                    if !is_shadow_pass {
                        let shader_opts_bytes: [u8; ModelRenderer::USER_SHADER_OPTS_SIZE] =
                            shader_opts
                                .as_bytes()
                                .try_into()
                                .expect("model shader opts size should match renderer buffer size");
                        if current_shader_opts_bytes != Some(shader_opts_bytes)
                            && let Ok(offset) =
                                model_renderer.upload_shader_opts_bytes(queue, &shader_opts_bytes)
                        {
                            current_shader_opts_bytes = Some(shader_opts_bytes);
                            current_shader_opts_offset = offset;
                        }
                    }

                    for part in model.parts.iter() {
                        if let Some(Some(mesh)) = models.get(part.id as usize) {
                            let mut globals = base_globals;
                            let mut material_texture_data = None;
                            if !is_shadow_pass {
                                let (albedo, pbr, normal, ao, emissive) = resolve_material_textures(
                                    images,
                                    textures,
                                    part,
                                    config.white_image_id,
                                    config.black_image_id,
                                    config.normal_image_id,
                                );

                                globals.albedo_uv = albedo.1;
                                globals.pbr_uv = pbr.1;
                                globals.normal_uv = normal.1;
                                globals.ao_uv = ao.1;
                                globals.emissive_uv = emissive.1;
                                material_texture_data = Some((albedo, pbr, normal, ao, emissive));
                            }

                            if let Ok(offset) = model_renderer.upload_globals(queue, &globals) {
                                let pipeline_ptr = pipeline as *const wgpu::RenderPipeline;
                                if current_pipeline != Some(pipeline_ptr) {
                                    rpass.set_pipeline(pipeline);
                                    current_pipeline = Some(pipeline_ptr);
                                }
                                if current_mesh_binding != Some((part.id, false)) {
                                    rpass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
                                    rpass.set_index_buffer(
                                        mesh.index_buffer.slice(..),
                                        wgpu::IndexFormat::Uint32,
                                    );
                                    current_mesh_binding = Some((part.id, false));
                                }

                                if is_shadow_pass {
                                    rpass.set_bind_group(
                                        0,
                                        &model_renderer.globals_bind_group,
                                        &[offset, 0],
                                    );
                                    if current_bone_offset != Some(bone_offset) {
                                        rpass.set_bind_group(
                                            1,
                                            &model_renderer.bone_matrices_bind_group,
                                            &[bone_offset],
                                        );
                                        current_bone_offset = Some(bone_offset);
                                    }
                                } else {
                                    rpass.set_bind_group(
                                        0,
                                        &model_renderer.globals_bind_group,
                                        &[offset, current_shader_opts_offset],
                                    );

                                    let (albedo, pbr, normal, ao, emissive) = material_texture_data
                                        .unwrap_or_else(|| {
                                            panic!(
                                                "[spot][render] material textures missing after resolution for mesh {}",
                                                part.id
                                            )
                                        });
                                    let material_key = MaterialBindGroupKey {
                                        texture_source_ids: [
                                            albedo.0, pbr.0, normal.0, ao.0, emissive.0,
                                        ],
                                    };
                                    let tex_bg = model_renderer.texture_bind_group_for_materials(
                                        device,
                                        material_key,
                                        [albedo.2, pbr.2, normal.2, ao.2, emissive.2],
                                    );
                                    if current_material_key != Some(material_key) {
                                        rpass.set_bind_group(1, tex_bg, &[]);
                                        current_material_key = Some(material_key);
                                    }

                                    if current_bone_offset != Some(bone_offset) {
                                        rpass.set_bind_group(
                                            2,
                                            &model_renderer.bone_matrices_bind_group,
                                            &[bone_offset],
                                        );
                                        current_bone_offset = Some(bone_offset);
                                    }
                                    if !environment_bound {
                                        rpass.set_bind_group(3, config.environment_bind_group, &[]);
                                        environment_bound = true;
                                    }
                                }

                                rpass.draw_indexed(0..mesh.index_count, 0, 0..1);
                            }
                        }
                    }
                }
                DrawCommand3D::ModelInstanced(
                    command_target_texture_id,
                    model,
                    opts,
                    shader_id,
                    shader_opts,
                    skin_id_cmd,
                    transforms,
                ) => {
                    if *command_target_texture_id != target_texture_id {
                        continue;
                    }
                    let model_mat = crate::math::mat4::from_translation(opts.position);
                    let rot_mat = crate::math::mat4::from_rotation(opts.rotation);
                    let scale_mat = crate::math::mat4::from_scale(opts.scale);
                    let model_mat_all = crate::math::mat4::multiply(
                        model_mat,
                        crate::math::mat4::multiply(rot_mat, scale_mat),
                    );

                    let mvp = if is_shadow_pass {
                        crate::math::mat4::multiply(lvp, model_mat_all)
                    } else {
                        crate::math::mat4::multiply(
                            proj,
                            crate::math::mat4::multiply(view_mat, model_mat_all),
                        )
                    };

                    let base_globals = crate::graphics::model_raw::ModelGlobals {
                        mvp,
                        model: model_mat_all,
                        extra: [opts.opacity, 0.0, 0.0, 0.0],
                        ..Default::default()
                    };
                    let is_transparent = opts.opacity < 1.0;
                    let pipeline = if is_shadow_pass {
                        config.instanced_shadow_pipeline
                    } else if is_transparent && *shader_id == 0 {
                        config.transparent_instanced_model_pipeline
                    } else if is_transparent {
                        config
                            .transparent_instanced_model_pipelines
                            .get(shader_id)
                            .unwrap_or(config.transparent_instanced_model_pipeline)
                    } else if *shader_id == 0 {
                        config.instanced_model_pipeline
                    } else {
                        config
                            .instanced_model_pipelines
                            .get(shader_id)
                            .unwrap_or(config.instanced_model_pipeline)
                    };
                    let mut bone_offset = 0;
                    if let Some(skin_id) = skin_id_cmd
                        && let Some(Some(skin)) = skins.get(*skin_id as usize)
                        && let Ok(off) = model_renderer.bone_offset_for_skin(
                            queue,
                            *skin_id,
                            &skin.bone_matrices,
                        )
                    {
                        bone_offset = off;
                    }
                    if !is_shadow_pass {
                        let shader_opts_bytes: [u8; ModelRenderer::USER_SHADER_OPTS_SIZE] =
                            shader_opts
                                .as_bytes()
                                .try_into()
                                .expect("model shader opts size should match renderer buffer size");
                        if current_shader_opts_bytes != Some(shader_opts_bytes)
                            && let Ok(offset) =
                                model_renderer.upload_shader_opts_bytes(queue, &shader_opts_bytes)
                        {
                            current_shader_opts_bytes = Some(shader_opts_bytes);
                            current_shader_opts_offset = offset;
                        }
                    }

                    if let Err(e) = model_renderer.upload_instances(queue, transforms.as_ref()) {
                        eprintln!("[spot][render] Failed to upload instances: {}", e);
                        continue;
                    }

                    for part in model.parts.iter() {
                        if let Some(Some(mesh)) = models.get(part.id as usize) {
                            let mut globals = base_globals;
                            let mut material_texture_data = None;
                            if !is_shadow_pass {
                                let (albedo, pbr, normal, ao, emissive) = resolve_material_textures(
                                    images,
                                    textures,
                                    part,
                                    config.white_image_id,
                                    config.black_image_id,
                                    config.normal_image_id,
                                );

                                globals.albedo_uv = albedo.1;
                                globals.pbr_uv = pbr.1;
                                globals.normal_uv = normal.1;
                                globals.ao_uv = ao.1;
                                globals.emissive_uv = emissive.1;
                                material_texture_data = Some((albedo, pbr, normal, ao, emissive));
                            }

                            if let Ok(offset) = model_renderer.upload_globals(queue, &globals) {
                                let pipeline_ptr = pipeline as *const wgpu::RenderPipeline;
                                if current_pipeline != Some(pipeline_ptr) {
                                    rpass.set_pipeline(pipeline);
                                    current_pipeline = Some(pipeline_ptr);
                                }
                                if current_mesh_binding != Some((part.id, true)) {
                                    rpass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
                                    rpass.set_vertex_buffer(
                                        1,
                                        model_renderer.instance_buffer.slice(..),
                                    );
                                    rpass.set_index_buffer(
                                        mesh.index_buffer.slice(..),
                                        wgpu::IndexFormat::Uint32,
                                    );
                                    current_mesh_binding = Some((part.id, true));
                                }

                                if is_shadow_pass {
                                    rpass.set_bind_group(
                                        0,
                                        &model_renderer.globals_bind_group,
                                        &[offset, 0],
                                    );
                                    if current_bone_offset != Some(bone_offset) {
                                        rpass.set_bind_group(
                                            1,
                                            &model_renderer.bone_matrices_bind_group,
                                            &[bone_offset],
                                        );
                                        current_bone_offset = Some(bone_offset);
                                    }
                                } else {
                                    rpass.set_bind_group(
                                        0,
                                        &model_renderer.globals_bind_group,
                                        &[offset, current_shader_opts_offset],
                                    );

                                    let (albedo, pbr, normal, ao, emissive) = material_texture_data
                                        .unwrap_or_else(|| {
                                            panic!(
                                                "[spot][render] material textures missing after resolution for instanced mesh {}",
                                                part.id
                                            )
                                        });
                                    let material_key = MaterialBindGroupKey {
                                        texture_source_ids: [
                                            albedo.0, pbr.0, normal.0, ao.0, emissive.0,
                                        ],
                                    };
                                    let tex_bg = model_renderer.texture_bind_group_for_materials(
                                        device,
                                        material_key,
                                        [albedo.2, pbr.2, normal.2, ao.2, emissive.2],
                                    );
                                    if current_material_key != Some(material_key) {
                                        rpass.set_bind_group(1, tex_bg, &[]);
                                        current_material_key = Some(material_key);
                                    }

                                    if current_bone_offset != Some(bone_offset) {
                                        rpass.set_bind_group(
                                            2,
                                            &model_renderer.bone_matrices_bind_group,
                                            &[bone_offset],
                                        );
                                        current_bone_offset = Some(bone_offset);
                                    }
                                    if !environment_bound {
                                        rpass.set_bind_group(3, config.environment_bind_group, &[]);
                                        environment_bound = true;
                                    }
                                }

                                rpass.draw_indexed(
                                    0..mesh.index_count,
                                    0,
                                    0..transforms.len() as u32,
                                );
                            }
                        }
                    }
                }
            }
        }
    }
}
