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

import type { CallSummary } from '@signalapp/ringrtc';
import type { ThunkAction } from 'redux-thunk';
import type { ReadonlyDeep } from 'type-fest';
import OS from '../../util/os/osMain.node.js';
import type { ExplodePromiseResultType } from '../../util/explodePromise.std.js';
import type {
  GroupV2PendingMemberType,
  ReadonlyMessageAttributesType,
} from '../../model-types.d.ts';
import type {
  ActionCreator,
  MessageChangedActionType,
  MessageDeletedActionType,
} from './conversations.preload.js';
import type { MessagePropsType } from '../selectors/message.preload.js';
import type { RecipientsByConversation } from './stories.preload.js';
import type { SafetyNumberChangeSource } from '../../types/SafetyNumberChangeSource.std.js';
import type { StateType as RootStateType } from '../reducer.preload.js';
import * as SingleServePromise from '../../services/singleServePromise.std.js';
import { isKeyTransparencyAvailable } from '../../services/keyTransparency.preload.js';
import * as Stickers from '../../types/Stickers.preload.js';
import type { ContactModalStateType } from '../../types/globalModals.std.js';
import { UsernameOnboardingState } from '../../types/globalModals.std.js';
import { createLogger } from '../../logging/log.std.js';
import {
  getMessagePropsSelector,
  getPropsForAttachment,
} from '../selectors/message.preload.js';
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions.std.js';
import { longRunningTaskWrapper } from '../../util/longRunningTaskWrapper.dom.js';
import { useBoundActions } from '../../hooks/useBoundActions.std.js';
import { isGroupV1 } from '../../util/whatTypeOfConversation.dom.js';
import { sleep } from '../../util/sleep.std.js';
import { SECOND } from '../../util/durations/index.std.js';
import { getGroupMigrationMembers } from '../../groups.preload.js';
import {
  MESSAGE_CHANGED,
  MESSAGE_DELETED,
  actions as conversationsActions,
} from './conversations.preload.js';
import { isDownloaded } from '../../util/Attachment.std.js';
import { isPermanentlyUndownloadable } from '../../jobs/AttachmentDownloadManager.preload.js';
import type { ButtonVariant } from '../../components/Button.dom.js';
import type { MessageRequestState } from '../../components/conversation/MessageRequestActionsConfirmation.dom.js';
import type { MessageForwardDraft } from '../../types/ForwardDraft.std.js';
import { hydrateRanges } from '../../util/BodyRange.node.js';
import {
  getConversationSelector,
  getHasMaxPinnedMessages,
  type GetConversationByIdType,
} from '../selectors/conversations.dom.js';
import { missingCaseError } from '../../util/missingCaseError.std.js';
import { ForwardMessagesModalType } from '../../components/ForwardMessagesModal.dom.js';
import type { CallLinkType } from '../../types/CallLink.std.js';
import type { LocalizerType } from '../../types/I18N.std.js';
import { linkCallRoute } from '../../util/signalRoutes.std.js';
import type { StartCallData } from '../../components/ConfirmLeaveCallModal.dom.js';
import type { CallQualitySurvey } from '../../types/CallQualitySurvey.std.js';
import { getMessageById } from '../../messages/getMessageById.preload.js';
import type { DataPropsType as TapToViewNotAvailablePropsType } from '../../components/TapToViewNotAvailableModal.dom.js';
import type { DataPropsType as BackfillFailureModalPropsType } from '../../components/BackfillFailureModal.dom.js';
import type { SmartDraftGifMessageSendModalProps } from '../smart/DraftGifMessageSendModal.preload.js';
import { onCriticalIdlePrimaryDeviceModalDismissed } from '../../util/handleServerAlerts.preload.js';
import type { PinMessageDialogData } from '../smart/PinMessageDialog.preload.js';
import type { StateThunk } from '../types.std.js';
import { itemStorage } from '../../textsecure/Storage.preload.js';

const log = createLogger('globalModals');

// State

export type EditHistoryMessagesType = ReadonlyDeep<
  Array<ReadonlyMessageAttributesType>
>;
export type EditNicknameAndNoteModalPropsType = ReadonlyDeep<{
  conversationId: string;
}>;
export type DeleteMessagesPropsType = ReadonlyDeep<{
  conversationId: string;
  messageIds: ReadonlyArray<string>;
  onDelete?: () => void;
}>;
export type ForwardMessagePropsType = ReadonlyDeep<MessagePropsType>;
export type ForwardMessagesPropsType = ReadonlyDeep<{
  type: ForwardMessagesModalType;
  messageDrafts: Array<MessageForwardDraft>;
  onForward?: () => void;
}>;
export type MessageRequestActionsConfirmationPropsType = ReadonlyDeep<{
  conversationId: string;
  state: MessageRequestState;
}>;
export type NotePreviewModalPropsType = ReadonlyDeep<{
  conversationId: string;
}>;
export type SafetyNumberChangedBlockingDataType = ReadonlyDeep<{
  promiseUuid: SingleServePromise.SingleServePromiseIdString;
  source?: SafetyNumberChangeSource;
}>;

type MigrateToGV2PropsType = ReadonlyDeep<{
  areWeInvited: boolean;
  conversationId: string;
  droppedMemberIds: Array<string>;
  hasMigrated: boolean;
  invitedMemberIds: Array<string>;
}>;

export type CallQualitySurveyPropsType = ReadonlyDeep<{
  callSummary: CallSummary;
  callType: CallQualitySurvey.CallType;
}>;

export type GroupMemberLabelInfoPropsType = ReadonlyDeep<{
  conversationId: string;
}>;

export type GlobalModalsStateType = ReadonlyDeep<{
  addUserToAnotherGroupModalContactId?: string;
  aboutContactModalState?: ContactModalStateType;
  backfillFailureModalProps: BackfillFailureModalPropsType | undefined;
  callLinkAddNameModalRoomId: string | null;
  callLinkEditModalRoomId: string | null;
  callLinkPendingParticipantContactId: string | undefined;
  callQualitySurveyProps: CallQualitySurveyPropsType | null;
  confirmLeaveCallModalState: StartCallData | null;
  contactModalState?: ContactModalStateType;
  criticalIdlePrimaryDeviceModal: boolean;
  deleteMessagesProps?: DeleteMessagesPropsType;
  draftGifMessageSendModalProps: SmartDraftGifMessageSendModalProps | null;
  debugLogErrorModalProps?: {
    description?: string;
  };
  editHistoryMessages?: EditHistoryMessagesType;
  editNicknameAndNoteModalProps: EditNicknameAndNoteModalPropsType | null;
  errorModalProps?: {
    buttonVariant?: ButtonVariant;
    description?: string;
    title?: string | null;
  };
  forwardMessagesProps?: ForwardMessagesPropsType;
  gv2MigrationProps?: MigrateToGV2PropsType;
  groupMemberLabelInfoModalState?: GroupMemberLabelInfoPropsType;
  hasConfirmationModal: boolean;
  isProfileNameWarningModalVisible: boolean;
  profileNameWarningModalConversationType?: string;
  isShortcutGuideModalVisible: boolean;
  isSignalConnectionsVisible: boolean;
  isStoriesSettingsVisible: boolean;
  isWhatsNewVisible: boolean;
  isKeyTransparencyErrorVisible: boolean;
  isKeyTransparencyOnboardingVisible: boolean;
  lowDiskSpaceBackupImportModal: {
    bytesNeeded: number;
  } | null;
  messageRequestActionsConfirmationProps: MessageRequestActionsConfirmationPropsType | null;
  notePreviewModalProps: NotePreviewModalPropsType | null;
  pinMessageDialogData: PinMessageDialogData | null;
  usernameOnboardingState: UsernameOnboardingState;
  mediaPermissionsModalProps?: {
    mediaType: 'camera' | 'microphone';
    requestor: 'call' | 'voiceNote';
    abortController: AbortController;
  };
  safetyNumberChangedBlockingData?: SafetyNumberChangedBlockingDataType;
  safetyNumberModalContactId?: string;
  stickerPackPreviewId?: string;
  tapToViewNotAvailableModalProps?: TapToViewNotAvailablePropsType;
  userNotFoundModalState?: UserNotFoundModalStateType;
}>;

// Actions

const SHOW_TAP_TO_VIEW_NOT_AVAILABLE_MODAL =
  'globalModals/SHOW_TAP_TO_VIEW_NOT_AVAILABLE_MODAL';
const HIDE_TAP_TO_VIEW_NOT_AVAILABLE_MODAL =
  'globalModals/HIDE_TAP_TO_VIEW_NOT_AVAILABLE_MODAL';
const SHOW_BACKFILL_FAILURE_MODAL = 'globalModals/SHOW_BACKFILL_FAILURE_MODAL';
const HIDE_BACKFILL_FAILURE_MODAL = 'globalModals/HIDE_BACKFILL_FAILURE_MODAL';
const HIDE_CONTACT_MODAL = 'globalModals/HIDE_CONTACT_MODAL';
const SHOW_CONTACT_MODAL = 'globalModals/SHOW_CONTACT_MODAL';
const HIDE_WHATS_NEW_MODAL = 'globalModals/HIDE_WHATS_NEW_MODAL_MODAL';
const SHOW_WHATS_NEW_MODAL = 'globalModals/SHOW_WHATS_NEW_MODAL_MODAL';
const HIDE_KEY_TRANSPARENCY_ERROR_DIALOG =
  'globalModals/HIDE_KEY_TRANSPARENCY_ERROR_DIALOG';
const SHOW_KEY_TRANSPARENCY_ERROR_DIALOG =
  'globalModals/SHOW_KEY_TRANSPARENCY_ERROR_DIALOG';
const HIDE_KEY_TRANSPARENCY_ONBOARDING_DIALOG =
  'globalModals/HIDE_KEY_TRANSPARENCY_ONBOARDING_DIALOG';
const SHOW_KEY_TRANSPARENCY_ONBOARDING_DIALOG =
  'globalModals/SHOW_KEY_TRANSPARENCY_ONBOARDING_DIALOG';
const HIDE_SERVICE_ID_NOT_FOUND_MODAL =
  'globalModals/HIDE_SERVICE_ID_NOT_FOUND_MODAL';
const SHOW_SERVICE_ID_NOT_FOUND_MODAL =
  'globalModals/SHOW_SERVICE_ID_NOT_FOUND_MODAL';
const SHOW_STORIES_SETTINGS = 'globalModals/SHOW_STORIES_SETTINGS';
const HIDE_STORIES_SETTINGS = 'globalModals/HIDE_STORIES_SETTINGS';
const TOGGLE_DELETE_MESSAGES_MODAL =
  'globalModals/TOGGLE_DELETE_MESSAGES_MODAL';
const TOGGLE_DRAFT_GIF_MESSAGE_SEND_MODAL =
  'globalModals/TOGGLE_DRAFT_GIF_MESSAGE_SEND_MODAL';
const TOGGLE_FORWARD_MESSAGES_MODAL =
  'globalModals/TOGGLE_FORWARD_MESSAGES_MODAL';
const TOGGLE_NOTE_PREVIEW_MODAL = 'globalModals/TOGGLE_NOTE_PREVIEW_MODAL';
const TOGGLE_PROFILE_NAME_WARNING_MODAL =
  'globalModals/TOGGLE_PROFILE_NAME_WARNING_MODAL';
const TOGGLE_SAFETY_NUMBER_MODAL = 'globalModals/TOGGLE_SAFETY_NUMBER_MODAL';
const TOGGLE_ADD_USER_TO_ANOTHER_GROUP_MODAL =
  'globalModals/TOGGLE_ADD_USER_TO_ANOTHER_GROUP_MODAL';
const TOGGLE_CALL_LINK_ADD_NAME_MODAL =
  'globalModals/TOGGLE_CALL_LINK_ADD_NAME_MODAL';
const TOGGLE_CALL_LINK_EDIT_MODAL = 'globalModals/TOGGLE_CALL_LINK_EDIT_MODAL';
const TOGGLE_CALL_LINK_PENDING_PARTICIPANT_MODAL =
  'globalModals/TOGGLE_CALL_LINK_PENDING_PARTICIPANT_MODAL';
export const SHOW_CALL_QUALITY_SURVEY = 'globalModals/SHOW_CALL_QUALITY_SURVEY';
export const HIDE_CALL_QUALITY_SURVEY = 'globalModals/HIDE_CALL_QUALITY_SURVEY';
const TOGGLE_ABOUT_MODAL = 'globalModals/TOGGLE_ABOUT_MODAL';
const TOGGLE_SIGNAL_CONNECTIONS_MODAL =
  'globalModals/TOGGLE_SIGNAL_CONNECTIONS_MODAL';
export const SHOW_SEND_ANYWAY_DIALOG = 'globalModals/SHOW_SEND_ANYWAY_DIALOG';
const HIDE_SEND_ANYWAY_DIALOG = 'globalModals/HIDE_SEND_ANYWAY_DIALOG';
const SHOW_GV2_MIGRATION_DIALOG = 'globalModals/SHOW_GV2_MIGRATION_DIALOG';
const CLOSE_GV2_MIGRATION_DIALOG = 'globalModals/CLOSE_GV2_MIGRATION_DIALOG';
const SHOW_STICKER_PACK_PREVIEW = 'globalModals/SHOW_STICKER_PACK_PREVIEW';
const CLOSE_STICKER_PACK_PREVIEW = 'globalModals/CLOSE_STICKER_PACK_PREVIEW';
const CLOSE_ERROR_MODAL = 'globalModals/CLOSE_ERROR_MODAL';
export const SHOW_ERROR_MODAL = 'globalModals/SHOW_ERROR_MODAL';
const CLOSE_DEBUG_LOG_ERROR_MODAL = 'globalModals/CLOSE_DEBUG_LOG_ERROR_MODAL';
const SHOW_DEBUG_LOG_ERROR_MODAL = 'globalModals/SHOW_DEBUG_LOG_ERROR_MODAL';
const TOGGLE_EDIT_NICKNAME_AND_NOTE_MODAL =
  'globalModals/TOGGLE_EDIT_NICKNAME_AND_NOTE_MODAL';
const TOGGLE_GROUP_MEMBER_LABEL_INFO_MODAL =
  'globalModals/TOGGLE_GROUP_MEMBER_LABEL_INFO_MODAL';
const TOGGLE_MESSAGE_REQUEST_ACTIONS_CONFIRMATION =
  'globalModals/TOGGLE_MESSAGE_REQUEST_ACTIONS_CONFIRMATION';
const CLOSE_SHORTCUT_GUIDE_MODAL = 'globalModals/CLOSE_SHORTCUT_GUIDE_MODAL';
const SHOW_SHORTCUT_GUIDE_MODAL = 'globalModals/SHOW_SHORTCUT_GUIDE_MODAL';
const TOGGLE_CONFIRMATION_MODAL = 'globalModals/TOGGLE_CONFIRMATION_MODAL';
const SHOW_EDIT_HISTORY_MODAL = 'globalModals/SHOW_EDIT_HISTORY_MODAL';
const CLOSE_EDIT_HISTORY_MODAL = 'globalModals/CLOSE_EDIT_HISTORY_MODAL';
const TOGGLE_USERNAME_ONBOARDING = 'globalModals/TOGGLE_USERNAME_ONBOARDING';
const TOGGLE_CONFIRM_LEAVE_CALL_MODAL =
  'globalModals/TOGGLE_CONFIRM_LEAVE_CALL_MODAL';
const CLOSE_MEDIA_PERMISSIONS_MODAL =
  'globalModals/CLOSE_MEDIA_PERMISSIONS_MODAL';
const SHOW_MEDIA_PERMISSIONS_MODAL =
  'globalModals/SHOW_MEDIA_PERMISSIONS_MODAL';
const SHOW_CRITICAL_IDLE_PRIMARY_DEVICE_MODAL =
  'globalModals/SHOW_CRITICAL_IDLE_PRIMARY_DEVICE_MODAL';
const HIDE_CRITICAL_IDLE_PRIMARY_DEVICE_MODAL =
  'globalModals/HIDE_CRITICAL_IDLE_PRIMARY_DEVICE_MODAL';
const SHOW_LOW_DISK_SPACE_BACKUP_IMPORT_MODAL =
  'globalModals/SHOW_LOW_DISK_SPACE_BACKUP_IMPORT_MODAL';
const HIDE_LOW_DISK_SPACE_BACKUP_IMPORT_MODAL =
  'globalModals/HIDE_LOW_DISK_SPACE_BACKUP_IMPORT_MODAL';
const TOGGLE_PIN_MESSAGE_DIALOG = 'globalModals/TOGGLE_PIN_MESSAGE_DIALOG';

export type UserNotFoundModalStateType = ReadonlyDeep<
  | {
      type: 'phoneNumber';
      phoneNumber: string;
    }
  | {
      type: 'username';
      username: string;
    }
>;

type HideTapToViewNotAvailableModalActionType = ReadonlyDeep<{
  type: typeof HIDE_TAP_TO_VIEW_NOT_AVAILABLE_MODAL;
}>;

type ShowTapToViewNotAvailableModalActionType = ReadonlyDeep<{
  type: typeof SHOW_TAP_TO_VIEW_NOT_AVAILABLE_MODAL;
  payload: TapToViewNotAvailablePropsType;
}>;

type HideBackfillFailureModalActionType = ReadonlyDeep<{
  type: typeof HIDE_BACKFILL_FAILURE_MODAL;
}>;

type ShowBackfillFailureModalActionType = ReadonlyDeep<{
  type: typeof SHOW_BACKFILL_FAILURE_MODAL;
  payload: BackfillFailureModalPropsType;
}>;

type HideContactModalActionType = ReadonlyDeep<{
  type: typeof HIDE_CONTACT_MODAL;
}>;

type ShowContactModalActionType = ReadonlyDeep<{
  type: typeof SHOW_CONTACT_MODAL;
  payload: ContactModalStateType;
}>;

type HideWhatsNewModalActionType = ReadonlyDeep<{
  type: typeof HIDE_WHATS_NEW_MODAL;
}>;

type ShowWhatsNewModalActionType = ReadonlyDeep<{
  type: typeof SHOW_WHATS_NEW_MODAL;
}>;

type HideKeyTransparencyErrorDialogActionType = ReadonlyDeep<{
  type: typeof HIDE_KEY_TRANSPARENCY_ERROR_DIALOG;
}>;

type ShowKeyTransparencyErrorDialogActionType = ReadonlyDeep<{
  type: typeof SHOW_KEY_TRANSPARENCY_ERROR_DIALOG;
}>;

type HideKeyTransparencyOnboardingDialogActionType = ReadonlyDeep<{
  type: typeof HIDE_KEY_TRANSPARENCY_ONBOARDING_DIALOG;
}>;

type ShowKeyTransparencyOnboardingDialogActionType = ReadonlyDeep<{
  type: typeof SHOW_KEY_TRANSPARENCY_ONBOARDING_DIALOG;
}>;

type HideUserNotFoundModalActionType = ReadonlyDeep<{
  type: typeof HIDE_SERVICE_ID_NOT_FOUND_MODAL;
}>;

export type ShowUserNotFoundModalActionType = ReadonlyDeep<{
  type: typeof SHOW_SERVICE_ID_NOT_FOUND_MODAL;
  payload: UserNotFoundModalStateType;
}>;

type ToggleDeleteMessagesModalActionType = ReadonlyDeep<{
  type: typeof TOGGLE_DELETE_MESSAGES_MODAL;
  payload: DeleteMessagesPropsType | undefined;
}>;

type ToggleDraftGifMessageSendModalActionType = ReadonlyDeep<{
  type: typeof TOGGLE_DRAFT_GIF_MESSAGE_SEND_MODAL;
  payload: SmartDraftGifMessageSendModalProps | null;
}>;

type ToggleForwardMessagesModalActionType = ReadonlyDeep<{
  type: typeof TOGGLE_FORWARD_MESSAGES_MODAL;
  payload: ForwardMessagesPropsType | undefined;
}>;

export type ToggleConfirmLeaveCallModalActionType = ReadonlyDeep<{
  type: typeof TOGGLE_CONFIRM_LEAVE_CALL_MODAL;
  payload: StartCallData | null;
}>;

type ToggleNotePreviewModalActionType = ReadonlyDeep<{
  type: typeof TOGGLE_NOTE_PREVIEW_MODAL;
  payload: NotePreviewModalPropsType | null;
}>;

export type ToggleProfileNameWarningModalActionType = ReadonlyDeep<{
  type: typeof TOGGLE_PROFILE_NAME_WARNING_MODAL;
  payload?: {
    conversationType: string;
  };
}>;

type ToggleSafetyNumberModalActionType = ReadonlyDeep<{
  type: typeof TOGGLE_SAFETY_NUMBER_MODAL;
  payload: string | undefined;
}>;

type ToggleAddUserToAnotherGroupModalActionType = ReadonlyDeep<{
  type: typeof TOGGLE_ADD_USER_TO_ANOTHER_GROUP_MODAL;
  payload: string | undefined;
}>;

type ToggleCallLinkAddNameModalActionType = ReadonlyDeep<{
  type: typeof TOGGLE_CALL_LINK_ADD_NAME_MODAL;
  payload: string | null;
}>;

type ToggleCallLinkEditModalActionType = ReadonlyDeep<{
  type: typeof TOGGLE_CALL_LINK_EDIT_MODAL;
  payload: string | null;
}>;

type ToggleCallLinkPendingParticipantModalActionType = ReadonlyDeep<{
  type: typeof TOGGLE_CALL_LINK_PENDING_PARTICIPANT_MODAL;
  payload: string | undefined;
}>;

export type ShowCallQualitySurveyActionType = ReadonlyDeep<{
  type: typeof SHOW_CALL_QUALITY_SURVEY;
  payload: CallQualitySurveyPropsType;
}>;

export type HideCallQualitySurveyActionType = ReadonlyDeep<{
  type: typeof HIDE_CALL_QUALITY_SURVEY;
}>;

type ToggleAboutContactModalActionType = ReadonlyDeep<{
  type: typeof TOGGLE_ABOUT_MODAL;
  payload: ContactModalStateType | undefined;
}>;

type ToggleSignalConnectionsModalActionType = ReadonlyDeep<{
  type: typeof TOGGLE_SIGNAL_CONNECTIONS_MODAL;
}>;

type ToggleConfirmationModalActionType = ReadonlyDeep<{
  type: typeof TOGGLE_CONFIRMATION_MODAL;
  payload: boolean;
}>;

type ToggleUsernameOnboardingActionType = ReadonlyDeep<{
  type: typeof TOGGLE_USERNAME_ONBOARDING;
}>;

type ShowStoriesSettingsActionType = ReadonlyDeep<{
  type: typeof SHOW_STORIES_SETTINGS;
}>;

type HideStoriesSettingsActionType = ReadonlyDeep<{
  type: typeof HIDE_STORIES_SETTINGS;
}>;

type StartMigrationToGV2ActionType = ReadonlyDeep<{
  type: typeof SHOW_GV2_MIGRATION_DIALOG;
  payload: MigrateToGV2PropsType;
}>;

type CloseGV2MigrationDialogActionType = ReadonlyDeep<{
  type: typeof CLOSE_GV2_MIGRATION_DIALOG;
}>;

export type ShowSendAnywayDialogActionType = ReadonlyDeep<{
  type: typeof SHOW_SEND_ANYWAY_DIALOG;
  payload: SafetyNumberChangedBlockingDataType & {
    untrustedByConversation: RecipientsByConversation;
  };
}>;

type HideSendAnywayDialogActiontype = ReadonlyDeep<{
  type: typeof HIDE_SEND_ANYWAY_DIALOG;
}>;

export type ShowStickerPackPreviewActionType = ReadonlyDeep<{
  type: typeof SHOW_STICKER_PACK_PREVIEW;
  payload: string;
}>;

type CloseStickerPackPreviewActionType = ReadonlyDeep<{
  type: typeof CLOSE_STICKER_PACK_PREVIEW;
}>;

type CloseErrorModalActionType = ReadonlyDeep<{
  type: typeof CLOSE_ERROR_MODAL;
}>;

export type ShowErrorModalActionType = ReadonlyDeep<{
  type: typeof SHOW_ERROR_MODAL;
  payload: {
    buttonVariant?: ButtonVariant;
    description?: string;
    title?: string | null;
  };
}>;

type CloseDebugLogErrorModalActionType = ReadonlyDeep<{
  type: typeof CLOSE_DEBUG_LOG_ERROR_MODAL;
}>;

type ShowDebugLogErrorModalActionType = ReadonlyDeep<{
  type: typeof SHOW_DEBUG_LOG_ERROR_MODAL;
  payload: {
    description?: string;
  };
}>;

type CloseMediaPermissionsModalActionType = ReadonlyDeep<{
  type: typeof CLOSE_MEDIA_PERMISSIONS_MODAL;
}>;

type ShowMediaPermissionsModalActionType = ReadonlyDeep<{
  type: typeof SHOW_MEDIA_PERMISSIONS_MODAL;
  payload: {
    mediaType: 'camera' | 'microphone';
    requestor: 'call' | 'voiceNote';
    abortController: AbortController;
  };
}>;

type ShowCriticalIdlePrimaryDeviceModalActionType = ReadonlyDeep<{
  type: typeof SHOW_CRITICAL_IDLE_PRIMARY_DEVICE_MODAL;
}>;

type HideCriticalIdlePrimaryDeviceModalActionType = ReadonlyDeep<{
  type: typeof HIDE_CRITICAL_IDLE_PRIMARY_DEVICE_MODAL;
}>;

type ShowLowDiskSpaceBackupImportModalActionType = ReadonlyDeep<{
  type: typeof SHOW_LOW_DISK_SPACE_BACKUP_IMPORT_MODAL;
  payload: {
    bytesNeeded: number;
  };
}>;

type HideLowDiskSpaceBackupImportModalActionType = ReadonlyDeep<{
  type: typeof HIDE_LOW_DISK_SPACE_BACKUP_IMPORT_MODAL;
}>;

type ToggleEditNicknameAndNoteModalActionType = ReadonlyDeep<{
  type: typeof TOGGLE_EDIT_NICKNAME_AND_NOTE_MODAL;
  payload: EditNicknameAndNoteModalPropsType | null;
}>;

type ToggleGroupMemberLabelInfoModalActionType = ReadonlyDeep<{
  type: typeof TOGGLE_GROUP_MEMBER_LABEL_INFO_MODAL;
  payload: GroupMemberLabelInfoPropsType | undefined;
}>;

type ToggleMessageRequestActionsConfirmationActionType = ReadonlyDeep<{
  type: typeof TOGGLE_MESSAGE_REQUEST_ACTIONS_CONFIRMATION;
  payload: MessageRequestActionsConfirmationPropsType | null;
}>;

type CloseShortcutGuideModalActionType = ReadonlyDeep<{
  type: typeof CLOSE_SHORTCUT_GUIDE_MODAL;
}>;

type ShowShortcutGuideModalActionType = ReadonlyDeep<{
  type: typeof SHOW_SHORTCUT_GUIDE_MODAL;
}>;

type ShowEditHistoryModalActionType = ReadonlyDeep<{
  type: typeof SHOW_EDIT_HISTORY_MODAL;
  payload: {
    messages: EditHistoryMessagesType;
  };
}>;

type CloseEditHistoryModalActionType = ReadonlyDeep<{
  type: typeof CLOSE_EDIT_HISTORY_MODAL;
}>;

type TogglePinMessageDialogActionType = ReadonlyDeep<{
  type: typeof TOGGLE_PIN_MESSAGE_DIALOG;
  payload: PinMessageDialogData | null;
}>;

export type GlobalModalsActionType = ReadonlyDeep<
  | CloseEditHistoryModalActionType
  | CloseDebugLogErrorModalActionType
  | CloseErrorModalActionType
  | CloseMediaPermissionsModalActionType
  | CloseGV2MigrationDialogActionType
  | CloseShortcutGuideModalActionType
  | CloseStickerPackPreviewActionType
  | HideBackfillFailureModalActionType
  | HideCallQualitySurveyActionType
  | HideContactModalActionType
  | HideCriticalIdlePrimaryDeviceModalActionType
  | HideKeyTransparencyErrorDialogActionType
  | HideKeyTransparencyOnboardingDialogActionType
  | HideLowDiskSpaceBackupImportModalActionType
  | HideSendAnywayDialogActiontype
  | HideStoriesSettingsActionType
  | HideTapToViewNotAvailableModalActionType
  | HideUserNotFoundModalActionType
  | HideWhatsNewModalActionType
  | MessageChangedActionType
  | MessageDeletedActionType
  | ShowBackfillFailureModalActionType
  | ShowCallQualitySurveyActionType
  | ShowCriticalIdlePrimaryDeviceModalActionType
  | ShowContactModalActionType
  | ShowDebugLogErrorModalActionType
  | ShowEditHistoryModalActionType
  | ShowErrorModalActionType
  | ShowKeyTransparencyErrorDialogActionType
  | ShowKeyTransparencyOnboardingDialogActionType
  | ShowLowDiskSpaceBackupImportModalActionType
  | ShowMediaPermissionsModalActionType
  | ShowSendAnywayDialogActionType
  | ShowShortcutGuideModalActionType
  | ShowStickerPackPreviewActionType
  | ShowStoriesSettingsActionType
  | ShowTapToViewNotAvailableModalActionType
  | ShowUserNotFoundModalActionType
  | ShowWhatsNewModalActionType
  | StartMigrationToGV2ActionType
  | ToggleAboutContactModalActionType
  | ToggleAddUserToAnotherGroupModalActionType
  | ToggleCallLinkAddNameModalActionType
  | ToggleCallLinkEditModalActionType
  | ToggleCallLinkPendingParticipantModalActionType
  | ToggleConfirmationModalActionType
  | ToggleConfirmLeaveCallModalActionType
  | ToggleDeleteMessagesModalActionType
  | ToggleDraftGifMessageSendModalActionType
  | ToggleEditNicknameAndNoteModalActionType
  | ToggleForwardMessagesModalActionType
  | ToggleGroupMemberLabelInfoModalActionType
  | ToggleMessageRequestActionsConfirmationActionType
  | ToggleNotePreviewModalActionType
  | ToggleProfileNameWarningModalActionType
  | ToggleSafetyNumberModalActionType
  | ToggleSignalConnectionsModalActionType
  | ToggleUsernameOnboardingActionType
  | TogglePinMessageDialogActionType
>;

// Action Creators

export const actions = {
  closeDebugLogErrorModal,
  closeEditHistoryModal,
  closeErrorModal,
  closeGV2MigrationDialog,
  closeShortcutGuideModal,
  closeStickerPackPreview,
  closeMediaPermissionsModal,
  ensureSystemMediaPermissions,
  finishKeyTransparencyOnboarding,
  hideBackfillFailureModal,
  hideBlockingSafetyNumberChangeDialog,
  hideCallQualitySurvey,
  hideContactModal,
  hideCriticalIdlePrimaryDeviceModal,
  hideKeyTransparencyErrorDialog,
  hideKeyTransparencyOnboardingDialog,
  hideLowDiskSpaceBackupImportModal,
  hideStoriesSettings,
  hideTapToViewNotAvailableModal,
  hideUserNotFoundModal,
  hideWhatsNewModal,
  showBackfillFailureModal,
  showBlockingSafetyNumberChangeDialog,
  showCallQualitySurvey,
  showContactModal,
  showCriticalIdlePrimaryDeviceModal,
  showDebugLogErrorModal,
  showEditHistoryModal,
  showErrorModal,
  showGV2MigrationDialog,
  showKeyTransparencyErrorDialog,
  showKeyTransparencyOnboardingDialog,
  showLowDiskSpaceBackupImportModal,
  showShareCallLinkViaSignal,
  showShortcutGuideModal,
  showStickerPackPreview,
  showStoriesSettings,
  showTapToViewNotAvailableModal,
  showUserNotFoundModal,
  showWhatsNewModal,
  toggleAboutContactModal,
  toggleAddUserToAnotherGroupModal,
  toggleCallLinkAddNameModal,
  toggleCallLinkEditModal,
  toggleCallLinkPendingParticipantModal,
  toggleConfirmationModal,
  toggleConfirmLeaveCallModal,
  toggleDeleteMessagesModal,
  toggleDraftGifMessageSendModal,
  toggleEditNicknameAndNoteModal,
  toggleForwardMessagesModal,
  toggleGroupMemberLabelInfoModal,
  toggleMessageRequestActionsConfirmation,
  toggleNotePreviewModal,
  toggleProfileNameWarningModal,
  toggleSafetyNumberModal,
  toggleSignalConnectionsModal,
  toggleUsernameOnboarding,
  showPinMessageDialog,
  hidePinMessageDialog,
};

export const useGlobalModalActions = (): BoundActionCreatorsMapObject<
  typeof actions
> => useBoundActions(actions);

function hideTapToViewNotAvailableModal(): HideTapToViewNotAvailableModalActionType {
  return {
    type: HIDE_TAP_TO_VIEW_NOT_AVAILABLE_MODAL,
  };
}

function showTapToViewNotAvailableModal(
  payload: TapToViewNotAvailablePropsType
): ShowTapToViewNotAvailableModalActionType {
  return {
    type: SHOW_TAP_TO_VIEW_NOT_AVAILABLE_MODAL,
    payload,
  };
}

function showBackfillFailureModal(
  payload: BackfillFailureModalPropsType
): ShowBackfillFailureModalActionType {
  return {
    type: SHOW_BACKFILL_FAILURE_MODAL,
    payload,
  };
}

function hideBackfillFailureModal(): HideBackfillFailureModalActionType {
  return {
    type: HIDE_BACKFILL_FAILURE_MODAL,
  };
}

function showCallQualitySurvey(
  payload: CallQualitySurveyPropsType
): ShowCallQualitySurveyActionType {
  return {
    type: SHOW_CALL_QUALITY_SURVEY,
    payload,
  };
}

export function hideCallQualitySurvey(): ThunkAction<
  void,
  RootStateType,
  unknown,
  HideCallQualitySurveyActionType
> {
  return dispatch => {
    window.IPC.closeDebugLog();
    window.IPC.closeCallDiagnostic();
    dispatch({ type: HIDE_CALL_QUALITY_SURVEY });
  };
}

function hideContactModal(): HideContactModalActionType {
  return {
    type: HIDE_CONTACT_MODAL,
  };
}

function showContactModal({
  activeCallDemuxId,
  contactId,
  conversationId,
}: {
  contactId: string;
  conversationId?: string;
  activeCallDemuxId?: number;
}): ShowContactModalActionType {
  return {
    type: SHOW_CONTACT_MODAL,
    payload: {
      activeCallDemuxId,
      contactId,
      conversationId,
    },
  };
}

function hideWhatsNewModal(): HideWhatsNewModalActionType {
  return {
    type: HIDE_WHATS_NEW_MODAL,
  };
}

function showWhatsNewModal(): ShowWhatsNewModalActionType {
  return {
    type: SHOW_WHATS_NEW_MODAL,
  };
}

function hideKeyTransparencyErrorDialog(): HideKeyTransparencyErrorDialogActionType {
  return {
    type: HIDE_KEY_TRANSPARENCY_ERROR_DIALOG,
  };
}

function showKeyTransparencyErrorDialog(): ShowKeyTransparencyErrorDialogActionType {
  return {
    type: SHOW_KEY_TRANSPARENCY_ERROR_DIALOG,
  };
}

function hideKeyTransparencyOnboardingDialog(): HideKeyTransparencyOnboardingDialogActionType {
  return {
    type: HIDE_KEY_TRANSPARENCY_ONBOARDING_DIALOG,
  };
}

function showKeyTransparencyOnboardingDialog(): ShowKeyTransparencyOnboardingDialogActionType {
  return {
    type: SHOW_KEY_TRANSPARENCY_ONBOARDING_DIALOG,
  };
}

function hideUserNotFoundModal(): HideUserNotFoundModalActionType {
  return {
    type: HIDE_SERVICE_ID_NOT_FOUND_MODAL,
  };
}

function showUserNotFoundModal(
  payload: UserNotFoundModalStateType
): ShowUserNotFoundModalActionType {
  return {
    type: SHOW_SERVICE_ID_NOT_FOUND_MODAL,
    payload,
  };
}

function hideStoriesSettings(): HideStoriesSettingsActionType {
  return { type: HIDE_STORIES_SETTINGS };
}

function showStoriesSettings(): ShowStoriesSettingsActionType {
  return { type: SHOW_STORIES_SETTINGS };
}

function showGV2MigrationDialog(
  conversationId: string
): ThunkAction<void, RootStateType, unknown, StartMigrationToGV2ActionType> {
  return async dispatch => {
    const conversation = window.ConversationController.get(conversationId);
    if (!conversation) {
      throw new Error(
        'showGV2MigrationDialog: Expected a conversation to be found. Doing nothing'
      );
    }

    const idForLogging = conversation.idForLogging();

    if (!isGroupV1(conversation.attributes)) {
      throw new Error(
        `showGV2MigrationDialog/${idForLogging}: Cannot start, not a GroupV1 group`
      );
    }

    // Note: this call will throw if, after generating member lists, we are no longer a
    //   member or are in the pending member list.
    const { droppedGV2MemberIds, pendingMembersV2 } =
      await longRunningTaskWrapper({
        idForLogging,
        name: 'getGroupMigrationMembers',
        task: () => getGroupMigrationMembers(conversation),
      });

    const invitedMemberIds = pendingMembersV2.map(
      (item: GroupV2PendingMemberType) => item.serviceId
    );

    dispatch({
      type: SHOW_GV2_MIGRATION_DIALOG,
      payload: {
        areWeInvited: false,
        conversationId,
        droppedMemberIds: droppedGV2MemberIds,
        hasMigrated: false,
        invitedMemberIds,
      },
    });
  };
}

function closeGV2MigrationDialog(): CloseGV2MigrationDialogActionType {
  return {
    type: CLOSE_GV2_MIGRATION_DIALOG,
  };
}

function toggleDeleteMessagesModal(
  props: DeleteMessagesPropsType | undefined
): ToggleDeleteMessagesModalActionType {
  return {
    type: TOGGLE_DELETE_MESSAGES_MODAL,
    payload: props,
  };
}

function toggleDraftGifMessageSendModal(
  props: SmartDraftGifMessageSendModalProps | null
): ToggleDraftGifMessageSendModalActionType {
  return {
    type: TOGGLE_DRAFT_GIF_MESSAGE_SEND_MODAL,
    payload: props,
  };
}

function toMessageForwardDraft(
  props: ForwardMessagePropsType,
  getConversation: GetConversationByIdType
): MessageForwardDraft {
  return {
    attachments: props.attachments ?? [],
    bodyRanges: hydrateRanges(props.bodyRanges, getConversation),
    hasContact: Boolean(props.contact),
    isSticker: Boolean(props.isSticker),
    messageBody: props.text,
    originalMessageId: props.id,
    previews: props.previews ?? [],
  };
}

export type ForwardMessagesPayload = ReadonlyDeep<
  | {
      type: ForwardMessagesModalType.Forward;
      messageIds: ReadonlyArray<string>;
    }
  | {
      type:
        | ForwardMessagesModalType.ForwardAttachment
        | ForwardMessagesModalType.ShareCallLink;
      draft: MessageForwardDraft;
    }
>;

function toggleForwardMessagesModal(
  payload: ForwardMessagesPayload | null,
  onForward?: () => void
): ThunkAction<
  void,
  RootStateType,
  unknown,
  ToggleForwardMessagesModalActionType
> {
  return async (dispatch, getState) => {
    if (payload == null) {
      dispatch({
        type: TOGGLE_FORWARD_MESSAGES_MODAL,
        payload: undefined,
      });
      return;
    }

    let messageDrafts: ReadonlyArray<MessageForwardDraft>;

    if (payload.type === ForwardMessagesModalType.Forward) {
      messageDrafts = await Promise.all(
        payload.messageIds.map(async messageId => {
          const message = await getMessageById(messageId);
          if (!message) {
            throw new Error(
              'toggleForwardMessagesModal: failed to find target message'
            );
          }
          const { attachments = [] } = message.attributes;

          if (
            !attachments.every(
              attachment =>
                isDownloaded(attachment) ||
                isPermanentlyUndownloadable(
                  attachment,
                  'attachment',
                  message.attributes
                )
            )
          ) {
            dispatch(
              conversationsActions.kickOffAttachmentDownload({ messageId })
            );
          }

          const state = getState();
          const messagePropsSelector = getMessagePropsSelector(state);
          const conversationSelector = getConversationSelector(state);

          const messageProps = messagePropsSelector(message.attributes);
          const messageDraft = toMessageForwardDraft(
            {
              ...messageProps,
              attachments: (messageProps.attachments ?? []).filter(
                attachment =>
                  !isPermanentlyUndownloadable(
                    attachment,
                    'attachment',
                    message.attributes
                  )
              ),
            },
            conversationSelector
          );

          return messageDraft;
        })
      );
    } else if (
      payload.type === ForwardMessagesModalType.ForwardAttachment ||
      payload.type === ForwardMessagesModalType.ShareCallLink
    ) {
      messageDrafts = [payload.draft];
    } else {
      throw missingCaseError(payload.type);
    }

    dispatch({
      type: TOGGLE_FORWARD_MESSAGES_MODAL,
      payload: { type: payload.type, messageDrafts, onForward },
    });
  };
}

function showShareCallLinkViaSignal(
  callLink: CallLinkType,
  i18n: LocalizerType
): ThunkAction<
  void,
  RootStateType,
  unknown,
  ToggleForwardMessagesModalActionType
> {
  return dispatch => {
    const url = linkCallRoute
      .toWebUrl({
        key: callLink.rootKey,
      })
      .toString();
    dispatch(
      toggleForwardMessagesModal({
        type: ForwardMessagesModalType.ShareCallLink,
        draft: {
          originalMessageId: null,
          hasContact: false,
          isSticker: false,
          previews: [
            {
              title: callLink.name,
              url,
              isCallLink: true,
            },
          ],
          messageBody: i18n(
            'icu:ShareCallLinkViaSignal__DraftMessageText',
            { url },
            { bidi: 'strip' }
          ),
        },
      })
    );
  };
}

export function toggleConfirmLeaveCallModal(
  payload: StartCallData | null
): ToggleConfirmLeaveCallModalActionType {
  return {
    type: TOGGLE_CONFIRM_LEAVE_CALL_MODAL,
    payload,
  };
}

function toggleNotePreviewModal(
  payload: NotePreviewModalPropsType | null
): ToggleNotePreviewModalActionType {
  return {
    type: TOGGLE_NOTE_PREVIEW_MODAL,
    payload,
  };
}

function toggleProfileNameWarningModal(
  conversationType?: string
): ToggleProfileNameWarningModalActionType {
  return {
    type: TOGGLE_PROFILE_NAME_WARNING_MODAL,
    payload: conversationType ? { conversationType } : undefined,
  };
}

function toggleSafetyNumberModal(
  safetyNumberModalContactId?: string
): ThunkAction<
  void,
  RootStateType,
  unknown,
  | ShowKeyTransparencyOnboardingDialogActionType
  | ToggleSafetyNumberModalActionType
> {
  return dispatch => {
    if (
      isKeyTransparencyAvailable() &&
      safetyNumberModalContactId != null &&
      !itemStorage.get('hasSeenKeyTransparencyOnboarding')
    ) {
      dispatch(showKeyTransparencyOnboardingDialog());
    }
    dispatch({
      type: TOGGLE_SAFETY_NUMBER_MODAL,
      payload: safetyNumberModalContactId,
    });
  };
}

function toggleAddUserToAnotherGroupModal(
  contactId?: string
): ToggleAddUserToAnotherGroupModalActionType {
  return {
    type: TOGGLE_ADD_USER_TO_ANOTHER_GROUP_MODAL,
    payload: contactId,
  };
}

function toggleCallLinkAddNameModal(
  roomId: string | null
): ToggleCallLinkAddNameModalActionType {
  return {
    type: TOGGLE_CALL_LINK_ADD_NAME_MODAL,
    payload: roomId,
  };
}

function toggleCallLinkEditModal(
  roomId: string | null
): ToggleCallLinkEditModalActionType {
  return {
    type: TOGGLE_CALL_LINK_EDIT_MODAL,
    payload: roomId,
  };
}

function toggleCallLinkPendingParticipantModal(
  contactId?: string
): ToggleCallLinkPendingParticipantModalActionType {
  return {
    type: TOGGLE_CALL_LINK_PENDING_PARTICIPANT_MODAL,
    payload: contactId,
  };
}

function toggleAboutContactModal(
  payload?: ContactModalStateType
): ToggleAboutContactModalActionType {
  return {
    type: TOGGLE_ABOUT_MODAL,
    payload,
  };
}

function toggleSignalConnectionsModal(): ToggleSignalConnectionsModalActionType {
  return {
    type: TOGGLE_SIGNAL_CONNECTIONS_MODAL,
  };
}

function toggleConfirmationModal(
  isOpen: boolean
): ToggleConfirmationModalActionType {
  return {
    type: TOGGLE_CONFIRMATION_MODAL,
    payload: isOpen,
  };
}

function toggleUsernameOnboarding(): ToggleUsernameOnboardingActionType {
  return { type: TOGGLE_USERNAME_ONBOARDING };
}

function showBlockingSafetyNumberChangeDialog(
  untrustedByConversation: RecipientsByConversation,
  explodedPromise: ExplodePromiseResultType<boolean>,
  source?: SafetyNumberChangeSource
): ThunkAction<void, RootStateType, unknown, ShowSendAnywayDialogActionType> {
  const promiseUuid = SingleServePromise.set<boolean>(explodedPromise);

  return dispatch => {
    dispatch({
      type: SHOW_SEND_ANYWAY_DIALOG,
      payload: {
        untrustedByConversation,
        promiseUuid,
        source,
      },
    });
  };
}

function hideBlockingSafetyNumberChangeDialog(): HideSendAnywayDialogActiontype {
  return {
    type: HIDE_SEND_ANYWAY_DIALOG,
  };
}

function closeStickerPackPreview(): ThunkAction<
  void,
  RootStateType,
  unknown,
  CloseStickerPackPreviewActionType
> {
  return async (dispatch, getState) => {
    const packId = getState().globalModals.stickerPackPreviewId;

    if (packId && Stickers.getStickerPack(packId) !== undefined) {
      await Stickers.removeEphemeralPack(packId);
    }

    dispatch({
      type: CLOSE_STICKER_PACK_PREVIEW,
    });
  };
}

export function showStickerPackPreview(
  packId: string,
  packKey: string
): ShowStickerPackPreviewActionType {
  // Intentionally not awaiting this so that we can show the modal right away.
  // The modal has a loading spinner on it.
  void Stickers.downloadEphemeralPack(packId, packKey);

  return {
    type: SHOW_STICKER_PACK_PREVIEW,
    payload: packId,
  };
}

function closeErrorModal(): CloseErrorModalActionType {
  return {
    type: CLOSE_ERROR_MODAL,
  };
}

function showErrorModal({
  buttonVariant,
  description,
  title,
}: {
  buttonVariant?: ButtonVariant;
  description?: string;
  title?: string;
}): ShowErrorModalActionType {
  return {
    type: SHOW_ERROR_MODAL,
    payload: {
      buttonVariant,
      description,
      title,
    },
  };
}

function closeDebugLogErrorModal(): CloseDebugLogErrorModalActionType {
  return {
    type: CLOSE_DEBUG_LOG_ERROR_MODAL,
  };
}

function showDebugLogErrorModal({
  description,
}: {
  description?: string;
}): ShowDebugLogErrorModalActionType {
  return {
    type: SHOW_DEBUG_LOG_ERROR_MODAL,
    payload: {
      description,
    },
  };
}

function closeMediaPermissionsModal(): CloseMediaPermissionsModalActionType {
  return {
    type: CLOSE_MEDIA_PERMISSIONS_MODAL,
  };
}

const MEDIA_PERMISSIONS_POLL_INTERVAL = SECOND;

export function ensureSystemMediaPermissions(
  mediaType: 'camera' | 'microphone',
  requestor: 'call' | 'voiceNote'
): ThunkAction<
  void,
  RootStateType,
  unknown,
  ShowMediaPermissionsModalActionType | CloseMediaPermissionsModalActionType
> {
  return async dispatch => {
    // Only macOS supported at the moment
    if (!OS.isMacOS()) {
      return;
    }

    const status = await window.IPC.getMediaAccessStatus(mediaType);
    if (status !== 'denied') {
      return;
    }

    const logId = `ensureSystemMediaPermissions(${mediaType}, ${requestor})`;
    log.warn(`${logId}: permission denied, showing UI`);

    const abortController = new AbortController();
    dispatch({
      type: SHOW_MEDIA_PERMISSIONS_MODAL,
      payload: { mediaType, requestor, abortController },
    });

    const { signal } = abortController;
    while (!signal.aborted) {
      // eslint-disable-next-line no-await-in-loop
      await sleep(MEDIA_PERMISSIONS_POLL_INTERVAL, signal);

      // eslint-disable-next-line no-await-in-loop
      const updatedStatus = await window.IPC.getMediaAccessStatus(mediaType);
      if (signal.aborted) {
        throw new Error('ensureSystemMediaPermissions: modal dismissed');
      }

      if (updatedStatus !== 'denied') {
        break;
      }
    }

    dispatch({ type: CLOSE_MEDIA_PERMISSIONS_MODAL });
  };
}

function finishKeyTransparencyOnboarding(): ThunkAction<
  void,
  RootStateType,
  unknown,
  HideKeyTransparencyOnboardingDialogActionType
> {
  return async dispatch => {
    await itemStorage.put('hasSeenKeyTransparencyOnboarding', true);
    dispatch(hideKeyTransparencyOnboardingDialog());
  };
}

function showCriticalIdlePrimaryDeviceModal(): ShowCriticalIdlePrimaryDeviceModalActionType {
  return {
    type: SHOW_CRITICAL_IDLE_PRIMARY_DEVICE_MODAL,
  };
}

function hideCriticalIdlePrimaryDeviceModal(): ThunkAction<
  void,
  RootStateType,
  unknown,
  HideCriticalIdlePrimaryDeviceModalActionType
> {
  return async dispatch => {
    await onCriticalIdlePrimaryDeviceModalDismissed();
    dispatch({
      type: HIDE_CRITICAL_IDLE_PRIMARY_DEVICE_MODAL,
    });
  };
}

function showLowDiskSpaceBackupImportModal(
  bytesNeeded: number
): ShowLowDiskSpaceBackupImportModalActionType {
  return {
    type: SHOW_LOW_DISK_SPACE_BACKUP_IMPORT_MODAL,
    payload: {
      bytesNeeded,
    },
  };
}

function hideLowDiskSpaceBackupImportModal(): HideLowDiskSpaceBackupImportModalActionType {
  return {
    type: HIDE_LOW_DISK_SPACE_BACKUP_IMPORT_MODAL,
  };
}

function toggleEditNicknameAndNoteModal(
  payload: EditNicknameAndNoteModalPropsType | null
): ToggleEditNicknameAndNoteModalActionType {
  return {
    type: TOGGLE_EDIT_NICKNAME_AND_NOTE_MODAL,
    payload,
  };
}

export type ToggleGroupMemberLabelInfoModalType = ReadonlyDeep<
  ActionCreator<typeof toggleGroupMemberLabelInfoModal>
>;
function toggleGroupMemberLabelInfoModal(
  payload: GroupMemberLabelInfoPropsType | undefined
): ToggleGroupMemberLabelInfoModalActionType {
  return {
    type: TOGGLE_GROUP_MEMBER_LABEL_INFO_MODAL,
    payload,
  };
}

function toggleMessageRequestActionsConfirmation(
  payload: {
    conversationId: string;
    state: MessageRequestState;
  } | null
): ToggleMessageRequestActionsConfirmationActionType {
  return {
    type: TOGGLE_MESSAGE_REQUEST_ACTIONS_CONFIRMATION,
    payload,
  };
}

function closeShortcutGuideModal(): CloseShortcutGuideModalActionType {
  return {
    type: CLOSE_SHORTCUT_GUIDE_MODAL,
  };
}

function showShortcutGuideModal(): ShowShortcutGuideModalActionType {
  return {
    type: SHOW_SHORTCUT_GUIDE_MODAL,
  };
}

function copyOverMessageAttributesIntoEditHistory(
  messageAttributes: ReadonlyDeep<ReadonlyMessageAttributesType>
): EditHistoryMessagesType | undefined {
  if (!messageAttributes.editHistory) {
    return;
  }

  return messageAttributes.editHistory.map(editedMessageAttributes => ({
    ...messageAttributes,
    // Always take attachments from the edited message (they might be absent)
    attachments: undefined,
    editMessageTimestamp: undefined,
    quote: undefined,
    preview: [],
    ...editedMessageAttributes,
    // For timestamp uniqueness of messages
    sent_at: editedMessageAttributes.timestamp,
  }));
}

function showEditHistoryModal(
  messageId: string
): ThunkAction<void, RootStateType, unknown, ShowEditHistoryModalActionType> {
  return async dispatch => {
    const message = await getMessageById(messageId);
    if (!message) {
      throw new Error('showEditHistoryModal: failed to find target message');
    }

    const nextEditHistoryMessages = copyOverMessageAttributesIntoEditHistory(
      message.attributes
    );

    if (!nextEditHistoryMessages) {
      log.warn('showEditHistoryModal: no edit history for message');
      return;
    }

    dispatch({
      type: SHOW_EDIT_HISTORY_MODAL,
      payload: {
        messages: nextEditHistoryMessages,
      },
    });
  };
}

function closeEditHistoryModal(): CloseEditHistoryModalActionType {
  return {
    type: CLOSE_EDIT_HISTORY_MODAL,
  };
}

function copyOverMessageAttributesIntoForwardMessages(
  messageDrafts: ReadonlyArray<MessageForwardDraft>,
  attributes: ReadonlyDeep<ReadonlyMessageAttributesType>
): ReadonlyArray<MessageForwardDraft> {
  return messageDrafts.map(messageDraft => {
    if (messageDraft.originalMessageId !== attributes.id) {
      return messageDraft;
    }
    return {
      ...messageDraft,
      attachments: attributes.attachments?.map(attachment =>
        getPropsForAttachment(attachment, 'attachment', attributes)
      ),
    };
  });
}

function showPinMessageDialog(
  messageId: string,
  isPinningDisappearingMessage: boolean
): StateThunk<TogglePinMessageDialogActionType> {
  return async (dispatch, getState) => {
    const hasMaxPinnedMessages = getHasMaxPinnedMessages(getState());
    dispatch({
      type: TOGGLE_PIN_MESSAGE_DIALOG,
      payload: {
        messageId,
        hasMaxPinnedMessages,
        isPinningDisappearingMessage,
      },
    });
  };
}

function hidePinMessageDialog(): TogglePinMessageDialogActionType {
  return {
    type: TOGGLE_PIN_MESSAGE_DIALOG,
    payload: null,
  };
}

// Reducer

export function getEmptyState(): GlobalModalsStateType {
  return {
    backfillFailureModalProps: undefined,
    hasConfirmationModal: false,
    callLinkAddNameModalRoomId: null,
    callLinkEditModalRoomId: null,
    callLinkPendingParticipantContactId: undefined,
    callQualitySurveyProps: null,
    confirmLeaveCallModalState: null,
    criticalIdlePrimaryDeviceModal: false,
    draftGifMessageSendModalProps: null,
    editNicknameAndNoteModalProps: null,
    isProfileNameWarningModalVisible: false,
    profileNameWarningModalConversationType: undefined,
    isShortcutGuideModalVisible: false,
    isSignalConnectionsVisible: false,
    isStoriesSettingsVisible: false,
    isWhatsNewVisible: false,
    isKeyTransparencyErrorVisible: false,
    isKeyTransparencyOnboardingVisible: false,
    lowDiskSpaceBackupImportModal: null,
    usernameOnboardingState: UsernameOnboardingState.NeverShown,
    messageRequestActionsConfirmationProps: null,
    tapToViewNotAvailableModalProps: undefined,
    notePreviewModalProps: null,
    pinMessageDialogData: null,
  };
}

export function reducer(
  state: Readonly<GlobalModalsStateType> = getEmptyState(),
  action: Readonly<GlobalModalsActionType>
): GlobalModalsStateType {
  if (action.type === TOGGLE_ABOUT_MODAL) {
    return {
      ...state,
      aboutContactModalState: action.payload,
    };
  }

  if (action.type === TOGGLE_CONFIRM_LEAVE_CALL_MODAL) {
    return {
      ...state,
      confirmLeaveCallModalState: action.payload,
    };
  }

  if (action.type === SHOW_CALL_QUALITY_SURVEY) {
    return {
      ...state,
      callQualitySurveyProps: action.payload,
    };
  }

  if (action.type === HIDE_CALL_QUALITY_SURVEY) {
    return {
      ...state,
      callQualitySurveyProps: null,
    };
  }

  if (action.type === TOGGLE_NOTE_PREVIEW_MODAL) {
    return {
      ...state,
      notePreviewModalProps: action.payload,
    };
  }

  if (action.type === TOGGLE_PROFILE_NAME_WARNING_MODAL) {
    return {
      ...state,
      isProfileNameWarningModalVisible: !state.isProfileNameWarningModalVisible,
      profileNameWarningModalConversationType:
        state.isProfileNameWarningModalVisible
          ? undefined
          : action.payload?.conversationType,
    };
  }

  if (action.type === SHOW_WHATS_NEW_MODAL) {
    return {
      ...state,
      isWhatsNewVisible: true,
    };
  }

  if (action.type === HIDE_WHATS_NEW_MODAL) {
    return {
      ...state,
      isWhatsNewVisible: false,
    };
  }

  if (action.type === SHOW_KEY_TRANSPARENCY_ERROR_DIALOG) {
    return {
      ...state,
      isKeyTransparencyErrorVisible: true,
    };
  }

  if (action.type === HIDE_KEY_TRANSPARENCY_ERROR_DIALOG) {
    return {
      ...state,
      isKeyTransparencyErrorVisible: false,
    };
  }

  if (action.type === SHOW_KEY_TRANSPARENCY_ONBOARDING_DIALOG) {
    return {
      ...state,
      isKeyTransparencyOnboardingVisible: true,
    };
  }

  if (action.type === HIDE_KEY_TRANSPARENCY_ONBOARDING_DIALOG) {
    return {
      ...state,
      isKeyTransparencyOnboardingVisible: false,
    };
  }

  if (action.type === HIDE_SERVICE_ID_NOT_FOUND_MODAL) {
    return {
      ...state,
      userNotFoundModalState: undefined,
    };
  }

  if (action.type === SHOW_SERVICE_ID_NOT_FOUND_MODAL) {
    return {
      ...state,
      userNotFoundModalState: {
        ...action.payload,
      },
    };
  }

  if (action.type === HIDE_TAP_TO_VIEW_NOT_AVAILABLE_MODAL) {
    return {
      ...state,
      tapToViewNotAvailableModalProps: undefined,
    };
  }

  if (action.type === SHOW_TAP_TO_VIEW_NOT_AVAILABLE_MODAL) {
    return {
      ...state,
      tapToViewNotAvailableModalProps: action.payload,
    };
  }

  if (action.type === SHOW_BACKFILL_FAILURE_MODAL) {
    return {
      ...state,
      backfillFailureModalProps: action.payload,
    };
  }

  if (action.type === HIDE_BACKFILL_FAILURE_MODAL) {
    return {
      ...state,
      backfillFailureModalProps: undefined,
    };
  }

  if (action.type === SHOW_CONTACT_MODAL) {
    const ourId = window.ConversationController.getOurConversationIdOrThrow();
    if (action.payload.contactId === ourId) {
      return {
        ...state,
        aboutContactModalState: action.payload,
      };
    }

    return {
      ...state,
      contactModalState: action.payload,
    };
  }

  if (action.type === HIDE_CONTACT_MODAL) {
    return {
      ...state,
      contactModalState: undefined,
    };
  }

  if (action.type === TOGGLE_SAFETY_NUMBER_MODAL) {
    return {
      ...state,
      safetyNumberModalContactId: action.payload,
    };
  }

  if (action.type === TOGGLE_ADD_USER_TO_ANOTHER_GROUP_MODAL) {
    return {
      ...state,
      addUserToAnotherGroupModalContactId: action.payload,
    };
  }

  if (action.type === TOGGLE_CALL_LINK_ADD_NAME_MODAL) {
    return {
      ...state,
      callLinkAddNameModalRoomId: action.payload,
    };
  }

  if (action.type === TOGGLE_CALL_LINK_EDIT_MODAL) {
    return {
      ...state,
      callLinkEditModalRoomId: action.payload,
    };
  }

  if (action.type === TOGGLE_CALL_LINK_PENDING_PARTICIPANT_MODAL) {
    return {
      ...state,
      callLinkPendingParticipantContactId: action.payload,
    };
  }

  if (action.type === TOGGLE_DELETE_MESSAGES_MODAL) {
    return {
      ...state,
      deleteMessagesProps: action.payload,
    };
  }

  if (action.type === TOGGLE_DRAFT_GIF_MESSAGE_SEND_MODAL) {
    return {
      ...state,
      draftGifMessageSendModalProps: action.payload,
    };
  }

  if (action.type === TOGGLE_FORWARD_MESSAGES_MODAL) {
    return {
      ...state,
      forwardMessagesProps: action.payload,
    };
  }

  if (action.type === HIDE_STORIES_SETTINGS) {
    return {
      ...state,
      isStoriesSettingsVisible: false,
    };
  }

  if (action.type === SHOW_STORIES_SETTINGS) {
    return {
      ...state,
      isStoriesSettingsVisible: true,
    };
  }

  if (action.type === TOGGLE_SIGNAL_CONNECTIONS_MODAL) {
    return {
      ...state,
      isSignalConnectionsVisible: !state.isSignalConnectionsVisible,
    };
  }

  if (action.type === TOGGLE_CONFIRMATION_MODAL) {
    return {
      ...state,
      hasConfirmationModal: action.payload,
    };
  }

  if (action.type === TOGGLE_USERNAME_ONBOARDING) {
    return {
      ...state,
      usernameOnboardingState:
        state.usernameOnboardingState === UsernameOnboardingState.Open
          ? UsernameOnboardingState.Closed
          : UsernameOnboardingState.Open,
    };
  }

  if (action.type === SHOW_SEND_ANYWAY_DIALOG) {
    const { promiseUuid, source } = action.payload;

    return {
      ...state,
      safetyNumberChangedBlockingData: {
        promiseUuid,
        source,
      },
    };
  }

  if (action.type === HIDE_SEND_ANYWAY_DIALOG) {
    return {
      ...state,
      safetyNumberChangedBlockingData: undefined,
    };
  }

  if (action.type === CLOSE_STICKER_PACK_PREVIEW) {
    return {
      ...state,
      stickerPackPreviewId: undefined,
    };
  }

  if (action.type === SHOW_STICKER_PACK_PREVIEW) {
    return {
      ...state,
      stickerPackPreviewId: action.payload,
    };
  }

  if (action.type === CLOSE_ERROR_MODAL) {
    return {
      ...state,
      errorModalProps: undefined,
    };
  }

  if (action.type === SHOW_ERROR_MODAL) {
    return {
      ...state,
      errorModalProps: action.payload,
    };
  }

  if (action.type === CLOSE_DEBUG_LOG_ERROR_MODAL) {
    return {
      ...state,
      debugLogErrorModalProps: undefined,
    };
  }

  if (action.type === SHOW_DEBUG_LOG_ERROR_MODAL) {
    return {
      ...state,
      debugLogErrorModalProps: action.payload,
    };
  }

  if (action.type === TOGGLE_EDIT_NICKNAME_AND_NOTE_MODAL) {
    return {
      ...state,
      editNicknameAndNoteModalProps: action.payload,
    };
  }

  if (action.type === TOGGLE_GROUP_MEMBER_LABEL_INFO_MODAL) {
    return {
      ...state,
      groupMemberLabelInfoModalState: action.payload,
    };
  }

  if (action.type === TOGGLE_MESSAGE_REQUEST_ACTIONS_CONFIRMATION) {
    return {
      ...state,
      messageRequestActionsConfirmationProps: action.payload,
    };
  }

  if (action.type === CLOSE_SHORTCUT_GUIDE_MODAL) {
    return {
      ...state,
      isShortcutGuideModalVisible: false,
    };
  }

  if (action.type === SHOW_SHORTCUT_GUIDE_MODAL) {
    return {
      ...state,
      isShortcutGuideModalVisible: true,
    };
  }

  if (action.type === SHOW_EDIT_HISTORY_MODAL) {
    return {
      ...state,
      editHistoryMessages: action.payload.messages,
    };
  }

  if (action.type === CLOSE_EDIT_HISTORY_MODAL) {
    return {
      ...state,
      editHistoryMessages: undefined,
    };
  }

  if (state.forwardMessagesProps != null) {
    if (action.type === MESSAGE_CHANGED) {
      if (
        !state.forwardMessagesProps.messageDrafts.some(message => {
          return message.originalMessageId === action.payload.id;
        })
      ) {
        return state;
      }

      return {
        ...state,
        forwardMessagesProps: {
          ...state.forwardMessagesProps,
          messageDrafts: copyOverMessageAttributesIntoForwardMessages(
            state.forwardMessagesProps.messageDrafts,
            action.payload.data
          ),
        },
      };
    }
  }

  if (state.editHistoryMessages != null) {
    if (action.type === MESSAGE_CHANGED || action.type === MESSAGE_DELETED) {
      if (action.type === MESSAGE_DELETED) {
        const hasMessageId = state.editHistoryMessages.some(
          edit => edit.id === action.payload.id
        );

        if (!hasMessageId) {
          return state;
        }

        return {
          ...state,
          editHistoryMessages: undefined,
        };
      }

      if (action.type === MESSAGE_CHANGED) {
        if (!action.payload.data.editHistory) {
          return state;
        }

        const hasMessageId = state.editHistoryMessages.some(
          edit => edit.id === action.payload.id
        );

        if (!hasMessageId) {
          return state;
        }

        const nextEditHistoryMessages =
          copyOverMessageAttributesIntoEditHistory(action.payload.data);

        if (!nextEditHistoryMessages) {
          return state;
        }

        return {
          ...state,
          editHistoryMessages: nextEditHistoryMessages,
        };
      }
    }
  }

  if (action.type === CLOSE_MEDIA_PERMISSIONS_MODAL) {
    state.mediaPermissionsModalProps?.abortController.abort();
    return {
      ...state,
      mediaPermissionsModalProps: undefined,
    };
  }

  if (action.type === SHOW_MEDIA_PERMISSIONS_MODAL) {
    return {
      ...state,
      mediaPermissionsModalProps: action.payload,
    };
  }

  if (action.type === SHOW_CRITICAL_IDLE_PRIMARY_DEVICE_MODAL) {
    return {
      ...state,
      criticalIdlePrimaryDeviceModal: true,
    };
  }

  if (action.type === HIDE_CRITICAL_IDLE_PRIMARY_DEVICE_MODAL) {
    return {
      ...state,
      criticalIdlePrimaryDeviceModal: false,
    };
  }

  if (action.type === SHOW_LOW_DISK_SPACE_BACKUP_IMPORT_MODAL) {
    return {
      ...state,
      lowDiskSpaceBackupImportModal: action.payload,
    };
  }

  if (action.type === HIDE_LOW_DISK_SPACE_BACKUP_IMPORT_MODAL) {
    return {
      ...state,
      lowDiskSpaceBackupImportModal: null,
    };
  }

  if (action.type === TOGGLE_PIN_MESSAGE_DIALOG) {
    return {
      ...state,
      pinMessageDialogData: action.payload,
    };
  }

  return state;
}
