import { assert } from "../../../../../../assert.js";
import { v3_dot } from "../../../../../vec3/v3_dot.js";
import { v3_compute_triangle_normal } from "../../../../triangle/v3_compute_triangle_normal.js";
import { Quadric3 } from "../../../simplify/quadratic/Quadric3.js";
import { NULL_POINTER } from "../BinaryTopology.js";

const scratch_coord_a = new Float32Array(3);
const scratch_coord_b = new Float32Array(3);
const scratch_coord_c = new Float32Array(3);
const scratch_normal = new Float32Array(3);

/**
 * Compute a per-vertex quadric error metric from every allocated triangular face.
 *
 * For each triangular face the supporting plane (n, d) is derived from its loop
 * vertices; the corresponding quadric is then accumulated onto each of those
 * three vertices' Quadric3. Non-triangular faces are skipped.
 *
 * @param {BinaryTopology} mesh
 * @returns {Quadric3[]} Array indexed by vertex ID. Entries for unallocated
 *     vertex slots are `undefined`.
 */
export function bt_mesh_compute_vertex_quadratics(mesh) {
    assert.defined(mesh, 'mesh');
    assert.notNull(mesh, 'mesh');

    const vertex_pool = mesh.vertices;
    const vertex_count = vertex_pool.size;

    const quadratics = new Array(vertex_count);

    for (let v = 0; v < vertex_count; v++) {
        if (vertex_pool.is_allocated(v)) {
            quadratics[v] = new Quadric3();
        }
    }

    const face_pool = mesh.faces;
    const face_count = face_pool.size;

    const q = new Quadric3();

    for (let f = 0; f < face_count; f++) {
        if (!face_pool.is_allocated(f)) {
            continue;
        }

        const la = mesh.face_read_loop(f);

        if (la === NULL_POINTER) {
            continue;
        }

        const lb = mesh.loop_read_next(la);
        const lc = mesh.loop_read_next(lb);

        // Skip non-triangles
        if (mesh.loop_read_next(lc) !== la) {
            continue;
        }

        const va = mesh.loop_read_vertex(la);
        const vb = mesh.loop_read_vertex(lb);
        const vc = mesh.loop_read_vertex(lc);

        mesh.vertex_read_coordinate(scratch_coord_a, 0, va);
        mesh.vertex_read_coordinate(scratch_coord_b, 0, vb);
        mesh.vertex_read_coordinate(scratch_coord_c, 0, vc);

        v3_compute_triangle_normal(
            scratch_normal, 0,
            scratch_coord_a[0], scratch_coord_a[1], scratch_coord_a[2],
            scratch_coord_b[0], scratch_coord_b[1], scratch_coord_b[2],
            scratch_coord_c[0], scratch_coord_c[1], scratch_coord_c[2]
        );

        const nx = scratch_normal[0];
        const ny = scratch_normal[1];
        const nz = scratch_normal[2];

        // Plane offset: d = -(n . v_a)
        const d = -v3_dot(
            nx, ny, nz,
            scratch_coord_a[0], scratch_coord_a[1], scratch_coord_a[2]
        );

        q.setFromVector4(nx, ny, nz, d);

        quadratics[va].add(q);
        quadratics[vb].add(q);
        quadratics[vc].add(q);
    }

    return quadratics;
}
