import { computeHashFloat } from "../../../core/primitives/numbers/computeHashFloat.js";
import { AbstractShape3D } from "../../../core/geom/3d/shape/AbstractShape3D.js";
import { UnitSphereShape3D } from "../../../core/geom/3d/shape/UnitSphereShape3D.js";
import { ColliderFlags } from "./ColliderFlags.js";

/**
 * Sentinel for {@link Collider#_bvhNode} prior to insertion. Matches the BVH's
 * own NULL_NODE convention without taking a direct import dependency on it.
 *
 * @type {number}
 */
export const COLLIDER_UNBOUND = -1;

/**
 * Collider component.
 *
 * Pairs a shared, immutable {@link AbstractShape3D} with a body-specific surface
 * material. World pose comes from the entity's own {@link Transform}; for compound
 * bodies the child collider entities are linked to the body via the existing
 * `ParentEntity` component.
 *
 * @author Alex Goldring
 * @copyright Company Named Limited (c) 2026
 */
export class Collider {

    /**
     * Surface geometry. Shared between colliders; never mutated.
     * @type {AbstractShape3D}
     */
    shape = UnitSphereShape3D.INSTANCE;

    /**
     * Coulomb friction coefficient. Combined with the other side's friction via
     * the system-wide combine rule (default: geometric mean).
     * @type {number}
     */
    friction = 0.5;

    /**
     * Coefficient of restitution. 0 = perfectly inelastic, 1 = perfectly elastic.
     * Combined via the system-wide combine rule.
     * @type {number}
     */
    restitution = 0;

    /**
     * Mass-per-unit-volume. Used by inertia-tensor helpers if the user opts in
     * to automatic mass computation; otherwise informational.
     * @type {number}
     */
    density = 1;

    /**
     * @type {ColliderFlags|number}
     */
    flags = ColliderFlags.None;

    /**
     * BVH node identifier assigned by {@link PhysicsSystem#link}. System-private.
     * @type {number}
     */
    _bvhNode = COLLIDER_UNBOUND;

    /**
     * Packed body id of the owning body, copied from the parent's
     * {@link RigidBody#_bodyId} at link time. System-private.
     * @type {number}
     */
    _bodyId = -1;

    /**
     *
     * @param {number|ColliderFlags} flag
     */
    setFlag(flag) {
        this.flags |= flag;
    }

    /**
     *
     * @param {number|ColliderFlags} flag
     */
    clearFlag(flag) {
        this.flags &= ~flag;
    }

    /**
     *
     * @param {number|ColliderFlags} flag
     * @returns {boolean}
     */
    getFlag(flag) {
        return (this.flags & flag) === flag;
    }

    /**
     *
     * @param {number|ColliderFlags} flag
     * @param {boolean} value
     */
    writeFlag(flag, value) {
        if (value) {
            this.setFlag(flag);
        } else {
            this.clearFlag(flag);
        }
    }

    toJSON() {
        return {
            friction: this.friction,
            restitution: this.restitution,
            density: this.density,
            flags: this.flags,
        };
    }

    fromJSON(json) {
        if (json.friction !== undefined) this.friction = json.friction;
        if (json.restitution !== undefined) this.restitution = json.restitution;
        if (json.density !== undefined) this.density = json.density;
        if (json.flags !== undefined) this.flags = json.flags;
    }

    /**
     * Value equality over the shape and the surface material. System-private
     * fields (`_bvhNode`, `_bodyId`) are excluded. Shapes are compared by value
     * (`shape.equals`), so two colliders sharing one shape instance or holding
     * equal shapes both compare equal.
     *
     * @param {Collider} other
     * @returns {boolean}
     */
    equals(other) {
        if (other === this) return true;
        if (other === null || other === undefined || other.isCollider !== true) return false;

        return this.friction === other.friction
            && this.restitution === other.restitution
            && this.density === other.density
            && this.flags === other.flags
            && this.shape.equals(other.shape);
    }

    /**
     * Hash over the same state as {@link equals}. Equal colliders hash equal.
     * @returns {number}
     */
    hash() {
        let h = this.shape.hash() | 0;
        h = (h * 31 + computeHashFloat(this.friction)) | 0;
        h = (h * 31 + computeHashFloat(this.restitution)) | 0;
        h = (h * 31 + computeHashFloat(this.density)) | 0;
        h = (h * 31 + (this.flags | 0)) | 0;
        return h;
    }
}

/**
 * @readonly
 * @type {string}
 */
Collider.typeName = "Collider";

/**
 * @readonly
 * @type {boolean}
 */
Collider.prototype.isCollider = true;
