// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only

import { isConversationInChatFolder } from '../types/ChatFolder.std.ts';
import { CurrentChatFolders } from '../types/CurrentChatFolders.std.ts';
import { isConversationMuted } from './isConversationMuted.std.ts';

import type { ConversationType } from '../state/ducks/conversations.preload.ts';
import type { ChatFolderId } from '../types/ChatFolder.std.ts';
import type { NotificationProfileType } from '../types/NotificationProfile.std.ts';

type MutableUnreadStats = {
  /**
   * Total of `conversation.unreadCount`
   * in all countable conversations in the set.
   *
   * Note: `conversation.unreadCount` should always include the number of
   * unread messages with mentions.
   */
  unreadCount: number;

  /**
   * Total of `conversation.unreadMentionsCount`
   * in all countable conversations in the set.
   */
  unreadMentionsCount: number;

  /**
   * Total of `unreadCount === 0 && markedRead == true`
   * in all countable conversations in the set.
   */
  readChatsMarkedUnreadCount: number;
};

/**
 * This can be used to describe unread counts of chats, stories, and calls,
 * individually or all of them together.
 */
export type UnreadStats = Readonly<MutableUnreadStats>;

/** @internal exported for testing */
export function _createUnreadStats(): MutableUnreadStats {
  return {
    unreadCount: 0,
    unreadMentionsCount: 0,
    readChatsMarkedUnreadCount: 0,
  };
}

export type UnreadStatsIncludeMuted =
  | 'setting-on' // badge-count-muted-conversations == true
  | 'setting-off' // badge-count-muted-conversations == false
  | 'force-include'
  | 'force-exclude';

export type UnreadStatsOptions = Readonly<{
  includeMuted: UnreadStatsIncludeMuted;
  activeProfile: NotificationProfileType | undefined;
}>;

export type ConversationPropsForUnreadStats = Readonly<
  Pick<
    ConversationType,
    | 'id'
    | 'type'
    | 'activeAt'
    | 'isArchived'
    | 'markedUnread'
    | 'muteExpiresAt'
    | 'unreadCount'
    | 'unreadMentionsCount'
    | 'left'
  >
>;

export type AllChatFoldersUnreadStats = Map<ChatFolderId, UnreadStats>;

/** @internal exported for testing */
export function _shouldExcludeMuted(
  includeMuted: UnreadStatsIncludeMuted
): boolean {
  return includeMuted === 'setting-off' || includeMuted === 'force-exclude';
}

/** @internal exported for testing */
export function _canCountConversation(
  conversation: ConversationPropsForUnreadStats,
  options: UnreadStatsOptions
): boolean {
  if (conversation.activeAt == null || conversation.activeAt === 0) {
    return false;
  }
  if (conversation.isArchived) {
    return false;
  }

  if (
    _shouldExcludeMuted(options.includeMuted) &&
    (isConversationMuted(conversation) ||
      (options.activeProfile &&
        !options.activeProfile.allowedMembers.has(conversation.id)))
  ) {
    return false;
  }
  if (conversation.left) {
    return false;
  }
  return true;
}

/** @internal exported for testing */
export function _countConversation(
  unreadStats: MutableUnreadStats,
  conversation: ConversationPropsForUnreadStats
): void {
  const mutable = unreadStats;
  const {
    unreadCount = 0,
    unreadMentionsCount = 0,
    markedUnread = false,
  } = conversation;

  const hasUnreadCount = unreadCount > 0 || unreadMentionsCount > 0;

  if (hasUnreadCount) {
    mutable.unreadCount += unreadCount;
    mutable.unreadMentionsCount += unreadMentionsCount;
  } else if (markedUnread) {
    mutable.readChatsMarkedUnreadCount += 1;
  }
}

export function isConversationUnread(
  conversation: ConversationPropsForUnreadStats,
  options: UnreadStatsOptions
): boolean {
  if (!_canCountConversation(conversation, options)) {
    return false;
  }
  const { unreadCount, unreadMentionsCount, markedUnread } = conversation;
  if (unreadCount != null && unreadCount !== 0) {
    return true;
  }
  if (unreadMentionsCount != null && unreadMentionsCount !== 0) {
    return true;
  }
  if (markedUnread) {
    return true;
  }
  return false;
}

export function countAllConversationsUnreadStats(
  conversations: ReadonlyArray<ConversationPropsForUnreadStats | undefined>,
  options: UnreadStatsOptions
): UnreadStats {
  const unreadStats = _createUnreadStats();

  for (const conversation of conversations) {
    if (conversation && _canCountConversation(conversation, options)) {
      _countConversation(unreadStats, conversation);
    }
  }

  return unreadStats;
}

export function countAllChatFoldersUnreadStats(
  currentChatFolders: CurrentChatFolders,
  conversations: ReadonlyArray<ConversationPropsForUnreadStats>,
  options: UnreadStatsOptions
): AllChatFoldersUnreadStats {
  const results = new Map<ChatFolderId, MutableUnreadStats>();
  const sortedChatFolders =
    CurrentChatFolders.toSortedArray(currentChatFolders);

  for (const conversation of conversations) {
    // skip if we shouldn't count it
    if (!_canCountConversation(conversation, options)) {
      continue;
    }

    const {
      unreadCount = 0,
      unreadMentionsCount = 0,
      markedUnread = false,
    } = conversation;

    // skip if we don't have any unreads
    if (unreadCount === 0 && unreadMentionsCount === 0 && !markedUnread) {
      continue;
    }

    // check which chatFolders should count this conversation
    for (const chatFolder of sortedChatFolders) {
      if (isConversationInChatFolder(chatFolder, conversation)) {
        let unreadStats = results.get(chatFolder.id);
        if (unreadStats == null) {
          unreadStats = _createUnreadStats();
          results.set(chatFolder.id, unreadStats);
        }

        _countConversation(unreadStats, conversation);
      }
    }
  }

  return results;
}
