//! The drawing model: everything is `mesh + material + transform`.
//!
//! A sprite is not a privileged primitive with its own `draw_sprite(x, y)` path.
//! It is the trivial case: a unit quad ([`Mesh::quad`]) with a textured-unlit
//! [`Material`] and a [`Transform`]. This is what makes the 2D->3D move additive:
//! a 3D model is the same `Drawable` shape with a richer mesh and material.

use crate::color::Color;
use crate::content::TextureId;
use crate::math::{Vec2, Vec3};
use crate::transform::Transform;

/// One GPU vertex. `#[repr(C)]` + `Pod`/`Zeroable` so it can be uploaded with
/// `bytemuck::cast_slice` straight into a wgpu vertex buffer.
#[repr(C)]
// PartialEq: the batching backend detects "this mesh IS the shared unit
// quad" by comparing vertices, so equality is part of the contract (v0.2).
#[derive(Debug, Copy, Clone, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Vertex {
    pub position: [f32; 3], // @location(0), unchanged
    /// NEW in v0.3 (layout version 2): the surface normal the lit material
    /// consumes. Conventional 3D ordering puts geometry first, so uv and
    /// color SHIFTED locations; every WGSL shader consuming Vertex (and the
    /// instance buffer, which numbers after it) updated in the same commit.
    /// Quads get (0, 0, 1): facing the 2D camera.
    pub normal: [f32; 3], // @location(1), NEW
    pub uv: [f32; 2],     // @location(2), was 1
    pub color: [f32; 4],  // @location(3), was 2
}

impl Vertex {
    /// The 2D constructor: normal defaults to +Z (facing the ortho camera),
    /// so every existing sprite call site keeps compiling unchanged.
    pub fn new(position: Vec3, uv: Vec2, color: Color) -> Self {
        Self {
            position: position.to_array(),
            normal: [0.0, 0.0, 1.0],
            uv: uv.to_array(),
            color: color.to_array(),
        }
    }

    /// The 3D constructor: geometry carries its real normal.
    pub fn with_normal(position: Vec3, normal: Vec3, uv: Vec2, color: Color) -> Self {
        Self {
            position: position.to_array(),
            normal: normal.to_array(),
            uv: uv.to_array(),
            color: color.to_array(),
        }
    }

    /// The vertex buffer layout the wgpu backend uses. Kept next to the struct
    /// so the two never drift apart.
    #[cfg(feature = "runtime")]
    pub fn layout() -> wgpu::VertexBufferLayout<'static> {
        use std::mem::size_of;
        wgpu::VertexBufferLayout {
            array_stride: size_of::<Vertex>() as wgpu::BufferAddress,
            step_mode: wgpu::VertexStepMode::Vertex,
            attributes: &[
                // position
                wgpu::VertexAttribute {
                    offset: 0,
                    shader_location: 0,
                    format: wgpu::VertexFormat::Float32x3,
                },
                // normal (v0.3)
                wgpu::VertexAttribute {
                    offset: (size_of::<[f32; 3]>()) as wgpu::BufferAddress,
                    shader_location: 1,
                    format: wgpu::VertexFormat::Float32x3,
                },
                // uv
                wgpu::VertexAttribute {
                    offset: (size_of::<[f32; 6]>()) as wgpu::BufferAddress,
                    shader_location: 2,
                    format: wgpu::VertexFormat::Float32x2,
                },
                // color
                wgpu::VertexAttribute {
                    offset: (size_of::<[f32; 8]>()) as wgpu::BufferAddress,
                    shader_location: 3,
                    format: wgpu::VertexFormat::Float32x4,
                },
            ],
        }
    }
}

/// CPU-side geometry. The backend uploads this to GPU buffers.
#[derive(Debug, Clone)]
pub struct Mesh {
    pub vertices: Vec<Vertex>,
    pub indices: Vec<u32>,
}

impl Mesh {
    /// A unit quad spanning `(0,0)..(1,1)` on the z=0 plane, UVs `(0,0)..(1,1)`,
    /// white vertex color. Scale/position it with a [`Transform`]. This is the
    /// sprite primitive.
    pub fn quad() -> Mesh {
        let white = Color::WHITE;
        let vertices = vec![
            Vertex::new(Vec3::new(0.0, 0.0, 0.0), Vec2::new(0.0, 0.0), white), // TL
            Vertex::new(Vec3::new(1.0, 0.0, 0.0), Vec2::new(1.0, 0.0), white), // TR
            Vertex::new(Vec3::new(1.0, 1.0, 0.0), Vec2::new(1.0, 1.0), white), // BR
            Vertex::new(Vec3::new(0.0, 1.0, 0.0), Vec2::new(0.0, 1.0), white), // BL
        ];
        let indices = vec![0, 1, 2, 0, 2, 3];
        Mesh { vertices, indices }
    }

    /// A unit cube spanning `(0,0,0)..(1,1,1)`: 24 vertices (four per face,
    /// so each face carries its own outward normal) and 36 indices, wound
    /// counter-clockwise SEEN FROM OUTSIDE, per the decided convention
    /// (right-handed, +Y up, CCW front faces): the lit pipeline back-culls,
    /// so winding is now a correctness property, not a comment.
    pub fn cube() -> Mesh {
        let white = Color::WHITE;
        // (face corners, outward normal), corners CCW from outside.
        let faces: [([[f32; 3]; 4], Vec3); 6] = [
            // +Z (front)
            ([[0., 0., 1.], [1., 0., 1.], [1., 1., 1.], [0., 1., 1.]], Vec3::Z),
            // -Z (back)
            ([[1., 0., 0.], [0., 0., 0.], [0., 1., 0.], [1., 1., 0.]], -Vec3::Z),
            // +X (right)
            ([[1., 0., 1.], [1., 0., 0.], [1., 1., 0.], [1., 1., 1.]], Vec3::X),
            // -X (left)
            ([[0., 0., 0.], [0., 0., 1.], [0., 1., 1.], [0., 1., 0.]], -Vec3::X),
            // +Y (top)
            ([[0., 1., 1.], [1., 1., 1.], [1., 1., 0.], [0., 1., 0.]], Vec3::Y),
            // -Y (bottom)
            ([[0., 0., 0.], [1., 0., 0.], [1., 0., 1.], [0., 0., 1.]], -Vec3::Y),
        ];
        let uvs = [
            Vec2::new(0.0, 0.0),
            Vec2::new(1.0, 0.0),
            Vec2::new(1.0, 1.0),
            Vec2::new(0.0, 1.0),
        ];
        let mut vertices = Vec::with_capacity(24);
        let mut indices = Vec::with_capacity(36);
        for (corners, normal) in faces {
            let base = vertices.len() as u32;
            for (corner, uv) in corners.iter().zip(uvs) {
                vertices.push(Vertex::with_normal(Vec3::from_array(*corner), normal, uv, white));
            }
            indices.extend_from_slice(&[base, base + 1, base + 2, base, base + 2, base + 3]);
        }
        Mesh { vertices, indices }
    }
}

/// Which shading model a material asks for (v0.3): the backend picks the
/// pipeline with an exhaustive `match`, so adding a third model forces
/// every pick site to handle it before the crate compiles again.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Shading {
    /// Texture times tint, no lighting: every 2D sprite, and any 3D mesh
    /// that wants to glow flat.
    #[default]
    Unlit,
    /// One directional light, one Lambert dot product, consuming the
    /// vertex normals (v0.3). The texture-times-tint multiply is preserved
    /// under the light term.
    Lit,
}

/// How a mesh is shaded: an optional texture multiplied by a base color,
/// plus the shading model choice (v0.3).
#[derive(Debug, Clone)]
pub struct Material {
    pub texture: Option<TextureId>,
    pub base_color: Color,
    /// Which rectangle of the texture this drawable samples, as a NORMALIZED
    /// `[min_x, min_y, size_x, size_y]`. The default `[0,0,1,1]` means "the
    /// whole texture"; a sprite on a texture atlas carries its region here
    /// (XNA's `sourceRectangle`, normalized). Rides per instance (v0.2).
    pub uv_rect: [f32; 4],
    pub shading: Shading,
}

impl Default for Material {
    fn default() -> Self {
        Self {
            texture: None,
            base_color: Color::WHITE,
            uv_rect: [0.0, 0.0, 1.0, 1.0],
            shading: Shading::Unlit,
        }
    }
}

/// Anything the renderer can draw. The whole rendering API is expressed in terms
/// of this trait, so user game objects implement it directly.
pub trait Drawable {
    fn mesh(&self) -> &Mesh;
    fn material(&self) -> &Material;
    fn transform(&self) -> Transform;
}

/// The convenience 2D primitive. A textured quad with a tint.
///
/// NOTE: this owns its own quad mesh for clarity. A production renderer would
/// share one unit-quad mesh across all sprites and batch them; that is an
/// optimization for later (see handoff.md "open questions"), not an API change,
/// because batching lives entirely inside the backend.
#[derive(Debug, Clone)]
pub struct Sprite {
    mesh: Mesh,
    material: Material,
    pub transform: Transform,
}

impl Sprite {
    /// A sprite of pixel size `w x h` at `(x, y)`, drawn with `texture`.
    pub fn new(texture: TextureId, x: f32, y: f32, w: f32, h: f32) -> Self {
        Self {
            mesh: Mesh::quad(),
            material: Material {
                texture: Some(texture),
                ..Default::default()
            },
            transform: Transform::from_xy_size(x, y, w, h),
        }
    }

    /// A solid-color sprite (no texture), useful before you have art.
    pub fn solid(color: Color, x: f32, y: f32, w: f32, h: f32) -> Self {
        Self {
            mesh: Mesh::quad(),
            material: Material {
                base_color: color,
                ..Default::default()
            },
            transform: Transform::from_xy_size(x, y, w, h),
        }
    }

    pub fn with_tint(mut self, color: Color) -> Self {
        self.material.base_color = color;
        self
    }

    /// Sample only `uv_rect` (normalized `[min_x, min_y, size_x, size_y]`) of
    /// the texture: the atlas-region hook. See `content::AtlasBuilder`.
    pub fn with_uv_rect(mut self, uv_rect: [f32; 4]) -> Self {
        self.material.uv_rect = uv_rect;
        self
    }

    /// Set the uv rect in place: the per-frame form animation uses
    /// (`sprite.set_uv_rect(sheet.frame_rect(anim.frame()))`).
    pub fn set_uv_rect(&mut self, rect: [f32; 4]) {
        self.material.uv_rect = rect;
    }
}

/// Flip a uv rect horizontally: start at the RIGHT edge, negative width.
/// The shader's remap, `rect.min + uv * rect.size`, then walks the texels
/// right to left. No transform change (which would mirror around the quad's
/// left edge, poison AABBs, and flip winding for future 3D culling), no
/// shader change: the one negation `SpriteEffects.FlipHorizontally` hid.
pub fn flip_x(r: [f32; 4]) -> [f32; 4] {
    [r[0] + r[2], r[1], -r[2], r[3]]
}

/// An arbitrary mesh + material + transform: the general drawable (v0.3).
/// A [`Sprite`] is the quad special case; a `Model` is anything else - a
/// cube, a loaded glTF mesh. Same trait, same draw call, same seams.
#[derive(Debug, Clone)]
pub struct Model {
    pub mesh: Mesh,
    pub material: Material,
    pub transform: Transform,
}

impl Model {
    /// A lit unit cube, ready to scale and place: the v0.3 hello-triangle.
    pub fn lit_cube() -> Self {
        Self {
            mesh: Mesh::cube(),
            material: Material { shading: Shading::Lit, ..Default::default() },
            transform: Transform::default(),
        }
    }
}

impl Drawable for Model {
    fn mesh(&self) -> &Mesh {
        &self.mesh
    }
    fn material(&self) -> &Material {
        &self.material
    }
    fn transform(&self) -> Transform {
        self.transform
    }
}

impl Drawable for Sprite {
    fn mesh(&self) -> &Mesh {
        &self.mesh
    }
    fn material(&self) -> &Material {
        &self.material
    }
    fn transform(&self) -> Transform {
        self.transform
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn quad_has_two_triangles() {
        let q = Mesh::quad();
        assert_eq!(q.vertices.len(), 4);
        assert_eq!(q.indices.len(), 6);
    }

    #[test]
    fn solid_sprite_has_no_texture() {
        let s = Sprite::solid(Color::RED, 0.0, 0.0, 32.0, 32.0);
        assert!(s.material().texture.is_none());
        assert_eq!(s.material().base_color, Color::RED);
    }
}
