import { MatrixClient, NotificationCountType, ReceiptType } from 'matrix-js-sdk';
import { getSettings } from '../state/settings';
import { readMarkedUnread, setMarkedUnread } from '../state/room/markedUnread';

export async function markAsRead(mx: MatrixClient, roomId: string, privateReceipt: boolean) {
  const { privateReadReceipts } = getSettings();
  const room = mx.getRoom(roomId);
  if (!room) return;

  // Reading a room clears an explicit "mark as unread" (MSC2867). The binder's
  // receipt-driven auto-clear does NOT fire when the room is already fully read
  // (no receipt is sent below in that case), so clear it directly here.
  if (readMarkedUnread(room)) {
    setMarkedUnread(mx, roomId, false).catch(() => undefined);
  }

  const receiptType =
    privateReceipt || privateReadReceipts ? ReceiptType.ReadPrivate : ReceiptType.Read;

  const timeline = room.getLiveTimeline().getEvents();
  const readEventId = room.getEventReadUpTo(mx.getUserId()!);

  const getLatestValidEvent = () => {
    for (let i = timeline.length - 1; i >= 0; i -= 1) {
      const latestEvent = timeline[i];
      if (latestEvent.getId() === readEventId) return null;
      if (!latestEvent.isSending()) return latestEvent;
    }
    return null;
  };

  const latestEvent = timeline.length > 0 ? getLatestValidEvent() : null;
  if (latestEvent) {
    // Unthreaded receipt: with client threadSupport enabled the SDK would
    // otherwise scope this to the main timeline (thread_id: "main"). Unthreaded
    // clears the main timeline + every event up to this one.
    await mx.sendReadReceipt(latestEvent, receiptType, true);
  }

  // Clear per-thread notification counts too — the room's unread dot sums them,
  // so an unread thread reply keeps the dot lit even after the main timeline is
  // read (threadSupport moves thread replies out of the main timeline, so the
  // unthreaded receipt above doesn't necessarily cover them).
  //
  // CRITICAL: only send for a GENUINE loaded thread reply, via thread.lastReply().
  // NEVER fall back to the thread root: a root event is "in the main timeline",
  // so sendReadReceipt(root, false) resolves (via threadIdForReceipt) to a MAIN
  // receipt at that old root event. If the root isn't in the loaded timeline it
  // moves the main read receipt onto an event we don't have -> getEventReadUpTo()
  // returns null -> the room is reported unread on every mark-read call (this was
  // the P6 regression, amplified by the bulk mark-all-orphan-rooms-read callers).
  // If a thread's replies aren't loaded (lastReply() null), just skip it.
  const threads = room.getThreads();
  await Promise.all(
    threads.map((thread) => {
      const unread =
        room.getThreadUnreadNotificationCount(thread.id, NotificationCountType.Total) ?? 0;
      if (unread <= 0) return undefined;
      const lastReply = thread.lastReply();
      if (!lastReply || lastReply.isSending()) return undefined;
      // Threaded receipt (unthreaded = false → the SDK scopes it to this thread
      // via the reply's real threadRootId; it never touches the main marker).
      return mx.sendReadReceipt(lastReply, receiptType, false).catch(() => undefined);
    }),
  );
}
