import { test } from 'node:test';
import assert from 'node:assert/strict';
import type { MatrixClient } from 'matrix-js-sdk';
import { memberChangeStore } from './useRoomMemberChange';

// Minimal fake client: records registered handlers and can emit a member change.
// The store registers exactly one RoomStateEvent.Members handler; we invoke it
// with the same (event, state, member) shape the SDK uses.
const makeFakeMx = () => {
  const handlers: Array<(e: unknown, s: unknown, m: unknown) => void> = [];
  const mx = {
    on: (_event: unknown, h: (e: unknown, s: unknown, m: unknown) => void) => {
      handlers.push(h);
    },
    removeListener: (_event: unknown, h: (e: unknown, s: unknown, m: unknown) => void) => {
      const i = handlers.indexOf(h);
      if (i >= 0) handlers.splice(i, 1);
    },
  };
  return {
    mx: mx as unknown as MatrixClient,
    listenerCount: () => handlers.length,
    emit: (roomId: string, userId: string) =>
      handlers.forEach((h) => h({ getRoomId: () => roomId }, undefined, { userId })),
  };
};

test('fans out only to the matching roomId|userId', () => {
  const { mx, emit } = makeFakeMx();
  let a = 0;
  let b = 0;
  memberChangeStore.subscribe(mx, '!fanout', '@a', () => {
    a += 1;
  });
  memberChangeStore.subscribe(mx, '!fanout', '@b', () => {
    b += 1;
  });

  emit('!fanout', '@a');
  assert.equal(a, 1);
  assert.equal(b, 0);

  emit('!other', '@a'); // different room, same user -> no fire
  assert.equal(a, 1);
});

test('registers exactly one client listener regardless of subscriber count', () => {
  const { mx, listenerCount } = makeFakeMx();
  memberChangeStore.subscribe(mx, '!one', '@a', () => {});
  memberChangeStore.subscribe(mx, '!one', '@b', () => {});
  memberChangeStore.subscribe(mx, '!one2', '@c', () => {});
  assert.equal(listenerCount(), 1);
});

test('idempotent unsubscribe does not evict a re-subscribed key', () => {
  const { mx, emit } = makeFakeMx();
  const unsubA = memberChangeStore.subscribe(mx, '!idem', '@a', () => {});
  unsubA();

  let count = 0;
  memberChangeStore.subscribe(mx, '!idem', '@a', () => {
    count += 1;
  });
  unsubA(); // double-invoke of the first unsub must not drop the new subscriber
  emit('!idem', '@a');
  assert.equal(count, 1);
});
