import {create} from 'zustand';
import {useShallow} from 'zustand/react/shallow';
import {
  HuDataMember,
  Message,
  ReactionInfo,
  ReactionWithUser,
  UserReaction,
} from '@modules/chats/interfaces';
import {getReactionText} from '@modules/chats/utils/reactions';
import {mapGlobalUserToChatUser} from '@modules/chats/utils/mappers/userMapper';
import {
  mapReactionToStore,
  mapReactionWithUserToDB,
} from '@modules/chats/utils/mappers/reactionMapper';
import {setReactionsForMessage} from '@modules/chats/db/services/reactions';
import {getReactions} from '@modules/chats/services/reactionsServices';
import reduxStore from '@redux/store';

const EMPTY_ARRAY: string[] = [];

/**
 * Sort reactions by count and name as backend is doing to avoid unconsistencies when backend data is loaded
 * @param reactions - ReactionInfo
 * @returns ReactionInfo
 */
const sortReactions = (reactions: ReactionInfo) => {
  return {
    ...reactions,
    summary: reactions.summary.sort((a, b) => {
      if (b.count !== a.count) {
        return b.count - a.count;
      }
      return a.name > b.name ? 1 : a.name < b.name ? -1 : 0;
    }),
    list: reactions.list.sort((a, b) => {
      if (b.count !== a.count) {
        return b.count - a.count;
      }
      return a.name > b.name ? 1 : a.name < b.name ? -1 : 0;
    }),
  };
};

const injectUserReactionToInfo = (
  info: ReactionInfo,
  userReaction: UserReaction & {user: HuDataMember},
): ReactionInfo => {
  const flags = {
    foundedInSummary: false,
    foundedInList: false,
  };

  const result: ReactionInfo = {
    summary:
      info.summary.length === 0
        ? [
            {
              name: userReaction.reactionName,
              count: 1,
              hasUserReaction: true,
              text: getReactionText({
                name: userReaction.reactionName,
                count: 1,
              }),
            },
          ]
        : info.summary.reduce((acc, reaction, index) => {
            if (reaction.name === userReaction.reactionName) {
              acc.push({
                ...reaction,
                count: reaction.count + 1,
                hasUserReaction: true,
                text: getReactionText({...reaction, count: reaction.count + 1}),
              });
              flags.foundedInSummary = true;
            } else {
              acc.push(reaction);
            }

            if (index === info.summary.length - 1 && !flags.foundedInSummary) {
              acc.push({
                name: userReaction.reactionName,
                count: 1,
                hasUserReaction: true,
                text: getReactionText({
                  name: userReaction.reactionName,
                  count: 1,
                }),
              });
            }
            return acc;
          }, [] as ReactionInfo['summary']),
    list:
      info.list.length === 0
        ? [
            {
              name: userReaction.reactionName,
              count: 1,
              users: [userReaction.user],
            },
          ]
        : info.list.reduce((acc, reaction, index) => {
            if (reaction.name === userReaction.reactionName) {
              acc.push({
                ...reaction,
                count: reaction.count + 1,
                users: [userReaction.user, ...reaction.users],
              });
              flags.foundedInList = true;
            } else {
              acc.push(reaction);
            }

            if (index === info.list.length - 1 && !flags.foundedInList) {
              acc.push({
                name: userReaction.reactionName,
                count: 1,
                users: [userReaction.user],
              });
            }
            return acc;
          }, [] as ReactionInfo['list']),
    userReactions: [...info.userReactions, userReaction.reactionName],
  };

  return sortReactions(result);
};

const ejectUserReactionFromInfo = (
  info: ReactionInfo,
  userReaction: UserReaction,
): ReactionInfo => {
  const result: ReactionInfo = {
    summary: info.summary
      .map(reaction =>
        reaction.name === userReaction.reactionName
          ? {
              ...reaction,
              count: reaction.count - 1,
              hasUserReaction: false,
              text: getReactionText({...reaction, count: reaction.count - 1}),
            }
          : reaction,
      )
      .filter(reaction => reaction.count > 0),
    userReactions: info.userReactions.filter(
      reaction => reaction !== userReaction.reactionName,
    ),
    list: info.list
      .map(reaction =>
        reaction.name === userReaction.reactionName
          ? {
              ...reaction,
              count: reaction.count - 1,
              users: reaction.users.filter(
                user => user.id !== userReaction.userId,
              ),
            }
          : reaction,
      )
      .filter(reaction => reaction.count > 0),
  };

  return sortReactions(result);
};

interface ReactionsStore {
  dialog: {
    show: boolean;
    message: Nullable<{
      clientMsgId: string;
      ts: string;
      channel: string;
    }>;
    initialTab?: string;
  };
  showDialog: (params: {
    channel: string;
    ts: string;
    clientMsgId: string;
    initialTab?: string;
  }) => void;
  hideDialog: () => void;
  messages: Record<string, ReactionInfo>;
  addMessageReactions: (clientMsgId: string, reactions: ReactionInfo) => void;
  addMessageReactionsBatch: (
    data: {clientMsgId: string; reactions: ReactionInfo}[],
  ) => void;
  setMessageReactions: (
    clientMsgId: string,
    reactions: ReactionWithUser[],
  ) => void;
  optimisticUpdateReaction: (reaction: UserReaction) => void;
  clear: () => void;
}

export const reactionsStore = create<ReactionsStore>(set => ({
  dialog: {
    show: false,
    message: null,
  },
  messages: {},
  showDialog: async ({channel, clientMsgId, ts, initialTab}) => {
    set({
      dialog: {
        show: true,
        message: {channel, clientMsgId, ts},
        initialTab,
      },
    });

    const response = await getReactions({
      channel,
      ts,
      full: true,
    });

    // Save reactions to the store
    set(state => ({
      messages: {
        ...state.messages,
        [clientMsgId]: mapReactionToStore({
          reactionsWithUser: response,
        }),
      },
    }));

    // Update DB
    setReactionsForMessage(
      clientMsgId,
      mapReactionWithUserToDB(response, clientMsgId),
    );
  },
  hideDialog: () => {
    set({dialog: {show: false, message: null}});
  },
  addMessageReactionsBatch: data => {
    if (!data.length) {
      return;
    }

    const newState = data.reduce<{[msgId: string]: ReactionInfo}>(
      (acc, {clientMsgId, reactions}) => {
        acc[clientMsgId] = reactions;

        return acc;
      },
      {},
    );

    set(state => ({
      messages: {
        ...state.messages,
        ...newState,
      },
    }));
  },
  addMessageReactions: (clientMsgId, reactions) => {
    set(state => ({
      messages: {
        ...state.messages,
        [clientMsgId]: reactions,
      },
    }));
  },
  setMessageReactions: (id, reactions) => {
    set(state => {
      return {
        messages: {
          ...state.messages,
          [id]: mapReactionToStore({
            reactionsWithUser: reactions,
          }),
        },
      };
    });
  },
  optimisticUpdateReaction: ({
    reactionName,
    userId,
    clientMsgId,
    action,
    channel,
  }) => {
    set(state => {
      const targetMessage = state.messages[clientMsgId];

      if (!targetMessage) {
        return {
          messages: {
            ...state.messages,
            [clientMsgId]: mapReactionToStore({
              reactions: [
                {name: reactionName, count: 1, users: [userId.toString()]},
              ],
            }),
          },
        };
      }

      if (action === 'add') {
        const userFromStore = reduxStore().store.getState().user;
        const currentUser = mapGlobalUserToChatUser(userFromStore);
        const info = injectUserReactionToInfo(targetMessage, {
          reactionName,
          userId,
          user: currentUser,
          channel,
          clientMsgId,
        });
        return {
          messages: {
            ...state.messages,
            [clientMsgId]: info,
          },
        };
      } else if (action === 'remove') {
        const info = ejectUserReactionFromInfo(targetMessage, {
          reactionName,
          userId,
          channel,
          clientMsgId,
        });
        return {
          messages: {
            ...state.messages,
            [clientMsgId]: info,
          },
        };
      }

      return state;
    });
  },
  clear: () => {
    set({messages: {}});
  },
}));

const emptyReactions: ReactionInfo[] = [];

export const useReactionSummary = (
  clientMsgId: string,
): ReactionInfo['summary'] | undefined => {
  return reactionsStore(
    useShallow(state => state.messages[clientMsgId]?.summary || emptyReactions),
  );
};

export const useReactionList = (clientMsgId?: string) =>
  reactionsStore(
    useShallow(state =>
      clientMsgId ? state.messages[clientMsgId]?.list : undefined,
    ),
  );

export const useReactionDialog = () =>
  reactionsStore(
    useShallow(state => ({
      show: state.dialog.show,
      message: state.dialog.message,
      initialTab: state.dialog.initialTab,
      hideDialog: state.hideDialog,
    })),
  );

export const useReactionDialogShown = () =>
  reactionsStore(state => state.dialog.show);

export const useUserReactions = (clientMsgId?: string) =>
  reactionsStore(
    useShallow(state =>
      clientMsgId
        ? state.messages[clientMsgId]?.userReactions || EMPTY_ARRAY
        : EMPTY_ARRAY,
    ),
  );

export const addMessageReactions = (
  clientMsgId: string,
  reactions: ReactionInfo,
) => {
  reactionsStore.getState().addMessageReactions(clientMsgId, reactions);
};

export const addMessageReactionsBatch = (
  data: Parameters<ReactionsStore['addMessageReactionsBatch']>[0],
) => {
  reactionsStore.getState().addMessageReactionsBatch(data);
};

export const optimisticUpdateReaction = (reaction: UserReaction) => {
  reactionsStore.getState().optimisticUpdateReaction(reaction);
};

export const setMessageReactions = (params: {
  clientMsgId: string;
  reactions: ReactionWithUser[];
}) => {
  reactionsStore
    .getState()
    .setMessageReactions(params.clientMsgId, params.reactions);
};

export const showReactionList = (params: {
  channel: string;
  ts: string;
  clientMsgId: string;
  initialTab?: string;
}) => {
  reactionsStore.getState().showDialog(params);
};

export const getUserReactions = (clientMsgId: string) => {
  return (
    reactionsStore.getState().messages[clientMsgId]?.userReactions ||
    EMPTY_ARRAY
  );
};

export const addReactionsFromStores = (messages: Message[]) => {
  addMessageReactionsBatch(
    messages.map(msg => ({
      clientMsgId: msg.client_msg_id,
      reactions: mapReactionToStore({
        reactions: msg.reactions,
      }),
    })),
  );
};

export const clearReactions = () => {
  reactionsStore.getState().clear();
};

export const getMessageReactionsCount = (clientMsgId: string) => {
  return reactionsStore.getState().messages[clientMsgId]?.summary?.length || 0;
};

export const useMessageReactionsCount = (clientMsgId: string) => {
  return reactionsStore(
    state => state.messages[clientMsgId]?.summary?.length || 0,
  );
};

export const useReactionsInMessages = (
  clientMsgIds: string[],
): Record<string, number> => {
  return reactionsStore(
    useShallow(state => {
      const result: Record<string, number> = {};
      for (const id of clientMsgIds) {
        const info = state.messages[id];
        if (info?.summary?.length) {
          result[id] = info.summary.length;
        }
      }
      return result;
    }),
  );
};
