import { combine_hash } from "../../../../core/collection/array/combine_hash.js";
import { computeHashFloat } from "../../../../core/primitives/numbers/computeHashFloat.js";
import { AbstractFluidEffector } from "./AbstractFluidEffector.js";

/**
 * Adds the same acceleration to every fluid cell each step (gravity, ambient wind).
 *
 * `force` is in WORLD units per second squared. The `world_to_grid` matrix is applied
 * (as a direction transform — translation ignored) inside {@link apply} to convert
 * the force into the target field's grid-cell units before depositing. The same
 * GlobalFluidEffector applied to fields with different `cell_size` therefore produces
 * the same world-space acceleration on each — gravity stays gravity.
 *
 * Solid cells are left untouched — the projection step would zero them anyway, but
 * skipping the write is slightly cheaper and keeps the solid mask authoritative for
 * effectors as well as the solver.
 */
export class GlobalFluidEffector extends AbstractFluidEffector {

    /**
     * Acceleration vector in WORLD units per second squared (e.g. `[0, -9.8, 0]` for
     * Earth gravity in metres-and-seconds). Per-step deposit into the velocity field
     * is `world_to_grid · force × dt`.
     *
     * @type {[number, number, number]}
     */
    force = [0, 0, 0];

    /**
     * @param {FluidField} field
     * @param {number} dt
     * @param {Float32Array|number[]} world_to_grid  Column-major 4×4 affine; only the
     *                                               linear (upper-left 3×3) part is
     *                                               used since force is a direction.
     */
    apply(field, dt, world_to_grid) {
        const wfx = this.force[0];
        const wfy = this.force[1];
        const wfz = this.force[2];

        if (wfx === 0 && wfy === 0 && wfz === 0) {
            return;
        }

        // Direction-transform: apply the linear part of world_to_grid (translation in
        // m[12..14] is for points, not directions). For the axis-aligned uniform-scale
        // matrices built by FluidSystem this collapses to division by cell_size, so
        // world m/s² becomes grid cells/s².
        const m = world_to_grid;
        const fx = (m[0] * wfx + m[4] * wfy + m[8]  * wfz) * dt;
        const fy = (m[1] * wfx + m[5] * wfy + m[9]  * wfz) * dt;
        const fz = (m[2] * wfx + m[6] * wfy + m[10] * wfz) * dt;

        const vx = field.velocity_x;
        const vy = field.velocity_y;
        const vz = field.velocity_z;
        const solid = field.solid;
        const n = vx.length;

        for (let i = 0; i < n; i++) {
            if (solid[i] !== 0) {
                continue;
            }
            vx[i] += fx;
            vy[i] += fy;
            vz[i] += fz;
        }
    }

    /**
     * @param {AbstractFluidEffector} other
     * @return {boolean}
     */
    equals(other) {
        if (other === this) {
            return true;
        }
        if (!(other instanceof GlobalFluidEffector)) {
            return false;
        }
        return this.force[0] === other.force[0]
            && this.force[1] === other.force[1]
            && this.force[2] === other.force[2];
    }

    /**
     * @return {number}
     */
    hash() {
        return combine_hash(
            computeHashFloat(this.force[0]),
            computeHashFloat(this.force[1]),
            computeHashFloat(this.force[2])
        );
    }

    /**
     * Position-free — applies to every cell of any field. Infinite bounds means the
     * broad-phase will never cull this effector.
     * @param {AABB3} out
     */
    getBoundingBox(out) {
        out.setInfiniteBounds();
    }
}
