import {useMemo, useRef} from 'react';
import {localEventsHandler} from '@config/synchroniser';
import {
  ExtendedMessage,
  ExtendedMessageType,
  Message,
  MessageStatus,
} from '@modules/chats/interfaces';
import {useReactionsInMessages} from '@modules/chats/store';
import {
  getLocalizedDateLabelByTs,
  CHATS_SPACING,
  isSystemMessage,
  hasUnsupportedBlocks,
  mapMessageWithLocalEvents,
} from '@modules/chats/utils';
import {
  UnreadsUpdaterService,
  UnreadState,
} from '@modules/chats/instances/updaters';
import {persistedUserId} from '@services/localStorage';

const getLastRead = (unreadState: Nullable<UnreadState>): Nullable<string> => {
  if (!unreadState || unreadState.count === 0 || !unreadState.last_read) {
    return null;
  }

  return unreadState.last_read;
};

interface Params {
  messages: Message[];
  channel: string;
  is_channel: boolean;
}

export const useExtendedMessages = ({
  messages,
  channel,
  is_channel,
}: Params) => {
  const unreadInfo = useRef(UnreadsUpdaterService.get(channel));
  const lastRead = useRef(getLastRead(unreadInfo.current));

  const clientMsgIds = useMemo(
    () => messages.map(msg => msg.client_msg_id),
    [messages],
  );

  const reactions = useReactionsInMessages(clientMsgIds);

  const extendedMessages = useMemo((): ExtendedMessage[] => {
    const allLocalEvents = localEventsHandler.getEvents();

    // #4: group local events by client_msg_id upfront for O(1) lookup per message
    const localEventsMap = new Map<string, typeof allLocalEvents>();
    for (const event of allLocalEvents) {
      for (const msg of messages) {
        if (event.id.includes(msg.client_msg_id)) {
          const bucket = localEventsMap.get(msg.client_msg_id) ?? [];
          bucket.push(event);
          localEventsMap.set(msg.client_msg_id, bucket);
          break;
        }
      }
    }

    // #1: pre-compute all dates once to avoid double-calling getLocalizedDateLabelByTs
    // per message boundary (was ~2n calls, now n)
    const dates = messages.map(m => getLocalizedDateLabelByTs(m.ts));

    // #3: hoist loop-invariant unread check; track with a flag to skip after first match
    const canShowUnreadPill = !!(
      unreadInfo.current?.count &&
      unreadInfo.current.count > 0 &&
      lastRead.current
    );
    let unreadPillAdded = false;

    const result: ExtendedMessage[] = [];
    let previousUser: Nullable<string> = null;

    for (let i = 0; i < messages.length; i++) {
      let message = messages[i];

      //Condition evaluated to hide the message from the UI
      if (message.deleted_from_ui) {
        continue;
      }

      // Ensure to update messages with its local events before rendering them.
      const messageUpdates = localEventsMap.get(message.client_msg_id);
      if (messageUpdates?.length) {
        message = mapMessageWithLocalEvents(message, messageUpdates);
      }

      const currentDate = dates[i];
      const isFirst = i === 0;
      const isSentByCurrentUser =
        Number(message.user) === Number(persistedUserId);

      if (i === 0) {
        result.push({
          extendedType: ExtendedMessageType.DatePill,
          data: {
            date: currentDate,
            version: 0,
          },
          // Include the length of the messages array to make mvcp ignore it when data changes
          id: `date-${messages.length}-${currentDate.toString()}`,
          ts: message.ts,
          isChannel: is_channel,
        });
        previousUser = null;
      }

      const useExtraMarginTop =
        !isFirst && (!previousUser || previousUser !== message.user);
      if (message.hidden) {
        result.push({
          extendedType: ExtendedMessageType.DeletedMessage,
          data: {
            ts: message.ts,
            status: message.status,
            version: message.version,
            username: message.username,
            client_msg_id: message.client_msg_id,
            sender_profile_picture: message.sender_profile_picture,
          },
          id: message.client_msg_id,
          ts: message.ts,
          isChannel: is_channel,
          isSentByCurrentUser,
          showSenderInfo:
            is_channel && message.user !== previousUser && !isSentByCurrentUser,
          isFirst,
          useExtraMarginTop,
        });
        previousUser = message.user;
      } else if (isSystemMessage(message)) {
        result.push({
          extendedType: ExtendedMessageType.SystemMessage,
          data: {
            blocks: message.blocks!,
            userId: message.user,
            username: message.username,
            version: message.version,
            height: message.height ?? CHATS_SPACING.systemMessageHeight,
          },
          id: message.client_msg_id,
          ts: message.ts,
          isChannel: is_channel,
          isFirst,
        });
        previousUser = null;
      } else if (
        (message.text ||
          message.attachments?.length ||
          message.pendingAssets?.length) &&
        !hasUnsupportedBlocks(message)
      ) {
        /**
         * Add a message with the sender info and the message data
         */
        result.push({
          extendedType: ExtendedMessageType.Default,
          data: message,
          id: message.client_msg_id,
          ts: message.ts,
          isChannel: is_channel,
          isSentByCurrentUser,
          showSenderInfo:
            is_channel && message.user !== previousUser && !isSentByCurrentUser,
          isFirst,
          useExtraMarginTop,
        });

        /**
         * Add a reaction section for default messages.
         * With this element we avoid jumping and overlapping when reactions are added/removed.
         * And we can animate the reactions section without affecting the message height.
         */
        if (reactions[message.client_msg_id]) {
          result.push({
            extendedType: ExtendedMessageType.Reactions,
            data: {
              clientMsgId: message.client_msg_id,
              messageTs: message.ts,
              channel,
              isSentByCurrentUser,
            },
            id: `${message.client_msg_id}-reactions`,
            ts: message.ts,
            isChannel: is_channel,
          });
        }

        previousUser = message.user;
      } else {
        // Handle unsupported message type
        result.push({
          extendedType: ExtendedMessageType.UnsupportedMessage,
          data: message,
          id: message.client_msg_id,
          ts: message.ts,
          isSentByCurrentUser,
          showSenderInfo:
            is_channel && message.user !== previousUser && !isSentByCurrentUser,
          isChannel: is_channel,
          isFirst,
          useExtraMarginTop,
        });
        previousUser = message.user;
      }

      const nextMessage = messages[i + 1];
      // #1: reuse pre-computed dates array instead of calling getLocalizedDateLabelByTs again
      const nextDate = dates[i + 1];

      // Add a date pill if the current date is different than next one
      if (currentDate !== nextDate && !!nextMessage) {
        result.push({
          extendedType: ExtendedMessageType.DatePill,
          data: {
            date: nextDate,
            version: 0,
          },
          id: `date-${messages.length}-${nextDate.toString()}`,
          ts: nextMessage.ts,
          isChannel: is_channel,
        });
        previousUser = null;
      }

      /**
       * Check if the message is the last read message, in order to put the pill right away after that one
       */
      // #3: canShowUnreadPill is hoisted; unreadPillAdded skips all checks after first match
      if (
        !unreadPillAdded &&
        canShowUnreadPill &&
        message.status !== MessageStatus.Sending &&
        message.ts &&
        message.ts <= lastRead.current! &&
        (!nextMessage || (nextMessage.ts && nextMessage.ts > lastRead.current!))
      ) {
        result.push({
          extendedType: ExtendedMessageType.UnreadPill,
          data: {
            isLastRead: true,
            unreadCount: unreadInfo.current!.count,
            version: 0,
          },
          id: `unread-${message.client_msg_id}`,
          ts: message.ts,
          isChannel: is_channel,
        });
        previousUser = null;
        unreadPillAdded = true;
      }
    }

    return result;
  }, [channel, is_channel, messages, reactions]);

  return extendedMessages;
};
