import type {
  ApiChat, ApiChatFullInfo, ApiChatType,
} from '../../api/types';
import type { ChatListType } from '../../types';
import type { GlobalState, TabArgs } from '../types';

import {
  ALL_FOLDER_ID, ARCHIVED_FOLDER_ID, MEMBERS_LOAD_SLICE, SAVED_FOLDER_ID, SERVICE_NOTIFICATIONS_USER_ID,
} from '../../config';
import { IS_TRANSLATION_SUPPORTED } from '../../util/browser/windowEnvironment';
import { isUserId } from '../../util/entities/ids';
import { getCurrentTabId } from '../../util/establishMultitabRole';
import {
  getHasAdminRight,
  isChatAdmin,
  isChatChannel,
  isChatPublic,
  isChatSuperGroup,
  isHistoryClearMessage,
  isUserBot,
  isUserOnline,
  isUserRightBanned,
} from '../helpers';
import { selectActiveRestrictionReasons } from './messages';
import { selectTabState } from './tabs';
import {
  selectBot, selectIsCurrentUserPremium, selectUser, selectUserFullInfo,
} from './users';

export function selectChat<T extends GlobalState>(global: T, chatId: string): ApiChat | undefined {
  return global.chats.byId[chatId];
}

export function selectChatFullInfo<T extends GlobalState>(global: T, chatId: string): ApiChatFullInfo | undefined {
  return global.chats.fullInfoById[chatId];
}

export function selectPeerFullInfo<T extends GlobalState>(global: T, peerId: string) {
  if (isUserId(peerId)) return selectUserFullInfo(global, peerId);
  return selectChatFullInfo(global, peerId);
}

export function selectChatListLoadingParameters<T extends GlobalState>(
  global: T, listType: ChatListType,
) {
  return global.chats.loadingParameters[listType];
}

export function selectIsChatWithSelf<T extends GlobalState>(global: T, chatId: string) {
  return chatId === global.currentUserId;
}

export function selectIsChatWithBot<T extends GlobalState>(global: T, chatId: string) {
  const user = selectUser(global, chatId);
  return user && isUserBot(user);
}

export function selectSupportChat<T extends GlobalState>(global: T) {
  return Object.values(global.chats.byId).find(({ isSupport }: ApiChat) => isSupport);
}

export function selectChatOnlineCount<T extends GlobalState>(global: T, chat: ApiChat) {
  const fullInfo = selectChatFullInfo(global, chat.id);
  if (isUserId(chat.id) || isChatChannel(chat) || !fullInfo) {
    return undefined;
  }

  if (!fullInfo.members || fullInfo.members.length === MEMBERS_LOAD_SLICE) {
    return fullInfo.onlineCount;
  }

  return fullInfo.members.reduce((onlineCount, { userId }) => {
    if (
      !selectIsChatWithSelf(global, userId)
      && global.users.byId[userId]
      && isUserOnline(global.users.byId[userId], global.users.statusesById[userId])
    ) {
      return onlineCount + 1;
    }

    return onlineCount;
  }, 0);
}

export function selectIsTrustedBot<T extends GlobalState>(global: T, botId: string) {
  return global.trustedBotIds.includes(botId) || global.appConfig.whitelistedBotIds?.includes(botId);
}

export function selectChatType<T extends GlobalState>(global: T, chatId: string): ApiChatType | undefined {
  const bot = selectBot(global, chatId);
  if (bot) {
    return 'bots';
  }

  const user = selectUser(global, chatId);
  if (user) {
    return 'users';
  }

  const chat = selectChat(global, chatId);
  if (!chat) return undefined;

  if (isChatChannel(chat)) {
    return 'channels';
  }

  return 'chats';
}

export function selectIsChatBotNotStarted<T extends GlobalState>(global: T, chatId: string) {
  const bot = selectBot(global, chatId);
  if (!bot) {
    return false;
  }

  const lastMessage = selectChatLastMessage(global, chatId);
  if (lastMessage && isHistoryClearMessage(lastMessage)) {
    return true;
  }

  return Boolean(!lastMessage);
}

export function selectAreActiveChatsLoaded<T extends GlobalState>(global: T): boolean {
  return Boolean(global.chats.listIds.active);
}

export function selectIsChatListed<T extends GlobalState>(
  global: T, chatId: string, type?: ChatListType,
): boolean {
  const { listIds } = global.chats;
  if (type) {
    const targetList = listIds[type];
    return Boolean(targetList && targetList.includes(chatId));
  }

  return Object.values(listIds).some((list) => list && list.includes(chatId));
}

export function selectChatListType<T extends GlobalState>(
  global: T, chatId: string,
): 'active' | 'archived' | undefined {
  const chat = selectChat(global, chatId);
  if (!chat || !selectIsChatListed(global, chatId)) {
    return undefined;
  }

  return chat.folderId === ARCHIVED_FOLDER_ID ? 'archived' : 'active';
}

export function selectChatFolder<T extends GlobalState>(global: T, folderId: number) {
  return global.chatFolders.byId[folderId];
}

export function selectTotalChatCount<T extends GlobalState>(global: T, listType: 'active' | 'archived'): number {
  const { totalCount } = global.chats;
  const allChatsCount = totalCount.all;
  const archivedChatsCount = totalCount.archived || 0;

  if (listType === 'archived') {
    return archivedChatsCount;
  }

  return allChatsCount ? allChatsCount - archivedChatsCount : 0;
}

export function selectIsChatPinned<T extends GlobalState>(
  global: T, chatId: string, folderId = ALL_FOLDER_ID,
): boolean {
  const { active, archived, saved } = global.chats.orderedPinnedIds;

  if (folderId === ALL_FOLDER_ID) {
    return Boolean(active?.includes(chatId));
  }

  if (folderId === ARCHIVED_FOLDER_ID) {
    return Boolean(archived?.includes(chatId));
  }

  if (folderId === SAVED_FOLDER_ID) {
    return Boolean(saved?.includes(chatId));
  }

  const { byId: chatFoldersById } = global.chatFolders;

  const { pinnedChatIds } = chatFoldersById[folderId] || {};
  return Boolean(pinnedChatIds?.includes(chatId));
}

// Slow, not to be used in `withGlobal`
export function selectChatByUsername<T extends GlobalState>(global: T, username: string) {
  const usernameLowered = username.toLowerCase();
  return Object.values(global.chats.byId).find(
    (chat) => chat.usernames?.some((c) => c.username.toLowerCase() === usernameLowered),
  );
}

export function selectIsServiceChatReady<T extends GlobalState>(global: T) {
  return Boolean(selectChat(global, SERVICE_NOTIFICATIONS_USER_ID));
}

export function selectSendAs<T extends GlobalState>(global: T, chatId: string) {
  const chat = selectChat(global, chatId);
  if (!chat) return undefined;

  const id = selectChatFullInfo(global, chatId)?.sendAsId;
  if (!id) return undefined;

  return selectUser(global, id) || selectChat(global, id);
}

export function selectRequestedDraft<T extends GlobalState>(
  global: T, chatId: string,
  ...[tabId = getCurrentTabId()]: TabArgs<T>
) {
  const { requestedDraft } = selectTabState(global, tabId);
  if (requestedDraft?.chatId === chatId && !requestedDraft.files?.length) {
    return requestedDraft.text;
  }
  return undefined;
}

export function selectRequestedDraftFiles<T extends GlobalState>(
  global: T, chatId: string,
  ...[tabId = getCurrentTabId()]: TabArgs<T>
) {
  const { requestedDraft } = selectTabState(global, tabId);
  if (requestedDraft?.chatId === chatId) {
    return requestedDraft.files;
  }
  return undefined;
}

export function filterChatIdsByType<T extends GlobalState>(
  global: T, chatIds: string[], filter: readonly ApiChatType[],
) {
  return chatIds.filter((id) => {
    const type = selectChatType(global, id);
    if (!type) {
      return false;
    }
    return filter.includes(type);
  });
}

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

  // https://github.com/TelegramMessenger/Telegram-iOS/blob/5126be83b3b9578fb014eb52ca553da9e7a8b83a/submodules/TelegramCore/Sources/TelegramEngine/Peers/Communities.swift#L6
  return !chat.migratedTo && Boolean(!isUserId(chatId) && ((isChatChannel(chat) || isChatSuperGroup(chat)) ? (
    chat.isCreator || getHasAdminRight(chat, 'inviteUsers')
    || (isChatPublic(chat) && !chat.isJoinRequest)
  ) : (chat.isCreator || getHasAdminRight(chat, 'inviteUsers'))));
}

export function selectCanShareFolder<T extends GlobalState>(global: T, folderId: number) {
  const folder = selectChatFolder(global, folderId);
  if (!folder) return false;

  const {
    bots, groups, channels, contacts, nonContacts, includedChatIds, pinnedChatIds,
    excludeArchived, excludeMuted, excludeRead, excludedChatIds,
  } = folder;

  return !bots && !groups && !channels && !contacts && !nonContacts
    && !excludeArchived && !excludeMuted && !excludeRead && !excludedChatIds?.length
    && (pinnedChatIds?.length || includedChatIds.length)
    && folder.includedChatIds.concat(folder.pinnedChatIds || []).some((chatId) => {
      return selectCanInviteToChat(global, chatId);
    });
}

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

  if (chat.hasAutoTranslation) return true;

  const { canTranslateChats } = global.settings.byKey;

  const isPremium = selectIsCurrentUserPremium(global);
  const isSavedMessages = selectIsChatWithSelf(global, chatId);

  return IS_TRANSLATION_SUPPORTED && canTranslateChats && isPremium && !isSavedMessages;
}

export function selectCanTranslateChat<T extends GlobalState>(
  global: T, chatId: string, ...[tabId = getCurrentTabId()]: TabArgs<T>
) {
  const chat = selectChat(global, chatId);
  if (!chat) return false;

  const requestedTranslation = selectRequestedChatTranslationLanguage(global, chatId, tabId);
  if (requestedTranslation) return true; // Prevent translation dropping on reevaluation

  const isLanguageDetectable = selectShouldDetectChatLanguage(global, chatId);
  const detectedLanguage = chat.detectedLanguage;

  const { doNotTranslate } = global.settings.byKey;

  return Boolean(isLanguageDetectable && detectedLanguage && !doNotTranslate.includes(detectedLanguage));
}

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

  return requestedTranslations.byChatId[chatId]?.toLanguage;
}

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

  return requestedTranslations.byChatId[chatId]?.tone || global.settings.byKey.translationTone || 'neutral';
}

export function selectSimilarChannelIds<T extends GlobalState>(
  global: T,
  chatId: string,
) {
  return global.chats.similarChannelsById[chatId];
}

export function selectSimilarBotsIds<T extends GlobalState>(
  global: T,
  chatId: string,
) {
  return global.chats.similarBotsById[chatId];
}

export function selectChatLastMessageId<T extends GlobalState>(
  global: T, chatId: string, listType: 'all' | 'saved' = 'all',
) {
  return global.chats.lastMessageIds[listType]?.[chatId];
}

export function selectChatLastMessage<T extends GlobalState>(
  global: T, chatId: string, listType: 'all' | 'saved' = 'all',
) {
  const id = selectChatLastMessageId(global, chatId, listType);
  if (!id) return undefined;

  const realChatId = listType === 'saved' ? global.currentUserId! : chatId;
  return global.messages.byChatId[realChatId]?.byId[id];
}

export function selectIsMonoforumAdmin<T extends GlobalState>(
  global: T, chatId: string,
) {
  const chat = selectChat(global, chatId);
  if (!chat?.isMonoforum) return;

  const channel = selectMonoforumChannel(global, chatId);
  if (!channel) return;

  return Boolean(chat.isCreator || getHasAdminRight(channel, 'manageDirectMessages'));
}

/**
 * Only selects monoforum channel for monoforum chats.
 * Returns `undefined` for other chats, including channels that have linked monoforum.
 */
export function selectMonoforumChannel<T extends GlobalState>(
  global: T, chatId: string,
) {
  const chat = selectChat(global, chatId);
  if (!chat) return;

  return chat.isMonoforum ? selectChat(global, chat.linkedMonoforumId!) : undefined;
}

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

  const activeRestrictions = selectActiveRestrictionReasons(global, chat.restrictionReasons);
  return activeRestrictions.length > 0;
}

export function selectAreFoldersPresent<T extends GlobalState>(global: T) {
  const ids = global.chatFolders.orderedIds;
  return Boolean(ids && ids.length > 1);
}

export function selectCanEditRank<T extends GlobalState>(global: T, {
  chatId, userId, isAdmin, isOwner,
}: {
  chatId: string;
  userId: string;
  isAdmin?: boolean;
  isOwner?: boolean;
}) {
  const chat = selectChat(global, chatId);
  if (!chat || chat.isNotJoined || chat.isRestricted) return false;

  const isCurrentUserAdmin = isChatAdmin(chat);
  const hasAdminRight = getHasAdminRight(chat, 'manageRanks');
  if (!isCurrentUserAdmin) return userId === global.currentUserId && !isUserRightBanned(chat, 'editRank'); // Users can edit only their own rank
  if (!hasAdminRight) return false;

  if (userId === global.currentUserId) return true; // Admin can edit own rank with permission

  if (!chat.isCreator && (isOwner || isAdmin)) return false; // Admin can't edit rank of owner or another admin

  return true;
}

export function selectCanEditOwnRank<T extends GlobalState>(global: T, chatId: string) {
  const chat = selectChat(global, chatId);
  if (!chat || chat.isNotJoined || chat.isRestricted) return false;

  const isCurrentUserAdmin = isChatAdmin(chat);
  if (!isCurrentUserAdmin) return !isUserRightBanned(chat, 'editRank'); // Users can edit only their own rank
  return getHasAdminRight(chat, 'manageRanks');
}
