import { useCallback, useSyncExternalStore } from 'react';
import { ClientEvent, MatrixClient } from 'matrix-js-sdk';
import { useMatrixClient } from '../hooks/useMatrixClient';
import { StateEvent } from '../../types/matrix/room';
import { subscribeStateEvents } from './stateEventDemux';

const EMPTY_SUB_ROOM_IDS = new Set<string>();

const setsEqual = (a: ReadonlySet<string>, b: ReadonlySet<string>): boolean => {
  if (a === b) return true;
  if (a.size !== b.size) return false;
  for (const id of a) {
    if (!b.has(id)) return false;
  }
  return true;
};

export const computeGlobalSubRoomIds = (mx: MatrixClient): ReadonlySet<string> => {
  const subRoomIds = new Set<string>();
  mx.getRooms().forEach((room) => {
    const subRoomsEvent = room.currentState.getStateEvents(StateEvent.PaarrotSubRooms, '');
    const content = subRoomsEvent?.getContent<{ children?: string[] }>();
    content?.children?.forEach((childId) => subRoomIds.add(childId));
  });
  return subRoomIds;
};

type SubRoomStore = {
  snapshot: ReadonlySet<string>;
  listeners: Set<() => void>;
};

const subRoomStores = new WeakMap<MatrixClient, SubRoomStore>();

const getSubRoomStore = (mx: MatrixClient): SubRoomStore => {
  let store = subRoomStores.get(mx);
  if (!store) {
    store = {
      snapshot: computeGlobalSubRoomIds(mx),
      listeners: new Set(),
    };
    subRoomStores.set(mx, store);

    const notify = () => {
      const next = computeGlobalSubRoomIds(mx);
      if (setsEqual(store!.snapshot, next)) return;
      store!.snapshot = next;
      store!.listeners.forEach((listener) => listener());
    };

    subscribeStateEvents(mx, (event) => {
      if (event.getType() === StateEvent.PaarrotSubRooms) {
        notify();
      }
    });

    mx.on(ClientEvent.Room, notify);
  }
  return store;
};

const subscribeGlobalSubRoomIds = (mx: MatrixClient, listener: () => void): (() => void) => {
  const store = getSubRoomStore(mx);
  store.listeners.add(listener);
  return () => {
    store.listeners.delete(listener);
  };
};

export const useGlobalSubRoomIds = (): ReadonlySet<string> => {
  const mx = useMatrixClient();
  return useSyncExternalStore(
    useCallback((listener) => subscribeGlobalSubRoomIds(mx, listener), [mx]),
    () => getSubRoomStore(mx).snapshot,
    () => EMPTY_SUB_ROOM_IDS
  );
};
