import { Room, RoomEvent, RoomMemberEvent, MatrixEvent } from 'matrix-js-sdk';
import { useEffect, useState } from 'react';
import { useMatrixClient } from './useMatrixClient';
import { reactionOrEditEvent } from '../utils/room';

// Receipts can land on reaction/edit events which RoomTimeline skips (renders null).
// Walk backwards from the receipt event to find the nearest event that IS rendered.
function nearestRenderableId(
  liveEvents: MatrixEvent[],
  eventIndex: Map<string, number>,
  evtId: string,
): string | null {
  const idx = eventIndex.get(evtId) ?? -1;
  if (idx === -1) return null;
  for (let i = idx; i >= 0; i--) {
    const e = liveEvents[i];
    if (!reactionOrEditEvent(e)) return e.getId() ?? null;
  }
  return null;
}

function computePositions(room: Room, myUserId: string): Map<string, string[]> {
  const map = new Map<string, string[]>();
  const liveEvents = room.getLiveTimeline().getEvents();
  // Build O(1) index once instead of O(T) findIndex per member
  const eventIndex = new Map<string, number>(liveEvents.map((e, i) => [e.getId() ?? '', i]));
  for (const member of room.getJoinedMembers()) {
    if (member.userId === myUserId) continue;
    const evtId = room.getEventReadUpTo(member.userId);
    if (!evtId) continue;
    const targetId = nearestRenderableId(liveEvents, eventIndex, evtId);
    if (!targetId) continue;
    const arr = map.get(targetId);
    if (arr) arr.push(member.userId);
    else map.set(targetId, [member.userId]);
  }
  return map;
}

export function useRoomReadPositions(room: Room): Map<string, string[]> {
  const mx = useMatrixClient();
  const myUserId = mx.getUserId() ?? '';
  const [positions, setPositions] = useState(() => computePositions(room, myUserId));

  useEffect(() => {
    setPositions(computePositions(room, myUserId));
    let debounceTimer: ReturnType<typeof setTimeout> | null = null;
    const onReceipt = (): void => {
      if (debounceTimer !== null) clearTimeout(debounceTimer);
      debounceTimer = setTimeout(() => {
        setPositions(computePositions(room, myUserId));
        debounceTimer = null;
      }, 150);
    };
    const onMembership = (): void => setPositions(computePositions(room, myUserId));
    room.on(RoomEvent.Receipt, onReceipt);
    (room as any).on(RoomMemberEvent.Membership, onMembership);
    return () => {
      if (debounceTimer !== null) clearTimeout(debounceTimer);
      room.removeListener(RoomEvent.Receipt, onReceipt);
      (room as any).removeListener(RoomMemberEvent.Membership, onMembership);
    };
  }, [room, myUserId]);

  return positions;
}
