import { Uint32Heap } from "../../../../../../collection/heap/Uint32Heap.js";
import { NULL_POINTER } from "../BinaryTopology.js";
import { bt_mesh_compute_edge_distance_eikonal } from "./bt_mesh_compute_edge_distance_eikonal.js";

/**
 * Build distance field for a given island (group of faces).
 * Distances are calculated as shortest from the boundary.
 *
 * Distances are the shortest distances from the boundary along the mesh.
 * Only the island vertices are considered.
 *
 * Unreachable vertices are marked with Infinity distance.
 * @param {Map<number, number>} vertex_boundary_distances Mapping from vertex ID to distance from boundary. The result is stored here.
 * @param {BinaryTopology} mesh
 * @param {Set<number>} island_vertices
 * @param {Set<number>} boundary_vertices Subset of island_vertices that are on the boundary.
 */
export function bt_mesh_build_boundary_distance_field(
    vertex_boundary_distances,
    mesh,
    island_vertices,
    boundary_vertices
) {

    const heap = new Uint32Heap(island_vertices.size);

    // initialize distances
    for (const v of island_vertices) {
        if (boundary_vertices.has(v)) {
            vertex_boundary_distances.set(v, 0);
            heap.insert(v, 0);
        } else {
            vertex_boundary_distances.set(v, Infinity);
        }
    }

    const scratch_vertex = new Float32Array(3);

    while (!heap.is_empty()) {
        const current_vertex = heap.pop_min();
        const current_distance = vertex_boundary_distances.get(current_vertex);

        // Traverse disk cycle to get neighboring edges
        const e_first = mesh.vertex_read_edge(current_vertex);
        if (e_first === NULL_POINTER) {
            continue;
        }

        let e_curr = e_first;
        do {
            const v1 = mesh.edge_read_vertex1(e_curr);
            const v2 = mesh.edge_read_vertex2(e_curr);

            const is_v1 = v1 === current_vertex;
            const v_other = is_v1 ? v2 : v1;

            // ---------------------------------------------------------
            // STEP A: Vertex Update (Dijkstra Fallback)
            // ---------------------------------------------------------
            // We must attempt to update the neighbor (v_other) directly from current_vertex.
            // This ensures that 'v_other' gets a valid distance (if it was Infinity),
            // allowing the subsequent Eikonal step to have a valid edge to propagate from.

            // 1. Read coordinates to compute edge length
            mesh.vertex_read_coordinate(scratch_vertex, 0, current_vertex);
            const cx = scratch_vertex[0];
            const cy = scratch_vertex[1];
            const cz = scratch_vertex[2];

            mesh.vertex_read_coordinate(scratch_vertex, 0, v_other);
            const ox = scratch_vertex[0];
            const oy = scratch_vertex[1];
            const oz = scratch_vertex[2];

            const edge_len = Math.hypot(cx - ox, cy - oy, cz - oz);
            const new_neighbor_dist = current_distance + edge_len;

            let other_distance = vertex_boundary_distances.get(v_other);

            // Update v_other if the direct edge path is shorter than what it currently has
            if (new_neighbor_dist < other_distance) {
                vertex_boundary_distances.set(v_other, new_neighbor_dist);
                heap.insert_or_update(v_other, new_neighbor_dist);
                // Update local variable so STEP B uses the tighter bound immediately
                other_distance = new_neighbor_dist;
            }

            // ---------------------------------------------------------
            // STEP B: Edge Update (Eikonal)
            // ---------------------------------------------------------
            // Now that we've ensured v_other is as up-to-date as possible,
            // we check if we can form a valid front (edge) to update the triangle's 3rd vertex.

            if (other_distance !== undefined && other_distance !== Infinity) {

                // Find the faces (triangles) sharing this edge using the radial cycle
                const l_first = mesh.edge_read_loop(e_curr);

                if (l_first !== NULL_POINTER) {
                    let l_curr = l_first;
                    do {
                        const face = mesh.loop_read_face(l_curr);

                        // Find the 3rd "target" vertex of this triangular face
                        let fl = mesh.face_read_loop(face);
                        let v_target = -1;


                        // TODO assert the 3-vertex assumption

                        // Iterate max 3 times (explicitly assuming faces are triangles)
                        for (let i = 0; i < 3; i++) {
                            const fv = mesh.loop_read_vertex(fl);
                            if (fv !== v1 && fv !== v2) {
                                v_target = fv;
                                break;
                            }
                            fl = mesh.loop_read_next(fl);
                        }

                        // If we found a valid 3rd vertex inside our island, attempt to relax its distance
                        if (v_target !== -1 && island_vertices.has(v_target)) {

                            const new_distance = bt_mesh_compute_edge_distance_eikonal(
                                mesh,
                                current_vertex,
                                v_other,
                                current_distance,
                                other_distance,
                                v_target
                            );

                            const existing_distance = vertex_boundary_distances.get(v_target);

                            if (new_distance < existing_distance) {
                                vertex_boundary_distances.set(v_target, new_distance);
                                heap.insert_or_update(v_target, new_distance);
                            }
                        }

                        // Move to the next face sharing this edge
                        l_curr = mesh.loop_read_radial_next(l_curr);
                    } while (l_curr !== l_first && l_curr !== NULL_POINTER);
                }
            }

            // Move to the next edge in the vertex's disk cycle
            e_curr = is_v1 ? mesh.edge_read_v1_disk_next(e_curr) : mesh.edge_read_v2_disk_next(e_curr);
        } while (e_curr !== e_first && e_curr !== NULL_POINTER);
    }
}