import { aabb2_distance_sqr_to_point } from "../aabb/aabb2_distance_sqr_to_point.js";
import { aabb2_intersects_ray } from "../aabb/aabb2_intersects_ray.js";

/**
 *
 * @type {QuadTreeNode[]}
 */
const node_stack = [];

/**
 * @template T
 * @param {QuadTreeNode<T>} tree
 * @param {number} origin_x
 * @param {number} origin_y
 * @param {number} direction_x
 * @param {number} direction_y
 * @param {number} max_distance
 * @param {function(data:T, origin_x:number, origin_y:number, direction_x:number, direction_y:number, max_distance2:number):boolean} [predicate]
 * @param {*} [predicateContext]
 * @returns {QuadTreeDatum<T>|undefined}
 */
export function qt_query_data_raycast(
    tree,
    origin_x, origin_y,
    direction_x, direction_y,
    max_distance,
    predicate, predicateContext
) {

    let stack_pointer = 0;

    const max_distance2 = max_distance * max_distance;

    node_stack[stack_pointer] = tree;
    stack_pointer++;

    /**
     *
     * @type {QuadTreeDatum<T>|undefined}
     */
    let best_match = undefined;
    let best_match_distance = max_distance2;

    while (stack_pointer > 0) {

        --stack_pointer;
        const node = node_stack[stack_pointer];

        const hit_node = aabb2_intersects_ray(node.x0, node.y0, node.x1, node.y1, origin_x, origin_y, direction_x, direction_y);

        if (!hit_node) {
            continue;
        }

        const d2 = aabb2_distance_sqr_to_point(node.x0, node.y0, node.x1, node.y1, origin_x, origin_y);

        if (d2 > best_match_distance) {
            // too far, not a match
            continue;
        }

        const data = node.data;
        const data_count = data.length;

        for (let i = 0; i < data_count; i++) {
            const datum = data[i];

            const hit_datum = aabb2_intersects_ray(datum.x0, datum.y0, datum.x1, datum.y1, origin_x, origin_y, direction_x, direction_y);

            if (!hit_datum) {
                continue;
            }

            const d_to_datum = aabb2_distance_sqr_to_point(datum.x0, datum.y0, datum.x1, datum.y1, origin_x, origin_y);

            if (d_to_datum < best_match_distance && predicate.call(
                predicateContext,
                datum.data,
                origin_x, origin_y,
                direction_x, direction_y,
                best_match_distance
            )) {
                best_match = datum;
                best_match_distance = d_to_datum;
            }
        }

        if (node.isSplit()) {
            node_stack[stack_pointer++] = node.topLeft;
            node_stack[stack_pointer++] = node.bottomLeft;
            node_stack[stack_pointer++] = node.topRight;
            node_stack[stack_pointer++] = node.bottomRight;
        }
    }

    return best_match;
}