import lowerBound from './lower-bound';
import { QueueAddOptions } from './options';
import { Queue, RunFunction } from './queue';

export interface PriorityQueueOptions extends QueueAddOptions {
  priority?: number;
}

export default class PriorityQueue
  implements Queue<RunFunction, PriorityQueueOptions>
{
  readonly #queue: Array<PriorityQueueOptions & { run: RunFunction }> = [];

  enqueue(run: RunFunction, options?: Partial<PriorityQueueOptions>): void {
    options = {
      priority: 0,
      ...options,
    };

    const element = {
      priority: options.priority,
      run,
    };

    if (
      this.size &&
      this.#queue[this.size - 1]!.priority! >= options.priority!
    ) {
      this.#queue.push(element);
      return;
    }

    const index = lowerBound(
      this.#queue,
      element,
      (a: Readonly<PriorityQueueOptions>, b: Readonly<PriorityQueueOptions>) =>
        b.priority! - a.priority!,
    );
    this.#queue.splice(index, 0, element);
  }

  dequeue(): RunFunction | undefined {
    const item = this.#queue.shift();
    return item?.run;
  }

  filter(options: Readonly<Partial<PriorityQueueOptions>>): RunFunction[] {
    return this.#queue
      .filter(
        (element: Readonly<PriorityQueueOptions>) =>
          element.priority === options.priority,
      )
      .map((element: Readonly<{ run: RunFunction }>) => element.run);
  }

  get size(): number {
    return this.#queue.length;
  }
}
