import { computeHashFloat } from "../../../primitives/numbers/computeHashFloat.js";
import { randomPointInSphere } from "../../random/randomPointInSphere.js";
import { v3_length } from "../../vec3/v3_length.js";
import { AbstractShape3D } from "./AbstractShape3D.js";
import { compute_signed_distance_gradient_by_sampling } from "./util/compute_signed_distance_gradient_by_sampling.js";

/**
 * Sphere of arbitrary `radius`, centred at the body's local origin.
 *
 * Mirrors the {@link BoxShape3D} (arbitrary half-extents) ↔
 * {@link UnitCubeShape3D} (fixed 0.5) relationship: this is the general
 * parametrised sphere, and {@link UnitSphereShape3D} is the `radius = 1`
 * special case kept for the JSON `'sphere'` tag and the shared `INSTANCE`
 * singleton.
 *
 * The physics narrowphase recognises spheres via the shared `isSphereShape3D`
 * prototype marker and reads `radius`.
 *
 * @author Alex Goldring
 * @copyright Company Named Limited (c) 2026
 */
export class SphereShape3D extends AbstractShape3D {
    constructor() {
        super();
        /**
         * Sphere radius, in the body's local frame.
         * @type {number}
         */
        this.radius = 1;
    }

    /**
     * Convenience constructor.
     * @param {number} radius
     * @returns {SphereShape3D}
     */
    static from(radius) {
        const s = new SphereShape3D();
        s.radius = radius;
        return s;
    }

    get volume() {
        const r = this.radius;
        return (4 / 3) * Math.PI * r * r * r;
    }

    get surface_area() {
        const r = this.radius;
        return 4 * Math.PI * r * r;
    }

    support(result, result_offset, direction_x, direction_y, direction_z) {
        // Farthest point along `direction` on a sphere of this radius. Matches
        // the unit-sphere convention (no normalisation — callers pass a unit
        // direction); for radius r it is simply the direction scaled by r.
        const r = this.radius;
        result[result_offset] = direction_x * r;
        result[result_offset + 1] = direction_y * r;
        result[result_offset + 2] = direction_z * r;
    }

    compute_bounding_box(result) {
        const r = this.radius;
        result[0] = -r;
        result[1] = -r;
        result[2] = -r;
        result[3] = r;
        result[4] = r;
        result[5] = r;
    }

    nearest_point_on_surface(result, reference) {
        const r_x = reference[0];
        const r_y = reference[1];
        const r_z = reference[2];

        const len = v3_length(r_x, r_y, r_z);
        // At the centre the nearest surface point is undefined; return the
        // centre rather than NaN/Inf.
        const d = len > 0 ? this.radius / len : 0;

        result[0] = r_x * d;
        result[1] = r_y * d;
        result[2] = r_z * d;
    }

    signed_distance_gradient_at_point(result, point) {
        return compute_signed_distance_gradient_by_sampling(result, this, point);
    }

    signed_distance_at_point(point) {
        return v3_length(point[0], point[1], point[2]) - this.radius;
    }

    contains_point(point) {
        const x = point[0];
        const y = point[1];
        const z = point[2];
        const r = this.radius;

        return (x * x + y * y + z * z) < r * r;
    }

    sample_random_point_in_volume(result, result_offset, random) {
        randomPointInSphere(random, result, result_offset);
        const r = this.radius;
        result[result_offset] *= r;
        result[result_offset + 1] *= r;
        result[result_offset + 2] *= r;
    }

    equals(other) {
        return super.equals(other) && this.radius === other.radius;
    }

    hash() {
        return computeHashFloat(this.radius);
    }
}

/**
 * Fast type-check marker. Lets the physics narrowphase short-circuit
 * sphere-involved pairs to closed-form solvers (reading `radius`) rather than
 * running GJK + EPA on a smooth surface (the EPA-degenerate case). `true` for
 * both {@link SphereShape3D} and {@link UnitSphereShape3D} (via the prototype
 * chain).
 * @readonly
 * @type {boolean}
 */
SphereShape3D.prototype.isSphereShape3D = true;
