import { assert } from "../../../../../core/assert.js";
import { bvh32_query_user_data_ray } from "../../../../../core/bvh2/binary/2/bvh32_query_user_data_ray.js";
import {
    typed_array_value_denormalize
} from "../../../../../core/collection/array/typed/typed_array_value_denormalize.js";
import { SurfacePoint3 } from "../../../../../core/geom/3d/SurfacePoint3.js";
import { computeTriangleRayIntersection } from "../../../../../core/geom/3d/triangle/computeTriangleRayIntersection.js";

const scratch_array = []
const scratch_hit = new SurfacePoint3()

/**
 *
 * @param {SurfacePoint3} result
 * @param {BinaryUint32BVH} bvh
 * @param {number[]|ArrayLike<number>} vertices
 * @param {number} vertex_offset Unless you're using an interleaved buffer of some kind, this will be 0
 * @param {number} vertex_stride Unless you're using an interleaved buffer, this should be 3
 * @param {boolean} vertex_data_normalized do we need to denormalize vertex data?
 * @param {number[]|ArrayLike<number>|undefined} indices if this is set to undefined - implicit indexing will be used
 * @param {number} origin_x
 * @param {number} origin_y
 * @param {number} origin_z
 * @param {number} direction_x
 * @param {number} direction_y
 * @param {number} direction_z
 * @param {number} max_distance How far can the ray travel from origin? tMax
 * @returns {boolean}
 */
export function bvh32_geometry_raycast(
    result,
    bvh,
    vertices, vertex_offset, vertex_stride,
    vertex_data_normalized,
    indices,
    origin_x, origin_y, origin_z,
    direction_x, direction_y, direction_z,
    max_distance
) {

    assert.isBoolean(vertex_data_normalized, 'vertex_data_normalized');
    assert.isNumber(max_distance, 'max_distance');

    let hit_found = false;

    const hit_count = bvh32_query_user_data_ray(
        scratch_array, 0,
        bvh,
        origin_x, origin_y, origin_z,
        direction_x, direction_y, direction_z,
        max_distance,
    );

    let best_distance_sqr = max_distance * max_distance;

    let a, b, c;

    for (let i = 0; i < hit_count; i++) {
        const triangle_index = scratch_array[i];

        const index3 = triangle_index * 3;

        if (indices !== undefined) {
            assert.lessThan(index3 + 2, indices.length, 'triangle index overflow, possibly geometry changed but tree was not rebuilt?');

            a = indices[index3];
            b = indices[index3 + 1];
            c = indices[index3 + 2];
        } else {
            // implicit indices
            a = index3;
            b = index3 + 1;
            c = index3 + 2;
        }

        const a_address = a * vertex_stride + vertex_offset;
        const b_address = b * vertex_stride + vertex_offset;
        const c_address = c * vertex_stride + vertex_offset;

        assert.lessThan(a_address + 2, vertices.length, 'a-vertex overflow');
        assert.lessThan(b_address + 2, vertices.length, 'b-vertex overflow');
        assert.lessThan(c_address + 2, vertices.length, 'c-vertex overflow');

        let ax = vertices[a_address];
        let ay = vertices[a_address + 1];
        let az = vertices[a_address + 2];

        let bx = vertices[b_address];
        let by = vertices[b_address + 1];
        let bz = vertices[b_address + 2];

        let cx = vertices[c_address];
        let cy = vertices[c_address + 1];
        let cz = vertices[c_address + 2];

        // denormalize if necessary
        if (vertex_data_normalized) {
            ax = typed_array_value_denormalize(ax, vertices);
            ay = typed_array_value_denormalize(ay, vertices);
            az = typed_array_value_denormalize(az, vertices);

            bx = typed_array_value_denormalize(bx, vertices);
            by = typed_array_value_denormalize(by, vertices);
            bz = typed_array_value_denormalize(bz, vertices);

            cx = typed_array_value_denormalize(cx, vertices);
            cy = typed_array_value_denormalize(cy, vertices);
            cz = typed_array_value_denormalize(cz, vertices);
        }

        const triangle_hit_found = computeTriangleRayIntersection(
            scratch_hit,
            origin_x, origin_y, origin_z,
            direction_x, direction_y, direction_z,
            ax, ay, az,
            bx, by, bz,
            cx, cy, cz
        )

        if (!triangle_hit_found) {
            continue;
        }

        hit_found = true;

        const distance_sqr = scratch_hit.position._distanceSqrTo(origin_x, origin_y, origin_z);

        if (distance_sqr < best_distance_sqr) {
            best_distance_sqr = distance_sqr;
            result.copy(scratch_hit);
        }
    }

    return hit_found;
}