import { Eventer, EventResult } from '../eventer';

describe('Eventer', () => {
  let eventer: Eventer<{
    test: { value: number };
    other: { message: string };
  }>;

  beforeEach(() => {
    eventer = new Eventer();
  });

  describe('on and off', () => {
    it('should register and call event handlers', () => {
      const handler = jest.fn();
      eventer.on('test', handler);
      eventer.emit('test', { value: 42 });

      expect(handler).toHaveBeenCalledTimes(1);
      expect(handler).toHaveBeenCalledWith({ value: 42 }, expect.any(EventResult));
    });

    it('should support multiple handlers for the same event', () => {
      const handler1 = jest.fn();
      const handler2 = jest.fn();
      eventer.on('test', handler1);
      eventer.on('test', handler2);
      eventer.emit('test', { value: 42 });

      expect(handler1).toHaveBeenCalledTimes(1);
      expect(handler2).toHaveBeenCalledTimes(1);
    });

    it('should remove specific handler with off', () => {
      const handler1 = jest.fn();
      const handler2 = jest.fn();
      eventer.on('test', handler1);
      eventer.on('test', handler2);
      eventer.off('test', handler1);
      eventer.emit('test', { value: 42 });

      expect(handler1).not.toHaveBeenCalled();
      expect(handler2).toHaveBeenCalledTimes(1);
    });

    it('should remove all handlers for an event type when off is called without handler', () => {
      const handler1 = jest.fn();
      const handler2 = jest.fn();
      eventer.on('test', handler1);
      eventer.on('test', handler2);
      eventer.off('test');
      eventer.emit('test', { value: 42 });

      expect(handler1).not.toHaveBeenCalled();
      expect(handler2).not.toHaveBeenCalled();
    });

    it('should return unsubscribe function from on', () => {
      const handler = jest.fn();
      const unsubscribe = eventer.on('test', handler);
      unsubscribe();
      eventer.emit('test', { value: 42 });

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

  describe('offAll', () => {
    it('should remove all event handlers', () => {
      const handler1 = jest.fn();
      const handler2 = jest.fn();
      eventer.on('test', handler1);
      eventer.on('other', handler2);
      eventer.offAll();
      eventer.emit('test', { value: 42 });
      eventer.emit('other', { message: 'hello' });

      expect(handler1).not.toHaveBeenCalled();
      expect(handler2).not.toHaveBeenCalled();
    });
  });

  describe('emit', () => {
    it('should return EventResult', () => {
      const result = eventer.emit('test', { value: 42 });
      expect(result).toBeInstanceOf(EventResult);
    });

    it('should stop propagation when stop is called', () => {
      const handler1 = jest.fn((ev, result) => {
        result.stop();
      });
      const handler2 = jest.fn();
      eventer.on('test', handler1);
      eventer.on('test', handler2);
      eventer.emit('test', { value: 42 });

      expect(handler1).toHaveBeenCalledTimes(1);
      expect(handler2).not.toHaveBeenCalled();
    });
  });

  describe('emitAsync', () => {
    it('should emit event asynchronously', (done) => {
      const handler = jest.fn(() => {
        expect(handler).toHaveBeenCalledTimes(1);
        done();
      });
      eventer.on('test', handler);
      eventer.emitAsync('test', { value: 42 });
      expect(handler).not.toHaveBeenCalled();
    });
  });

  describe('pipe', () => {
    it('should pipe listeners to another eventer', () => {
      const handler1 = jest.fn();
      const handler2 = jest.fn();
      eventer.on('test', handler1);
      eventer.on('other', handler2);

      const newEventer = new Eventer();
      const callback = jest.fn((type, handler) => {
        newEventer.on(type as any, handler);
      });

      eventer.pipe(callback);

      newEventer.emit('test', { value: 42 });
      newEventer.emit('other', { message: 'hello' });

      expect(handler1).toHaveBeenCalledTimes(1);
      expect(handler2).toHaveBeenCalledTimes(1);
    });

    it('should clear current listeners when clearCurrent is true', () => {
      const handler = jest.fn();
      eventer.on('test', handler);

      const newEventer = new Eventer();
      const callback = jest.fn((type, handler) => {
        newEventer.on(type as any, handler);
      });

      eventer.pipe(callback, true);
      eventer.emit('test', { value: 42 });

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

describe('EventResult', () => {
  it('should track preventDefault state', () => {
    const result = new EventResult();
    expect(result.isDefaultPrevented).toBe(false);
    result.preventDefault();
    expect(result.isDefaultPrevented).toBe(true);
  });

  it('should track stop state', () => {
    const result = new EventResult();
    expect(result.isStopped).toBe(false);
    result.stop();
    expect(result.isStopped).toBe(true);
  });
});

