import { EventBus } from '@common/EventBus';

describe('EventBus', () => {
    let bus: EventBus;

    beforeEach(() => {
        bus = new EventBus();
    });

    it('delivers emitted payload to on() subscribers', () => {
        const spy = jest.fn();
        bus.on<number>('hit', spy);
        bus.emit('hit', 42);
        expect(spy).toHaveBeenCalledWith(42);
    });

    it('ignores duplicate subscriptions of the same handler', () => {
        const spy = jest.fn();
        bus.on('hit', spy);
        bus.on('hit', spy);
        bus.emit('hit', 1);
        expect(spy).toHaveBeenCalledTimes(1);
        expect(bus.listenerCount('hit')).toBe(1);
    });

    it('fires once() handlers exactly one time', () => {
        const spy = jest.fn();
        bus.once('hit', spy);
        bus.emit('hit', 1);
        bus.emit('hit', 2);
        expect(spy).toHaveBeenCalledTimes(1);
        expect(bus.listenerCount('hit')).toBe(0);
    });

    it('off(event, fn) removes only that handler', () => {
        const a = jest.fn();
        const b = jest.fn();
        bus.on('hit', a);
        bus.on('hit', b);
        bus.off('hit', a);
        bus.emit('hit', 1);
        expect(a).not.toHaveBeenCalled();
        expect(b).toHaveBeenCalledTimes(1);
    });

    it('off(event) clears all handlers of that event', () => {
        const a = jest.fn();
        const b = jest.fn();
        bus.on('hit', a);
        bus.on('hit', b);
        bus.off('hit');
        bus.emit('hit', 1);
        expect(a).not.toHaveBeenCalled();
        expect(b).not.toHaveBeenCalled();
        expect(bus.listenerCount('hit')).toBe(0);
    });

    it('isolates exceptions — one bad listener does not break fan-out', () => {
        const err = jest.fn();
        bus.setErrorHook(err);
        const good = jest.fn();
        bus.on('hit', () => {
            throw new Error('boom');
        });
        bus.on('hit', good);
        bus.emit('hit', 1);
        expect(good).toHaveBeenCalledTimes(1);
        expect(err).toHaveBeenCalledTimes(1);
        expect(err.mock.calls[0][0]).toBe('hit');
    });

    it('emit on an unknown event is a no-op', () => {
        expect(() => bus.emit('ghost', 1)).not.toThrow();
    });

    it('clear() drops every handler of every event', () => {
        bus.on('a', () => {});
        bus.on('b', () => {});
        bus.clear();
        expect(bus.listenerCount('a')).toBe(0);
        expect(bus.listenerCount('b')).toBe(0);
    });

    it('supports off for a non-existent handler without error', () => {
        const spy = jest.fn();
        bus.off('hit', spy);
        expect(bus.listenerCount('hit')).toBe(0);
    });

    it('handles a once-handler unsubscribing itself during emit', () => {
        const calls: number[] = [];
        bus.once<number>('tick', (n) => calls.push(n));
        bus.on<number>('tick', (n) => calls.push(n * 10));
        bus.emit('tick', 1);
        bus.emit('tick', 2);
        expect(calls).toEqual([1, 10, 20]);
    });
});
