import { solveCubic } from "../solveCubic.js";
import { spline3_hermite_bounds } from "./spline3_hermite_bounds.js";
import { spline3_hermite_bounds_t } from "./spline3_hermite_bounds_t.js";

/*
 1D specialization: A and B are scalar functions on [0, 1]. The "intersection"
 is anywhere their value ranges coincide.

 Two regimes:
   - Ranges overlap: any (s, t) with A(s) = v = B(t) for some v in the overlap
     is a true intersection. We pick v = midpoint of the overlap, then solve
     each cubic A(s) = v and B(t) = v for the first root in [0, 1].
   - Ranges disjoint: the closest pair sits at the inner extrema — A's max if A
     is below B (or A's min if A is above B), paired with the corresponding
     extremum of B. The parameter at each extremum comes from
     `spline3_hermite_bounds_t`.
*/

const _bounds_value = new Float64Array(4); // [a_lo, a_hi, b_lo, b_hi]
const _bounds_t = new Float64Array(4);     // [a_t_min, a_t_max, b_t_min, b_t_max]
const _cubic_roots = new Float64Array(3);

function first_cubic_root_in_unit(p0, p1, m0, m1, target) {
    // A(s) - target = (2 p0 - 2 p1 + m0 + m1) s³
    //               + (-3 p0 + 3 p1 - 2 m0 - m1) s²
    //               +  m0 s
    //               + (p0 - target)
    const a3 = 2 * p0 - 2 * p1 + m0 + m1;
    const a2 = -3 * p0 + 3 * p1 - 2 * m0 - m1;
    const a1 = m0;
    const a0 = p0 - target;

    const count = solveCubic(_cubic_roots, 0, a3, a2, a1, a0);
    let best_s = -1;
    for (let i = 0; i < count; i++) {
        const r = _cubic_roots[i];
        if (r >= 0 && r <= 1) {
            if (best_s < 0 || r < best_s) best_s = r;
        }
    }
    return best_s;
}

/**
 * Critical points of squared distance between two 1D cubic Hermite curves.
 * Inputs are flat 4-element arrays `[p0, p1, m0, m1]` for each curve.
 *
 * Always writes exactly one (s, t) pair to
 * `result[result_offset], result[result_offset + 1]` and returns 1. The pair
 * is either:
 *   - an intersection point where A(s) = B(t) (when value ranges overlap), or
 *   - the closest-approach pair at the relevant inner extrema (when disjoint).
 *
 * Caller is responsible for evaluating both curves at (s, t) and computing
 * squared distance.
 *
 * Required buffer size: `result.length >= result_offset + 2` (one (s, t) pair).
 *
 * @param {Float64Array|number[]} a length 4
 * @param {Float64Array|number[]} b length 4
 * @param {Float64Array|number[]} result length >= result_offset + 2
 * @param {number} result_offset
 * @returns {number} number of (s, t) pairs written (always 1)
 */
export function spline3_hermite_intersection_spline3_hermite_1d(
    a, b,
    result, result_offset
) {
    const a_p0 = a[0], a_p1 = a[1], a_m0 = a[2], a_m1 = a[3];
    const b_p0 = b[0], b_p1 = b[1], b_m0 = b[2], b_m1 = b[3];

    spline3_hermite_bounds(_bounds_value, 0, 1, a_p0, a_p1, a_m0, a_m1);
    const a_lo = _bounds_value[0];
    const a_hi = _bounds_value[1];

    spline3_hermite_bounds(_bounds_value, 2, 1, b_p0, b_p1, b_m0, b_m1);
    const b_lo = _bounds_value[2];
    const b_hi = _bounds_value[3];

    const overlap_lo = a_lo > b_lo ? a_lo : b_lo;
    const overlap_hi = a_hi < b_hi ? a_hi : b_hi;

    if (overlap_lo <= overlap_hi) {
        // Ranges overlap → exact intersection at distance 0.
        const target = 0.5 * (overlap_lo + overlap_hi);

        let s = first_cubic_root_in_unit(a_p0, a_p1, a_m0, a_m1, target);
        let t = first_cubic_root_in_unit(b_p0, b_p1, b_m0, b_m1, target);

        // The cubic root finder shouldn't miss given that target lies inside
        // both ranges, but guard against pathological numerics by falling back
        // to the relevant extremum parameter.
        if (s < 0 || t < 0) {
            spline3_hermite_bounds_t(_bounds_t, 0, 1, a_p0, a_p1, a_m0, a_m1);
            spline3_hermite_bounds_t(_bounds_t, 2, 1, b_p0, b_p1, b_m0, b_m1);
            if (s < 0) s = (target === a_lo) ? _bounds_t[0] : _bounds_t[1];
            if (t < 0) t = (target === b_lo) ? _bounds_t[2] : _bounds_t[3];
        }

        result[result_offset] = s;
        result[result_offset + 1] = t;
        return 1;
    }

    // Ranges disjoint. Recover the t parameters at the inner extrema.
    spline3_hermite_bounds_t(_bounds_t, 0, 1, a_p0, a_p1, a_m0, a_m1);
    spline3_hermite_bounds_t(_bounds_t, 2, 1, b_p0, b_p1, b_m0, b_m1);
    const a_t_min = _bounds_t[0];
    const a_t_max = _bounds_t[1];
    const b_t_min = _bounds_t[2];
    const b_t_max = _bounds_t[3];

    let s, t;
    if (a_hi < b_lo) {
        s = a_t_max;
        t = b_t_min;
    } else {
        s = a_t_min;
        t = b_t_max;
    }

    result[result_offset] = s;
    result[result_offset + 1] = t;
    return 1;
}
