import { useCallback, useEffect, useMemo, useState } from 'react';
import {
  EventType,
  MatrixEvent,
  RelationType,
  Room,
  RoomEvent,
} from 'matrix-js-sdk';
import { RelationsEvent } from 'matrix-js-sdk/lib/models/relations';
import { MessageEvent } from '../../../types/matrix/room';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { usePowerLevels } from '../../hooks/usePowerLevels';
import { useRoomCreators } from '../../hooks/useRoomCreators';
import { useRoomPermissions } from '../../hooks/useRoomPermissions';
import { getEventReactions, getReactionContent } from '../../utils/room';
import { eventWithShortcode, factoryEventSentBy } from '../../utils/matrix';

export function useForumMessageReactions(room: Room, mEvent: MatrixEvent | undefined) {
  const mx = useMatrixClient();
  const [relationsRevision, setRelationsRevision] = useState(0);
  const powerLevels = usePowerLevels(room);
  const creators = useRoomCreators(room);
  const permissions = useRoomPermissions(creators, powerLevels);

  const canSendReaction = permissions.event(MessageEvent.Reaction, mx.getSafeUserId());
  const canPinEvent = permissions.stateEvent('m.room.pinned_events', mx.getSafeUserId());

  const roomTimelineSet = room.getUnfilteredTimelineSet();
  const eventId = mEvent?.getId();

  const bumpRelations = useCallback(() => {
    setRelationsRevision((revision) => revision + 1);
  }, []);

  useEffect(() => {
    if (!eventId) return undefined;

    const isReactionForEvent = (matrixEvent: MatrixEvent) => {
      if (matrixEvent.getType() !== EventType.Reaction) return false;
      return matrixEvent.getRelation()?.event_id === eventId;
    };

    const handleTimeline = (matrixEvent: MatrixEvent, eventRoom?: Room) => {
      if (eventRoom?.roomId !== room.roomId) return;
      if (isReactionForEvent(matrixEvent)) {
        bumpRelations();
      }
    };

    const handleRedaction = (_matrixEvent: MatrixEvent, eventRoom?: Room) => {
      if (eventRoom?.roomId !== room.roomId) return;
      bumpRelations();
    };

    mx.on(RoomEvent.Timeline, handleTimeline);
    mx.on(RoomEvent.Redaction, handleRedaction);

    return () => {
      mx.removeListener(RoomEvent.Timeline, handleTimeline);
      mx.removeListener(RoomEvent.Redaction, handleRedaction);
    };
  }, [bumpRelations, eventId, mx, room.roomId]);

  useEffect(() => {
    if (!eventId) return undefined;

    const relations = getEventReactions(roomTimelineSet, eventId);
    if (!relations) return undefined;

    const handleRelationsUpdate = () => {
      bumpRelations();
    };

    relations.on(RelationsEvent.Add, handleRelationsUpdate);
    relations.on(RelationsEvent.Redaction, handleRelationsUpdate);
    relations.on(RelationsEvent.Remove, handleRelationsUpdate);

    return () => {
      relations.removeListener(RelationsEvent.Add, handleRelationsUpdate);
      relations.removeListener(RelationsEvent.Redaction, handleRelationsUpdate);
      relations.removeListener(RelationsEvent.Remove, handleRelationsUpdate);
    };
  }, [bumpRelations, eventId, relationsRevision, roomTimelineSet]);

  const reactionRelations = useMemo(() => {
    if (!eventId) return undefined;
    return getEventReactions(roomTimelineSet, eventId);
    // relationsRevision forces re-read after timeline / relation updates
  }, [eventId, relationsRevision, roomTimelineSet]);

  const hasReactions = Boolean(reactionRelations?.getSortedAnnotationsByKey()?.length);

  const handleReactionToggle = useCallback(
    (targetEventId: string, key: string, shortcode?: string) => {
      const relations = getEventReactions(roomTimelineSet, targetEventId);
      const allReactions = relations?.getSortedAnnotationsByKey() ?? [];
      const [, reactionsSet] = allReactions.find(([k]) => k === key) ?? [];
      const reactions = reactionsSet ? Array.from(reactionsSet) : [];
      const myReaction = reactions.find(factoryEventSentBy(mx.getSafeUserId()));

      if (myReaction?.isRelation()) {
        const myReactionId = myReaction.getId();
        if (myReactionId) {
          mx.redactEvent(room.roomId, myReactionId).finally(() => {
            bumpRelations();
            window.setTimeout(bumpRelations, 250);
          });
        }
        return;
      }

      const rShortcode =
        shortcode ||
        (reactions.find(eventWithShortcode)?.getContent().shortcode as string | undefined);
      mx.sendEvent(
        room.roomId,
        MessageEvent.Reaction as never,
        getReactionContent(targetEventId, key, rShortcode)
      ).finally(() => {
        bumpRelations();
        window.setTimeout(bumpRelations, 250);
      });
    },
    [bumpRelations, mx, room.roomId, roomTimelineSet]
  );

  return {
    canSendReaction,
    canPinEvent,
    reactionRelations,
    hasReactions,
    handleReactionToggle,
    eventId,
  };
}
