import { assert } from "../../../assert.js";
import {
    bt_mesh_fuse_duplicate_edges
} from "../topology/struct/binary/io/edge/bt_mesh_fuse_duplicate_edges.js";
import {
    bt_mesh_kill_short_edges
} from "../topology/struct/binary/io/edge/bt_mesh_kill_short_edges.js";
import {
    bt_mesh_cleanup_faceless_references
} from "../topology/struct/binary/io/bt_mesh_cleanup_faceless_references.js";
import {
    bt_mesh_close_boundary_holes
} from "../topology/struct/binary/io/bt_mesh_close_boundary_holes.js";
import { bt_mesh_compact } from "../topology/struct/binary/io/bt_mesh_compact.js";
import {
    bt_mesh_sample_interior_grid_points
} from "../topology/struct/binary/query/bt_mesh_sample_interior_grid_points.js";
import {
    bt_merge_verts_by_distance
} from "../topology/struct/binary/io/vertex/bt_merge_verts_by_distance.js";
import { compute_delaunay_tetrahedral_mesh } from "./delaunay/compute_delaunay_tetrahedral_mesh.js";
import {
    tetrahedral_mesh_carve_outside_surface
} from "./tetrahedral_mesh_carve_outside_surface.js";

// Default vertex-merge tolerance for the pre-tetrahedralization weld pass.
// Two surface vertices closer than this collapse to one; below this scale
// they would just create degenerate sliver tets anyway. Generous enough to
// soak up float32 round-trip drift; tight enough not to swallow real
// detail at typical model scales (Suzanne, cube, bunny, …).
const VERTEX_MERGE_DISTANCE = 1e-4;


/**
 * Tetrahedralize the volume enclosed by a triangle surface mesh. The result
 * is a Delaunay tetrahedral mesh whose vertices are the surface's vertices
 * (plus any hole-closing centroids and any opt-in interior Steiner points)
 * and whose enclosed region approximates the surface's interior.
 *
 * The input is treated as airtight; any boundary holes are closed up front
 * by fanning each boundary loop around its centroid (a Steiner vertex is
 * added at each hole's centroid). The closed surface then drives the
 * Delaunay input set and the inside/outside classification used to carve
 * away exterior tets — non-convex shapes (e.g., Stanford bunny) are
 * supported.
 *
 * The caller's surface mesh is not mutated; a clone is taken internally.
 *
 * Interior Steiner points are OFF by default. Pass a positive
 * `interior_steiner_cells_per_dim` (e.g. ⌈cbrt(surface_vert_count)⌉) to
 * fill the volume with interior detail — useful for FEM / simulation where
 * thin sliver tets and surface-bound vertex distribution would hurt
 * conditioning. Note that interior Steiner does NOT reduce the carving's
 * volume gap (that comes from tets straddling the surface, and adding
 * interior points doesn't shrink those); a tighter volume gap needs
 * surface subdivision or true constraint-recovery, neither of which is
 * implemented here.
 *
 * Interior Steiner can also HURT total volume on pathological inputs (two
 * solids meeting at a single vertex, very thin hollow shells) — the
 * Delaunay produces bridge tets across the pinch that carry legitimate
 * interior on each side but whose centroids land in the void and get
 * carved. Leave Steiner off (the default) for those cases.
 *
 * @param {TetrahedralMesh} tet_mesh empty, populated in place
 * @param {BinaryTopology} surface triangle surface mesh, near-airtight
 * @param {number} [interior_steiner_cells_per_dim] grid resolution along the
 *        longest bbox axis for interior Steiner sampling; 0 disables
 * @returns {Float32Array} flat (x,y,z) positions array indexed by tet vertex ID
 */
export function compute_tetrahedral_mesh_from_surface(
    tet_mesh,
    surface,
    interior_steiner_cells_per_dim = 0
) {
    assert.defined(tet_mesh, 'tet_mesh');
    assert.equal(tet_mesh.isTetrahedralMesh, true, 'tet_mesh.isTetrahedralMesh !== true');
    assert.defined(surface, 'surface');
    assert.equal(surface.isBinaryTopology, true, 'surface.isBinaryTopology !== true');

    const working_surface = surface.clone();

    // Pre-pass: kill any edge whose endpoints are within VERTEX_MERGE_DISTANCE
    // (and any pre-existing self-loops). If left in place, bt_merge_verts_by_distance
    // would collapse the endpoints via bt_vertex_replace, which silently
    // produces a self-loop edge (v1 === v2). That self-loop has its v1 and v2
    // disk pointers in the same vertex's cycle, branching the cycle in a way
    // that hangs the downstream bt_mesh_fuse_duplicate_edges walker. Killing
    // the short edges up front (and cleaning the faceless rubble they leave)
    // means the merge sees no direct-edge pairs and cannot make self-loops.
    // The existing bt_mesh_simplify pipeline uses the same safety pattern via
    // bt_edge_kill_parallels around each edge collapse.
    bt_mesh_kill_short_edges(working_surface, VERTEX_MERGE_DISTANCE);
    bt_mesh_cleanup_faceless_references(working_surface);

    // GLTF (and most asset-pipeline outputs) duplicate per-corner attributes
    // at smoothing/UV splits, so a "unit cube" arrives with 24 positions
    // instead of 8 and every face-pair edge ends up as a boundary edge —
    // hole closing then chokes on figure-8 pinch vertices and we yield zero
    // tets. Merge coincident vertices; this is a no-op on a topology that
    // already has unique positions.
    bt_merge_verts_by_distance(working_surface, VERTEX_MERGE_DISTANCE);

    // The merge only re-points edge endpoints; it doesn't fuse the now-parallel
    // edges that share both endpoints. Without the fuse pass, each cube corner
    // still has duplicate edges in its disk cycle and the boundary walker
    // still sees it as a pinch. Fuse must follow merge.
    bt_mesh_fuse_duplicate_edges(working_surface);

    // Compact removes any dangling unallocated vertex / edge slots so vertex
    // IDs are dense (required so the flat positions array index matches the
    // BinaryTopology vertex ID after extraction).
    bt_mesh_compact(working_surface);

    bt_mesh_close_boundary_holes(working_surface);

    const surface_vertex_count = working_surface.vertices.size;

    // Interior Steiner points are opt-in. Caller chooses cells_per_dim — a
    // sensible default for FEM-style usage is roughly cbrt(surface_vert_count)
    // (interior point spacing matches surface vertex spacing).
    const interior_positions = interior_steiner_cells_per_dim > 0
        ? bt_mesh_sample_interior_grid_points(working_surface, interior_steiner_cells_per_dim)
        : new Float32Array(0);
    const interior_count = interior_positions.length / 3;

    const total_count = surface_vertex_count + interior_count;
    const positions = new Float32Array(total_count * 3);

    const coord = [0, 0, 0];
    for (let v = 0; v < surface_vertex_count; v++) {
        working_surface.vertex_read_coordinate(coord, 0, v);
        positions[v * 3] = coord[0];
        positions[v * 3 + 1] = coord[1];
        positions[v * 3 + 2] = coord[2];
    }
    positions.set(interior_positions, surface_vertex_count * 3);

    const ok = compute_delaunay_tetrahedral_mesh(tet_mesh, positions, total_count);
    if (!ok) {
        return positions;
    }

    tetrahedral_mesh_carve_outside_surface(tet_mesh, positions, working_surface);

    return positions;
}
