import { tap, expect } from '@git.zone/tstest/tapbundle';
import { BinaryHeap } from '../../../ts/core/utils/binary-heap.js';

interface TestItem {
  id: string;
  priority: number;
  value: string;
}

tap.test('should create empty heap', async () => {
  const heap = new BinaryHeap<number>((a, b) => a - b);
  
  expect(heap.size).toEqual(0);
  expect(heap.isEmpty()).toBeTrue();
  expect(heap.peek()).toBeUndefined();
});

tap.test('should insert and extract in correct order', async () => {
  const heap = new BinaryHeap<number>((a, b) => a - b);
  
  heap.insert(5);
  heap.insert(3);
  heap.insert(7);
  heap.insert(1);
  heap.insert(9);
  heap.insert(4);
  
  expect(heap.size).toEqual(6);
  
  // Extract in ascending order
  expect(heap.extract()).toEqual(1);
  expect(heap.extract()).toEqual(3);
  expect(heap.extract()).toEqual(4);
  expect(heap.extract()).toEqual(5);
  expect(heap.extract()).toEqual(7);
  expect(heap.extract()).toEqual(9);
  expect(heap.extract()).toBeUndefined();
});

tap.test('should work with custom objects and comparator', async () => {
  const heap = new BinaryHeap<TestItem>(
    (a, b) => a.priority - b.priority,
    (item) => item.id
  );
  
  heap.insert({ id: 'a', priority: 5, value: 'five' });
  heap.insert({ id: 'b', priority: 2, value: 'two' });
  heap.insert({ id: 'c', priority: 8, value: 'eight' });
  heap.insert({ id: 'd', priority: 1, value: 'one' });
  
  const first = heap.extract();
  expect(first?.priority).toEqual(1);
  expect(first?.value).toEqual('one');
  
  const second = heap.extract();
  expect(second?.priority).toEqual(2);
  expect(second?.value).toEqual('two');
});

tap.test('should support reverse order (max heap)', async () => {
  const heap = new BinaryHeap<number>((a, b) => b - a);
  
  heap.insert(5);
  heap.insert(3);
  heap.insert(7);
  heap.insert(1);
  heap.insert(9);
  
  // Extract in descending order
  expect(heap.extract()).toEqual(9);
  expect(heap.extract()).toEqual(7);
  expect(heap.extract()).toEqual(5);
});

tap.test('should extract by predicate', async () => {
  const heap = new BinaryHeap<TestItem>((a, b) => a.priority - b.priority);
  
  heap.insert({ id: 'a', priority: 5, value: 'five' });
  heap.insert({ id: 'b', priority: 2, value: 'two' });
  heap.insert({ id: 'c', priority: 8, value: 'eight' });
  
  const extracted = heap.extractIf(item => item.id === 'b');
  expect(extracted?.id).toEqual('b');
  expect(heap.size).toEqual(2);
  
  // Should not find it again
  const notFound = heap.extractIf(item => item.id === 'b');
  expect(notFound).toBeUndefined();
});

tap.test('should extract by key', async () => {
  const heap = new BinaryHeap<TestItem>(
    (a, b) => a.priority - b.priority,
    (item) => item.id
  );
  
  heap.insert({ id: 'a', priority: 5, value: 'five' });
  heap.insert({ id: 'b', priority: 2, value: 'two' });
  heap.insert({ id: 'c', priority: 8, value: 'eight' });
  
  expect(heap.hasKey('b')).toBeTrue();
  
  const extracted = heap.extractByKey('b');
  expect(extracted?.id).toEqual('b');
  expect(heap.size).toEqual(2);
  expect(heap.hasKey('b')).toBeFalse();
  
  // Should not find it again
  const notFound = heap.extractByKey('b');
  expect(notFound).toBeUndefined();
});

tap.test('should throw when using key operations without extractKey', async () => {
  const heap = new BinaryHeap<TestItem>((a, b) => a.priority - b.priority);
  
  heap.insert({ id: 'a', priority: 5, value: 'five' });
  
  let error: Error | null = null;
  try {
    heap.extractByKey('a');
  } catch (e: any) {
    error = e;
  }
  
  expect(error).not.toBeNull();
  expect(error?.message).toContain('extractKey function must be provided');
});

tap.test('should handle duplicates correctly', async () => {
  const heap = new BinaryHeap<number>((a, b) => a - b);
  
  heap.insert(5);
  heap.insert(5);
  heap.insert(5);
  heap.insert(3);
  heap.insert(7);
  
  expect(heap.size).toEqual(5);
  expect(heap.extract()).toEqual(3);
  expect(heap.extract()).toEqual(5);
  expect(heap.extract()).toEqual(5);
  expect(heap.extract()).toEqual(5);
  expect(heap.extract()).toEqual(7);
});

tap.test('should convert to array without modifying heap', async () => {
  const heap = new BinaryHeap<number>((a, b) => a - b);
  
  heap.insert(5);
  heap.insert(3);
  heap.insert(7);
  
  const array = heap.toArray();
  expect(array).toContain(3);
  expect(array).toContain(5);
  expect(array).toContain(7);
  expect(array.length).toEqual(3);
  
  // Heap should still be intact
  expect(heap.size).toEqual(3);
  expect(heap.extract()).toEqual(3);
});

tap.test('should clear the heap', async () => {
  const heap = new BinaryHeap<TestItem>(
    (a, b) => a.priority - b.priority,
    (item) => item.id
  );
  
  heap.insert({ id: 'a', priority: 5, value: 'five' });
  heap.insert({ id: 'b', priority: 2, value: 'two' });
  
  expect(heap.size).toEqual(2);
  expect(heap.hasKey('a')).toBeTrue();
  
  heap.clear();
  
  expect(heap.size).toEqual(0);
  expect(heap.isEmpty()).toBeTrue();
  expect(heap.hasKey('a')).toBeFalse();
});

tap.test('should handle complex extraction patterns', async () => {
  const heap = new BinaryHeap<number>((a, b) => a - b);
  
  // Insert numbers 1-10 in random order
  [8, 3, 5, 9, 1, 7, 4, 10, 2, 6].forEach(n => heap.insert(n));
  
  // Extract some in order
  expect(heap.extract()).toEqual(1);
  expect(heap.extract()).toEqual(2);
  
  // Insert more
  heap.insert(0);
  heap.insert(1.5);
  
  // Continue extracting
  expect(heap.extract()).toEqual(0);
  expect(heap.extract()).toEqual(1.5);
  expect(heap.extract()).toEqual(3);
  
  // Verify remaining size (10 - 2 extracted + 2 inserted - 3 extracted = 7)
  expect(heap.size).toEqual(7);
});

tap.start();