/**
 * Apply the world-frame inverse inertia tensor of a rigid body to a world-space
 * vector and write the result.
 *
 * Body-frame inverse inertia is supplied as a diagonal in principal axes
 * (a Vector3-like with `.x .y .z`). World inverse inertia is
 * `I_w⁻¹ = R · diag · R^T` where `R` is the body's orientation. We compute
 * `result = R · diag · R^T · v` without materialising the full 3x3 matrix:
 *
 *   1. `v_body = R^T · v`   (rotate v into body frame)
 *   2. scale component-wise by the diagonal
 *   3. `result = R · v_body_scaled`
 *
 * Quaternion-rotate-vector uses the standard `q · v · q*` identity. Inverse
 * rotation uses the conjugate quaternion.
 *
 * Sphere short-circuit: if the inverse inertia is isotropic (ix === iy === iz),
 * the rotation cancels and `result = ix * v`. Skip the trig and save the work.
 *
 * Decoupled from RigidBody / Transform on purpose — callers pass only what
 * is actually read. Lets `compute_penetration` and other standalone
 * geometry helpers reuse this without manufacturing a body.
 *
 * @param {number[]|Float64Array} result length >= 3
 * @param {number} result_offset
 * @param {{x: number, y: number, z: number}} inv_inertia_local
 *        Body-frame inverse inertia diagonal (typically `rb.inverseInertiaLocal`).
 * @param {{x: number, y: number, z: number, w: number}} rotation
 *        Body orientation as a unit quaternion (typically `transform.rotation`).
 * @param {number} vx
 * @param {number} vy
 * @param {number} vz
 */
export function world_inverse_inertia_apply(
    result, result_offset,
    inv_inertia_local, rotation,
    vx, vy, vz
) {
    // Thin object-reading wrapper over the raw-float kernel. Reads the
    // quaternion fields eagerly (harmless in the isotropic short-circuit,
    // where they're ignored) so the kernel is the single source of the math —
    // keeping object callers and the data-oriented (SoA) solver path
    // bit-identical.
    world_inverse_inertia_apply_raw(
        result, result_offset,
        inv_inertia_local.x, inv_inertia_local.y, inv_inertia_local.z,
        rotation.x, rotation.y, rotation.z, rotation.w,
        vx, vy, vz
    );
}

/**
 * Raw-float counterpart of {@link world_inverse_inertia_apply}: the same
 * `I_w⁻¹ = R · diag · R^T` application, with the inverse-inertia diagonal and
 * orientation quaternion passed as scalars rather than read off
 * `{x,y,z}` / `{x,y,z,w}` objects. The data-oriented contact / joint solver
 * reads these straight out of its `SolverBodyState` typed array, avoiding the
 * per-impulse object dereference on the hot path. The arithmetic is identical
 * to the object version (same operation order), so results are bit-identical.
 *
 * @param {number[]|Float64Array} result length >= 3
 * @param {number} result_offset
 * @param {number} ix
 * @param {number} iy
 * @param {number} iz Body-frame inverse inertia diagonal.
 * @param {number} qx
 * @param {number} qy
 * @param {number} qz
 * @param {number} qw Body orientation as a unit quaternion.
 * @param {number} vx
 * @param {number} vy
 * @param {number} vz
 */
export function world_inverse_inertia_apply_raw(
    result, result_offset,
    ix, iy, iz,
    qx, qy, qz, qw,
    vx, vy, vz
) {

    if (ix === iy && iy === iz) {
        // Isotropic — rotation cancels.
        result[result_offset] = vx * ix;
        result[result_offset + 1] = vy * ix;
        result[result_offset + 2] = vz * ix;
        return;
    }

    // Step 1: v_body = R^T · v.  R^T corresponds to applying the conjugate
    // quaternion (-qx, -qy, -qz, qw). Inlined for V8 inliner — see
    // PosedShape.support and core/geom/vec3/v3_quat3_apply_inverse.js
    // for the canonical form.
    const tx = qw * vx - qy * vz + qz * vy;
    const ty = qw * vy - qz * vx + qx * vz;
    const tz = qw * vz - qx * vy + qy * vx;
    const tw = qx * vx + qy * vy + qz * vz;

    const vbx = tx * qw + tw * qx + ty * qz - tz * qy;
    const vby = ty * qw + tw * qy + tz * qx - tx * qz;
    const vbz = tz * qw + tw * qz + tx * qy - ty * qx;

    // Step 2: scale by the principal-frame diagonal.
    const sx = vbx * ix;
    const sy = vby * iy;
    const sz = vbz * iz;

    // Step 3: result = R · scaled = q · scaled · q*. Inlined likewise.
    const ux = qw * sx + qy * sz - qz * sy;
    const uy = qw * sy + qz * sx - qx * sz;
    const uz = qw * sz + qx * sy - qy * sx;
    const uw = -qx * sx - qy * sy - qz * sz;

    result[result_offset] = ux * qw - uw * qx - uy * qz + uz * qy;
    result[result_offset + 1] = uy * qw - uw * qy - uz * qx + ux * qz;
    result[result_offset + 2] = uz * qw - uw * qz - ux * qy + uy * qx;
}
