import { v3_quat3_apply } from "../../../core/geom/vec3/v3_quat3_apply.js";
import { ray_sphere_local, ray_box_local, ray_capsule_local } from "./ray_shapes.js";
import { refine_ray_concave } from "./refine_ray_concave.js";

/**
 * Sentinel returned by {@link refine_ray_hit} for a shape it has no exact ray
 * test for (composite / arbitrary convex). The caller should keep the
 * broadphase AABB result for that leaf rather than treat it as a miss.
 * Distinct from `Infinity` (a refined miss) and any finite hit distance.
 * @type {number}
 */
export const RAY_REFINE_UNSUPPORTED = -1;

const lo = new Float64Array(3); // ray origin in shape-local frame
const ld = new Float64Array(3); // ray direction in shape-local frame
const ln = new Float64Array(3); // surface normal in shape-local frame

/**
 * Refine a broadphase ray hit against a body's true shape geometry.
 *
 * Transforms the world ray into the shape's local frame once (rotate by the
 * inverse body rotation — a unit direction stays unit, so the hit distance is
 * preserved), runs the matching canonical primitive ({@link ray_shapes}), and
 * rotates the returned local normal back to world. Spheres are
 * rotation-invariant, so they only need the translation.
 *
 * Handles the convex primitives (sphere / box / capsule). Concave shapes
 * (mesh / heightmap) and arbitrary composite convex shapes return
 * {@link RAY_REFINE_UNSUPPORTED} — the concave path is wired in separately, and
 * composites fall back to the broadphase AABB hit (no regression).
 *
 * @param {AbstractShape3D} shape
 * @param {{x:number,y:number,z:number}} position  body world position
 * @param {ArrayLike<number>} rotation  body world rotation quaternion (x,y,z,w)
 * @param {number} ox @param {number} oy @param {number} oz  ray origin (world)
 * @param {number} dx @param {number} dy @param {number} dz  ray dir (world, unit)
 * @param {number} tMax
 * @param {Float64Array|Vector3} outNormal  world surface normal, written on a
 *   finite hit
 * @returns {number} hit distance, `Infinity` on a refined miss, or
 *   {@link RAY_REFINE_UNSUPPORTED} when the shape has no exact ray test here
 */
export function refine_ray_hit(shape, position, rotation, ox, oy, oz, dx, dy, dz, tMax, outNormal) {
    if (shape.isSphereShape3D === true) {
        // Rotation-invariant: translate into the sphere's frame; the local
        // normal is already the world normal.
        return ray_sphere_local(outNormal, ox - position.x, oy - position.y, oz - position.z, dx, dy, dz, tMax, shape.radius);
    }

    if (shape.isBoxShape3D === true || shape.isCapsuleShape3D === true) {
        const qx = rotation[0], qy = rotation[1], qz = rotation[2], qw = rotation[3];
        // World → local: rotate by the inverse (conjugate) body rotation.
        v3_quat3_apply(lo, 0, ox - position.x, oy - position.y, oz - position.z, -qx, -qy, -qz, qw);
        v3_quat3_apply(ld, 0, dx, dy, dz, -qx, -qy, -qz, qw);

        let t;
        if (shape.isBoxShape3D === true) {
            const h = shape.half_extents;
            t = ray_box_local(ln, lo[0], lo[1], lo[2], ld[0], ld[1], ld[2], tMax, h.x, h.y, h.z);
        } else {
            t = ray_capsule_local(ln, lo[0], lo[1], lo[2], ld[0], ld[1], ld[2], tMax, shape.radius, shape.height * 0.5);
        }
        if (t === Infinity) return Infinity;

        // Local normal → world: rotate by the body rotation.
        v3_quat3_apply(outNormal, 0, ln[0], ln[1], ln[2], qx, qy, qz, qw);
        return t;
    }

    // Concave (mesh / heightmap): triangle decomposition.
    if (shape.is_convex === false) {
        return refine_ray_concave(shape, position, rotation, ox, oy, oz, dx, dy, dz, tMax, outNormal);
    }

    // Arbitrary composite convex (TransformedShape3D / UnionShape3D / …):
    // no exact ray test yet — caller falls back to the broadphase AABB hit.
    return RAY_REFINE_UNSUPPORTED;
}
