import { useCallback, useEffect, useRef, useState } from 'react';
import { MatrixEvent, Room, RoomEvent } from 'matrix-js-sdk';
import { BurstPoint, resolveBurstOrigin } from './burstOrigin';
import {
  EMOJI_CONFETTI_EVENT_TYPE,
  EmojiConfettiBurst,
  EmojiConfettiContent,
} from './types';

function scheduleBurstFromValues(
  burstId: string,
  emojis: string[],
  targetEventId: string | undefined,
  onBurst: (burst: EmojiConfettiBurst) => void,
  attempt = 0,
  explicitOrigin?: BurstPoint
) {
  const targetElement =
    targetEventId &&
    document.querySelector(`[data-message-id="${CSS.escape(targetEventId)}"]`);

  if (targetEventId && !targetElement && attempt < 8) {
    window.setTimeout(
      () =>
        scheduleBurstFromValues(
          burstId,
          emojis,
          targetEventId,
          onBurst,
          attempt + 1,
          explicitOrigin
        ),
      50
    );
    return;
  }

  onBurst({
    id: burstId,
    targetEventId: targetEventId ?? '',
    emojis: emojis.length > 0 ? emojis : ['🎉'],
    origin: resolveBurstOrigin(targetEventId, emojis, explicitOrigin),
  });
}

function scheduleBurst(
  event: MatrixEvent,
  onBurst: (burst: EmojiConfettiBurst) => void,
  attempt = 0
) {
  const eventId = event.getId();
  if (!eventId) return;

  const { emojis, targetEventId } = parseEmojiConfettiContent(event.getContent());
  scheduleBurstFromValues(eventId, emojis, targetEventId, onBurst, attempt);
}

function parseEmojiConfettiContent(content: unknown): { emojis: string[]; targetEventId?: string } {
  const confettiContent = content as EmojiConfettiContent;
  const emojis = Array.isArray(confettiContent.emojis)
    ? confettiContent.emojis.filter((emoji): emoji is string => typeof emoji === 'string' && emoji.length > 0)
    : [];

  return {
    emojis: emojis.length > 0 ? emojis : ['🎉'],
    targetEventId:
      typeof confettiContent.target_event_id === 'string'
        ? confettiContent.target_event_id
        : undefined,
  };
}

export function useEmojiConfetti(room: Room) {
  const [bursts, setBursts] = useState<EmojiConfettiBurst[]>([]);
  const seenEventIdsRef = useRef(new Set<string>());
  const ownEventIdsRef = useRef(new Set<string>());

  const addBurst = useCallback((burst: EmojiConfettiBurst) => {
    setBursts((current) => [...current, burst]);
  }, []);

  const registerOwnEventId = useCallback((eventId: string) => {
    ownEventIdsRef.current.add(eventId);
  }, []);

  const queueBurst = useCallback((event: MatrixEvent) => {
    const eventId = event.getId();
    if (!eventId || seenEventIdsRef.current.has(eventId)) return;

    seenEventIdsRef.current.add(eventId);

    if (ownEventIdsRef.current.has(eventId)) {
      ownEventIdsRef.current.delete(eventId);
      return;
    }

    scheduleBurst(event, addBurst);
  }, [addBurst]);

  const triggerLocalBurst = useCallback(
    (targetEventId: string, emojis: string[], origin?: BurstPoint) => {
      const burstId = `local-${targetEventId}-${Date.now()}`;
      scheduleBurstFromValues(burstId, emojis, targetEventId, addBurst, 0, origin);
    },
    [addBurst]
  );

  useEffect(() => {
    const handleTimeline: (
      event: MatrixEvent,
      eventRoom: Room | undefined,
      toStartOfTimeline?: boolean,
      removed?: boolean
    ) => void = (event, eventRoom, toStartOfTimeline, removed) => {
      if (removed || toStartOfTimeline || eventRoom?.roomId !== room.roomId) return;
      if (event.getType() !== EMOJI_CONFETTI_EVENT_TYPE) return;

      queueBurst(event);
    };

    room.on(RoomEvent.Timeline, handleTimeline);
    return () => {
      room.off(RoomEvent.Timeline, handleTimeline);
    };
  }, [queueBurst, room]);

  const removeBurst = useCallback((burstId: string) => {
    setBursts((current) => current.filter((burst) => burst.id !== burstId));
  }, []);

  return {
    bursts,
    removeBurst,
    triggerLocalBurst,
    registerOwnEventId,
  };
}
