import { assert } from "../../../../../../../assert.js";
import { NULL_POINTER } from "../../BinaryTopology.js";
import { bt_disk_edge_add } from "../bt_disk_edge_add.js";
import { bt_disk_edge_remove } from "../bt_disk_edge_remove.js";
import { bt_radial_loop_add } from "../bt_radial_loop_add.js";
import { bt_radial_loop_remove } from "../bt_radial_loop_remove.js";
import { bt_edge_create } from "./bt_edge_create.js";


const f_normal = new Float32Array(3);

const c1 = new Float32Array(3);
const c2 = new Float32Array(3);

const n1 = new Float32Array(3);
const n2 = new Float32Array(3);

const c_new = new Float32Array(3);
const n_new = new Float32Array(3);

/**
 * Splits an edge at parameter t and triangulates adjacent faces.
 *
 * NOTE: Assumes that the input mesh is triangulated.
 * NOTE: Does not check for created self-intersections or degenerate edges/faces. Make sure that if you pass t <= 0 or t >= 1 - you understand the implications.
 *
 * @param {BinaryTopology} mesh
 * @param {number} edge_id The edge to split
 * @param {number} t Interpolation parameter (0.0 to 1.0)
 * @returns {number} The newly created vertex ID
 */
export function bt_edge_split(
    mesh,
    edge_id,
    t
) {
    assert.isNonNegativeInteger(edge_id, 'edge_id');
    assert.isNumber(t, 't');
    assert.greaterThanOrEqual(t, 0, 't');
    assert.lessThanOrEqual(t, 1, 't');

    assert.defined(mesh, 'mesh');
    assert.notNull(mesh, 'mesh');

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

    // ----------------------------------------------------------------
    // 1. Interpolate and Create New Vertex
    // ----------------------------------------------------------------

    mesh.vertex_read_coordinate(c1, 0, v1);
    mesh.vertex_read_coordinate(c2, 0, v2);

    c_new[0] = c1[0] + (c2[0] - c1[0]) * t;
    c_new[1] = c1[1] + (c2[1] - c1[1]) * t;
    c_new[2] = c1[2] + (c2[2] - c1[2]) * t;

    mesh.vertex_read_normal(n1, 0, v1);
    mesh.vertex_read_normal(n2, 0, v2);

    n_new[0] = n1[0] + (n2[0] - n1[0]) * t;
    n_new[1] = n1[1] + (n2[1] - n1[1]) * t;
    n_new[2] = n1[2] + (n2[2] - n1[2]) * t;

    const len = Math.sqrt(n_new[0] ** 2 + n_new[1] ** 2 + n_new[2] ** 2);
    if (len > 0) {
        n_new[0] /= len;
        n_new[1] /= len;
        n_new[2] /= len;
    }

    const v_new = mesh.vertices.allocate();
    mesh.vertex_write_coordinate(v_new, c_new, 0);
    mesh.vertex_write_normal(v_new, n_new, 0);
    mesh.vertex_write_edge(v_new, NULL_POINTER); // Initialize disk cycle

    // ----------------------------------------------------------------
    // 2. Gather Connected Faces (Radial Cycle)
    // ----------------------------------------------------------------
    const loops_to_split = [];
    let curr_loop = mesh.edge_read_loop(edge_id);
    if (curr_loop !== NULL_POINTER) {
        let start_loop = curr_loop;
        do {
            loops_to_split.push(curr_loop);
            curr_loop = mesh.loop_read_radial_next(curr_loop);
        } while (curr_loop !== start_loop && curr_loop !== NULL_POINTER);
    }

    // ----------------------------------------------------------------
    // 3. Split the Edge Topologically
    // ----------------------------------------------------------------
    // Shrink original edge to (v1, v_new)
    bt_disk_edge_remove(mesh, edge_id, v2);
    mesh.edge_write_vertex2(edge_id, v_new);
    bt_disk_edge_add(mesh, edge_id, v_new);

    // Create new edge for (v_new, v2)
    // Note: bt_edge_create handles the disk cycle attachments for us!
    const e_new = bt_edge_create(mesh, v_new, v2);

    // ----------------------------------------------------------------
    // 4. Split Adjacent Faces
    // ----------------------------------------------------------------


    for (let i = 0; i < loops_to_split.length; i++) {
        const l_start = loops_to_split[i];
        const l_end = mesh.loop_read_next(l_start);
        const l_opp = mesh.loop_read_next(l_end);

        const v_start = mesh.loop_read_vertex(l_start);
        const v_end = mesh.loop_read_vertex(l_end);
        const v_opp = mesh.loop_read_vertex(l_opp);

        const f = mesh.loop_read_face(l_start);
        mesh.face_read_normal(f_normal, 0, f);

        const e_end_opp = mesh.loop_read_edge(l_end);

        // Determine which physical edge corresponds to this loop's direction
        const is_start_v1 = (v_start === v1);
        const edge_start_new = is_start_v1 ? edge_id : e_new;
        const edge_new_end = is_start_v1 ? e_new : edge_id;

        // Create the cross edge dividing the triangle
        const e_cross = bt_edge_create(mesh, v_new, v_opp);

        // --- FACE 1: Re-use existing face (v_start, v_new, v_opp) ---

        // Update l_start (ensure it points to the correct half of the split edge)
        if (mesh.loop_read_edge(l_start) !== edge_start_new) {
            bt_radial_loop_remove(mesh, l_start, mesh.loop_read_edge(l_start));
            bt_radial_loop_add(mesh, l_start, edge_start_new);
        }

        // Update l_end (now acts as the loop for v_new -> v_opp)
        bt_radial_loop_remove(mesh, l_end, e_end_opp);
        mesh.loop_write_vertex(l_end, v_new);
        bt_radial_loop_add(mesh, l_end, e_cross);

        // l_opp remains unchanged (v_opp -> v_start)

        // --- FACE 2: Create new face (v_new, v_end, v_opp) ---
        const f2 = mesh.faces.allocate();
        mesh.face_write_normal(f2, f_normal, 0);

        const nl1 = mesh.loop_create();
        const nl2 = mesh.loop_create();
        const nl3 = mesh.loop_create();

        // nl1: v_new -> v_end
        mesh.loop_write_vertex(nl1, v_new);
        mesh.loop_write_face(nl1, f2);
        bt_radial_loop_add(mesh, nl1, edge_new_end);

        // nl2: v_end -> v_opp
        mesh.loop_write_vertex(nl2, v_end);
        mesh.loop_write_face(nl2, f2);
        bt_radial_loop_add(mesh, nl2, e_end_opp);

        // nl3: v_opp -> v_new
        mesh.loop_write_vertex(nl3, v_opp);
        mesh.loop_write_face(nl3, f2);
        bt_radial_loop_add(mesh, nl3, e_cross);

        // Wire up loops for f2
        mesh.loop_write_next(nl1, nl2);
        mesh.loop_write_prev(nl1, nl3);
        mesh.loop_write_next(nl2, nl3);
        mesh.loop_write_prev(nl2, nl1);
        mesh.loop_write_next(nl3, nl1);
        mesh.loop_write_prev(nl3, nl2);

        mesh.face_write_loop(f2, nl1);
    }

    return v_new;
}