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

/**
 * ECS component holding effectors that contribute to fluid fields each simulation step.
 *
 * Effectors here are decoupled from any specific {@link FluidField} — {@link FluidSystem}
 * gathers all effectors across all entities and applies them to every fluid field each
 * step. Effectors that don't overlap a given field self-cull via their radius / position
 * checks, so attachment is implicit rather than configured.
 *
 * Pair this component with a `Transform` on the same entity to let position-bearing
 * effectors ({@link WakeFluidEffector}, {@link ImpulseFluidEffector}, ...) follow the
 * entity. The system calls each effector's {@link AbstractFluidEffector.syncFromTransform}
 * with the entity's Transform before stepping the simulator.
 */
export class FluidEffectorsComponent {

    /**
     * Effectors applied each simulation step.
     * @type {AbstractFluidEffector[]}
     */
    effectors = [];

    /**
     * Append an effector. The same instance may only be added once; pushing duplicates
     * causes the effector to fire twice per step.
     * @param {AbstractFluidEffector} effector
     */
    addEffector(effector) {
        assert.defined(effector, "effector");
        assert.notNull(effector, "effector");
        this.effectors.push(effector);
    }

    /**
     * Remove an effector by reference. No-op if not present.
     * @param {AbstractFluidEffector} effector
     */
    removeEffector(effector) {
        const i = this.effectors.indexOf(effector);
        if (i !== -1) {
            this.effectors.splice(i, 1);
        }
    }

    /**
     * Position-dependent equality: same number of effectors, each pair at the same
     * index `.equals` each other. Re-ordering counts as a change.
     * @param {FluidEffectorsComponent} other
     * @return {boolean}
     */
    equals(other) {
        if (other === this) {
            return true;
        }
        if (!(other instanceof FluidEffectorsComponent)) {
            return false;
        }
        const a = this.effectors;
        const b = other.effectors;
        const n = a.length;
        if (n !== b.length) {
            return false;
        }
        for (let i = 0; i < n; i++) {
            if (!a[i].equals(b[i])) {
                return false;
            }
        }
        return true;
    }

    /**
     * Order-dependent hash, FNV-like fold over child effector hashes.
     * @return {number}
     */
    hash() {
        const a = this.effectors;
        const n = a.length;
        let h = n;
        for (let i = 0; i < n; i++) {
            h = ((h << 5) - h) + a[i].hash();
            h |= 0;
        }
        return h;
    }
}

FluidEffectorsComponent.prototype.isFluidEffectorsComponent = true;
