// A collision shape, as an aspect on a 3D Node. PURE GEOMETRY: attaching a Shape does not by itself
// make the node rigid or a sensor — pair it with a Physics (rigid body) or a Trigger (sensor), both
// of which require a Shape. The Shape also makes the node pointer-pickable: on its own it creates a
// lightweight "pick-only" JoltPhysics body (collides with nothing, exists only for the raycast), so
// any node with a Shape can receive click/touchstart. Physics/Trigger REPLACE that body with the real
// one. Set `raycast = false` to opt a shape out of pointer picking.
//
//   wall.aspect(Shape, { box: [1, 2, 1] }).aspect(Physics, { motion: 'static' })
//   coin.aspect(Shape, { sphere: 0.5 }).aspect(Trigger)
//   crate.aspect(Shape, {}).aspect(Physics)                 // auto box from the mesh AABB × world scale
//
// Explicit dims are world-space half-extents (not scaled by the node). Use {} ("auto") to track the
// mesh × world scale. Note: a Shape-only pick body is a STATIC snapshot at attach time — for a moving
// pickable object add a Physics body (which follows the sim), rather than relying on the pick body.

import { Aspect } from "../core/Aspect"
import { cx, cy, cz, type Vec3Like } from "../math/vec"
import type { Node } from "./Node"

export class Shape extends Aspect<"shape", Node> {
  static readonly aspect = "shape"

  /** Box half-extents [hx, hy, hz] (world units). */
  box?: Vec3Like
  /** Sphere radius. */
  sphere?: number
  /** Cylinder aligned on Y: half-height + radius. */
  cylinder?: { halfHeight: number, radius: number }
  /** Capsule aligned on Y: half-height of the cylinder section + radius. */
  capsule?: { halfHeight: number, radius: number }
  /** Whether pointer rays can hit this shape (click / touchstart). Default true. */
  raycast = true

  private _shapeId = 0
  private _bodyId = 0

  /** @internal native shape id (0 if the build has no physics). */
  get shapeId(): number { return this._shapeId }
  /** @internal current native body id (a pick-only body until Physics/Trigger claim it). */
  get bodyId(): number { return this._bodyId }

  onAttach(): void {
    if (!_creator.physicsHasSupport || !_creator.physicsHasSupport()) return
    this._shapeId = this._build()
    if (this._shapeId) this._createPickBody()
  }

  onDetach(): void {
    if (this._bodyId) { _creator.physicsRemoveBody(this._bodyId); this._bodyId = 0 }
    if (this._shapeId) { _creator.physicsDestroyShape(this._shapeId); this._shapeId = 0 }
  }

  /** @internal Physics/Trigger take over: drop the pick body and hand back the shape id. */
  _claim(): number {
    if (this._bodyId) { _creator.physicsRemoveBody(this._bodyId); this._bodyId = 0 }
    return this._shapeId
  }
  /** @internal Physics/Trigger report the body they created, so Shape owns its lifetime + pickability. */
  _ownBody(bodyId: number): void {
    this._bodyId = bodyId
    if (!this.raycast && bodyId) _creator.physicsSetPickable(bodyId, false)
  }
  /** @internal restore a pick-only body when Physics/Trigger detach but the Shape remains. */
  _recreatePickBody(): void {
    if (!this._bodyId && this._shapeId) this._createPickBody()
  }

  private _createPickBody(): void {
    this._bodyId = _creator.physicsCreateBody(this.node.id, this._shapeId, 0, 0, false, /*pickOnly*/ true)
    if (!this.raycast && this._bodyId) _creator.physicsSetPickable(this._bodyId, false)
  }

  private _build(): number {
    if (this.sphere !== undefined) return _creator.physicsBuildSphere(this.sphere)
    if (this.cylinder) return _creator.physicsBuildCylinder(this.cylinder.halfHeight, this.cylinder.radius)
    if (this.capsule) return _creator.physicsBuildCapsule(this.capsule.halfHeight, this.capsule.radius)
    if (this.box) return _creator.physicsBuildBox(cx(this.box), cy(this.box), cz(this.box))

    // auto: a box from the mesh AABB × world scale (so the physical box matches the rendered size).
    const s = this.node.worldScale
    let hx = 0.5, hy = 0.5, hz = 0.5
    const geom = this.node.geometry
    if (geom) {
      const v = geom.vertices
      let minX = Infinity, minY = Infinity, minZ = Infinity, maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity
      for (let i = 0; i + 2 < v.length; i += 3) {
        if (v[i] < minX) minX = v[i]
        if (v[i] > maxX) maxX = v[i]
        if (v[i + 1] < minY) minY = v[i + 1]
        if (v[i + 1] > maxY) maxY = v[i + 1]
        if (v[i + 2] < minZ) minZ = v[i + 2]
        if (v[i + 2] > maxZ) maxZ = v[i + 2]
      }
      hx = (maxX - minX) * 0.5; hy = (maxY - minY) * 0.5; hz = (maxZ - minZ) * 0.5
    }
    return _creator.physicsBuildBox(hx * s.x, hy * s.y, hz * s.z)
  }
}
