text: // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
  // See LICENSE.txt for license information.
  
  import {batchActions} from 'redux-batched-actions';
  
  import {ViewTypes} from 'app/constants';
  
  import {ChannelTypes, RoleTypes, GroupTypes} from '@mm-redux/action_types';
  import {
      fetchMyChannelsAndMembers,
      getChannelByNameAndTeamName,
      joinChannel,
      leaveChannel as serviceLeaveChannel,
  } from '@mm-redux/actions/channels';
  import {savePreferences} from '@mm-redux/actions/preferences';
  import {getLicense} from '@mm-redux/selectors/entities/general';
  import {selectTeam} from '@mm-redux/actions/teams';
  import {Client4} from '@mm-redux/client';
  import {General, Preferences} from '@mm-redux/constants';
  import {getPostIdsInChannel} from '@mm-redux/selectors/entities/posts';
  import {isMinimumServerVersion} from '@mm-redux/utils/helpers';
  import {
      getCurrentChannelId,
      getRedirectChannelNameForTeam,
      getChannelsNameMapInTeam,
      getMyChannelMemberships,
      isManuallyUnread,
  } from '@mm-redux/selectors/entities/channels';
  import {getCurrentUserId} from '@mm-redux/selectors/entities/users';
  import {getTeamByName, getCurrentTeam} from '@mm-redux/selectors/entities/teams';
  
  import {getChannelByName as selectChannelByName, getChannelsIdForTeam} from '@mm-redux/utils/channel_utils';
  import EventEmitter from '@mm-redux/utils/event_emitter';
  
  import {lastChannelIdForTeam, loadSidebarDirectMessagesProfiles} from '@actions/helpers/channels';
  import {getPosts, getPostsBefore, getPostsSince, loadUnreadChannelPosts} from '@actions/views/post';
  import {INSERT_TO_COMMENT, INSERT_TO_DRAFT} from '@constants/post_draft';
  import {getChannelReachable} from '@selectors/channel';
  import telemetry from '@telemetry';
  import {isDirectChannelVisible, isGroupChannelVisible, getChannelSinceValue} from '@utils/channels';
  import {isPendingPost} from '@utils/general';
  
  const MAX_RETRIES = 3;
  
  export function loadChannelsByTeamName(teamName, errorHandler) {
      return async (dispatch, getState) => {
          const state = getState();
          const {currentTeamId} = state.entities.teams;
  
          if (teamName) {
              const team = getTeamByName(state, teamName);
  
              if (!team && errorHandler) {
                  errorHandler();
                  return {error: true};
              }
  
              if (team && team.id !== currentTeamId) {
                  await dispatch(fetchMyChannelsAndMembers(team.id));
              }
          }
  
          return {data: true};
      };
  }
  
  export function loadPostsIfNecessaryWithRetry(channelId) {
      return async (dispatch, getState) => {
          const state = getState();
          const postIds = getPostIdsInChannel(state, channelId);
          const actions = [];
  
          const time = Date.now();
  
          let loadMorePostsVisible = true;
          let postAction;
          if (!postIds || postIds.length < ViewTypes.POST_VISIBILITY_CHUNK_SIZE) {
              // Get the first page of posts if it appears we haven't gotten it yet, like the webapp
              postAction = getPosts(channelId);
          } else {
              const since = getChannelSinceValue(state, channelId, postIds);
              postAction = getPostsSince(channelId, since);
          }
  
          const received = await dispatch(fetchPostActionWithRetry(postAction));
  
          if (received) {
              actions.push({
                  type: ViewTypes.RECEIVED_POSTS_FOR_CHANNEL_AT_TIME,
                  channelId,
                  time,
              },
              setChannelRetryFailed(false));
  
              if (received?.order) {
                  const count = received.order.length;
                  loadMorePostsVisible = count >= ViewTypes.POST_VISIBILITY_CHUNK_SIZE;
              }
          }
  
          actions.push(setLoadMorePostsVisible(loadMorePostsVisible));
          dispatch(batchActions(actions, 'BATCH_LOAD_POSTS_IN_CHANNEL'));
      };
  }
  
  export function fetchPostActionWithRetry(action, maxTries = MAX_RETRIES) {
      return async (dispatch) => {
          for (let i = 0; i <= maxTries; i++) {
              const {data} = await dispatch(action); // eslint-disable-line no-await-in-loop
  
              if (data) {
                  return data;
              }
          }
  
          dispatch(setChannelRetryFailed(true));
  
          return null;
      };
  }
  
  export function selectInitialChannel(teamId) {
      return (dispatch, getState) => {
          const state = getState();
          const channelId = lastChannelIdForTeam(state, teamId);
  
          dispatch(handleSelectChannel(channelId));
      };
  }
  
  export function selectPenultimateChannel(teamId) {
      return (dispatch, getState) => {
          const state = getState();
          const {channels, myMembers} = state.entities.channels;
          const {currentUserId} = state.entities.users;
          const {myPreferences} = state.entities.preferences;
          const lastChannelForTeam = state.views.team.lastChannelForTeam[teamId];
          const lastChannelId = lastChannelForTeam && lastChannelForTeam.length > 1 ? lastChannelForTeam[1] : '';
          const lastChannel = channels[lastChannelId];
  
          const isDMVisible = lastChannel && lastChannel.type === General.DM_CHANNEL &&
              isDirectChannelVisible(currentUserId, myPreferences, lastChannel);
  
          const isGMVisible = lastChannel && lastChannel.type === General.GM_CHANNEL &&
              isGroupChannelVisible(myPreferences, lastChannel);
  
          if (
              myMembers[lastChannelId] &&
              lastChannel &&
              lastChannel.delete_at === 0 &&
              (lastChannel.team_id === teamId || isDMVisible || isGMVisible)
          ) {
              dispatch(handleSelectChannel(lastChannelId));
              return;
          }
  
          dispatch(selectDefaultChannel(teamId));
      };
  }
  
  export function selectDefaultChannel(teamId) {
      return (dispatch, getState) => {
          const state = getState();
  
          const channelsInTeam = getChannelsNameMapInTeam(state, teamId);
          const channel = selectChannelByName(channelsInTeam, getRedirectChannelNameForTeam(state, teamId));
          let channelId;
          if (channel) {
              channelId = channel.id;
          } else {
              // Handle case when the default channel cannot be found
              // so we need to get the first available channel of the team
              const channels = Object.values(channelsInTeam);
              const firstChannel = channels.length ? channels[0].id : '';
              channelId = firstChannel;
          }
  
          if (channelId) {
              dispatch(handleSelectChannel(channelId));
          }
      };
  }
  
  export function handleSelectChannel(channelId) {
      return async (dispatch, getState) => {
          const dt = Date.now();
          const state = getState();
          const {channels, currentChannelId, myMembers} = state.entities.channels;
          const {currentTeamId} = state.entities.teams;
          const channel = channels[channelId];
          const member = myMembers[channelId];
  
          if (channel) {
              dispatch(loadPostsIfNecessaryWithRetry(channelId));
  
              let previousChannelId = null;
              if (currentChannelId !== channelId) {
                  previousChannelId = currentChannelId;
              }
  
              const actions = markAsViewedAndReadBatch(state, channelId, previousChannelId);
              actions.push({
                  type: ChannelTypes.SELECT_CHANNEL,
                  data: channelId,
                  extra: {
                      channel,
                      member,
                      teamId: channel.team_id || currentTeamId,
                  },
              });
  
              dispatch(batchActions(actions, 'BATCH_SWITCH_CHANNEL'));
  
              console.log('channel switch to', channel?.display_name, channelId, (Date.now() - dt), 'ms'); //eslint-disable-line
          }
  
          return {data: true};
      };
  }
  
  export function handleSelectChannelByName(channelName, teamName, errorHandler) {
      return async (dispatch, getState) => {
          let state = getState();
          const {teams: currentTeams, currentTeamId} = state.entities.teams;
          const currentTeam = currentTeams[currentTeamId];
          const currentTeamName = currentTeam?.name;
          const response = await dispatch(getChannelByNameAndTeamName(teamName || currentTeamName, channelName));
          const {error, data: channel} = response;
          const currentChannelId = getCurrentChannelId(state);
  
          state = getState();
          const reachable = getChannelReachable(state, channelName, teamName);
  
          if (!reachable && errorHandler) {
              errorHandler();
          }
  
          // Fallback to API response error, if any.
          if (error) {
              return {error};
          }
  
          if (teamName && teamName !== currentTeamName) {
              const team = getTeamByName(state, teamName);
              dispatch(selectTeam(team));
          }
  
          if (channel && currentChannelId !== channel.id) {
              if (channel.type === General.OPEN_CHANNEL) {
                  const myMemberships = getMyChannelMemberships(state);
                  if (!myMemberships[channel.id]) {
                      const currentUserId = getCurrentUserId(state);
                      console.log('joining channel', channel?.display_name, channel.id); //eslint-disable-line
                      const result = await dispatch(joinChannel(currentUserId, '', channel.id));
                      if (result.error || !result.data || !result.data.channel) {
                          return result;
                      }
                  }
              }
              dispatch(handleSelectChannel(channel.id));
          }
  
          return {data: true};
      };
  }
  
  export function handlePostDraftChanged(channelId, draft) {
      return (dispatch, getState) => {
          const state = getState();
  
          if (state.views.channel.drafts[channelId]?.draft !== draft) {
              dispatch({
                  type: ViewTypes.POST_DRAFT_CHANGED,
                  channelId,
                  draft,
              });
          }
      };
  }
  
  export function insertToDraft(value) {
      return (dispatch, getState) => {
          const state = getState();
          const threadId = state.entities.posts.selectedPostId;
  
          const insertEvent = threadId ? INSERT_TO_COMMENT : INSERT_TO_DRAFT;
  
          EventEmitter.emit(insertEvent, value);
      };
  }
  
  export function markChannelViewedAndRead(channelId, previousChannelId, markOnServer = true) {
      return (dispatch, getState) => {
          const state = getState();
          const actions = markAsViewedAndReadBatch(state, channelId, previousChannelId, markOnServer);
  
          dispatch(batchActions(actions, 'BATCH_MARK_CHANNEL_VIEWED_AND_READ'));
  
          return {data: true};
      };
  }
  
  export function markAsViewedAndReadBatch(state, channelId, prevChannelId = '', markOnServer = true) {
      const actions = [];
      const {channels, myMembers} = state.entities.channels;
      const channel = channels[channelId];
      const member = myMembers[channelId];
      const prevMember = myMembers[prevChannelId];
      const prevChanManuallyUnread = isManuallyUnread(state, prevChannelId);
      const prevChannel = (!prevChanManuallyUnread && prevChannelId) ? channels[prevChannelId] : null; // May be null since prevChannelId is optional
  
      if (markOnServer) {
          Client4.viewMyChannel(channelId, prevChanManuallyUnread ? '' : prevChannelId).catch(() => {
              // do nothing just adding the handler to avoid the warning
          });
      }
  
      if (member) {
          actions.push({
              type: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBER,
              data: {...member, last_viewed_at: Date.now()},
          });
  
          if (isManuallyUnread(state, channelId)) {
              actions.push({
                  type: ChannelTypes.REMOVE_MANUALLY_UNREAD,
                  data: {channelId},
              });
          }
  
          if (channel) {
              const unreadMessageCount = channel.total_msg_count - member.msg_count;
              actions.push({
                  type: ChannelTypes.SET_UNREAD_MSG_COUNT,
                  data: {
                      channelId,
                      count: unreadMessageCount,
                  },
              }, {
                  type: ChannelTypes.DECREMENT_UNREAD_MSG_COUNT,
                  data: {
                      teamId: channel.team_id,
                      channelId,
                      amount: unreadMessageCount,
                  },
              }, {
                  type: ChannelTypes.DECREMENT_UNREAD_MENTION_COUNT,
                  data: {
                      teamId: channel.team_id,
                      channelId,
                      amount: member.mention_count,
                  },
              });
          }
      }
  
      if (prevMember) {
          if (!prevChanManuallyUnread) {
              actions.push({
                  type: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBER,
                  data: {...prevMember, last_viewed_at: Date.now()},
              });
          }
  
          if (prevChannel) {
              actions.push({
                  type: ChannelTypes.DECREMENT_UNREAD_MSG_COUNT,
                  data: {
                      teamId: prevChannel.team_id,
                      channelId: prevChannelId,
                      amount: prevChannel.total_msg_count - prevMember.msg_count,
                  },
              }, {
                  type: ChannelTypes.DECREMENT_UNREAD_MENTION_COUNT,
                  data: {
                      teamId: prevChannel.team_id,
                      channelId: prevChannelId,
                      amount: prevMember.mention_count,
                  },
              });
          }
      }
  
      return actions;
  }
  
  export function markChannelViewedAndReadOnReconnect(channelId) {
      return (dispatch, getState) => {
          if (isManuallyUnread(getState(), channelId)) {
              return;
          }
  
          dispatch(markChannelViewedAndRead(channelId));
      };
  }
  
  export function toggleDMChannel(otherUserId, visible, channelId) {
      return async (dispatch, getState) => {
          const state = getState();
          const {currentUserId} = state.entities.users;
  
          const dm = [{
              user_id: currentUserId,
              category: Preferences.CATEGORY_DIRECT_CHANNEL_SHOW,
              name: otherUserId,
              value: visible,
          }, {
              user_id: currentUserId,
              category: Preferences.CATEGORY_CHANNEL_OPEN_TIME,
              name: channelId,
              value: Date.now().toString(),
          }];
  
          dispatch(savePreferences(currentUserId, dm));
      };
  }
  
  export function toggleGMChannel(channelId, visible) {
      return async (dispatch, getState) => {
          const state = getState();
          const {currentUserId} = state.entities.users;
  
          const gm = [{
              user_id: currentUserId,
              category: Preferences.CATEGORY_GROUP_CHANNEL_SHOW,
              name: channelId,
              value: visible,
          }];
  
          dispatch(savePreferences(currentUserId, gm));
      };
  }
  
  export function closeDMChannel(channel) {
      return async (dispatch, getState) => {
          const state = getState();
          const currentChannelId = getCurrentChannelId(state);
  
          dispatch(toggleDMChannel(channel.teammate_id, 'false'));
          if (channel.id === currentChannelId) {
              dispatch(selectInitialChannel(state.entities.teams.currentTeamId));
          }
      };
  }
  
  export function closeGMChannel(channel) {
      return async (dispatch, getState) => {
          const state = getState();
          const currentChannelId = getCurrentChannelId(state);
  
          toggleGMChannel(channel.id, 'false')(dispatch, getState);
          if (channel.id === currentChannelId) {
              selectInitialChannel(state.entities.teams.currentTeamId)(dispatch, getState);
          }
      };
  }
  
  export function refreshChannelWithRetry(channelId) {
      return async (dispatch) => {
          dispatch(setChannelRefreshing(true));
          const posts = await dispatch(fetchPostActionWithRetry(getPosts(channelId)));
          const actions = [setChannelRefreshing(false)];
  
          if (posts) {
              actions.push(setChannelRetryFailed(false));
          }
  
          dispatch(batchActions(actions, 'BATCH_REEFRESH_CHANNEL'));
          return posts;
      };
  }
  
  export function leaveChannel(channel, reset = false) {
      return async (dispatch, getState) => {
          const state = getState();
          const {currentChannelId} = state.entities.channels;
          const {currentTeamId} = state.entities.teams;
  
          dispatch({
              type: ViewTypes.REMOVE_LAST_CHANNEL_FOR_TEAM,
              data: {
                  teamId: currentTeamId,
                  channelId: channel.id,
              },
          });
  
          if (channel.id === currentChannelId || reset) {
              await dispatch(selectDefaultChannel(currentTeamId));
          }
  
          await dispatch(serviceLeaveChannel(channel.id));
      };
  }
  
  export function setChannelLoading(loading = true) {
      if (loading) {
          telemetry.start(['channel:loading']);
      } else {
          telemetry.end(['channel:loading']);
      }
  
      return {
          type: ViewTypes.SET_CHANNEL_LOADER,
          loading,
      };
  }
  
  export function setChannelRefreshing(loading = true) {
      return {
          type: ViewTypes.SET_CHANNEL_REFRESHING,
          loading,
      };
  }
  
  export function setChannelRetryFailed(failed = true) {
      return {
          type: ViewTypes.SET_CHANNEL_RETRY_FAILED,
          failed,
      };
  }
  
  export function setChannelDisplayName(displayName) {
      return {
          type: ViewTypes.SET_CHANNEL_DISPLAY_NAME,
          displayName,
      };
  }
  
  // Returns true if there are more posts to load
  export function increasePostVisibility(channelId, postId) {
      return async (dispatch, getState) => {
          const state = getState();
          const {loadingPosts} = state.views.channel;
          const currentUserId = getCurrentUserId(state);
  
          if (loadingPosts[channelId]) {
              return true;
          }
  
          if (!postId) {
              // No posts are visible, so the channel is empty
              return true;
          }
  
          if (isPendingPost(postId, currentUserId)) {
              // This is the first created post in the channel
              return true;
          }
  
          telemetry.reset();
          telemetry.start(['posts:loading']);
  
          dispatch({
              type: ViewTypes.LOADING_POSTS,
              data: true,
              channelId,
          });
  
          const pageSize = ViewTypes.POST_VISIBILITY_CHUNK_SIZE;
  
          const postAction = getPostsBefore(channelId, postId, 0, pageSize);
          const result = await dispatch(fetchPostActionWithRetry(postAction));
  
          const actions = [{
              type: ViewTypes.LOADING_POSTS,
              data: false,
              channelId,
          }];
  
          if (result) {
              actions.push(setChannelRetryFailed(false));
          }
  
          let hasMorePost = false;
          if (result?.order) {
              const count = result.order.length;
              hasMorePost = count >= pageSize;
  
              actions.push(setLoadMorePostsVisible(hasMorePost));
          }
  
          dispatch(batchActions(actions, 'BATCH_LOAD_MORE_POSTS'));
          telemetry.end(['posts:loading']);
          telemetry.save();
  
          return hasMorePost;
      };
  }
  
  function setLoadMorePostsVisible(visible) {
      return {
          type: ViewTypes.SET_LOAD_MORE_POSTS_VISIBLE,
          data: visible,
      };
  }
  
  function loadGroupData() {
      return async (dispatch, getState) => {
          const state = getState();
          const actions = [];
          const team = getCurrentTeam(state);
          const currentUserId = getCurrentUserId(state);
          const serverVersion = state.entities.general.serverVersion;
          const license = getLicense(state);
          const hasLicense = license?.IsLicensed === 'true' && license?.LDAPGroups === 'true';
  
          if (hasLicense && team && isMinimumServerVersion(serverVersion, 5, 24)) {
              for (let i = 0; i <= MAX_RETRIES; i++) {
                  try {
                      if (team.group_constrained) {
                          const [getAllGroupsAssociatedToChannelsInTeam, getAllGroupsAssociatedToTeam] = await Promise.all([ //eslint-disable-line no-await-in-loop
                              Client4.getAllGroupsAssociatedToChannelsInTeam(team.id, true),
                              Client4.getAllGroupsAssociatedToTeam(team.id, true),
                          ]);
  
                          if (getAllGroupsAssociatedToChannelsInTeam.groups) {
                              actions.push({
                                  type: GroupTypes.RECEIVED_ALL_GROUPS_ASSOCIATED_TO_CHANNELS_IN_TEAM,
                                  data: {groupsByChannelId: getAllGroupsAssociatedToChannelsInTeam.groups},
                              });
                          }
  
                          if (getAllGroupsAssociatedToTeam) {
                              actions.push({
                                  type: GroupTypes.RECEIVED_ALL_GROUPS_ASSOCIATED_TO_TEAM,
                                  data: {...getAllGroupsAssociatedToTeam, teamID: team.id},
                              });
                          }
                      } else {
                          const [getAllGroupsAssociatedToChannelsInTeam, getGroups] = await Promise.all([ //eslint-disable-line no-await-in-loop
                              Client4.getAllGroupsAssociatedToChannelsInTeam(team.id, true),
                              Client4.getGroups(true, 0, 0),
                          ]);
  
                          if (getAllGroupsAssociatedToChannelsInTeam.groups) {
                              actions.push({
                                  type: GroupTypes.RECEIVED_ALL_GROUPS_ASSOCIATED_TO_CHANNELS_IN_TEAM,
                                  data: {groupsByChannelId: getAllGroupsAssociatedToChannelsInTeam.groups},
                              });
                          }
  
                          if (getGroups) {
                              actions.push({
                                  type: GroupTypes.RECEIVED_GROUPS,
                                  data: getGroups,
                              });
                          }
                      }
                      break;
                  } catch (err) {
                      if (i === MAX_RETRIES) {
                          return {error: err};
                      }
                  }
              }
  
              try {
                  const myGroups = await Client4.getGroupsByUserId(currentUserId);
                  if (myGroups.length) {
                      actions.push({
                          type: GroupTypes.RECEIVED_MY_GROUPS,
                          data: myGroups,
                      });
                  }
              } catch {
                  // do nothing
              }
          }
  
          if (actions.length) {
              dispatch(batchActions(actions, 'BATCH_GROUP_DATA'));
          }
  
          return {data: true};
      };
  }
  
  export function loadChannelsForTeam(teamId, skipDispatch = false) {
      return async (dispatch, getState) => {
          const state = getState();
          const currentUserId = getCurrentUserId(state);
          const data = {
              sync: true,
              teamId,
              teamChannels: getChannelsIdForTeam(state, teamId),
          };
  
          const actions = [];
  
          if (currentUserId) {
              for (let i = 0; i <= MAX_RETRIES; i++) {
                  try {
                      console.log('Fetching channels attempt', teamId, (i + 1)); //eslint-disable-line no-console
                      const [channels, channelMembers] = await Promise.all([ //eslint-disable-line no-await-in-loop
                          Client4.getMyChannels(teamId, true),
                          Client4.getMyChannelMembers(teamId),
                      ]);
  
                      data.channels = channels;
                      data.channelMembers = channelMembers;
                      break;
                  } catch (err) {
                      if (i === MAX_RETRIES) {
                          const hasChannelsLoaded = state.entities.channels.channelsInTeam[teamId]?.size > 0;
                          return {error: hasChannelsLoaded ? null : err};
                      }
                  }
              }
  
              if (data.channels) {
                  actions.push({
                      type: ChannelTypes.RECEIVED_MY_CHANNELS_WITH_MEMBERS,
                      data,
                  });
  
                  if (!skipDispatch) {
                      const rolesToLoad = new Set();
                      const members = data.channelMembers;
                      for (const member of members) {
                          for (const role of member.roles.split(' ')) {
                              rolesToLoad.add(role);
                          }
                      }
  
                      if (rolesToLoad.size > 0) {
                          try {
                              data.roles = await Client4.getRolesByNames(Array.from(rolesToLoad));
                              if (data.roles.length) {
                                  actions.push({
                                      type: RoleTypes.RECEIVED_ROLES,
                                      data: data.roles,
                                  });
                              }
                          } catch {
                              //eslint-disable-next-line no-console
                              console.log('Could not retrieve channel members roles for the user');
                          }
                      }
  
                      dispatch(batchActions(actions, 'BATCH_LOAD_CHANNELS_FOR_TEAM'));
                  }
  
                  // Fetch needed profiles from channel creators and direct channels
                  dispatch(loadSidebar(data));
  
                  dispatch(loadUnreadChannelPosts(data.channels, data.channelMembers));
              }
  
              dispatch(loadGroupData());
          }
  
          return {data};
      };
  }
  
  export function loadSidebar(data) {
      return async (dispatch, getState) => {
          const state = getState();
          const {channels, channelMembers} = data;
  
          const sidebarActions = await loadSidebarDirectMessagesProfiles(state, channels, channelMembers);
          if (sidebarActions.length) {
              dispatch(batchActions(sidebarActions, 'BATCH_LOAD_SIDEBAR'));
          }
  
          return {data: true};
      };
  }
  
  export function resetUnreadMessageCount(channelId) {
      return async (dispatch) => {
          dispatch({
              type: ChannelTypes.SET_UNREAD_MSG_COUNT,
              data: {
                  channelId,
                  count: 0,
              },
          });
      };
  }
  