const EPSILON = 1e-10;

/**
 * NxN matrix Householder reduction to upper Hessenberg form via similarity
 * transformations. After this routine, `a[c*n + r] == 0` for every `r > c+1`
 * — i.e. only the main diagonal and the first sub-diagonal carry non-zeros
 * below the diagonal.
 *
 * Inner loops are arranged so the hot accesses stream one column at a time
 * (contiguous in column-major).
 *
 * @see http://www-in.aut.ac.jp/~minemura/pub/Csimu/C/QRmethod.html
 * @param {number[]|Float64Array} a Square matrix, column-major flat layout (a[j*n + i] = row i, col j); modified in place
 * @param {number} n matrix size (e.g. 4 for 4x4 matrix)
 */
export function matrix_householder_in_place(a, n) {
    const u = new Float64Array(n);
    const d = new Float64Array(n);
    const ds = new Float64Array(n);

    let i, j, k;
    let sum;

    for (k = 0; k <= n - 3; k++) {

        const kOff = k * n;

        for (i = 0; i <= k; i++) {
            u[i] = 0;
        }

        // Read the sub-diagonal of column k. With column-major storage, this
        // is contiguous in memory.
        for (i = k + 1; i < n; i++) {
            u[i] = a[kOff + i];
        }

        // Build transformation vector
        sum = 0.0;

        for (i = k + 1; i < n; i++) {
            sum = sum + u[i] * u[i];
        }

        if (sum < EPSILON * EPSILON) {
            // sub-column already zero — nothing to reduce at this k
            continue;
        }

        const u_k_1 = u[k + 1];
        const u_k_1_abs = Math.abs(u_k_1);

        // sigma = sign(u[k+1]) · ‖u‖. When u[k+1] = 0 the sign is arbitrary
        // (entries below are still nonzero by the `sum` check above), so pick
        // +‖u‖.
        const sigma = u_k_1_abs < EPSILON
            ? Math.sqrt(sum)
            : Math.sqrt(sum) * u_k_1 / u_k_1_abs;

        u[k + 1] += sigma;

        const v_norm = Math.sqrt(2.0 * sigma * u[k + 1]);

        for (i = k + 1; i < n; i++) {
            u[i] /= v_norm;
        }

        // Similarity transformation A := (I - 2uuᵀ) A (I - 2uuᵀ).
        // Compute d = A u (matrix-vector with column-major: outer over j,
        // inner over i streams one column at a time).
        for (i = 0; i < n; i++) {
            d[i] = 0.0;
        }
        for (j = k + 1; j < n; j++) {
            const u_j = u[j];
            const jOff = j * n;
            for (i = 0; i < n; i++) {
                d[i] += a[jOff + i] * u_j;
            }
        }

        // Compute ds = Aᵀ u. In column-major, row i of Aᵀ is column i of A,
        // which is contiguous starting at i*n. So the inner loop streams one
        // column.
        for (i = 0; i < n; i++) {
            let s = 0.0;
            const iOff = i * n;
            for (j = k + 1; j < n; j++) {
                s += a[iOff + j] * u[j];
            }
            ds[i] = s;
        }

        let ud = 0.0;
        let uds = 0.0;

        for (i = k + 1; i < n; i++) {
            const u_i = u[i];

            ud += u_i * d[i];
            uds += u_i * ds[i];
        }

        for (i = 0; i < n; i++) {
            const u_i = u[i];

            d[i] = 2.0 * (d[i] - ud * u_i);
            ds[i] = 2.0 * (ds[i] - uds * u_i);
        }

        // Outer-product update A -= u·dsᵀ + d·uᵀ. Walk one column j at a time
        // (inner i is contiguous within column j).
        for (j = 0; j < n; j++) {
            const u_j = u[j];
            const ds_j = ds[j];
            const jOff = j * n;
            for (i = 0; i < n; i++) {
                a[jOff + i] -= u[i] * ds_j + d[i] * u_j;
            }
        }
    }
}
