///|
pub(all) enum BodyType {
  Dynamic
  Fixed
  Kinematic
} derive(Eq, Debug, ToJson)

///|
pub extend BodyType with Debug::{to_repr}

///|
pub extend BodyType with Eq::{not_equal, equal}

///|
pub extend BodyType with ToJson::{to_json}

///|
pub(all) struct BodyId {
  value : Int64
} derive(Eq, Debug, Hash, ToJson)

///|
pub extend BodyId with Debug::{to_repr}

///|
pub extend BodyId with Eq::{not_equal, equal}

///|
pub extend BodyId with Hash::{hash, hash_combine}

///|
pub extend BodyId with ToJson::{to_json}

///|
pub fn BodyId::new(value : Int64) -> BodyId {
  { value, }
}

///|
pub fn BodyId::value(self : BodyId) -> Int64 {
  self.value
}

///|
pub(all) struct ColliderId {
  value : Int64
} derive(Eq, Debug, Hash, ToJson)

///|
pub extend ColliderId with Debug::{to_repr}

///|
pub extend ColliderId with Eq::{not_equal, equal}

///|
pub extend ColliderId with Hash::{hash, hash_combine}

///|
pub extend ColliderId with ToJson::{to_json}

///|
pub fn ColliderId::new(value : Int64) -> ColliderId {
  { value, }
}

///|
pub fn ColliderId::value(self : ColliderId) -> Int64 {
  self.value
}

///|
pub(all) enum Shape {
  Ball(Double)
  Cuboid(@moui_3d.Vec3)
  CapsuleX(Double, Double)
  CapsuleY(Double, Double)
  CapsuleZ(Double, Double)
  Triangle(@moui_3d.Vec3, @moui_3d.Vec3, @moui_3d.Vec3)
  Trimesh(Array[@moui_3d.Vec3], Array[(Int, Int, Int)])
} derive(Eq, Debug, ToJson)

///|
pub extend Shape with Debug::{to_repr}

///|
pub extend Shape with Eq::{not_equal, equal}

///|
pub extend Shape with ToJson::{to_json}

///|
pub(all) struct BodyDesc {
  body_type : BodyType
  position : @moui_3d.Vec3
  rotation : @moui_3d.Quat
  gravity_scale : Double
} derive(Eq, Debug, ToJson)

///|
pub extend BodyDesc with Debug::{to_repr}

///|
pub extend BodyDesc with Eq::{not_equal, equal}

///|
pub extend BodyDesc with ToJson::{to_json}

///|
pub fn BodyDesc::new(
  body_type? : BodyType = Dynamic,
  position? : @moui_3d.Vec3 = @moui_3d.Vec3::zero(),
  rotation? : @moui_3d.Quat = @moui_3d.Quat::identity(),
  gravity_scale? : Double = 1.0,
) -> BodyDesc {
  { body_type, position, rotation, gravity_scale, }
}

///|
pub fn BodyDesc::body_type(self : BodyDesc) -> BodyType {
  self.body_type
}

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

///|
pub fn BodyDesc::rotation(self : BodyDesc) -> @moui_3d.Quat {
  self.rotation
}

///|
pub fn BodyDesc::gravity_scale(self : BodyDesc) -> Double {
  self.gravity_scale
}

///|
pub(all) struct ColliderDesc {
  shape : Shape
  body : BodyId?
  sensor : Bool
  friction : Double
  restitution : Double
} derive(Eq, Debug, ToJson)

///|
pub extend ColliderDesc with Debug::{to_repr}

///|
pub extend ColliderDesc with Eq::{not_equal, equal}

///|
pub extend ColliderDesc with ToJson::{to_json}

///|
pub fn ColliderDesc::new(
  shape~ : Shape,
  body? : BodyId? = None,
  sensor? : Bool = false,
  friction? : Double = 0.5,
  restitution? : Double = 0.0,
) -> ColliderDesc {
  {
    shape,
    body,
    sensor,
    friction: friction.max(0.0),
    restitution: restitution.max(0.0).min(1.0),
  }
}

///|
pub fn ColliderDesc::shape(self : ColliderDesc) -> Shape {
  self.shape
}

///|
pub fn ColliderDesc::body(self : ColliderDesc) -> BodyId? {
  self.body
}

///|
pub fn ColliderDesc::sensor(self : ColliderDesc) -> Bool {
  self.sensor
}

///|
pub fn ColliderDesc::friction(self : ColliderDesc) -> Double {
  self.friction
}

///|
pub fn ColliderDesc::restitution(self : ColliderDesc) -> Double {
  self.restitution
}

///|
pub(all) struct RayHit {
  collider : ColliderId
  distance : Double
  position : @moui_3d.Vec3
  normal : @moui_3d.Vec3
} derive(Debug)

///|
pub extend RayHit with Debug::{to_repr}

///|
pub fn RayHit::collider(self : RayHit) -> ColliderId {
  self.collider
}

///|
pub fn RayHit::distance(self : RayHit) -> Double {
  self.distance
}

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

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

///|
pub fn RayHit::new(
  collider~ : ColliderId,
  distance~ : Double,
  position~ : @moui_3d.Vec3,
  normal~ : @moui_3d.Vec3,
) -> RayHit {
  { collider, distance, position, normal, }
}

///|
pub(all) struct PhysicsWorld {
  gravity : @moui_3d.Vec3
  mut next_body : Int64
  mut next_collider : Int64
  bodies : Map[BodyId, BodyDesc]
  colliders : Map[ColliderId, ColliderDesc]
} derive(Debug)

///|
pub extend PhysicsWorld with Debug::{to_repr}

///|
pub fn PhysicsWorld::new(
  gravity? : @moui_3d.Vec3 = @moui_3d.Vec3::new(x=0.0, y=-9.81, z=0.0),
) -> PhysicsWorld {
  { gravity, next_body: 1L, next_collider: 1L, bodies: {}, colliders: {}, }
}

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

///|
pub fn PhysicsWorld::create_body(
  self : PhysicsWorld,
  desc : BodyDesc,
) -> BodyId {
  let id = BodyId::new(self.next_body)
  self.next_body = self.next_body + 1L
  self.bodies[id] = desc
  id
}

///|
pub fn PhysicsWorld::create_collider(
  self : PhysicsWorld,
  desc : ColliderDesc,
) -> ColliderId {
  let id = ColliderId::new(self.next_collider)
  self.next_collider = self.next_collider + 1L
  self.colliders[id] = desc
  id
}

///|
pub fn PhysicsWorld::body(self : PhysicsWorld, id : BodyId) -> BodyDesc? {
  self.bodies.get(id)
}

///|
pub fn PhysicsWorld::collider(
  self : PhysicsWorld,
  id : ColliderId,
) -> ColliderDesc? {
  self.colliders.get(id)
}

///|
/// Backend hook. The neutral world does not guess physics; an adapter owns the
/// actual integration and returns a deterministic step count.
pub fn PhysicsWorld::step(self : PhysicsWorld, delta_seconds : Double) -> Int {
  ignore(self)
  if delta_seconds <= 0.0 {
    0
  } else {
    1
  }
}