import { assert } from "../assert.js";
import { EPSILON } from "./EPSILON.js";
import { solveQuadratic } from "./solveQuadratic.js";

const ONE_THIRD = 1.0 / 3.0;
const TWO_THIRDS_PI = 2.0 * Math.PI / 3.0;

/**
 * Return real solutions for a cubic polynomial: ax³ + bx² + cx + d
 * Repeated roots are written once each — multiplicity is not duplicated in the output.
 * Imaginary roots are not provided.
 *
 * @param {number[]|Float32Array|Float64Array} result solutions are written here
 * @param {number} result_offset offset into result array where solutions are written to
 * @param {number} a
 * @param {number} b
 * @param {number} c
 * @param {number} d
 * @returns {number} number of real roots found (0, 1, 2, or 3)
 */
export function solveCubic(result, result_offset, a, b, c, d) {
    assert.isNumber(a, 'a');
    assert.isNumber(b, 'b');
    assert.isNumber(c, 'c');
    assert.isNumber(d, 'd');

    if (Math.abs(a) < EPSILON) {
        // degrade to quadratic
        return solveQuadratic(result, result_offset, b, c, d);
    }

    // normalize: x³ + B·x² + C·x + D = 0
    const inv_a = 1 / a;
    const B = b * inv_a;
    const C = c * inv_a;
    const D = d * inv_a;

    // depress via x = y - B/3 → y³ + p·y + q = 0
    const B_third = B * ONE_THIRD;
    const p = C - B * B_third;
    const q = 2 * B_third * B_third * B_third - C * B_third + D;

    if (Math.abs(p) < EPSILON && Math.abs(q) < EPSILON) {
        // triple root at y = 0
        result[result_offset] = -B_third;
        return 1;
    }

    // Cardano's discriminant criterion. ratio = (q/2)² + (p/3)³.
    //   ratio > 0  → one real root, two complex
    //   ratio = 0  → repeated real roots (single + double, or triple covered above)
    //   ratio < 0  → three distinct real roots
    const half_q = q * 0.5;
    const third_p = p * ONE_THIRD;
    const ratio = half_q * half_q + third_p * third_p * third_p;

    if (ratio > EPSILON) {
        const sqrt_ratio = Math.sqrt(ratio);
        const u = Math.cbrt(-half_q + sqrt_ratio);
        const v = Math.cbrt(-half_q - sqrt_ratio);

        result[result_offset] = u + v - B_third;
        return 1;
    }

    if (ratio < -EPSILON) {
        // p must be negative for ratio < 0; sqrt(-p/3) is real
        const m = 2 * Math.sqrt(-third_p);
        const theta_third = ONE_THIRD * Math.acos(3 * q / (p * m));

        result[result_offset]     = m * Math.cos(theta_third) - B_third;
        result[result_offset + 1] = m * Math.cos(theta_third - TWO_THIRDS_PI) - B_third;
        result[result_offset + 2] = m * Math.cos(theta_third - 2 * TWO_THIRDS_PI) - B_third;
        return 3;
    }

    // ratio ≈ 0: simple + double root
    const u = Math.cbrt(-half_q);
    result[result_offset]     = 2 * u - B_third;
    result[result_offset + 1] = -u - B_third;
    return 2;
}
