///|
/// 3D mesh data with configurable vertex layout.

///|
pub let vertex3d_stride : Int = 8

///|
pub struct MeshBounds3D {
  min_x : Double
  min_y : Double
  min_z : Double
  max_x : Double
  max_y : Double
  max_z : Double
} derive(Debug)

///|
pub impl Show for MeshBounds3D with output(self, logger) {
  logger.write_object(to_repr(self))
}

///|
pub struct Mesh3D {
  vertex_data : Array[Double]
  indices : Array[Int]
  bounds : MeshBounds3D
  format : VertexFormat
} derive(Debug)

///|
pub impl Show for Mesh3D with output(self, logger) {
  logger.write_object(to_repr(self))
}

///|
/// Create a Mesh3D with standard_3d format (stride 8).
pub fn Mesh3D::new(vertex_data : Array[Double], indices : Array[Int]) -> Mesh3D {
  let format = VertexFormat::standard_3d()
  {
    vertex_data,
    indices,
    bounds: compute_mesh_bounds(vertex_data, format.stride()),
    format,
  }
}

///|
/// Create a Mesh3D with an explicit vertex format.
pub fn Mesh3D::new_with_format(
  vertex_data : Array[Double],
  indices : Array[Int],
  format : VertexFormat,
) -> Mesh3D {
  {
    vertex_data,
    indices,
    bounds: compute_mesh_bounds(vertex_data, format.stride()),
    format,
  }
}

///|
pub fn Mesh3D::vertex_count(self : Mesh3D) -> Int {
  self.vertex_data.length() / self.format.stride()
}

///|
pub fn Mesh3D::triangle_count(self : Mesh3D) -> Int {
  self.indices.length() / 3
}

///|
pub fn Mesh3D::bounds(self : Mesh3D) -> MeshBounds3D {
  self.bounds
}

///|
fn compute_mesh_bounds(
  vertex_data : Array[Double],
  stride : Int,
) -> MeshBounds3D {
  let vertex_count = vertex_data.length() / stride
  if vertex_count == 0 {
    return {
      min_x: 0.0,
      min_y: 0.0,
      min_z: 0.0,
      max_x: 0.0,
      max_y: 0.0,
      max_z: 0.0,
    }
  }
  let mut min_x = vertex_data[0]
  let mut min_y = vertex_data[1]
  let mut min_z = vertex_data[2]
  let mut max_x = min_x
  let mut max_y = min_y
  let mut max_z = min_z
  for i in 1.. max_x {
      max_x = x
    }
    if y > max_y {
      max_y = y
    }
    if z > max_z {
      max_z = z
    }
  }
  { min_x, min_y, min_z, max_x, max_y, max_z }
}

///|
pub fn push_vertex(
  data : Array[Double],
  px : Double,
  py : Double,
  pz : Double,
  nx : Double,
  ny : Double,
  nz : Double,
  u : Double,
  v : Double,
) -> Unit {
  data.push(px)
  data.push(py)
  data.push(pz)
  data.push(nx)
  data.push(ny)
  data.push(nz)
  data.push(u)
  data.push(v)
}

///|
/// Unit cube centered at origin with side length `size`.
pub fn Mesh3D::cube(size : Double) -> Mesh3D {
  let h = size / 2.0
  let data : Array[Double] = []
  let indices : Array[Int] = []
  let mut base = 0
  // +Z face (front)
  push_vertex(data, -h, -h, h, 0.0, 0.0, 1.0, 0.0, 1.0)
  push_vertex(data, h, -h, h, 0.0, 0.0, 1.0, 1.0, 1.0)
  push_vertex(data, h, h, h, 0.0, 0.0, 1.0, 1.0, 0.0)
  push_vertex(data, -h, h, h, 0.0, 0.0, 1.0, 0.0, 0.0)
  indices.push(base + 0)
  indices.push(base + 1)
  indices.push(base + 2)
  indices.push(base + 2)
  indices.push(base + 3)
  indices.push(base + 0)
  base = base + 4
  // -Z face (back)
  push_vertex(data, h, -h, -h, 0.0, 0.0, -1.0, 0.0, 1.0)
  push_vertex(data, -h, -h, -h, 0.0, 0.0, -1.0, 1.0, 1.0)
  push_vertex(data, -h, h, -h, 0.0, 0.0, -1.0, 1.0, 0.0)
  push_vertex(data, h, h, -h, 0.0, 0.0, -1.0, 0.0, 0.0)
  indices.push(base + 0)
  indices.push(base + 1)
  indices.push(base + 2)
  indices.push(base + 2)
  indices.push(base + 3)
  indices.push(base + 0)
  base = base + 4
  // +X face (right)
  push_vertex(data, h, -h, h, 1.0, 0.0, 0.0, 0.0, 1.0)
  push_vertex(data, h, -h, -h, 1.0, 0.0, 0.0, 1.0, 1.0)
  push_vertex(data, h, h, -h, 1.0, 0.0, 0.0, 1.0, 0.0)
  push_vertex(data, h, h, h, 1.0, 0.0, 0.0, 0.0, 0.0)
  indices.push(base + 0)
  indices.push(base + 1)
  indices.push(base + 2)
  indices.push(base + 2)
  indices.push(base + 3)
  indices.push(base + 0)
  base = base + 4
  // -X face (left)
  push_vertex(data, -h, -h, -h, -1.0, 0.0, 0.0, 0.0, 1.0)
  push_vertex(data, -h, -h, h, -1.0, 0.0, 0.0, 1.0, 1.0)
  push_vertex(data, -h, h, h, -1.0, 0.0, 0.0, 1.0, 0.0)
  push_vertex(data, -h, h, -h, -1.0, 0.0, 0.0, 0.0, 0.0)
  indices.push(base + 0)
  indices.push(base + 1)
  indices.push(base + 2)
  indices.push(base + 2)
  indices.push(base + 3)
  indices.push(base + 0)
  base = base + 4
  // +Y face (top)
  push_vertex(data, -h, h, h, 0.0, 1.0, 0.0, 0.0, 1.0)
  push_vertex(data, h, h, h, 0.0, 1.0, 0.0, 1.0, 1.0)
  push_vertex(data, h, h, -h, 0.0, 1.0, 0.0, 1.0, 0.0)
  push_vertex(data, -h, h, -h, 0.0, 1.0, 0.0, 0.0, 0.0)
  indices.push(base + 0)
  indices.push(base + 1)
  indices.push(base + 2)
  indices.push(base + 2)
  indices.push(base + 3)
  indices.push(base + 0)
  base = base + 4
  // -Y face (bottom)
  push_vertex(data, -h, -h, -h, 0.0, -1.0, 0.0, 0.0, 1.0)
  push_vertex(data, h, -h, -h, 0.0, -1.0, 0.0, 1.0, 1.0)
  push_vertex(data, h, -h, h, 0.0, -1.0, 0.0, 1.0, 0.0)
  push_vertex(data, -h, -h, h, 0.0, -1.0, 0.0, 0.0, 0.0)
  indices.push(base + 0)
  indices.push(base + 1)
  indices.push(base + 2)
  indices.push(base + 2)
  indices.push(base + 3)
  indices.push(base + 0)
  Mesh3D::new(data, indices)
}

///|
/// XZ plane centered at origin with given width and depth.
pub fn Mesh3D::plane(
  width : Double,
  depth : Double,
  subdivisions? : Int = 4,
) -> Mesh3D {
  let hw = width / 2.0
  let hd = depth / 2.0
  let n = subdivisions
  let data : Array[Double] = []
  let indices : Array[Int] = []
  // Generate (n+1)×(n+1) grid of vertices
  for row in 0..<=n {
    let t = row.to_double() / n.to_double()
    let z = -hd + t * depth
    let v = t
    for col in 0..<=n {
      let s = col.to_double() / n.to_double()
      let x = -hw + s * width
      let u = s
      push_vertex(data, x, 0.0, z, 0.0, 1.0, 0.0, u, v)
    }
  }
  // Generate triangles (CCW winding when viewed from above: +Y direction)
  let cols = n + 1
  for row in 0.. Mesh3D {
  let data : Array[Double] = []
  let indices : Array[Int] = []
  for ring in 0..<=rings {
    let phi = @math.PI * ring.to_double() / rings.to_double()
    let sin_phi = @math.sin(phi)
    let cos_phi = @math.cos(phi)
    for seg in 0..<=segments {
      let theta = 2.0 * @math.PI * seg.to_double() / segments.to_double()
      let sin_theta = @math.sin(theta)
      let cos_theta = @math.cos(theta)
      let nx = cos_theta * sin_phi
      let ny = cos_phi
      let nz = sin_theta * sin_phi
      let u = seg.to_double() / segments.to_double()
      let v = ring.to_double() / rings.to_double()
      push_vertex(data, radius * nx, radius * ny, radius * nz, nx, ny, nz, u, v)
    }
  }
  let cols = segments + 1
  for ring in 0.. Unit {
  indices.push(base)
  indices.push(base + 1)
  indices.push(base + 2)
  indices.push(base + 2)
  indices.push(base + 3)
  indices.push(base)
}

///|
/// Box mesh with given width (X), height (Y), depth (Z), bottom at y=0.
/// 5 faces (no bottom face): front, back, right, left, top.
pub fn Mesh3D::box(width : Double, height : Double, depth : Double) -> Mesh3D {
  let hw = width / 2.0
  let hd = depth / 2.0
  let data : Array[Double] = []
  let indices : Array[Int] = []
  let mut base = 0
  // +Z face (front)
  push_vertex(data, -hw, 0.0, hd, 0.0, 0.0, 1.0, 0.0, 1.0)
  push_vertex(data, hw, 0.0, hd, 0.0, 0.0, 1.0, 1.0, 1.0)
  push_vertex(data, hw, height, hd, 0.0, 0.0, 1.0, 1.0, 0.0)
  push_vertex(data, -hw, height, hd, 0.0, 0.0, 1.0, 0.0, 0.0)
  push_quad(indices, base)
  base = base + 4
  // -Z face (back)
  push_vertex(data, hw, 0.0, -hd, 0.0, 0.0, -1.0, 0.0, 1.0)
  push_vertex(data, -hw, 0.0, -hd, 0.0, 0.0, -1.0, 1.0, 1.0)
  push_vertex(data, -hw, height, -hd, 0.0, 0.0, -1.0, 1.0, 0.0)
  push_vertex(data, hw, height, -hd, 0.0, 0.0, -1.0, 0.0, 0.0)
  push_quad(indices, base)
  base = base + 4
  // +X face (right)
  push_vertex(data, hw, 0.0, hd, 1.0, 0.0, 0.0, 0.0, 1.0)
  push_vertex(data, hw, 0.0, -hd, 1.0, 0.0, 0.0, 1.0, 1.0)
  push_vertex(data, hw, height, -hd, 1.0, 0.0, 0.0, 1.0, 0.0)
  push_vertex(data, hw, height, hd, 1.0, 0.0, 0.0, 0.0, 0.0)
  push_quad(indices, base)
  base = base + 4
  // -X face (left)
  push_vertex(data, -hw, 0.0, -hd, -1.0, 0.0, 0.0, 0.0, 1.0)
  push_vertex(data, -hw, 0.0, hd, -1.0, 0.0, 0.0, 1.0, 1.0)
  push_vertex(data, -hw, height, hd, -1.0, 0.0, 0.0, 1.0, 0.0)
  push_vertex(data, -hw, height, -hd, -1.0, 0.0, 0.0, 0.0, 0.0)
  push_quad(indices, base)
  base = base + 4
  // +Y face (top)
  push_vertex(data, -hw, height, hd, 0.0, 1.0, 0.0, 0.0, 1.0)
  push_vertex(data, hw, height, hd, 0.0, 1.0, 0.0, 1.0, 1.0)
  push_vertex(data, hw, height, -hd, 0.0, 1.0, 0.0, 1.0, 0.0)
  push_vertex(data, -hw, height, -hd, 0.0, 1.0, 0.0, 0.0, 0.0)
  push_quad(indices, base)
  ignore(base)
  Mesh3D::new(data, indices)
}