import { describe, it, expect, vi, beforeEach } from 'vitest';
import { EventEmitter } from './EventEmitter';

interface TestEvents {
  test: string;
  count: number;
  data: { value: string };
}

describe('EventEmitter', () => {
  let emitter: EventEmitter<TestEvents>;

  beforeEach(() => {
    emitter = new EventEmitter();
  });

  describe('on', () => {
    it('should register an event listener', () => {
      const callback = vi.fn();
      emitter.on('test', callback);

      emitter.emit('test', 'hello');

      expect(callback).toHaveBeenCalledWith('hello');
      expect(callback).toHaveBeenCalledTimes(1);
    });

    it('should return an unsubscribe function', () => {
      const callback = vi.fn();
      const unsubscribe = emitter.on('test', callback);

      emitter.emit('test', 'first');
      unsubscribe();
      emitter.emit('test', 'second');

      expect(callback).toHaveBeenCalledTimes(1);
      expect(callback).toHaveBeenCalledWith('first');
    });

    it('should allow multiple listeners for the same event', () => {
      const callback1 = vi.fn();
      const callback2 = vi.fn();

      emitter.on('test', callback1);
      emitter.on('test', callback2);
      emitter.emit('test', 'value');

      expect(callback1).toHaveBeenCalledWith('value');
      expect(callback2).toHaveBeenCalledWith('value');
    });
  });

  describe('off', () => {
    it('should remove an event listener', () => {
      const callback = vi.fn();
      emitter.on('test', callback);

      emitter.emit('test', 'first');
      emitter.off('test', callback);
      emitter.emit('test', 'second');

      expect(callback).toHaveBeenCalledTimes(1);
    });

    it('should not throw when removing non-existent listener', () => {
      const callback = vi.fn();
      expect(() => emitter.off('test', callback)).not.toThrow();
    });
  });

  describe('emit', () => {
    it('should call all listeners with the data', () => {
      const callback = vi.fn();
      emitter.on('data', callback);

      emitter.emit('data', { value: 'test' });

      expect(callback).toHaveBeenCalledWith({ value: 'test' });
    });

    it('should not throw when emitting to no listeners', () => {
      expect(() => emitter.emit('test', 'value')).not.toThrow();
    });

    it('should catch errors in listeners and continue', () => {
      const errorCallback = vi.fn(() => {
        throw new Error('Test error');
      });
      const successCallback = vi.fn();

      emitter.on('test', errorCallback);
      emitter.on('test', successCallback);

      const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
      emitter.emit('test', 'value');

      expect(errorCallback).toHaveBeenCalled();
      expect(successCallback).toHaveBeenCalled();
      expect(consoleSpy).toHaveBeenCalled();

      consoleSpy.mockRestore();
    });
  });

  describe('once', () => {
    it('should call listener only once', () => {
      const callback = vi.fn();
      emitter.once('test', callback);

      emitter.emit('test', 'first');
      emitter.emit('test', 'second');

      expect(callback).toHaveBeenCalledTimes(1);
      expect(callback).toHaveBeenCalledWith('first');
    });

    it('should return an unsubscribe function', () => {
      const callback = vi.fn();
      const unsubscribe = emitter.once('test', callback);

      unsubscribe();
      emitter.emit('test', 'value');

      expect(callback).not.toHaveBeenCalled();
    });
  });

  describe('clear', () => {
    it('should remove all listeners', () => {
      const callback1 = vi.fn();
      const callback2 = vi.fn();

      emitter.on('test', callback1);
      emitter.on('count', callback2);
      emitter.clear();

      emitter.emit('test', 'value');
      emitter.emit('count', 42);

      expect(callback1).not.toHaveBeenCalled();
      expect(callback2).not.toHaveBeenCalled();
    });
  });

  describe('clearEvent', () => {
    it('should remove all listeners for a specific event', () => {
      const testCallback = vi.fn();
      const countCallback = vi.fn();

      emitter.on('test', testCallback);
      emitter.on('count', countCallback);
      emitter.clearEvent('test');

      emitter.emit('test', 'value');
      emitter.emit('count', 42);

      expect(testCallback).not.toHaveBeenCalled();
      expect(countCallback).toHaveBeenCalledWith(42);
    });
  });

  describe('listenerCount', () => {
    it('should return the number of listeners for an event', () => {
      expect(emitter.listenerCount('test')).toBe(0);

      emitter.on('test', () => {});
      expect(emitter.listenerCount('test')).toBe(1);

      emitter.on('test', () => {});
      expect(emitter.listenerCount('test')).toBe(2);
    });

    it('should return 0 for events with no listeners', () => {
      expect(emitter.listenerCount('count')).toBe(0);
    });
  });
});
