import { assert } from "../../../core/assert.js";
import { bvh_query_user_data_overlaps_aabb } from "../../../core/bvh2/bvh3/query/bvh_query_user_data_overlaps_aabb.js";
import { pack_body_id } from "../body/BodyStorage.js";

const scratch_aabb = new Float64Array(6);
const candidates = new Uint32Array(1024);

/**
 * Iterate every awake body, query both BVHs from each of its attached
 * collider leaves, dedupe via the {@link ManifoldStore}, optionally gate
 * each candidate through a user-supplied filter, and append each surviving
 * body-pair to `pair_list_out`.
 *
 * Compound-body aware: a body with multiple colliders has multiple BVH
 * leaves (one per collider). Each leaf's AABB query may turn up the same
 * other-body multiple times (across the multiple leaves on either side)
 * but the per-slot touched flag collapses those into one entry in the
 * canonical `(min, max)` pair list — narrowphase then resolves the
 * collider-cross-product internally.
 *
 * Determinism: pair encounter order is fully determined by the awake-list
 * order × per-body collider-list order × BVH leaf traversal order. All
 * three are deterministic functions of the link/unlink sequence and the
 * BVH state, so two worlds with the same scene history produce identical
 * pair lists.
 *
 * @param {BodyStorage} storage
 * @param {BVH} dynamic_bvh
 * @param {BVH} static_bvh
 * @param {ManifoldStore} manifolds
 * @param {Array<Array<{collider: Collider, transform: Transform, entity: number, bvhNode: number}>>} body_collider_lists
 * @param {PairList} pair_list_out cleared and filled
 * @param {((idA: number, idB: number) => boolean) | null} pair_filter optional
 *        gate. Returning `false` rejects the pair entirely — no manifold
 *        slot, no narrowphase, no contact events. Existing cached
 *        manifolds for a rejected pair time out naturally via the
 *        manifold-store grace counter.
 * @param {*} [pair_filter_this_arg]
 */
export function generate_pairs(
    storage,
    dynamic_bvh,
    static_bvh,
    manifolds,
    body_collider_lists,
    pair_list_out,
    pair_filter = null,
    pair_filter_this_arg = null,
) {
    pair_list_out.clear();

    const awake_count = storage.awake_count;

    for (let i = 0; i < awake_count; i++) {
        const body_idx = storage.awake_at(i);
        const list = body_collider_lists[body_idx];
        if (list === undefined || list.length === 0) continue;

        const gen = storage.generation_at(body_idx);
        const my_packed = pack_body_id(body_idx, gen); // single-source the bit layout

        for (let k = 0; k < list.length; k++) {
            const node = list[k].bvhNode;
            dynamic_bvh.node_get_aabb(node, scratch_aabb);

            // Dynamic ↔ Dynamic.
            let n = bvh_query_user_data_overlaps_aabb(
                candidates, 0,
                dynamic_bvh,
                scratch_aabb
            );
            // The BVH query writes leaves unconditionally — at capacity it both
            // drops leaves (typed-array OOB writes no-op) AND returns a count
            // past the buffer end, so the loop below would read `undefined`
            // candidates and build garbage pairs. Guard the buffer size.
            assert.lessThan(n, candidates.length, 'generate_pairs: dynamic broadphase overflowed the candidate buffer');
            // Production-safe clamp: the assert is stripped at bundle time, so
            // without this an overflowing query (count past the buffer) would read
            // `undefined` candidates and fabricate NaN-keyed garbage pairs. Drop
            // the overflow deterministically — the fat AABBs persist, so any
            // missed pair is re-detected next frame.
            if (n > candidates.length) n = candidates.length;
            for (let c = 0; c < n; c++) {
                const other = candidates[c];
                if (other === my_packed) continue;

                const idA = my_packed < other ? my_packed : other;
                const idB = my_packed < other ? other : my_packed;

                const existing = manifolds.find(idA, idB);
                if (existing !== -1 && manifolds.is_touched(existing)) continue;
                if (pair_filter !== null && !pair_filter.call(pair_filter_this_arg, idA, idB)) continue;
                manifolds.acquire(idA, idB);
                pair_list_out.push(idA, idB);
            }

            // Dynamic ↔ Static.
            n = bvh_query_user_data_overlaps_aabb(
                candidates, 0,
                static_bvh,
                scratch_aabb
            );
            assert.lessThan(n, candidates.length, 'generate_pairs: static broadphase overflowed the candidate buffer');
            if (n > candidates.length) n = candidates.length; // prod-safe clamp (see dynamic loop above)
            for (let c = 0; c < n; c++) {
                const other = candidates[c];
                const idA = my_packed < other ? my_packed : other;
                const idB = my_packed < other ? other : my_packed;

                const existing = manifolds.find(idA, idB);
                if (existing !== -1 && manifolds.is_touched(existing)) continue;
                if (pair_filter !== null && !pair_filter.call(pair_filter_this_arg, idA, idB)) continue;
                manifolds.acquire(idA, idB);
                pair_list_out.push(idA, idB);
            }
        }
    }
}
