// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
/// Rapier-based 2D physics integration (Bevy-like, 2D-first).
///
/// Note:
/// - Physics v1 ignores `@math.Transform.scale`.
/// - For compound bodies, use `ColliderParent2d` (child colliders) or `ColliderShape2d::compound`.

///|
// ECS component marker for codegen (`mgstudio gen`).
#ecs.component
pub struct RigidBody2d {
  body_type : @dynamics.RigidBodyType
}

///|
pub fn RigidBody2d::dynamic() -> RigidBody2d {
  { body_type: @dynamics.RigidBodyType::Dynamic }
}

///|
pub fn RigidBody2d::fixed() -> RigidBody2d {
  { body_type: @dynamics.RigidBodyType::Fixed }
}

///|
pub fn RigidBody2d::kinematic_position_based() -> RigidBody2d {
  { body_type: @dynamics.RigidBodyType::KinematicPositionBased }
}

///|
pub fn RigidBody2d::kinematic_velocity_based() -> RigidBody2d {
  { body_type: @dynamics.RigidBodyType::KinematicVelocityBased }
}

///|
pub enum ColliderShape2d {
  Ball(Float)
  Cuboid(Float, Float)
  CapsuleX(Float, Float)
  CapsuleY(Float, Float)
  Segment(@math.Vec2, @math.Vec2)
  Polyline(Array[@math.Vec2], Array[(Int, Int)]?)
  ConvexPolygon(Array[@math.Vec2])
  TriMesh(Array[@math.Vec2], Array[(Int, Int, Int)])
  /// A compound shape composed of sub-shapes with local transforms.
  Compound(Array[(@math.Transform, ColliderShape2d)])
  /// Escape hatch: use a Rapier shape directly.
  Raw(@collision.Shape)
}

///|
/// Constructors for `ColliderShape2d`.
///
/// Note: MoonBit currently exports enums as "read-only" by default, so external
/// packages cannot construct `ColliderShape2d` via `ColliderShape2d::Ball(...)`.
/// These helpers are the public way to create shapes.
pub fn ColliderShape2d::ball(radius : Float) -> ColliderShape2d {
  Ball(radius)
}

///|
pub fn ColliderShape2d::cuboid(hx : Float, hy : Float) -> ColliderShape2d {
  Cuboid(hx, hy)
}

///|
pub fn ColliderShape2d::capsule_x(
  half_height : Float,
  radius : Float,
) -> ColliderShape2d {
  CapsuleX(half_height, radius)
}

///|
pub fn ColliderShape2d::capsule_y(
  half_height : Float,
  radius : Float,
) -> ColliderShape2d {
  CapsuleY(half_height, radius)
}

///|
pub fn ColliderShape2d::segment(
  a : @math.Vec2,
  b : @math.Vec2,
) -> ColliderShape2d {
  Segment(a, b)
}

///|
pub fn ColliderShape2d::polyline(
  vertices : Array[@math.Vec2],
  indices? : Array[(Int, Int)]? = None,
) -> ColliderShape2d {
  Polyline(vertices, indices)
}

///|
/// The vertices are expected to describe a convex polygon in counter-clockwise order.
pub fn ColliderShape2d::convex_polygon(
  vertices : Array[@math.Vec2],
) -> ColliderShape2d {
  ConvexPolygon(vertices)
}

///|
pub fn ColliderShape2d::trimesh(
  vertices : Array[@math.Vec2],
  indices : Array[(Int, Int, Int)],
) -> ColliderShape2d {
  TriMesh(vertices, indices)
}

///|
pub fn ColliderShape2d::compound(
  parts : Array[(@math.Transform, ColliderShape2d)],
) -> ColliderShape2d {
  Compound(parts)
}

///|
pub fn ColliderShape2d::raw(shape : @collision.Shape) -> ColliderShape2d {
  Raw(shape)
}

///|
// ECS component marker for codegen (`mgstudio gen`).
#ecs.component
pub struct Collider2d {
  shape : ColliderShape2d
  sensor : Bool
}

///|
pub fn Collider2d::new(shape : ColliderShape2d) -> Collider2d {
  { shape, sensor: false }
}

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

///|
/// Attach a collider entity to a different rigid-body entity.
///
/// This is the 2D equivalent of Bevy+Rapier "child colliders": set `RigidBody2d`
/// on one entity and `Collider2d + ColliderParent2d` on another to create
/// multi-collider rigid bodies.
///
/// Note: The collider entity's `@math.Transform` is interpreted as the collider
/// pose relative to the parent body.
#ecs.component
pub struct ColliderParent2d {
  parent : @core.Entity
}

///|
pub fn ColliderParent2d::new(parent : @core.Entity) -> ColliderParent2d {
  { parent, }
}

///|
// ECS component marker for codegen (`mgstudio gen`).
#ecs.component
pub struct Velocity2d {
  linvel : @math.Vec2
  angvel : Float
}

///|
pub fn Velocity2d::zero() -> Velocity2d {
  { linvel: @math.Vec2::new(0.0F, 0.0F), angvel: 0.0F }
}

///|
pub fn Velocity2d::new(linvel : @math.Vec2, angvel : Float) -> Velocity2d {
  { linvel, angvel }
}

///|
// ECS component marker for codegen (`mgstudio gen`).
#ecs.component
pub struct Damping2d {
  linear : Float
  angular : Float
}

///|
pub fn Damping2d::new(linear : Float, angular : Float) -> Damping2d {
  { linear, angular }
}

///|
// ECS component marker for codegen (`mgstudio gen`).
#ecs.component
pub struct GravityScale2d {
  scale : Float
}

///|
pub fn GravityScale2d::new(scale : Float) -> GravityScale2d {
  GravityScale2d::{ scale, }
}

///|
// ECS component marker for codegen (`mgstudio gen`).
#ecs.component
pub struct LockedAxes2d {
  lock_translations : Bool
  lock_rotations : Bool
}

///|
pub fn LockedAxes2d::new(
  lock_translations : Bool,
  lock_rotations : Bool,
) -> LockedAxes2d {
  { lock_translations, lock_rotations }
}

///|
pub fn LockedAxes2d::locked() -> LockedAxes2d {
  { lock_translations: true, lock_rotations: true }
}

///|
// ECS component marker for codegen (`mgstudio gen`).
#ecs.component
pub struct Ccd2d {
  enabled : Bool
  soft_prediction : Float
}

///|
pub fn Ccd2d::enabled() -> Ccd2d {
  { enabled: true, soft_prediction: 0.0F }
}

///|
pub fn Ccd2d::new(enabled : Bool, soft_prediction : Float) -> Ccd2d {
  { enabled, soft_prediction }
}

///|
// ECS component marker for codegen (`mgstudio gen`).
#ecs.component
pub struct Dominance2d {
  group : Int
}

///|
pub fn Dominance2d::new(group : Int) -> Dominance2d {
  Dominance2d::{ group, }
}

///|
// ECS component marker for codegen (`mgstudio gen`).
#ecs.component
pub struct Sleeping2d {
  sleeping : Bool
}

///|
pub fn Sleeping2d::new(sleeping : Bool) -> Sleeping2d {
  Sleeping2d::{ sleeping, }
}

///|
// ECS component marker for codegen (`mgstudio gen`).
#ecs.component
pub struct ExternalForce2d {
  force : @math.Vec2
  torque : Float
}

///|
pub fn ExternalForce2d::zero() -> ExternalForce2d {
  { force: @math.Vec2::new(0.0F, 0.0F), torque: 0.0F }
}

///|
pub fn ExternalForce2d::new(
  force : @math.Vec2,
  torque : Float,
) -> ExternalForce2d {
  { force, torque }
}

///|
// ECS component marker for codegen (`mgstudio gen`).
#ecs.component
pub struct ExternalImpulse2d {
  impulse : @math.Vec2
  torque_impulse : Float
}

///|
pub fn ExternalImpulse2d::zero() -> ExternalImpulse2d {
  { impulse: @math.Vec2::new(0.0F, 0.0F), torque_impulse: 0.0F }
}

///|
pub fn ExternalImpulse2d::new(
  impulse : @math.Vec2,
  torque_impulse : Float,
) -> ExternalImpulse2d {
  { impulse, torque_impulse }
}

///|
// ECS component marker for codegen (`mgstudio gen`).
#ecs.component
pub struct ContactForceEventThreshold2d {
  threshold : Float
}

///|
pub fn ContactForceEventThreshold2d::new(
  threshold : Float,
) -> ContactForceEventThreshold2d {
  ContactForceEventThreshold2d::{ threshold, }
}

///|
// ECS component marker for codegen (`mgstudio gen`).
#ecs.component
pub struct ColliderDensity2d {
  density : Float
}

///|
pub fn ColliderDensity2d::new(density : Float) -> ColliderDensity2d {
  { density, }
}

///|
// ECS component marker for codegen (`mgstudio gen`).
#ecs.component
pub struct Friction2d {
  coefficient : Float
}

///|
pub fn Friction2d::new(coefficient : Float) -> Friction2d {
  { coefficient, }
}

///|
// ECS component marker for codegen (`mgstudio gen`).
#ecs.component
pub struct FrictionCombineRule2d {
  rule : @dynamics.CoefficientCombineRule
}

///|
pub fn FrictionCombineRule2d::new(
  rule : @dynamics.CoefficientCombineRule,
) -> FrictionCombineRule2d {
  { rule, }
}

///|
// ECS component marker for codegen (`mgstudio gen`).
#ecs.component
pub struct Restitution2d {
  coefficient : Float
}

///|
pub fn Restitution2d::new(coefficient : Float) -> Restitution2d {
  { coefficient, }
}

///|
// ECS component marker for codegen (`mgstudio gen`).
#ecs.component
pub struct RestitutionCombineRule2d {
  rule : @dynamics.CoefficientCombineRule
}

///|
pub fn RestitutionCombineRule2d::new(
  rule : @dynamics.CoefficientCombineRule,
) -> RestitutionCombineRule2d {
  { rule, }
}

///|
// ECS component marker for codegen (`mgstudio gen`).
#ecs.component
pub struct CollisionGroups2d {
  memberships : Int
  filter : Int
  test_mode : @dynamics.InteractionTestMode
}

///|
pub fn CollisionGroups2d::new(
  memberships : Int,
  filter : Int,
  test_mode : @dynamics.InteractionTestMode,
) -> CollisionGroups2d {
  { memberships, filter, test_mode }
}

///|
// ECS component marker for codegen (`mgstudio gen`).
#ecs.component
pub struct SolverGroups2d {
  memberships : Int
  filter : Int
  test_mode : @dynamics.InteractionTestMode
}

///|
pub fn SolverGroups2d::new(
  memberships : Int,
  filter : Int,
  test_mode : @dynamics.InteractionTestMode,
) -> SolverGroups2d {
  { memberships, filter, test_mode }
}

///|
/// Enable/disable a collider without despawning it.
#ecs.component
pub struct ColliderEnabled2d {
  enabled : Bool
}

///|
pub fn ColliderEnabled2d::new(enabled : Bool) -> ColliderEnabled2d {
  { enabled, }
}

///|
pub fn ColliderEnabled2d::disabled() -> ColliderEnabled2d {
  { enabled: false }
}

///|
/// Per-collider active collision type mask.
#ecs.component
pub struct ActiveCollisionTypes2d {
  types : @collision.ActiveCollisionTypes
}

///|
pub fn ActiveCollisionTypes2d::new(
  types : @collision.ActiveCollisionTypes,
) -> ActiveCollisionTypes2d {
  { types, }
}

///|
/// Per-collider active event mask (collision/contact-force events).
#ecs.component
pub struct ActiveEvents2d {
  events : @collision.ActiveEvents
}

///|
pub fn ActiveEvents2d::new(events : @collision.ActiveEvents) -> ActiveEvents2d {
  { events, }
}

///|
/// Per-collider active hook mask (enables contact/intersection filtering, etc.).
#ecs.component
pub struct ActiveHooks2d {
  hooks : @collision.ActiveHooks
}

///|
pub fn ActiveHooks2d::new(hooks : @collision.ActiveHooks) -> ActiveHooks2d {
  { hooks, }
}

///|
/// Per-collider contact skin thickness.
#ecs.component
pub struct ContactSkin2d {
  skin : Float
}

///|
pub fn ContactSkin2d::new(skin : Float) -> ContactSkin2d {
  { skin, }
}

///|
/// Override per-collider mass properties.
#ecs.component
pub struct ColliderMassProperties2d {
  mass_properties : @rapier_core.MassProperties
}

///|
pub fn ColliderMassProperties2d::new(
  mass_properties : @rapier_core.MassProperties,
) -> ColliderMassProperties2d {
  { mass_properties, }
}

///|
/// Enable/disable a rigid body without despawning it.
#ecs.component
pub struct RigidBodyEnabled2d {
  enabled : Bool
}

///|
pub fn RigidBodyEnabled2d::new(enabled : Bool) -> RigidBodyEnabled2d {
  { enabled, }
}

///|
pub fn RigidBodyEnabled2d::disabled() -> RigidBodyEnabled2d {
  { enabled: false }
}

///|
/// Additional mass-properties applied on top of the collider-derived mass.
#ecs.component
pub struct AdditionalMassProperties2d {
  props : @dynamics.RigidBodyAdditionalMassProps
}

///|
pub fn AdditionalMassProperties2d::mass(
  mass : Float,
) -> AdditionalMassProperties2d {
  { props: @dynamics.RigidBodyAdditionalMassProps::mass(mass) }
}

///|
pub fn AdditionalMassProperties2d::mass_properties(
  props : @rapier_core.MassProperties,
) -> AdditionalMassProperties2d {
  { props: @dynamics.RigidBodyAdditionalMassProps::mass_properties(props) }
}

///|
/// Additional solver iterations for a rigid body.
#ecs.component
pub struct AdditionalSolverIterations2d {
  iterations : Int
}

///|
pub fn AdditionalSolverIterations2d::new(
  iterations : Int,
) -> AdditionalSolverIterations2d {
  { iterations, }
}

///|
// ECS component marker for codegen (`mgstudio gen`).
#ecs.component
pub struct RapierBodyHandle2d {
  handle : @dynamics.RigidBodyHandle
}

///|
pub fn RapierBodyHandle2d::new(
  handle : @dynamics.RigidBodyHandle,
) -> RapierBodyHandle2d {
  { handle, }
}

///|
// ECS component marker for codegen (`mgstudio gen`).
#ecs.component
pub struct RapierColliderHandle2d {
  handle : @collision.ColliderHandle
}

///|
pub fn RapierColliderHandle2d::new(
  handle : @collision.ColliderHandle,
) -> RapierColliderHandle2d {
  { handle, }
}

///|
/// Joint authoring components live in `physics2d_joints.mbt`.

///|
// ECS resource marker for codegen (`mgstudio gen`).
#ecs.resource
pub struct Physics2dConfig {
  gravity : @math.Vec2
  /// If false, the physics pipeline does not step.
  physics_pipeline_active : Bool
  /// Integration parameters (dt is overridden by mgstudio fixed timestep).
  integration : @dynamics.IntegrationParameters
}

///|
pub fn Physics2dConfig::default() -> Physics2dConfig {
  {
    gravity: @math.Vec2::new(0.0F, -9.81F),
    physics_pipeline_active: true,
    integration: @dynamics.IntegrationParameters::default(),
  }
}

///|
pub fn Physics2dConfig::new(gravity : @math.Vec2) -> Physics2dConfig {
  Physics2dConfig::{ ..Physics2dConfig::default(), gravity, }
}

///|
pub fn Physics2dConfig::with_gravity(
  self : Physics2dConfig,
  gravity : @math.Vec2,
) -> Physics2dConfig {
  Physics2dConfig::{ ..self, gravity, }
}

///|
pub fn Physics2dConfig::with_physics_pipeline_active(
  self : Physics2dConfig,
  active : Bool,
) -> Physics2dConfig {
  Physics2dConfig::{ ..self, physics_pipeline_active: active }
}

///|
pub fn Physics2dConfig::with_integration_parameters(
  self : Physics2dConfig,
  integration : @dynamics.IntegrationParameters,
) -> Physics2dConfig {
  Physics2dConfig::{ ..self, integration, }
}

///|
// ECS resource marker for codegen (`mgstudio gen`).
#ecs.resource
pub struct Physics2dContext {
  rigid_bodies : @dynamics.RigidBodySet
  colliders : @collision.ColliderSet
  islands : @dynamics.IslandManager
  broad_phase : @collision.BroadPhaseBvh
  narrow_phase : @collision.NarrowPhase
  impulse_joints : @dynamics.ImpulseJointSet
  multibody_joints : @dynamics.MultibodyJointSet
  ccd : @dynamics.CCDSolver
  mut pipeline : @pipeline.PhysicsPipeline
  mut integration : @dynamics.IntegrationParameters
  mut hooks : @pipeline.PhysicsHooks
  mut event_handler : @pipeline.EventHandler
  mut query_pipeline : @collision.QueryPipelineMut

  // (entity.id, entity.generation) -> handle
  entity_to_body : @hashmap.HashMap[(Int, Int), @dynamics.RigidBodyHandle]
  entity_to_collider : @hashmap.HashMap[(Int, Int), @collision.ColliderHandle]
  entity_to_impulse_joint : @hashmap.HashMap[
    (Int, Int),
    @dynamics.ImpulseJointHandle,
  ]
  entity_to_multibody_joint : @hashmap.HashMap[
    (Int, Int),
    @dynamics.MultibodyJointHandle,
  ]
  // (collider_handle.id, collider_handle.generation) -> entity
  collider_to_entity : @hashmap.HashMap[(Int, Int), @core.Entity]
}

///|
pub fn Physics2dContext::new() -> Physics2dContext {
  let rigid_bodies = @dynamics.RigidBodySet::new()
  let colliders = @collision.ColliderSet::new()
  let query_pipeline = @collision.QueryPipelineMut::new(
    @collision.QueryFilter::new(),
    rigid_bodies,
    colliders,
  )
  Physics2dContext::{
    rigid_bodies,
    colliders,
    islands: @dynamics.IslandManager::new(),
    broad_phase: @collision.BroadPhaseBvh::new(),
    narrow_phase: @collision.NarrowPhase::new(),
    impulse_joints: @dynamics.ImpulseJointSet::new(),
    multibody_joints: @dynamics.MultibodyJointSet::new(),
    ccd: @dynamics.CCDSolver::new(),
    pipeline: @pipeline.PhysicsPipeline::new(),
    integration: @dynamics.IntegrationParameters::default(),
    hooks: @pipeline.PhysicsHooks::new(),
    event_handler: @pipeline.EventHandler::new(),
    query_pipeline,
    entity_to_body: @hashmap.HashMap::new(),
    entity_to_collider: @hashmap.HashMap::new(),
    entity_to_impulse_joint: @hashmap.HashMap::new(),
    entity_to_multibody_joint: @hashmap.HashMap::new(),
    collider_to_entity: @hashmap.HashMap::new(),
  }
}

///|
pub fn Physics2dContext::query(
  self : Physics2dContext,
) -> @collision.QueryPipeline {
  self.query_pipeline.as_ref()
}

///|
pub fn[
  W : @core.ChangeTickWorld + @core.SystemContextWorld + @core.SpawnWorld + @math.Has_Transform + Has_RigidBody2d + Has_Collider2d + Has_ColliderParent2d + Has_KinematicCharacterController2d + Has_KinematicCharacterControllerOutput2d + Has_Velocity2d + Has_Damping2d + Has_GravityScale2d + Has_LockedAxes2d + Has_Ccd2d + Has_Dominance2d + Has_Sleeping2d + Has_ExternalForce2d + Has_ExternalImpulse2d + Has_RigidBodyEnabled2d + Has_AdditionalMassProperties2d + Has_AdditionalSolverIterations2d + Has_ContactForceEventThreshold2d + Has_ColliderDensity2d + Has_Friction2d + Has_FrictionCombineRule2d + Has_Restitution2d + Has_RestitutionCombineRule2d + Has_CollisionGroups2d + Has_SolverGroups2d + Has_ColliderEnabled2d + Has_ActiveCollisionTypes2d + Has_ActiveEvents2d + Has_ActiveHooks2d + Has_ContactSkin2d + Has_ColliderMassProperties2d + Has_ImpulseJoint2d + Has_MultibodyJoint2d + Has_RapierBodyHandle2d + Has_RapierColliderHandle2d + Has_RapierImpulseJointHandle2d + Has_RapierMultibodyJointHandle2d + HasRes_Physics2dConfig + HasRes_Physics2dContext + HasRes_Physics2dEvents,
] physics2d_plugin(
  app : @app.App[W],
) -> @app.App[W] {
  app
  .add_startup_system(physics2d_init_resources_system)
  .add_pre_update_system(physics2d_clear_events_system)
  .add_pre_update_system(physics2d_cleanup_despawned_system)
  .add_fixed_update_system(physics2d_sync_insertions_system)
  .add_fixed_update_system(physics2d_sync_joints_system)
  .add_fixed_update_system(physics2d_sync_world_to_rapier_system)
  .add_fixed_update_system(physics2d_character_controller_system)
  .add_fixed_update_system(physics2d_step_system)
  .add_fixed_update_system(physics2d_writeback_system)
  .add_post_update_system(physics2d_debug_render_system)
}