import { assert } from "../../../../../../../assert.js";
import { v3_distance_sqr } from "../../../../../../vec3/v3_distance_sqr.js";
import { bt_edge_kill } from "./bt_edge_kill.js";

const scratch_a = [0, 0, 0];
const scratch_b = [0, 0, 0];

/**
 * Kill every edge whose endpoints are within `max_distance` of each other —
 * including any pre-existing self-loops (v1 === v2). Adjacent faces are
 * cascaded through {@link bt_edge_kill}, so a sliver triangle (one short
 * edge, two longer ones) loses the short edge and its face here, and the
 * other two edges are left orphan for the standard cleanup pass to remove.
 *
 * The intended pre-pass before {@link bt_merge_verts_by_distance}. The merge
 * uses `bt_vertex_replace` internally, which produces a self-loop edge if
 * the two vertices it's collapsing are directly edge-connected — and
 * self-loops corrupt the disk cycle in ways that hang the downstream
 * `bt_mesh_fuse_duplicate_edges` walker. Killing the short edges up front
 * means the merge never sees a direct-edge pair, so it never produces a
 * self-loop, so the fuse pass terminates cleanly.
 *
 * @param {BinaryTopology} mesh
 * @param {number} [max_distance] inclusive endpoint distance; defaults match
 *                                {@link bt_merge_verts_by_distance}
 * @returns {number} number of edges killed
 */
export function bt_mesh_kill_short_edges(mesh, max_distance = 0.0001) {
    assert.defined(mesh, "mesh");
    assert.greaterThan(max_distance, 0, "max_distance must be greater than 0");

    const max_distance_sqr = max_distance * max_distance;

    const edge_pool = mesh.edges;
    const edge_count = edge_pool.size;

    // Collect victims first, kill after — bt_edge_kill mutates disk cycles and
    // face pools, so iterating the pool while mutating it is fragile.
    const victims = [];

    for (let e = 0; e < edge_count; e++) {
        if (!edge_pool.is_allocated(e)) continue;

        const v1 = mesh.edge_read_vertex1(e);
        const v2 = mesh.edge_read_vertex2(e);

        if (v1 === v2) {
            victims.push(e);
            continue;
        }

        mesh.vertex_read_coordinate(scratch_a, 0, v1);
        mesh.vertex_read_coordinate(scratch_b, 0, v2);

        const d2 = v3_distance_sqr(
            scratch_a[0], scratch_a[1], scratch_a[2],
            scratch_b[0], scratch_b[1], scratch_b[2],
        );

        if (d2 <= max_distance_sqr) {
            victims.push(e);
        }
    }

    for (let i = 0; i < victims.length; i++) {
        const e = victims[i];
        if (edge_pool.is_allocated(e)) {
            bt_edge_kill(mesh, e);
        }
    }

    return victims.length;
}
