// 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.

///|
/// High-level query helpers (Bevy-like) built on top of Rapier's QueryPipeline.

///|
pub struct RayHit2d {
  entity : @core.Entity
  toi : Float
}

///|
pub struct RayHitNormal2d {
  entity : @core.Entity
  toi : Float
  normal : @math.Vec2
}

///|
pub struct PointProjection2d {
  entity : @core.Entity
  point : @math.Vec2
  is_inside : Bool
}

///|
pub struct PointProjectionFeature2d {
  entity : @core.Entity
  point : @math.Vec2
  is_inside : Bool
  feature : @collision.FeatureId
}

///|
pub struct ShapeCastHit2d {
  entity : @core.Entity
  toi : Float
  point : @math.Vec2
  normal : @math.Vec2
}

///|
fn entity_from_collider_handle(
  ctx : Physics2dContext,
  h : @collision.ColliderHandle,
) -> @core.Entity? {
  ctx.collider_to_entity.get(h.into_raw_parts())
}

///|
pub fn Physics2dContext::rigid_body_handle_of(
  self : Physics2dContext,
  entity : @core.Entity,
) -> @dynamics.RigidBodyHandle? {
  self.entity_to_body.get(entity_key(entity))
}

///|
pub fn Physics2dContext::collider_handle_of(
  self : Physics2dContext,
  entity : @core.Entity,
) -> @collision.ColliderHandle? {
  self.entity_to_collider.get(entity_key(entity))
}

///|
pub fn Physics2dContext::query_filter_exclude_entity(
  self : Physics2dContext,
  filter : @collision.QueryFilter,
  entity : @core.Entity,
) -> @collision.QueryFilter {
  match self.collider_handle_of(entity) {
    Some(h) => filter.exclude_collider(h)
    None =>
      match self.rigid_body_handle_of(entity) {
        Some(bh) => filter.exclude_rigid_body(bh)
        None => filter
      }
  }
}

///|

///|
fn rect_to_aabb(rect : @math.Rect) -> @rapier_core.Aabb {
  @rapier_core.Aabb::new(
    mg_vec2_to_rapier(rect.min),
    mg_vec2_to_rapier(rect.max),
  )
}

///|
pub fn Physics2dContext::cast_ray_entity(
  self : Physics2dContext,
  origin : @math.Vec2,
  dir : @math.Vec2,
  max_toi : Float,
  solid : Bool,
) -> RayHit2d? {
  let ray = @collision.Ray::new(
    mg_vec2_to_rapier(origin),
    mg_vec2_to_rapier(dir),
  )
  match self.query().cast_ray(ray, max_toi, solid) {
    Some((h, toi)) =>
      match entity_from_collider_handle(self, h) {
        Some(e) => Some(RayHit2d::{ entity: e, toi })
        None => None
      }
    None => None
  }
}

///|
pub fn Physics2dContext::cast_ray_entity_with_filter(
  self : Physics2dContext,
  origin : @math.Vec2,
  dir : @math.Vec2,
  max_toi : Float,
  solid : Bool,
  filter : @collision.QueryFilter,
) -> RayHit2d? {
  let ray = @collision.Ray::new(
    mg_vec2_to_rapier(origin),
    mg_vec2_to_rapier(dir),
  )
  match self.query().with_filter(filter).cast_ray(ray, max_toi, solid) {
    Some((h, toi)) =>
      match entity_from_collider_handle(self, h) {
        Some(e) => Some(RayHit2d::{ entity: e, toi })
        None => None
      }
    None => None
  }
}

///|
pub fn Physics2dContext::cast_ray_and_get_normal_entity(
  self : Physics2dContext,
  origin : @math.Vec2,
  dir : @math.Vec2,
  max_toi : Float,
  solid : Bool,
) -> RayHitNormal2d? {
  let ray = @collision.Ray::new(
    mg_vec2_to_rapier(origin),
    mg_vec2_to_rapier(dir),
  )
  match self.query().cast_ray_and_get_normal(ray, max_toi, solid) {
    Some((h, inter)) =>
      match entity_from_collider_handle(self, h) {
        Some(e) =>
          Some(RayHitNormal2d::{
            entity: e,
            toi: inter.toi(),
            normal: rapier_vec2_to_mg(inter.normal()),
          })
        None => None
      }
    None => None
  }
}

///|
pub fn Physics2dContext::cast_ray_and_get_normal_entity_with_filter(
  self : Physics2dContext,
  origin : @math.Vec2,
  dir : @math.Vec2,
  max_toi : Float,
  solid : Bool,
  filter : @collision.QueryFilter,
) -> RayHitNormal2d? {
  let ray = @collision.Ray::new(
    mg_vec2_to_rapier(origin),
    mg_vec2_to_rapier(dir),
  )
  match
    self
    .query()
    .with_filter(filter)
    .cast_ray_and_get_normal(ray, max_toi, solid) {
    Some((h, inter)) =>
      match entity_from_collider_handle(self, h) {
        Some(e) =>
          Some(RayHitNormal2d::{
            entity: e,
            toi: inter.toi(),
            normal: rapier_vec2_to_mg(inter.normal()),
          })
        None => None
      }
    None => None
  }
}

///|
pub fn Physics2dContext::intersect_ray_entities_with_filter(
  self : Physics2dContext,
  origin : @math.Vec2,
  dir : @math.Vec2,
  max_toi : Float,
  solid : Bool,
  filter : @collision.QueryFilter,
) -> Array[RayHitNormal2d] {
  let ray = @collision.Ray::new(
    mg_vec2_to_rapier(origin),
    mg_vec2_to_rapier(dir),
  )
  let hits = self.query().with_filter(filter).intersect_ray(ray, max_toi, solid)
  let out : Array[RayHitNormal2d] = []
  for pair in hits {
    let (h, inter) = pair
    if entity_from_collider_handle(self, h) is Some(e) {
      out.push(RayHitNormal2d::{
        entity: e,
        toi: inter.toi(),
        normal: rapier_vec2_to_mg(inter.normal()),
      })
    }
  }
  out
}

///|
pub fn Physics2dContext::intersect_ray_entities(
  self : Physics2dContext,
  origin : @math.Vec2,
  dir : @math.Vec2,
  max_toi : Float,
  solid : Bool,
) -> Array[RayHitNormal2d] {
  self.intersect_ray_entities_with_filter(
    origin,
    dir,
    max_toi,
    solid,
    @collision.QueryFilter::new(),
  )
}

///|
pub fn Physics2dContext::intersect_point_entities(
  self : Physics2dContext,
  point : @math.Vec2,
) -> Array[@core.Entity] {
  let hs = self.query().intersect_point(mg_vec2_to_rapier(point))
  let out : Array[@core.Entity] = []
  for h in hs {
    if entity_from_collider_handle(self, h) is Some(e) {
      out.push(e)
    }
  }
  out
}

///|
pub fn Physics2dContext::intersect_point_entities_with_filter(
  self : Physics2dContext,
  point : @math.Vec2,
  filter : @collision.QueryFilter,
) -> Array[@core.Entity] {
  let hs = self
    .query()
    .with_filter(filter)
    .intersect_point(mg_vec2_to_rapier(point))
  let out : Array[@core.Entity] = []
  for h in hs {
    if entity_from_collider_handle(self, h) is Some(e) {
      out.push(e)
    }
  }
  out
}

///|
pub fn Physics2dContext::project_point_entity(
  self : Physics2dContext,
  point : @math.Vec2,
  max_dist : Float,
  solid : Bool,
) -> PointProjection2d? {
  match self.query().project_point(mg_vec2_to_rapier(point), max_dist, solid) {
    Some((h, proj)) =>
      match entity_from_collider_handle(self, h) {
        Some(e) =>
          Some(PointProjection2d::{
            entity: e,
            point: rapier_vec2_to_mg(proj.point()),
            is_inside: proj.is_inside(),
          })
        None => None
      }
    None => None
  }
}

///|
pub fn Physics2dContext::project_point_entity_with_filter(
  self : Physics2dContext,
  point : @math.Vec2,
  max_dist : Float,
  solid : Bool,
  filter : @collision.QueryFilter,
) -> PointProjection2d? {
  match
    self
    .query()
    .with_filter(filter)
    .project_point(mg_vec2_to_rapier(point), max_dist, solid) {
    Some((h, proj)) =>
      match entity_from_collider_handle(self, h) {
        Some(e) =>
          Some(PointProjection2d::{
            entity: e,
            point: rapier_vec2_to_mg(proj.point()),
            is_inside: proj.is_inside(),
          })
        None => None
      }
    None => None
  }
}

///|
pub fn Physics2dContext::project_point_and_get_feature_entity(
  self : Physics2dContext,
  point : @math.Vec2,
  max_dist : Float,
  solid : Bool,
) -> PointProjectionFeature2d? {
  match
    self
    .query()
    .project_point_and_get_feature(mg_vec2_to_rapier(point), max_dist, solid) {
    Some((h, proj, feature)) =>
      match entity_from_collider_handle(self, h) {
        Some(e) =>
          Some(PointProjectionFeature2d::{
            entity: e,
            point: rapier_vec2_to_mg(proj.point()),
            is_inside: proj.is_inside(),
            feature,
          })
        None => None
      }
    None => None
  }
}

///|
pub fn Physics2dContext::project_point_and_get_feature_entity_with_filter(
  self : Physics2dContext,
  point : @math.Vec2,
  max_dist : Float,
  solid : Bool,
  filter : @collision.QueryFilter,
) -> PointProjectionFeature2d? {
  match
    self
    .query()
    .with_filter(filter)
    .project_point_and_get_feature(mg_vec2_to_rapier(point), max_dist, solid) {
    Some((h, proj, feature)) =>
      match entity_from_collider_handle(self, h) {
        Some(e) =>
          Some(PointProjectionFeature2d::{
            entity: e,
            point: rapier_vec2_to_mg(proj.point()),
            is_inside: proj.is_inside(),
            feature,
          })
        None => None
      }
    None => None
  }
}

///|
pub fn Physics2dContext::intersect_shape_entities(
  self : Physics2dContext,
  shape : ColliderShape2d,
  shape_pos : @math.Transform,
) -> Array[@core.Entity] {
  self.intersect_shape_entities_with_filter(
    shape,
    shape_pos,
    @collision.QueryFilter::new(),
  )
}

///|
pub fn Physics2dContext::intersect_shape_entities_with_filter(
  self : Physics2dContext,
  shape : ColliderShape2d,
  shape_pos : @math.Transform,
  filter : @collision.QueryFilter,
) -> Array[@core.Entity] {
  let hs = self
    .query()
    .with_filter(filter)
    .intersect_shape(
      mg_transform_to_iso2(shape_pos),
      collider_shape2d_to_rapier(shape),
    )
  let out : Array[@core.Entity] = []
  for h in hs {
    if entity_from_collider_handle(self, h) is Some(e) {
      out.push(e)
    }
  }
  out
}

///|
pub fn Physics2dContext::intersect_aabb_entities_conservative(
  self : Physics2dContext,
  rect : @math.Rect,
) -> Array[@core.Entity] {
  self.intersect_aabb_entities_conservative_with_filter(
    rect,
    @collision.QueryFilter::new(),
  )
}

///|
pub fn Physics2dContext::intersect_aabb_entities_conservative_with_filter(
  self : Physics2dContext,
  rect : @math.Rect,
  filter : @collision.QueryFilter,
) -> Array[@core.Entity] {
  let hs = self
    .query()
    .with_filter(filter)
    .intersect_aabb_conservative(rect_to_aabb(rect))
  let out : Array[@core.Entity] = []
  for h in hs {
    if entity_from_collider_handle(self, h) is Some(e) {
      out.push(e)
    }
  }
  out
}

///|
pub fn Physics2dContext::cast_shape_entity(
  self : Physics2dContext,
  shape : ColliderShape2d,
  shape_pos : @math.Transform,
  shape_vel : @math.Vec2,
  options : @collision.ShapeCastOptions,
) -> ShapeCastHit2d? {
  let query = self.query()
  match
    query.cast_shape(
      mg_transform_to_iso2(shape_pos),
      mg_vec2_to_rapier(shape_vel),
      collider_shape2d_to_rapier(shape),
      options,
    ) {
    Some((h, hit)) =>
      match entity_from_collider_handle(self, h) {
        Some(e) =>
          Some(ShapeCastHit2d::{
            entity: e,
            toi: hit.toi(),
            point: rapier_vec2_to_mg(hit.point()),
            normal: rapier_vec2_to_mg(hit.normal()),
          })
        None => None
      }
    None => None
  }
}

///|
pub fn Physics2dContext::cast_shape_entity_with_filter(
  self : Physics2dContext,
  shape : ColliderShape2d,
  shape_pos : @math.Transform,
  shape_vel : @math.Vec2,
  options : @collision.ShapeCastOptions,
  filter : @collision.QueryFilter,
) -> ShapeCastHit2d? {
  let query = self.query().with_filter(filter)
  match
    query.cast_shape(
      mg_transform_to_iso2(shape_pos),
      mg_vec2_to_rapier(shape_vel),
      collider_shape2d_to_rapier(shape),
      options,
    ) {
    Some((h, hit)) =>
      match entity_from_collider_handle(self, h) {
        Some(e) =>
          Some(ShapeCastHit2d::{
            entity: e,
            toi: hit.toi(),
            point: rapier_vec2_to_mg(hit.point()),
            normal: rapier_vec2_to_mg(hit.normal()),
          })
        None => None
      }
    None => None
  }
}