import { type InfiniteData } from 'react-query';

import { type AxiosResponse } from 'axios';

import { sharedPostKeys } from 'src/components/post/SharedPost/keys';
import { queryClient } from 'src/config/react-query';
import { profileKeys } from 'src/pages/dashboard/profile/queries';
import { type Comment } from 'src/types/comments';
import { DEFAULT_EMOJI } from 'src/types/feed';
import { type InsightsTypes, type Post } from 'src/types/posts';
import { type Updater } from 'src/types/queries';
import { type Reaction } from 'src/types/reaction';
import { type CursorPagination } from 'src/types/services';
import { type AudienceTypes } from 'src/types/stream';
import {
  decreaseCommentCount,
  includesComment,
  lastCommentUpdater,
  unshiftChildUpdater,
  unshiftCommentUpdater,
} from 'src/utils/comments';
import { addVote, findPostIndexOnPages, findPostOnPages } from 'src/utils/feed';
import { findIndexOnPages } from 'src/utils/pages';
import { functionalUpdate } from 'src/utils/queries';
import {
  addReaction,
  removeReaction,
  sortReactions,
} from 'src/utils/reactions';

import { invalidateReactionUserList } from 'src/components/dashboard/reactions/queries';

export const feedKeys = {
  all: () => ['posts'],
  list: () => [...feedKeys.all(), 'list'],
  allSearch: () => [...feedKeys.all(), 'search'],
  search: (query: string) => [...feedKeys.allSearch(), query],
  allDetail: () => [...feedKeys.all(), 'detail'],
  detail: (id: number | string) => [...feedKeys.allDetail(), Number(id)],
  pinned: () => [...feedKeys.all(), 'pinPost'],
  scheduled: () => [...feedKeys.all(), 'scheduled'],
  comment: {
    all: () => [...feedKeys.all(), 'comment'],
    list: () => [...feedKeys.comment.all(), 'list'],
    detail: (id: number | string) => [...feedKeys.comment.list(), Number(id)],
    liveStream: (id: number | string) => [
      ...feedKeys.comment.list(),
      'liveStream',
      Number(id),
    ],
  },
  childComment: {
    all: () => [...feedKeys.all(), 'childComment'],
    list: () => [...feedKeys.childComment.all(), 'list'],
    detail: (id: number | string) => [
      ...feedKeys.childComment.list(),
      Number(id),
    ],
  },
  linkPreview: {
    all: () => [...feedKeys.all(), 'linkPreview'],
    allDetail: () => [...feedKeys.linkPreview.all(), 'detail'],
    detail: (url: string) => [...feedKeys.linkPreview.allDetail(), url],
  },
  segmentate: {
    all: () => [...feedKeys.all(), 'segmentate'],
    groups: () => [...feedKeys.segmentate.all(), 'groups'],
    usersList: (segmentationItemIds: string, includeLoggedUser: boolean) => [
      ...feedKeys.segmentate.all(),
      'usersList',
      segmentationItemIds,
      includeLoggedUser,
    ],
    usersCount: (segmentationItemIds: string, includeLoggedUser: boolean) => [
      ...feedKeys.segmentate.all(),
      'usersCount',
      segmentationItemIds,
      includeLoggedUser,
    ],
  },
  insights: {
    allDetail: () => [...feedKeys.all(), 'insights'],
    detail: (id: number | string) => [
      ...feedKeys.insights.allDetail(),
      Number(id),
    ],
    summary: (id: number | string) => [
      ...feedKeys.insights.detail(id),
      'summary',
    ],
    audience: (id: number | string, type: InsightsTypes, query: string) => [
      ...feedKeys.insights.detail(id),
      type,
      'audience',
      query,
    ],
    liveStreamTimeline: (id: string) => [
      ...feedKeys.insights.detail(id),
      'liveStreamTimeline',
    ],
    liveStreamStats: (id: string) => [
      ...feedKeys.insights.detail(id),
      id,
      'liveStraemStats',
    ],
    liveStreamViewerReports: (id: string, type: AudienceTypes, query = '') => [
      ...feedKeys.insights.detail(id),
      id,
      'liveStreamViewerReports',
      type,
      query,
    ],
  },
  translate: {
    all: () => [...feedKeys.all(), 'translate'],
    detail: (id: number | string) => [
      ...feedKeys.translate.all(),
      'detail',
      Number(id),
    ],
  },
  pendingApproval: {
    all: () => [...feedKeys.all(), 'pendingApproval'],
    postList: (params = {}) => [
      ...feedKeys.pendingApproval.all(),
      'postList',
      params,
    ],
  },
  keyUpdates: {
    all: () => [...feedKeys.all(), 'keyUpdates'],
    unread: () => [...feedKeys.keyUpdates.all(), 'unread'],
    postList: (unreadKeyUpdates: boolean) => [
      ...feedKeys.keyUpdates.all(),
      'list',
      unreadKeyUpdates,
    ],
    preview: () => [...feedKeys.keyUpdates.all(), 'preview'],
  },
  poll: {
    all: () => [...feedKeys.all(), 'poll'],
    voters: (postId: number, pollId: number, pollOptionId: number) => [
      ...feedKeys.poll.all(),
      'voters',
      postId,
      pollId,
      pollOptionId,
    ],
    livestream: (livestreamId: string) => [
      ...feedKeys.poll.all(),
      'livestream',
      livestreamId,
    ],
  },
};

export const dropFeedListData = (postItemId: number) => {
  queryClient.setQueryData<
    InfiniteData<AxiosResponse<CursorPagination<Post[]>>> | undefined
  >(feedKeys.list(), oldData => {
    if (!oldData) return oldData;

    const [pageIndex, postIndex] = findPostIndexOnPages(
      postItemId,
      oldData.pages,
    );

    if (pageIndex === -1 || postIndex === -1) return oldData;

    oldData.pages[pageIndex].data.items.splice(postIndex, 1);
    return { ...oldData, _updated: Date.now() };
  });
};

//// **** ADD DATA POSTS **** ////
export const pushFeedListData = (postItem: Post) => {
  queryClient.setQueryData<
    InfiniteData<AxiosResponse<CursorPagination<Post[]>>> | undefined
  >(feedKeys.list(), oldData => {
    if (!oldData?.pages?.[0]?.data?.items) return oldData;

    const newPost = { ...postItem, commentCount: postItem.commentCount || 0 };
    oldData.pages[0].data.items.splice(0, 0, newPost);
    return { ...oldData, _updated: Date.now() };
  });
};

export const setFeedListData = (postItemId: number, updater: Updater<Post>) => {
  queryClient.setQueryData<
    InfiniteData<AxiosResponse<CursorPagination<Post[]>>> | undefined
  >(feedKeys.list(), oldData => {
    if (!oldData) return oldData;

    const [pageIndex, postIndex] = findPostIndexOnPages(
      postItemId,
      oldData.pages,
    );

    if (pageIndex === -1 || postIndex === -1) return oldData;

    oldData.pages[pageIndex].data.items[postIndex] = functionalUpdate(
      updater,
      oldData.pages[pageIndex].data.items[postIndex],
    );
    return { ...oldData, _updated: Date.now() };
  });
};

// TODO: Refactor to use immutable updates (return new object references)
// CAUTION: Test thoroughly after changing - mutations may cause subtle UI bugs
export const setSearchPostListData = (
  postItemId: number,
  query: string,
  updater: Updater<Post>,
) => {
  queryClient.setQueryData(feedKeys.search(query), (oldData: any): any => {
    if (!oldData) return oldData;

    const [pageIndex, postIndex] = findPostIndexOnPages(
      postItemId,
      oldData.pages,
    );

    if (pageIndex !== -1 && postIndex !== -1) {
      oldData.pages[pageIndex].data[postIndex] = functionalUpdate(
        updater,
        oldData.pages[pageIndex].data[postIndex],
      );
    }

    return oldData;
  });
};

// TODO: Refactor to use immutable updates (return new object references)
// CAUTION: Test thoroughly after changing - mutations may cause subtle UI bugs
export const setAllSearchPostListData = (
  postItemId: number,
  updater: Updater<Post>,
) => {
  queryClient.setQueriesData(feedKeys.allSearch(), (oldData: any): any => {
    if (!oldData) return oldData;

    const [pageIndex, postIndex] = findPostIndexOnPages(
      postItemId,
      oldData.pages,
    );

    if (pageIndex !== -1 && postIndex !== -1) {
      oldData.pages[pageIndex].data[postIndex] = functionalUpdate(
        updater,
        oldData.pages[pageIndex].data[postIndex],
      );
    }

    return oldData;
  });
};

export const setFeedDetailData = (
  postItemId: number,
  updater: Updater<Post>,
) => {
  queryClient.setQueryData<AxiosResponse<Post> | undefined>(
    feedKeys.detail(postItemId),
    oldData => {
      if (!oldData) return oldData;

      oldData.data = functionalUpdate(updater, oldData.data);

      return oldData;
    },
  );
};

export const setPinPostData = (postId: number, updater: Updater<Post>) => {
  queryClient.setQueryData<
    InfiniteData<AxiosResponse<CursorPagination<Post[]>>> | undefined
  >(feedKeys.pinned(), oldData => {
    if (!oldData) return oldData;

    const [pageIndex, postIndex] = findPostIndexOnPages(postId, oldData.pages);

    if (pageIndex === -1 || postIndex === -1) return oldData;

    oldData.pages[pageIndex].data.items[postIndex] = functionalUpdate(
      updater,
      oldData.pages[pageIndex].data.items[postIndex],
    );
    return { ...oldData, _updated: Date.now() };
  });
};

export const addFeedAndPinListDataReaction = (
  postItemId: number,
  emoji: string,
  unified: string,
  isLoggedUserReaction: boolean = true,
) => {
  setFeedListData(postItemId, oldPost => {
    if (!oldPost) return;

    oldPost.reactions = addReaction(
      oldPost.reactions,
      emoji,
      unified,
      isLoggedUserReaction,
    );

    sortReactions(oldPost.reactions, DEFAULT_EMOJI.emoji);

    invalidateReactionUserList(postItemId, emoji);

    return oldPost;
  });
  setPinPostData(postItemId, oldPost => {
    if (!oldPost) return oldPost;
    oldPost.reactions = addReaction(oldPost.reactions, emoji, unified);
    return oldPost;
  });
};

export const removeFeedAndPinListDataReaction = (
  postItemId: number,
  emoji: string,
  isLoggedUserReaction: boolean = true,
) => {
  setFeedListData(postItemId, oldPost => {
    if (!oldPost) return;

    oldPost.reactions = removeReaction(
      oldPost.reactions,
      emoji,
      isLoggedUserReaction,
    );

    sortReactions(oldPost.reactions, DEFAULT_EMOJI.emoji);

    invalidateReactionUserList(postItemId, emoji);

    return oldPost;
  });
  setPinPostData(postItemId, oldPost => {
    if (!oldPost) return oldPost;
    oldPost.reactions = removeReaction(oldPost.reactions, emoji);
    return oldPost;
  });
};

export const reactionUpdater = (
  reactions: Reaction[],
  updater: Updater<Reaction[]>,
) => {
  if (!reactions) return reactions;

  reactions = functionalUpdate(updater, reactions);

  sortReactions(reactions, DEFAULT_EMOJI.emoji);

  return reactions;
};

export const updateSearchPostListDataReaction = (
  postItemId: number,
  query: string,
  updater: Updater<Reaction[]>,
) => {
  setSearchPostListData(postItemId, query, oldPost => {
    if (!oldPost) return;

    oldPost.reactions = reactionUpdater(oldPost.reactions, updater);

    return oldPost;
  });
};

export const addFeedDetailDataReaction = (
  postItemId: number,
  emoji: string,
  unified: string,
  isLoggedUserReaction: boolean = true,
) => {
  setFeedDetailData(postItemId, oldPost => {
    if (!oldPost) return;

    oldPost.reactions = addReaction(
      oldPost.reactions,
      emoji,
      unified,
      isLoggedUserReaction,
    );

    sortReactions(oldPost.reactions, DEFAULT_EMOJI.emoji);

    return oldPost;
  });
};

export const removeFeedDetailDataReaction = (
  postItemId: number,
  emoji: string,
  isLoggedUserReaction: boolean = true,
) => {
  setFeedDetailData(postItemId, oldPost => {
    if (!oldPost) return;

    oldPost.reactions = removeReaction(
      oldPost.reactions,
      emoji,
      isLoggedUserReaction,
    );

    sortReactions(oldPost.reactions, DEFAULT_EMOJI.emoji);

    return oldPost;
  });
};

// TODO: Refactor to use immutable updates (return new object references)
// CAUTION: Test thoroughly after changing - mutations may cause subtle UI bugs
export const addPinnedPost = (post: Post) => {
  queryClient.setQueryData<
    InfiniteData<AxiosResponse<CursorPagination<Post[]>>> | undefined
  >(feedKeys.pinned(), oldData => {
    if (!oldData) return oldData;

    // Check if post already exists to avoid duplicates
    const [pageIndex, postIndex] = findPostIndexOnPages(post.id, oldData.pages);

    if (pageIndex === -1 && postIndex === -1) {
      // Append the new pinned post to the first page
      post.commentCount = post.commentCount || 0;
      oldData.pages[0]?.data?.items?.splice(0, 0, post);
    }

    return oldData;
  });
};

// TODO: Refactor to use immutable updates (return new object references)
// CAUTION: Test thoroughly after changing - mutations may cause subtle UI bugs
export const removePinnedPost = (postId?: number) => {
  queryClient.setQueryData<
    InfiniteData<AxiosResponse<CursorPagination<Post[]>>> | undefined
  >(feedKeys.pinned(), oldData => {
    if (!oldData) return oldData;

    if (postId) {
      // Remove specific post from pinned posts
      const [pageIndex, postIndex] = findPostIndexOnPages(
        postId,
        oldData.pages,
      );

      if (pageIndex !== -1 && postIndex !== -1) {
        oldData.pages[pageIndex].data.items.splice(postIndex, 1);
      }
    } else {
      // If no postId provided, clear all (backward compatibility)
      oldData.pages = [];
    }

    return oldData;
  });
};

// TODO: Refactor to use immutable updates (return new object references)
// CAUTION: Test thoroughly after changing - mutations may cause subtle UI bugs
export const removePost = (postId: number) => {
  queryClient.setQueryData<
    InfiniteData<AxiosResponse<CursorPagination<Post[]>>> | undefined
  >(feedKeys.list(), oldData => {
    if (!oldData?.pages?.length) return oldData;

    const [pageIndex, postIndex] = findPostIndexOnPages(postId, oldData.pages);

    if (pageIndex !== -1 && postIndex !== -1) {
      oldData.pages[pageIndex].data.items.splice(postIndex, 1);
    }

    return oldData;
  });
};

export const isPostInFeedCache = (postId: number): boolean => {
  const feedData = queryClient.getQueryData<
    InfiniteData<AxiosResponse<CursorPagination<Post[]>>> | undefined
  >(feedKeys.list());
  if (!feedData) return false;
  const [pageIndex] = findPostIndexOnPages(postId, feedData.pages);
  return pageIndex !== -1;
};

export const refetchAllPosts = () =>
  queryClient.invalidateQueries(feedKeys.list());

export const invalidateFeedScheduledPostList = () => {
  queryClient.invalidateQueries(feedKeys.scheduled());
};

export const postCreated = () => {
  queryClient.invalidateQueries(feedKeys.list());
  queryClient.invalidateQueries(feedKeys.scheduled());
  queryClient.invalidateQueries(feedKeys.allSearch());
};

export const postUpdated = (id: number) => {
  queryClient.invalidateQueries(feedKeys.list());
  queryClient.invalidateQueries(feedKeys.scheduled());
  queryClient.invalidateQueries(feedKeys.detail(id));
  queryClient.invalidateQueries(feedKeys.segmentate.all());
  queryClient.invalidateQueries(feedKeys.pinned());
  queryClient.invalidateQueries(feedKeys.allSearch());
  queryClient.invalidateQueries(sharedPostKeys.feed.detail(id));
};

export const postDeleted = () => {
  queryClient.invalidateQueries(feedKeys.list());
  queryClient.invalidateQueries(feedKeys.scheduled());
  queryClient.invalidateQueries(feedKeys.pinned());
  queryClient.invalidateQueries(feedKeys.allSearch());
  queryClient.invalidateQueries(profileKeys.feed.all());
};

export const refetchAllTranslations = () => {
  queryClient.invalidateQueries(feedKeys.translate.all());
};

export const refetchAllPendingApprovalPostRequests = () => {
  invalidateFeedPendingApprovalPostList();
};

export const addVoteInPoll = (
  id: number,
  pollOptionId: number,
  isLoggedUserAnswer?: boolean,
) => {
  queryClient.setQueryData<
    InfiniteData<AxiosResponse<CursorPagination<Post[]>>> | undefined
  >(feedKeys.list(), oldData => {
    if (!oldData) return oldData;
    const post = findPostOnPages(id, oldData.pages);
    if (!post) return oldData;
    addVote(post, pollOptionId, isLoggedUserAnswer);
    return oldData;
  });
};

// COMMENTS //
export const increasePostCommentAmount = (postItemId: number) => {
  setFeedListData(postItemId, oldPost => {
    if (!oldPost) return;

    return { ...oldPost, commentCount: oldPost.commentCount + 1 };
  });
};

export const disableOrEnableCommentsFeedListData = (
  postItemId: number,
  commentsEnabled: boolean,
) => {
  queryClient.setQueryData<
    InfiniteData<AxiosResponse<CursorPagination<Post[]>>> | undefined
  >(feedKeys.list(), oldData => {
    if (!oldData) return oldData;
    const [pageIndex, postIndex] = findPostIndexOnPages(
      postItemId,
      oldData.pages,
    );

    if (pageIndex !== -1 && postIndex !== -1) {
      oldData.pages[pageIndex].data.items[postIndex].commentsEnabled =
        commentsEnabled;
    }

    return oldData;
  });
};

// SET COMMENTS //
// TODO: Refactor to use immutable updates (return new object references)
// CAUTION: Test thoroughly after changing - mutations may cause subtle UI bugs
export const setCommentListData = (
  postItemId: number,
  commentId: number,
  updater: Updater<Comment>,
) => {
  queryClient.setQueryData<InfiniteData<AxiosResponse<Comment[]>> | undefined>(
    feedKeys.comment.detail(postItemId),
    oldData => {
      if (!oldData) return oldData;

      const [pageIndex, childIndex] = findIndexOnPages<Comment>(
        oldData.pages,
        comment => comment.id === commentId,
      );

      if (pageIndex !== -1 && childIndex !== -1) {
        oldData.pages[pageIndex].data[childIndex] = functionalUpdate(
          updater,
          oldData.pages[pageIndex].data[childIndex],
        );
      }

      return oldData;
    },
  );
};

export const setLastCommentDataFeedList = (
  postId: number,
  commentId: number,
  updater: Updater<Comment>,
) => {
  setFeedListData(postId, oldPost =>
    lastCommentUpdater(oldPost, commentId, updater),
  );
};

export const setLastCommentDataPinPost = (
  postId: number,
  commentId: number,
  updater: Updater<Comment>,
) => {
  setPinPostData(postId, oldPost =>
    lastCommentUpdater(oldPost, commentId, updater),
  );
};

export const setLastCommentDataPostDetail = (
  postId: number,
  commentId: number,
  updater: Updater<Comment>,
) => {
  setFeedDetailData(postId, oldPost =>
    lastCommentUpdater(oldPost, commentId, updater),
  );
};

export const setLastCommentDataAllSearchList = (
  postId: number,
  commentId: number,
  updater: Updater<Comment>,
) => {
  setAllSearchPostListData(postId, oldPost =>
    lastCommentUpdater(oldPost, commentId, updater),
  );
};

export const setLastCommentDataKeyUpdatesList = (
  postId: number,
  commentId: number,
  updater: Updater<Comment>,
) => {
  setKeyUpdatesListData(postId, oldPost =>
    lastCommentUpdater(oldPost, commentId, updater),
  );
};

// UPDATE COMMENTS //
export const reactionCommentUpdater = (
  oldComment: Comment,
  updater: Updater<Reaction[]>,
) => {
  if (!oldComment) return oldComment;

  oldComment.reactions = functionalUpdate(updater, oldComment.reactions);

  sortReactions(oldComment.reactions, DEFAULT_EMOJI.emoji);

  return oldComment;
};

export const updateReactionCommentData = (
  postId: number,
  commentId: number,
  updater: Updater<Reaction[]>,
) => {
  setLastCommentDataFeedList(postId, commentId, oldComment =>
    oldComment ? reactionCommentUpdater(oldComment, updater) : oldComment,
  );
  setLastCommentDataPinPost(postId, commentId, oldComment =>
    oldComment ? reactionCommentUpdater(oldComment, updater) : oldComment,
  );
  setLastCommentDataPostDetail(postId, commentId, oldComment =>
    oldComment ? reactionCommentUpdater(oldComment, updater) : oldComment,
  );
  setLastCommentDataAllSearchList(postId, commentId, oldComment =>
    oldComment ? reactionCommentUpdater(oldComment, updater) : oldComment,
  );
  setCommentListData(postId, commentId, oldComment =>
    oldComment ? reactionCommentUpdater(oldComment, updater) : oldComment,
  );
};

export const updateComment = (postId: number, comment: Comment) => {
  setCommentListData(postId, comment.id, comment);
  setLastCommentDataFeedList(postId, comment.id, comment);
  setLastCommentDataPinPost(postId, comment.id, comment);
  setLastCommentDataPostDetail(postId, comment.id, comment);
  setLastCommentDataAllSearchList(postId, comment.id, comment);
  queryClient.invalidateQueries(feedKeys.comment.detail(postId));
  queryClient.invalidateQueries(feedKeys.keyUpdates.all());
};

// DELETE COMMENTS //
// TODO: Refactor to use immutable updates (return new object references)
// CAUTION: Test thoroughly after changing - mutations may cause subtle UI bugs
const dropCommentUpdater = (oldPost: Post, commentId: number) => {
  if (!oldPost?.lastComments) return oldPost;

  const commentIndex = oldPost.lastComments.findIndex(
    comment => comment.id === commentId,
  );

  if (commentIndex !== -1) {
    oldPost.commentCount = decreaseCommentCount(oldPost.commentCount);
    oldPost.lastComments.splice(commentIndex, 1);
  }

  return oldPost;
};

export const dropComment = (postId: number, commentId: number) => {
  setPinPostData(postId, oldPost =>
    oldPost ? dropCommentUpdater(oldPost, commentId) : oldPost,
  );
  setFeedListData(postId, oldPost =>
    oldPost ? dropCommentUpdater(oldPost, commentId) : oldPost,
  );
  setFeedDetailData(postId, oldPost =>
    oldPost ? dropCommentUpdater(oldPost, commentId) : oldPost,
  );
  setAllSearchPostListData(postId, oldPost =>
    oldPost ? dropCommentUpdater(oldPost, commentId) : oldPost,
  );
  queryClient.invalidateQueries(feedKeys.comment.detail(postId));
  queryClient.invalidateQueries(feedKeys.keyUpdates.all());

  dropCommentDataCommentList(postId, commentId);
  dropCommentDataChildCommentList(commentId);
};

// TODO: Refactor to use immutable updates (return new object references)
// CAUTION: Test thoroughly after changing - mutations may cause subtle UI bugs
export const dropCommentDataCommentList = (
  postId: number,
  commentId: number,
) => {
  queryClient.setQueryData<InfiniteData<AxiosResponse<Comment[]>> | undefined>(
    feedKeys.comment.detail(postId),
    oldData => {
      if (!oldData) return oldData;

      const [pageIndex, commentIndex] = findIndexOnPages<Comment>(
        oldData.pages,
        comment => comment.id === commentId,
      );

      if (pageIndex !== -1 && commentIndex !== -1) {
        oldData.pages[pageIndex].data.splice(commentIndex, 1);
      }

      return oldData;
    },
  );
};

export const dropCommentDataChildCommentList = (commentId: number) => {
  queryClient.removeQueries(feedKeys.childComment.detail(commentId));
};

export const unshiftComment = (postId: number, comment: Comment) => {
  setPinPostData(postId, oldPost => unshiftCommentUpdater(oldPost, comment));
  setAllSearchPostListData(postId, oldPost =>
    unshiftCommentUpdater(oldPost, comment),
  );
  setFeedDetailData(postId, oldPost => unshiftCommentUpdater(oldPost, comment));
  setFeedListData(postId, oldPost => unshiftCommentUpdater(oldPost, comment));
  unshiftCommentDataCommentList(postId, comment);
  queryClient.invalidateQueries(feedKeys.keyUpdates.postList(false));
  queryClient.invalidateQueries(feedKeys.keyUpdates.postList(true));
};

export const unshiftCommentDataCommentList = (
  postId: number,
  comment: Comment,
) => {
  queryClient.setQueryData<InfiniteData<AxiosResponse<Comment[]>> | undefined>(
    feedKeys.comment.detail(postId),
    oldData => {
      if (!oldData?.pages) return oldData;

      if (!includesComment(oldData.pages[0].data, comment)) {
        oldData.pages[0].data.unshift(comment);
      }

      return oldData;
    },
  );
  queryClient.invalidateQueries(feedKeys.comment.liveStream(postId));
};

// SET CHILD COMMENTS
export const childCommentUpdater = (
  oldComment: Comment,
  childId: number,
  updater: Updater<Comment>,
) => {
  if (!oldComment?.children) return oldComment;

  const childIndex = oldComment.children.findIndex(
    child => child.id === childId,
  );

  if (childIndex !== -1) {
    oldComment.children[childIndex] = functionalUpdate(
      updater,
      oldComment.children[childIndex],
    );
  }

  return oldComment;
};

export const setChildLastCommentDataFeedList = (
  postId: number,
  commentId: number,
  childId: number,
  updater: Updater<Comment>,
) => {
  setLastCommentDataFeedList(postId, commentId, oldComment =>
    oldComment ? childCommentUpdater(oldComment, childId, updater) : oldComment,
  );
};

export const setChildLastCommentDataPostDetail = (
  postId: number,
  commentId: number,
  childId: number,
  updater: Updater<Comment>,
) => {
  setLastCommentDataPostDetail(postId, commentId, oldComment =>
    oldComment ? childCommentUpdater(oldComment, childId, updater) : oldComment,
  );
};

export const setChildLastCommentDataPinPost = (
  postId: number,
  commentId: number,
  childId: number,
  updater: Updater<Comment>,
) => {
  setLastCommentDataPinPost(postId, commentId, oldComment =>
    oldComment ? childCommentUpdater(oldComment, childId, updater) : oldComment,
  );
};

export const setChildLastCommentDataAllSearchList = (
  postId: number,
  commentId: number,
  childId: number,
  updater: Updater<Comment>,
) => {
  setLastCommentDataAllSearchList(postId, commentId, oldComment =>
    oldComment ? childCommentUpdater(oldComment, childId, updater) : oldComment,
  );
};

export const setChildCommentDataCommentList = (
  postId: number,
  commentId: number,
  childId: number,
  updater: Updater<Comment>,
) => {
  setCommentListData(postId, commentId, oldComment =>
    oldComment ? childCommentUpdater(oldComment, childId, updater) : oldComment,
  );
};

// TODO: Refactor to use immutable updates (return new object references)
// CAUTION: Test thoroughly after changing - mutations may cause subtle UI bugs
export const setChildCommentDataChildCommentList = (
  commentId: number,
  childId: number,
  updater: Updater<Comment>,
) => {
  queryClient.setQueryData(
    feedKeys.childComment.detail(commentId),
    (oldData: any) => {
      if (!oldData) return oldData;

      const [pageIndex, childIndex] = findIndexOnPages<Comment>(
        oldData.pages,
        child => child.id === childId,
      );

      if (pageIndex !== -1 && childIndex !== -1) {
        oldData.pages[pageIndex].data[childIndex] = functionalUpdate(
          updater,
          oldData.pages[pageIndex].data[childIndex],
        );
      }

      return oldData;
    },
  );
};

// UPDATE CHILD COMMENTS
export const updateChildLastCommentDataPostDetail = (
  postId: number,
  commentId: number,
  commentChildId: number,
  commentBody: string,
  commentAttachments: any[],
  commentBodyAttributes: any,
) => {
  queryClient.setQueryData(feedKeys.detail(postId), (oldData: any): any => {
    if (!oldData) return oldData;

    const indexCommentAux = oldData.data.lastComments?.findIndex(
      (elem: Comment) => elem.id === commentId,
    );

    const indexChildCommentAux = oldData.data?.lastComments[
      indexCommentAux
    ]?.children.findIndex((elem: Comment) => elem.id === commentChildId);

    oldData.data.lastComments[indexCommentAux].children[
      indexChildCommentAux
    ].body = commentBody;
    oldData.data.lastComments[indexCommentAux].children[
      indexChildCommentAux
    ].attachments = commentAttachments;
    oldData.data.lastComments[indexCommentAux].children[
      indexChildCommentAux
    ].bodyAttributes = commentBodyAttributes;

    return oldData;
  });
};

export const updateReactionChildCommentData = (
  postId: number,
  commentId: number,
  childId: number,
  updater: Updater<Reaction[]>,
) => {
  setChildCommentDataCommentList(postId, commentId, childId, oldComment =>
    oldComment ? reactionCommentUpdater(oldComment, updater) : oldComment,
  );
  setChildCommentDataChildCommentList(commentId, childId, oldComment =>
    oldComment ? reactionCommentUpdater(oldComment, updater) : oldComment,
  );
  setChildLastCommentDataFeedList(postId, commentId, childId, oldComment =>
    oldComment ? reactionCommentUpdater(oldComment, updater) : oldComment,
  );
  setChildLastCommentDataPinPost(postId, commentId, childId, oldComment =>
    oldComment ? reactionCommentUpdater(oldComment, updater) : oldComment,
  );
  setChildLastCommentDataPostDetail(postId, commentId, childId, oldComment =>
    oldComment ? reactionCommentUpdater(oldComment, updater) : oldComment,
  );
  setChildLastCommentDataAllSearchList(
    postId,
    commentId,
    childId,
    oldComment =>
      oldComment ? reactionCommentUpdater(oldComment, updater) : oldComment,
  );
};

export const updateChildComment = (
  postId: number,
  parentId: number,
  comment: Comment,
) => {
  setChildCommentDataCommentList(postId, parentId, comment.id, comment);
  setChildCommentDataChildCommentList(parentId, comment.id, comment);
  setChildLastCommentDataFeedList(postId, parentId, comment.id, comment);
  setChildLastCommentDataPinPost(postId, parentId, comment.id, comment);
  setChildLastCommentDataPostDetail(postId, parentId, comment.id, comment);
  setChildLastCommentDataAllSearchList(postId, parentId, comment.id, comment);
};

export const childLastCommentListDataReactionPostDetail = (
  postId: number,
  commentId: number,
  commentChildId: number,
  emoji: string,
  reactionFN: Function,
  isLoggedUserReaction: boolean = true,
) => {
  queryClient.setQueryData(feedKeys.detail(postId), (oldData: any): any => {
    if (!oldData) return oldData;

    const indexCommentAux = oldData.data.lastComments.findIndex(
      (elem: Comment) => elem.id === commentId,
    );

    const indexChildCommentAux = oldData.data.lastComments[
      indexCommentAux
    ].children.findIndex((elem: Comment) => elem.id === commentChildId);

    oldData.data.lastComments[indexCommentAux].children[
      indexChildCommentAux
    ].reactions = reactionFN(
      oldData.data.lastComments[indexCommentAux].children[indexChildCommentAux]
        .reactions,
      emoji,
      isLoggedUserReaction,
    );

    sortReactions(
      oldData.data.lastComments[indexCommentAux].children[indexChildCommentAux]
        .reactions,
      DEFAULT_EMOJI.emoji,
    );

    return oldData;
  });
};

// DELETE CHILD COMMENT //
// TODO: Refactor to use immutable updates (return new object references)
// CAUTION: Test thoroughly after changing - mutations may cause subtle UI bugs
const dropChildCommentUpdater = (
  oldComment: Comment,
  parentId: number,
  childId: number,
) => {
  if (!oldComment?.children) return oldComment;

  const childIndex = oldComment.children.findIndex(
    child => child.id === childId,
  );

  if (childIndex !== -1) {
    oldComment.childrenNumber = decreaseCommentCount(oldComment.childrenNumber);
    oldComment.children.splice(childIndex, 1);
  }

  return oldComment;
};

export const dropChildComment = (
  postId: number,
  parentId: number,
  childId: number,
) => {
  setLastCommentDataFeedList(postId, parentId, oldComment =>
    oldComment
      ? dropChildCommentUpdater(oldComment, parentId, childId)
      : oldComment,
  );
  setLastCommentDataPostDetail(postId, parentId, oldComment =>
    oldComment
      ? dropChildCommentUpdater(oldComment, parentId, childId)
      : oldComment,
  );
  setCommentListData(postId, parentId, oldComment =>
    oldComment
      ? dropChildCommentUpdater(oldComment, parentId, childId)
      : oldComment,
  );
  setLastCommentDataPinPost(postId, parentId, oldComment =>
    oldComment
      ? dropChildCommentUpdater(oldComment, parentId, childId)
      : oldComment,
  );
  setLastCommentDataAllSearchList(postId, parentId, oldComment =>
    oldComment
      ? dropChildCommentUpdater(oldComment, parentId, childId)
      : oldComment,
  );

  dropChildCommentDataChildCommentList(parentId, childId);
};

// TODO: Refactor to use immutable updates (return new object references)
// CAUTION: Test thoroughly after changing - mutations may cause subtle UI bugs
export const dropChildCommentDataChildCommentList = (
  commentId: number,
  childId: number,
) => {
  queryClient.setQueryData(
    feedKeys.childComment.detail(commentId),
    (oldData: any) => {
      if (!oldData) return oldData;

      const [pageIndex, childIndex] = findIndexOnPages<Comment>(
        oldData.pages,
        child => child.id === childId,
      );

      if (pageIndex !== -1 && childIndex !== -1) {
        oldData.pages[pageIndex].data.splice(childIndex, 1);
      }

      return oldData;
    },
  );
};

// ADD CHILD COMMENT //

export const unshiftChildComment = (
  postId: number,
  parentId: number,
  childComment: Comment,
) => {
  setCommentListData(postId, parentId, oldComment =>
    unshiftChildUpdater(oldComment, childComment),
  );
  setLastCommentDataPinPost(postId, parentId, oldComment =>
    unshiftChildUpdater(oldComment, childComment),
  );
  setLastCommentDataAllSearchList(postId, parentId, oldComment =>
    unshiftChildUpdater(oldComment, childComment),
  );
  setLastCommentDataPostDetail(postId, parentId, oldComment =>
    unshiftChildUpdater(oldComment, childComment),
  );
  setLastCommentDataFeedList(postId, parentId, oldComment =>
    unshiftChildUpdater(oldComment, childComment),
  );
  setLastCommentDataKeyUpdatesList(postId, parentId, oldComment =>
    unshiftChildUpdater(oldComment, childComment),
  );
  unshiftChildCommentDataChildCommentList(parentId, childComment);
};

export const unshiftChildCommentDataChildCommentList = (
  commentId: number,
  childComment: Comment,
) => {
  queryClient.setQueryData(
    feedKeys.childComment.detail(commentId),
    (oldData: any) => {
      if (!oldData?.pages) return oldData;

      if (!includesComment(oldData.pages[0].data, childComment)) {
        oldData.pages[0].data.unshift(childComment);
      }

      return oldData;
    },
  );
};

export const invalidateFeedPendingApprovalPostList = () =>
  queryClient.invalidateQueries(feedKeys.pendingApproval.postList());

export const invalidateFeedPostDetail = (postId: number | string) =>
  queryClient.invalidateQueries(feedKeys.detail(postId));

export const invalidateFeedSearchPosts = () => {
  queryClient.invalidateQueries(feedKeys.allSearch());
};

// TODO: Refactor to use immutable updates (return new object references)
// CAUTION: Test thoroughly after changing - mutations may cause subtle UI bugs
export const setKeyUpdatesListData = (
  postItemId: number,
  updater: Updater<Post>,
) => {
  [true, false].forEach(unreadKeyUpdates => {
    queryClient.setQueryData<
      InfiniteData<AxiosResponse<CursorPagination<Post[]>>> | undefined
    >(feedKeys.keyUpdates.postList(unreadKeyUpdates), oldData => {
      if (!oldData) return oldData;

      const [pageIndex, postIndex] = findPostIndexOnPages(
        postItemId,
        oldData.pages,
      );

      if (pageIndex !== -1 && postIndex !== -1) {
        oldData.pages[pageIndex].data.items[postIndex] = functionalUpdate(
          updater,
          oldData.pages[pageIndex].data.items[postIndex],
        );
      }

      return oldData;
    });
  });
};

export const addKeyUpdatesListDataReaction = (
  postItemId: number,
  emoji: string,
  unified: string,
  isLoggedUserReaction: boolean = true,
) => {
  setKeyUpdatesListData(postItemId, oldPost => {
    if (!oldPost) return;

    oldPost.reactions = addReaction(
      oldPost.reactions,
      emoji,
      unified,
      isLoggedUserReaction,
    );

    sortReactions(oldPost.reactions, DEFAULT_EMOJI.emoji);

    invalidateReactionUserList(postItemId, emoji);

    return oldPost;
  });
};

export const removeKeyUpdatesListDataReaction = (
  postItemId: number,
  emoji: string,
  isLoggedUserReaction: boolean = true,
) => {
  setKeyUpdatesListData(postItemId, oldPost => {
    if (!oldPost) return;

    oldPost.reactions = removeReaction(
      oldPost.reactions,
      emoji,
      isLoggedUserReaction,
    );

    sortReactions(oldPost.reactions, DEFAULT_EMOJI.emoji);

    invalidateReactionUserList(postItemId, emoji);

    return oldPost;
  });
};

export const invalidateLivestreamPollData = (livestreamId: string) => {
  queryClient.invalidateQueries(feedKeys.poll.livestream(livestreamId));
};
