// Shader validation tests — verify all WGSL shaders are structurally valid.
// Tests run without a GPU device by checking WGSL source syntax properties.
// Per CODESPEC §1.1: tests use debug profile (correctness only, not perf).
// Per LINTSPEC §4.6: explicit, actionable test names.

// ── Shader Source Loading ──────────────────────────────────────────────

const GRID_WGSL: &str = include_str!("../shaders/grid.wgsl");
const AXES_WGSL: &str = include_str!("../shaders/axes.wgsl");
const PARTICLE_WGSL: &str = include_str!("../shaders/particle.wgsl");
const PRIMITIVE_WGSL: &str = include_str!("../shaders/primitive.wgsl");
const PROCEDURAL_WGSL: &str = include_str!("../shaders/procedural.wgsl");
const SPRITE_WGSL: &str = include_str!("../shaders/sprite.wgsl");
const TWEEN_WGSL: &str = include_str!("../shaders/tween.wgsl");
const PICKING_WGSL: &str = include_str!("../shaders/picking.wgsl");
const GIZMO_WGSL: &str = include_str!("../shaders/gizmo.wgsl");
const TEXTURED_WGSL: &str = include_str!("../shaders/textured.wgsl");

// Physics shaders
const PHYSICS_COMMON: &str = include_str!("../shaders/physics/physics_common.wgsl");
const PARTICLE_BILLBOARD: &str = include_str!("../shaders/physics/particle_billboard.wgsl");
const PARTICLE_BEAM: &str = include_str!("../shaders/physics/particle_beam.wgsl");
const PARTICLE_COLLISION: &str = include_str!("../shaders/physics/particle_collision.wgsl");
const PARTICLE_COMPACT: &str = include_str!("../shaders/physics/particle_compact.wgsl");
const PARTICLE_INDIRECT: &str = include_str!("../shaders/physics/particle_indirect.wgsl");
const PARTICLE_MESH: &str = include_str!("../shaders/physics/particle_mesh.wgsl");
const PARTICLE_MESHLET: &str = include_str!("../shaders/physics/particle_meshlet.wgsl");
const PARTICLE_POINTCLOUD: &str = include_str!("../shaders/physics/particle_pointcloud.wgsl");
const PARTICLE_PROMOTE: &str = include_str!("../shaders/physics/particle_promote.wgsl");
const PARTICLE_RIBBON: &str = include_str!("../shaders/physics/particle_ribbon.wgsl");
const PARTICLE_SHARD: &str = include_str!("../shaders/physics/particle_shard.wgsl");
const PARTICLE_SIMULATE: &str = include_str!("../shaders/physics/particle_simulate.wgsl");
const PARTICLE_SPAWN: &str = include_str!("../shaders/physics/particle_spawn.wgsl");
const PARTICLE_STRETCHED: &str = include_str!("../shaders/physics/particle_stretched.wgsl");

// ── WGSL Structure Validation ──────────────────────────────────────────

/// Verify a shader source contains expected entry points and is non-empty.
fn validate_wgsl(name: &str, source: &str, expected_entries: &[&str]) {
    assert!(!source.is_empty(), "Shader {} is empty", name);
    assert!(!source.contains('\0'), "Shader {} contains null bytes", name);
    for entry in expected_entries {
        assert!(
            source.contains(entry),
            "Shader {} missing expected entry point or annotation: {}",
            name,
            entry
        );
    }
    // WGSL must have balanced braces.
    let opens = source.chars().filter(|c| *c == '{').count();
    let closes = source.chars().filter(|c| *c == '}').count();
    assert_eq!(
        opens, closes,
        "Shader {} has unbalanced braces: {} open, {} close",
        name, opens, closes
    );
}

// ── Core Shader Tests ──────────────────────────────────────────────────

#[test]
fn grid_shader_valid() {
    validate_wgsl("grid", GRID_WGSL, &["vs_main", "fs_main", "Uniforms"]);
    assert!(
        GRID_WGSL.contains("view_proj"),
        "Grid shader must reference view_proj uniform"
    );
    assert!(
        GRID_WGSL.contains("grid_spacing"),
        "Grid shader must reference grid_spacing"
    );
}

#[test]
fn axes_shader_valid() {
    validate_wgsl("axes", AXES_WGSL, &["vs_main", "fs_main", "Uniforms"]);
    assert!(
        AXES_WGSL.contains("axis_length"),
        "Axes shader must reference axis_length"
    );
}

#[test]
fn primitive_shader_valid() {
    validate_wgsl("primitive", PRIMITIVE_WGSL, &["vs_main", "fs_main", "Uniforms"]);
    assert!(
        PRIMITIVE_WGSL.contains("model"),
        "Primitive shader must have model matrix"
    );
    assert!(
        PRIMITIVE_WGSL.contains("light_dir"),
        "Primitive shader must have light direction"
    );
    assert!(
        PRIMITIVE_WGSL.contains("camera_pos"),
        "Primitive shader must have camera position"
    );
}

#[test]
fn particle_shader_valid() {
    validate_wgsl("particle", PARTICLE_WGSL, &["@compute", "@vertex", "@fragment"]);
}

#[test]
fn procedural_shader_valid() {
    validate_wgsl("procedural", PROCEDURAL_WGSL, &["@compute"]);
}

#[test]
fn sprite_shader_valid() {
    validate_wgsl("sprite", SPRITE_WGSL, &["@vertex", "@fragment"]);
}

#[test]
fn tween_shader_valid() {
    validate_wgsl("tween", TWEEN_WGSL, &["@compute"]);
}

#[test]
fn picking_shader_valid() {
    validate_wgsl("picking", PICKING_WGSL, &["vs_main", "fs_main", "Uniforms"]);
    assert!(
        PICKING_WGSL.contains("object_id"),
        "Picking shader must reference object_id"
    );
    assert!(
        PICKING_WGSL.contains("vec4<u32>"),
        "Picking shader must output vec4<u32>"
    );
}

#[test]
fn gizmo_shader_valid() {
    validate_wgsl("gizmo", GIZMO_WGSL, &["vs_main", "fs_main", "Uniforms"]);
    assert!(GIZMO_WGSL.contains("model"), "Gizmo shader must have model matrix");
    assert!(
        GIZMO_WGSL.contains("view_proj"),
        "Gizmo shader must have view_proj matrix"
    );
}

#[test]
fn textured_shader_valid() {
    validate_wgsl("textured", TEXTURED_WGSL, &["vs_main", "fs_main", "Uniforms"]);
    assert!(
        TEXTURED_WGSL.contains("t_diffuse"),
        "Textured shader must reference diffuse texture"
    );
    assert!(
        TEXTURED_WGSL.contains("s_diffuse"),
        "Textured shader must reference diffuse sampler"
    );
    assert!(
        TEXTURED_WGSL.contains("tex_coords"),
        "Textured shader must reference tex_coords"
    );
    assert!(
        TEXTURED_WGSL.contains("base_color_factor"),
        "Textured shader must reference base_color_factor"
    );
}

// ── Physics Shader Tests ────────────────────────────────────────────────

#[test]
fn physics_common_defines_shared_types() {
    assert!(!PHYSICS_COMMON.is_empty());
    assert!(
        PHYSICS_COMMON.contains("ParticleState"),
        "physics_common must define ParticleState"
    );
    assert!(
        PHYSICS_COMMON.contains("SimParams"),
        "physics_common must define SimParams"
    );
}

#[test]
fn physics_compute_shaders_valid() {
    let compute_shaders = [
        ("particle_collision", PARTICLE_COLLISION),
        ("particle_compact", PARTICLE_COMPACT),
        ("particle_indirect", PARTICLE_INDIRECT),
        ("particle_promote", PARTICLE_PROMOTE),
        ("particle_simulate", PARTICLE_SIMULATE),
        ("particle_spawn", PARTICLE_SPAWN),
    ];
    for (name, source) in compute_shaders {
        assert!(!source.is_empty(), "{} is empty", name);
        // Physics compute shaders are composed with physics_common.wgsl prefix.
        // They reference types defined there, so just check non-empty and balanced braces.
        let opens = source.chars().filter(|c| *c == '{').count();
        let closes = source.chars().filter(|c| *c == '}').count();
        assert_eq!(opens, closes, "{} has unbalanced braces", name);
    }
}

#[test]
fn physics_render_shaders_valid() {
    let render_shaders = [
        ("particle_billboard", PARTICLE_BILLBOARD),
        ("particle_beam", PARTICLE_BEAM),
        ("particle_mesh", PARTICLE_MESH),
        ("particle_meshlet", PARTICLE_MESHLET),
        ("particle_pointcloud", PARTICLE_POINTCLOUD),
        ("particle_ribbon", PARTICLE_RIBBON),
        ("particle_shard", PARTICLE_SHARD),
        ("particle_stretched", PARTICLE_STRETCHED),
    ];
    for (name, source) in render_shaders {
        assert!(!source.is_empty(), "{} is empty", name);
        let opens = source.chars().filter(|c| *c == '{').count();
        let closes = source.chars().filter(|c| *c == '}').count();
        assert_eq!(opens, closes, "{} has unbalanced braces", name);
    }
}

// ── Shader Count Inventory ──────────────────────────────────────────────

#[test]
fn shader_inventory_complete() {
    // 10 core shaders + 15 physics shaders + 8 DreamFabric shaders = 33 total.
    let core_count = 10; // grid, axes, primitive, textured, particle, procedural, sprite, tween, picking, gizmo
    let physics_count = 15; // physics_common + 6 compute + 8 render
    let fabric_count = 8; // quantum_cull, meshlet_vertex_fallback, meshlet_mesh, dreammatter_{simulate,emit,cull}, materialize, meshlet_utils
    let total = core_count + physics_count + fabric_count;
    assert_eq!(total, 33, "Expected 33 shaders in inventory");
}

// ── GPU Type Layout Tests ──────────────────────────────────────────────

#[test]
fn primitive_vertex_is_pod() {
    // Verify PrimitiveVertex is bytemuck-compatible (Pod + Zeroable).
    let v = dreamwell_gpu::primitives::PrimitiveVertex {
        position: [0.0; 3],
        normal: [0.0; 3],
        color: [1.0; 4],
    };
    let bytes = bytemuck::bytes_of(&v);
    assert_eq!(bytes.len(), 40);
}

#[test]
fn meshlet_struct_layout() {
    assert_eq!(std::mem::size_of::<dreamwell_gpu::mesh_shader::Meshlet>(), 16);
}

#[test]
fn all_primitive_shapes_have_meshlets() {
    use dreamwell_gpu::primitives::*;
    for shape in PrimitiveShape::ALL {
        let mesh = shape.generate(DEFAULT_PRIMITIVE_COLOR);
        assert!(!mesh.meshlets.is_empty(), "Shape {} generated 0 meshlets", shape.name());
        // Verify meshlet bounds.
        for (i, m) in mesh.meshlets.iter().enumerate() {
            assert!(
                m.vertex_count <= 64,
                "Shape {} meshlet {} has {} verts (max 64)",
                shape.name(),
                i,
                m.vertex_count
            );
            assert!(
                m.triangle_count <= 126,
                "Shape {} meshlet {} has {} tris (max 126)",
                shape.name(),
                i,
                m.triangle_count
            );
        }
    }
}

// ── GPU Feature Detection ──────────────────────────────────────────────

#[test]
fn capabilities_default_disabled() {
    let cap = dreamwell_gpu::features::GpuCapabilities {
        backend: wgpu::Backend::Vulkan,
        adapter_name: String::new(),
        driver_info: String::new(),
        depth_clip_control: false,
        timestamp_query: false,
        indirect_first_instance: false,
        immediates: false,
        max_immediate_size: 0,
        ray_tracing: false,
        mesh_shader: false,
        mesh_shader_multiview: false,
        max_blas_primitive_count: 0,
        max_blas_geometry_count: 0,
        max_tlas_instance_count: 0,
        max_mesh_output_layers: 0,
        max_mesh_multiview_view_count: 0,
    };
    assert!(!cap.ray_tracing);
    assert!(!cap.mesh_shader);
    let summary = cap.summary();
    assert!(summary.contains("Vulkan"));
}

// ── Transform Helpers ──────────────────────────────────────────────────

#[test]
fn identity_transform_is_correct() {
    let t = dreamwell_gpu::ray_tracing::IDENTITY_TRANSFORM;
    // Diagonal = 1, off-diagonal = 0
    assert_eq!(t[0], 1.0); // m00
    assert_eq!(t[5], 1.0); // m11
    assert_eq!(t[10], 1.0); // m22
    assert_eq!(t[3], 0.0); // tx
    assert_eq!(t[7], 0.0); // ty
    assert_eq!(t[11], 0.0); // tz
}

#[test]
fn translation_transform_offsets() {
    let t = dreamwell_gpu::ray_tracing::translation_transform(3.0, 5.0, 7.0);
    assert_eq!(t[3], 3.0);
    assert_eq!(t[7], 5.0);
    assert_eq!(t[11], 7.0);
    // Rotation part should be identity.
    assert_eq!(t[0], 1.0);
    assert_eq!(t[5], 1.0);
    assert_eq!(t[10], 1.0);
}
