/**
* Binary max-heap priority queue.
* Higher priority items are dequeued first.
* O(log n) enqueue/dequeue, O(n) clearLowPrioEvents.
* Maintains a backward-compatible `.values` getter for queue length checks.
*/
class PriorityQueue {
constructor() {
/** @type {Array.<{node: *, priority: number}>} */
this._heap = [];
}
/** Backward-compatible accessor — existing code checks .values.length */
get values() {
return this._heap;
}
enqueue(node, priority) {
this._heap.push({ node, priority });
this._bubbleUp(this._heap.length - 1);
}
dequeue() {
const heap = this._heap;
if (heap.length === 0) return undefined;
const top = heap[0];
const last = heap.pop();
if (heap.length > 0) {
heap[0] = last;
this._sinkDown(0);
}
return top;
}
clearLowPrioEvents(lowerThan = 0) {
// Filter then rebuild heap in O(n)
this._heap = this._heap.filter((item) => item.priority >= lowerThan);
this._buildHeap();
}
size() {
return this._heap.length;
}
// --- Heap internals ---
_bubbleUp(idx) {
const heap = this._heap;
while (idx > 0) {
const parent = (idx - 1) >> 1;
if (heap[idx].priority > heap[parent].priority) {
[heap[idx], heap[parent]] = [heap[parent], heap[idx]];
idx = parent;
} else {
break;
}
}
}
_sinkDown(idx) {
const heap = this._heap;
const len = heap.length;
for (;;) { // eslint-disable-line no-constant-condition
let largest = idx;
const left = 2 * idx + 1;
const right = 2 * idx + 2;
if (left < len && heap[left].priority > heap[largest].priority) largest = left;
if (right < len && heap[right].priority > heap[largest].priority) largest = right;
if (largest !== idx) {
[heap[idx], heap[largest]] = [heap[largest], heap[idx]];
idx = largest;
} else {
break;
}
}
}
_buildHeap() {
for (let i = (this._heap.length >> 1) - 1; i >= 0; i--) {
this._sinkDown(i);
}
}
}