import { assert } from "../../../../core/assert.js";

/**
 * Subtract the pressure gradient from a velocity field in-place, making the velocity
 * (approximately) divergence-free:
 *
 *     v(c) -= 0.5 * ( p(x+1) - p(x-1), p(y+1) - p(y-1), p(z+1) - p(z-1) )
 *
 * Pairs with {@link v3_grid_solve_pressure}. After this, divergence will be
 * close to zero in the bulk; residual divergence at boundaries / under-converged solves
 * is normal.
 *
 * Boundary cells use one-sided differences. Solid cells have their velocity zeroed
 * (no-slip), and neighbour gradients across a solid face reflect the cell's own
 * pressure (consistent with the Neumann condition used in the solve), which keeps the
 * flow tangent to the boundary.
 *
 * Everything this needs is read straight from `neighbour_mask`, the same pre-baked
 * per-cell bitmask {@link v3_grid_solve_pressure} consumes (populated by
 * {@link FluidField.recomputeSolidNeighbourMask}). Encoding:
 *
 *     bit 0 (= 1)   : -x neighbour is fluid
 *     bit 1 (= 2)   : +x neighbour is fluid
 *     bit 2 (= 4)   : -y neighbour is fluid
 *     bit 3 (= 8)   : +y neighbour is fluid
 *     bit 4 (= 16)  : -z neighbour is fluid
 *     bit 5 (= 32)  : +z neighbour is fluid
 *     bit 7 (= 128) : this cell is itself solid
 *
 * A set neighbour bit means "that neighbour is fluid (in-bounds AND non-solid)", so a
 * clear bit reflects the cell's own pressure — folding the boundary check and the
 * solid-neighbour check into one register-resident bit-test per face.
 *
 * Bit 7 carries the self-solid flag that used to require the separate `solid` array.
 * The solve can't use the low 6 bits for this (a solid cell and an isolated fluid cell
 * both have zero fluid-neighbour bits) but needs opposite handling here — the solid
 * must be zeroed for no-slip, while the isolated fluid's gradient already nets to zero
 * (every face reflects its own pressure) and must be left alone. Bit 7 distinguishes
 * them, so a single mask read drives both the zeroing and the gradient.
 *
 * @param {Float32Array} vel_x          Mutated in place.
 * @param {Float32Array} vel_y          Mutated in place.
 * @param {Float32Array} vel_z          Mutated in place.
 * @param {Float32Array} pressure       From {@link v3_grid_solve_pressure}.
 * @param {number} res_x
 * @param {number} res_y
 * @param {number} res_z
 * @param {Uint8Array} neighbour_mask   Length ≥ res_x*res_y*res_z. Same buffer the
 *     pressure solve uses; MUST be recomputed whenever the solid mask changes.
 */
export function v3_grid_subtract_pressure_gradient(vel_x, vel_y, vel_z, pressure, res_x, res_y, res_z, neighbour_mask) {
    const cell_count = res_x * res_y * res_z;
    assert.greaterThanOrEqual(vel_x.length, cell_count, "vel_x covers grid");
    assert.greaterThanOrEqual(vel_y.length, cell_count, "vel_y covers grid");
    assert.greaterThanOrEqual(vel_z.length, cell_count, "vel_z covers grid");
    assert.greaterThanOrEqual(pressure.length, cell_count, "pressure covers grid");
    assert.greaterThanOrEqual(neighbour_mask.length, cell_count, "neighbour_mask covers grid");

    const slice_size = res_x * res_y;

    for (let z = 0; z < res_z; z++) {
        const z_off = z * slice_size;

        for (let y = 0; y < res_y; y++) {
            const y_off = y * res_x;

            for (let x = 0; x < res_x; x++) {
                const c = z_off + y_off + x;

                const mask = neighbour_mask[c];

                if (mask & 128) {
                    // Solid cell (bit 7): enforce no-slip.
                    vel_x[c] = 0;
                    vel_y[c] = 0;
                    vel_z[c] = 0;
                    continue;
                }

                // A clear bit means the neighbour is out-of-bounds or solid; reflect
                // the cell's own pressure so that face contributes 0 to the gradient,
                // consistent with ∂p/∂n = 0 at the wall.
                const p_xm = (mask & 1)  ? pressure[c - 1]          : pressure[c];
                const p_xp = (mask & 2)  ? pressure[c + 1]          : pressure[c];
                const p_ym = (mask & 4)  ? pressure[c - res_x]      : pressure[c];
                const p_yp = (mask & 8)  ? pressure[c + res_x]      : pressure[c];
                const p_zm = (mask & 16) ? pressure[c - slice_size] : pressure[c];
                const p_zp = (mask & 32) ? pressure[c + slice_size] : pressure[c];

                vel_x[c] -= 0.5 * (p_xp - p_xm);
                vel_y[c] -= 0.5 * (p_yp - p_ym);
                vel_z[c] -= 0.5 * (p_zp - p_zm);
            }
        }
    }
}
