import { HashMap, LinkedHashMap } from '../../../../src';
import { getRandomInt, getRandomIntArray } from '../../../utils';

describe('HashMap', () => {
  let hashMap: HashMap<string, number>;

  beforeEach(() => {
    hashMap = new HashMap<string, number>();
  });

  it('should initialize correctly', () => {
    expect(hashMap.size).toBe(0);
    expect(hashMap.isEmpty()).toBe(true);
  });

  it('should set and get values', () => {
    hashMap.set('one', 1);
    hashMap.set('two', 2);
    hashMap.set('three', 3);

    expect(hashMap.get('one')).toBe(1);
    expect(hashMap.get('two')).toBe(2);
    expect(hashMap.get('three')).toBe(3);
  });

  it('should handle key collisions', () => {
    // Force a collision by setting two different keys to the same bucket
    hashMap.set('key1', 1);
    hashMap.set('key2', 2);

    expect(hashMap.get('key1')).toBe(1);
    expect(hashMap.get('key2')).toBe(2);
  });

  it('should delete values', () => {
    hashMap.set('one', 1);
    hashMap.set('two', 2);

    hashMap.delete('one');
    expect(hashMap.get('one')).toBeUndefined();
    expect(hashMap.size).toBe(1);
  });

  it('should clear the HashMap', () => {
    hashMap.set('one', 1);
    hashMap.set('two', 2);

    hashMap.clear();
    expect(hashMap.size).toBe(0);
    expect(hashMap.isEmpty()).toBe(true);
  });

  it('should iterate over entries', () => {
    hashMap.set('one', 1);
    hashMap.set('two', 2);
    hashMap.set('three', 3);
  });

  it('should resize the table when load factor is exceeded', () => {
    // Set a small initial capacity for testing resizing
    hashMap = new HashMap<string, number>();

    hashMap.set('one', 1);
    hashMap.set('two', 2);
    hashMap.set('three', 3);
    hashMap.set('four', 4); // This should trigger a resize

    // expect(hashMap.table.length).toBe(8);
    expect(hashMap.get('one')).toBe(1);
    expect(hashMap.get('two')).toBe(2);
    expect(hashMap.get('three')).toBe(3);
    expect(hashMap.get('four')).toBe(4);
  });

  it('should allow using a custom hash function', () => {
    hashMap = new HashMap<string, number>();

    hashMap.set('one', 1);
    hashMap.set('two', 2);

    expect(hashMap.get('one')).toBe(1);
    expect(hashMap.get('two')).toBe(2);
    // Since the custom hash function always returns 0, these keys will collide.
    // Make sure they are stored separately.
    // expect(hashMap.table[0].length).toBe(2);
  });

  it('should clone', () => {
    hashMap = new HashMap<string, number>();

    hashMap.set('one', 1);
    hashMap.set('two', 2);
    for (let i = 3; i <= 100; i++) {
      hashMap.set(i.toString(), i);
    }

    expect(hashMap.get('one')).toBe(1);
    expect(hashMap.get('two')).toBe(2);
    expect(hashMap.get('86')).toBe(86);
    expect(hashMap.size).toBe(100);
    hashMap.delete('two');
    expect(hashMap.size).toBe(99);

    const cloned = hashMap.clone();
    expect(cloned.get('one')).toBe(1);
    expect(cloned.get('two')).toBe(undefined);
    expect(cloned.get('86')).toBe(86);
    expect(cloned.size).toBe(99);
  });

  describe('HashMap Test2', () => {
    let hashMap: HashMap;

    beforeEach(() => {
      hashMap = new HashMap();
    });

    it('should create an empty map', () => {
      expect(hashMap.size).toBe(0);
    });

    it('should add a key-value pair', () => {
      hashMap.set('key1', 'value1');
      expect(hashMap.get('key1')).toBe('value1');
      expect(hashMap.hasValue('value1')).toBe(true);
      expect(hashMap.hasValue('value2')).toBe(false);
    });

    it('should handle object keys correctly', () => {
      const keyObj = { id: 1 };
      hashMap.set(keyObj, 'objectValue');
      expect(hashMap.has(keyObj)).toBe(true);
      expect(hashMap.get(keyObj)).toBe('objectValue');
    });

    it('Inheritability test', () => {
      class ExtendedHashMap<K, V> extends HashMap<K, V> {
        someOtherParam?: string;

        constructor(
          elements: Iterable<[K, V]> = [],
          options?: {
            hashFn?: (key: K) => string;
            someOtherParam: string;
          }
        ) {
          const { someOtherParam, ...restOptions } = options || {};
          super(elements, restOptions);
          this.someOtherParam = someOtherParam;
        }
      }

      const eHM = new ExtendedHashMap<string, number>([], {
        someOtherParam: 'someOtherParam'
      });
      eHM.set('one', 1);
      expect(eHM.get('one')).toBe(1);
    });

    it('should raw elements toEntry', () => {
      const rawCollection = [
        { id: 1, name: 'item 1' },
        { id: 2, name: 'item 2' }
      ];
      const hm = new HashMap<number, string, { id: number; name: string }>(rawCollection, {
        toEntryFn: rawElement => [rawElement.id, rawElement.name]
      });

      expect(hm.has(1)).toBe(true);
      expect(hm.get(2)).toBe('item 2');
      expect(hm.size).toBe(2);
    });

    it('should update the value for an existing key', () => {
      hashMap.set('key1', 'value1');
      hashMap.set('key1', 'newValue');
      expect(hashMap.get('key1')).toBe('newValue');
    });

    it('should return undefined for a non-existent key', () => {
      expect(hashMap.get('nonExistentKey')).toBeUndefined();
    });

    it('should remove a key-value pair', () => {
      hashMap.set('key1', 'value1');
      hashMap.delete('key1');
      expect(hashMap.get('key1')).toBeUndefined();
    });

    it('should clear the map', () => {
      hashMap.set('key1', 'value1');
      expect(hashMap.size).toBe(1);

      hashMap.clear();
      expect(hashMap.size).toBe(0);
    });

    it('should iterate over values', () => {
      hashMap.set('key1', 'value1');
      hashMap.set('key2', 'value2');
      const values = [];
      for (const value of hashMap) {
        values.push(value);
      }
      expect(values).toEqual([
        ['key1', 'value1'],
        ['key2', 'value2']
      ]);
    });

    function compareHashMaps(hashMap: HashMap<unknown, unknown>, stdMap: Map<unknown, unknown>) {
      expect(hashMap.size).toEqual(stdMap.size);
      stdMap.forEach((value, key) => {
        expect(hashMap.get(key)).toEqual(value);
      });
    }

    const stdMap: Map<unknown, unknown> = new Map();
    const arr: number[] = getRandomIntArray(1000, 1, 10000);

    it('delete test', () => {
      for (const item of arr) {
        stdMap.set(item, item);
        hashMap.set(item, item);
      }
      for (const item of arr) {
        if (Math.random() > 0.6) {
          expect(hashMap.delete(item)).toEqual(stdMap.delete(item));
        }
      }
      compareHashMaps(hashMap, stdMap);

      for (let i = 0; i < 1000; ++i) {
        const random = getRandomInt(0, 100);
        expect(hashMap.delete(random)).toEqual(stdMap.delete(random));
      }
      compareHashMaps(hashMap, stdMap);
    });
  });

  describe('HashMap for coordinate object keys', () => {
    const hashMap: HashMap<[number, number], number> = new HashMap();
    const codObjs: [number, number][] = [];
    for (let i = 0; i < 10; i++) {
      const codObj: [number, number] = [getRandomInt(-10000, 10000), i];
      codObjs.push(codObj);
    }
    it('set elements in hash map', () => {
      for (let i = 0; i < codObjs.length; i++) {
        const codObj = codObjs[i];
        hashMap.set(codObj, i);
      }
    });

    it('get elements in hash map', () => {
      for (let i = 0; i < codObjs.length; i++) {
        const codObj = codObjs[i];
        if (codObj) {
          expect(hashMap.get(codObj)).toBe(i);
        }
      }
    });

    it('should spread elements in hash map', () => {
      expect([...hashMap]).toEqual(codObjs.map(codObj => [codObj, codObj[1]]));
    });

    it('delete elements in hash map', () => {
      for (let i = 0; i < 10; i++) {
        if (i === 500) expect(hashMap.size).toBe(500);
        const codObj = codObjs[i];
        if (codObj) hashMap.delete(codObj);
      }
      expect(hashMap.size).toBe(0);
    });
  });

  describe('HashMap setMany, keys, values', () => {
    const hm: HashMap<number, number> = new HashMap<number, number>();

    beforeEach(() => {
      hm.clear();
      hm.setMany([
        [2, 2],
        [3, 3],
        [4, 4],
        [5, 5]
      ]);
      hm.setMany([
        [2, 2],
        [3, 3],
        [4, 4],
        [6, 6]
      ]);
    });

    it('keys', () => {
      expect([...hm.keys()]).toEqual([2, 3, 4, 5, 6]);
    });

    it('values', () => {
      expect([...hm.values()]).toEqual([2, 3, 4, 5, 6]);
    });

    it('print', () => {
      expect(hm.toVisual()).toEqual([
        [2, 2],
        [3, 3],
        [4, 4],
        [5, 5],
        [6, 6]
      ]);
    });
  });

  describe('HashMap HOF', () => {
    let hashMap: HashMap<string, string>;

    beforeEach(() => {
      hashMap = new HashMap<string, string>();
      hashMap.set('key1', 'value1');
      hashMap.set('key2', 'value2');
      hashMap.set('key3', 'value3');
    });

    it('every() returns true if all elements match the condition', () => {
      expect(hashMap.every(value => typeof value === 'string')).toBe(true);
    });

    it('some() returns true if any element matches the condition', () => {
      expect(hashMap.some(key => key === 'key1')).toBe(true);
    });

    it('forEach() should execute a function for each element', () => {
      const mockCallback = jest.fn();
      hashMap.forEach(mockCallback);
      expect(mockCallback.mock.calls.length).toBe(3);
    });

    it('map() should transform each element', () => {
      const newHashMap = hashMap.map((key, value) => value.toUpperCase());
      expect(newHashMap.get('key1')).toBe('VALUE1');
    });

    it('filter() should remove elements that do not match the condition', () => {
      const filteredHashMap = hashMap.filter(key => key !== 'key1');
      expect(filteredHashMap.has('key1')).toBe(false);
    });

    it('reduce() should accumulate values', () => {
      const result = hashMap.reduce((acc, value) => acc + value, '');
      expect(result).toBe('value1value2value3');
    });

    it('should spread in an array', () => {
      expect([...hashMap]).toEqual([
        ['key1', 'value1'],
        ['key2', 'value2'],
        ['key3', 'value3']
      ]);
    });

    it('should find', () => {
      const found = hashMap.find((key, value) => value === 'value1');
      expect(found).toEqual(['key1', 'value1']);
      const notFound = hashMap.find(value => value === 'value6');
      expect(notFound).toEqual(undefined);
    });

    it('should every', () => {
      const isEvery = hashMap.every((key, value) => value.substring(0, 5) === 'value');
      expect(isEvery).toEqual(true);
      const isEvery4 = hashMap.every((key, value) => value.substring(0, 4) === 'value');
      expect(isEvery4).toEqual(false);
    });

    it('should some', () => {
      const isSome = hashMap.some((key, value) => value.substring(5, 6) === '2');
      expect(isSome).toEqual(true);
      const isSome4 = hashMap.some((key, value) => value.substring(0, 5) === 'value');
      expect(isSome4).toEqual(true);
    });

    it('should forEach', () => {
      hashMap.forEach((key, value, index) => expect(value.substring(5, 6)).toBe(String(index + 1)));
    });

    it('should entries', () => {
      const entries = hashMap.entries();
      expect(entries.next()).toEqual({ done: false, value: ['key1', 'value1'] });
      expect(entries.next()).toEqual({ done: false, value: ['key2', 'value2'] });
      expect(entries.next()).toEqual({ done: false, value: ['key3', 'value3'] });
      expect(entries.next()).toEqual({ done: true, value: undefined });
    });

    it('should keys', () => {
      const keys = hashMap.keys();
      expect(keys.next()).toEqual({ done: false, value: 'key1' });
      expect(keys.next()).toEqual({ done: false, value: 'key2' });
      expect(keys.next()).toEqual({ done: false, value: 'key3' });
      expect(keys.next()).toEqual({ done: true, value: undefined });
    });

    it('should values', () => {
      const values = hashMap.values();
      expect(values.next()).toEqual({ done: false, value: 'value1' });
      expect(values.next()).toEqual({ done: false, value: 'value2' });
      expect(values.next()).toEqual({ done: false, value: 'value3' });
      expect(values.next()).toEqual({ done: true, value: undefined });
    });
  });
});

describe('LinkedHashMap', () => {
  let hashMap: LinkedHashMap;

  beforeEach(() => {
    hashMap = new LinkedHashMap();
  });

  it('should create an empty map', () => {
    expect(hashMap.size).toBe(0);
  });

  it('should add a key-value pair', () => {
    expect(hashMap.first).toBe(undefined);
    hashMap.set('key1', 'value1');
    expect(hashMap.get('key1')).toBe('value1');
  });

  it('should handle object keys correctly', () => {
    const keyObj = { id: 1 };
    hashMap.set(keyObj, 'objectValue');
    expect(hashMap.get(keyObj)).toBe('objectValue');
  });

  it('should handle number keys correctly', () => {
    hashMap.set(999, { a: '999Value' });
    expect(hashMap.get(999)).toEqual({
      a: '999Value'
    });
  });

  it('should update the value for an existing key', () => {
    hashMap.set('key1', 'value1');
    hashMap.set('key1', 'newValue');
    expect(hashMap.get('key1')).toBe('newValue');
  });

  it('should return undefined for a non-existent key', () => {
    expect(hashMap.get('nonExistentKey')).toBeUndefined();
  });

  it('should remove a key-value pair', () => {
    hashMap.set('key1', 'value1');
    hashMap.delete('key1');
    expect(hashMap.get('key1')).toBeUndefined();
  });

  it('should clear the map', () => {
    hashMap.set('key1', 'value1');
    expect(hashMap.size).toBe(1);

    hashMap.clear();
    expect(hashMap.size).toBe(0);
  });

  it('should iterate over values', () => {
    hashMap.set('key1', 'value1');
    hashMap.set('key2', 'value2');
    const values = [];
    for (const value of hashMap) {
      values.push(value);
    }
    expect(values).toEqual([
      ['key1', 'value1'],
      ['key2', 'value2']
    ]);
  });

  function compareHashMaps(hashMap: LinkedHashMap<unknown, unknown>, stdMap: Map<unknown, unknown>) {
    expect(hashMap.size).toEqual(stdMap.size);
    let index = 0;
    stdMap.forEach((value, key) => {
      if (index === 0) {
        expect(hashMap.first).toEqual([key, value]);
        expect(hashMap.begin().next().value).toEqual([key, value]);
      } else if (index === hashMap.size - 1) {
        expect(hashMap.last).toEqual([key, value]);
        expect(hashMap.reverseBegin().next().value).toEqual([key, value]);
      } else if (index <= 1000) {
        expect(hashMap.at(index)).toBe(value);
      }
      expect(hashMap.get(key)).toEqual(value);
      index++;
    });
  }

  const stdMap: Map<unknown, unknown> = new Map();
  const arr: number[] = getRandomIntArray(1000, 1, 10000);

  it('delete test', () => {
    for (const item of arr) {
      stdMap.set(item, item);
      hashMap.set(item, item);
    }
    for (const item of arr) {
      if (Math.random() > 0.6) {
        expect(hashMap.delete(item)).toEqual(stdMap.delete(item));
      }
    }
    compareHashMaps(hashMap, stdMap);

    for (let i = 0; i < 1000; ++i) {
      const random = getRandomInt(0, 100);
      expect(hashMap.delete(random)).toEqual(stdMap.delete(random));
    }
    compareHashMaps(hashMap, stdMap);
  });

  it('should iterate correctly with reverse iterators', () => {
    hashMap.set('key1', 'value1');
    hashMap.set('key2', 'value2');
    const iterator = hashMap.reverseBegin();
    expect(iterator.next().value).toEqual(['key2', 'value2']);
  });

  it('should return the last element', () => {
    hashMap.set('key1', 'value1');
    hashMap.set('key2', 'value2');
    expect(hashMap.last).toEqual(['key2', 'value2']);
  });

  it('should return undefined for empty map', () => {
    expect(hashMap.last).toBeUndefined();
  });

  it('should get element at specific index', () => {
    hashMap.set('key1', 'value1');
    hashMap.set('key2', 'value2');
    expect(hashMap.at(1)).toBe('value2');
  });

  it('should hashFn, objHashFn, toEntryFn work well', () => {
    const data: Array<{ name: number }> = [{ name: 1 }, { name: 2 }, { name: 3 }];
    const hm = new LinkedHashMap(data, {
      hashFn: key => String(key),
      objHashFn: obj => obj,
      toEntryFn: ({ name }) => [{ name }, name]
    });

    expect(hm.hashFn).toBeTruthy();
    expect(hm.objHashFn).toBeTruthy();
    expect(hm.toEntryFn).toBeTruthy();
    expect([...hm]).toEqual([
      [{ name: 1 }, 1],
      [{ name: 2 }, 2],
      [{ name: 3 }, 3]
    ]);
  });

  it('should begin, reverseBegin', () => {
    const data: Array<{ name: number }> = [{ name: 1 }, { name: 2 }, { name: 3 }];
    const hm = new LinkedHashMap(data, {
      hashFn: key => String(key),
      objHashFn: obj => obj,
      toEntryFn: ({ name }) => [{ name }, name]
    });

    expect(hm.begin().next()).toEqual({
      done: false,
      value: [
        {
          name: 1
        },
        1
      ]
    });

    expect(hm.reverseBegin().next()).toEqual({
      done: false,
      value: [
        {
          name: 3
        },
        3
      ]
    });
  });

  it('should clone', () => {
    hashMap = new LinkedHashMap<string, number>();

    hashMap.set('one', 1);
    hashMap.set('two', 2);
    for (let i = 3; i <= 100; i++) {
      hashMap.set(i.toString(), i);
    }

    expect(hashMap.get('one')).toBe(1);
    expect(hashMap.get('two')).toBe(2);
    expect(hashMap.get('86')).toBe(86);
    expect(hashMap.size).toBe(100);
    hashMap.delete('two');
    expect(hashMap.size).toBe(99);

    const cloned = hashMap.clone();
    expect(cloned.get('one')).toBe(1);
    expect(cloned.get('two')).toBe(undefined);
    expect(cloned.get('86')).toBe(86);
    expect(cloned.size).toBe(99);
  });

  describe('LinkedHashMap basic', () => {
    let hashMap: LinkedHashMap<string, number>;

    beforeEach(() => {
      hashMap = new LinkedHashMap<string, number>();
    });

    it('should initialize correctly', () => {
      expect(hashMap.size).toBe(0);
      // expect(hashMap.table.length).toBe(16);
      // expect(hashMap.loadFactor).toBe(0.75);
      // expect(hashMap.capacityMultiplier).toBe(2);
      // expect(hashMap.initialCapacity).toBe(16);
      expect(hashMap.isEmpty()).toBe(true);
    });

    it('should put and get values', () => {
      hashMap.set('one', 1);
      hashMap.set('two', 2);
      hashMap.set('three', 3);

      expect(hashMap.get('one')).toBe(1);
      expect(hashMap.get('two')).toBe(2);
      expect(hashMap.get('three')).toBe(3);
    });

    it('should handle key collisions', () => {
      // Force a collision by setting two different keys to the same bucket
      hashMap.set('key1', 1);
      hashMap.set('key2', 2);

      expect(hashMap.get('key1')).toBe(1);
      expect(hashMap.get('key2')).toBe(2);
    });

    it('should delete values', () => {
      hashMap.set('one', 1);
      hashMap.set('two', 2);

      hashMap.delete('one');
      expect(hashMap.get('one')).toBeUndefined();
      expect(hashMap.size).toBe(1);
      hashMap.deleteAt(0);
      // expect(hashMap.get('two')).toBe(undefined); // TODO #99
      expect(hashMap.size).toBe(0);
    });

    it('should clear the LinkedHashMap', () => {
      hashMap.set('one', 1);
      hashMap.set('two', 2);

      hashMap.clear();
      expect(hashMap.size).toBe(0);
      expect(hashMap.isEmpty()).toBe(true);
    });

    it('should iterate over entries', () => {
      hashMap.set('one', 1);
      hashMap.set('two', 2);
      hashMap.set('three', 3);

      // const entries = Array.from(hashMap.entries());
      // expect(entries).toContainEqual(['one', 1]);
      // expect(entries).toContainEqual(['two', 2]);
      // expect(entries).toContainEqual(['three', 3]);
    });

    it('should resize the table when load factor is exceeded', () => {
      // Set a small initial capacity for testing resizing
      hashMap = new LinkedHashMap<string, number>();

      hashMap.set('one', 1);
      hashMap.set('two', 2);
      hashMap.set('three', 3);
      hashMap.set('four', 4); // This should trigger a resize

      // expect(hashMap.table.length).toBe(8);
      expect(hashMap.get('one')).toBe(1);
      expect(hashMap.get('two')).toBe(2);
      expect(hashMap.get('three')).toBe(3);
      expect(hashMap.get('four')).toBe(4);
    });

    it('should allow using a custom hash function', () => {
      hashMap = new LinkedHashMap<string, number>();

      hashMap.set('one', 1);
      hashMap.set('two', 2);

      expect(hashMap.get('one')).toBe(1);
      expect(hashMap.get('two')).toBe(2);
      // Since the custom hash function always returns 0, these keys will collide.
      // Make sure they are stored separately.
      // expect(hashMap.table[0].length).toBe(2);
    });

    // it('should handle number keys correctly', () => {
    //   const hm = new LinkedHashMap();
    //   hm.set(999, { a: '999Value' });
    //   hm.set('999', {a: '999StrValue'})
    //   expect(hm.get(999)).toEqual({ a: '999Value' });
    //   expect(hm.get('999')).toEqual({ a: '999StrValue1' });
    // });
  });

  describe('coordinate object keys', () => {
    const hashMap: LinkedHashMap<[number, number], number> = new LinkedHashMap();
    const codObjs: [number, number][] = [];

    it('set elements in hash map', () => {
      for (let i = 0; i < 1000; i++) {
        const codObj: [number, number] = [getRandomInt(-10000, 10000), i];
        codObjs.push(codObj);
        hashMap.set(codObj, i);
      }
    });

    it('get elements in hash map', () => {
      for (let i = 0; i < 1000; i++) {
        const codObj = codObjs[i];
        if (codObj) {
          expect(hashMap.get(codObj)).toBe(i);
        }
      }
    });

    it('delete elements in hash map', () => {
      for (let i = 0; i < 1000; i++) {
        if (i === 500) expect(hashMap.size).toBe(500);
        const codObj = codObjs[i];
        if (codObj) hashMap.delete(codObj);
      }
      expect(hashMap.size).toBe(0);
    });
  });

  describe('setMany, keys, values', () => {
    const hm: LinkedHashMap<number, number> = new LinkedHashMap<number, number>();

    beforeEach(() => {
      hm.clear();
      hm.setMany([
        [2, 2],
        [3, 3],
        [4, 4],
        [5, 5]
      ]);
      hm.setMany([
        [2, 2],
        [3, 3],
        [4, 4],
        [6, 6]
      ]);
    });

    it('keys', () => {
      expect([...hm.keys()]).toEqual([2, 3, 4, 5, 6]);
    });

    it('values', () => {
      expect([...hm.values()]).toEqual([2, 3, 4, 5, 6]);
    });

    it('entries', () => {
      expect([...hm.entries()]).toEqual([
        [2, 2],
        [3, 3],
        [4, 4],
        [5, 5],
        [6, 6]
      ]);
    });

    it('every', () => {
      expect(hm.every(value => value > 4)).toBe(false);
    });

    it('some', () => {
      expect(hm.some(value => value > 6)).toBe(false);
    });

    it('hasValue', () => {
      expect(hm.hasValue(3)).toBe(true);
      expect(hm.hasValue(7)).toBe(false);
    });

    it('print', () => {
      // hm.print();
    });
  });

  describe('HashMap HOF', () => {
    let hashMap: LinkedHashMap;

    beforeEach(() => {
      hashMap = new LinkedHashMap<string, string>();
      hashMap.set('key1', 'value1');
      hashMap.set('key2', 'value2');
      hashMap.set('key3', 'value3');
    });

    it('every() returns true if all elements match the condition', () => {
      expect(hashMap.every(value => typeof value === 'string')).toBe(true);
    });

    it('some() returns true if any element matches the condition', () => {
      expect(hashMap.some(key => key === 'key1')).toBe(true);
    });

    it('forEach() should execute a function for each element', () => {
      const mockCallback = jest.fn();
      hashMap.forEach(mockCallback);
      expect(mockCallback.mock.calls.length).toBe(3);
    });

    it('map() should transform each element', () => {
      const newHashMap = hashMap.map((key, value) => [key, value.toUpperCase()]);
      expect(newHashMap.get('key1')).toBe('VALUE1');
    });

    it('filter() should remove elements that do not match the condition', () => {
      const filteredHashMap = hashMap.filter(key => key !== 'key1');
      expect(filteredHashMap.has('key1')).toBe(false);
    });

    it('reduce() should accumulate values', () => {
      const result = hashMap.reduce((acc, value) => acc + value, '');
      expect(result).toBe('value1value2value3');
    });
  });
});

describe('classic uses', () => {
  it('@example should maintain insertion order', () => {
    const linkedHashMap = new LinkedHashMap<number, string>();
    linkedHashMap.set(1, 'A');
    linkedHashMap.set(2, 'B');
    linkedHashMap.set(3, 'C');

    const result = Array.from(linkedHashMap);
    expect(result).toEqual([
      [1, 'A'],
      [2, 'B'],
      [3, 'C']
    ]);
  });

  it('should allow reverse iteration', () => {
    const linkedHashMap = new LinkedHashMap<number, string>();
    linkedHashMap.set(1, 'A');
    linkedHashMap.set(2, 'B');
    linkedHashMap.set(3, 'C');

    const result = Array.from(linkedHashMap.reverseBegin());
    expect(result).toEqual([
      [3, 'C'],
      [2, 'B'],
      [1, 'A']
    ]);
  });

  it('should allow fast deletion at an index', () => {
    const linkedHashMap = new LinkedHashMap<number, string>();
    linkedHashMap.set(1, 'A');
    linkedHashMap.set(2, 'B');
    linkedHashMap.set(3, 'C');

    linkedHashMap.deleteAt(1);

    const result = Array.from(linkedHashMap);
    expect(result).toEqual([
      [1, 'A'],
      [3, 'C']
    ]);
  });

  it('should filter entries correctly', () => {
    const linkedHashMap = new LinkedHashMap<number, string>();
    linkedHashMap.set(1, 'A');
    linkedHashMap.set(2, 'B');
    linkedHashMap.set(3, 'C');

    const filteredMap = linkedHashMap.filter((key, value) => value !== 'B');

    const result = Array.from(filteredMap);
    expect(result).toEqual([
      [1, 'A'],
      [3, 'C']
    ]);
  });

  it('should map entries to a new LinkedHashMap', () => {
    const linkedHashMap = new LinkedHashMap<number, string>();
    linkedHashMap.set(1, 'A');
    linkedHashMap.set(2, 'B');

    const mappedMap = linkedHashMap.map((key, value) => [value, key]);

    const result = Array.from(mappedMap);
    expect(result).toEqual([
      ['A', 1],
      ['B', 2]
    ]);
  });
});

describe('classic uses', () => {
  it('@example fast lookup of values by key', () => {
    const hashMap = new HashMap<number, string>();
    hashMap.set(1, 'A');
    hashMap.set(2, 'B');
    hashMap.set(3, 'C');

    expect(hashMap.get(1)).toBe('A');
    expect(hashMap.get(2)).toBe('B');
    expect(hashMap.get(3)).toBe('C');
    expect(hashMap.get(99)).toBeUndefined(); // Key not present
  });

  it('@example remove duplicates when adding multiple entries', () => {
    const hashMap = new HashMap<number, string>();
    hashMap.set(1, 'A');
    hashMap.set(2, 'B');
    hashMap.set(1, 'C'); // Update value for key 1

    expect(hashMap.size).toBe(2);
    expect(hashMap.get(1)).toBe('C');
    expect(hashMap.get(2)).toBe('B');
  });

  it('@example count occurrences of keys', () => {
    const data = [1, 2, 1, 3, 2, 1];

    const countMap = new HashMap<number, number>();
    for (const key of data) {
      countMap.set(key, (countMap.get(key) || 0) + 1);
    }

    expect(countMap.get(1)).toBe(3);
    expect(countMap.get(2)).toBe(2);
    expect(countMap.get(3)).toBe(1);
  });

  it('should group entries by a key-derived property', () => {
    const entries = [
      { id: 1, group: 'A' },
      { id: 2, group: 'B' },
      { id: 3, group: 'A' },
      { id: 4, group: 'B' }
    ];

    const groupedMap = new HashMap<string, number[]>();

    for (const entry of entries) {
      const group = entry.group;
      const id = entry.id;
      if (!groupedMap.has(group)) {
        groupedMap.set(group, []);
      }
      groupedMap.get(group)?.push(id);
    }

    expect(groupedMap.get('A')).toEqual([1, 3]);
    expect(groupedMap.get('B')).toEqual([2, 4]);
  });
});
