///|
pub fn backend_id() -> String {
  "rapier3d-0.5.1"
}

///|
pub fn supports_native_world() -> Bool {
  true
}

///|
/// Native Rapier 3D adapter. Rapier handles stay private so applications keep
/// the stable neutral `BodyId`/`ColliderId` contract from `moui_3d_physics`.
pub struct RapierWorld {
  gravity : @moui_3d.Vec3
  bodies : @rapier_dynamics.RigidBodySet3D
  colliders : @rapier_collision.ColliderSet3D
  pipeline : @rapier_pipeline.PhysicsPipeline3DReal
  islands : @rapier_dynamics.IslandManager3D
  broad_phase : @rapier_collision.BroadPhase3D
  narrow_phase : @rapier_collision.NarrowPhase3D
  body_handles : Map[@moui_3d_physics.BodyId, @rapier_dynamics.RigidBodyHandle]
  collider_handles : Map[
    @moui_3d_physics.ColliderId,
    @rapier_collision.ColliderHandle3D,
  ]
  mut next_body : Int64
  mut next_collider : Int64
}

///|
pub fn RapierWorld::new(
  gravity? : @moui_3d.Vec3 = @moui_3d.Vec3::new(x=0.0, y=-9.81, z=0.0),
) -> RapierWorld {
  {
    gravity,
    bodies: RigidBodySet3D(),
    colliders: ColliderSet3D(),
    pipeline: PhysicsPipeline3DReal(),
    islands: IslandManager3D(),
    broad_phase: BroadPhase3D(),
    narrow_phase: NarrowPhase3D(),
    body_handles: {},
    collider_handles: {},
    next_body: 1L,
    next_collider: 1L,
  }
}

///|
pub fn RapierWorld::gravity(self : RapierWorld) -> @moui_3d.Vec3 {
  self.gravity
}

///|
pub fn RapierWorld::create_body(
  self : RapierWorld,
  desc : @moui_3d_physics.BodyDesc,
) -> @moui_3d_physics.BodyId {
  let builder = match desc.body_type() {
    Dynamic => @rapier_dynamics.RigidBodyBuilder3D::dynamic()
    Fixed => @rapier_dynamics.RigidBodyBuilder3D::fixed()
    Kinematic => @rapier_dynamics.RigidBodyBuilder3D::kinematic_position_based()
  }
  let rapier_body = builder
    .translation(to_rapier_vec3(desc.position()))
    .rotation(to_rapier_quat(desc.rotation()))
    .gravity_scale(Float::from_double(desc.gravity_scale()))
    .build()
  let handle = self.bodies.insert(rapier_body)
  let id = @moui_3d_physics.BodyId::new(self.next_body)
  self.next_body = self.next_body + 1L
  self.body_handles[id] = handle
  id
}

///|
pub fn RapierWorld::create_collider(
  self : RapierWorld,
  desc : @moui_3d_physics.ColliderDesc,
) -> @moui_3d_physics.ColliderId? {
  let builder = match desc.shape() {
    Ball(radius) =>
      @rapier_collision.ColliderBuilder3D::ball(Float::from_double(radius))
    Cuboid(half_extents) =>
      @rapier_collision.ColliderBuilder3D::cuboid(
        Float::from_double(half_extents.x()),
        Float::from_double(half_extents.y()),
        Float::from_double(half_extents.z()),
      )
    CapsuleX(radius, half_height) =>
      @rapier_collision.ColliderBuilder3D::capsule_x(
        Float::from_double(radius),
        Float::from_double(half_height),
      )
    CapsuleY(radius, half_height) =>
      @rapier_collision.ColliderBuilder3D::capsule_y(
        Float::from_double(radius),
        Float::from_double(half_height),
      )
    CapsuleZ(radius, half_height) =>
      @rapier_collision.ColliderBuilder3D::capsule_z(
        Float::from_double(radius),
        Float::from_double(half_height),
      )
    Triangle(a, b, c) =>
      @rapier_collision.ColliderBuilder3D::triangle(
        to_rapier_vec3(a),
        to_rapier_vec3(b),
        to_rapier_vec3(c),
      )
    Trimesh(vertices, indices) =>
      match
        @rapier_collision.ColliderBuilder3D::trimesh(
          vertices.map(to_rapier_vec3),
          indices,
        ) {
        None => return None
        Some(value) => value
      }
  }
  let collider = builder
    .sensor(desc.sensor())
    .friction(Float::from_double(desc.friction()))
    .restitution(Float::from_double(desc.restitution()))
    .build()
  let handle = match desc.body() {
    None => self.colliders.insert(collider)
    Some(body_id) =>
      match self.body_handles.get(body_id) {
        None => return None
        Some(body) =>
          self.colliders.insert_with_parent(collider, body, self.bodies)
      }
  }
  let id = @moui_3d_physics.ColliderId::new(self.next_collider)
  self.next_collider = self.next_collider + 1L
  self.collider_handles[id] = handle
  Some(id)
}

///|
pub fn RapierWorld::body_translation(
  self : RapierWorld,
  id : @moui_3d_physics.BodyId,
) -> @moui_3d.Vec3? {
  match self.body_handles.get(id) {
    None => None
    Some(handle) =>
      self.bodies.get(handle).map(body => from_rapier_vec3(body.translation()))
  }
}

///|
pub fn RapierWorld::body_rotation(
  self : RapierWorld,
  id : @moui_3d_physics.BodyId,
) -> @moui_3d.Quat? {
  match self.body_handles.get(id) {
    None => None
    Some(handle) =>
      self.bodies.get(handle).map(body => from_rapier_quat(body.rotation()))
  }
}

///|
pub fn RapierWorld::step(self : RapierWorld, delta_seconds : Double) -> Int {
  if delta_seconds <= 0.0 {
    return 0
  }
  let params = @rapier_dynamics.IntegrationParameters::default().set_dt(
    Float::from_double(delta_seconds),
  )
  self.pipeline.step(
    to_rapier_vec3(self.gravity),
    params,
    self.islands,
    self.broad_phase,
    self.narrow_phase,
    self.bodies,
    self.colliders,
  )
  1
}

///|
pub fn RapierWorld::cast_ray(
  self : RapierWorld,
  ray : @moui_3d.Ray,
  max_distance : Double,
) -> @moui_3d_physics.RayHit? {
  if max_distance <= 0.0 {
    return None
  }
  let query = @rapier_collision.QueryPipeline3DReal::QueryPipeline3DReal(
    QueryFilter3DReal(),
    self.bodies,
    self.colliders,
  )
  let rapier_ray = @rapier_collision.Ray3::Ray3(
    to_rapier_vec3(ray.origin()),
    to_rapier_vec3(ray.direction()),
  )
  match
    query.cast_ray_and_get_normal(
      self.bodies,
      self.colliders,
      rapier_ray,
      Float::from_double(max_distance),
      true,
    ) {
    None => None
    Some((handle, intersection)) => {
      let collider = find_collider_id(self.collider_handles, handle)
      match collider {
        None => None
        Some(id) => {
          let distance = intersection.toi().to_double()
          Some(
            @moui_3d_physics.RayHit::new(
              collider=id,
              distance~,
              position=ray.at(distance),
              normal=from_rapier_vec3(intersection.normal()),
            ),
          )
        }
      }
    }
  }
}

///|
fn to_rapier_vec3(value : @moui_3d.Vec3) -> @rapier_core.Vec3 {
  Vec3(
    Float::from_double(value.x()),
    Float::from_double(value.y()),
    Float::from_double(value.z()),
  )
}

///|
fn from_rapier_vec3(value : @rapier_core.Vec3) -> @moui_3d.Vec3 {
  @moui_3d.Vec3::new(
    x=value.x.to_double(),
    y=value.y.to_double(),
    z=value.z.to_double(),
  )
}

///|
fn to_rapier_quat(value : @moui_3d.Quat) -> @rapier_core.Quat {
  Quat(
    Float::from_double(value.x),
    Float::from_double(value.y),
    Float::from_double(value.z),
    Float::from_double(value.w),
  )
}

///|
fn from_rapier_quat(value : @rapier_core.Quat) -> @moui_3d.Quat {
  @moui_3d.Quat::new(
    x=value.x.to_double(),
    y=value.y.to_double(),
    z=value.z.to_double(),
    w=value.w.to_double(),
  )
}

///|
fn find_collider_id(
  handles : Map[@moui_3d_physics.ColliderId, @rapier_collision.ColliderHandle3D],
  wanted : @rapier_collision.ColliderHandle3D,
) -> @moui_3d_physics.ColliderId? {
  for id in handles.keys() {
    match handles.get(id) {
      Some(handle) if handle.equals(wanted) => return Some(id)
      _ => ()
    }
  }
  None
}