// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Injectable } from "@angular/core";
import { switchMap } from "rxjs";

import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { EventType, EventResponse } from "@bitwarden/common/dirt/event-logs";
import { DeviceType } from "@bitwarden/common/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { BitwardenIcon } from "@bitwarden/components";

export const SEND_EVENTS_HREF_PREFIX = "#send-events:";
export const MEMBER_EVENTS_HREF_PREFIX = "#member-events:";

@Injectable()
export class EventService {
  private policies: Policy[];

  constructor(
    private i18nService: I18nService,
    policyService: PolicyService,
    accountService: AccountService,
    private configService: ConfigService,
  ) {
    accountService.activeAccount$
      .pipe(
        getUserId,
        switchMap((userId) => policyService.policies$(userId)),
      )
      .subscribe((policies) => {
        this.policies = policies;
      });
  }

  getDefaultDateFilters() {
    const d = new Date();
    const end = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23, 59);
    d.setDate(d.getDate() - 30);
    const start = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0);
    return [this.toDateTimeLocalString(start), this.toDateTimeLocalString(end)];
  }

  formatDateFilters(filterStart: string, filterEnd: string) {
    const start: Date = new Date(filterStart);
    const end: Date = new Date(filterEnd + ":59.999");
    if (isNaN(start.getTime()) || isNaN(end.getTime()) || end < start) {
      throw new Error("Invalid date range.");
    }
    return [start.toISOString(), end.toISOString()];
  }

  async getEventInfo(ev: EventResponse, options = new EventOptions()): Promise<EventInfo> {
    const appInfo = this.getAppInfo(ev);
    const { message, humanReadableMessage } = await this.getEventMessage(ev, options);
    return {
      message: message,
      humanReadableMessage: humanReadableMessage,
      appIcon: appInfo[0],
      appName: appInfo[1],
    };
  }

  private async getEventMessage(ev: EventResponse, options: EventOptions) {
    let msg = "";
    let humanReadableMsg = "";

    const vfo1Enabled = await this.configService.getFeatureFlag(FeatureFlag.VFO1Foundation);
    if (vfo1Enabled) {
      const vfo1Result = this.getEventMessageVfo1(ev, options);
      if (vfo1Result.msg && vfo1Result.humanReadableMsg) {
        return {
          message: vfo1Result.msg,
          humanReadableMessage: vfo1Result.humanReadableMsg,
        };
      }
    }

    switch (ev.type) {
      // User
      case EventType.User_LoggedIn:
        msg = humanReadableMsg = this.i18nService.t("loggedIn");
        break;
      case EventType.User_ChangedPassword:
        msg = humanReadableMsg = this.i18nService.t("changedPassword");
        break;
      case EventType.User_Updated2fa:
        msg = humanReadableMsg = this.i18nService.t("enabledUpdated2fa");
        break;
      case EventType.User_Disabled2fa:
        msg = humanReadableMsg = this.i18nService.t("disabled2fa");
        break;
      case EventType.User_Recovered2fa:
        msg = humanReadableMsg = this.i18nService.t("recovered2fa");
        break;
      case EventType.User_FailedLogIn:
        msg = humanReadableMsg = this.i18nService.t("failedLogin");
        break;
      case EventType.User_FailedLogIn2fa:
        msg = humanReadableMsg = this.i18nService.t("failedLogin2fa");
        break;
      case EventType.User_ClientExportedVault:
        msg = humanReadableMsg = this.i18nService.t("exportedVault");
        break;
      case EventType.User_UpdatedTempPassword:
        msg = humanReadableMsg = this.i18nService.t(
          "userResetMasterPasswordThroughAccountRecovery",
        );
        break;
      case EventType.User_MigratedKeyToKeyConnector:
        msg = humanReadableMsg = this.i18nService.t("migratedKeyConnector");
        break;
      case EventType.User_RequestedDeviceApproval:
        msg = humanReadableMsg = this.i18nService.t("requestedDeviceApproval");
        break;
      case EventType.User_TdeOffboardingPasswordSet:
        msg = humanReadableMsg = this.i18nService.t("tdeOffboardingPasswordSet");
        break;
      // Cipher
      case EventType.Cipher_Created:
        msg = this.i18nService.t("createdItemId", this.formatCipherId(ev, options));
        humanReadableMsg = this.i18nService.t("createdItemId", this.getShortId(ev.cipherId));
        break;
      case EventType.Cipher_Updated:
        msg = this.i18nService.t("editedItemId", this.formatCipherId(ev, options));
        humanReadableMsg = this.i18nService.t("editedItemId", this.getShortId(ev.cipherId));
        break;
      case EventType.Cipher_Deleted:
        msg = this.i18nService.t("permanentlyDeletedItemId", this.formatCipherId(ev, options));
        humanReadableMsg = this.i18nService.t(
          "permanentlyDeletedItemId",
          this.getShortId(ev.cipherId),
        );
        break;
      case EventType.Cipher_SoftDeleted:
        msg = this.i18nService.t("deletedItemId", this.formatCipherId(ev, options));
        humanReadableMsg = this.i18nService.t("deletedItemId", this.getShortId(ev.cipherId));
        break;
      case EventType.Cipher_Restored:
        msg = this.i18nService.t("restoredItemId", this.formatCipherId(ev, options));
        humanReadableMsg = this.i18nService.t("restoredItemId", this.formatCipherId(ev, options));
        break;
      case EventType.Cipher_AttachmentCreated:
        msg = this.i18nService.t("createdAttachmentForItem", this.formatCipherId(ev, options));
        humanReadableMsg = this.i18nService.t(
          "createdAttachmentForItem",
          this.getShortId(ev.cipherId),
        );
        break;
      case EventType.Cipher_AttachmentDeleted:
        msg = this.i18nService.t("deletedAttachmentForItem", this.formatCipherId(ev, options));
        humanReadableMsg = this.i18nService.t(
          "deletedAttachmentForItem",
          this.getShortId(ev.cipherId),
        );
        break;
      case EventType.Cipher_Shared:
        msg = this.i18nService.t("movedItemIdToOrg", this.formatCipherId(ev, options));
        humanReadableMsg = this.i18nService.t("movedItemIdToOrg", this.getShortId(ev.cipherId));
        break;
      case EventType.Cipher_ClientViewed:
        msg = this.i18nService.t("viewedItemId", this.formatCipherId(ev, options));
        humanReadableMsg = this.i18nService.t("viewedItemId", this.getShortId(ev.cipherId));
        break;
      case EventType.Cipher_ClientToggledPasswordVisible:
        msg = this.i18nService.t("viewedPasswordItemId", this.formatCipherId(ev, options));
        humanReadableMsg = this.i18nService.t("viewedPasswordItemId", this.getShortId(ev.cipherId));
        break;
      case EventType.Cipher_ClientToggledHiddenFieldVisible:
        msg = this.i18nService.t("viewedHiddenFieldItemId", this.formatCipherId(ev, options));
        humanReadableMsg = this.i18nService.t(
          "viewedHiddenFieldItemId",
          this.getShortId(ev.cipherId),
        );
        break;
      case EventType.Cipher_ClientToggledCardNumberVisible:
        msg = this.i18nService.t("viewedCardNumberItemId", this.formatCipherId(ev, options));
        humanReadableMsg = this.i18nService.t(
          "viewedCardNumberItemId",
          this.getShortId(ev.cipherId),
        );
        break;
      case EventType.Cipher_ClientToggledCardCodeVisible:
        msg = this.i18nService.t("viewedSecurityCodeItemId", this.formatCipherId(ev, options));
        humanReadableMsg = this.i18nService.t(
          "viewedSecurityCodeItemId",
          this.getShortId(ev.cipherId),
        );
        break;
      case EventType.Cipher_ClientCopiedHiddenField:
        msg = this.i18nService.t("copiedHiddenFieldItemId", this.formatCipherId(ev, options));
        humanReadableMsg = this.i18nService.t(
          "copiedHiddenFieldItemId",
          this.getShortId(ev.cipherId),
        );
        break;
      case EventType.Cipher_ClientCopiedPassword:
        msg = this.i18nService.t("copiedPasswordItemId", this.formatCipherId(ev, options));
        humanReadableMsg = this.i18nService.t("copiedPasswordItemId", this.getShortId(ev.cipherId));
        break;
      case EventType.Cipher_ClientCopiedCardCode:
        msg = this.i18nService.t("copiedSecurityCodeItemId", this.formatCipherId(ev, options));
        humanReadableMsg = this.i18nService.t(
          "copiedSecurityCodeItemId",
          this.getShortId(ev.cipherId),
        );
        break;
      case EventType.Cipher_ClientAutofilled:
        msg = this.i18nService.t("autofilledItemId", this.formatCipherId(ev, options));
        humanReadableMsg = this.i18nService.t("autofilledItemId", this.getShortId(ev.cipherId));
        break;
      case EventType.Cipher_UpdatedCollections:
        msg = this.i18nService.t("editedCollectionsForItem", this.formatCipherId(ev, options));
        humanReadableMsg = this.i18nService.t(
          "editedCollectionsForItem",
          this.getShortId(ev.cipherId),
        );
        break;
      case EventType.Cipher_ClientToggledBankAccountNumberVisible:
        msg = this.i18nService.t("viewedBankAccountNumberItemId", this.formatCipherId(ev, options));
        humanReadableMsg = this.i18nService.t(
          "viewedBankAccountNumberItemId",
          this.getShortId(ev.cipherId),
        );
        break;
      case EventType.Cipher_ClientToggledBankAccountPinVisible:
        msg = this.i18nService.t("viewedBankAccountPinItemId", this.formatCipherId(ev, options));
        humanReadableMsg = this.i18nService.t(
          "viewedBankAccountPinItemId",
          this.getShortId(ev.cipherId),
        );
        break;
      case EventType.Cipher_ClientCopiedBankAccountNumber:
        msg = this.i18nService.t("copiedBankAccountNumberItemId", this.formatCipherId(ev, options));
        humanReadableMsg = this.i18nService.t(
          "copiedBankAccountNumberItemId",
          this.getShortId(ev.cipherId),
        );
        break;
      case EventType.Cipher_ClientCopiedBankAccountPin:
        msg = this.i18nService.t("copiedBankAccountPinItemId", this.formatCipherId(ev, options));
        humanReadableMsg = this.i18nService.t(
          "copiedBankAccountPinItemId",
          this.getShortId(ev.cipherId),
        );
        break;
      case EventType.Cipher_ClientToggledLicenseNumberVisible:
        msg = this.i18nService.t("viewedLicenseNumberItemId", this.formatCipherId(ev, options));
        humanReadableMsg = this.i18nService.t(
          "viewedLicenseNumberItemId",
          this.getShortId(ev.cipherId),
        );
        break;
      case EventType.Cipher_ClientCopiedLicenseNumber:
        msg = this.i18nService.t("copiedLicenseNumberItemId", this.formatCipherId(ev, options));
        humanReadableMsg = this.i18nService.t(
          "copiedLicenseNumberItemId",
          this.getShortId(ev.cipherId),
        );
        break;
      case EventType.Cipher_ClientCopiedPassportNumber:
        msg = this.i18nService.t("copiedPassportNumberItemId", this.formatCipherId(ev, options));
        humanReadableMsg = this.i18nService.t(
          "copiedPassportNumberItemId",
          this.getShortId(ev.cipherId),
        );
        break;
      case EventType.Cipher_ClientToggledPassportNumberVisible:
        msg = this.i18nService.t("viewedPassportNumberItemId", this.formatCipherId(ev, options));
        humanReadableMsg = this.i18nService.t(
          "viewedPassportNumberItemId",
          this.getShortId(ev.cipherId),
        );
        break;
      case EventType.Cipher_ClientCopiedIban:
        msg = this.i18nService.t("copiedIbanItemId", this.formatCipherId(ev, options));
        humanReadableMsg = this.i18nService.t("copiedIbanItemId", this.getShortId(ev.cipherId));
        break;
      case EventType.Cipher_ClientToggledIbanVisible:
        msg = this.i18nService.t("viewedIbanItemId", this.formatCipherId(ev, options));
        humanReadableMsg = this.i18nService.t("viewedIbanItemId", this.getShortId(ev.cipherId));
        break;
      case EventType.Cipher_ClientCopiedSwiftCode:
        msg = this.i18nService.t("copiedSwiftCodeItemId", this.formatCipherId(ev, options));
        humanReadableMsg = this.i18nService.t(
          "copiedSwiftCodeItemId",
          this.getShortId(ev.cipherId),
        );
        break;
      case EventType.Cipher_ClientToggledSwiftCodeVisible:
        msg = this.i18nService.t("viewedSwiftCodeItemId", this.formatCipherId(ev, options));
        humanReadableMsg = this.i18nService.t(
          "viewedSwiftCodeItemId",
          this.getShortId(ev.cipherId),
        );
        break;
      case EventType.Cipher_ClientToggledNationalIdentificationNumberVisible:
        msg = this.i18nService.t(
          "viewedNationalIdentificationNumberItemId",
          this.formatCipherId(ev, options),
        );
        humanReadableMsg = this.i18nService.t(
          "viewedNationalIdentificationNumberItemId",
          this.getShortId(ev.cipherId),
        );
        break;
      case EventType.Cipher_ClientCopiedNationalIdentificationNumber:
        msg = this.i18nService.t(
          "copiedNationalIdentificationNumberItemId",
          this.formatCipherId(ev, options),
        );
        humanReadableMsg = this.i18nService.t(
          "copiedNationalIdentificationNumberItemId",
          this.getShortId(ev.cipherId),
        );
        break;

      // Collection
      case EventType.Collection_Created:
        msg = this.i18nService.t("createdCollectionId", this.formatCollectionId(ev));
        humanReadableMsg = this.i18nService.t(
          "createdCollectionId",
          this.getShortId(ev.collectionId),
        );
        break;
      case EventType.Collection_Updated:
        msg = this.i18nService.t("editedCollectionId", this.formatCollectionId(ev));
        humanReadableMsg = this.i18nService.t(
          "editedCollectionId",
          this.getShortId(ev.collectionId),
        );
        break;
      case EventType.Collection_Deleted:
        msg = this.i18nService.t("deletedCollectionId", this.formatCollectionId(ev));
        humanReadableMsg = this.i18nService.t(
          "deletedCollectionId",
          this.getShortId(ev.collectionId),
        );
        break;
      // Group
      case EventType.Group_Created:
        msg = this.i18nService.t("createdGroupId", this.formatGroupId(ev));
        humanReadableMsg = this.i18nService.t("createdGroupId", this.getShortId(ev.groupId));
        break;
      case EventType.Group_Updated:
        msg = this.i18nService.t("editedGroupId", this.formatGroupId(ev));
        humanReadableMsg = this.i18nService.t("editedGroupId", this.getShortId(ev.groupId));
        break;
      case EventType.Group_Deleted:
        msg = this.i18nService.t("deletedGroupId", this.formatGroupId(ev));
        humanReadableMsg = this.i18nService.t("deletedGroupId", this.getShortId(ev.groupId));
        break;
      // Org user
      case EventType.OrganizationUser_Invited:
        msg = this.i18nService.t("invitedUserId", this.formatOrgUserId(ev));
        humanReadableMsg = this.i18nService.t(
          "invitedUserId",
          this.getShortId(ev.organizationUserId),
        );
        break;
      case EventType.OrganizationUser_Confirmed:
        msg = this.i18nService.t("confirmedUserId", this.formatOrgUserId(ev));
        humanReadableMsg = this.i18nService.t(
          "confirmedUserId",
          this.getShortId(ev.organizationUserId),
        );
        break;
      case EventType.OrganizationUser_Updated:
        msg = this.i18nService.t("editedUserId", this.formatOrgUserId(ev));
        humanReadableMsg = this.i18nService.t(
          "editedUserId",
          this.getShortId(ev.organizationUserId),
        );
        break;
      case EventType.OrganizationUser_Removed:
        msg = this.i18nService.t("removedUserId", this.formatOrgUserId(ev));
        humanReadableMsg = this.i18nService.t(
          "removedUserId",
          this.getShortId(ev.organizationUserId),
        );
        break;
      case EventType.OrganizationUser_UpdatedGroups:
        msg = this.i18nService.t("editedGroupsForUser", this.formatOrgUserId(ev));
        humanReadableMsg = this.i18nService.t(
          "editedGroupsForUser",
          this.getShortId(ev.organizationUserId),
        );
        break;
      case EventType.OrganizationUser_UnlinkedSso:
        msg = this.i18nService.t("unlinkedSsoUser", this.formatOrgUserId(ev));
        humanReadableMsg = this.i18nService.t(
          "unlinkedSsoUser",
          this.getShortId(ev.organizationUserId),
        );
        break;
      case EventType.OrganizationUser_ResetPassword_Enroll:
        msg = this.i18nService.t("eventEnrollAccountRecovery", this.formatOrgUserId(ev));
        humanReadableMsg = this.i18nService.t(
          "eventEnrollAccountRecovery",
          this.getShortId(ev.organizationUserId),
        );
        break;
      case EventType.OrganizationUser_ResetPassword_Withdraw:
        msg = this.i18nService.t("eventWithdrawAccountRecovery", this.formatOrgUserId(ev));
        humanReadableMsg = this.i18nService.t(
          "eventWithdrawAccountRecovery",
          this.getShortId(ev.organizationUserId),
        );
        break;
      case EventType.OrganizationUser_AdminResetPassword:
        msg = this.i18nService.t(
          "eventAccountRecoveryWithMasterPasswordInitiated",
          this.formatOrgUserId(ev),
        );
        humanReadableMsg = this.i18nService.t(
          "eventAccountRecoveryWithMasterPasswordInitiated",
          this.getShortId(ev.organizationUserId),
        );
        break;
      case EventType.OrganizationUser_AdminResetTwoFactor:
        msg = this.i18nService.t(
          "eventAccountRecoveryWithTwoStepLoginInitiated",
          this.formatOrgUserId(ev),
        );
        humanReadableMsg = this.i18nService.t(
          "eventAccountRecoveryWithTwoStepLoginInitiated",
          this.getShortId(ev.organizationUserId),
        );
        break;
      case EventType.OrganizationUser_ResetSsoLink:
        msg = this.i18nService.t("eventResetSsoLink", this.formatOrgUserId(ev));
        humanReadableMsg = this.i18nService.t(
          "eventResetSsoLink",
          this.getShortId(ev.organizationUserId),
        );
        break;
      case EventType.OrganizationUser_FirstSsoLogin:
        msg = this.i18nService.t("firstSsoLogin", this.formatOrgUserId(ev));
        humanReadableMsg = this.i18nService.t(
          "firstSsoLogin",
          this.getShortId(ev.organizationUserId),
        );
        break;
      case EventType.OrganizationUser_Revoked:
        msg = this.i18nService.t("revokedUserId", this.formatOrgUserId(ev));
        humanReadableMsg = this.i18nService.t(
          "revokedUserId",
          this.getShortId(ev.organizationUserId),
        );
        break;
      case EventType.OrganizationUser_Restored:
        msg = this.i18nService.t("restoredUserId", this.formatOrgUserId(ev));
        humanReadableMsg = this.i18nService.t(
          "restoredUserId",
          this.getShortId(ev.organizationUserId),
        );
        break;
      case EventType.OrganizationUser_Staged:
        msg = this.i18nService.t("stagedUserId", this.formatOrgUserId(ev));
        humanReadableMsg = this.i18nService.t(
          "stagedUserId",
          this.getShortId(ev.organizationUserId),
        );
        break;
      case EventType.OrganizationUser_ApprovedAuthRequest:
        msg = this.i18nService.t("approvedAuthRequest", this.formatOrgUserId(ev));
        humanReadableMsg = this.i18nService.t(
          "approvedAuthRequest",
          this.getShortId(ev.organizationUserId),
        );
        break;
      case EventType.OrganizationUser_RejectedAuthRequest:
        msg = this.i18nService.t("rejectedAuthRequest", this.formatOrgUserId(ev));
        humanReadableMsg = this.i18nService.t(
          "rejectedAuthRequest",
          this.getShortId(ev.organizationUserId),
        );
        break;
      case EventType.OrganizationUser_Deleted:
        msg = this.i18nService.t("deletedUserIdEventMessage", this.formatOrgUserId(ev));
        humanReadableMsg = this.i18nService.t(
          "deletedUserIdEventMessage",
          this.getShortId(ev.organizationUserId),
        );
        break;
      case EventType.OrganizationUser_Left:
        msg = this.i18nService.t("userLeftOrganization", this.formatOrgUserId(ev));
        humanReadableMsg = this.i18nService.t(
          "userLeftOrganization",
          this.getShortId(ev.organizationUserId),
        );
        break;
      case EventType.OrganizationUser_AutomaticallyConfirmed:
        msg = this.i18nService.t("automaticallyConfirmedUserId", this.formatOrgUserId(ev));
        humanReadableMsg = this.i18nService.t(
          "automaticallyConfirmedUserId",
          this.getShortId(ev.organizationUserId),
        );
        break;
      case EventType.OrganizationUser_SelfRevoked:
        msg = humanReadableMsg = this.i18nService.t("userSelfRevokedOrganizationOwnership");
        break;
      case EventType.OrganizationUser_Revoked_TwoFactorNonCompliance:
        msg = this.i18nService.t("revokedUserIdTwoFactorNonCompliance", this.formatOrgUserId(ev));
        humanReadableMsg = this.i18nService.t(
          "revokedUserIdTwoFactorNonCompliance",
          this.getShortId(ev.organizationUserId),
        );
        break;
      case EventType.OrganizationUser_Revoked_SingleOrganizationNonCompliance:
        msg = this.i18nService.t(
          "revokedUserIdSingleOrganizationNonCompliance",
          this.formatOrgUserId(ev),
        );
        humanReadableMsg = this.i18nService.t(
          "revokedUserIdSingleOrganizationNonCompliance",
          this.getShortId(ev.organizationUserId),
        );
        break;
      case EventType.OrganizationUser_NotificationBannerActionClicked:
        msg = humanReadableMsg = this.i18nService.t("clickedVaultBannerButton");
        break;
      case EventType.OrganizationUser_InviteLinkAccepted:
        msg = this.i18nService.t("inviteLinkEventAccepted", this.formatOrgUserId(ev));
        humanReadableMsg = this.i18nService.t(
          "inviteLinkEventAccepted",
          this.getShortId(ev.organizationUserId),
        );
        break;
      case EventType.OrganizationUser_AdminChangedEmail:
        msg = this.i18nService.t("eventAdminChangedUserEmail", this.formatOrgUserId(ev));
        humanReadableMsg = this.i18nService.t(
          "eventAdminChangedUserEmail",
          this.getShortId(ev.organizationUserId),
        );
        break;
      case EventType.OrganizationUser_InviteLinkConfirmed:
        msg = this.i18nService.t("inviteLinkEventMemberConfirmed", this.formatOrgUserId(ev));
        humanReadableMsg = this.i18nService.t(
          "inviteLinkEventMemberConfirmed",
          this.getShortId(ev.organizationUserId),
        );
        break;
      // Org
      case EventType.Organization_Updated:
        msg = humanReadableMsg = this.i18nService.t("editedOrgSettings");
        break;
      case EventType.Organization_PurgedVault:
        msg = humanReadableMsg = this.i18nService.t("purgedOrganizationVault");
        break;
      case EventType.Organization_ClientExportedVault:
        msg = humanReadableMsg = this.i18nService.t("exportedOrganizationVault");
        break;
      case EventType.Organization_VaultAccessed:
        msg = humanReadableMsg = this.i18nService.t("vaultAccessedByProvider");
        break;
      case EventType.Organization_EnabledSso:
        msg = humanReadableMsg = this.i18nService.t("enabledSso");
        break;
      case EventType.Organization_DisabledSso:
        msg = humanReadableMsg = this.i18nService.t("ssoTurnedOff");
        break;
      case EventType.Organization_EnabledKeyConnector:
        msg = humanReadableMsg = this.i18nService.t("enabledKeyConnector");
        break;
      case EventType.Organization_DisabledKeyConnector:
        msg = humanReadableMsg = this.i18nService.t("disabledKeyConnector");
        break;
      case EventType.Organization_SponsorshipsSynced:
        msg = humanReadableMsg = this.i18nService.t("sponsorshipsSynced");
        break;
      case EventType.Organization_CollectionManagementUpdated:
        msg = this.i18nService.t("modifiedCollectionManagement", this.formatOrganizationId(ev));
        humanReadableMsg = this.i18nService.t(
          "modifiedCollectionManagement",
          this.getShortId(ev.organizationId),
        );
        break;
      case EventType.Organization_CollectionManagement_LimitCollectionCreationEnabled:
        msg = this.i18nService.t("limitCollectionCreationEnabled", this.formatOrganizationId(ev));
        humanReadableMsg = this.i18nService.t(
          "limitCollectionCreationEnabled",
          this.getShortId(ev.organizationId),
        );
        break;
      case EventType.Organization_CollectionManagement_LimitCollectionCreationDisabled:
        msg = this.i18nService.t("limitCollectionCreationDisabled", this.formatOrganizationId(ev));
        humanReadableMsg = this.i18nService.t(
          "limitCollectionCreationDisabled",
          this.getShortId(ev.organizationId),
        );
        break;
      case EventType.Organization_CollectionManagement_LimitCollectionDeletionEnabled:
        msg = this.i18nService.t("limitCollectionDeletionEnabled", this.formatOrganizationId(ev));
        humanReadableMsg = this.i18nService.t(
          "limitCollectionDeletionEnabled",
          this.getShortId(ev.organizationId),
        );
        break;
      case EventType.Organization_CollectionManagement_LimitCollectionDeletionDisabled:
        msg = this.i18nService.t("limitCollectionDeletionDisabled", this.formatOrganizationId(ev));
        humanReadableMsg = this.i18nService.t(
          "limitCollectionDeletionDisabled",
          this.getShortId(ev.organizationId),
        );
        break;
      case EventType.Organization_CollectionManagement_LimitItemDeletionEnabled:
        msg = this.i18nService.t("limitItemDeletionEnabled", this.formatOrganizationId(ev));
        humanReadableMsg = this.i18nService.t(
          "limitItemDeletionEnabled",
          this.getShortId(ev.organizationId),
        );
        break;
      case EventType.Organization_CollectionManagement_LimitItemDeletionDisabled:
        msg = this.i18nService.t("limitItemDeletionDisabled", this.formatOrganizationId(ev));
        humanReadableMsg = this.i18nService.t(
          "limitItemDeletionDisabled",
          this.getShortId(ev.organizationId),
        );
        break;
      case EventType.Organization_CollectionManagement_AllowAdminAccessToAllCollectionItemsEnabled:
        msg = this.i18nService.t(
          "allowAdminAccessToAllCollectionItemsEnabled",
          this.formatOrganizationId(ev),
        );
        humanReadableMsg = this.i18nService.t(
          "allowAdminAccessToAllCollectionItemsEnabled",
          this.getShortId(ev.organizationId),
        );
        break;
      case EventType.Organization_CollectionManagement_AllowAdminAccessToAllCollectionItemsDisabled:
        msg = this.i18nService.t(
          "allowAdminAccessToAllCollectionItemsDisabled",
          this.formatOrganizationId(ev),
        );
        humanReadableMsg = this.i18nService.t(
          "allowAdminAccessToAllCollectionItemsDisabled",
          this.getShortId(ev.organizationId),
        );
        break;
      case EventType.Organization_ItemOrganization_Accepted:
        msg = humanReadableMsg = this.i18nService.t("userAcceptedTransfer");
        break;
      case EventType.Organization_ItemOrganization_Declined:
        msg = this.i18nService.t("revokedUserIdDeclinedTransfer", this.formatOrgUserId(ev));
        humanReadableMsg = this.i18nService.t(
          "revokedUserIdDeclinedTransfer",
          this.getShortId(ev.organizationUserId),
        );
        break;
      case EventType.Organization_AutoConfirmEnabled_Admin:
        msg = humanReadableMsg = this.i18nService.t("autoConfirmEnabledByAdmin");
        break;
      case EventType.Organization_AutoConfirmDisabled_Admin:
        msg = humanReadableMsg = this.i18nService.t("autoConfirmDisabledByAdmin");
        break;
      case EventType.Organization_AutoConfirmEnabled_Portal:
        msg = humanReadableMsg = this.i18nService.t("autoConfirmEnabledByPortal");
        break;
      case EventType.Organization_AutoConfirmDisabled_Portal:
        msg = humanReadableMsg = this.i18nService.t("autoConfirmDisabledByPortal");
        break;
      case EventType.Organization_InviteLinkCreated:
        msg = humanReadableMsg = this.i18nService.t("inviteLinkEventCreated");
        break;
      case EventType.Organization_InviteLinkDomainsEdited:
        msg = humanReadableMsg = this.i18nService.t("inviteLinkEventDomainsEdited");
        break;
      case EventType.Organization_InviteLinkDeleted:
        msg = humanReadableMsg = this.i18nService.t("inviteLinkEventDeleted");
        break;
      case EventType.Organization_InviteLinkClientCopied:
        msg = humanReadableMsg = this.i18nService.t("inviteLinkEventCopied");
        break;
      case EventType.Organization_InviteLinkRefreshed:
        msg = humanReadableMsg = this.i18nService.t("inviteLinkEventRegenerated");
        break;
      case EventType.Organization_InviteLinkConfirmEnabled:
        msg = humanReadableMsg = this.i18nService.t("inviteLinkEventConfirmEnabled");
        break;
      case EventType.Organization_InviteLinkConfirmDisabled:
        msg = humanReadableMsg = this.i18nService.t("inviteLinkEventConfirmDisabled");
        break;

      // Policies
      case EventType.Policy_Updated: {
        msg = this.i18nService.t("modifiedPolicyId", this.formatPolicyId(ev));

        const policy = this.policies.filter((p) => p.id === ev.policyId)[0];
        let p1 = this.getShortId(ev.policyId);
        if (policy != null) {
          p1 = PolicyType[policy.type];
        }

        humanReadableMsg = this.i18nService.t("modifiedPolicyId", p1);
        break;
      }
      // Provider users:
      case EventType.ProviderUser_Invited:
        msg = this.i18nService.t("invitedUserId", this.formatProviderUserId(ev));
        humanReadableMsg = this.i18nService.t("invitedUserId", this.getShortId(ev.providerUserId));
        break;
      case EventType.ProviderUser_Confirmed:
        msg = this.i18nService.t("confirmedUserId", this.formatProviderUserId(ev));
        humanReadableMsg = this.i18nService.t(
          "confirmedUserId",
          this.getShortId(ev.providerUserId),
        );
        break;
      case EventType.ProviderUser_Updated:
        msg = this.i18nService.t("editedUserId", this.formatProviderUserId(ev));
        humanReadableMsg = this.i18nService.t("editedUserId", this.getShortId(ev.providerUserId));
        break;
      case EventType.ProviderUser_Removed:
        msg = this.i18nService.t("removedUserId", this.formatProviderUserId(ev));
        humanReadableMsg = this.i18nService.t("removedUserId", this.getShortId(ev.providerUserId));
        break;
      case EventType.ProviderOrganization_Created:
        msg = this.i18nService.t("createdOrganizationId", this.formatProviderOrganizationId(ev));
        humanReadableMsg = this.i18nService.t(
          "createdOrganizationId",
          this.getShortId(ev.providerOrganizationId),
        );
        break;
      case EventType.ProviderOrganization_Added:
        msg = this.i18nService.t("addedOrganizationId", this.formatProviderOrganizationId(ev));
        humanReadableMsg = this.i18nService.t(
          "addedOrganizationId",
          this.getShortId(ev.providerOrganizationId),
        );
        break;
      case EventType.ProviderOrganization_Removed:
        msg = this.i18nService.t("removedOrganizationId", this.formatProviderOrganizationId(ev));
        humanReadableMsg = this.i18nService.t(
          "removedOrganizationId",
          this.getShortId(ev.providerOrganizationId),
        );
        break;
      case EventType.ProviderOrganization_VaultAccessed:
        msg = this.i18nService.t("accessedClientVault", this.formatProviderOrganizationId(ev));
        humanReadableMsg = this.i18nService.t(
          "accessedClientVault",
          this.getShortId(ev.providerOrganizationId),
        );
        break;
      // Org Domain claiming events
      case EventType.OrganizationDomain_Added:
        msg = humanReadableMsg = this.i18nService.t("addedDomain", this.escapeHtml(ev.domainName));
        break;
      case EventType.OrganizationDomain_Removed:
        msg = humanReadableMsg = this.i18nService.t(
          "removedDomain",
          this.escapeHtml(ev.domainName),
        );
        break;
      case EventType.OrganizationDomain_Verified:
        msg = humanReadableMsg = this.i18nService.t(
          "domainClaimedEvent",
          this.escapeHtml(ev.domainName),
        );
        break;
      case EventType.OrganizationDomain_NotVerified:
        msg = humanReadableMsg = this.i18nService.t(
          "domainNotClaimedEvent",
          this.escapeHtml(ev.domainName),
        );
        break;
      // Secrets Manager
      case EventType.Secret_Retrieved:
        msg = this.i18nService.t("accessedSecretWithId", this.formatSecretId(ev, options));
        humanReadableMsg = this.i18nService.t("accessedSecretWithId", this.getShortId(ev.secretId));
        break;
      case EventType.Secret_Created:
        msg = this.i18nService.t("createdSecretWithId", this.formatSecretId(ev, options));
        humanReadableMsg = this.i18nService.t("createdSecretWithId", this.getShortId(ev.secretId));
        break;
      case EventType.Secret_Deleted:
        msg = this.i18nService.t("deletedSecretWithId", this.formatSecretId(ev, options));
        humanReadableMsg = this.i18nService.t("deletedSecretWithId", this.getShortId(ev.secretId));
        break;
      case EventType.Secret_Permanently_Deleted:
        msg = this.i18nService.t(
          "permanentlyDeletedSecretWithId",
          this.formatSecretId(ev, options),
        );
        humanReadableMsg = this.i18nService.t(
          "permanentlyDeletedSecretWithId",
          this.getShortId(ev.secretId),
        );
        break;
      case EventType.Secret_Restored:
        msg = this.i18nService.t("restoredSecretWithId", this.formatSecretId(ev, options));
        humanReadableMsg = this.i18nService.t("restoredSecretWithId", this.getShortId(ev.secretId));
        break;
      case EventType.Secret_Edited:
        msg = this.i18nService.t("editedSecretWithId", this.formatSecretId(ev, options));
        humanReadableMsg = this.i18nService.t("editedSecretWithId", this.getShortId(ev.secretId));
        break;
      case EventType.Project_Retrieved:
        msg = this.i18nService.t(
          "accessedProjectWithIdentifier",
          this.formatProjectId(ev, options),
        );
        humanReadableMsg = this.i18nService.t(
          "accessedProjectWithIdentifier",
          this.getShortId(ev.projectId),
        );
        break;
      case EventType.Project_Created:
        msg = this.i18nService.t("createdProjectWithId", this.formatProjectId(ev, options));
        humanReadableMsg = this.i18nService.t(
          "createdProjectWithId",
          this.getShortId(ev.projectId),
        );
        break;
      case EventType.Project_Deleted:
        msg = this.i18nService.t("deletedProjectWithId", this.formatProjectId(ev, options));
        humanReadableMsg = this.i18nService.t(
          "deletedProjectWithId",
          this.getShortId(ev.projectId),
        );
        break;
      case EventType.Project_Edited:
        msg = this.i18nService.t("editedProjectWithId", this.formatProjectId(ev, options));
        humanReadableMsg = this.i18nService.t("editedProjectWithId", this.getShortId(ev.projectId));
        break;
      case EventType.ServiceAccount_UserAdded:
        msg = this.i18nService.t(
          "addedUserToServiceAccountWithId",
          this.formatUserId(ev, options),
          this.formatServiceAccountId(ev, options),
        );
        humanReadableMsg = this.i18nService.t(
          "addedUserToServiceAccountWithId",
          this.formatUserId(ev, options),
          this.formatServiceAccountId(ev, options),
        );
        break;
      case EventType.ServiceAccount_UserRemoved:
        msg = this.i18nService.t(
          "removedUserToServiceAccountWithId",
          this.formatUserId(ev, options),
          this.formatServiceAccountId(ev, options),
        );
        humanReadableMsg = this.i18nService.t(
          "removedUserToServiceAccountWithId",
          this.formatUserId(ev, options),
          this.formatServiceAccountId(ev, options),
        );
        break;
      case EventType.ServiceAccount_GroupRemoved:
        msg = this.i18nService.t(
          "removedGroupFromServiceAccountWithId",
          this.formatGroupId(ev),
          this.formatServiceAccountId(ev, options),
        );
        humanReadableMsg = this.i18nService.t(
          "removedGroupFromServiceAccountWithId",
          this.formatGroupId(ev),
          this.formatServiceAccountId(ev, options),
        );
        break;
      case EventType.ServiceAccount_GroupAdded:
        msg = this.i18nService.t(
          "addedGroupToServiceAccountId",
          this.formatGroupId(ev),
          this.formatServiceAccountId(ev, options),
        );
        humanReadableMsg = this.i18nService.t(
          "addedGroupToServiceAccountId",
          this.formatGroupId(ev),
          this.formatServiceAccountId(ev, options),
        );
        break;
      case EventType.ServiceAccount_Created:
        msg = this.i18nService.t(
          "serviceAccountCreatedWithId",
          this.formatServiceAccountId(ev, options),
        );
        humanReadableMsg = this.i18nService.t(
          "serviceAccountCreatedWithId",
          this.formatServiceAccountId(ev, options),
        );
        break;
      case EventType.ServiceAccount_Deleted:
        msg = this.i18nService.t(
          "serviceAccountDeletedWithId",
          this.formatServiceAccountId(ev, options),
        );
        humanReadableMsg = this.i18nService.t(
          "serviceAccountDeletedWithId",
          this.formatServiceAccountId(ev, options),
        );
        break;
      case EventType.PhishingBlocker_SiteAccessed:
        msg = this.i18nService.t("phishingBlockerSiteAccessed");
        humanReadableMsg = this.i18nService.t("phishingBlockerSiteAccessed");
        break;
      case EventType.PhishingBlocker_SiteExited:
        msg = this.i18nService.t("phishingBlockerSiteExited");
        humanReadableMsg = this.i18nService.t("phishingBlockerSiteExited");
        break;
      case EventType.PhishingBlocker_Bypassed:
        msg = this.i18nService.t("phishingBlockerBypassed");
        humanReadableMsg = this.i18nService.t("phishingBlockerBypassed");
        break;
      // Send
      case EventType.Send_Created_Text:
        msg = this.i18nService.t("createdTextSendV2", this.formatSendId(ev, options));
        humanReadableMsg = this.i18nService.t(
          "createdTextSendV2",
          this.formatSendIdText(ev, options),
        );
        break;
      case EventType.Send_Created_Text_WithEmailVerification:
        msg = this.i18nService.t(
          "createdTextSendWithEmailVerificationV2",
          this.formatSendId(ev, options),
        );
        humanReadableMsg = this.i18nService.t(
          "createdTextSendWithEmailVerificationV2",
          this.formatSendIdText(ev, options),
        );
        break;
      case EventType.Send_Created_Text_WithPasswordProtection:
        msg = this.i18nService.t(
          "createdTextSendWithPasswordProtectionV2",
          this.formatSendId(ev, options),
        );
        humanReadableMsg = this.i18nService.t(
          "createdTextSendWithPasswordProtectionV2",
          this.formatSendIdText(ev, options),
        );
        break;
      case EventType.Send_Created_File:
        msg = this.i18nService.t("createdFileSendV2", this.formatSendId(ev, options));
        humanReadableMsg = this.i18nService.t(
          "createdFileSendV2",
          this.formatSendIdText(ev, options),
        );
        break;
      case EventType.Send_Created_File_WithEmailVerification:
        msg = this.i18nService.t(
          "createdFileSendWithEmailVerificationV2",
          this.formatSendId(ev, options),
        );
        humanReadableMsg = this.i18nService.t(
          "createdFileSendWithEmailVerificationV2",
          this.formatSendIdText(ev, options),
        );
        break;
      case EventType.Send_Created_File_WithPasswordProtection:
        msg = this.i18nService.t(
          "createdFileSendWithPasswordProtectionV2",
          this.formatSendId(ev, options),
        );
        humanReadableMsg = this.i18nService.t(
          "createdFileSendWithPasswordProtectionV2",
          this.formatSendIdText(ev, options),
        );
        break;
      case EventType.Send_Updated_Text:
        msg = this.i18nService.t("editedTextSendV2", this.formatSendId(ev, options));
        humanReadableMsg = this.i18nService.t(
          "editedTextSendV2",
          this.formatSendIdText(ev, options),
        );
        break;
      case EventType.Send_Updated_File:
        msg = this.i18nService.t("editedFileSendV2", this.formatSendId(ev, options));
        humanReadableMsg = this.i18nService.t(
          "editedFileSendV2",
          this.formatSendIdText(ev, options),
        );
        break;
      case EventType.Send_Deleted_Text:
        msg = this.i18nService.t("deletedTextSendV2", this.formatSendId(ev, options));
        humanReadableMsg = this.i18nService.t(
          "deletedTextSendV2",
          this.formatSendIdText(ev, options),
        );
        break;
      case EventType.Send_Deleted_File:
        msg = this.i18nService.t("deletedFileSendV2", this.formatSendId(ev, options));
        humanReadableMsg = this.i18nService.t(
          "deletedFileSendV2",
          this.formatSendIdText(ev, options),
        );
        break;
      case EventType.Send_Accessed_Text:
        msg = this.i18nService.t(
          "accessedTextSendV3",
          this.formatSendId(ev, options),
          this.formatSendCreatorId(ev, options),
        );
        humanReadableMsg = this.i18nService.t(
          "accessedTextSendV3",
          this.formatSendIdText(ev, options),
          this.getShortId(ev.userId),
        );
        break;
      case EventType.Send_Accessed_File:
        msg = this.i18nService.t(
          "accessedFileSendV3",
          this.formatSendId(ev, options),
          this.formatSendCreatorId(ev, options),
        );
        humanReadableMsg = this.i18nService.t(
          "accessedFileSendV3",
          this.formatSendIdText(ev, options),
          this.getShortId(ev.userId),
        );
        break;

      default:
        break;
    }
    return {
      message: msg === "" ? null : msg,
      humanReadableMessage: humanReadableMsg === "" ? null : humanReadableMsg,
    };
  }

  // Produces the updated terminology/personalization for event types that have one; returns
  // empty strings for anything else (including cases where an organization name was needed but
  // couldn't be resolved), signaling getEventMessage to fall back to its own messaging.
  private getEventMessageVfo1(
    ev: EventResponse,
    options: EventOptions,
  ): { msg: string; humanReadableMsg: string } {
    let msg = "";
    let humanReadableMsg = "";

    switch (ev.type) {
      case EventType.Cipher_Shared: {
        const orgName = this.resolveOrgName(ev, options);
        if (orgName != null) {
          msg = this.i18nService.t(
            "movedItemIdToOrgWithName",
            this.formatCipherId(ev, options),
            this.escapeHtml(orgName),
          );
          humanReadableMsg = this.i18nService.t(
            "movedItemIdToOrgWithName",
            this.getShortId(ev.cipherId),
            orgName,
          );
        }
        break;
      }
      case EventType.Cipher_UpdatedCollections:
        msg = this.i18nService.t("editedSharedFoldersForItem", this.formatCipherId(ev, options));
        humanReadableMsg = this.i18nService.t(
          "editedSharedFoldersForItem",
          this.getShortId(ev.cipherId),
        );
        break;
      case EventType.Collection_Created:
        msg = this.i18nService.t("createdSharedFolderId", this.formatCollectionId(ev));
        humanReadableMsg = this.i18nService.t(
          "createdSharedFolderId",
          this.getShortId(ev.collectionId),
        );
        break;
      case EventType.Collection_Updated:
        msg = this.i18nService.t("editedSharedFolderId", this.formatCollectionId(ev));
        humanReadableMsg = this.i18nService.t(
          "editedSharedFolderId",
          this.getShortId(ev.collectionId),
        );
        break;
      case EventType.Collection_Deleted:
        msg = this.i18nService.t("deletedSharedFolderId", this.formatCollectionId(ev));
        humanReadableMsg = this.i18nService.t(
          "deletedSharedFolderId",
          this.getShortId(ev.collectionId),
        );
        break;
      case EventType.OrganizationUser_Revoked: {
        const orgName = this.resolveOrgName(ev, options);
        if (orgName != null) {
          msg = this.i18nService.t(
            "revokedUserIdWithOrgName",
            this.formatOrgUserId(ev),
            this.escapeHtml(orgName),
          );
          humanReadableMsg = this.i18nService.t(
            "revokedUserIdWithOrgName",
            this.getShortId(ev.organizationUserId),
            orgName,
          );
        }
        break;
      }
      case EventType.OrganizationUser_Restored: {
        const orgName = this.resolveOrgName(ev, options);
        if (orgName != null) {
          msg = this.i18nService.t(
            "restoredUserIdWithOrgName",
            this.formatOrgUserId(ev),
            this.escapeHtml(orgName),
          );
          humanReadableMsg = this.i18nService.t(
            "restoredUserIdWithOrgName",
            this.getShortId(ev.organizationUserId),
            orgName,
          );
        }
        break;
      }
      case EventType.OrganizationUser_Left: {
        const orgName = this.resolveOrgName(ev, options);
        if (orgName != null) {
          msg = this.i18nService.t(
            "userLeftOrganizationWithName",
            this.formatOrgUserId(ev),
            this.escapeHtml(orgName),
          );
          humanReadableMsg = this.i18nService.t(
            "userLeftOrganizationWithName",
            this.getShortId(ev.organizationUserId),
            orgName,
          );
        }
        break;
      }
      case EventType.OrganizationUser_SelfRevoked: {
        const orgName = this.resolveOrgName(ev, options);
        if (orgName != null) {
          msg = this.i18nService.t(
            "userSelfRevokedOrganizationOwnershipWithName",
            this.escapeHtml(orgName),
          );
          humanReadableMsg = this.i18nService.t(
            "userSelfRevokedOrganizationOwnershipWithName",
            orgName,
          );
        }
        break;
      }
      case EventType.OrganizationUser_Revoked_TwoFactorNonCompliance: {
        const orgName = this.resolveOrgName(ev, options);
        if (orgName != null) {
          msg = this.i18nService.t(
            "revokedUserIdTwoFactorNonComplianceWithOrgName",
            this.formatOrgUserId(ev),
            this.escapeHtml(orgName),
          );
          humanReadableMsg = this.i18nService.t(
            "revokedUserIdTwoFactorNonComplianceWithOrgName",
            this.getShortId(ev.organizationUserId),
            orgName,
          );
        }
        break;
      }
      case EventType.OrganizationUser_Revoked_SingleOrganizationNonCompliance: {
        const orgName = this.resolveOrgName(ev, options);
        if (orgName != null) {
          msg = this.i18nService.t(
            "revokedUserIdSingleOrganizationNonComplianceWithOrgName",
            this.formatOrgUserId(ev),
            this.escapeHtml(orgName),
          );
          humanReadableMsg = this.i18nService.t(
            "revokedUserIdSingleOrganizationNonComplianceWithOrgName",
            this.getShortId(ev.organizationUserId),
            orgName,
          );
        }
        break;
      }
      case EventType.Organization_Updated: {
        const orgName = this.resolveOrgName(ev, options);
        if (orgName != null) {
          msg = this.i18nService.t("editedOrgSettingsWithName", this.escapeHtml(orgName));
          humanReadableMsg = this.i18nService.t("editedOrgSettingsWithName", orgName);
        }
        break;
      }
      case EventType.Organization_PurgedVault: {
        const orgName = this.resolveOrgName(ev, options);
        if (orgName != null) {
          msg = this.i18nService.t("purgedOrganizationVaultWithName", this.escapeHtml(orgName));
          humanReadableMsg = this.i18nService.t("purgedOrganizationVaultWithName", orgName);
        }
        break;
      }
      case EventType.Organization_ClientExportedVault: {
        const orgName = this.resolveOrgName(ev, options);
        if (orgName != null) {
          msg = this.i18nService.t("exportedOrganizationVaultWithName", this.escapeHtml(orgName));
          humanReadableMsg = this.i18nService.t("exportedOrganizationVaultWithName", orgName);
        }
        break;
      }
      case EventType.Organization_CollectionManagementUpdated:
        msg = this.i18nService.t("modifiedSharedFolderManagement", this.formatOrganizationId(ev));
        humanReadableMsg = this.i18nService.t(
          "modifiedSharedFolderManagement",
          this.getShortId(ev.organizationId),
        );
        break;
      case EventType.Organization_CollectionManagement_LimitCollectionCreationEnabled:
        msg = this.i18nService.t("limitSharedFolderCreationEnabled", this.formatOrganizationId(ev));
        humanReadableMsg = this.i18nService.t(
          "limitSharedFolderCreationEnabled",
          this.getShortId(ev.organizationId),
        );
        break;
      case EventType.Organization_CollectionManagement_LimitCollectionCreationDisabled:
        msg = this.i18nService.t(
          "limitSharedFolderCreationDisabled",
          this.formatOrganizationId(ev),
        );
        humanReadableMsg = this.i18nService.t(
          "limitSharedFolderCreationDisabled",
          this.getShortId(ev.organizationId),
        );
        break;
      case EventType.Organization_CollectionManagement_LimitCollectionDeletionEnabled:
        msg = this.i18nService.t("limitSharedFolderDeletionEnabled", this.formatOrganizationId(ev));
        humanReadableMsg = this.i18nService.t(
          "limitSharedFolderDeletionEnabled",
          this.getShortId(ev.organizationId),
        );
        break;
      case EventType.Organization_CollectionManagement_LimitCollectionDeletionDisabled:
        msg = this.i18nService.t(
          "limitSharedFolderDeletionDisabled",
          this.formatOrganizationId(ev),
        );
        humanReadableMsg = this.i18nService.t(
          "limitSharedFolderDeletionDisabled",
          this.getShortId(ev.organizationId),
        );
        break;
      case EventType.Organization_CollectionManagement_AllowAdminAccessToAllCollectionItemsEnabled:
        msg = this.i18nService.t(
          "allowAdminAccessToAllSharedFolderItemsEnabled",
          this.formatOrganizationId(ev),
        );
        humanReadableMsg = this.i18nService.t(
          "allowAdminAccessToAllSharedFolderItemsEnabled",
          this.getShortId(ev.organizationId),
        );
        break;
      case EventType.Organization_CollectionManagement_AllowAdminAccessToAllCollectionItemsDisabled:
        msg = this.i18nService.t(
          "allowAdminAccessToAllSharedFolderItemsDisabled",
          this.formatOrganizationId(ev),
        );
        humanReadableMsg = this.i18nService.t(
          "allowAdminAccessToAllSharedFolderItemsDisabled",
          this.getShortId(ev.organizationId),
        );
        break;
      case EventType.Organization_ItemOrganization_Accepted: {
        const orgName = this.resolveOrgName(ev, options);
        if (orgName != null) {
          msg = this.i18nService.t("userAcceptedTransferWithOrgName", this.escapeHtml(orgName));
          humanReadableMsg = this.i18nService.t("userAcceptedTransferWithOrgName", orgName);
        }
        break;
      }
      case EventType.Organization_ItemOrganization_Declined: {
        const orgName = this.resolveOrgName(ev, options);
        if (orgName != null) {
          msg = this.i18nService.t(
            "revokedUserIdDeclinedTransferWithOrgName",
            this.formatOrgUserId(ev),
            this.escapeHtml(orgName),
          );
          humanReadableMsg = this.i18nService.t(
            "revokedUserIdDeclinedTransferWithOrgName",
            this.getShortId(ev.organizationUserId),
            orgName,
          );
        }
        break;
      }
      case EventType.ProviderOrganization_Created: {
        const orgName = this.resolveOrgName(ev, options);
        if (orgName != null) {
          msg = this.i18nService.t(
            "createdOrganizationIdWithName",
            this.formatProviderOrganizationId(ev),
            this.escapeHtml(orgName),
          );
          humanReadableMsg = this.i18nService.t(
            "createdOrganizationIdWithName",
            this.getShortId(ev.providerOrganizationId),
            orgName,
          );
        }
        break;
      }
      case EventType.ProviderOrganization_Added: {
        const orgName = this.resolveOrgName(ev, options);
        if (orgName != null) {
          msg = this.i18nService.t(
            "addedOrganizationIdWithName",
            this.formatProviderOrganizationId(ev),
            this.escapeHtml(orgName),
          );
          humanReadableMsg = this.i18nService.t(
            "addedOrganizationIdWithName",
            this.getShortId(ev.providerOrganizationId),
            orgName,
          );
        }
        break;
      }
      case EventType.ProviderOrganization_Removed: {
        const orgName = this.resolveOrgName(ev, options);
        if (orgName != null) {
          msg = this.i18nService.t(
            "removedOrganizationIdWithName",
            this.formatProviderOrganizationId(ev),
            this.escapeHtml(orgName),
          );
          humanReadableMsg = this.i18nService.t(
            "removedOrganizationIdWithName",
            this.getShortId(ev.providerOrganizationId),
            orgName,
          );
        }
        break;
      }
      case EventType.ProviderOrganization_VaultAccessed: {
        const orgName = this.resolveOrgName(ev, options);
        if (orgName != null) {
          msg = this.i18nService.t(
            "accessedClientVaultWithName",
            this.formatProviderOrganizationId(ev),
            this.escapeHtml(orgName),
          );
          humanReadableMsg = this.i18nService.t(
            "accessedClientVaultWithName",
            this.getShortId(ev.providerOrganizationId),
            orgName,
          );
        }
        break;
      }
      default:
        break;
    }

    return { msg, humanReadableMsg };
  }

  private getAppInfo(ev: EventResponse): [BitwardenIcon, string] {
    if (ev.serviceAccountId) {
      return ["bwi-globe", this.i18nService.t("sdk")];
    }

    switch (ev.deviceType) {
      case DeviceType.Android:
        return ["bwi-mobile", this.i18nService.t("mobile") + " - Android"];
      case DeviceType.iOS:
        return ["bwi-mobile", this.i18nService.t("mobile") + " - iOS"];
      case DeviceType.UWP:
        return ["bwi-mobile", this.i18nService.t("mobile") + " - Windows"];
      case DeviceType.ChromeExtension:
        return ["bwi-puzzle", this.i18nService.t("extension") + " - Chrome"];
      case DeviceType.FirefoxExtension:
        return ["bwi-puzzle", this.i18nService.t("extension") + " - Firefox"];
      case DeviceType.OperaExtension:
        return ["bwi-puzzle", this.i18nService.t("extension") + " - Opera"];
      case DeviceType.EdgeExtension:
        return ["bwi-puzzle", this.i18nService.t("extension") + " - Edge"];
      case DeviceType.VivaldiExtension:
        return ["bwi-puzzle", this.i18nService.t("extension") + " - Vivaldi"];
      case DeviceType.SafariExtension:
        return ["bwi-puzzle", this.i18nService.t("extension") + " - Safari"];
      case DeviceType.WindowsDesktop:
        return ["bwi-desktop", this.i18nService.t("desktop") + " - Windows"];
      case DeviceType.MacOsDesktop:
        return ["bwi-desktop", this.i18nService.t("desktop") + " - macOS"];
      case DeviceType.LinuxDesktop:
        return ["bwi-desktop", this.i18nService.t("desktop") + " - Linux"];
      case DeviceType.ChromeBrowser:
        return ["bwi-browser", this.i18nService.t("webVault") + " - Chrome"];
      case DeviceType.FirefoxBrowser:
        return ["bwi-browser", this.i18nService.t("webVault") + " - Firefox"];
      case DeviceType.OperaBrowser:
        return ["bwi-browser", this.i18nService.t("webVault") + " - Opera"];
      case DeviceType.SafariBrowser:
        return ["bwi-browser", this.i18nService.t("webVault") + " - Safari"];
      case DeviceType.VivaldiBrowser:
        return ["bwi-browser", this.i18nService.t("webVault") + " - Vivaldi"];
      case DeviceType.EdgeBrowser:
        return ["bwi-browser", this.i18nService.t("webVault") + " - Edge"];
      case DeviceType.IEBrowser:
        return ["bwi-browser", this.i18nService.t("webVault") + " - IE"];
      case DeviceType.DuckDuckGoBrowser:
        return ["bwi-browser", this.i18nService.t("webVault") + " - DuckDuckGo"];
      case DeviceType.Server:
        return ["bwi-user-monitor", this.i18nService.t("server")];
      case DeviceType.WindowsCLI:
        return ["bwi-cli", this.i18nService.t("cli") + " - Windows"];
      case DeviceType.MacOsCLI:
        return ["bwi-cli", this.i18nService.t("cli") + " - macOS"];
      case DeviceType.LinuxCLI:
        return ["bwi-cli", this.i18nService.t("cli") + " - Linux"];
      case DeviceType.UnknownBrowser:
        return [
          "bwi-browser",
          this.i18nService.t("webVault") + " - " + this.i18nService.t("unknown"),
        ];
      default:
        return ["bwi-globe", this.i18nService.t("unknown")];
    }
  }

  private formatCipherId(ev: EventResponse, options: EventOptions) {
    const shortId = this.getShortId(ev.cipherId);
    if (ev.organizationId == null || !options.cipherInfo) {
      return "<code>" + this.escapeHtml(shortId) + "</code>";
    }
    const a = this.makeAnchor(shortId);
    a.setAttribute(
      "href",
      `#/organizations/${ev.organizationId}/vault?search=${shortId}&viewEvents=${ev.cipherId}&type=all`,
    );
    return a.outerHTML;
  }

  private formatGroupId(ev: EventResponse) {
    const shortId = this.getShortId(ev.groupId);
    const a = this.makeAnchor(shortId);
    a.setAttribute("href", "#/organizations/" + ev.organizationId + "/groups?search=" + shortId);
    return a.outerHTML;
  }

  private formatCollectionId(ev: EventResponse) {
    const shortId = this.getShortId(ev.collectionId);
    const a = this.makeAnchor(shortId);
    a.setAttribute(
      "href",
      `#/organizations/${ev.organizationId}/vault?collectionId=${ev.collectionId}`,
    );
    return a.outerHTML;
  }

  private formatOrgUserId(ev: EventResponse) {
    const shortId = this.getShortId(ev.organizationUserId);
    const a = this.makeAnchor(shortId);
    a.setAttribute(
      "href",
      "#/organizations/" +
        ev.organizationId +
        "/members?search=" +
        shortId +
        "&viewEvents=" +
        ev.organizationUserId,
    );
    return a.outerHTML;
  }

  private formatProviderUserId(ev: EventResponse) {
    const shortId = this.getShortId(ev.providerUserId);
    const a = this.makeAnchor(shortId);
    a.setAttribute(
      "href",
      "#/providers/" +
        ev.providerId +
        "/manage/people?search=" +
        shortId +
        "&viewEvents=" +
        ev.providerUserId,
    );
    return a.outerHTML;
  }

  private formatProviderOrganizationId(ev: EventResponse) {
    const shortId = this.getShortId(ev.providerOrganizationId);
    const a = this.makeAnchor(shortId);
    a.setAttribute("href", "#/providers/" + ev.providerId + "/clients?search=" + shortId);
    return a.outerHTML;
  }

  private formatOrganizationId(ev: EventResponse) {
    const shortId = this.getShortId(ev.organizationId);
    const a = this.makeAnchor(shortId);
    a.setAttribute("href", "#/organizations/" + ev.organizationId + "/settings/account");
    return a.outerHTML;
  }

  private formatPolicyId(ev: EventResponse) {
    const shortId = this.getShortId(ev.policyId);
    const a = this.makeAnchor(shortId);
    a.setAttribute(
      "href",
      "#/organizations/" + ev.organizationId + "/settings/policies?policyId=" + ev.policyId,
    );
    return a.outerHTML;
  }

  formatSecretId(ev: EventResponse, options: EventOptions): string {
    const shortId = this.getShortId(ev.secretId);
    if (options.disableLink) {
      return shortId;
    }
    const a = this.makeAnchor(shortId);
    a.setAttribute(
      "href",
      "#/sm/" +
        ev.organizationId +
        "/secrets?search=" +
        shortId +
        "&viewEvents=" +
        ev.secretId +
        "&type=all",
    );
    return a.outerHTML;
  }

  formatServiceAccountId(ev: EventResponse, options: EventOptions): string {
    const shortId = this.getShortId(ev.grantedServiceAccountId);
    if (options.disableLink) {
      return shortId;
    }
    const a = this.makeAnchor(shortId);
    a.setAttribute(
      "href",
      "#/sm/" +
        ev.organizationId +
        "/machine-accounts?search=" +
        shortId +
        "&viewEvents=" +
        ev.grantedServiceAccountId +
        "&type=all",
    );
    return a.outerHTML;
  }

  formatUserId(ev: EventResponse, options: EventOptions): string {
    const shortId = this.getShortId(ev.userId);
    if (options.disableLink) {
      return shortId;
    }
    const a = this.makeAnchor(shortId);
    a.setAttribute("href", "#/organizations/" + ev.organizationId + "/members?search=" + shortId);
    return a.outerHTML;
  }

  formatProjectId(ev: EventResponse, options: EventOptions): string {
    const shortId = this.getShortId(ev.projectId);
    if (options.disableLink) {
      return shortId;
    }
    const a = this.makeAnchor(shortId);
    a.setAttribute(
      "href",
      "#/sm/" +
        ev.organizationId +
        "/projects?search=" +
        shortId +
        "&viewEvents=" +
        ev.projectId +
        "&type=all",
    );
    return a.outerHTML;
  }

  private makeAnchor(shortId: string) {
    const a = document.createElement("a");
    a.title = this.i18nService.t("view");
    const code = document.createElement("code");
    code.textContent = shortId;
    a.appendChild(code);
    return a;
  }

  private formatSendId(ev: EventResponse, options: EventOptions): string {
    if (options.hideSendId || ev.sendId == null) {
      return "";
    }
    const shortId = this.getShortId(ev.sendId);
    const a = this.makeAnchor(shortId);
    a.title = this.i18nService.t("viewSendEvents", shortId);
    a.setAttribute("href", SEND_EVENTS_HREF_PREFIX + ev.sendId);
    return " " + a.outerHTML;
  }

  private formatSendIdText(ev: EventResponse, options: EventOptions): string {
    return options.hideSendId || ev.sendId == null ? "" : " " + this.getShortId(ev.sendId);
  }

  private formatSendCreatorId(ev: EventResponse, options: EventOptions): string {
    if (ev.userId == null) {
      return "";
    }
    const shortId = this.getShortId(ev.userId);
    // Render plain text (no link) when the creator is not a member we can open events for
    if (options.linkableMemberIds != null && !options.linkableMemberIds.has(ev.userId)) {
      return "<code>" + this.escapeHtml(shortId) + "</code>";
    }
    const a = this.makeAnchor(shortId);
    a.title = this.i18nService.t("viewMemberEvents", shortId);
    a.setAttribute("href", MEMBER_EVENTS_HREF_PREFIX + ev.userId);
    return a.outerHTML;
  }

  private getShortId(id: string) {
    return id?.substring(0, 8);
  }

  // Resolves the display name of the organization an event pertains to. Returns undefined if the
  // caller hasn't wired up a resolver or it can't resolve one, so callers can fall back to
  // messaging that doesn't reference an organization name.
  private resolveOrgName(ev: EventResponse, options: EventOptions): string | undefined {
    return options.getOrganizationName?.(ev);
  }

  private escapeHtml(unsafe: string): string {
    if (!unsafe) {
      return unsafe;
    }
    const div = document.createElement("div");
    div.textContent = unsafe;
    return div.innerHTML;
  }

  private toDateTimeLocalString(date: Date) {
    return (
      date.getFullYear() +
      "-" +
      this.pad(date.getMonth() + 1) +
      "-" +
      this.pad(date.getDate()) +
      "T" +
      this.pad(date.getHours()) +
      ":" +
      this.pad(date.getMinutes())
    );
  }

  private pad(num: number) {
    const norm = Math.floor(Math.abs(num));
    return (norm < 10 ? "0" : "") + norm;
  }
}

export class EventInfo {
  message: string;
  humanReadableMessage: string;
  appIcon: BitwardenIcon;
  appName: string;
}

export class EventOptions {
  cipherInfo = true;
  disableLink = false;
  // Set when rendering inside a Send-scoped dialog, where repeating the Send id on every row is redundant.
  hideSendId = false;
  // User ids whose member events can be opened. When provided, the Send creator id renders
  // as a link only if its id is in this set; otherwise it renders as plain text, since clicking a
  // non-member's id would do nothing. An empty set means nothing is linkable; leaving it undefined
  // keeps all creator ids linked (for callers that don't gate on membership).
  linkableMemberIds?: ReadonlySet<string>;
  // Resolves the display name of the organization an event pertains to, used to personalize
  // organization-level event copy (e.g. "Purged Acme Inc vault."). A function rather than a plain
  // string because a single batch of events can span multiple organizations (e.g. the provider
  // events page, where each row references a different client organization).
  getOrganizationName?: (ev: EventResponse) => string | undefined;
}
