import { bt_mesh_walk_boundary_loops } from "../query/bt_mesh_walk_boundary_loops.js";
import { bt_mesh_face_poke } from "./face/bt_face_poke.js";
import { bt_radial_loop_add } from "./bt_radial_loop_add.js";

const scratch_coord = [0, 0, 0];

/**
 * Walk a boundary cycle (returned by {@link bt_mesh_walk_boundary_loops}) and
 * produce a vertex/edge sequence starting at `start_vertex` and going around
 * the cycle in the direction `start_vertex` dictates.
 *
 * Output arrays are filled in place; both have length equal to `cycle.length`.
 *
 * @param {BinaryTopology} mesh
 * @param {number[]} cycle
 * @param {number} start_vertex must be an endpoint of one of the cycle's edges
 * @param {number[]} out_vertices
 * @param {number[]} out_edges
 */
function order_cycle_from(mesh, cycle, start_vertex, out_vertices, out_edges) {
    const n = cycle.length;

    const v_to_edges = new Map();
    for (let i = 0; i < n; i++) {
        const e = cycle[i];
        const v1 = mesh.edge_read_vertex1(e);
        const v2 = mesh.edge_read_vertex2(e);
        let bucket = v_to_edges.get(v1);
        if (bucket === undefined) {
            bucket = [];
            v_to_edges.set(v1, bucket);
        }
        bucket.push(e);
        bucket = v_to_edges.get(v2);
        if (bucket === undefined) {
            bucket = [];
            v_to_edges.set(v2, bucket);
        }
        bucket.push(e);
    }

    let v_curr = start_vertex;
    let e_prev = -1;

    for (let i = 0; i < n; i++) {
        out_vertices[i] = v_curr;

        const edges_at_v = v_to_edges.get(v_curr);
        const e_next = (edges_at_v[0] === e_prev) ? edges_at_v[1] : edges_at_v[0];

        out_edges[i] = e_next;

        const e_v1 = mesh.edge_read_vertex1(e_next);
        const e_v2 = mesh.edge_read_vertex2(e_next);
        v_curr = (e_v1 === v_curr) ? e_v2 : e_v1;
        e_prev = e_next;
    }
}

/**
 * Close every boundary hole in `mesh` by fan-tessellating each boundary loop
 * around its centroid. A new Steiner vertex is added at the centroid of each
 * hole; one triangle is emitted per boundary edge, connecting the edge's two
 * vertices to the centroid.
 *
 * After this returns, no edge in the mesh is a boundary edge (every edge has
 * exactly two faces on its radial cycle).
 *
 * Throws if any boundary vertex has more than two boundary edges (figure-8 /
 * pinched boundaries). Mutates the mesh in place.
 *
 * @param {BinaryTopology} mesh
 * @returns {number} number of boundary holes that were closed
 */
export function bt_mesh_close_boundary_holes(mesh) {
    const cycles = bt_mesh_walk_boundary_loops(mesh);

    const ordered_vertices = [];
    const ordered_edges = [];

    for (const cycle of cycles) {
        const n = cycle.length;

        // Each new loop on a boundary edge must wind opposite the existing
        // loop, so the shared edge ends up with consistent radial orientation.
        const seed_edge = cycle[0];
        const seed_loop = mesh.edge_read_loop(seed_edge);
        const existing_loop_vertex = mesh.loop_read_vertex(seed_loop);
        const seed_v1 = mesh.edge_read_vertex1(seed_edge);
        const seed_v2 = mesh.edge_read_vertex2(seed_edge);
        const start_vertex = (existing_loop_vertex === seed_v1) ? seed_v2 : seed_v1;

        ordered_vertices.length = n;
        ordered_edges.length = n;
        order_cycle_from(mesh, cycle, start_vertex, ordered_vertices, ordered_edges);

        // Compute centroid of the hole's vertices.
        let cx = 0, cy = 0, cz = 0;
        for (let i = 0; i < n; i++) {
            mesh.vertex_read_coordinate(scratch_coord, 0, ordered_vertices[i]);
            cx += scratch_coord[0];
            cy += scratch_coord[1];
            cz += scratch_coord[2];
        }
        cx /= n;
        cy /= n;
        cz /= n;

        // Allocate a new face plus n loops, wire them into the face cycle and
        // each boundary edge's radial cycle. Face normal stays zero; downstream
        // tetrahedralization is position-only.
        const new_face = mesh.faces.allocate();

        const loops = new Array(n);
        for (let i = 0; i < n; i++) {
            loops[i] = mesh.loop_create();
            mesh.loop_write_vertex(loops[i], ordered_vertices[i]);
            mesh.loop_write_face(loops[i], new_face);
            bt_radial_loop_add(mesh, loops[i], ordered_edges[i]);
        }

        for (let i = 0; i < n; i++) {
            mesh.loop_write_next(loops[i], loops[(i + 1) % n]);
            mesh.loop_write_prev(loops[i], loops[(i - 1 + n) % n]);
        }

        mesh.face_write_loop(new_face, loops[0]);

        // Fan from centroid — adds the Steiner vertex and replaces the n-gon
        // face with n triangles.
        bt_mesh_face_poke(mesh, new_face, cx, cy, cz);
    }

    return cycles.length;
}
