// @flow
import * as ACTIONS from 'constants/action_types';
import { handleActions } from 'util/redux-utils';

const defaultState: CommentsState = {
  commentById: {}, // commentId -> Comment
  byId: {}, // ClaimID -> list of comments
  repliesByParentId: {}, // ParentCommentID -> list of reply comments
  topLevelCommentsById: {}, // ClaimID -> list of top level comments
  // TODO:
  // Remove commentsByUri
  // It is not needed and doesn't provide anything but confusion
  commentsByUri: {}, // URI -> claimId
  superChatsByUri: {},
  isLoading: false,
  isCommenting: false,
  myComments: undefined,
  isFetchingReacts: false,
  pendingCommentReactions: [],
  typesReacting: [],
  myReactsByCommentId: undefined,
  othersReactsByCommentId: undefined,
  moderationBlockList: undefined,
  fetchingModerationBlockList: false,
  blockingByUri: {},
  unBlockingByUri: {},
  settingsByChannelId: {}, // ChannelId -> PerChannelSettings
  fetchingSettings: false,
  fetchingBlockedWords: false,
};

export default handleActions(
  {
    [ACTIONS.COMMENT_CREATE_STARTED]: (state: CommentsState, action: any): CommentsState => ({
      ...state,
      isCommenting: true,
    }),

    [ACTIONS.COMMENT_CREATE_FAILED]: (state: CommentsState, action: any) => ({
      ...state,
      isCommenting: false,
    }),

    [ACTIONS.COMMENT_CREATE_COMPLETED]: (state: CommentsState, action: any): CommentsState => {
      const {
        comment,
        claimId,
        uri,
        livestream,
      }: { comment: Comment, claimId: string, uri: string, livestream: boolean } = action.data;
      const commentById = Object.assign({}, state.commentById);
      const byId = Object.assign({}, state.byId);
      const topLevelCommentsById = Object.assign({}, state.topLevelCommentsById); // was byId {ClaimId -> [commentIds...]}
      const repliesByParentId = Object.assign({}, state.repliesByParentId); // {ParentCommentID -> [commentIds...] } list of reply comments
      const commentsByUri = Object.assign({}, state.commentsByUri);
      const comments = byId[claimId] || [];
      const newCommentIds = comments.slice();

      // If it was created during a livestream, let the websocket handler perform the state update
      if (!livestream) {
        // add the comment by its ID
        commentById[comment.comment_id] = comment;

        // push the comment_id to the top of ID list
        newCommentIds.unshift(comment.comment_id);
        byId[claimId] = newCommentIds;

        if (comment['parent_id']) {
          if (!repliesByParentId[comment.parent_id]) {
            repliesByParentId[comment.parent_id] = [comment.comment_id];
          } else {
            repliesByParentId[comment.parent_id].unshift(comment.comment_id);
          }
        } else {
          if (!topLevelCommentsById[claimId]) {
            commentsByUri[uri] = claimId;
            topLevelCommentsById[claimId] = [comment.comment_id];
          } else {
            topLevelCommentsById[claimId].unshift(comment.comment_id);
          }
        }
      }

      return {
        ...state,
        topLevelCommentsById,
        repliesByParentId,
        commentById,
        byId,
        commentsByUri,
        isLoading: false,
        isCommenting: false,
      };
    },

    [ACTIONS.COMMENT_REACTION_LIST_STARTED]: (state: CommentsState, action: any): CommentsState => ({
      ...state,
      isFetchingReacts: true,
    }),

    [ACTIONS.COMMENT_REACTION_LIST_FAILED]: (state: CommentsState, action: any) => ({
      ...state,
      isFetchingReacts: false,
    }),

    [ACTIONS.COMMENT_REACT_FAILED]: (state: CommentsState, action: any): CommentsState => {
      const commentReaction = action.data; // String: reactionHash + type
      const newReactingTypes = new Set(state.pendingCommentReactions);
      newReactingTypes.delete(commentReaction);

      return {
        ...state,
        pendingCommentReactions: Array.from(newReactingTypes),
      };
    },

    [ACTIONS.COMMENT_REACT_STARTED]: (state: CommentsState, action: any): CommentsState => {
      const commentReaction = action.data;
      const newReactingTypes = new Set(state.pendingCommentReactions);
      newReactingTypes.add(commentReaction);

      return {
        ...state,
        pendingCommentReactions: Array.from(newReactingTypes),
      };
    },

    [ACTIONS.COMMENT_REACT_COMPLETED]: (state: CommentsState, action: any): CommentsState => {
      const commentReaction = action.data; // String: reactionHash + type
      const newReactingTypes = new Set(state.pendingCommentReactions);
      newReactingTypes.delete(commentReaction);

      return {
        ...state,
        pendingCommentReactions: Array.from(newReactingTypes),
      };
    },

    [ACTIONS.COMMENT_REACTION_LIST_COMPLETED]: (state: CommentsState, action: any): CommentsState => {
      const { myReactions, othersReactions } = action.data;
      const myReacts = Object.assign({}, state.myReactsByCommentId);
      const othersReacts = Object.assign({}, state.othersReactsByCommentId);
      if (myReactions) {
        Object.entries(myReactions).forEach(([commentId, reactions]) => {
          myReacts[commentId] = Object.entries(reactions).reduce((acc, [name, count]) => {
            if (count === 1) {
              acc.push(name);
            }
            return acc;
          }, []);
        });
      }
      if (othersReactions) {
        Object.entries(othersReactions).forEach(([commentId, reactions]) => {
          othersReacts[commentId] = reactions;
        });
      }

      return {
        ...state,
        isFetchingReacts: false,
        myReactsByCommentId: myReacts,
        othersReactsByCommentId: othersReacts,
      };
    },

    [ACTIONS.COMMENT_LIST_STARTED]: (state) => ({ ...state, isLoading: true }),

    [ACTIONS.COMMENT_LIST_COMPLETED]: (state: CommentsState, action: any) => {
      const { comments, claimId, uri } = action.data;

      const commentById = Object.assign({}, state.commentById);
      const byId = Object.assign({}, state.byId);
      const topLevelCommentsById = Object.assign({}, state.topLevelCommentsById); // was byId {ClaimId -> [commentIds...]}
      const commentsByUri = Object.assign({}, state.commentsByUri);

      const tempRepliesByParent = {};
      const topLevelComments = [];
      if (comments) {
        // we use an Array to preserve order of listing
        // in reality this doesn't matter and we can just
        // sort comments by their timestamp
        const commentIds = Array(comments.length);

        // map the comment_ids to the new comments
        for (let i = 0; i < comments.length; i++) {
          const comment = comments[i];
          if (comment['parent_id']) {
            if (!tempRepliesByParent[comment.parent_id]) {
              tempRepliesByParent[comment.parent_id] = [comment.comment_id];
            } else {
              tempRepliesByParent[comment.parent_id].push(comment.comment_id);
            }
          } else {
            commentById[comment.comment_id] = comment;
            topLevelComments.push(comment.comment_id);
          }
          commentIds[i] = comments[i].comment_id;
          commentById[commentIds[i]] = comments[i];
        }
        topLevelCommentsById[claimId] = topLevelComments;

        byId[claimId] = commentIds;
        commentsByUri[uri] = claimId;
      }

      const repliesByParentId = Object.assign({}, state.repliesByParentId, tempRepliesByParent); // {ParentCommentID -> [commentIds...] } list of reply comments

      return {
        ...state,
        topLevelCommentsById,
        repliesByParentId,
        byId,
        commentById,
        commentsByUri,
        isLoading: false,
      };
    },

    [ACTIONS.COMMENT_SUPER_CHAT_LIST_FAILED]: (state: CommentsState, action: any) => ({
      ...state,
      isLoading: false,
    }),
    [ACTIONS.COMMENT_SUPER_CHAT_LIST_STARTED]: (state) => ({ ...state, isLoading: true }),

    [ACTIONS.COMMENT_SUPER_CHAT_LIST_COMPLETED]: (state: CommentsState, action: any) => {
      const { comments, totalAmount, uri } = action.data;

      return {
        ...state,
        superChatsByUri: {
          ...state.superChatsByUri,
          [uri]: {
            comments,
            totalAmount,
          },
        },
        isLoading: false,
      };
    },

    [ACTIONS.COMMENT_LIST_FAILED]: (state: CommentsState, action: any) => ({
      ...state,
      isLoading: false,
    }),

    [ACTIONS.COMMENT_RECEIVED]: (state: CommentsState, action: any) => {
      const { uri, claimId, comment } = action.data;
      const commentsByUri = Object.assign({}, state.commentsByUri);
      const commentsByClaimId = Object.assign({}, state.byId);
      const allCommentsById = Object.assign({}, state.commentById);
      const topLevelCommentsById = Object.assign({}, state.topLevelCommentsById);
      const superChatsByUri = Object.assign({}, state.superChatsByUri);
      const commentsForId = topLevelCommentsById[claimId];

      allCommentsById[comment.comment_id] = comment;
      commentsByUri[uri] = claimId;

      if (commentsForId) {
        const newCommentsForId = commentsForId.slice();
        const commentExists = newCommentsForId.includes(comment.comment_id);
        if (!commentExists) {
          newCommentsForId.unshift(comment.comment_id);
        }

        topLevelCommentsById[claimId] = newCommentsForId;
      } else {
        topLevelCommentsById[claimId] = [comment.comment_id];
      }

      // We don't care to keep existing lower level comments since this is just for livestreams
      commentsByClaimId[claimId] = topLevelCommentsById[claimId];

      if (comment.support_amount > 0) {
        const superChatForUri = superChatsByUri[uri];
        const superChatCommentsForUri = superChatForUri && superChatForUri.comments;

        let sortedSuperChatComments = [];
        let hasAddedNewComment = false;
        if (superChatCommentsForUri && superChatCommentsForUri.length > 0) {
          // Go for the entire length of superChatCommentsForUri since a comment will be added to this list
          for (var i = 0; i < superChatCommentsForUri.length; i++) {
            const existingSuperChat = superChatCommentsForUri[i];
            if (existingSuperChat.support_amount < comment.support_amount && !hasAddedNewComment) {
              hasAddedNewComment = true;
              sortedSuperChatComments.push(comment);
              sortedSuperChatComments.push(existingSuperChat);
            } else {
              sortedSuperChatComments.push(existingSuperChat);
            }

            // If the new superchat hasn't been added yet, it must be the smallest superchat in the list
            if (
              i === superChatCommentsForUri.length - 1 &&
              sortedSuperChatComments.length === superChatCommentsForUri.length
            ) {
              sortedSuperChatComments.push(comment);
            }
          }

          superChatsByUri[uri].comments = sortedSuperChatComments;
          superChatsByUri[uri].totalAmount += comment.support_amount;
        } else {
          superChatsByUri[uri] = { comments: [comment], totalAmount: comment.support_amount };
        }
      }

      return {
        ...state,
        byId: commentsByClaimId,
        commentById: allCommentsById,
        commentsByUri,
        topLevelCommentsById,
        superChatsByUri,
      };
    },

    [ACTIONS.COMMENT_ABANDON_STARTED]: (state: CommentsState, action: any) => ({
      ...state,
      isLoading: true,
    }),
    [ACTIONS.COMMENT_ABANDON_COMPLETED]: (state: CommentsState, action: any) => {
      const { comment_id } = action.data;
      const commentById = Object.assign({}, state.commentById);
      const byId = Object.assign({}, state.byId);

      // to remove the comment and its references
      const claimId = commentById[comment_id].claim_id;
      for (let i = 0; i < byId[claimId].length; i++) {
        if (byId[claimId][i] === comment_id) {
          byId[claimId].splice(i, 1);
          break;
        }
      }
      delete commentById[comment_id];

      return {
        ...state,
        commentById,
        byId,
        isLoading: false,
      };
    },

    [ACTIONS.COMMENT_ABANDON_FAILED]: (state: CommentsState, action: any) => ({
      ...state,
      isCommenting: false,
    }),
    [ACTIONS.COMMENT_UPDATE_STARTED]: (state: CommentsState, action: any) => ({
      ...state,
      isCommenting: true,
    }),
    [ACTIONS.COMMENT_UPDATE_COMPLETED]: (state: CommentsState, action: any) => {
      const { comment } = action.data;
      const commentById = Object.assign({}, state.commentById);
      commentById[comment.comment_id] = comment;

      return {
        ...state,
        commentById,
        isCommenting: false,
      };
    },

    [ACTIONS.COMMENT_UPDATE_FAILED]: (state: CommentsState, action: any) => ({
      ...state,
      isCmmenting: false,
    }),
    [ACTIONS.COMMENT_MODERATION_BLOCK_LIST_STARTED]: (state: CommentsState, action: any) => ({
      ...state,
      fetchingModerationBlockList: true,
    }),
    [ACTIONS.COMMENT_MODERATION_BLOCK_LIST_COMPLETED]: (state: CommentsState, action: any) => {
      const { blockList } = action.data;

      return {
        ...state,
        moderationBlockList: blockList,
        fetchingModerationBlockList: false,
      };
    },
    [ACTIONS.COMMENT_MODERATION_BLOCK_LIST_FAILED]: (state: CommentsState, action: any) => ({
      ...state,
      fetchingModerationBlockList: false,
    }),

    [ACTIONS.COMMENT_MODERATION_BLOCK_STARTED]: (state: CommentsState, action: any) => ({
      ...state,
      blockingByUri: {
        ...state.blockingByUri,
        [action.data.uri]: true,
      },
    }),

    [ACTIONS.COMMENT_MODERATION_UN_BLOCK_STARTED]: (state: CommentsState, action: any) => ({
      ...state,
      unBlockingByUri: {
        ...state.unBlockingByUri,
        [action.data.uri]: true,
      },
    }),
    [ACTIONS.COMMENT_MODERATION_BLOCK_FAILED]: (state: CommentsState, action: any) => ({
      ...state,
      blockingByUri: {
        ...state.blockingByUri,
        [action.data.uri]: false,
      },
    }),

    [ACTIONS.COMMENT_MODERATION_UN_BLOCK_FAILED]: (state: CommentsState, action: any) => ({
      ...state,
      unBlockingByUri: {
        ...state.unBlockingByUri,
        [action.data.uri]: false,
      },
    }),

    [ACTIONS.COMMENT_MODERATION_BLOCK_COMPLETE]: (state: CommentsState, action: any) => {
      const { channelUri } = action.data;
      const commentById = Object.assign({}, state.commentById);
      const blockingByUri = Object.assign({}, state.blockingByUri);
      const moderationBlockList = state.moderationBlockList || [];
      const newModerationBlockList = moderationBlockList.slice();

      for (const commentId in commentById) {
        const comment = commentById[commentId];

        if (channelUri === comment.channel_url) {
          delete commentById[comment.comment_id];
        }
      }

      delete blockingByUri[channelUri];

      newModerationBlockList.push(channelUri);

      return {
        ...state,
        commentById,
        blockingByUri,
        moderationBlockList: newModerationBlockList,
      };
    },
    [ACTIONS.COMMENT_MODERATION_UN_BLOCK_COMPLETE]: (state: CommentsState, action: any) => {
      const { channelUri } = action.data;
      const unBlockingByUri = Object.assign(state.unBlockingByUri, {});
      const moderationBlockList = state.moderationBlockList || [];
      const newModerationBlockList = moderationBlockList.slice().filter((uri) => uri !== channelUri);

      delete unBlockingByUri[channelUri];

      return {
        ...state,
        unBlockingByUri,
        moderationBlockList: newModerationBlockList,
      };
    },

    [ACTIONS.COMMENT_FETCH_SETTINGS_STARTED]: (state: CommentsState, action: any) => ({
      ...state,
      fetchingSettings: true,
    }),
    [ACTIONS.COMMENT_FETCH_SETTINGS_FAILED]: (state: CommentsState, action: any) => ({
      ...state,
      fetchingSettings: false,
    }),
    [ACTIONS.COMMENT_FETCH_SETTINGS_COMPLETED]: (state: CommentsState, action: any) => {
      return {
        ...state,
        settingsByChannelId: action.data,
        fetchingSettings: false,
      };
    },

    [ACTIONS.COMMENT_FETCH_BLOCKED_WORDS_STARTED]: (state: CommentsState, action: any) => ({
      ...state,
      fetchingBlockedWords: true,
    }),
    [ACTIONS.COMMENT_FETCH_BLOCKED_WORDS_FAILED]: (state: CommentsState, action: any) => ({
      ...state,
      fetchingBlockedWords: false,
    }),
    [ACTIONS.COMMENT_FETCH_BLOCKED_WORDS_COMPLETED]: (state: CommentsState, action: any) => {
      const blockedWordsByChannelId = action.data;
      const settingsByChannelId = Object.assign({}, state.settingsByChannelId);

      // blockedWordsByChannelId: {string: [string]}
      Object.entries(blockedWordsByChannelId).forEach((x) => {
        const channelId = x[0];
        if (!settingsByChannelId[channelId]) {
          settingsByChannelId[channelId] = {};
        }
        settingsByChannelId[channelId].words = x[1];
      });

      return {
        ...state,
        settingsByChannelId,
        fetchingBlockedWords: false,
      };
    },
  },
  defaultState
);