/* @vitest-environment jsdom */

import { RoomSessionDoorbellEvent } from '@nitrots/nitro-renderer';
import { act, cleanup, renderHook } from '@testing-library/react';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { useDoorbellState } from '../src/hooks/rooms/widgets/useDoorbellState';
import { clearMockEventDispatcher, mockEventDispatcher } from './mocks/renderer-mock';

// Server push helper — mirrors the renderer wire by emitting the same
// constants the SUT listens to. The real constructor takes a session
// reference too; pass null since the SUT only reads `.userName`.
const dispatchDoorbell = (type: string, userName: string) =>
{
    act(() =>
    {
        mockEventDispatcher.dispatchEvent(new RoomSessionDoorbellEvent(type, null as any, userName));
    });
};

describe('useDoorbellState', () =>
{
    beforeEach(() =>
    {
        clearMockEventDispatcher();
    });

    afterEach(() =>
    {
        cleanup();
    });

    it('starts with no users pending', () =>
    {
        const { result } = renderHook(() => useDoorbellState());

        expect(result.current).toEqual([]);
    });

    it('appends the username from a DOORBELL event', () =>
    {
        const { result } = renderHook(() => useDoorbellState());

        dispatchDoorbell(RoomSessionDoorbellEvent.DOORBELL, 'Alice');

        expect(result.current).toEqual([ 'Alice' ]);
    });

    it('ignores duplicate DOORBELL events for the same username', () =>
    {
        const { result } = renderHook(() => useDoorbellState());

        dispatchDoorbell(RoomSessionDoorbellEvent.DOORBELL, 'Alice');
        dispatchDoorbell(RoomSessionDoorbellEvent.DOORBELL, 'Alice');

        expect(result.current).toEqual([ 'Alice' ]);
    });

    it('removes a user on RSDE_ACCEPTED while keeping the others', () =>
    {
        const { result } = renderHook(() => useDoorbellState());

        dispatchDoorbell(RoomSessionDoorbellEvent.DOORBELL, 'Alice');
        dispatchDoorbell(RoomSessionDoorbellEvent.DOORBELL, 'Bob');
        dispatchDoorbell(RoomSessionDoorbellEvent.RSDE_ACCEPTED, 'Alice');

        expect(result.current).toEqual([ 'Bob' ]);
    });

    it('removes a user on RSDE_REJECTED', () =>
    {
        const { result } = renderHook(() => useDoorbellState());

        dispatchDoorbell(RoomSessionDoorbellEvent.DOORBELL, 'Carol');
        dispatchDoorbell(RoomSessionDoorbellEvent.RSDE_REJECTED, 'Carol');

        expect(result.current).toEqual([]);
    });

    it('ignores accept/reject events for users that were never pending', () =>
    {
        const { result } = renderHook(() => useDoorbellState());

        dispatchDoorbell(RoomSessionDoorbellEvent.RSDE_ACCEPTED, 'Ghost');

        expect(result.current).toEqual([]);
    });

    it('unsubscribes from all three events on unmount', () =>
    {
        const { unmount } = renderHook(() => useDoorbellState());

        expect(mockEventDispatcher.hasListeners(RoomSessionDoorbellEvent.DOORBELL)).toBe(true);
        expect(mockEventDispatcher.hasListeners(RoomSessionDoorbellEvent.RSDE_ACCEPTED)).toBe(true);
        expect(mockEventDispatcher.hasListeners(RoomSessionDoorbellEvent.RSDE_REJECTED)).toBe(true);

        unmount();

        expect(mockEventDispatcher.hasListeners(RoomSessionDoorbellEvent.DOORBELL)).toBe(false);
        expect(mockEventDispatcher.hasListeners(RoomSessionDoorbellEvent.RSDE_ACCEPTED)).toBe(false);
        expect(mockEventDispatcher.hasListeners(RoomSessionDoorbellEvent.RSDE_REJECTED)).toBe(false);
    });
});
