///|
fn bounds_from_arrays(
  values : FixedArray[Float],
  ints : FixedArray[Int],
) -> Bounds {
  {
    center_x: values[0],
    center_y: values[1],
    center_z: values[2],
    radius: values[3],
    cone_apex_x: values[4],
    cone_apex_y: values[5],
    cone_apex_z: values[6],
    cone_axis_x: values[7],
    cone_axis_y: values[8],
    cone_axis_z: values[9],
    cone_cutoff: values[10],
    cone_axis_s8_x: ints[0],
    cone_axis_s8_y: ints[1],
    cone_axis_s8_z: ints[2],
    cone_cutoff_s8: ints[3],
  }
}

///|
pub fn build_meshlets_bound(
  index_count : Int,
  max_vertices : Int,
  max_triangles : Int,
) -> Int raise MeshoptError {
  require_triangle_indices(index_count)
  require(
    max_vertices > 0 && max_vertices <= 256,
    "max vertices must be in 1..=256",
  )
  require(
    max_triangles > 0 && max_triangles <= 512,
    "max triangles must be in 1..=512",
  )
  build_meshlets_bound_ffi(index_count, max_vertices, max_triangles)
}

///|
pub fn build_meshlets(
  indices : FixedArray[UInt],
  positions : FixedArray[Float],
  vertex_count : Int,
  positions_stride : Int,
  max_vertices : Int,
  max_triangles : Int,
  cone_weight? : Float = 0.0F,
) -> MeshletBuild raise MeshoptError {
  require_triangle_indices(indices.length())
  require_float_stream(
    positions, vertex_count, positions_stride, 12, "positions",
  )
  let bound = build_meshlets_bound(
    indices.length(),
    max_vertices,
    max_triangles,
  )
  let meshlets = FixedArray::make(bound * 4, 0U)
  let vertices = FixedArray::make(indices.length(), 0U)
  let triangles = Bytes::make(indices.length(), 0)
  let int_params : FixedArray[Int] = [
    indices.length(),
    vertex_count,
    positions_stride,
    max_vertices,
    max_triangles,
  ]
  let float_params : FixedArray[Float] = [cone_weight]
  let meshlet_count = build_meshlets_ffi(
    meshlets, vertices, triangles, indices, positions, int_params, float_params,
  )
  { meshlets, vertices, triangles, meshlet_count }
}

///|
pub fn build_meshlets_scan(
  indices : FixedArray[UInt],
  vertex_count : Int,
  max_vertices : Int,
  max_triangles : Int,
) -> MeshletBuild raise MeshoptError {
  require_triangle_indices(indices.length())
  require(vertex_count >= 0, "vertex count must be non-negative")
  let bound = build_meshlets_bound(
    indices.length(),
    max_vertices,
    max_triangles,
  )
  let meshlets = FixedArray::make(bound * 4, 0U)
  let vertices = FixedArray::make(indices.length(), 0U)
  let triangles = Bytes::make(indices.length(), 0)
  let meshlet_count = build_meshlets_scan_ffi(
    meshlets,
    vertices,
    triangles,
    indices,
    indices.length(),
    vertex_count,
    max_vertices,
    max_triangles,
  )
  { meshlets, vertices, triangles, meshlet_count }
}

///|
pub fn build_meshlets_flex(
  indices : FixedArray[UInt],
  positions : FixedArray[Float],
  vertex_count : Int,
  positions_stride : Int,
  max_vertices : Int,
  min_triangles : Int,
  max_triangles : Int,
  cone_weight? : Float = 0.0F,
  split_factor? : Float = 1.0F,
) -> MeshletBuild raise MeshoptError {
  require_triangle_indices(indices.length())
  require_float_stream(
    positions, vertex_count, positions_stride, 12, "positions",
  )
  require(min_triangles > 0, "minimum triangles must be positive")
  let bound = build_meshlets_bound(
    indices.length(),
    max_vertices,
    max_triangles,
  )
  let meshlets = FixedArray::make(bound * 4, 0U)
  let vertices = FixedArray::make(indices.length(), 0U)
  let triangles = Bytes::make(indices.length(), 0)
  let int_params : FixedArray[Int] = [
    indices.length(),
    vertex_count,
    positions_stride,
    max_vertices,
    min_triangles,
    max_triangles,
  ]
  let float_params : FixedArray[Float] = [cone_weight, split_factor]
  let meshlet_count = build_meshlets_flex_ffi(
    meshlets, vertices, triangles, indices, positions, int_params, float_params,
  )
  { meshlets, vertices, triangles, meshlet_count }
}

///|
pub fn build_meshlets_spatial(
  indices : FixedArray[UInt],
  positions : FixedArray[Float],
  vertex_count : Int,
  positions_stride : Int,
  max_vertices : Int,
  min_triangles : Int,
  max_triangles : Int,
  fill_weight? : Float = 0.5F,
) -> MeshletBuild raise MeshoptError {
  require_triangle_indices(indices.length())
  require_float_stream(
    positions, vertex_count, positions_stride, 12, "positions",
  )
  require(min_triangles > 0, "minimum triangles must be positive")
  let bound = build_meshlets_bound(
    indices.length(),
    max_vertices,
    max_triangles,
  )
  let meshlets = FixedArray::make(bound * 4, 0U)
  let vertices = FixedArray::make(indices.length(), 0U)
  let triangles = Bytes::make(indices.length(), 0)
  let int_params : FixedArray[Int] = [
    indices.length(),
    vertex_count,
    positions_stride,
    max_vertices,
    min_triangles,
    max_triangles,
  ]
  let float_params : FixedArray[Float] = [fill_weight]
  let meshlet_count = build_meshlets_spatial_ffi(
    meshlets, vertices, triangles, indices, positions, int_params, float_params,
  )
  { meshlets, vertices, triangles, meshlet_count }
}

///|
pub fn optimize_meshlet(
  meshlet_vertices : FixedArray[UInt],
  meshlet_triangles : Bytes,
  triangle_count : Int,
  vertex_count : Int,
) -> Unit raise MeshoptError {
  require(triangle_count >= 0, "triangle count must be non-negative")
  require(vertex_count >= 0, "vertex count must be non-negative")
  require_fixedarray_length(meshlet_vertices, vertex_count, "meshlet vertices")
  require_bytes_length(
    meshlet_triangles,
    triangle_count * 3,
    "meshlet triangles",
  )
  optimize_meshlet_ffi(
    meshlet_vertices, meshlet_triangles, triangle_count, vertex_count,
  )
}

///|
pub fn optimize_meshlet_level(
  meshlet_vertices : FixedArray[UInt],
  meshlet_triangles : Bytes,
  triangle_count : Int,
  vertex_count : Int,
  level : Int,
) -> Unit raise MeshoptError {
  require(
    level >= 0 && level <= 9,
    "meshlet optimization level must be in 0..=9",
  )
  require(triangle_count >= 0, "triangle count must be non-negative")
  require(vertex_count >= 0, "vertex count must be non-negative")
  require_fixedarray_length(meshlet_vertices, vertex_count, "meshlet vertices")
  require_bytes_length(
    meshlet_triangles,
    triangle_count * 3,
    "meshlet triangles",
  )
  optimize_meshlet_level_ffi(
    meshlet_vertices, vertex_count, meshlet_triangles, triangle_count, level,
  )
}

///|
pub fn extract_meshlet_indices(
  indices : FixedArray[UInt],
) -> (FixedArray[UInt], Bytes, Int) raise MeshoptError {
  require_triangle_indices(indices.length())
  let vertices = FixedArray::make(indices.length(), 0U)
  let triangles = Bytes::make(indices.length(), 0)
  let vertex_count = extract_meshlet_indices_ffi(
    vertices,
    triangles,
    indices,
    indices.length(),
  )
  (vertices, triangles, vertex_count)
}

///|
pub fn encode_meshlet_bound(
  max_vertices : Int,
  max_triangles : Int,
) -> Int raise MeshoptError {
  require(
    max_vertices > 0 && max_vertices <= 256,
    "max vertices must be in 1..=256",
  )
  require(
    max_triangles > 0 && max_triangles <= 512,
    "max triangles must be in 1..=512",
  )
  encode_meshlet_bound_ffi(max_vertices, max_triangles)
}

///|
pub fn encode_meshlet(
  vertices : FixedArray[UInt],
  vertex_count : Int,
  triangles : Bytes,
  triangle_count : Int,
) -> MeshoptData raise MeshoptError {
  require(vertex_count >= 0, "vertex count must be non-negative")
  require(triangle_count >= 0, "triangle count must be non-negative")
  require_fixedarray_length(vertices, vertex_count, "meshlet vertices")
  require_bytes_length(triangles, triangle_count * 3, "meshlet triangles")
  let buffer = Bytes::make(
    encode_meshlet_bound(vertex_count, triangle_count),
    0,
  )
  let length = encode_meshlet_ffi(
    buffer,
    buffer.length(),
    vertices,
    vertex_count,
    triangles,
    triangle_count,
  )
  if length == 0 {
    raise MeshoptError("meshlet encoding failed")
  }
  shrink_bytes(buffer, length)
  { data: buffer, length }
}

///|
pub fn decode_meshlet_raw(
  encoded : Bytes,
  vertex_count : Int,
  triangle_count : Int,
) -> MeshletDecodeRaw raise MeshoptError {
  require(vertex_count >= 0, "vertex count must be non-negative")
  require(triangle_count >= 0, "triangle count must be non-negative")
  let vertices = FixedArray::make(vertex_count, 0U)
  let triangles = FixedArray::make(triangle_count * 3, 0U)
  let status = decode_meshlet_raw_ffi(
    vertices,
    vertex_count,
    triangles,
    triangle_count,
    encoded,
    encoded.length(),
  )
  if status != 0 {
    raise MeshoptError("meshlet decoding failed")
  }
  { vertices, triangles }
}

///|
pub fn decode_meshlet(
  encoded : Bytes,
  vertex_count : Int,
  vertex_size : Int,
  triangle_count : Int,
  triangle_size : Int,
) -> MeshletDecode raise MeshoptError {
  require(vertex_count >= 0, "vertex count must be non-negative")
  require(vertex_size > 0, "vertex size must be positive")
  require(triangle_count >= 0, "triangle count must be non-negative")
  require(
    triangle_size == 3 || triangle_size == 4,
    "triangle size must be 3 or 4 bytes",
  )
  let vertices = Bytes::make(vertex_count * vertex_size, 0)
  let triangles = Bytes::make(triangle_count * triangle_size, 0)
  let status = decode_meshlet_ffi(
    vertices,
    vertex_count,
    vertex_size,
    triangles,
    triangle_count,
    triangle_size,
    encoded,
    encoded.length(),
  )
  if status != 0 {
    raise MeshoptError("meshlet decoding failed")
  }
  { vertices, triangles }
}

///|
pub fn compute_cluster_bounds(
  indices : FixedArray[UInt],
  positions : FixedArray[Float],
  vertex_count : Int,
  positions_stride : Int,
) -> Bounds raise MeshoptError {
  require_triangle_indices(indices.length())
  require_float_stream(
    positions, vertex_count, positions_stride, 12, "positions",
  )
  let values = FixedArray::make(11, 0.0F)
  let ints = FixedArray::make(4, 0)
  compute_cluster_bounds_ffi(
    values,
    ints,
    indices,
    indices.length(),
    positions,
    vertex_count,
    positions_stride,
  )
  bounds_from_arrays(values, ints)
}

///|
pub fn compute_meshlet_bounds(
  meshlet_vertices : FixedArray[UInt],
  meshlet_triangles : Bytes,
  triangle_count : Int,
  positions : FixedArray[Float],
  vertex_count : Int,
  positions_stride : Int,
) -> Bounds raise MeshoptError {
  require(triangle_count >= 0, "triangle count must be non-negative")
  require_float_stream(
    positions, vertex_count, positions_stride, 12, "positions",
  )
  require_bytes_length(
    meshlet_triangles,
    triangle_count * 3,
    "meshlet triangles",
  )
  let values = FixedArray::make(11, 0.0F)
  let ints = FixedArray::make(4, 0)
  compute_meshlet_bounds_ffi(
    values, ints, meshlet_vertices, meshlet_triangles, triangle_count, positions,
    vertex_count, positions_stride,
  )
  bounds_from_arrays(values, ints)
}

///|
pub fn compute_sphere_bounds(
  positions : FixedArray[Float],
  count : Int,
  positions_stride : Int,
  radii? : FixedArray[Float] = [],
  radii_stride? : Int = 0,
) -> Bounds raise MeshoptError {
  require_float_stream(positions, count, positions_stride, 12, "positions")
  if radii_stride > 0 {
    require_float_stream(radii, count, radii_stride, 4, "radii")
  }
  let values = FixedArray::make(11, 0.0F)
  let ints = FixedArray::make(4, 0)
  compute_sphere_bounds_ffi(
    values,
    ints,
    positions,
    count,
    positions_stride,
    radii,
    radii_stride,
    if radii_stride > 0 {
      count
    } else {
      0
    },
  )
  bounds_from_arrays(values, ints)
}