import { clamp01 } from "../../../math/clamp01.js";

/**
 * Lengths-squared below this threshold are treated as "this segment is a
 * point" so the algorithm degrades gracefully to point-segment closest.
 * @type {number}
 */
const SEGMENT_EPS = 1e-12;

/**
 * Closest-point pair on two 3D line segments. Writes parameters `s` and `t`
 * (each in `[0, 1]`) into `out[0]` and `out[1]` such that:
 *
 *     closest_point_on_S1 = P1 + s * (Q1 - P1)
 *     closest_point_on_S2 = P2 + t * (Q2 - P2)
 *
 * Source: Ericson, *Real-Time Collision Detection*, §5.1.9. Robust to
 * degenerate inputs (point-like segments, parallel segments) — both
 * cases fall back to the clamped point-on-line projection.
 *
 * @param {number[]|Float64Array} out length >= 2; receives [s, t]
 * @param {number} p1x  segment-A start x
 * @param {number} p1y
 * @param {number} p1z
 * @param {number} q1x  segment-A end x
 * @param {number} q1y
 * @param {number} q1z
 * @param {number} p2x  segment-B start x
 * @param {number} p2y
 * @param {number} p2z
 * @param {number} q2x  segment-B end x
 * @param {number} q2y
 * @param {number} q2z
 */
export function line3_closest_points_segment_segment(
    out,
    p1x, p1y, p1z, q1x, q1y, q1z,
    p2x, p2y, p2z, q2x, q2y, q2z
) {
    const d1x = q1x - p1x, d1y = q1y - p1y, d1z = q1z - p1z;
    const d2x = q2x - p2x, d2y = q2y - p2y, d2z = q2z - p2z;
    const rx  = p1x - p2x, ry  = p1y - p2y, rz  = p1z - p2z;

    const a = d1x * d1x + d1y * d1y + d1z * d1z; // |d1|²
    const e = d2x * d2x + d2y * d2y + d2z * d2z; // |d2|²
    const f = d2x * rx + d2y * ry + d2z * rz;    // d2·r

    let s, t;

    if (a <= SEGMENT_EPS && e <= SEGMENT_EPS) {
        // Both degenerate to points.
        s = 0; t = 0;
    } else if (a <= SEGMENT_EPS) {
        // S1 is a point.
        s = 0;
        t = clamp01(f / e);
    } else {
        const c = d1x * rx + d1y * ry + d1z * rz; // d1·r
        if (e <= SEGMENT_EPS) {
            // S2 is a point.
            t = 0;
            s = clamp01(-c / a);
        } else {
            const b = d1x * d2x + d1y * d2y + d1z * d2z; // d1·d2
            const denom = a * e - b * b; // always >= 0

            if (denom !== 0) {
                s = clamp01((b * f - c * e) / denom);
            } else {
                // Segments are parallel — any s is fine; pick 0.
                s = 0;
            }

            t = (b * s + f) / e;

            if (t < 0) {
                t = 0;
                s = clamp01(-c / a);
            } else if (t > 1) {
                t = 1;
                s = clamp01((b - c) / a);
            }
        }
    }

    out[0] = s;
    out[1] = t;
}
