import {useMemo} from 'react';
import {useShallow} from 'zustand/react/shallow';

import {Conversation, Message} from '../interfaces';
import {GroupsStore, useGroups} from './zustand/useGroups';
import {sortMessages} from '../utils/messages';
import {useTotalUnreadCount} from './zustand/unreads';
import {useMessagesSearch} from './zustand/messagesSearch';

export const useConversations = () => {
  return useGroups(
    useShallow((state: GroupsStore) => {
      const conversations: Conversation[] = [];

      for (const conversation of Object.values(state.groups)) {
        const conversationInfo = conversation.groupInfo;

        // Add to the list if:
        // 1. Conversation info exists (we need for one edge case where user is kicked out of the group but user is in that chat)
        // 2. Latest message exists
        if (!!conversationInfo && conversationInfo.latest) {
          conversations.push(conversationInfo);
        }
      }

      return conversations.sort((a, b) => {
        const aPinnedAt = a.properties?.pinned_at;
        const bPinnedAt = b.properties?.pinned_at;

        // Both are pinned - sort by pinned_at (newer first)
        if (aPinnedAt && bPinnedAt) {
          return bPinnedAt - aPinnedAt;
        }

        // Only a is pinned - a comes first
        if (aPinnedAt && !bPinnedAt) {
          return -1;
        }

        // Only b is pinned - b comes first
        if (!aPinnedAt && bPinnedAt) {
          return 1;
        }

        // Neither is pinned - sort by latest message timestamp (existing logic)
        const aTs = a.latest?.ts;
        const bTs = b.latest?.ts;
        if (!aTs && !bTs) {
          return 0;
        }
        if (!aTs) {
          return 1;
        }
        if (!bTs) {
          return -1;
        }
        return bTs < aTs ? -1 : 1;
      });
    }),
  );
};

export const useConversationsSize = () => {
  return useGroups(
    state =>
      Object.values(state.groups).filter(({groupInfo}) => groupInfo?.latest)
        .length,
  );
};

export const useMarkAsUnreadTotal = () => {
  return useGroups(
    useShallow(state =>
      Object.values(state.groups).reduce((acc: number, group) => {
        return group.groupInfo?.properties?.marked_unread_at ? acc + 1 : acc;
      }, 0),
    ),
  );
};

export const useConversation = (id: string): Conversation | undefined =>
  useGroups(useShallow(state => state.groups[id]?.groupInfo));

export const useConversationUser = (id: string) =>
  useGroups(useShallow(state => state.groups[id]?.groupInfo?.other_user));

export const useIsChannel = (channel: string): boolean =>
  useGroups(state => !!state.groups[channel]?.groupInfo?.is_channel);

const emptyArray: Message[] = [];

export const useMessages = (channel: string) => {
  const {messagesWindow, pendingMessages} = useGroups(
    useShallow(state => ({
      messagesWindow: state?.groups?.[channel]?.messagesWindow || emptyArray,
      pendingMessages: state?.groups?.[channel]?.pendingMessages || emptyArray,
    })),
  );
  const messages = useMemo(
    () => sortMessages(messagesWindow, pendingMessages),
    [messagesWindow, pendingMessages],
  );

  return messages;
};

export const useShouldFetchInitialMessages = (conversationId: string) =>
  useGroups(
    useShallow(
      state =>
        state.groups[conversationId]?.shouldFetchInitialMessages || false,
    ),
  );

export const useHasFirstPageLoadError = (channel: string) =>
  useGroups(state => !!state.groups[channel]?.hasFirstPageLoadError);

export const useIsStoreReady = () =>
  useGroups(state => !state.isUpdatesBlocked);

export const useIsFetchingEvents = () =>
  useGroups(state => state.isFetchingEvents);

export const useIsConversationsLoading = () =>
  useGroups(state => !state.isConversationsLoaded);

export const useIsFetchingAllConversations = () =>
  useGroups(state => state.isFetchingAllConversations);

export const useIsChatsError = () => useGroups(state => state.isError);

export const useChatsTotalUnreads = () => {
  const unreads = useTotalUnreadCount();
  const conversationsMarkedUnread = useMarkAsUnreadTotal();
  return unreads + conversationsMarkedUnread;
};

/**
 * Get a shallow conversation value from the store. Returns `undefined` if the conversation is not found.
 * @param id Conversation id
 * @param selector Selector function to get the value from the conversation
 */
export const useShallowConversation = <T>(
  id: string,
  selector: (conversation: Conversation) => T,
): T | undefined =>
  useGroups(
    useShallow(state => {
      const groupInfo = state.groups[id]?.groupInfo;
      return groupInfo ? selector(groupInfo) : undefined;
    }),
  );

/**
 * Get actions related to conversation messages loading
 * @param id Conversation id
 */
export const useConversationLoadActions = (id: string) =>
  useGroups(
    useShallow(state => ({
      loadBelowMessagesIfNeeded: state.loadBelowMessagesIfNeeded,
      loadAboveMessages: state.loadAboveMessages,
      registerScrollToBottom: state.registerScrollToBottom,
      unregisterScrollToBottom: state.unregisterScrollToBottom,
      setIsScrolling: state.setIsScrolling,
      lastMessageTs: state?.groups?.[id]?.lastMessageTs || '',
    })),
  );

export const useScrollToMessageActions = () =>
  useGroups(
    useShallow(state => ({
      startScrollToMessage: state.startScrollToMessage,
      setTargetMessageId: state.setTargetMessageId,
    })),
  );

export const useMessagesSearchStore = () =>
  useMessagesSearch(
    useShallow(state => ({
      setCurrentSelectedMatch: state.setCurrentSelectedMatch,
      matchedMessagesTsIndexMap: state.matchedMessagesTsIndexMap,
      isSearchVisible: state.isSearchVisible,
      currentSelectedMatch: state.currentSelectedMatch,
    })),
  );

/**
 * Get a map of conversation ids to other user id
 */
export const getMapsForIMChannels = (): {
  channelsToUsers: Map<string, number>;
  usersToChannels: Map<number, string>;
} => {
  const groups = useGroups.getState().groups;
  const imConversations = Object.values(groups).filter(
    ({groupInfo}) => groupInfo?.is_im && groupInfo?.other_user?.id,
  );
  const channelsToUsers = new Map<string, number>();
  const usersToChannels = new Map<number, string>();

  imConversations.forEach(({groupInfo}) => {
    channelsToUsers.set(groupInfo.id, groupInfo.other_user!.id);
    usersToChannels.set(groupInfo.other_user!.id, groupInfo.id);
  });

  return {
    channelsToUsers,
    usersToChannels,
  };
};
