import { MatrixClient, MatrixEvent } from 'matrix-js-sdk';
import { useCallback, useSyncExternalStore } from 'react';
import { useMatrixClient } from '../hooks/useMatrixClient';
import {
  CallMember,
  CALL_MEMBER_EVENT_TYPE,
  getActiveCallMembers,
} from '../features/call/callMemberUtils';
import { subscribeStateEvents } from './stateEventDemux';

const EXPIRY_CHECK_MS = 30_000;
const EMPTY_CALL_MEMBERS: CallMember[] = [];

type RoomSnapshot = {
  members: CallMember[];
  isActive: boolean;
};

type Registry = {
  snapshots: Map<string, RoomSnapshot>;
  globalListeners: Set<() => void>;
  roomListeners: Map<string, Set<() => void>>;
  unsubState: () => void;
  expiryTimer: ReturnType<typeof setInterval>;
};

const registries = new WeakMap<MatrixClient, Registry>();

const notifyRoom = (reg: Registry, roomId: string) => {
  reg.roomListeners.get(roomId)?.forEach((listener) => listener());
  reg.globalListeners.forEach((listener) => listener());
};

const refreshRoom = (reg: Registry, mx: MatrixClient, roomId: string) => {
  const room = mx.getRoom(roomId);
  const members = room ? getActiveCallMembers(room) : [];
  const isActive = members.length > 0;
  const prev = reg.snapshots.get(roomId);

  if (prev && prev.isActive === isActive) {
    const unchanged =
      prev.members.length === members.length &&
      prev.members.every((member, index) => member.userId === members[index]?.userId);
    if (unchanged) return;
  }

  if (isActive) {
    reg.snapshots.set(roomId, { members, isActive });
  } else {
    reg.snapshots.delete(roomId);
  }
  notifyRoom(reg, roomId);
};

const refreshAllRooms = (reg: Registry, mx: MatrixClient) => {
  const seen = new Set<string>();
  mx.getRooms().forEach((room) => {
    seen.add(room.roomId);
    refreshRoom(reg, mx, room.roomId);
  });
  reg.snapshots.forEach((_, roomId) => {
    if (!seen.has(roomId)) {
      reg.snapshots.delete(roomId);
      notifyRoom(reg, roomId);
    }
  });
};

const getOrCreateRegistry = (mx: MatrixClient): Registry => {
  let reg = registries.get(mx);
  if (reg) return reg;

  reg = {
    snapshots: new Map(),
    globalListeners: new Set(),
    roomListeners: new Map(),
    unsubState: subscribeStateEvents(mx, (event: MatrixEvent) => {
      if (event.getType() !== CALL_MEMBER_EVENT_TYPE) return;
      const roomId = event.getRoomId();
      if (roomId) refreshRoom(reg!, mx, roomId);
    }),
    expiryTimer: setInterval(() => refreshAllRooms(reg!, mx), EXPIRY_CHECK_MS),
  };

  refreshAllRooms(reg, mx);
  registries.set(mx, reg);
  return reg;
};

const subscribeRoom = (mx: MatrixClient, roomId: string, listener: () => void): (() => void) => {
  const reg = getOrCreateRegistry(mx);
  let roomSet = reg.roomListeners.get(roomId);
  if (!roomSet) {
    roomSet = new Set();
    reg.roomListeners.set(roomId, roomSet);
  }
  roomSet.add(listener);
  return () => {
    roomSet!.delete(listener);
    if (roomSet!.size === 0) {
      reg.roomListeners.delete(roomId);
    }
  };
};

export const roomHasActiveCall = (mx: MatrixClient, roomId: string): boolean =>
  getOrCreateRegistry(mx).snapshots.get(roomId)?.isActive ?? false;

export const getRoomCallMembersSnapshot = (mx: MatrixClient, roomId: string): CallMember[] =>
  getOrCreateRegistry(mx).snapshots.get(roomId)?.members ?? EMPTY_CALL_MEMBERS;

export const useRoomHasActiveCall = (roomId: string): boolean => {
  const mx = useMatrixClient();
  return useSyncExternalStore(
    useCallback((listener) => subscribeRoom(mx, roomId, listener), [mx, roomId]),
    () => roomHasActiveCall(mx, roomId),
    () => false
  );
};

export const useRoomCallMembersFromRegistry = (roomId: string, enabled = true) => {
  const mx = useMatrixClient();

  const subscribe = useCallback(
    (listener: () => void) => (enabled ? subscribeRoom(mx, roomId, listener) : () => {}),
    [mx, roomId, enabled]
  );

  const getSnapshot = useCallback(
    () => (enabled ? getRoomCallMembersSnapshot(mx, roomId) : EMPTY_CALL_MEMBERS),
    [mx, roomId, enabled]
  );

  const callMembers = useSyncExternalStore(subscribe, getSnapshot, () => EMPTY_CALL_MEMBERS);

  const isCallActive = callMembers.length > 0;
  const myUserId = mx.getUserId();
  const othersInCall = callMembers.filter((m) => m.userId !== myUserId);

  return {
    callMembers,
    isCallActive,
    othersInCall,
    callMemberCount: callMembers.length,
  };
};
