import type {
  ApiChat,
  ApiInputMessageReplyInfo,
  ApiMessage,
  ApiMessageEntityCustomEmoji,
  ApiMessageForwardInfo,
  ApiMessageOutgoingStatus,
  ApiPeer, ApiRestrictionReason, ApiSponsoredMessage,
  ApiStickerSetInfo,
  MediaContainer,
} from '../../api/types';
import type {
  ChatTranslatedMessages,
  MessageListType,
  TextSummary,
  ThreadId,
  TranslationTone,
} from '../../types';
import type { IAllowedAttachmentOptions } from '../helpers';
import type {
  GlobalState, TabArgs,
} from '../types';
import { ApiMessageEntityTypes, MAIN_THREAD_ID } from '../../api/types';

import {
  GENERAL_TOPIC_ID,
  SERVICE_NOTIFICATIONS_USER_ID,
  WEB_APP_PLATFORM,
} from '../../config';
import { IS_TRANSLATION_SUPPORTED } from '../../util/browser/windowEnvironment';
import { isUserId } from '../../util/entities/ids';
import { getCurrentTabId } from '../../util/establishMultitabRole';
import { findLast } from '../../util/iteratees';
import { getMessageKey, isLocalMessageId } from '../../util/keys/messageKey';
import { parseTranslationCacheKey } from '../../util/keys/translationKey';
import { isIpRevealingMedia } from '../../util/media/ipRevealingMedia';
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
import { getServerTime } from '../../util/serverTime';
import { getDocumentExtension } from '../../components/common/helpers/documentInfo';
import { API_GENERAL_ID_LIMIT } from '../../limits';
import {
  canSendReaction,
  getAllowedAttachmentOptions,
  getCanPostInChat,
  getHasAdminRight,
  getIsSavedDialog,
  getMessageAudio,
  getMessageDocument,
  getMessageLink,
  getMessagePaidMedia,
  getMessagePhoto,
  getMessageVideo,
  getMessageVoice,
  getSendingState,
  getWebPagePhoto,
  getWebPageVideo,
  hasMessageTtl,
  isActionMessage,
  isChatBasicGroup,
  isChatChannel,
  isChatGroup,
  isChatSuperGroup,
  isCommonBoxChat,
  isExpiredMessage,
  isForwardedMessage,
  isMessageDocumentSticker,
  isMessageFailed,
  isMessageLocal,
  isMessageTranslatable,
  isOwnMessage,
  isServiceNotificationMessage,
  isUserRightBanned,
  prepareMessageReplyInfo,
} from '../helpers';
import { getMessageReplyInfo } from '../helpers/replies';
import {
  selectChat,
  selectChatFullInfo,
  selectChatLastMessageId,
  selectIsChatRestricted,
  selectIsChatWithBot,
  selectIsChatWithSelf,
  selectRequestedChatTranslationLanguage,
} from './chats';
import { selectCurrentLimit } from './limits';
import { selectMessageDownloadableMedia } from './media';
import { selectPeer, selectPeerPaidMessagesStars } from './peers';
import { selectPeerStory } from './stories';
import { selectCustomEmoji, selectIsStickerFavorite } from './symbols';
import { selectTabState } from './tabs';
import {
  selectEditingId,
  selectEditingScheduledId,
  selectTabThreadParam,
  selectThreadByMessage,
  selectThreadInfo,
  selectThreadLocalState,
  selectThreadLocalStateParam,
  selectThreadReadState,
} from './threads';
import { selectTopic, selectTopicFromMessage } from './topics';
import {
  selectBot, selectIsUserChatProtected, selectUser, selectUserStatus,
} from './users';

export function selectCurrentMessageList<T extends GlobalState>(
  global: T,
  ...[tabId = getCurrentTabId()]: TabArgs<T>
) {
  const { messageLists } = selectTabState(global, tabId);

  if (messageLists.length) {
    return messageLists[messageLists.length - 1];
  }

  return undefined;
}

export function selectCurrentChat<T extends GlobalState>(
  global: T,
  ...[tabId = getCurrentTabId()]: TabArgs<T>
) {
  const { chatId } = selectCurrentMessageList(global, tabId) || {};

  return chatId ? selectChat(global, chatId) : undefined;
}

export function selectChatMessages<T extends GlobalState>(global: T, chatId: string) {
  return global.messages.byChatId[chatId]?.byId;
}

export function selectChatScheduledMessages<T extends GlobalState>(global: T, chatId: string) {
  return global.scheduledMessages.byChatId[chatId]?.byId;
}

export function selectListedIds<T extends GlobalState>(global: T, chatId: string, threadId: ThreadId) {
  return selectThreadLocalStateParam(global, chatId, threadId, 'listedIds');
}

export function selectOutlyingListByMessageId<T extends GlobalState>(
  global: T, chatId: string, threadId: ThreadId, messageId: number,
) {
  const outlyingLists = selectOutlyingLists(global, chatId, threadId);
  if (!outlyingLists) {
    return undefined;
  }

  return outlyingLists.find((list) => {
    return list[0] <= messageId && list[list.length - 1] >= messageId;
  });
}

export function selectOutlyingLists<T extends GlobalState>(
  global: T, chatId: string, threadId: ThreadId,
) {
  return selectThreadLocalStateParam(global, chatId, threadId, 'outlyingLists');
}

export function selectCurrentMessageIds<T extends GlobalState>(
  global: T,
  chatId: string, threadId: ThreadId, messageListType: MessageListType,
  ...[tabId = getCurrentTabId()]: TabArgs<T>
) {
  switch (messageListType) {
    case 'thread':
      return selectViewportIds(global, chatId, threadId, tabId);
    case 'pinned':
      return selectPinnedIds(global, chatId, threadId);
    case 'scheduled':
      return selectScheduledIds(global, chatId, threadId);
  }

  return undefined;
}

export function selectViewportIds<T extends GlobalState>(
  global: T, chatId: string, threadId: ThreadId, ...[tabId = getCurrentTabId()]: TabArgs<T>
) {
  return selectTabThreadParam(global, chatId, threadId, 'viewportIds', tabId);
}

export function selectPinnedIds<T extends GlobalState>(global: T, chatId: string, threadId: ThreadId) {
  return selectThreadLocalStateParam(global, chatId, threadId, 'pinnedIds');
}

export function selectScheduledIds<T extends GlobalState>(global: T, chatId: string, threadId: ThreadId) {
  return selectThreadLocalStateParam(global, chatId, threadId, 'scheduledIds');
}

export function selectFirstMessageId<T extends GlobalState>(global: T, chatId: string, threadId: ThreadId) {
  return selectThreadLocalStateParam(global, chatId, threadId, 'firstMessageId');
}

export function selectIsMessageInCurrentMessageList<T extends GlobalState>(
  global: T, chatId: string, message: ApiMessage,
  ...[tabId = getCurrentTabId()]: TabArgs<T>
) {
  const currentMessageList = selectCurrentMessageList(global, tabId);
  if (!currentMessageList) {
    return false;
  }

  const { threadInfo } = selectThreadByMessage(global, message) || {};
  return (
    chatId === currentMessageList.chatId
    && (
      (currentMessageList.threadId === MAIN_THREAD_ID)
      || (threadInfo && currentMessageList.threadId === threadInfo.threadId)
    )
  );
}

export function selectIsViewportNewest<T extends GlobalState>(
  global: T, chatId: string, threadId: ThreadId,
  ...[tabId = getCurrentTabId()]: TabArgs<T>
) {
  const viewportIds = selectViewportIds(global, chatId, threadId, tabId);
  if (!viewportIds || !viewportIds.length) {
    return true;
  }

  const isSavedDialog = getIsSavedDialog(chatId, threadId, global.currentUserId);

  let lastMessageId: number;

  if (threadId === MAIN_THREAD_ID) {
    const id = selectChatLastMessageId(global, chatId);
    if (!id) {
      return true;
    }
    lastMessageId = id;
  } else if (isSavedDialog) {
    const id = selectChatLastMessageId(global, String(threadId), 'saved');
    if (!id) {
      return true;
    }
    lastMessageId = id;
  } else {
    const threadInfo = selectThreadInfo(global, chatId, threadId);
    if (!threadInfo || !threadInfo.lastMessageId) {
      if (!threadInfo?.threadId) return undefined;
      // No messages in thread, except for the thread message itself
      lastMessageId = Number(threadInfo?.threadId);
    } else {
      lastMessageId = threadInfo.lastMessageId;
    }
  }

  // Edge case: outgoing `lastMessage` is updated with a delay to optimize animation
  if (isLocalMessageId(lastMessageId) && !selectChatMessage(global, chatId, lastMessageId)) {
    return true;
  }

  return viewportIds[viewportIds.length - 1] >= lastMessageId;
}

export function selectChatMessage<T extends GlobalState>(global: T, chatId: string, messageId: number) {
  const chatMessages = selectChatMessages(global, chatId);

  return chatMessages ? chatMessages[messageId] : undefined;
}

export function selectScheduledMessage<T extends GlobalState>(global: T, chatId: string, messageId: number) {
  const chatMessages = selectChatScheduledMessages(global, chatId);

  return chatMessages ? chatMessages[messageId] : undefined;
}

export function selectQuickReplyMessage<T extends GlobalState>(global: T, messageId: number) {
  return global.quickReplies.messagesById[messageId];
}

export function selectEditingMessage<T extends GlobalState>(
  global: T, chatId: string, threadId: ThreadId, messageListType: MessageListType,
) {
  if (messageListType === 'scheduled') {
    const messageId = selectEditingScheduledId(global, chatId);
    return messageId ? selectScheduledMessage(global, chatId, messageId) : undefined;
  } else {
    const messageId = selectEditingId(global, chatId, threadId);
    return messageId ? selectChatMessage(global, chatId, messageId) : undefined;
  }
}

export function selectFocusedMessageId<T extends GlobalState>(
  global: T, chatId: string, ...[tabId = getCurrentTabId()]: TabArgs<T>
) {
  const { chatId: focusedChatId, messageId } = selectTabState(global, tabId).focusedMessage || {};

  return focusedChatId === chatId ? messageId : undefined;
}

export function selectIsMessageFocused<T extends GlobalState>(
  global: T, message: ApiMessage, currentThreadId: ThreadId,
  ...[tabId = getCurrentTabId()]: TabArgs<T>
) {
  const focusedId = selectFocusedMessageId(global, message.chatId, tabId);
  const threadId = selectTabState(global, tabId).focusedMessage?.threadId;

  if (currentThreadId !== threadId) return false;

  return focusedId ? focusedId === message.id || focusedId === message.previousLocalId : false;
}

export function selectIsMessageUnread<T extends GlobalState>(
  global: T, chatId: string, threadId: ThreadId, messageId: number, messageListType: MessageListType,
) {
  const { lastReadOutboxMessageId } = selectThreadReadState(global, chatId, threadId) || {};
  const message = selectChatMessage(global, chatId, messageId);
  if (!message) {
    return false;
  }

  return messageListType === 'scheduled' || isMessageLocal(message)
    || !lastReadOutboxMessageId || lastReadOutboxMessageId < message.id;
}

export function selectOutgoingStatus<T extends GlobalState>(
  global: T, chatId: string, threadId: ThreadId, messageId: number, messageListType: MessageListType,
): ApiMessageOutgoingStatus {
  if (!selectIsMessageUnread(global, chatId, threadId, messageId, messageListType)) {
    return 'read';
  }

  const message = selectChatMessage(global, chatId, messageId);
  if (!message) {
    return 'failed'; // Should never happen
  }

  return getSendingState(message);
}

export function selectSender<T extends GlobalState>(global: T, message: ApiMessage): ApiPeer | undefined {
  const { senderId } = message;
  const chat = selectChat(global, message.chatId);
  const currentUser = selectUser(global, global.currentUserId!);
  if (!senderId) {
    return message.isOutgoing ? currentUser : chat;
  }

  if (chat && isChatChannel(chat) && !chat.areProfilesShown) return chat;

  return selectPeer(global, senderId);
}

export function getSendersFromSelectedMessages<T extends GlobalState>(
  global: T,
  chatId: string,
  messageIds: number[],
) {
  return messageIds.map((id) => {
    const message = selectChatMessage(global, chatId, id);
    return message && selectSender(global, message);
  }).filter(Boolean);
}

export function selectSenderFromMessage<T extends GlobalState>(
  global: T,
  chatId: string,
  messageId: number,
): ApiPeer | undefined {
  const message = selectChatMessage(global, chatId, messageId);

  return message && selectSender(global, message);
}

export function selectSenderFromHeader<T extends GlobalState>(
  global: T,
  header: ApiMessageForwardInfo,
) {
  const { fromId } = header;
  if (fromId) {
    return selectPeer(global, fromId);
  }

  return undefined;
}

export function selectForwardedSender<T extends GlobalState>(
  global: T, message: ApiMessage,
): ApiPeer | undefined {
  const isStoryForward = Boolean(message.content.storyData);
  if (isStoryForward) {
    const peerId = message.content.storyData!.peerId;
    return selectPeer(global, peerId);
  }

  const { forwardInfo } = message;
  if (!forwardInfo) {
    return undefined;
  }

  if (forwardInfo.isChannelPost && forwardInfo.fromChatId) {
    return selectChat(global, forwardInfo.fromChatId);
  }

  if (forwardInfo.hiddenUserName) {
    return undefined;
  }

  if (forwardInfo.fromId) {
    return selectPeer(global, forwardInfo.fromId);
  }

  if (forwardInfo.savedFromPeerId) {
    return selectPeer(global, forwardInfo.savedFromPeerId);
  }

  return undefined;
}

export function selectPoll<T extends GlobalState>(global: T, pollId: string) {
  return global.messages.pollById[pollId];
}

export function selectPollFromMessage<T extends GlobalState>(global: T, message: MediaContainer) {
  if (!message.content.pollId) return undefined;
  return selectPoll(global, message.content.pollId);
}

export function selectWebPage<T extends GlobalState>(global: T, webPageId: string) {
  return global.messages.webPageById[webPageId];
}

export function selectWebPageFromMessage<T extends GlobalState>(global: T, message: MediaContainer) {
  if (!message.content.webPage) return undefined;
  return selectWebPage(global, message.content.webPage.id);
}

export function selectFullWebPage<T extends GlobalState>(global: T, webPageId: string) {
  const webPage = selectWebPage(global, webPageId);
  if (!webPage || webPage.webpageType !== 'full') return undefined;
  return webPage;
}

export function selectFullWebPageFromMessage<T extends GlobalState>(global: T, message: MediaContainer) {
  if (!message.content.webPage) return undefined;
  return selectFullWebPage(global, message.content.webPage.id);
}

const MAX_MESSAGES_TO_DELETE_OWNER_TOPIC = 10;
export function selectCanDeleteOwnerTopic<T extends GlobalState>(global: T, chatId: string, topicId: number) {
  const topic = selectTopic(global, chatId, topicId);
  if (topic && !topic.isOwner) return false;

  const localThreadState = selectThreadLocalState(global, chatId, topicId);
  if (!localThreadState) return false;

  const { listedIds } = localThreadState;
  if (!listedIds
    // Plus one for root message
    || listedIds.length + 1 >= MAX_MESSAGES_TO_DELETE_OWNER_TOPIC) {
    return false;
  }

  const hasNotOutgoingMessages = listedIds.some((messageId) => {
    const message = selectChatMessage(global, chatId, messageId);
    return !message || !message.isOutgoing;
  });

  return !hasNotOutgoingMessages;
}

export function selectCanDeleteTopic<T extends GlobalState>(global: T, chatId: string, topicId: number) {
  const chat = selectChat(global, chatId);
  if (!chat) return false;

  if (topicId === GENERAL_TOPIC_ID) return false;

  return chat.isBotForum
    || chat.isCreator
    || getHasAdminRight(chat, 'deleteMessages')
    || (chat.isForum
      && selectCanDeleteOwnerTopic(global, chat.id, topicId));
}

export function selectCanReplyToMessage<T extends GlobalState>(global: T, message: ApiMessage, threadId: ThreadId) {
  const chat = selectChat(global, message.chatId);
  const isRestricted = selectIsChatRestricted(global, message.chatId);
  if (!chat || isRestricted || chat.isForbidden) return false;

  const isLocal = isMessageLocal(message);
  const isServiceNotification = isServiceNotificationMessage(message);

  if (isLocal || isServiceNotification) return false;

  const threadInfo = selectThreadInfo(global, message.chatId, threadId);
  const isMessageThread = Boolean(!threadInfo?.isCommentsInfo && threadInfo?.fromChannelId);
  const chatFullInfo = selectChatFullInfo(global, chat.id);
  const topic = selectTopic(global, chat.id, threadId);
  const canPostInChat = getCanPostInChat(chat, topic, isMessageThread, chatFullInfo);
  if (!canPostInChat) return false;

  const messageTopic = selectTopicFromMessage(global, message);
  return !messageTopic || !messageTopic.isClosed || messageTopic.isOwner || getHasAdminRight(chat, 'manageTopics');
}

export function selectCanForwardMessage<T extends GlobalState>(global: T, message: ApiMessage) {
  const isLocal = isMessageLocal(message);
  const isServiceNotification = isServiceNotificationMessage(message);
  const isAction = isActionMessage(message);
  const hasTtl = hasMessageTtl(message);
  const { content } = message;

  const webPage = selectFullWebPageFromMessage(global, message);

  const story = content.storyData
    ? selectPeerStory(global, content.storyData.peerId, content.storyData.id)
    : (webPage?.story
      ? selectPeerStory(global, webPage.story.peerId, webPage.story.id)
      : undefined
    );
  const isChatProtected = selectIsChatProtected(global, message.chatId);
  const isStoryForwardForbidden = story && ('isDeleted' in story || ('noForwards' in story && story.noForwards));
  const canForward = (
    !isLocal && !isAction && !isChatProtected && !isStoryForwardForbidden
    && (message.isForwardingAllowed || isServiceNotification) && !hasTtl
  );

  return canForward;
}

// This selector is slow and not to be used within lists (e.g. Message component)
export function selectAllowedMessageActionsSlow<T extends GlobalState>(
  global: T, message: ApiMessage, threadId: ThreadId,
) {
  const chat = selectChat(global, message.chatId);
  const isRestricted = selectIsChatRestricted(global, message.chatId);
  if (!chat || isRestricted) {
    return {};
  }

  const chatFullInfo = selectChatFullInfo(global, message.chatId);
  const isPrivate = isUserId(chat.id);
  const isChatWithSelf = selectIsChatWithSelf(global, message.chatId);
  const isBasicGroup = isChatBasicGroup(chat);
  const isSuperGroup = isChatSuperGroup(chat);
  const isChannel = isChatChannel(chat);
  const isBotChat = Boolean(selectBot(global, chat.id));
  const isLocal = isMessageLocal(message);
  const isFailed = isMessageFailed(message);
  const isServiceNotification = isServiceNotificationMessage(message);
  const isOwn = isOwnMessage(message);
  const isForwarded = isForwardedMessage(message);
  const isAction = isActionMessage(message);
  const hasTtl = hasMessageTtl(message);
  const { content } = message;
  const isDocumentSticker = isMessageDocumentSticker(message);
  const isBoostMessage = message.content.action?.type === 'boostApply';
  const isMonoforum = chat.isMonoforum;

  // https://github.com/telegramdesktop/tdesktop/blob/6627de646022af1394134974477109cd1439e1bb/Telegram/SourceFiles/data/data_peer_values.cpp#L367C2-L372C3
  const canPinMessage = (() => {
    if (isPrivate || chat.isCreator) return true;

    if (isChannel) {
      return getHasAdminRight(chat, 'editMessages');
    }

    const hasPinMessageRight = getHasAdminRight(chat, 'pinMessages');
    const isPinMessageRightBanned = isUserRightBanned(chat, 'pinMessages', chatFullInfo);

    if (isSuperGroup) {
      const hasUsernameOrGeo = chat.hasUsername || chat.hasGeo;
      return (hasPinMessageRight || !hasUsernameOrGeo) && !isPinMessageRightBanned;
    }

    if (isBasicGroup) {
      return !chat.isForbidden && !chat.isNotJoined && hasPinMessageRight && !isPinMessageRightBanned;
    }

    return hasPinMessageRight;
  })();

  // https://github.com/telegramdesktop/tdesktop/blob/335095a332607c41a8d20b47e61f5bbd66366d4b/Telegram/SourceFiles/data/data_peer.cpp#L653
  const canEditMessagesIndefinitely = (() => {
    if (content.todo) return true;
    if (isPrivate) return isChatWithSelf;
    if (isBasicGroup) return false;
    if (isSuperGroup) return canPinMessage;
    if (isChannel) return chat.isCreator || getHasAdminRight(chat, 'editMessages');
    return false;
  })();

  const isMessageEditable = (
    (
      canEditMessagesIndefinitely
      || getServerTime() - message.date < (global.config?.editTimeLimit || Infinity)
    ) && !(
      content.sticker || content.contact || content.pollId || content.action
      || (content.video?.isRound) || content.location || content.invoice || content.giveaway || content.giveawayResults
      || isDocumentSticker || content.dice
    )
    && !isForwarded
    && !message.viaBotId
    && !chat.isForbidden
  );

  const isSavedDialog = getIsSavedDialog(chat.id, threadId, global.currentUserId);

  const canReply = selectCanReplyToMessage(global, message, threadId);
  const canReplyGlobally = canReply || (!isSavedDialog && !isLocal && !isServiceNotification
    && (isSuperGroup || isBasicGroup || isChatChannel(chat)));

  let canPin = !isLocal && !isServiceNotification && !isAction && canPinMessage && !isSavedDialog;
  let canUnpin = false;

  const pinnedMessageIds = selectPinnedIds(global, chat.id, threadId);

  if (canPin) {
    canUnpin = Boolean(pinnedMessageIds && pinnedMessageIds.includes(message.id));
    canPin = !canUnpin;
  }

  const canNotDeleteBoostMessage = isBoostMessage && isOwn
    && !chat.isCreator && !getHasAdminRight(chat, 'deleteMessages');

  const canDelete = (!isLocal || isFailed) && !isServiceNotification && !canNotDeleteBoostMessage && (
    isPrivate
    || isOwn
    || isBasicGroup
    || chat.isCreator
    || getHasAdminRight(chat, 'deleteMessages')
  );

  const canReport = !isPrivate && !isOwn;

  const canDeleteForAll = canDelete && !chat.isForbidden && (
    (isPrivate && !isChatWithSelf && !isBotChat && !content.dice)
    || (isBasicGroup && (
      isOwn || getHasAdminRight(chat, 'deleteMessages') || chat.isCreator
    ))
  );

  const hasMessageEditRight = isOwn || (isChannel && (chat.isCreator || getHasAdminRight(chat, 'editMessages')));

  const canEdit = !isLocal && !isAction && isMessageEditable && hasMessageEditRight;

  const hasSticker = Boolean(message.content.sticker);
  const hasFavoriteSticker = hasSticker && selectIsStickerFavorite(global, message.content.sticker!);
  const canFaveSticker = !isAction && hasSticker && !hasFavoriteSticker;
  const canUnfaveSticker = !isAction && hasFavoriteSticker;
  const canCopy = !isAction;
  const canCopyLink = !isLocal && !isAction && (isChannel || isSuperGroup) && !isMonoforum;
  const canSelect = !isLocal && !isAction;

  const canDownload = selectMessageDownloadableMedia(global, message) && !hasTtl;

  const canSaveGif = message.content.video?.isGif;

  const poll = content.pollId ? selectPoll(global, content.pollId) : undefined;
  const hasChosenPollAnswer = Boolean(
    poll && Object.values(poll.results.resultByOption || {}).some((result) => result.isChosen),
  );
  const canRevote = poll && !poll.summary.isClosed && !poll.summary.isRevoteDisabled
    && hasChosenPollAnswer;
  const canClosePoll = hasMessageEditRight && poll && !poll.summary.isClosed && !isForwarded;

  const noOptions = [
    canReply,
    canReplyGlobally,
    canEdit,
    canPin,
    canUnpin,
    canReport,
    canDelete,
    canDeleteForAll,
    canFaveSticker,
    canUnfaveSticker,
    canCopy,
    canCopyLink,
    canSelect,
    canDownload,
    canSaveGif,
    canRevote,
    canClosePoll,
  ].every((ability) => !ability);

  return {
    noOptions,
    canReply,
    canReplyGlobally,
    canEdit,
    canPin,
    canUnpin,
    canReport,
    canDelete,
    canDeleteForAll,
    canFaveSticker,
    canUnfaveSticker,
    canCopy,
    canCopyLink,
    canSelect,
    canDownload,
    canSaveGif,
    canRevote,
    canClosePoll,
  };
}

export function selectCanCopyMessageLink<T extends GlobalState>(
  global: T, message: ApiMessage,
) {
  const chat = selectChat(global, message.chatId);
  if (!chat || selectIsChatRestricted(global, message.chatId)) return false;

  const isLocal = isMessageLocal(message);
  const isAction = isActionMessage(message);
  const isChannel = isChatChannel(chat);
  const isSuperGroup = isChatSuperGroup(chat);

  return !isLocal && !isAction && (isChannel || isSuperGroup) && !chat.isMonoforum;
}

export function selectCanDeleteMessages<T extends GlobalState>(
  global: T,
  chatId: string,
  threadId: ThreadId,
  messageIds: number[],
) {
  const chatMessages = selectChatMessages(global, chatId);

  if (messageIds.length > API_GENERAL_ID_LIMIT) {
    return {};
  }

  const messageActions = messageIds
    .map((id) => chatMessages[id] && selectAllowedMessageActionsSlow(global, chatMessages[id], threadId))
    .filter(Boolean);

  return {
    canDelete: messageActions.every((actions) => actions.canDelete),
    canDeleteForAll: messageActions.every((actions) => actions.canDeleteForAll),
  };
}

export function selectCanDeleteSelectedMessages<T extends GlobalState>(
  global: T,
  messageIds?: number[],
  ...[tabId = getCurrentTabId()]: TabArgs<T>
) {
  const { messageIds: selectedMessageIds } = selectTabState(global, tabId).selectedMessages || {};
  const { chatId, threadId } = selectCurrentMessageList(global, tabId) || {};
  const messageIdList = messageIds?.length ? messageIds : selectedMessageIds;
  if (!chatId || !threadId || !messageIdList) {
    return {};
  }

  return selectCanDeleteMessages(global, chatId, threadId, messageIdList);
}

export function selectCanReportSelectedMessages<T extends GlobalState>(
  global: T,
  ...[tabId = getCurrentTabId()]: TabArgs<T>
) {
  const { messageIds: selectedMessageIds } = selectTabState(global, tabId).selectedMessages || {};
  const { chatId, threadId } = selectCurrentMessageList(global, tabId) || {};
  const chatMessages = chatId && selectChatMessages(global, chatId);
  if (!chatMessages || !selectedMessageIds || !threadId) {
    return false;
  }

  const messageActions = selectedMessageIds
    .map((id) => chatMessages[id] && selectAllowedMessageActionsSlow(global, chatMessages[id], threadId))
    .filter(Boolean);

  return messageActions.every((actions) => actions.canReport);
}

export function selectCanDownloadSelectedMessages<T extends GlobalState>(
  global: T,
  ...[tabId = getCurrentTabId()]: TabArgs<T>
) {
  const { messageIds: selectedMessageIds } = selectTabState(global, tabId).selectedMessages || {};
  const { chatId, threadId } = selectCurrentMessageList(global, tabId) || {};
  const chatMessages = chatId && selectChatMessages(global, chatId);
  if (!chatMessages || !selectedMessageIds || !threadId) {
    return false;
  }

  const messageActions = selectedMessageIds
    .map((id) => chatMessages[id] && selectAllowedMessageActionsSlow(global, chatMessages[id], threadId))
    .filter(Boolean);

  return messageActions.some((actions) => actions.canDownload);
}

export function selectActiveDownloads<T extends GlobalState>(
  global: T,
  ...[tabId = getCurrentTabId()]: TabArgs<T>
) {
  return selectTabState(global, tabId).activeDownloads;
}

export function selectUploadProgress<T extends GlobalState>(global: T, message: ApiMessage) {
  return global.fileUploads.byMessageKey[getMessageKey(message)]?.progress;
}

export function selectRealLastReadId<T extends GlobalState>(global: T, chatId: string, threadId: ThreadId) {
  const readState = selectThreadReadState(global, chatId, threadId);
  if (!readState) {
    return undefined;
  }

  // `lastReadInboxMessageId` is empty for new chats
  if (!readState.lastReadInboxMessageId) {
    // For new comments, mark thread start as the last read
    if (threadId !== MAIN_THREAD_ID && readState.unreadCount && typeof threadId === 'number') {
      return threadId;
    }

    return undefined;
  }

  const lastMessageId = selectChatLastMessageId(global, chatId);

  if (!lastMessageId || readState.unreadCount) {
    return readState.lastReadInboxMessageId;
  }

  return lastMessageId;
}

export function selectFirstUnreadId<T extends GlobalState>(
  global: T, chatId: string, threadId: ThreadId,
) {
  const chat = selectChat(global, chatId);

  if (threadId === MAIN_THREAD_ID) {
    if (!chat) {
      return undefined;
    }
  } else {
    const threadInfo = selectThreadInfo(global, chatId, threadId);
    const readState = selectThreadReadState(global, chatId, threadId);
    if (!threadInfo || !readState
      || (threadInfo.lastMessageId !== undefined && threadInfo.lastMessageId === readState.lastReadInboxMessageId)) {
      return undefined;
    }
  }

  const outlyingLists = selectOutlyingLists(global, chatId, threadId);
  const listedIds = selectListedIds(global, chatId, threadId);
  const byId = selectChatMessages(global, chatId);
  if (!byId || !(outlyingLists?.length || listedIds)) {
    return undefined;
  }

  const lastReadId = selectRealLastReadId(global, chatId, threadId);
  if (!lastReadId && chat && chat.isNotJoined) {
    return undefined;
  }

  const lastReadServiceNotificationId = chatId === SERVICE_NOTIFICATIONS_USER_ID
    ? global.serviceNotifications.reduce((max, notification) => {
      return !notification.isUnread && notification.id > max ? notification.id : max;
    }, -1)
    : -1;

  function findAfterLastReadId(listIds: number[]) {
    return listIds.find((id) => {
      return (
        (!lastReadId || id > lastReadId)
        && byId[id]
        && !byId[id].isTypingDraft
        && (!byId[id].isOutgoing || byId[id].isFromScheduled)
        && id > lastReadServiceNotificationId
      );
    });
  }

  if (outlyingLists?.length) {
    const found = outlyingLists.map((list) => findAfterLastReadId(list)).filter(Boolean)[0];
    if (found) {
      return found;
    }
  }

  if (listedIds) {
    const found = findAfterLastReadId(listedIds);
    if (found) {
      return found;
    }
  }

  return undefined;
}

export function selectIsForwardModalOpen<T extends GlobalState>(
  global: T,
  ...[tabId = getCurrentTabId()]: TabArgs<T>
) {
  const { isShareMessageModalShown } = selectTabState(global, tabId);
  return Boolean(isShareMessageModalShown);
}

export function selectCommonBoxChatId<T extends GlobalState>(global: T, messageId: number) {
  const fromLastMessage = Object.values(global.chats.byId).find((chat) => (
    isCommonBoxChat(chat) && selectChatLastMessageId(global, chat.id) === messageId
  ));
  if (fromLastMessage) {
    return fromLastMessage.id;
  }

  const { byChatId } = global.messages;
  return Object.keys(byChatId).find((chatId) => {
    const chat = selectChat(global, chatId);
    return chat && isCommonBoxChat(chat) && byChatId[chat.id].byId[messageId];
  });
}

export function selectIsInSelectMode<T extends GlobalState>(
  global: T,
  ...[tabId = getCurrentTabId()]: TabArgs<T>
) {
  const { selectedMessages } = selectTabState(global, tabId);

  return Boolean(selectedMessages);
}

export function selectIsMessageSelected<T extends GlobalState>(
  global: T, messageId: number,
  ...[tabId = getCurrentTabId()]: TabArgs<T>
) {
  const { messageIds } = selectTabState(global, tabId).selectedMessages || {};
  if (!messageIds) {
    return false;
  }

  return messageIds.includes(messageId);
}

export function selectForwardedMessageIdsByGroupId<T extends GlobalState>(
  global: T, chatId: string, groupedId: string,
) {
  const chatMessages = selectChatMessages(global, chatId);
  if (!chatMessages) {
    return undefined;
  }

  return Object.values(chatMessages)
    .filter((message) => message.groupedId === groupedId && message.forwardInfo)
    .map(({ forwardInfo }) => forwardInfo!.fromMessageId);
}

export function selectMessageIdsByGroupId<T extends GlobalState>(global: T, chatId: string, groupedId: string) {
  const chatMessages = selectChatMessages(global, chatId);
  if (!chatMessages) {
    return undefined;
  }

  return Object.keys(chatMessages)
    .map(Number)
    .filter((id) => chatMessages[id].groupedId === groupedId);
}

export function selectIsDocumentGroupSelected<T extends GlobalState>(
  global: T, chatId: string, groupedId: string,
  ...[tabId = getCurrentTabId()]: TabArgs<T>
) {
  const { messageIds: selectedIds } = selectTabState(global, tabId).selectedMessages || {};
  if (!selectedIds) {
    return false;
  }

  const groupIds = selectMessageIdsByGroupId(global, chatId, groupedId);
  return groupIds && groupIds.every((id) => selectedIds.includes(id));
}

export function selectSelectedMessagesCount<T extends GlobalState>(
  global: T,
  ...[tabId = getCurrentTabId()]: TabArgs<T>
) {
  const { messageIds } = selectTabState(global, tabId).selectedMessages || {};

  return messageIds ? messageIds.length : 0;
}

export function selectNewestMessageWithBotKeyboardButtons<T extends GlobalState>(
  global: T, chatId: string, threadId: ThreadId = MAIN_THREAD_ID,
  ...[tabId = getCurrentTabId()]: TabArgs<T>
): ApiMessage | undefined {
  const chat = selectChat(global, chatId);
  if (!chat) {
    return undefined;
  }

  const chatMessages = selectChatMessages(global, chatId);
  const viewportIds = selectViewportIds(global, chatId, threadId, tabId);
  if (!chatMessages || !viewportIds) {
    return undefined;
  }

  const messageId = findLast(viewportIds, (id) => {
    const message = chatMessages[id];
    return message && selectShouldDisplayReplyKeyboard(global, message);
  });

  const replyHideMessageId = findLast(viewportIds, (id) => {
    const message = chatMessages[id];
    return message && selectShouldHideReplyKeyboard(global, message);
  });

  if (messageId && replyHideMessageId && replyHideMessageId > messageId) {
    return undefined;
  }

  return messageId ? chatMessages[messageId] : undefined;
}

function selectShouldHideReplyKeyboard<T extends GlobalState>(global: T, message: ApiMessage) {
  const {
    shouldHideKeyboardButtons,
    isHideKeyboardSelective,
    isMentioned,
  } = message;
  if (!shouldHideKeyboardButtons) return false;

  const replyToMessageId = getMessageReplyInfo(message)?.replyToMsgId;

  if (isHideKeyboardSelective) {
    if (isMentioned) return true;
    if (!replyToMessageId) return false;

    const replyMessage = selectChatMessage(global, message.chatId, replyToMessageId);
    return Boolean(replyMessage?.senderId === global.currentUserId);
  }
  return true;
}

function selectShouldDisplayReplyKeyboard<T extends GlobalState>(global: T, message: ApiMessage) {
  const {
    keyboardButtons,
    shouldHideKeyboardButtons,
    isKeyboardSelective,
    isMentioned,
  } = message;
  if (!keyboardButtons || shouldHideKeyboardButtons) return false;

  const replyToMessageId = getMessageReplyInfo(message)?.replyToMsgId;

  if (isKeyboardSelective) {
    if (isMentioned) return true;
    if (!replyToMessageId) return false;

    const replyMessage = selectChatMessage(global, message.chatId, replyToMessageId);
    return Boolean(replyMessage?.senderId === global.currentUserId);
  }

  return true;
}

export function selectCanAutoLoadMedia<T extends GlobalState>(
  global: T, message: ApiMessage | ApiSponsoredMessage,
) {
  const chat = selectChat(global, message.chatId);
  if (!chat) {
    return undefined;
  }

  const sender = 'id' in message ? selectSender(global, message) : undefined;

  const webPage = selectWebPageFromMessage(global, message);
  const isPhoto = Boolean(getMessagePhoto(message) || getWebPagePhoto(webPage));
  const isVideo = Boolean(getMessageVideo(message) || getWebPageVideo(webPage));
  const isFile = Boolean(getMessageAudio(message) || getMessageVoice(message) || getMessageDocument(message));

  const {
    canAutoLoadPhotoFromContacts,
    canAutoLoadPhotoInPrivateChats,
    canAutoLoadPhotoInGroups,
    canAutoLoadPhotoInChannels,
    canAutoLoadVideoFromContacts,
    canAutoLoadVideoInPrivateChats,
    canAutoLoadVideoInGroups,
    canAutoLoadVideoInChannels,
    canAutoLoadFileFromContacts,
    canAutoLoadFileInPrivateChats,
    canAutoLoadFileInGroups,
    canAutoLoadFileInChannels,
  } = global.settings.byKey;

  if (isPhoto) {
    return canAutoLoadMedia({
      global,
      chat,
      sender,
      canAutoLoadMediaFromContacts: canAutoLoadPhotoFromContacts,
      canAutoLoadMediaInPrivateChats: canAutoLoadPhotoInPrivateChats,
      canAutoLoadMediaInGroups: canAutoLoadPhotoInGroups,
      canAutoLoadMediaInChannels: canAutoLoadPhotoInChannels,
    });
  }

  if (isVideo) {
    return canAutoLoadMedia({
      global,
      chat,
      sender,
      canAutoLoadMediaFromContacts: canAutoLoadVideoFromContacts,
      canAutoLoadMediaInPrivateChats: canAutoLoadVideoInPrivateChats,
      canAutoLoadMediaInGroups: canAutoLoadVideoInGroups,
      canAutoLoadMediaInChannels: canAutoLoadVideoInChannels,
    });
  }

  if (isFile) {
    return canAutoLoadMedia({
      global,
      chat,
      sender,
      canAutoLoadMediaFromContacts: canAutoLoadFileFromContacts,
      canAutoLoadMediaInPrivateChats: canAutoLoadFileInPrivateChats,
      canAutoLoadMediaInGroups: canAutoLoadFileInGroups,
      canAutoLoadMediaInChannels: canAutoLoadFileInChannels,
    });
  }

  return true;
}

function canAutoLoadMedia<T extends GlobalState>({
  global,
  chat,
  sender,
  canAutoLoadMediaFromContacts,
  canAutoLoadMediaInPrivateChats,
  canAutoLoadMediaInGroups,
  canAutoLoadMediaInChannels,
}: {
  global: T;
  chat: ApiChat;
  canAutoLoadMediaFromContacts: boolean;
  canAutoLoadMediaInPrivateChats: boolean;
  canAutoLoadMediaInGroups: boolean;
  canAutoLoadMediaInChannels: boolean;
  sender?: ApiPeer;
}) {
  const isMediaFromContact = Boolean(sender && (
    selectIsChatWithSelf(global, sender.id) || selectUser(global, sender.id)?.isContact
  ));

  return Boolean(
    (isMediaFromContact && canAutoLoadMediaFromContacts)
    || (!isMediaFromContact && canAutoLoadMediaInPrivateChats && isUserId(chat.id))
    || (canAutoLoadMediaInGroups && isChatGroup(chat))
    || (canAutoLoadMediaInChannels && isChatChannel(chat)),
  );
}

export function selectLastServiceNotification<T extends GlobalState>(global: T) {
  const { serviceNotifications } = global;
  const maxId = Math.max(...serviceNotifications.map(({ id }) => id));

  return serviceNotifications.find(({ id, isDeleted }) => !isDeleted && id === maxId);
}

export function selectIsMessageProtected<T extends GlobalState>(global: T, message?: ApiMessage) {
  return Boolean(message && (
    message.isProtected || selectIsChatProtected(global, message.chatId) || hasMessageTtl(message)
    || getMessagePaidMedia(message)
  ));
}

export function selectIsChatProtected<T extends GlobalState>(global: T, chatId: string) {
  const chat = selectChat(global, chatId);

  if (!chat) return false;

  if (chat.isProtected || (isUserId(chatId) && selectIsUserChatProtected(global, chatId))) {
    return true;
  }

  return false;
}

export function selectHasProtectedMessage<T extends GlobalState>(global: T, chatId: string, messageIds?: number[]) {
  if (selectIsChatProtected(global, chatId)) {
    return true;
  }

  if (!messageIds) {
    return false;
  }

  const messages = selectChatMessages(global, chatId);

  return messageIds.some((messageId) => messages[messageId]?.isProtected);
}

export function selectCanForwardMessages<T extends GlobalState>(global: T, chatId: string, messageIds?: number[]) {
  if (selectIsChatProtected(global, chatId)) {
    return false;
  }

  if (!messageIds) {
    return false;
  }

  const messages = selectChatMessages(global, chatId);

  return messageIds
    .map((id) => messages[id])
    .every((message) => message && !hasMessageTtl(message)
      && (message.isForwardingAllowed || isServiceNotificationMessage(message)));
}

export function selectHasIpRevealingMedia<T extends GlobalState>(global: T, chatId: string, messageIds: number[]) {
  const messages = selectChatMessages(global, chatId);

  return messageIds
    .map((id) => messages[id])
    .some((message) => {
      if (!message) return false;

      const document = getMessageDocument(message);
      if (!document) return false;

      const extension = getDocumentExtension(document);
      if (!extension) return false;

      return isIpRevealingMedia({ mimeType: document.mimeType, extension });
    });
}

export function selectSponsoredMessage<T extends GlobalState>(global: T, chatId: string) {
  const message = global.messages.sponsoredByChatId[chatId];

  return message && message.expiresAt >= Math.round(Date.now() / 1000) ? message : undefined;
}

export function selectDefaultReaction<T extends GlobalState>(global: T, chatId: string) {
  if (chatId === SERVICE_NOTIFICATIONS_USER_ID) return undefined;

  const isPrivate = isUserId(chatId);
  const defaultReaction = global.config?.defaultReaction;
  if (!defaultReaction) {
    return undefined;
  }

  if (isPrivate) {
    return defaultReaction;
  }

  const chatReactions = selectChatFullInfo(global, chatId)?.enabledReactions;
  if (!chatReactions || !canSendReaction(defaultReaction, chatReactions)) {
    return undefined;
  }

  return defaultReaction;
}

export function selectMaxUserReactions<T extends GlobalState>(global: T): number {
  return selectCurrentLimit(global, 'maxReactions');
}

// Slow, not to be used in `withGlobal`
export function selectVisibleUsers<T extends GlobalState>(
  global: T,
  ...[tabId = getCurrentTabId()]: TabArgs<T>
) {
  const { chatId, threadId } = selectCurrentMessageList(global, tabId) || {};
  if (!chatId || !threadId) {
    return undefined;
  }

  const messageIds = selectTabThreadParam(global, chatId, threadId, 'viewportIds', tabId);
  if (!messageIds) {
    return undefined;
  }

  return messageIds.map((messageId) => {
    const { senderId } = selectChatMessage(global, chatId, messageId) || {};
    return senderId ? selectUser(global, senderId) : undefined;
  }).filter(Boolean);
}

export function selectShouldSchedule<T extends GlobalState>(
  global: T,
  ...[tabId = getCurrentTabId()]: TabArgs<T>
) {
  return selectCurrentMessageList(global, tabId)?.type === 'scheduled';
}

export function selectCanSchedule<T extends GlobalState>(
  global: T,
  ...[tabId = getCurrentTabId()]: TabArgs<T>
) {
  const chatId = selectCurrentMessageList(global, tabId)?.chatId;
  const paidMessagesStars = chatId ? selectPeerPaidMessagesStars(global, chatId) : undefined;
  return !paidMessagesStars;
}

export function selectCanScheduleUntilOnline<T extends GlobalState>(global: T, id: string) {
  const isChatWithSelf = selectIsChatWithSelf(global, id);
  const chatBot = selectBot(global, id);
  const paidMessagesStars = selectPeerPaidMessagesStars(global, id);
  return Boolean(!paidMessagesStars
    && !isChatWithSelf && !chatBot && isUserId(id) && selectUserStatus(global, id)?.wasOnline);
}

export function selectCustomEmojis(message: ApiMessage) {
  const entities = message.content.text?.entities;
  return entities?.filter((entity): entity is ApiMessageEntityCustomEmoji => (
    entity.type === ApiMessageEntityTypes.CustomEmoji
  ));
}

export function selectMessageCustomEmojiSets<T extends GlobalState>(
  global: T, message: ApiMessage,
): ApiStickerSetInfo[] | undefined {
  const customEmojis = selectCustomEmojis(message);
  if (!customEmojis) return MEMO_EMPTY_ARRAY;
  const documents = customEmojis.map((entity) => selectCustomEmoji(global, entity.documentId));
  // If some emoji still loading, do not return empty array
  if (!documents.every(Boolean)) return undefined;
  const sets = documents.map((doc) => doc.stickerSetInfo);

  return sets.reduce((acc, set) => {
    if ('shortName' in set) {
      if (acc.some((s) => 'shortName' in s && s.shortName === set.shortName)) {
        return acc;
      }
    }

    if ('id' in set) {
      if (acc.some((s) => 'id' in s && s.id === set.id)) {
        return acc;
      }
    }
    acc.push(set); // Optimization
    return acc;
  }, [] as ApiStickerSetInfo[]);
}

export function selectForwardsContainVoiceMessages<T extends GlobalState>(
  global: T,
  ...[tabId = getCurrentTabId()]: TabArgs<T>
) {
  const { messageIds, fromChatId } = selectTabState(global, tabId).forwardMessages;
  if (!messageIds) return false;
  const chatMessages = selectChatMessages(global, fromChatId!);
  return messageIds.some((messageId) => {
    const message = chatMessages[messageId];
    return Boolean(message.content.voice) || Boolean(message.content.video?.isRound);
  });
}

export function selectMessageSummary<T extends GlobalState>(
  global: T, chatId: string, messageId: number, toLanguageCode?: string,
): TextSummary | undefined {
  const message = selectChatMessage(global, chatId, messageId);
  if (!message?.summaryLanguageCode) return undefined;

  if (toLanguageCode && toLanguageCode !== message.summaryLanguageCode) {
    const messageTranslations = selectMessageTranslations(global, chatId, toLanguageCode);
    return messageTranslations[messageId]?.summary;
  }

  return global.messages.byChatId[chatId].summaryById[messageId];
}

export function selectChatTranslations<T extends GlobalState>(
  global: T, chatId: string,
): ChatTranslatedMessages {
  return global.translations.byChatId[chatId];
}

export function selectMessageTranslations<T extends GlobalState>(
  global: T, chatId: string, cacheKey: string,
) {
  return selectChatTranslations(global, chatId)?.byLangCode[cacheKey] || {};
}

export function selectRequestedMessageTranslationLanguage<T extends GlobalState>(
  global: T, chatId: string, messageId: number, ...[tabId = getCurrentTabId()]: TabArgs<T>
): string | undefined {
  const requestedInChat = selectTabState(global, tabId).requestedTranslations.byChatId[chatId];
  return requestedInChat?.toLanguage || requestedInChat?.manualMessages?.[messageId];
}

export function selectRequestedMessageTranslationTone<T extends GlobalState>(
  global: T, chatId: string, messageId: number, ...[tabId = getCurrentTabId()]: TabArgs<T>
): TranslationTone | undefined {
  const requestedInChat = selectTabState(global, tabId).requestedTranslations.byChatId[chatId];

  if (requestedInChat?.toLanguage) {
    return requestedInChat.tone || 'neutral';
  }

  const cacheKey = requestedInChat?.manualMessages?.[messageId];
  if (!cacheKey) return undefined;

  const { tone } = parseTranslationCacheKey(cacheKey);
  return tone;
}

export function selectReplyCanBeSentToChat<T extends GlobalState>(
  global: T,
  toChatId: string,
  fromChatId: string,
  replyInfo: ApiInputMessageReplyInfo,
) {
  if (!replyInfo.replyToMsgId) return false;
  const fromRealChatId = replyInfo?.replyToPeerId ?? fromChatId;
  if (toChatId === fromRealChatId) return true;
  const chatMessages = selectChatMessages(global, fromRealChatId);
  const message = chatMessages[replyInfo.replyToMsgId];

  return !isExpiredMessage(message);
}
export function selectForwardsCanBeSentToChat<T extends GlobalState>(
  global: T,
  toChatId: string,
  ...[tabId = getCurrentTabId()]: TabArgs<T>
) {
  const { messageIds, storyId, fromChatId } = selectTabState(global, tabId).forwardMessages;
  const chat = selectChat(global, toChatId);
  if ((!messageIds && !storyId) || !chat) return false;

  if (storyId) {
    return true;
  }

  const chatFullInfo = selectChatFullInfo(global, toChatId);
  const chatMessages = selectChatMessages(global, fromChatId!);

  const isSavedMessages = toChatId ? selectIsChatWithSelf(global, toChatId) : undefined;
  const isChatWithBot = toChatId ? selectIsChatWithBot(global, toChatId) : undefined;
  const options = getAllowedAttachmentOptions(chat, chatFullInfo, isChatWithBot, isSavedMessages);
  return !messageIds!.some((messageId) => сheckMessageSendingDenied(chatMessages[messageId], options));
}
function сheckMessageSendingDenied(message: ApiMessage, options: IAllowedAttachmentOptions) {
  const isVoice = message.content.voice;
  const isRoundVideo = message.content.video?.isRound;
  const isPhoto = message.content.photo;
  const isGif = message.content.video?.isGif;
  const isVideo = message.content.video && !isRoundVideo && !isGif;
  const isAudio = message.content.audio;
  const isDocument = message.content.document;
  const isSticker = message.content.sticker;
  const isPlainText = message.content.text
    && !isVoice && !isRoundVideo && !isSticker && !isDocument && !isAudio && !isVideo && !isPhoto && !isGif;

  return (isVoice && !options.canSendVoices)
    || (isRoundVideo && !options.canSendRoundVideos)
    || (isSticker && !options.canSendStickers)
    || (isDocument && !options.canSendDocuments)
    || (isAudio && !options.canSendAudios)
    || (isVideo && !options.canSendVideos)
    || (isPhoto && !options.canSendPhotos)
    || (isGif && !options.canSendGifs)
    || (isPlainText && !options.canSendPlainText);
}

export function selectCanTranslateMessage<T extends GlobalState>(
  global: T, message: ApiMessage, detectedLanguage?: string, ...[tabId = getCurrentTabId()]: TabArgs<T>
) {
  const { canTranslate: isTranslationEnabled, doNotTranslate } = global.settings.byKey;

  const canTranslateLanguage = !detectedLanguage || !doNotTranslate.includes(detectedLanguage);

  const isTranslatable = isMessageTranslatable(message);

  // Separate translations are disabled when chat translation enabled
  const chatRequestedLanguage = selectRequestedChatTranslationLanguage(global, message.chatId, tabId);

  return IS_TRANSLATION_SUPPORTED && isTranslationEnabled && canTranslateLanguage && isTranslatable
    && !chatRequestedLanguage;
}

export function selectTopicLink<T extends GlobalState>(
  global: T, chatId: string, topicId?: ThreadId,
) {
  const chat = selectChat(global, chatId);
  if (!chat || !chat.isForum || chat.isBotForum) {
    return undefined;
  }

  return getMessageLink(chat, topicId);
}

export function selectMessageReplyInfo<T extends GlobalState>(
  global: T, chatId: string, threadId: ThreadId, additionalReplyInfo?: ApiInputMessageReplyInfo,
) {
  const chat = selectChat(global, chatId);
  if (!chat) return undefined;

  return prepareMessageReplyInfo(threadId, additionalReplyInfo);
}

export function selectReplyMessage<T extends GlobalState>(global: T, message: ApiMessage) {
  const { replyToMsgId, replyToPeerId } = getMessageReplyInfo(message) || {};
  const replyMessage = replyToMsgId
    ? selectChatMessage(global, replyToPeerId || message.chatId, replyToMsgId) : undefined;

  return replyMessage;
}

export function selectActiveRestrictionReasons<T extends GlobalState>(
  global: T, restrictionReasons?: ApiRestrictionReason[],
): ApiRestrictionReason[] {
  if (!restrictionReasons) return [];

  const { ignoreRestrictionReasons } = global.appConfig;

  return restrictionReasons.filter((reason) => {
    const isForCurrentPlatform = reason.platform === 'all' || reason.platform === WEB_APP_PLATFORM;
    if (!isForCurrentPlatform) return false;

    const shouldIgnore = ignoreRestrictionReasons?.includes(reason.reason);
    return !shouldIgnore;
  });
}
