import { computeHashFloat } from "../../../core/primitives/numbers/computeHashFloat.js";
import Vector3 from "../../../core/geom/Vector3.js";
import { BodyKind } from "./BodyKind.js";
import { RigidBodyFlags } from "./RigidBodyFlags.js";
import { SleepState } from "./SleepState.js";

/**
 * Sentinel value for {@link RigidBody#_bodyId} prior to allocation by a
 * {@link PhysicsSystem}. After {@link PhysicsSystem#link}, the id is a packed
 * `(index << 8) | generation` integer.
 *
 * @type {number}
 */
export const RIGID_BODY_UNALLOCATED = -1;

/**
 * Rigid-body component.
 *
 * Pose lives on {@link Transform}; this component owns the dynamic state that is not
 * part of TRS: velocity, mass, inertia, damping, gravity scale, layer / mask
 * filtering and a small set of bit-flags ({@link RigidBodyFlags}).
 *
 * Defaults are conservative: the body translates under forces but cannot rotate
 * unless the user explicitly sets {@link inverseInertiaLocal} — forgetting to
 * configure inertia results in a locked-rotation body, never a NaN tumble.
 *
 * @author Alex Goldring
 * @copyright Company Named Limited (c) 2026
 */
export class RigidBody {

    /**
     * @type {BodyKind|number}
     */
    kind = BodyKind.Dynamic;

    /**
     * Linear velocity in world space, m/s.
     * @readonly
     * @type {Vector3}
     */
    linearVelocity = new Vector3(0, 0, 0);

    /**
     * Angular velocity in world space, rad/s.
     * @readonly
     * @type {Vector3}
     */
    angularVelocity = new Vector3(0, 0, 0);

    /**
     * Accumulator for forces applied between fixed-update steps. Consumed and
     * zeroed by the integrator. User code applies via
     * {@link PhysicsSystem#applyForce}; do not write directly.
     * @readonly
     * @type {Vector3}
     */
    accumulatedForce = new Vector3(0, 0, 0);

    /**
     * Accumulator for torques applied between fixed-update steps. Consumed and
     * zeroed by the integrator.
     * @readonly
     * @type {Vector3}
     */
    accumulatedTorque = new Vector3(0, 0, 0);

    /**
     * Total mass, kg. Must be positive for Dynamic bodies; Static / Kinematic
     * bodies behave as if mass were infinite regardless of this value.
     * @type {number}
     */
    mass = 1;

    /**
     * Inverse principal-frame inertia, diagonal. Components of zero lock the
     * corresponding rotational degree of freedom. The default of (0,0,0) means
     * rotation is locked until the user sets a value.
     * @readonly
     * @type {Vector3}
     */
    inverseInertiaLocal = new Vector3(0, 0, 0);

    /**
     * @type {number}
     */
    linearDamping = 0;

    /**
     * @type {number}
     */
    angularDamping = 0;

    /**
     * Multiplier for the world gravity on this body. 1 = normal gravity,
     * 0 = floating, negative values invert.
     * @type {number}
     */
    gravityScale = 1;

    /**
     * Layer bitmask identifying this body's category.
     * @type {number}
     */
    layer = 1;

    /**
     * Mask of layers this body collides with.
     * @type {number}
     */
    mask = 0xFFFFFFFF | 0;

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

    /**
     * Owned by the {@link PhysicsSystem}; user code must not write.
     * @type {SleepState|number}
     */
    sleepState = SleepState.Awake;

    /**
     * Seconds spent below the sleep-threshold velocity. The system increments
     * this each step the body is essentially at rest; once it crosses
     * {@link PhysicsSystem}'s sleep threshold, the body is moved out of the
     * active set. Reset to 0 whenever the body moves above threshold.
     *
     * Transient runtime state — not serialised, not user-writable.
     * @type {number}
     */
    sleep_timer = 0;

    /**
     * Next body in this body's atomic-sleep group, expressed as a body
     * **index** (NOT a packed id). Sleep groups are circular doubly-linked
     * lists threaded through every member when an island goes to sleep
     * atomically; on wake, the physics system walks the chain to wake every
     * member in the same step.
     *
     * `-1` when the body is awake or asleep as a singleton.
     *
     * Transient runtime state — not serialised, not user-writable.
     * @type {number}
     */
    sleep_group_next = -1;

    /**
     * Previous body in this body's atomic-sleep group. See {@link sleep_group_next}.
     * @type {number}
     */
    sleep_group_prev = -1;

    /**
     * Packed `(index << 8) | generation` body handle. {@link RIGID_BODY_UNALLOCATED}
     * before the body is linked into a {@link PhysicsSystem}. System-private.
     * @type {number}
     */
    _bodyId = RIGID_BODY_UNALLOCATED;

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

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

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

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

    toJSON() {
        return {
            kind: this.kind,
            linearVelocity: this.linearVelocity.toJSON(),
            angularVelocity: this.angularVelocity.toJSON(),
            mass: this.mass,
            inverseInertiaLocal: this.inverseInertiaLocal.toJSON(),
            linearDamping: this.linearDamping,
            angularDamping: this.angularDamping,
            gravityScale: this.gravityScale,
            layer: this.layer,
            mask: this.mask,
            flags: this.flags,
        };
    }

    fromJSON(json) {
        if (json.kind !== undefined) this.kind = json.kind;
        if (json.linearVelocity !== undefined) this.linearVelocity.fromJSON(json.linearVelocity);
        if (json.angularVelocity !== undefined) this.angularVelocity.fromJSON(json.angularVelocity);
        if (json.mass !== undefined) this.mass = json.mass;
        if (json.inverseInertiaLocal !== undefined) this.inverseInertiaLocal.fromJSON(json.inverseInertiaLocal);
        if (json.linearDamping !== undefined) this.linearDamping = json.linearDamping;
        if (json.angularDamping !== undefined) this.angularDamping = json.angularDamping;
        if (json.gravityScale !== undefined) this.gravityScale = json.gravityScale;
        if (json.layer !== undefined) this.layer = json.layer;
        if (json.mask !== undefined) this.mask = json.mask;
        if (json.flags !== undefined) this.flags = json.flags;
    }

    /**
     * Value equality over the configurable / persistent state — the same field
     * set that is serialized. Transient runtime state (`_bodyId`, `sleepState`,
     * sleep timers, accumulated force/torque) is system-owned and excluded.
     *
     * @param {RigidBody} other
     * @returns {boolean}
     */
    equals(other) {
        if (other === this) return true;
        if (other === null || other === undefined || other.isRigidBody !== true) return false;

        return this.kind === other.kind
            && this.mass === other.mass
            && this.linearDamping === other.linearDamping
            && this.angularDamping === other.angularDamping
            && this.gravityScale === other.gravityScale
            && this.layer === other.layer
            && this.mask === other.mask
            && this.flags === other.flags
            && this.linearVelocity.equals(other.linearVelocity)
            && this.angularVelocity.equals(other.angularVelocity)
            && this.inverseInertiaLocal.equals(other.inverseInertiaLocal);
    }

    /**
     * Hash over the same persistent state as {@link equals}. Equal bodies hash
     * equal.
     * @returns {number}
     */
    hash() {
        let h = this.kind | 0;
        h = (h * 31 + computeHashFloat(this.mass)) | 0;
        h = (h * 31 + computeHashFloat(this.linearDamping)) | 0;
        h = (h * 31 + computeHashFloat(this.angularDamping)) | 0;
        h = (h * 31 + computeHashFloat(this.gravityScale)) | 0;
        h = (h * 31 + (this.layer | 0)) | 0;
        h = (h * 31 + (this.mask | 0)) | 0;
        h = (h * 31 + (this.flags | 0)) | 0;
        h = (h * 31 + this.linearVelocity.hash()) | 0;
        h = (h * 31 + this.angularVelocity.hash()) | 0;
        h = (h * 31 + this.inverseInertiaLocal.hash()) | 0;
        return h;
    }
}

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

/**
 * @readonly
 * @type {boolean}
 */
RigidBody.prototype.isRigidBody = true;
