import { atom, useSetAtom } from 'jotai';
import { MatrixClient, MatrixEvent, Room, RoomEvent, RoomEventHandlerMap } from 'matrix-js-sdk';
import { useEffect } from 'react';
import { AccountDataEvent } from '../../../types/matrix/accountData';

// MSC2867 — "mark a room as unread". A per-room account-data flag `{ unread }`.
// Stable type `m.marked_unread`; servers/clients predating the stabilization use
// the unstable `com.famedly.marked_unread`. We read either and write both so the
// flag round-trips across the ecosystem.
const UNSTABLE_MARKED_UNREAD = 'com.famedly.marked_unread';

export const readMarkedUnread = (room: Room): boolean => {
  const stable = room.getAccountData(AccountDataEvent.MarkedUnread)?.getContent()?.unread;
  if (typeof stable === 'boolean') return stable;
  return room.getAccountData(UNSTABLE_MARKED_UNREAD)?.getContent()?.unread === true;
};

/** Set of room ids the user has explicitly marked as unread. */
export const markedUnreadAtom = atom<Set<string>>(new Set<string>());

/** Write (or clear) the marked-unread flag on both the stable + unstable keys. */
export const setMarkedUnread = (
  mx: MatrixClient,
  roomId: string,
  unread: boolean,
): Promise<unknown> =>
  Promise.all([
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    mx.setRoomAccountData(roomId, AccountDataEvent.MarkedUnread as any, { unread }),
    // Best-effort mirror for older servers; never fail the primary write on it.
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    mx.setRoomAccountData(roomId, UNSTABLE_MARKED_UNREAD as any, { unread }).catch(() => undefined),
  ]);

export const receiptIsMine = (event: MatrixEvent, userId: string): boolean => {
  const content = event.getContent();
  return Object.keys(content).some((eventId) =>
    Object.keys(content[eventId] ?? {}).some(
      (receiptType) => content[eventId][receiptType]?.[userId],
    ),
  );
};

// True only when OUR receipt in this event is for the main timeline — either
// unthreaded (no thread_id) or thread_id "main". A receipt scoped to a specific
// thread (thread_id === <threadRootId>) must NOT clear the whole-room marked
// flag, since only that one thread was read.
export const myMainReceiptPresent = (event: MatrixEvent, userId: string): boolean => {
  const content = event.getContent();
  return Object.keys(content).some((eventId) =>
    Object.keys(content[eventId] ?? {}).some((receiptType) => {
      const receipt = content[eventId][receiptType]?.[userId];
      if (!receipt) return false;
      const threadId = (receipt as { thread_id?: string }).thread_id;
      return threadId === undefined || threadId === 'main';
    }),
  );
};

export const useBindMarkedUnreadAtom = (mx: MatrixClient, anAtom: typeof markedUnreadAtom) => {
  const setAtom = useSetAtom(anAtom);

  useEffect(() => {
    const seed = new Set<string>();
    mx.getRooms().forEach((room) => {
      if (readMarkedUnread(room)) seed.add(room.roomId);
    });
    setAtom(seed);

    const syncRoom = (room: Room) => {
      const marked = readMarkedUnread(room);
      setAtom((prev) => {
        if (marked === prev.has(room.roomId)) return prev;
        const next = new Set(prev);
        if (marked) next.add(room.roomId);
        else next.delete(room.roomId);
        return next;
      });
    };

    const onAccountData: RoomEventHandlerMap[RoomEvent.AccountData] = (_event, room) => {
      syncRoom(room);
    };
    // Reading a room clears its marked-unread flag (MSC2867): when our own
    // MAIN-timeline read receipt lands for a room that's currently marked, clear
    // it. Gated to main/unthreaded receipts so reading a single thread doesn't
    // wipe the whole-room flag. (This also fires for receipts from our other
    // devices; the local read path clears via markAsRead in notifications.ts.)
    const onReceipt: RoomEventHandlerMap[RoomEvent.Receipt] = (event, room) => {
      const myId = mx.getUserId();
      if (!myId || !readMarkedUnread(room)) return;
      if (myMainReceiptPresent(event, myId)) {
        setMarkedUnread(mx, room.roomId, false).catch(() => undefined);
      }
    };
    const onMembership: RoomEventHandlerMap[RoomEvent.MyMembership] = (room) => {
      if (room.getMyMembership() !== 'join') {
        setAtom((prev) => {
          if (!prev.has(room.roomId)) return prev;
          const next = new Set(prev);
          next.delete(room.roomId);
          return next;
        });
      }
    };

    mx.on(RoomEvent.AccountData, onAccountData);
    mx.on(RoomEvent.Receipt, onReceipt);
    mx.on(RoomEvent.MyMembership, onMembership);
    return () => {
      mx.removeListener(RoomEvent.AccountData, onAccountData);
      mx.removeListener(RoomEvent.Receipt, onReceipt);
      mx.removeListener(RoomEvent.MyMembership, onMembership);
    };
  }, [mx, setAtom]);
};
