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";

const scratch_edges = new Uint32Array(1024);

/**
 * Patch mesh structure to make all loops/edges/faces point to this new replacement vertex instead of the victim.
 * This effectively removes any usage of the victim from the mesh enabling us to safely remove it.
 *
 * Note: both victim and replacement vertices must be part of the mesh.
 * Note: does not actually remove the victim vertex from the mesh, only links that point to it that will now point to `replacement` instead.
 * Note: may create degenerate edges if victim and replacement are a part of the same edge originally.
 * @param {BinaryTopology} mesh
 * @param {number} victim
 * @param {number} replacement
 */
export function bt_vertex_replace(
    mesh,
    victim,
    replacement
) {

    if (victim === replacement) {
        // special case
        return;
    }

    // 1. Traverse and collect all edges attached to the victim vertex.
    // We collect them into an array first because we will be mutating their disk cycles
    // during the loop, which would break an inline traversal.
    let edges_to_move_count = 0;
    let curr_e = mesh.vertex_read_edge(victim);

    if (curr_e !== NULL_POINTER) {
        let start_e = curr_e;
        do {

            assert.lessThan(edges_to_move_count, scratch_edges.length, "edges_to_move_count");

            scratch_edges[edges_to_move_count++] = curr_e;

            // Move to the next edge in the victim's disk cycle
            if (mesh.edge_read_vertex1(curr_e) === victim) {
                curr_e = mesh.edge_read_v1_disk_next(curr_e);
            } else {
                curr_e = mesh.edge_read_v2_disk_next(curr_e);
            }
        } while (curr_e !== start_e && curr_e !== NULL_POINTER);
    }

    // 2. Unlink the victim vertex by clearing its edge pointer.
    mesh.vertex_write_edge(victim, NULL_POINTER);

    // 3. Process each edge: update loops, update vertices, and insert into replacement's disk cycle.
    for (let i = 0; i < edges_to_move_count; i++) {
        const e = scratch_edges[i];

        // --- Update Loops ---
        // Loops are corner representations; we must check the radial cycle of loops around this edge
        let curr_l = mesh.edge_read_loop(e);
        if (curr_l !== NULL_POINTER) {
            let start_l = curr_l;
            do {
                if (mesh.loop_read_vertex(curr_l) === victim) {
                    mesh.loop_write_vertex(curr_l, replacement);
                }
                curr_l = mesh.loop_read_radial_next(curr_l);
            } while (curr_l !== start_l && curr_l !== NULL_POINTER);
        }

        // --- Update Edge Vertices ---
        // Three cases. Per the disk-cycle convention (see BinaryTopology.js
        // __edge_pool comment), every edge occupies exactly one slot in each of
        // its endpoints' cycles -- for self-loops that means one slot total, in
        // the v1 slot of the unique endpoint's cycle.
        //
        //   (a) Normal: e was attached to victim only. Move the victim endpoint
        //       onto replacement and splice e into replacement's cycle.
        //   (b) Direct parallel: e connects victim and replacement. Becomes a
        //       self-loop on replacement. e is already in replacement's disk on
        //       the OTHER side; remove it there, update the vertex, then re-add
        //       on the v1 side per convention.
        //   (c) Self-loop on victim: e was a self-loop on victim. Becomes a
        //       self-loop on replacement. Update BOTH vertex fields (the legacy
        //       single-write path would have left vertex2 = victim, dangling
        //       after bt_vert_kill(victim) runs upstream), then add to
        //       replacement's cycle from scratch.
        const e_v1 = mesh.edge_read_vertex1(e);
        const e_v2 = mesh.edge_read_vertex2(e);
        const is_v1 = e_v1 === victim;
        const other_endpoint = is_v1 ? e_v2 : e_v1;

        if (other_endpoint === victim) {
            // case (c): self-loop on victim -> self-loop on replacement
            mesh.edge_write_vertex1(e, replacement);
            mesh.edge_write_vertex2(e, replacement);
            bt_disk_edge_add(mesh, e, replacement);
            continue;
        }

        if (other_endpoint === replacement) {
            // case (b): direct parallel becoming a self-loop on replacement.
            // e is currently in replacement's disk on the side OPPOSITE the
            // victim side; remove it there before rewriting the vertex slot.
            bt_disk_edge_remove(mesh, e, replacement);

            if (is_v1) {
                mesh.edge_write_vertex1(e, replacement);
            } else {
                mesh.edge_write_vertex2(e, replacement);
            }

            // Now e is a self-loop; bt_disk_edge_add will write the v1 slot
            // per the convention.
            bt_disk_edge_add(mesh, e, replacement);
            continue;
        }

        // case (a): normal path. Move the victim endpoint onto replacement and
        // splice e into replacement's cycle.
        if (is_v1) {
            mesh.edge_write_vertex1(e, replacement);
        } else {
            mesh.edge_write_vertex2(e, replacement);
        }

        // --- Insert Edge into Replacement's Disk Cycle ---
        const base_edge = mesh.vertex_read_edge(replacement);

        if (base_edge === NULL_POINTER) {
            // This is the first edge attached to the replacement vertex
            mesh.vertex_write_edge(replacement, e);
            if (is_v1) {
                mesh.edge_write_v1_disk_next(e, e);
                mesh.edge_write_v1_disk_prev(e, e);
            } else {
                mesh.edge_write_v2_disk_next(e, e);
                mesh.edge_write_v2_disk_prev(e, e);
            }
        } else {
            // Splice this edge into the existing disk cycle before base_edge
            const base_is_v1 = mesh.edge_read_vertex1(base_edge) === replacement;
            const base_prev = base_is_v1
                ? mesh.edge_read_v1_disk_prev(base_edge)
                : mesh.edge_read_v2_disk_prev(base_edge);

            const prev_is_v1 = mesh.edge_read_vertex1(base_prev) === replacement;

            // 1. Link base_prev -> e
            if (prev_is_v1) {
                mesh.edge_write_v1_disk_next(base_prev, e);
            } else {
                mesh.edge_write_v2_disk_next(base_prev, e);
            }

            // 2. Link e -> base_prev & e -> base_edge
            if (is_v1) {
                mesh.edge_write_v1_disk_prev(e, base_prev);
                mesh.edge_write_v1_disk_next(e, base_edge);
            } else {
                mesh.edge_write_v2_disk_prev(e, base_prev);
                mesh.edge_write_v2_disk_next(e, base_edge);
            }

            // 3. Link base_edge -> e
            if (base_is_v1) {
                mesh.edge_write_v1_disk_prev(base_edge, e);
            } else {
                mesh.edge_write_v2_disk_prev(base_edge, e);
            }
        }
    }
}