import SGSimpleSettings
import Foundation
import UIKit
import Postbox
import SwiftSignalKit
import Display
import AsyncDisplayKit
import TelegramCore
import SafariServices
import MobileCoreServices
import Intents
import LegacyComponents
import TelegramPresentationData
import TelegramUIPreferences
import DeviceAccess
import TextFormat
import TelegramBaseController
import AccountContext
import TelegramStringFormatting
import OverlayStatusController
import DeviceLocationManager
import ShareController
import UrlEscaping
import ContextUI
import ComposePollUI
import AlertUI
import PresentationDataUtils
import UndoUI
import TelegramCallsUI
import TelegramNotices
import GameUI
import ScreenCaptureDetection
import GalleryUI
import OpenInExternalAppUI
import LegacyUI
import InstantPageUI
import LocationUI
import BotPaymentsUI
import DeleteChatPeerActionSheetItem
import HashtagSearchUI
import LegacyMediaPickerUI
import Emoji
import PeerAvatarGalleryUI
import PeerInfoUI
import RaiseToListen
import UrlHandling
import AvatarNode
import AppBundle
import LocalizedPeerData
import PhoneNumberFormat
import SettingsUI
import UrlWhitelist
import TelegramIntents
import TooltipUI
import StatisticsUI
import MediaResources
import GalleryData
import ChatInterfaceState
import InviteLinksUI
import Markdown
import TelegramPermissionsUI
import Speak
import TranslateUI
import UniversalMediaPlayer
import WallpaperBackgroundNode
import ChatListUI
import CalendarMessageScreen
import ReactionSelectionNode
import ReactionListContextMenuContent
import AttachmentUI
import AttachmentTextInputPanelNode
import MediaPickerUI
import ChatPresentationInterfaceState
import Pasteboard
import ChatSendMessageActionUI
import ChatTextLinkEditUI
import WebUI
import PremiumUI
import ImageTransparency
import StickerPackPreviewUI
import TextNodeWithEntities
import EntityKeyboard
import ChatTitleView
import EmojiStatusComponent
import ChatTimerScreen
import MediaPasteboardUI
import ChatListHeaderComponent
import ChatControllerInteraction
import FeaturedStickersScreen
import ChatEntityKeyboardInputNode
import StorageUsageScreen
import AvatarEditorScreen
import ChatScheduleTimeController
import ICloudResources
import StoryContainerScreen
import MoreHeaderButton
import VolumeButtons
import ChatAvatarNavigationNode
import ChatContextQuery
import PeerReportScreen
import PeerSelectionController
import SaveToCameraRoll
import ChatMessageDateAndStatusNode
import ReplyAccessoryPanelNode
import TextSelectionNode
import ChatMessagePollBubbleContentNode
import ChatMessageItem
import ChatMessageItemImpl
import ChatMessageItemView
import ChatMessageItemCommon
import ChatMessageAnimatedStickerItemNode
import ChatMessageBubbleItemNode
import ChatNavigationButton
import WebsiteType
import ChatQrCodeScreen
import PeerInfoScreen
import MediaEditor
import MediaEditorScreen
import WallpaperGalleryScreen
import WallpaperGridScreen
import VideoMessageCameraScreen
import TopMessageReactions
import AudioWaveform
import PeerNameColorScreen
import ChatEmptyNode
import ChatMediaInputStickerGridItem
import AdsInfoScreen
import MessageUI
import PhoneNumberFormat
import OwnershipTransferController
import OldChannelsController
import BrowserUI
import NotificationPeerExceptionController

public enum ChatControllerPeekActions {
    case standard
    case remove(() -> Void)
}

public final class ChatControllerOverlayPresentationData {
    public let expandData: (ASDisplayNode?, () -> Void)
    public init(expandData: (ASDisplayNode?, () -> Void)) {
        self.expandData = expandData
    }
}

enum ChatLocationInfoData {
    case peer(Promise<PeerView>)
    case replyThread(Promise<Message?>)
    case customChatContents
}

enum ChatRecordingActivity {
    case voice
    case instantVideo
    case none
}

public enum NavigateToMessageLocation {
    case id(MessageId, NavigateToMessageParams)
    case index(MessageIndex)
    case upperBound(PeerId)
    
    var messageId: MessageId? {
        switch self {
            case let .id(id, _):
                return id
            case let .index(index):
                return index.id
            case .upperBound:
                return nil
        }
    }
    
    var peerId: PeerId {
        switch self {
            case let .id(id, _):
                return id.peerId
            case let .index(index):
                return index.id.peerId
            case let .upperBound(peerId):
                return peerId
        }
    }
}

func isTopmostChatController(_ controller: ChatControllerImpl) -> Bool {
    if let _ = controller.navigationController {
        var hasOther = false
        controller.window?.forEachController({ c in
            if c is ChatControllerImpl {
                if controller !== c {
                    hasOther = true
                } else {
                    hasOther = false
                }
            }
        })
        if hasOther {
            return false
        }
    }
    return true
}

func calculateSlowmodeActiveUntilTimestamp(account: Account, untilTimestamp: Int32?) -> Int32? {
    guard let untilTimestamp = untilTimestamp else {
        return nil
    }
    let timestamp = Int32(Date().timeIntervalSince1970)
    let remainingTime = max(0, untilTimestamp - timestamp)
    if remainingTime == 0 {
        return nil
    } else {
        return untilTimestamp
    }
}

struct ScrolledToMessageId: Equatable {
    struct AllowedReplacementDirections: OptionSet {
        var rawValue: Int32
        
        static let up = AllowedReplacementDirections(rawValue: 1 << 0)
        static let down = AllowedReplacementDirections(rawValue: 1 << 1)
    }
    
    var id: MessageId
    var allowedReplacementDirection: AllowedReplacementDirections
}

public final class ChatControllerImpl: TelegramBaseController, ChatController, GalleryHiddenMediaTarget, UIDropInteractionDelegate {
    var validLayout: ContainerViewLayout?
    
    public weak var parentController: ViewController?
    public weak var customNavigationController: NavigationController?

    let currentChatListFilter: Int32?
    let chatNavigationStack: [ChatNavigationStackItem]
    let customChatNavigationStack: [EnginePeer.Id]?
    
    public var peekActions: ChatControllerPeekActions = .standard
    var didSetup3dTouch: Bool = false
    
    let context: AccountContext
    public let chatLocation: ChatLocation
    public let subject: ChatControllerSubject?
    var botStart: ChatControllerInitialBotStart?
    var attachBotStart: ChatControllerInitialAttachBotStart?
    var botAppStart: ChatControllerInitialBotAppStart?
    let mode: ChatControllerPresentationMode
    
    let peerDisposable = MetaDisposable()
    let titleDisposable = MetaDisposable()
    var accountPeerDisposable: Disposable?
    let navigationActionDisposable = MetaDisposable()
    var networkStateDisposable: Disposable?
    
    let messageIndexDisposable = MetaDisposable()
    
    let _chatLocationInfoReady = Promise<Bool>()
    var didSetChatLocationInfoReady = false
    let chatLocationInfoData: ChatLocationInfoData
    
    let cachedDataReady = Promise<Bool>()
    var didSetCachedDataReady = false

    let wallpaperReady = Promise<Bool>()
    let presentationReady = Promise<Bool>()
    
    var presentationInterfaceState: ChatPresentationInterfaceState
    let presentationInterfaceStatePromise: ValuePromise<ChatPresentationInterfaceState>
    public var presentationInterfaceStateSignal: Signal<Any, NoError> {
        return self.presentationInterfaceStatePromise.get() |> map { $0 }
    }
    
    public var selectedMessageIds: Set<EngineMessage.Id>? {
        return self.presentationInterfaceState.interfaceState.selectionState?.selectedIds
    }
    
    let chatThemeEmoticonPromise = Promise<String?>()
    let chatWallpaperPromise = Promise<TelegramWallpaper?>()
    
    var chatTitleView: ChatTitleView?
    var leftNavigationButton: ChatNavigationButton?
    var rightNavigationButton: ChatNavigationButton?
    var secondaryRightNavigationButton: ChatNavigationButton?
    var chatInfoNavigationButton: ChatNavigationButton?
    
    var moreBarButton: MoreHeaderButton
    var moreInfoNavigationButton: ChatNavigationButton?
    
    var peerView: PeerView?
    var threadInfo: EngineMessageHistoryThread.Info?
    
    var historyStateDisposable: Disposable?
    
    let galleryHiddenMesageAndMediaDisposable = MetaDisposable()
    let temporaryHiddenGalleryMediaDisposable = MetaDisposable()
    
    let galleryPresentationContext = PresentationContext()

    let chatBackgroundNode: WallpaperBackgroundNode
    public private(set) var controllerInteraction: ChatControllerInteraction?
    var interfaceInteraction: ChatPanelInterfaceInteraction?
    
    let messageContextDisposable = MetaDisposable()
    let controllerNavigationDisposable = MetaDisposable()
    let sentMessageEventsDisposable = MetaDisposable()
    let failedMessageEventsDisposable = MetaDisposable()
    let sentPeerMediaMessageEventsDisposable = MetaDisposable()
    weak var currentFailedMessagesAlertController: ViewController?
    let messageActionCallbackDisposable = MetaDisposable()
    let messageActionUrlAuthDisposable = MetaDisposable()
    let editMessageDisposable = MetaDisposable()
    let editMessageErrorsDisposable = MetaDisposable()
    let enqueueMediaMessageDisposable = MetaDisposable()
    var resolvePeerByNameDisposable: MetaDisposable?
    var shareStatusDisposable: MetaDisposable?
    var clearCacheDisposable: MetaDisposable?
    var bankCardDisposable: MetaDisposable?
    var hasActiveGroupCallDisposable: Disposable?
    var sendAsPeersDisposable: Disposable?
    var preloadAttachBotIconsDisposables: DisposableSet?
    var keepMessageCountersSyncrhonizedDisposable: Disposable?
    var keepSavedMessagesSyncrhonizedDisposable: Disposable?
    var saveMediaDisposable: MetaDisposable?
    var giveawayStatusDisposable: MetaDisposable?
    var nameColorDisposable: Disposable?
    
    let editingMessage = ValuePromise<Float?>(nil, ignoreRepeated: true)
    let startingBot = ValuePromise<Bool>(false, ignoreRepeated: true)
    let unblockingPeer = ValuePromise<Bool>(false, ignoreRepeated: true)
    public let searching = ValuePromise<Bool>(false, ignoreRepeated: true)
    let searchResult = Promise<(SearchMessagesResult, SearchMessagesState, SearchMessagesLocation)?>()
    let loadingMessage = Promise<ChatLoadingMessageSubject?>(nil)
    let performingInlineSearch = ValuePromise<Bool>(false, ignoreRepeated: true)
    
    var stateServiceTasks: [AnyHashable: Disposable] = [:]
    
    var preloadHistoryPeerId: PeerId?
    let preloadHistoryPeerIdDisposable = MetaDisposable()

    var preloadNextChatPeerId: PeerId?
    let preloadNextChatPeerIdDisposable = MetaDisposable()
    
    var preloadSavedMessagesChatsDisposable: Disposable?
    
    let botCallbackAlertMessage = Promise<String?>(nil)
    var botCallbackAlertMessageDisposable: Disposable?
    
    var selectMessagePollOptionDisposables: DisposableDict<MessageId>?
    var selectPollOptionFeedback: HapticFeedback?
    
    var resolveUrlDisposable: MetaDisposable?
    
    var contextQueryStates: [ChatPresentationInputQueryKind: (ChatPresentationInputQuery, Disposable)] = [:]
    var searchQuerySuggestionState: (ChatPresentationInputQuery?, Disposable)?
    var urlPreviewQueryState: (UrlPreviewState?, Disposable)?
    var editingUrlPreviewQueryState: (UrlPreviewState?, Disposable)?
    var replyMessageState: (EngineMessage.Id, Disposable)?
    var searchState: ChatSearchState?
    
    var shakeFeedback: HapticFeedback?
    
    var recordingModeFeedback: HapticFeedback?
    var recorderFeedback: HapticFeedback?
    var audioRecorderValue: ManagedAudioRecorder?
    var audioRecorder = Promise<ManagedAudioRecorder?>()
    var audioRecorderDisposable: Disposable?
    var audioRecorderStatusDisposable: Disposable?
    
    var videoRecorderValue: VideoMessageCameraScreen?
    var videoRecorder = Promise<VideoMessageCameraScreen?>()
    var videoRecorderDisposable: Disposable?
    
    var recorderDataDisposable = MetaDisposable()
    
    var buttonKeyboardMessageDisposable: Disposable?
    var cachedDataDisposable: Disposable?
    var chatUnreadCountDisposable: Disposable?
    var buttonUnreadCountDisposable: Disposable?
    var chatUnreadMentionCountDisposable: Disposable?
    var peerInputActivitiesDisposable: Disposable?
    
    var peerInputActivitiesPromise = Promise<[(Peer, PeerInputActivity)]>()
    var interactiveEmojiSyncDisposable = MetaDisposable()
    
    var recentlyUsedInlineBotsValue: [Peer] = []
    var recentlyUsedInlineBotsDisposable: Disposable?
    
    var unpinMessageDisposable: MetaDisposable?
        
    let typingActivityPromise = Promise<Bool>(false)
    var inputActivityDisposable: Disposable?
    var recordingActivityValue: ChatRecordingActivity = .none
    let recordingActivityPromise = ValuePromise<ChatRecordingActivity>(.none, ignoreRepeated: true)
    var recordingActivityDisposable: Disposable?
    var acquiredRecordingActivityDisposable: Disposable?
    let choosingStickerActivityPromise = ValuePromise<Bool>(false)
    var choosingStickerActivityDisposable: Disposable?
    
    var searchDisposable: MetaDisposable?
    
    var historyNavigationStack = ChatHistoryNavigationStack()
    
    public let canReadHistory = ValuePromise<Bool>(true, ignoreRepeated: true)
    public let hasBrowserOrAppInFront = Promise<Bool>(false)
    var reminderActivity: NSUserActivity?
    var isReminderActivityEnabled: Bool = false
    
    var canReadHistoryValue = false {
        didSet {
            self.computedCanReadHistoryPromise.set(self.canReadHistoryValue)
        }
    }
    var canReadHistoryDisposable: Disposable?
    var computedCanReadHistoryPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
    
    var themeEmoticonAndDarkAppearancePreviewPromise = Promise<(String?, Bool?)>((nil, nil))
    var didSetPresentationData = false
    var presentationData: PresentationData
    var presentationDataPromise = Promise<PresentationData>()
    override public var updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>) {
        return (self.presentationData, self.presentationDataPromise.get())
    }
    var presentationDataDisposable: Disposable?
    var forcedTheme: PresentationTheme?
    var forcedNavigationBarTheme: PresentationTheme?
    var forcedWallpaper: TelegramWallpaper?
    
    var automaticMediaDownloadSettings: MediaAutoDownloadSettings
    var automaticMediaDownloadSettingsDisposable: Disposable?
    
    var disableStickerAnimationsPromise = ValuePromise<Bool>(false)
    var disableStickerAnimationsValue = false
    var disableStickerAnimations: Bool {
        get {
            return self.disableStickerAnimationsValue
        } set {
            self.disableStickerAnimationsPromise.set(newValue)
        }
    }
    var stickerSettings: ChatInterfaceStickerSettings
    var stickerSettingsDisposable: Disposable?
    
    var applicationInForegroundDisposable: Disposable?
    var applicationInFocusDisposable: Disposable?
    
    let checksTooltipDisposable = MetaDisposable()
    var shouldDisplayChecksTooltip = false
    
    let peerSuggestionsDisposable = MetaDisposable()
    let peerSuggestionsDismissDisposable = MetaDisposable()
    var displayedConvertToGigagroupSuggestion = false
    
    var checkedPeerChatServiceActions = false
    
    var willAppear = false
    var didAppear = false
    var scheduledActivateInput: ChatControllerActivateInput?
    
    var raiseToListen: RaiseToListenManager?
    var voicePlaylistDidEndTimestamp: Double = 0.0

    weak var emojiTooltipController: TooltipController?
    weak var sendingOptionsTooltipController: TooltipController?
    weak var searchResultsTooltipController: TooltipController?
    weak var messageTooltipController: TooltipController?
    weak var videoUnmuteTooltipController: TooltipController?
    var didDisplayVideoUnmuteTooltip = false
    var didDisplayGroupEmojiTip = false
    var didDisplaySendWhenOnlineTip = false
    let displaySendWhenOnlineTipDisposable = MetaDisposable()
    
    weak var silentPostTooltipController: TooltipController?
    weak var mediaRecordingModeTooltipController: TooltipController?
    weak var mediaRestrictedTooltipController: TooltipController?
    var mediaRestrictedTooltipControllerMode = true
    weak var checksTooltipController: TooltipController?
    weak var copyProtectionTooltipController: TooltipController?
    weak var emojiPackTooltipController: TooltipScreen?
    weak var birthdayTooltipController: TooltipScreen?
    
    weak var slowmodeTooltipController: ChatSlowmodeHintController?
    
    weak var currentContextController: ContextController?
    public var visibleContextController: ViewController? {
        return self.currentContextController
    }
    
    weak var sendMessageActionsController: ChatSendMessageActionSheetController?
    var searchResultsController: ChatSearchResultsController?

    weak var themeScreen: ChatThemeScreen?
    
    weak var currentPinchController: PinchController?
    weak var currentPinchSourceItemNode: ListViewItemNode?
    
    var screenCaptureManager: ScreenCaptureDetectionManager?
    let chatAdditionalDataDisposable = MetaDisposable()
    
    var reportIrrelvantGeoNoticePromise = Promise<Bool?>()
    var reportIrrelvantGeoNotice: Bool?
    var reportIrrelvantGeoDisposable: Disposable?
    
    var hasScheduledMessages: Bool = false
    
    var volumeButtonsListener: VolumeButtonsListener?
    
    var beginMediaRecordingRequestId: Int = 0
    var lockMediaRecordingRequestId: Int?
    
    var updateSlowmodeStatusDisposable = MetaDisposable()
    var updateSlowmodeStatusTimerValue: Int32?
    
    var isDismissed = false
    
    var focusOnSearchAfterAppearance: (ChatSearchDomain, String)?
    
    let keepPeerInfoScreenDataHotDisposable = MetaDisposable()
    let preloadAvatarDisposable = MetaDisposable()
    
    let peekData: ChatPeekTimeout?
    let peekTimerDisposable = MetaDisposable()
    
    let createVoiceChatDisposable = MetaDisposable()
    
    let selectAddMemberDisposable = MetaDisposable()
    let addMemberDisposable = MetaDisposable()
    let joinChannelDisposable = MetaDisposable()
    
    var shouldDisplayDownButton = false

    var hasEmbeddedTitleContent = false
    var isEmbeddedTitleContentHidden = false
    
    let chatLocationContextHolder: Atomic<ChatLocationContextHolder?>
    
    weak var attachmentController: AttachmentController?
    weak var currentWebAppController: ViewController?
    
    weak var currentImportMessageTooltip: UndoOverlayController?
    
    public var customNavigationBarContentNode: NavigationBarContentNode?
    public var customNavigationPanelNode: ChatControllerCustomNavigationPanelNode?
    public var stateUpdated: ((ContainedViewLayoutTransition) -> Void)?

    public var customDismissSearch: (() -> Void)?

    public override var customData: Any? {
        return self.chatLocation
    }
    
    override public var customNavigationData: CustomViewControllerNavigationData? {
        get {
            if let peerId = self.chatLocation.peerId {
                return ChatControllerNavigationData(peerId: peerId, threadId: self.chatLocation.threadId)
            } else {
                return nil
            }
        }
    }
    
    override public var interactiveNavivationGestureEdgeWidth: InteractiveTransitionGestureRecognizerEdgeWidth? {
        return .widthMultiplier(factor: 0.35, min: 16.0, max: 200.0)
    }
    
    var scheduledScrollToMessageId: (MessageId, NavigateToMessageParams)?
    
    public var purposefulAction: (() -> Void)?
    public var dismissPreviewing: (() -> Void)?
    
    var updatedClosedPinnedMessageId: ((MessageId) -> Void)?
    var requestedUnpinAllMessages: ((Int, MessageId) -> Void)?
    
    public var isSelectingMessagesUpdated: ((Bool) -> Void)?
    
    let scrolledToMessageId = ValuePromise<ScrolledToMessageId?>(nil, ignoreRepeated: true)
    var scrolledToMessageIdValue: ScrolledToMessageId? = nil {
        didSet {
            self.scrolledToMessageId.set(self.scrolledToMessageIdValue)
        }
    }
    
    var translationStateDisposable: Disposable?
    var chatLanguagePredictionDisposable: Disposable?
    var predictedChatLanguage: String?
    var premiumGiftSuggestionDisposable: Disposable?
    
    var nextChannelToReadDisposable: Disposable?
    var offerNextChannelToRead = false
    
    var inviteRequestsContext: PeerInvitationImportersContext?
    var inviteRequestsDisposable = MetaDisposable()
    
    public var overlayTitle: String? {
        var title: String?
        if let threadInfo = self.threadInfo {
            title = threadInfo.title
        } else if let peerView = self.peerView {
            if let peer = peerViewMainPeer(peerView) {
                title = EnginePeer(peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)
            }
        }
        return title
    }
    
    var currentSpeechHolder: SpeechSynthesizerHolder?
    
    var powerSavingMonitoringDisposable: Disposable?
    
    var avatarNode: ChatAvatarNavigationNode?
    var storyStats: PeerStoryStats?
    
    var performTextSelectionAction: ((Message?, Bool, NSAttributedString, TextSelectionAction) -> Void)?
    var performOpenURL: ((Message?, String, Promise<Bool>?) -> Void)?
    
    var networkSpeedEventsDisposable: Disposable?
    
    var stickerVideoExport: MediaEditorVideoExport?
    
    var messageComposeController: MFMessageComposeViewController?
    
    weak var currentSendStarsUndoController: UndoOverlayController?
    var currentSendStarsUndoMessageId: EngineMessage.Id?
    var currentSendStarsUndoCount: Int = 0
    
    public var alwaysShowSearchResultsAsList: Bool = false {
        didSet {
            self.presentationInterfaceState = self.presentationInterfaceState.updatedDisplayHistoryFilterAsList(self.alwaysShowSearchResultsAsList)
            self.chatDisplayNode.alwaysShowSearchResultsAsList = self.alwaysShowSearchResultsAsList
        }
    }
    
    public var includeSavedPeersInSearchResults: Bool = false {
        didSet {
            self.chatDisplayNode.includeSavedPeersInSearchResults = self.includeSavedPeersInSearchResults
        }
    }
    
    public var showListEmptyResults: Bool = false {
        didSet {
            self.chatDisplayNode.showListEmptyResults = self.showListEmptyResults
        }
    }
    
    public init(
        context: AccountContext,
        chatLocation: ChatLocation,
        chatLocationContextHolder: Atomic<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(value: nil),
        subject: ChatControllerSubject? = nil,
        botStart: ChatControllerInitialBotStart? = nil,
        attachBotStart: ChatControllerInitialAttachBotStart? = nil,
        botAppStart: ChatControllerInitialBotAppStart? = nil,
        mode: ChatControllerPresentationMode = .standard(.default),
        peekData: ChatPeekTimeout? = nil,
        peerNearbyData: ChatPeerNearbyData? = nil,
        chatListFilter: Int32? = nil,
        chatNavigationStack: [ChatNavigationStackItem] = [],
        customChatNavigationStack: [EnginePeer.Id]? = nil,
        params: ChatControllerParams? = nil
    ) {
        let _ = ChatControllerCount.modify { value in
            return value + 1
        }
        
        self.context = context
        self.chatLocation = chatLocation
        self.chatLocationContextHolder = chatLocationContextHolder
        self.subject = subject
        self.botStart = botStart
        self.attachBotStart = attachBotStart
        self.botAppStart = botAppStart
        self.mode = mode
        self.peekData = peekData
        self.currentChatListFilter = chatListFilter
        self.chatNavigationStack = chatNavigationStack
        self.customChatNavigationStack = customChatNavigationStack
        
        self.forcedTheme = params?.forcedTheme
        self.forcedNavigationBarTheme = params?.forcedNavigationBarTheme
        self.forcedWallpaper = params?.forcedWallpaper

        var useSharedAnimationPhase = false
        switch mode {
        case .standard(.default):
            useSharedAnimationPhase = true
        default:
            break
        }
        self.chatBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: useSharedAnimationPhase)
        self.wallpaperReady.set(self.chatBackgroundNode.isReady)
        
        var locationBroadcastPanelSource: LocationBroadcastPanelSource
        var groupCallPanelSource: GroupCallPanelSource
        
        switch chatLocation {
        case let .peer(peerId):
            locationBroadcastPanelSource = .peer(peerId)
            switch subject {
            case .message, .none:
                groupCallPanelSource = .peer(peerId)
            default:
                groupCallPanelSource = .none
            }
            self.chatLocationInfoData = .peer(Promise())
        case let .replyThread(replyThreadMessage):
            locationBroadcastPanelSource = .none
            groupCallPanelSource = .none
            let promise = Promise<Message?>()
            if let effectiveMessageId = replyThreadMessage.effectiveMessageId {
                promise.set(context.engine.data.subscribe(TelegramEngine.EngineData.Item.Messages.Message(id: effectiveMessageId))
                            |> map { message -> Message? in
                    guard let message = message else {
                        return nil
                    }
                    return message._asMessage()
                })
            } else {
                promise.set(.single(nil))
            }
            self.chatLocationInfoData = .replyThread(promise)
        case .customChatContents:
            locationBroadcastPanelSource = .none
            groupCallPanelSource = .none
            self.chatLocationInfoData = .customChatContents
        }
        
        var presentationData = context.sharedContext.currentPresentationData.with { $0 }
        if let forcedTheme = self.forcedTheme {
            presentationData = presentationData.withUpdated(theme: forcedTheme)
        }
        if let forcedWallpaper = self.forcedWallpaper {
            presentationData = presentationData.withUpdated(chatWallpaper: forcedWallpaper)
        }
        self.presentationData = presentationData
        self.automaticMediaDownloadSettings = context.sharedContext.currentAutomaticMediaDownloadSettings
        
        self.stickerSettings = ChatInterfaceStickerSettings()
        
        self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: context.account.peerId, mode: mode, chatLocation: chatLocation, subject: subject, peerNearbyData: peerNearbyData, greetingData: context.prefetchManager?.preloadedGreetingSticker, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil)
        
        if case let .customChatContents(customChatContents) = subject {
            switch customChatContents.kind {
            case .quickReplyMessageInput:
                break
            case let .businessLinkSetup(link):
                if !link.message.isEmpty {
                    self.presentationInterfaceState = self.presentationInterfaceState.updatedInterfaceState({ interfaceState in
                        return interfaceState.withUpdatedEffectiveInputState(ChatTextInputState(inputText: chatInputStateStringWithAppliedEntities(link.message, entities: link.entities)))
                    })
                }
            case .hashTagSearch:
                break
            }
        }
        
        self.presentationInterfaceStatePromise = ValuePromise(self.presentationInterfaceState)
        
        var mediaAccessoryPanelVisibility = MediaAccessoryPanelVisibility.none
        if case .standard = mode {
            mediaAccessoryPanelVisibility = .specific(size: .compact)
        } else {
            locationBroadcastPanelSource = .none
            groupCallPanelSource = .none
        }
        let navigationBarPresentationData: NavigationBarPresentationData?
        switch mode {
        case .inline, .standard(.embedded):
            navigationBarPresentationData = nil
        default:
            navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData, hideBackground: self.context.sharedContext.immediateExperimentalUISettings.playerEmbedding ? true : false, hideBadge: false)
        }
        
        self.moreBarButton = MoreHeaderButton(color: self.presentationData.theme.rootController.navigationBar.buttonColor)
        self.moreBarButton.isUserInteractionEnabled = true
        
        super.init(context: context, navigationBarPresentationData: navigationBarPresentationData, mediaAccessoryPanelVisibility: mediaAccessoryPanelVisibility, locationBroadcastPanelSource: locationBroadcastPanelSource, groupCallPanelSource: groupCallPanelSource)
        
        self.automaticallyControlPresentationContextLayout = false
        self.blocksBackgroundWhenInOverlay = true
        self.acceptsFocusWhenInOverlay = true
        
        self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
        
        self.ready.set(.never())
        
        self.scrollToTop = { [weak self] in
            guard let strongSelf = self, strongSelf.isNodeLoaded else {
                return
            }
            if let attachmentController = strongSelf.attachmentController {
                attachmentController.scrollToTop?()
            } else {
                strongSelf.chatDisplayNode.scrollToTop()
            }
        }
        
        self.attemptNavigation = { [weak self] action in
            guard let strongSelf = self else {
                return true
            }
            
            if let _ = strongSelf.videoRecorderValue {
                return false
            }
            
            strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts()
            
            if strongSelf.presentVoiceMessageDiscardAlert(action: action, performAction: false) {
                return false
            }
                        
            if case let .customChatContents(customChatContents) = strongSelf.presentationInterfaceState.subject {
                switch customChatContents.kind {
                case .hashTagSearch:
                    return true
                case let .quickReplyMessageInput(_, shortcutType):
                    if let historyView = strongSelf.chatDisplayNode.historyNode.originalHistoryView, historyView.entries.isEmpty {
                        
                        let titleString: String
                        let textString: String
                        switch shortcutType {
                        case .generic:
                            titleString = strongSelf.presentationData.strings.QuickReply_ChatRemoveGeneric_Title
                            textString = strongSelf.presentationData.strings.QuickReply_ChatRemoveGeneric_Text
                        case .greeting:
                            titleString = strongSelf.presentationData.strings.QuickReply_ChatRemoveGreetingMessage_Title
                            textString = strongSelf.presentationData.strings.QuickReply_ChatRemoveGreetingMessage_Text
                        case .away:
                            titleString = strongSelf.presentationData.strings.QuickReply_ChatRemoveAwayMessage_Title
                            textString = strongSelf.presentationData.strings.QuickReply_ChatRemoveAwayMessage_Text
                        }
                        
                        strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: titleString, text: textString, actions: [
                            TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}),
                            TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.QuickReply_ChatRemoveGeneric_DeleteAction, action: { [weak strongSelf] in
                                strongSelf?.dismiss()
                            })
                        ]), in: .window(.root))
                        
                        return false
                    }
                case let .businessLinkSetup(link):
                    var inputText = convertMarkdownToAttributes(strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText)
                    inputText = trimChatInputText(inputText)
                    let entities = generateChatInputTextEntities(inputText, generateLinks: false)
                    
                    let message = inputText.string
                    
                    if message != link.message || entities != link.entities {
                        strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.Business_Links_AlertUnsavedText, actions: [
                            TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}),
                            TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.Business_Links_AlertUnsavedAction, action: { [weak strongSelf] in
                                strongSelf?.dismiss()
                            })
                        ]), in: .window(.root))
                        
                        return false
                    }
                }
            }
            
            return true
        }
        
        let controllerInteraction = ChatControllerInteraction(openMessage: { [weak self] message, params in
            guard let strongSelf = self, strongSelf.isNodeLoaded, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(message.id) else {
                return false
            }
            
            let mode = params.mode
            
            let displayVoiceMessageDiscardAlert: () -> Bool = {
                if strongSelf.presentVoiceMessageDiscardAlert(action: { [weak self] in
                    if let strongSelf = self {
                        Queue.mainQueue().after(0.1, {
                            let _ = strongSelf.controllerInteraction?.openMessage(message, params)
                        })
                    }
                }, performAction: false) {
                    return false
                }
                return true
            }
            
            strongSelf.commitPurposefulAction()
            strongSelf.dismissAllTooltips()
            
            strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts()
            
            var openMessageByAction = false
            var isLocation = false
                        
            for media in message.media {
                if media is TelegramMediaMap {
                    if !displayVoiceMessageDiscardAlert() {
                        return false
                    }
                    isLocation = true
                }
                if let file = media as? TelegramMediaFile {
                    if file.isInstantVideo {
                        if strongSelf.chatDisplayNode.isInputViewFocused {
                            strongSelf.returnInputViewFocus = true
                            strongSelf.chatDisplayNode.dismissInput()
                        }
                    }
                    if file.isMusic || file.isVoice || file.isInstantVideo {
                        if !displayVoiceMessageDiscardAlert() {
                            return false
                        }
                        
                        if (file.isVoice || file.isInstantVideo) && message.minAutoremoveOrClearTimeout == viewOnceTimeout {
                            strongSelf.openViewOnceMediaMessage(message)
                            return false
                        }
                    } else if file.isVideo {
                        if !displayVoiceMessageDiscardAlert() {
                            return false
                        }
                    }
                }
                if let paidContent = media as? TelegramMediaPaidContent, let extendedMedia = paidContent.extendedMedia.first {
                    switch extendedMedia {
                        case .preview:
                            if displayVoiceMessageDiscardAlert() {
                                strongSelf.controllerInteraction?.openCheckoutOrReceipt(message.id, params)
                                return true
                            } else {
                                return false
                            }
                        case .full:
                            break
                    }
                } else if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia {
                    switch extendedMedia {
                        case .preview:
                            if displayVoiceMessageDiscardAlert() {
                                strongSelf.controllerInteraction?.openCheckoutOrReceipt(message.id, nil)
                                return true
                            } else {
                                return false
                            }
                        case .full:
                            break
                    }
                } else if media is TelegramMediaGiveaway || media is TelegramMediaGiveawayResults {
                    let progress = params.progress
                    let presentationData = strongSelf.presentationData
                    
                    var signal = strongSelf.context.engine.payments.premiumGiveawayInfo(peerId: message.id.peerId, messageId: message.id)
                    let disposable: MetaDisposable
                    if let current = strongSelf.giveawayStatusDisposable {
                        disposable = current
                    } else {
                        disposable = MetaDisposable()
                        strongSelf.giveawayStatusDisposable = disposable
                    }
                    
                    let progressSignal = Signal<Never, NoError> { [weak self] subscriber in
                        if let progress {
                            progress.set(.single(true))
                            return ActionDisposable {
                                Queue.mainQueue().async() {
                                    progress.set(.single(false))
                                }
                            }
                        } else {
                            let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil))
                            self?.present(controller, in: .window(.root))
                            return ActionDisposable { [weak controller] in
                                Queue.mainQueue().async() {
                                    controller?.dismiss()
                                }
                            }
                        }
                    }
                    |> runOn(Queue.mainQueue())
                    |> delay(0.25, queue: Queue.mainQueue())
                    let progressDisposable = progressSignal.startStrict()
                    
                    signal = signal
                    |> afterDisposed {
                        Queue.mainQueue().async {
                            progressDisposable.dispose()
                        }
                    }
                    disposable.set((signal
                    |> deliverOnMainQueue).startStrict(next: { [weak self] info in
                        if let strongSelf = self, let info {
                            strongSelf.displayGiveawayStatusInfo(messageId: message.id, giveawayInfo: info)
                        }
                    }))
                    
                    return true
                } else if let action = media as? TelegramMediaAction {
                    if !displayVoiceMessageDiscardAlert() {
                        return false
                    }
                    switch action.action {
                        case .pinnedMessageUpdated, .gameScore, .setSameChatWallpaper, .giveawayResults, .customText:
                            for attribute in message.attributes {
                                if let attribute = attribute as? ReplyMessageAttribute {
                                    strongSelf.navigateToMessage(from: message.id, to: .id(attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.isQuote ? attribute.quote.flatMap { quote in NavigateToMessageParams.Quote(string: quote.text, offset: quote.offset) } : nil)))
                                    break
                                }
                            }
                        case let .photoUpdated(image):
                            openMessageByAction = image != nil
                        case .groupPhoneCall, .inviteToGroupPhoneCall:
                            if let activeCall = strongSelf.presentationInterfaceState.activeGroupCallInfo?.activeCall {
                                strongSelf.joinGroupCall(peerId: message.id.peerId, invite: nil, activeCall: EngineGroupCallDescription(id: activeCall.id, accessHash: activeCall.accessHash, title: activeCall.title, scheduleTimestamp: activeCall.scheduleTimestamp, subscribedToScheduled: activeCall.subscribedToScheduled, isStream: activeCall.isStream))
                            } else {
                                var canManageGroupCalls = false
                                if let channel = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer as? TelegramChannel {
                                    if channel.flags.contains(.isCreator) || channel.hasPermission(.manageCalls) {
                                        canManageGroupCalls = true
                                    }
                                } else if let group = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer as? TelegramGroup {
                                    if case .creator = group.role {
                                        canManageGroupCalls = true
                                    } else if case let .admin(rights, _) = group.role {
                                        if rights.rights.contains(.canManageCalls) {
                                            canManageGroupCalls = true
                                        }
                                    }
                                }
                                
                                if canManageGroupCalls {
                                    let text: String
                                    if let channel = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer as? TelegramChannel, case .broadcast = channel.info {
                                        text = strongSelf.presentationData.strings.LiveStream_CreateNewVoiceChatText
                                    } else {
                                        text = strongSelf.presentationData.strings.VoiceChat_CreateNewVoiceChatText
                                    }
                                    strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.VoiceChat_CreateNewVoiceChatStartNow, action: {
                                        if let strongSelf = self {
                                            var dismissStatus: (() -> Void)?
                                            let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: {
                                                dismissStatus?()
                                            }))
                                            dismissStatus = { [weak self, weak statusController] in
                                                self?.createVoiceChatDisposable.set(nil)
                                                statusController?.dismiss()
                                            }
                                            strongSelf.present(statusController, in: .window(.root))
                                            strongSelf.createVoiceChatDisposable.set((strongSelf.context.engine.calls.createGroupCall(peerId: message.id.peerId, title: nil, scheduleDate: nil, isExternalStream: false)
                                            |> deliverOnMainQueue).startStrict(next: { [weak self] info in
                                                guard let strongSelf = self else {
                                                    return
                                                }
                                                strongSelf.joinGroupCall(peerId: message.id.peerId, invite: nil, activeCall: EngineGroupCallDescription(id: info.id, accessHash: info.accessHash, title: info.title, scheduleTimestamp: info.scheduleTimestamp, subscribedToScheduled: info.subscribedToScheduled, isStream: info.isStream))
                                            }, error: { [weak self] error in
                                                dismissStatus?()
                                                
                                                guard let strongSelf = self else {
                                                    return
                                                }
                                            
                                                let text: String
                                                switch error {
                                                case .generic, .scheduledTooLate:
                                                    text = strongSelf.presentationData.strings.Login_UnknownError
                                                case .anonymousNotAllowed:
                                                    if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info {
                                                        text = strongSelf.presentationData.strings.LiveStream_AnonymousDisabledAlertText
                                                    } else {
                                                        text = strongSelf.presentationData.strings.VoiceChat_AnonymousDisabledAlertText
                                                    }
                                                }
                                                strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
                                            }, completed: {
                                                dismissStatus?()
                                            }))
                                        }
                                    }), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.VoiceChat_CreateNewVoiceChatSchedule, action: {
                                        if let strongSelf = self {
                                            strongSelf.context.scheduleGroupCall(peerId: message.id.peerId)
                                        }
                                    }), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {})], actionLayout: .vertical), in: .window(.root))
                                }
                            }
                            return true
                        case .messageAutoremoveTimeoutUpdated:
                            var canSetupAutoremoveTimeout = false
                            
                            if let _ = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat {
                                canSetupAutoremoveTimeout = false
                            } else if let group = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramGroup {
                                if !group.hasBannedPermission(.banChangeInfo) {
                                    canSetupAutoremoveTimeout = true
                                }
                            } else if let user = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramUser {
                                if user.id != strongSelf.context.account.peerId && user.botInfo == nil {
                                    canSetupAutoremoveTimeout = true
                                }
                            } else if let channel = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel {
                                if channel.hasPermission(.changeInfo) {
                                    canSetupAutoremoveTimeout = true
                                }
                            }
                            
                            if canSetupAutoremoveTimeout {
                                strongSelf.presentAutoremoveSetup()
                            }
                        case let .paymentSent(currency, _, _, _, _):
                            if currency == "XTR" {
                                let _ = (context.engine.payments.requestBotPaymentReceipt(messageId: message.id)
                                |> deliverOnMainQueue).start(next: { [weak self] receipt in
                                    guard let self else {
                                        return
                                    }
                                    self.push(self.context.sharedContext.makeStarsReceiptScreen(context: self.context, receipt: receipt))
                                })
                            } else {
                                strongSelf.present(BotReceiptController(context: strongSelf.context, messageId: message.id), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
                            }
                            /*for attribute in message.attributes {
                                if let attribute = attribute as? ReplyMessageAttribute {
                                    //strongSelf.navigateToMessage(from: message.id, to: .id(attribute.messageId))
                                    break
                                }
                            }*/
                            return true
                        case .setChatTheme:
                            strongSelf.presentThemeSelection()
                            return true
                        case let .setChatWallpaper(wallpaper, _):
                            guard let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else {
                                return true
                            }
                            if let peer = peer as? TelegramChannel {
                                if peer.flags.contains(.isCreator) || peer.adminRights?.rights.contains(.canChangeInfo) == true {
                                    let _ = (context.engine.peers.getChannelBoostStatus(peerId: peer.id)
                                    |> deliverOnMainQueue).start(next: { [weak self] boostStatus in
                                        guard let self else {
                                            return
                                        }
                                        self.push(ChannelAppearanceScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, peerId: peer.id, boostStatus: boostStatus))
                                    })
                                }
                                return true
                            }
                            guard message.effectivelyIncoming(strongSelf.context.account.peerId), let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else {
                                strongSelf.presentThemeSelection()
                                return true
                            }
                            strongSelf.chatDisplayNode.dismissInput()
                            var options = WallpaperPresentationOptions()
                            var intensity: Int32?
                            if let settings = wallpaper.settings {
                                if settings.blur {
                                    options.insert(.blur)
                                }
                                if settings.motion {
                                    options.insert(.motion)
                                }
                                if case let .file(file) = wallpaper, !file.isPattern {
                                    intensity = settings.intensity
                                }
                            }
                            let wallpaperPreviewController = WallpaperGalleryController(context: strongSelf.context, source: .wallpaper(wallpaper, options, [], intensity, nil, nil), mode: .peer(EnginePeer(peer), true))
                            wallpaperPreviewController.apply = { [weak wallpaperPreviewController] entry, options, _, _, brightness, forBoth in
                                var settings: WallpaperSettings?
                                if case let .wallpaper(wallpaper, _) = entry {
                                    let baseSettings = wallpaper.settings
                                    var intensity: Int32? = baseSettings?.intensity
                                    if case let .file(file) = wallpaper, !file.isPattern {
                                        if let brightness {
                                            intensity = max(0, min(100, Int32(brightness * 100.0)))
                                        }
                                    }
                                    settings = WallpaperSettings(blur: options.contains(.blur), motion: options.contains(.motion), colors: baseSettings?.colors ?? [], intensity: intensity, rotation: baseSettings?.rotation)
                                }
                                let _ = (strongSelf.context.engine.themes.setExistingChatWallpaper(messageId: message.id, settings: settings, forBoth: forBoth)
                                |> deliverOnMainQueue).startStandalone()
                                Queue.mainQueue().after(0.1) {
                                    wallpaperPreviewController?.dismiss()
                                }
                            }
                            strongSelf.push(wallpaperPreviewController)
                            return true
                        case let .giftPremium(_, _, duration, _, _):
                            strongSelf.chatDisplayNode.dismissInput()
                            let fromPeerId: PeerId = message.author?.id == strongSelf.context.account.peerId ? strongSelf.context.account.peerId : message.id.peerId
                            let toPeerId: PeerId = message.author?.id == strongSelf.context.account.peerId ? message.id.peerId : strongSelf.context.account.peerId
                            let controller = PremiumIntroScreen(context: strongSelf.context, source: .gift(from: fromPeerId, to: toPeerId, duration: duration, giftCode: nil))
                            strongSelf.push(controller)
                            return true
                        case .giftStars:
                            let controller = strongSelf.context.sharedContext.makeStarsGiftScreen(context: strongSelf.context, message: EngineMessage(message))
                            strongSelf.push(controller)
                            return true
                        case let .giftCode(slug, _, _, _, _, _, _, _, _):
                            strongSelf.openResolved(result: .premiumGiftCode(slug: slug), sourceMessageId: message.id, progress: params.progress)
                            return true
                        case .prizeStars:
                            let controller = strongSelf.context.sharedContext.makeStarsGiftScreen(context: strongSelf.context, message: EngineMessage(message))
                            strongSelf.push(controller)
                            return true
                        case let .suggestedProfilePhoto(image):
                            strongSelf.chatDisplayNode.dismissInput()
                            if let image = image {
                                if message.effectivelyIncoming(strongSelf.context.account.peerId) {
                                    if let emojiMarkup = image.emojiMarkup {
                                        let controller = AvatarEditorScreen(context: strongSelf.context, inputData: AvatarEditorScreen.inputData(context: strongSelf.context, isGroup: false), peerType: .user, markup: emojiMarkup)
                                        controller.imageCompletion = { [weak self] image, commit in
                                            if let strongSelf = self {
                                                if let rootController = strongSelf.effectiveNavigationController as? TelegramRootController, let settingsController = rootController.accountSettingsController as? PeerInfoScreenImpl {
                                                    settingsController.updateProfilePhoto(image, mode: .accept)
                                                    commit()
                                                }
                                            }
                                        }
                                        controller.videoCompletion = { [weak self] image, url, adjustments, commit in
                                            if let strongSelf = self {
                                                if let rootController = strongSelf.effectiveNavigationController as? TelegramRootController, let settingsController = rootController.accountSettingsController as? PeerInfoScreenImpl {
                                                    settingsController.updateProfileVideo(image, mode: .accept, asset: AVURLAsset(url: url), adjustments: adjustments)
                                                    commit()
                                                }
                                            }
                                        }
                                        strongSelf.push(controller)
                                    } else {
                                        var selectedNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?
                                        strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in
                                            if let itemNode = itemNode as? ChatMessageItemView {
                                                if let result = itemNode.transitionNode(id: message.id, media: image, adjustRect: false) {
                                                    selectedNode = result
                                                }
                                            }
                                        }
                                        let transitionView = selectedNode?.0.view
                                        
                                        let senderName: String?
                                        if let peer = message.peers[message.id.peerId] {
                                            senderName = EnginePeer(peer).compactDisplayTitle
                                        } else {
                                            senderName = nil
                                        }
                                        
                                        legacyAvatarEditor(context: strongSelf.context, media: .message(message: MessageReference(message), media: image), transitionView: transitionView, senderName: senderName, present: { [weak self] c, a in
                                            self?.present(c, in: .window(.root), with: a)
                                        }, imageCompletion: { [weak self] image in
                                            if let strongSelf = self {
                                                if let rootController = strongSelf.effectiveNavigationController as? TelegramRootController, let settingsController = rootController.accountSettingsController as? PeerInfoScreenImpl {
                                                    settingsController.updateProfilePhoto(image, mode: .accept)
                                                }
                                            }
                                        }, videoCompletion: { [weak self] image, url, adjustments in
                                            if let strongSelf = self {
                                                if let rootController = strongSelf.effectiveNavigationController as? TelegramRootController, let settingsController = rootController.accountSettingsController as? PeerInfoScreenImpl {
                                                    settingsController.updateProfileVideo(image, mode: .accept, asset: AVURLAsset(url: url), adjustments: adjustments)
                                                }
                                            }
                                        })
                                    }
                                } else {
                                    openMessageByAction = true
                                }
                            }
                        case .boostsApplied:
                            strongSelf.controllerInteraction?.openGroupBoostInfo(nil, 0)
                            return true
                        default:
                            break
                    }
                    if !openMessageByAction {
                        return true
                    }
                }
            }
            
            let openChatLocation = strongSelf.chatLocation
            var chatFilterTag: MemoryBuffer?
            if case let .customTag(value, _) = strongSelf.chatDisplayNode.historyNode.tag {
                chatFilterTag = value
            }
            
            var standalone = false
            if case .customChatContents = strongSelf.chatLocation {
                standalone = true
            }
            
            return context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, updatedPresentationData: strongSelf.updatedPresentationData, chatLocation: openChatLocation, chatFilterTag: chatFilterTag, chatLocationContextHolder: strongSelf.chatLocationContextHolder, message: message, mediaIndex: params.mediaIndex, standalone: standalone, reverseMessageGalleryOrder: false, mode: mode, navigationController: strongSelf.effectiveNavigationController, dismissInput: {
                self?.chatDisplayNode.dismissInput()
            }, present: { c, a, i in
                if case .current = i {
                    c.presentationArguments = a
                    c.statusBar.alphaUpdated = { [weak self] transition in
                        guard let self else {
                            return
                        }
                        self.updateStatusBarPresentation(animated: transition.isAnimated)
                    }
                    self?.galleryPresentationContext.present(c, on: PresentationSurfaceLevel(rawValue: 0), blockInteraction: true, completion: {})
                } else {
                    self?.present(c, in: .window(.root), with: a, blockInteraction: true)
                }
            }, transitionNode: { messageId, media, adjustRect in
                var selectedNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?
                if let strongSelf = self {
                    strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in
                        if let itemNode = itemNode as? ChatMessageItemView {
                            if let result = itemNode.transitionNode(id: messageId, media: media, adjustRect: adjustRect) {
                                selectedNode = result
                            }
                        }
                    }
                }
                return selectedNode
            }, addToTransitionSurface: { view in
                guard let strongSelf = self else {
                    return
                }
                strongSelf.chatDisplayNode.historyNode.view.superview?.insertSubview(view, aboveSubview: strongSelf.chatDisplayNode.historyNode.view)
            }, openUrl: { url in
                self?.openUrl(url, concealed: false, skipConcealedAlert: isLocation, message: nil)
            }, openPeer: { peer, navigation in
                self?.openPeer(peer: EnginePeer(peer), navigation: navigation, fromMessage: nil)
            }, callPeer: { peerId, isVideo in
                self?.controllerInteraction?.callPeer(peerId, isVideo)
            }, enqueueMessage: { message in
                self?.sendMessages([message])
            }, sendSticker: canSendMessagesToChat(strongSelf.presentationInterfaceState) ? { fileReference, sourceNode, sourceRect in
                return self?.controllerInteraction?.sendSticker(fileReference, false, false, nil, false, sourceNode, sourceRect, nil, []) ?? false
            } : nil, sendEmoji: canSendMessagesToChat(strongSelf.presentationInterfaceState) ? { text, attribute in
                self?.controllerInteraction?.sendEmoji(text, attribute, false)
            } : nil, setupTemporaryHiddenMedia: { signal, centralIndex, galleryMedia in
                if let strongSelf = self {
                    strongSelf.temporaryHiddenGalleryMediaDisposable.set((signal |> deliverOnMainQueue).startStrict(next: { entry in
                        if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction {
                            var messageIdAndMedia: [MessageId: [Media]] = [:]
                            
                            if let entry = entry as? InstantPageGalleryEntry, entry.index == centralIndex {
                                messageIdAndMedia[message.id] = [galleryMedia]
                            }
                            
                            controllerInteraction.hiddenMedia = messageIdAndMedia
                            
                            strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in
                                if let itemNode = itemNode as? ChatMessageItemView {
                                    itemNode.updateHiddenMedia()
                                }
                            }
                        }
                    }))
                }
            }, chatAvatarHiddenMedia: { signal, media in
                if let strongSelf = self {
                    strongSelf.temporaryHiddenGalleryMediaDisposable.set((signal |> deliverOnMainQueue).startStrict(next: { messageId in
                        if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction {
                            var messageIdAndMedia: [MessageId: [Media]] = [:]
                            
                            if let messageId = messageId {
                                messageIdAndMedia[messageId] = [media]
                            }
                            
                            controllerInteraction.hiddenMedia = messageIdAndMedia
                            
                            strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in
                                if let itemNode = itemNode as? ChatMessageItemView {
                                    itemNode.updateHiddenMedia()
                                }
                            }
                        }
                    }))
                }
            }, actionInteraction: GalleryControllerActionInteraction(
                openUrl: { [weak self] url, concealed in
                    if let strongSelf = self {
                        strongSelf.openUrl(url, concealed: concealed, message: nil)
                    }
                }, openUrlIn: { [weak self] url in
                    if let strongSelf = self {
                        strongSelf.openUrlIn(url)
                    }
                }, openPeerMention: { [weak self] mention in
                    if let strongSelf = self {
                        strongSelf.controllerInteraction?.openPeerMention(mention, nil)
                    }
                }, openPeer: { [weak self] peer in
                    if let strongSelf = self {
                        strongSelf.controllerInteraction?.openPeer(peer, .default, nil, .default)
                    }
                }, openHashtag: { [weak self] peerName, hashtag in
                    if let strongSelf = self {
                        strongSelf.controllerInteraction?.openHashtag(peerName, hashtag)
                    }
                }, openBotCommand: { [weak self] command in
                    if let strongSelf = self {
                        strongSelf.controllerInteraction?.sendBotCommand(nil, command)
                    }
                }, openAd: { [weak self] messageId in
                    if let strongSelf = self {
                        strongSelf.controllerInteraction?.activateAdAction(messageId, nil)
                    }
                }, addContact: { [weak self] phoneNumber in
                    if let strongSelf = self {
                        strongSelf.controllerInteraction?.addContact(phoneNumber)
                    }
                }, storeMediaPlaybackState: { [weak self] messageId, timestamp, playbackRate in
                    guard let strongSelf = self else {
                        return
                    }
                    var storedState: MediaPlaybackStoredState?
                    if let timestamp = timestamp {
                        storedState = MediaPlaybackStoredState(timestamp: timestamp, playbackRate: AudioPlaybackRate(playbackRate))
                    }
                    let _ = updateMediaPlaybackStoredStateInteractively(engine: strongSelf.context.engine, messageId: messageId, state: storedState).startStandalone()
                }, editMedia: { [weak self] messageId, snapshots, transitionCompletion in
                    guard let strongSelf = self else {
                        return
                    }
                    
                    let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: messageId))
                    |> deliverOnMainQueue).startStandalone(next: { [weak self] message in
                        guard let strongSelf = self, let message = message else {
                            return
                        }
                        
                        var mediaReference: AnyMediaReference?
                        for media in message.media {
                            if let image = media as? TelegramMediaImage {
                                mediaReference = AnyMediaReference.standalone(media: image)
                            } else if let file = media as? TelegramMediaFile {
                                mediaReference = AnyMediaReference.standalone(media: file)
                            } else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
                                if let image = content.image {
                                    mediaReference = AnyMediaReference.standalone(media: image)
                                } else if let file = content.file {
                                    mediaReference = AnyMediaReference.standalone(media: file)
                                }
                            }
                        }
                        
                        if let mediaReference = mediaReference, let peer = message.peers[message.id.peerId] {
                            legacyMediaEditor(context: strongSelf.context, peer: peer, threadTitle: strongSelf.threadInfo?.title, media: mediaReference, mode: .draw, initialCaption: NSAttributedString(), snapshots: snapshots, transitionCompletion: {
                                transitionCompletion()
                            }, getCaptionPanelView: { [weak self] in
                                return self?.getCaptionPanelView(isFile: false)
                            }, sendMessagesWithSignals: { [weak self] signals, _, _ in
                                if let strongSelf = self {
                                    strongSelf.enqueueMediaMessages(signals: signals, silentPosting: false)
                                }
                            }, present: { [weak self] c, a in
                                self?.present(c, in: .window(.root), with: a)
                            })
                        }
                    })
                }, updateCanReadHistory: { [weak self] canReadHistory in
                    self?.canReadHistory.set(canReadHistory)
                }),
                getSourceRect: { [weak self] in
                    guard let strongSelf = self else {
                        return nil
                    }
                    var rect: CGRect?
                    strongSelf.chatDisplayNode.historyNode.forEachVisibleMessageItemNode({ itemNode in
                        if itemNode.item?.message.id == message.id {
                            rect = itemNode.view.convert(itemNode.contentFrame(), to: nil)
                        }
                    })
                    return rect
                }
            ))
        }, sgStartMessageEdit: { [weak self] message in
            if let strongSelf = self {
                strongSelf.interfaceInteraction?.setupEditMessage(message.id, { _ in })
            }
        }, openPeer: { [weak self] peer, navigation, fromMessage, source in
            var expandAvatar = false
            if case let .groupParticipant(storyStats, avatarHeaderNode) = source {
                if let storyStats, storyStats.totalCount != 0, let avatarHeaderNode = avatarHeaderNode as? ChatMessageAvatarHeaderNodeImpl {
                    self?.openStories(peerId: peer.id, avatarHeaderNode: avatarHeaderNode, avatarNode: nil)
                    return
                } else {
                    expandAvatar = true
                }
            }
            var fromReactionMessageId: MessageId?
            if case .reaction = source {
                fromReactionMessageId = fromMessage?.id
            }
            self?.openPeer(peer: peer, navigation: navigation, fromMessage: fromMessage, fromReactionMessageId: fromReactionMessageId, expandAvatar: expandAvatar)
        }, openPeerMention: { [weak self] name, progress in
            self?.openPeerMention(name, progress: progress)
        }, openMessageContextMenu: { [weak self] message, selectAll, node, frame, anyRecognizer, location in
            guard let self, self.isNodeLoaded else {
                return
            }
            self.openMessageContextMenu(message: message, selectAll: selectAll, node: node, frame: frame, anyRecognizer: anyRecognizer, location: location)
        }, openMessageReactionContextMenu: { [weak self] message, sourceView, gesture, value in
            guard let self else {
                return
            }
            
            self.openMessageReactionContextMenu(message: message, sourceView: sourceView, gesture: gesture, value: value)
        }, updateMessageReaction: { [weak self] initialMessage, reaction, force, sourceView in
            guard let strongSelf = self else {
                return
            }
            guard let messages = strongSelf.chatDisplayNode.historyNode.messageGroupInCurrentHistoryView(initialMessage.id) else {
                return
            }
            guard let message = messages.first else {
                return
            }
            if case .default = reaction, strongSelf.chatLocation.peerId == strongSelf.context.account.peerId {
                return
            }
            if case let .customChatContents(customChatContents) = strongSelf.presentationInterfaceState.subject {
                if case let .hashTagSearch(publicPosts) = customChatContents.kind, publicPosts {
                    return
                }
            }
            
            if !force && message.areReactionsTags(accountPeerId: strongSelf.context.account.peerId) {
                if case .pinnedMessages = strongSelf.subject {
                    return
                }
                
                if !strongSelf.presentationInterfaceState.isPremium {
                    strongSelf.presentTagPremiumPaywall()
                    return
                }
                
                strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in
                    guard let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item else {
                        return
                    }
                    guard item.message.id == message.id else {
                        return
                    }
                    
                    let chosenReaction: MessageReaction.Reaction?
                    
                    switch reaction {
                    case .default:
                        switch item.associatedData.defaultReaction {
                        case .none:
                            chosenReaction = nil
                        case let .builtin(value):
                            chosenReaction = .builtin(value)
                        case let .custom(fileId):
                            chosenReaction = .custom(fileId)
                        case .stars:
                            chosenReaction = .stars
                        }
                    case let .reaction(value):
                        switch value {
                        case let .builtin(value):
                            chosenReaction = .builtin(value)
                        case let .custom(fileId):
                            chosenReaction = .custom(fileId)
                        case .stars:
                            chosenReaction = .stars
                        }
                    }
                    
                    guard let chosenReaction = chosenReaction else {
                        return
                    }
                    
                    let tag = ReactionsMessageAttribute.messageTag(reaction: chosenReaction)
                    if strongSelf.presentationInterfaceState.historyFilter?.customTag == tag {
                        if let sourceView {
                            strongSelf.openMessageReactionContextMenu(message: message, sourceView: sourceView, gesture: nil, value: chosenReaction)
                        }
                    } else {
                        strongSelf.chatDisplayNode.historyNode.frozenMessageForScrollingReset = message.id
                        strongSelf.interfaceInteraction?.updateHistoryFilter { _ in
                            return ChatPresentationInterfaceState.HistoryFilter(customTag: tag, isActive: true)
                        }
                    }
                }
                return
            }
            
            let _ = (peerMessageAllowedReactions(context: strongSelf.context, message: message)
            |> deliverOnMainQueue).startStandalone(next: { allowedReactions, _ in
                guard let strongSelf = self else {
                    return
                }
                
                strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in
                    guard let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item else {
                        return
                    }
                    guard item.message.id == message.id else {
                        return
                    }
                    
                    let chosenReaction: MessageReaction.Reaction?
                    
                    switch reaction {
                    case .default:
                        switch item.associatedData.defaultReaction {
                        case .none:
                            chosenReaction = nil
                        case let .builtin(value):
                            chosenReaction = .builtin(value)
                        case let .custom(fileId):
                            chosenReaction = .custom(fileId)
                        case .stars:
                            chosenReaction = .stars
                        }
                    case let .reaction(value):
                        switch value {
                        case let .builtin(value):
                            chosenReaction = .builtin(value)
                        case let .custom(fileId):
                            chosenReaction = .custom(fileId)
                        case .stars:
                            chosenReaction = .stars
                        }
                    }
                    
                    guard let chosenReaction = chosenReaction else {
                        return
                    }
                    
                    if case .stars = chosenReaction {
                        if strongSelf.selectPollOptionFeedback == nil {
                            strongSelf.selectPollOptionFeedback = HapticFeedback()
                        }
                        strongSelf.selectPollOptionFeedback?.tap()
                        
                        itemNode.awaitingAppliedReaction = (chosenReaction, { [weak itemNode] in
                            guard let strongSelf = self else {
                                return
                            }
                            if let itemNode = itemNode, let item = itemNode.item, let availableReactions = item.associatedData.availableReactions, let targetView = itemNode.targetReactionView(value: chosenReaction) {
                                var reactionItem: ReactionItem?
                                
                                switch chosenReaction {
                                case .builtin, .stars:
                                    for reaction in availableReactions.reactions {
                                        guard let centerAnimation = reaction.centerAnimation else {
                                            continue
                                        }
                                        guard let aroundAnimation = reaction.aroundAnimation else {
                                            continue
                                        }
                                        if reaction.value == chosenReaction {
                                            reactionItem = ReactionItem(
                                                reaction: ReactionItem.Reaction(rawValue: reaction.value),
                                                appearAnimation: reaction.appearAnimation,
                                                stillAnimation: reaction.selectAnimation,
                                                listAnimation: centerAnimation,
                                                largeListAnimation: reaction.activateAnimation,
                                                applicationAnimation: aroundAnimation,
                                                largeApplicationAnimation: reaction.effectAnimation,
                                                isCustom: false
                                            )
                                            break
                                        }
                                    }
                                case let .custom(fileId):
                                    if let itemFile = item.message.associatedMedia[MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile {
                                        reactionItem = ReactionItem(
                                            reaction: ReactionItem.Reaction(rawValue: chosenReaction),
                                            appearAnimation: itemFile,
                                            stillAnimation: itemFile,
                                            listAnimation: itemFile,
                                            largeListAnimation: itemFile,
                                            applicationAnimation: nil,
                                            largeApplicationAnimation: nil,
                                            isCustom: true
                                        )
                                    }
                                }
                                
                                if let reactionItem = reactionItem {
                                    let standaloneReactionAnimation = StandaloneReactionAnimation(genericReactionEffect: strongSelf.chatDisplayNode.historyNode.takeGenericReactionEffect())
                                    
                                    strongSelf.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
                                    
                                    strongSelf.chatDisplayNode.addSubnode(standaloneReactionAnimation)
                                    standaloneReactionAnimation.frame = strongSelf.chatDisplayNode.bounds
                                    standaloneReactionAnimation.animateReactionSelection(
                                        context: strongSelf.context,
                                        theme: strongSelf.presentationData.theme,
                                        animationCache: strongSelf.controllerInteraction!.presentationContext.animationCache,
                                        reaction: reactionItem,
                                        avatarPeers: [],
                                        playHaptic: false,
                                        isLarge: false,
                                        targetView: targetView,
                                        addStandaloneReactionAnimation: { standaloneReactionAnimation in
                                            guard let strongSelf = self else {
                                                return
                                            }
                                            strongSelf.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
                                            standaloneReactionAnimation.frame = strongSelf.chatDisplayNode.bounds
                                            strongSelf.chatDisplayNode.addSubnode(standaloneReactionAnimation)
                                        },
                                        onHit: { [weak itemNode] in
                                            guard let strongSelf = self else {
                                                return
                                            }
                                            if let itemNode = itemNode, let targetView = itemNode.targetReactionView(value: chosenReaction), strongSelf.context.sharedContext.energyUsageSettings.fullTranslucency {
                                                strongSelf.chatDisplayNode.wrappingNode.triggerRipple(at: targetView.convert(targetView.bounds.center, to: strongSelf.chatDisplayNode.view))
                                            }
                                        },
                                        completion: { [weak standaloneReactionAnimation] in
                                            standaloneReactionAnimation?.removeFromSupernode()
                                        }
                                    )
                                }
                            }
                        })
                        
                        guard let starsContext = strongSelf.context.starsContext else {
                            return
                        }
                        guard let peerId = strongSelf.chatLocation.peerId else {
                            return
                        }
                        let _ = (combineLatest(
                            starsContext.state,
                            strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.ReactionSettings(id: peerId))
                        )
                        |> take(1)
                        |> deliverOnMainQueue).start(next: { [weak strongSelf] state, reactionSettings in
                            guard let strongSelf, let balance = state?.balance else {
                                return
                            }
                            
                            if case let .known(reactionSettings) = reactionSettings, let starsAllowed = reactionSettings.starsAllowed, !starsAllowed {
                                if let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer {
                                    strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.Chat_ToastStarsReactionsDisabled(peer.debugDisplayTitle).string, actions: [
                                        TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {})
                                    ]), in: .window(.root))
                                }
                                return
                            }
                            
                            if balance < 1 {
                                let _ = (strongSelf.context.engine.payments.starsTopUpOptions()
                                |> take(1)
                                |> deliverOnMainQueue).startStandalone(next: { [weak strongSelf] options in
                                    guard let strongSelf, let peerId = strongSelf.chatLocation.peerId else {
                                        return
                                    }
                                    guard let starsContext = strongSelf.context.starsContext else {
                                        return
                                    }
                                    
                                    let purchaseScreen = strongSelf.context.sharedContext.makeStarsPurchaseScreen(context: strongSelf.context, starsContext: starsContext, options: options, purpose: .reactions(peerId: peerId, requiredStars: 1), completion: { result in
                                        let _ = result
                                        //TODO:release
                                    })
                                    strongSelf.push(purchaseScreen)
                                })
                                
                                return
                            }
                            
                            let _ = (strongSelf.context.engine.messages.sendStarsReaction(id: message.id, count: 1, isAnonymous: nil)
                            |> deliverOnMainQueue).startStandalone(next: { isAnonymous in
                                guard let strongSelf = self else {
                                    return
                                }
                                strongSelf.displayOrUpdateSendStarsUndo(messageId: message.id, count: 1, isAnonymous: isAnonymous)
                            })
                        })
                    } else {
                        var removedReaction: MessageReaction.Reaction?
                        var messageAlreadyHasThisReaction = false
                        
                        let currentReactions = mergedMessageReactions(attributes: message.attributes, isTags: message.areReactionsTags(accountPeerId: context.account.peerId))?.reactions ?? []
                        var updatedReactions: [MessageReaction.Reaction] = currentReactions.filter(\.isSelected).map(\.value)
                        
                        if let index = updatedReactions.firstIndex(where: { $0 == chosenReaction }) {
                            removedReaction = chosenReaction
                            updatedReactions.remove(at: index)
                        } else {
                            updatedReactions.append(chosenReaction)
                            messageAlreadyHasThisReaction = currentReactions.contains(where: { $0.value == chosenReaction })
                        }
                        
                        if removedReaction == nil {
                            if !canAddMessageReactions(message: message) {
                                itemNode.openMessageContextMenu()
                                return
                            }
                            
                            if strongSelf.context.sharedContext.immediateExperimentalUISettings.disableQuickReaction {
                                itemNode.openMessageContextMenu()
                                return
                            }
                            
                            guard let allowedReactions = allowedReactions else {
                                itemNode.openMessageContextMenu()
                                return
                            }
                            
                            switch allowedReactions {
                            case let .set(set):
                                if !messageAlreadyHasThisReaction && updatedReactions.contains(where: { !set.contains($0) }) {
                                    itemNode.openMessageContextMenu()
                                    return
                                }
                            case .all:
                                break
                            }
                        }
                        
                        if removedReaction == nil && !updatedReactions.isEmpty {
                            if strongSelf.selectPollOptionFeedback == nil {
                                strongSelf.selectPollOptionFeedback = HapticFeedback()
                            }
                            strongSelf.selectPollOptionFeedback?.tap()
                            
                            itemNode.awaitingAppliedReaction = (chosenReaction, { [weak itemNode] in
                                guard let strongSelf = self else {
                                    return
                                }
                                if let itemNode = itemNode, let item = itemNode.item, let availableReactions = item.associatedData.availableReactions, let targetView = itemNode.targetReactionView(value: chosenReaction) {
                                    var reactionItem: ReactionItem?
                                    
                                    switch chosenReaction {
                                    case .builtin, .stars:
                                        for reaction in availableReactions.reactions {
                                            guard let centerAnimation = reaction.centerAnimation else {
                                                continue
                                            }
                                            guard let aroundAnimation = reaction.aroundAnimation else {
                                                continue
                                            }
                                            if reaction.value == chosenReaction {
                                                reactionItem = ReactionItem(
                                                    reaction: ReactionItem.Reaction(rawValue: reaction.value),
                                                    appearAnimation: reaction.appearAnimation,
                                                    stillAnimation: reaction.selectAnimation,
                                                    listAnimation: centerAnimation,
                                                    largeListAnimation: reaction.activateAnimation,
                                                    applicationAnimation: aroundAnimation,
                                                    largeApplicationAnimation: reaction.effectAnimation,
                                                    isCustom: false
                                                )
                                                break
                                            }
                                        }
                                    case let .custom(fileId):
                                        if let itemFile = item.message.associatedMedia[MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile {
                                            reactionItem = ReactionItem(
                                                reaction: ReactionItem.Reaction(rawValue: chosenReaction),
                                                appearAnimation: itemFile,
                                                stillAnimation: itemFile,
                                                listAnimation: itemFile,
                                                largeListAnimation: itemFile,
                                                applicationAnimation: nil,
                                                largeApplicationAnimation: nil,
                                                isCustom: true
                                            )
                                        }
                                    }
                                    
                                    if let reactionItem = reactionItem {
                                        let standaloneReactionAnimation = StandaloneReactionAnimation(genericReactionEffect: strongSelf.chatDisplayNode.historyNode.takeGenericReactionEffect())
                                        
                                        strongSelf.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
                                        
                                        strongSelf.chatDisplayNode.addSubnode(standaloneReactionAnimation)
                                        standaloneReactionAnimation.frame = strongSelf.chatDisplayNode.bounds
                                        standaloneReactionAnimation.animateReactionSelection(
                                            context: strongSelf.context,
                                            theme: strongSelf.presentationData.theme,
                                            animationCache: strongSelf.controllerInteraction!.presentationContext.animationCache,
                                            reaction: reactionItem,
                                            avatarPeers: [],
                                            playHaptic: false,
                                            isLarge: false,
                                            targetView: targetView,
                                            addStandaloneReactionAnimation: { standaloneReactionAnimation in
                                                guard let strongSelf = self else {
                                                    return
                                                }
                                                strongSelf.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
                                                standaloneReactionAnimation.frame = strongSelf.chatDisplayNode.bounds
                                                strongSelf.chatDisplayNode.addSubnode(standaloneReactionAnimation)
                                            },
                                            completion: { [weak standaloneReactionAnimation] in
                                                standaloneReactionAnimation?.removeFromSupernode()
                                            }
                                        )
                                    }
                                }
                            })
                        } else {
                            strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts(itemNode: itemNode)
                            
                            if let removedReaction = removedReaction, let targetView = itemNode.targetReactionView(value: removedReaction), shouldDisplayInlineDateReactions(message: message, isPremium: strongSelf.presentationInterfaceState.isPremium, forceInline: false) {
                                var hideRemovedReaction: Bool = false
                                if let reactions = mergedMessageReactions(attributes: message.attributes, isTags: message.areReactionsTags(accountPeerId: context.account.peerId)) {
                                    for reaction in reactions.reactions {
                                        if reaction.value == removedReaction {
                                            hideRemovedReaction = reaction.count == 1
                                            break
                                        }
                                    }
                                }
                                
                                let standaloneDismissAnimation = StandaloneDismissReactionAnimation()
                                standaloneDismissAnimation.frame = strongSelf.chatDisplayNode.bounds
                                strongSelf.chatDisplayNode.addSubnode(standaloneDismissAnimation)
                                standaloneDismissAnimation.animateReactionDismiss(sourceView: targetView, hideNode: hideRemovedReaction, isIncoming: message.effectivelyIncoming(strongSelf.context.account.peerId), completion: { [weak standaloneDismissAnimation] in
                                    standaloneDismissAnimation?.removeFromSupernode()
                                })
                            }
                        }
                        
                        let mappedUpdatedReactions = updatedReactions.map { reaction -> UpdateMessageReaction in
                            switch reaction {
                            case let .builtin(value):
                                return .builtin(value)
                            case let .custom(fileId):
                                return .custom(fileId: fileId, file: nil)
                            case .stars:
                                return .stars
                            }
                        }
                        
                        if !strongSelf.presentationInterfaceState.isPremium && mappedUpdatedReactions.count > strongSelf.context.userLimits.maxReactionsPerMessage {
                            let _ = (ApplicationSpecificNotice.incrementMultipleReactionsSuggestion(accountManager: strongSelf.context.sharedContext.accountManager)
                                     |> deliverOnMainQueue).startStandalone(next: { [weak self] count in
                                guard let self else {
                                    return
                                }
                                if count < 1 {
                                    let context = self.context
                                    let controller = UndoOverlayController(
                                        presentationData: self.presentationData,
                                        content: .premiumPaywall(title: nil, text: self.presentationData.strings.Chat_Reactions_MultiplePremiumTooltip, customUndoText: nil, timeout: nil, linkAction: nil),
                                        elevatedLayout: false,
                                        action: { [weak self] action in
                                            if case .info = action {
                                                if let self {
                                                    let controller = context.sharedContext.makePremiumIntroController(context: context, source: .reactions, forceDark: false, dismissed: nil)
                                                    self.push(controller)
                                                }
                                            }
                                            return true
                                        }
                                    )
                                    self.present(controller, in: .current)
                                }
                            })
                        }
                        
                        let _ = updateMessageReactionsInteractively(account: strongSelf.context.account, messageIds: [message.id], reactions: mappedUpdatedReactions, isLarge: false, storeAsRecentlyUsed: false).startStandalone()
                    }
                }
            })
        }, activateMessagePinch: { [weak self] sourceNode in
            guard let strongSelf = self else {
                return
            }

            var sourceItemNode: ListViewItemNode?
            strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in
                guard let itemNode = itemNode as? ListViewItemNode else {
                    return
                }
                if sourceNode.view.isDescendant(of: itemNode.view) {
                    sourceItemNode = itemNode
                }
            }

            let isSecret = strongSelf.presentationInterfaceState.copyProtectionEnabled || strongSelf.chatLocation.peerId?.namespace == Namespaces.Peer.SecretChat
            let pinchController = PinchController(sourceNode: sourceNode, disableScreenshots: isSecret, getContentAreaInScreenSpace: {
                guard let strongSelf = self else {
                    return CGRect()
                }

                return strongSelf.chatDisplayNode.view.convert(strongSelf.chatDisplayNode.frameForVisibleArea(), to: nil)
            })
            strongSelf.currentPinchController = pinchController
            strongSelf.currentPinchSourceItemNode = sourceItemNode
            strongSelf.window?.presentInGlobalOverlay(pinchController)
        }, openMessageContextActions: { message, node, rect, gesture in
            gesture?.cancel()
        }, navigateToMessage: { [weak self] fromId, id, params in
            guard let self else {
                return
            }
            self.navigateToMessage(fromId: fromId, id: id, params: params)
        }, navigateToMessageStandalone: { [weak self] id in
            self?.navigateToMessage(from: nil, to: .id(id, NavigateToMessageParams(timestamp: nil, quote: nil)), forceInCurrentChat: false)
        }, navigateToThreadMessage: { [weak self] peerId, threadId, messageId in
            if let context = self?.context, let navigationController = self?.effectiveNavigationController {
                let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .always).startStandalone()
            }
        }, tapMessage: nil, clickThroughMessage: { [weak self] view, location in
            self?.chatDisplayNode.dismissInput(view: view, location: location)
        }, toggleMessagesSelection: { [weak self] ids, value in
            guard let strongSelf = self, strongSelf.isNodeLoaded else {
                return
            }
            
            if let subject = strongSelf.subject, case .messageOptions = subject, !value {
                let selectedCount = strongSelf.presentationInterfaceState.interfaceState.selectionState?.selectedIds.count ?? 0
                let updatedSelectedCount = selectedCount - ids.count
                if updatedSelectedCount < 1 {
                    return
                }
            }
            
            strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withToggledSelectedMessages(ids, value: value) } })
            if let selectionState = strongSelf.presentationInterfaceState.interfaceState.selectionState {
                let count = selectionState.selectedIds.count
                let text = strongSelf.presentationData.strings.VoiceOver_Chat_MessagesSelected(Int32(count))
                DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: {
                    UIAccessibility.post(notification: UIAccessibility.Notification.announcement, argument: text as NSString)
                })
            }
        }, sendCurrentMessage: { [weak self] silentPosting, messageEffect in
            if let strongSelf = self {
                if let _ = strongSelf.presentationInterfaceState.interfaceState.mediaDraftState {
                    strongSelf.sendMediaRecording(silentPosting: silentPosting, messageEffect: messageEffect)
                } else {
                    strongSelf.chatDisplayNode.sendCurrentMessage(silentPosting: silentPosting, messageEffect: messageEffect)
                }
            }
        }, sendMessage: { [weak self] text in
            guard let strongSelf = self, canSendMessagesToChat(strongSelf.presentationInterfaceState) else {
                return
            }
            
            var isScheduledMessages = false
            if case .scheduledMessages = strongSelf.presentationInterfaceState.subject {
                isScheduledMessages = true
            }
            
            guard !isScheduledMessages else {
                strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.ScheduledMessages_BotActionUnavailable, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
                return
            }
            strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
                if let strongSelf = self {
                    strongSelf.chatDisplayNode.collapseInput()
                    
                    strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
                        $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) }
                    })
                }
            }, nil)
            var attributes: [MessageAttribute] = []
            let entities = generateTextEntities(text, enabledTypes: .all)
            if !entities.isEmpty {
                attributes.append(TextEntitiesMessageAttribute(entities: entities))
            }
            
            let peerId = strongSelf.chatLocation.peerId
            if peerId?.namespace != Namespaces.Peer.SecretChat, let interactiveEmojis = strongSelf.chatDisplayNode.interactiveEmojis, interactiveEmojis.emojis.contains(text) {
                strongSelf.sendMessages([.message(text: "", attributes: [], inlineStickers: [:], mediaReference: AnyMediaReference.standalone(media: TelegramMediaDice(emoji: text)), threadId: strongSelf.chatLocation.threadId, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])])
            } else {
                strongSelf.sendMessages([.message(text: text, attributes: attributes, inlineStickers: [:], mediaReference: nil, threadId: strongSelf.chatLocation.threadId, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])])
            }
        }, sendSticker: { [weak self] fileReference, silentPosting, schedule, query, clearInput, sourceView, sourceRect, sourceLayer, bubbleUpEmojiOrStickersets in
            guard let strongSelf = self else {
                return false
            }
            
            if let _ = strongSelf.presentationInterfaceState.slowmodeState, strongSelf.presentationInterfaceState.subject != .scheduledMessages {
                strongSelf.interfaceInteraction?.displaySlowmodeTooltip(sourceView, sourceRect)
                return false
            }
            
            if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, peer.hasBannedPermission(.banSendStickers) != nil {
                if let boostsToUnrestrict = strongSelf.presentationInterfaceState.boostsToUnrestrict, boostsToUnrestrict > 0, (strongSelf.presentationInterfaceState.appliedBoosts ?? 0) < boostsToUnrestrict {
                    strongSelf.interfaceInteraction?.openBoostToUnrestrict()
                    return false
                }
            }
            
            var attributes: [MessageAttribute] = []
            if let query = query {
                attributes.append(EmojiSearchQueryMessageAttribute(query: query))
            }

            let correlationId = Int64.random(in: 0 ..< Int64.max)

            var replyPanel: ReplyAccessoryPanelNode?
            if let accessoryPanelNode = strongSelf.chatDisplayNode.accessoryPanelNode as? ReplyAccessoryPanelNode {
                replyPanel = accessoryPanelNode
            }

            var shouldAnimateMessageTransition = strongSelf.chatDisplayNode.shouldAnimateMessageTransition
            if let _ = sourceView.asyncdisplaykit_node as? ChatEmptyNodeStickerContentNode {
                shouldAnimateMessageTransition = true
            }

            strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
                if let strongSelf = self {
                    strongSelf.chatDisplayNode.collapseInput()
                    
                    strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { current in
                        var current = current
                        current = current.updatedInterfaceState { interfaceState in
                            var interfaceState = interfaceState
                            interfaceState = interfaceState.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil)
                            if clearInput {
                                interfaceState = interfaceState.withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString()))
                            }
                            return interfaceState
                        }.updatedInputMode { current in
                            if case let .media(mode, maybeExpanded, focused) = current, maybeExpanded != nil {
                                return .media(mode: mode, expanded: nil, focused: focused)
                            }
                            return current
                        }

                        return current
                    })
                }
            }, shouldAnimateMessageTransition ? correlationId : nil)

            if shouldAnimateMessageTransition {
                if let sourceNode = sourceView.asyncdisplaykit_node as? ChatMediaInputStickerGridItemNode {
                    strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .stickerMediaInput(input: .inputPanel(itemNode: sourceNode), replyPanel: replyPanel), initiated: {
                        guard let strongSelf = self else {
                            return
                        }
                        strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { current in
                            var current = current
                            current = current.updatedInputMode { current in
                                if case let .media(mode, maybeExpanded, focused) = current, maybeExpanded != nil {
                                    return .media(mode: mode, expanded: nil, focused: focused)
                                }
                                return current
                            }

                            return current
                        })
                    })
                } else if let sourceNode = sourceView.asyncdisplaykit_node as? HorizontalStickerGridItemNode {
                    strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .stickerMediaInput(input: .mediaPanel(itemNode: sourceNode), replyPanel: replyPanel), initiated: {})
                } else if let sourceNode = sourceView.asyncdisplaykit_node as? ChatEmptyNodeStickerContentNode {
                    strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .stickerMediaInput(input: .emptyPanel(itemNode: sourceNode), replyPanel: nil), initiated: {})
                } else if let sourceLayer = sourceLayer {
                    strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .stickerMediaInput(input: .universal(sourceContainerView: sourceView, sourceRect: sourceRect, sourceLayer: sourceLayer), replyPanel: replyPanel), initiated: {
                        guard let strongSelf = self else {
                            return
                        }
                        strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { current in
                            var current = current
                            current = current.updatedInputMode { current in
                                if case let .media(mode, maybeExpanded, focused) = current, maybeExpanded != nil {
                                    return .media(mode: mode, expanded: nil, focused: focused)
                                }
                                return current
                            }

                            return current
                        })
                    })
                }
            }
            
            let messages: [EnqueueMessage]  = [.message(text: "", attributes: attributes, inlineStickers: [:], mediaReference: fileReference.abstract, threadId: strongSelf.chatLocation.threadId, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: correlationId, bubbleUpEmojiOrStickersets: bubbleUpEmojiOrStickersets)]
            if silentPosting {
                let transformedMessages = strongSelf.transformEnqueueMessages(messages, silentPosting: silentPosting)
                strongSelf.sendMessages(transformedMessages)
            } else if schedule {
                strongSelf.presentScheduleTimePicker(completion: { [weak self] scheduleTime in
                    if let strongSelf = self {
                        let transformedMessages = strongSelf.transformEnqueueMessages(messages, silentPosting: false, scheduleTime: scheduleTime)
                        strongSelf.sendMessages(transformedMessages)
                    }
                })
            } else {
                let transformedMessages = strongSelf.transformEnqueueMessages(messages)
                strongSelf.sendMessages(transformedMessages)
            }
            return true
        }, sendEmoji: { [weak self] text, attribute, immediately in
            if let strongSelf = self {
                if immediately {
                    if let file = attribute.file {
                        var bubbleUpEmojiOrStickersets: [ItemCollectionId] = []
                        for attribute in file.attributes {
                            if case let .CustomEmoji(_, _, _, packReference) = attribute {
                                if case let .id(id, _) = packReference {
                                    bubbleUpEmojiOrStickersets.append(ItemCollectionId(namespace: Namespaces.ItemCollection.CloudEmojiPacks, id: id))
                                }
                            }
                        }
                        
                        strongSelf.sendMessages([.message(text: text, attributes: [TextEntitiesMessageAttribute(entities: [MessageTextEntity(range: 0 ..< (text as NSString).length, type: .CustomEmoji(stickerPack: nil, fileId: file.fileId.id))])], inlineStickers: [file.fileId : file], mediaReference: nil, threadId: strongSelf.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: bubbleUpEmojiOrStickersets)], commit: false)
                    }
                } else {
                    strongSelf.interfaceInteraction?.insertText(NSAttributedString(string: text, attributes: [ChatTextInputAttributes.customEmoji: attribute]))
                    strongSelf.updateChatPresentationInterfaceState(interactive: true, { state in
                        return state.updatedInputMode({ _ in
                            return .text
                        })
                    })
                    
                    let _ = (ApplicationSpecificNotice.getEmojiTooltip(accountManager: strongSelf.context.sharedContext.accountManager)
                    |> deliverOnMainQueue).startStandalone(next: { count in
                        guard let strongSelf = self else {
                            return
                        }
                        if count < 2 {
                            let _ = ApplicationSpecificNotice.incrementEmojiTooltip(accountManager: strongSelf.context.sharedContext.accountManager).startStandalone()
                            
                            Queue.mainQueue().after(0.5, {
                                strongSelf.displayEmojiTooltip()
                            })
                        }
                    })
                }
            }
        }, sendGif: { [weak self] fileReference, sourceView, sourceRect, silentPosting, schedule in
            if let strongSelf = self {
                if let _ = strongSelf.presentationInterfaceState.slowmodeState, strongSelf.presentationInterfaceState.subject != .scheduledMessages {
                    strongSelf.interfaceInteraction?.displaySlowmodeTooltip(sourceView, sourceRect)
                    return false
                }
                
                if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, peer.hasBannedPermission(.banSendGifs) != nil {
                    if let boostsToUnrestrict = strongSelf.presentationInterfaceState.boostsToUnrestrict, boostsToUnrestrict > 0, (strongSelf.presentationInterfaceState.appliedBoosts ?? 0) < boostsToUnrestrict {
                        strongSelf.interfaceInteraction?.openBoostToUnrestrict()
                        return false
                    }
                }
                
                strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
                    if let strongSelf = self {
                        strongSelf.chatDisplayNode.collapseInput()
                        
                        strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
                            $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) }.updatedInputMode { current in
                                if case let .media(mode, maybeExpanded, focused) = current, maybeExpanded != nil  {
                                    return .media(mode: mode, expanded: nil, focused: focused)
                                }
                                return current
                            }
                        })
                    }
                }, nil)
                
                var messages = [EnqueueMessage.message(text: "", attributes: [], inlineStickers: [:], mediaReference: fileReference.abstract, threadId: strongSelf.chatLocation.threadId, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]
                if silentPosting {
                    messages = strongSelf.transformEnqueueMessages(messages, silentPosting: true)
                    strongSelf.sendMessages(messages)
                } else if schedule {
                    strongSelf.presentScheduleTimePicker(completion: { [weak self] scheduleTime in
                        if let strongSelf = self {
                            let transformedMessages = strongSelf.transformEnqueueMessages(messages, silentPosting: false, scheduleTime: scheduleTime)
                            strongSelf.sendMessages(transformedMessages)
                        }
                    })
                } else {
                    messages = strongSelf.transformEnqueueMessages(messages)
                    strongSelf.sendMessages(messages)
                }
            }
            return true
        }, sendBotContextResultAsGif: { [weak self] collection, result, sourceView, sourceRect, silentPosting, resetTextInputState in
            guard let strongSelf = self else {
                return false
            }
            if case .pinnedMessages = strongSelf.presentationInterfaceState.subject {
                return false
            }
            if let _ = strongSelf.presentationInterfaceState.slowmodeState, strongSelf.presentationInterfaceState.subject != .scheduledMessages {
                strongSelf.interfaceInteraction?.displaySlowmodeTooltip(sourceView, sourceRect)
                return false
            }
            
            if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, peer.hasBannedPermission(.banSendGifs) != nil {
                if let boostsToUnrestrict = strongSelf.presentationInterfaceState.boostsToUnrestrict, boostsToUnrestrict > 0, (strongSelf.presentationInterfaceState.appliedBoosts ?? 0) < boostsToUnrestrict {
                    strongSelf.interfaceInteraction?.openBoostToUnrestrict()
                    return false
                }
            }
            
            strongSelf.enqueueChatContextResult(collection, result, hideVia: true, closeMediaInput: true, silentPosting: silentPosting, resetTextInputState: resetTextInputState)
            
            return true
        }, requestMessageActionCallback: { [weak self] messageId, data, isGame, requiresPassword in
            guard let strongSelf = self else {
                return
            }
            guard strongSelf.presentationInterfaceState.subject != .scheduledMessages else {
                strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.ScheduledMessages_BotActionUnavailable, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
                return
            }
            
            let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: messageId))
            |> deliverOnMainQueue).startStandalone(next: { message in
                guard let strongSelf = self, let message = message else {
                    return
                }
                
                strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
                    return $0.updatedTitlePanelContext {
                        if !$0.contains(where: {
                            switch $0 {
                                case .requestInProgress:
                                    return true
                                default:
                                    return false
                            }
                        }) {
                            var updatedContexts = $0
                            updatedContexts.append(.requestInProgress)
                            return updatedContexts.sorted()
                        }
                        return $0
                    }
                })
                
                let proceedWithResult: (MessageActionCallbackResult) -> Void = { [weak self] result in
                    guard let strongSelf = self else {
                        return
                    }
                    
                    switch result {
                        case .none:
                            break
                        case let .alert(text):
                            strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
                        case let .toast(text):
                            let message: Signal<String?, NoError> = .single(text)
                            let noMessage: Signal<String?, NoError> = .single(nil)
                            let delayedNoMessage: Signal<String?, NoError> = noMessage |> delay(1.0, queue: Queue.mainQueue())
                            strongSelf.botCallbackAlertMessage.set(message |> then(delayedNoMessage))
                        case let .url(url):
                            if isGame {
                                let openBot: () -> Void = {
                                    guard let strongSelf = self else {
                                        return
                                    }
                                    
                                    strongSelf.chatDisplayNode.dismissInput()
                                    strongSelf.effectiveNavigationController?.pushViewController(GameController(context: strongSelf.context, url: url, message: message))
                                }

                                var botPeer: TelegramUser?
                                for attribute in message.attributes {
                                    if let attribute = attribute as? InlineBotMessageAttribute {
                                        if let peerId = attribute.peerId {
                                            botPeer = message.peers[peerId] as? TelegramUser
                                        }
                                    }
                                }
                                if botPeer == nil {
                                    if case let .user(peer) = message.author, peer.botInfo != nil {
                                        botPeer = peer
                                    } else if let peer = message.peers[message.id.peerId] as? TelegramUser, peer.botInfo != nil {
                                        botPeer = peer
                                    }
                                }
                                
                                if let botPeer = botPeer {
                                    let _ = (ApplicationSpecificNotice.getBotGameNotice(accountManager: strongSelf.context.sharedContext.accountManager, peerId: botPeer.id)
                                    |> deliverOnMainQueue).startStandalone(next: { value in
                                        guard let strongSelf = self else {
                                            return
                                        }

                                        if value {
                                            openBot()
                                        } else {
                                            strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Conversation_BotInteractiveUrlAlert(EnginePeer(botPeer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { }), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
                                                if let strongSelf = self {
                                                    let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: strongSelf.context.sharedContext.accountManager, peerId: botPeer.id).startStandalone()
                                                    openBot()
                                                }
                                            })]), in: .window(.root), with: nil)
                                        }
                                    })
                                }
                            } else {
                                strongSelf.openUrl(url, concealed: false)
                            }
                    }
                }
                
                let updateProgress = { [weak self] in
                    Queue.mainQueue().async {
                        if let strongSelf = self {
                            strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
                                return $0.updatedTitlePanelContext {
                                    if let index = $0.firstIndex(where: {
                                        switch $0 {
                                            case .requestInProgress:
                                                return true
                                            default:
                                                return false
                                        }
                                    }) {
                                        var updatedContexts = $0
                                        updatedContexts.remove(at: index)
                                        return updatedContexts
                                    }
                                    return $0
                                }
                            })
                        }
                    }
                }
                
                let context = strongSelf.context
                if requiresPassword {
                    strongSelf.messageActionCallbackDisposable.set(((strongSelf.context.engine.messages.requestMessageActionCallbackPasswordCheck(messageId: messageId, isGame: isGame, data: data)
                    |> afterDisposed {
                        updateProgress()
                    })
                    |> deliverOnMainQueue).startStrict(error: { error in
                        let controller = ownershipTransferController(context: context, updatedPresentationData: strongSelf.updatedPresentationData, initialError: error, present: { c, a in
                            strongSelf.present(c, in: .window(.root), with: a)
                        }, commit: { password in
                            return context.engine.messages.requestMessageActionCallback(messageId: messageId, isGame: isGame, password: password, data: data)
                            |> afterDisposed {
                                updateProgress()
                            }
                        }, completion: { result in
                            proceedWithResult(result)
                        })
                        strongSelf.present(controller, in: .window(.root))
                    }))
                } else {
                    strongSelf.messageActionCallbackDisposable.set(((context.engine.messages.requestMessageActionCallback(messageId: messageId, isGame: isGame, password: nil, data: data)
                    |> afterDisposed {
                        updateProgress()
                    })
                    |> deliverOnMainQueue).startStrict(next: { result in
                        proceedWithResult(result)
                    }))
                }
            })
        }, requestMessageActionUrlAuth: { [weak self] defaultUrl, subject in
            if let strongSelf = self {
                guard strongSelf.presentationInterfaceState.subject != .scheduledMessages else {
                    strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.ScheduledMessages_BotActionUnavailable, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
                    return
                }
                strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
                    return $0.updatedTitlePanelContext {
                        if !$0.contains(where: {
                            switch $0 {
                                case .requestInProgress:
                                    return true
                                default:
                                    return false
                            }
                        }) {
                            var updatedContexts = $0
                            updatedContexts.append(.requestInProgress)
                            return updatedContexts.sorted()
                        }
                        return $0
                    }
                })
                strongSelf.messageActionUrlAuthDisposable.set(((combineLatest(strongSelf.context.account.postbox.loadedPeerWithId(strongSelf.context.account.peerId), strongSelf.context.engine.messages.requestMessageActionUrlAuth(subject: subject) |> afterDisposed {
                    Queue.mainQueue().async {
                        if let strongSelf = self {
                            strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
                                return $0.updatedTitlePanelContext {
                                    if let index = $0.firstIndex(where: {
                                        switch $0 {
                                            case .requestInProgress:
                                                return true
                                            default:
                                                return false
                                        }
                                    }) {
                                        var updatedContexts = $0
                                        updatedContexts.remove(at: index)
                                        return updatedContexts
                                    }
                                    return $0
                                }
                            })
                        }
                    }
                })) |> deliverOnMainQueue).startStrict(next: { peer, result in
                    if let strongSelf = self {
                        switch result {
                            case .default:
                                strongSelf.openUrl(defaultUrl, concealed: false, skipUrlAuth: true)
                            case let .request(domain, bot, requestWriteAccess):
                                let controller = chatMessageActionUrlAuthController(context: strongSelf.context, defaultUrl: defaultUrl, domain: domain, bot: bot, requestWriteAccess: requestWriteAccess, displayName: EnginePeer(peer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), open: { [weak self] authorize, allowWriteAccess in
                                    if let strongSelf = self {
                                        if authorize {
                                            strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
                                                return $0.updatedTitlePanelContext {
                                                    if !$0.contains(where: {
                                                        switch $0 {
                                                            case .requestInProgress:
                                                                return true
                                                            default:
                                                                return false
                                                        }
                                                    }) {
                                                        var updatedContexts = $0
                                                        updatedContexts.append(.requestInProgress)
                                                        return updatedContexts.sorted()
                                                    }
                                                    return $0
                                                }
                                            })
                                            
                                            strongSelf.messageActionUrlAuthDisposable.set(((strongSelf.context.engine.messages.acceptMessageActionUrlAuth(subject: subject, allowWriteAccess: allowWriteAccess) |> afterDisposed {
                                                Queue.mainQueue().async {
                                                    if let strongSelf = self {
                                                        strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
                                                            return $0.updatedTitlePanelContext {
                                                                if let index = $0.firstIndex(where: {
                                                                    switch $0 {
                                                                        case .requestInProgress:
                                                                            return true
                                                                        default:
                                                                            return false
                                                                    }
                                                                }) {
                                                                    var updatedContexts = $0
                                                                    updatedContexts.remove(at: index)
                                                                    return updatedContexts
                                                                }
                                                                return $0
                                                            }
                                                        })
                                                    }
                                                }
                                            }) |> deliverOnMainQueue).startStrict(next: { [weak self] result in
                                                if let strongSelf = self {
                                                    switch result {
                                                        case let .accepted(url):
                                                            strongSelf.openUrl(url, concealed: false, skipUrlAuth: true)
                                                        default:
                                                            strongSelf.openUrl(defaultUrl, concealed: false, skipUrlAuth: true)
                                                    }
                                                }
                                            }))
                                        } else {
                                            strongSelf.openUrl(defaultUrl, concealed: false, skipUrlAuth: true)
                                        }
                                    }
                                })
                                strongSelf.chatDisplayNode.dismissInput()
                                strongSelf.present(controller, in: .window(.root))
                            case let .accepted(url):
                                strongSelf.openUrl(url, concealed: false, forceExternal: true, skipUrlAuth: true)
                        }
                    }
                }))
            }
        }, activateSwitchInline: { [weak self] peerId, inputString, peerTypes in
            guard let strongSelf = self else {
                return
            }
            guard strongSelf.presentationInterfaceState.subject != .scheduledMessages else {
                strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.ScheduledMessages_BotActionUnavailable, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
                return
            }
            if let botStart = strongSelf.botStart, case let .automatic(returnToPeerId, scheduled) = botStart.behavior {
                let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: returnToPeerId))
                |> deliverOnMainQueue).startStandalone(next: { peer in
                    if let strongSelf = self, let peer = peer {
                        strongSelf.openPeer(peer: peer, navigation: .chat(textInputState: ChatTextInputState(inputText: NSAttributedString(string: inputString)), subject: scheduled ? .scheduledMessages : nil, peekData: nil), fromMessage: nil)
                    }
                })
            } else {
                if let peerId = peerId {
                    let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
                    |> deliverOnMainQueue).startStandalone(next: { peer in
                        if let strongSelf = self, let peer = peer {
                            strongSelf.openPeer(peer: peer, navigation: .chat(textInputState: ChatTextInputState(inputText: NSAttributedString(string: inputString)), subject: nil, peekData: nil), fromMessage: nil)
                        }
                    })
                } else {
                    strongSelf.openPeer(peer: nil, navigation: .chat(textInputState: ChatTextInputState(inputText: NSAttributedString(string: inputString)), subject: nil, peekData: nil), fromMessage: nil, peerTypes: peerTypes)
                }
            }
        }, openUrl: { [weak self] urlData in
            guard let strongSelf = self else {
                return
            }
            let url = urlData.url
            let concealed = urlData.concealed
            let message = urlData.message
            let progress = urlData.progress
            let forceExternal = urlData.external ?? false
            
            var skipConcealedAlert = false
            if let author = message?.author, author.isVerified {
                skipConcealedAlert = true
            }
            
            if let message, let adAttribute = message.attributes.first(where: { $0 is AdMessageAttribute }) as? AdMessageAttribute {
                strongSelf.chatDisplayNode.historyNode.adMessagesContext?.markAction(opaqueId: adAttribute.opaqueId)
            }
            
            if let performOpenURL = strongSelf.performOpenURL {
                performOpenURL(message, url, progress)
            } else {
                strongSelf.openUrl(url, concealed: concealed, forceExternal: forceExternal, skipConcealedAlert: skipConcealedAlert, message: message, allowInlineWebpageResolution: urlData.allowInlineWebpageResolution, progress: progress)
            }
        }, shareCurrentLocation: { [weak self] in
            if let strongSelf = self {
                if case .pinnedMessages = strongSelf.presentationInterfaceState.subject {
                    return
                }
                guard strongSelf.presentationInterfaceState.subject != .scheduledMessages else {
                    strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.ScheduledMessages_BotActionUnavailable, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
                    return
                }
                strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: strongSelf.presentationData.strings.Conversation_ShareBotLocationConfirmationTitle, text: strongSelf.presentationData.strings.Conversation_ShareBotLocationConfirmation, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
                    if let strongSelf = self, let locationManager = strongSelf.context.sharedContext.locationManager {
                        let _ = (currentLocationManagerCoordinate(manager: locationManager, timeout: 5.0)
                        |> deliverOnMainQueue).startStandalone(next: { coordinate in
                            if let strongSelf = self {
                                if let coordinate = coordinate {
                                    strongSelf.sendMessages([.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaMap(latitude: coordinate.latitude, longitude: coordinate.longitude, heading: nil, accuracyRadius: nil, venue: nil, liveBroadcastingTimeout: nil, liveProximityNotificationRadius: nil)), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])])
                                } else {
                                    strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {})]), in: .window(.root))
                                }
                            }
                        })
                    }
                })]), in: .window(.root))
            }
        }, shareAccountContact: { [weak self] in
            if let strongSelf = self {
                if case .pinnedMessages = strongSelf.presentationInterfaceState.subject {
                    return
                }
                
                guard strongSelf.presentationInterfaceState.subject != .scheduledMessages else {
                    strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.ScheduledMessages_BotActionUnavailable, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
                    return
                }
                strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: strongSelf.presentationData.strings.Conversation_ShareBotContactConfirmationTitle, text: strongSelf.presentationData.strings.Conversation_ShareBotContactConfirmation, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
                    if let strongSelf = self {
                        let _ = (strongSelf.context.account.postbox.loadedPeerWithId(strongSelf.context.account.peerId)
                        |> deliverOnMainQueue).startStandalone(next: { peer in
                            if let peer = peer as? TelegramUser, let phone = peer.phone, !phone.isEmpty {
                                strongSelf.sendMessages([.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaContact(firstName: peer.firstName ?? "", lastName: peer.lastName ?? "", phoneNumber: phone, peerId: peer.id, vCardData: nil)), threadId: strongSelf.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])])
                            }
                        })
                    }
                })]), in: .window(.root))
            }
        }, sendBotCommand: { [weak self] messageId, command in
            if let strongSelf = self, canSendMessagesToChat(strongSelf.presentationInterfaceState) {
                strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({}, nil)
                var postAsReply = false
                if !command.contains("@") {
                    switch strongSelf.chatLocation {
                        case let .peer(peerId):
                            if (peerId.namespace == Namespaces.Peer.CloudChannel || peerId.namespace == Namespaces.Peer.CloudGroup) {
                                postAsReply = true
                            }
                        case .replyThread:
                            postAsReply = true
                        case .customChatContents:
                            postAsReply = true
                    }
                    
                    if let messageId = messageId, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) {
                        if let author = message.author as? TelegramUser, author.botInfo != nil {
                        } else {
                            postAsReply = false
                        }
                    }
                }
                
                strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
                    if let strongSelf = self {
                        strongSelf.chatDisplayNode.collapseInput()
                        
                        strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
                            $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))).withUpdatedComposeDisableUrlPreviews([]) }
                        })
                    }
                }, nil)
                var attributes: [MessageAttribute] = []
                let entities = generateTextEntities(command, enabledTypes: .all)
                if !entities.isEmpty {
                    attributes.append(TextEntitiesMessageAttribute(entities: entities))
                }
                var replyToMessageId: EngineMessageReplySubject?
                if postAsReply, let messageId {
                    replyToMessageId = EngineMessageReplySubject(messageId: messageId, quote: nil)
                }
                strongSelf.sendMessages([.message(text: command, attributes: attributes, inlineStickers: [:], mediaReference: nil, threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyToMessageId, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])])
            }
        }, openInstantPage: { [weak self] message, associatedData in
            if let strongSelf = self, strongSelf.isNodeLoaded, let navigationController = strongSelf.effectiveNavigationController, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(message.id) {
                let _ = strongSelf.presentVoiceMessageDiscardAlert(action: {
                    strongSelf.chatDisplayNode.dismissInput()
                    if let controller = strongSelf.context.sharedContext.makeInstantPageController(context: strongSelf.context, message: message, sourcePeerType: associatedData?.automaticDownloadPeerType) {
                        navigationController.pushViewController(controller)
                    }
                    if case .overlay = strongSelf.presentationInterfaceState.mode {
                        strongSelf.chatDisplayNode.dismissAsOverlay()
                    }
                })
            }
        }, openWallpaper: { [weak self] message in
            if let strongSelf = self, strongSelf.isNodeLoaded, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(message.id) {
                let _ = strongSelf.presentVoiceMessageDiscardAlert(action: {
                    strongSelf.chatDisplayNode.dismissInput()
                    strongSelf.context.sharedContext.openChatWallpaper(context: strongSelf.context, message: message, present: { [weak self] c, a in
                        self?.push(c)
                    })
                })
            }
        }, openTheme: { [weak self] message in
            if let strongSelf = self, strongSelf.isNodeLoaded, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(message.id) {
                let _ = strongSelf.presentVoiceMessageDiscardAlert(action: {
                    strongSelf.chatDisplayNode.dismissInput()
                    openChatTheme(context: strongSelf.context, message: message, pushController: { [weak self] c in
                        self?.effectiveNavigationController?.pushViewController(c)
                    }, present: { [weak self] c, a in
                        self?.present(c, in: .window(.root), with: a, blockInteraction: true)
                    })
                })
            }
        }, openHashtag: { [weak self] peerName, hashtag in
            guard let strongSelf = self else {
                return
            }
            strongSelf.openHashtag(hashtag, peerName: peerName)
        }, updateInputState: { [weak self] f in
            if let strongSelf = self {
                strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
                    return $0.updatedInterfaceState {
                        let updatedState: ChatTextInputState
                        if canSendMessagesToChat(strongSelf.presentationInterfaceState) {
                            updatedState = f($0.effectiveInputState)
                        } else {
                            updatedState = ChatTextInputState()
                        }
                        return $0.withUpdatedEffectiveInputState(updatedState)
                    }
                })
            }
        }, updateInputMode: { [weak self] f in
            self?.updateChatPresentationInterfaceState(animated: true, interactive: true, {
                return $0.updatedInputMode(f)
            })
        }, openMessageShareMenu: { [weak self] id in
            guard let self else {
                return
            }
            self.openMessageShareMenu(id: id)
        }, presentController: { [weak self] controller, arguments in
            self?.present(controller, in: .window(.root), with: arguments)
        }, presentControllerInCurrent: { [weak self] controller, arguments in
            if controller is UndoOverlayController {
                self?.dismissAllTooltips()
            }
            self?.present(controller, in: .current, with: arguments)
        }, navigationController: { [weak self] in
            return self?.navigationController as? NavigationController
        }, chatControllerNode: { [weak self] in
            return self?.chatDisplayNode
        }, presentGlobalOverlayController: { [weak self] controller, arguments in
            self?.presentInGlobalOverlay(controller, with: arguments)
        }, callPeer: { [weak self] peerId, isVideo in
            if let strongSelf = self {
                let _ = strongSelf.presentVoiceMessageDiscardAlert(action: {
                    strongSelf.commitPurposefulAction()
                    
                    let _ = (context.account.viewTracker.peerView(peerId)
                    |> take(1)
                    |> map { view -> Peer? in
                        return peerViewMainPeer(view)
                    }
                    |> deliverOnMainQueue).startStandalone(next: { peer in
                        guard let peer = peer else {
                            return
                        }
                        
                        if let cachedUserData = strongSelf.peerView?.cachedData as? CachedUserData, cachedUserData.callsPrivate {
                            let presentationData = context.sharedContext.currentPresentationData.with { $0 }
                            
                            strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: presentationData.strings.Call_ConnectionErrorTitle, text: presentationData.strings.Call_PrivacyErrorMessage(EnginePeer(peer).compactDisplayTitle).string, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
                            return
                        }
                        
                        context.requestCall(peerId: peer.id, isVideo: isVideo, completion: {})
                    })
                })
            }
        }, longTap: { [weak self] action, params in
            if let self {
                self.openLinkLongTap(action, params: params)
            }
        }, openCheckoutOrReceipt: { [weak self] messageId, params in
            guard let strongSelf = self else {
                return
            }
            strongSelf.commitPurposefulAction()
            
            var isScheduledMessages = false
            if case .scheduledMessages = strongSelf.presentationInterfaceState.subject {
                isScheduledMessages = true
            }
            
            guard !isScheduledMessages else {
                strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.ScheduledMessages_BotActionUnavailable, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
                return
            }
            
            let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: messageId))
            |> deliverOnMainQueue).startStandalone(next: { message in
                guard let strongSelf = self, let message else {
                    return
                }
                
                for media in message.media {
                    if let paidContent = media as? TelegramMediaPaidContent {
                        let progressSignal = Signal<Never, NoError> { _ in
                            params?.progress?.set(.single(true))
                            return ActionDisposable {
                                params?.progress?.set(.single(false))
                            }
                        }
                        |> runOn(Queue.mainQueue())
                        |> delay(0.25, queue: Queue.mainQueue())
                        let progressDisposable = progressSignal.startStrict()
                        
                        strongSelf.chatDisplayNode.dismissInput()
                        let inputData = Promise<BotCheckoutController.InputData?>()
                        inputData.set(BotCheckoutController.InputData.fetch(context: strongSelf.context, source: .message(message.id))
                        |> map(Optional.init)
                        |> `catch` { _ -> Signal<BotCheckoutController.InputData?, NoError> in
                            return .single(nil)
                        })
                        if let starsContext = strongSelf.context.starsContext {
                            let starsInputData = combineLatest(
                                inputData.get(),
                                starsContext.state
                            )
                            |> map { data, state -> (StarsContext.State, BotPaymentForm, EnginePeer?, EnginePeer?)? in
                                if let data, let state {
                                    return (state, data.form, data.botPeer, message.forwardInfo?.sourceMessageId == nil ? message.author : nil)
                                } else {
                                    return nil
                                }
                            }
                            let _ = (starsInputData |> filter { $0 != nil } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in
                                guard let strongSelf = self, let extendedMedia = paidContent.extendedMedia.first, case let .preview(dimensions, immediateThumbnailData, _) = extendedMedia else {
                                    return
                                }
                                var messageId = messageId
                                if let sourceMessageId = message.forwardInfo?.sourceMessageId {
                                    messageId = sourceMessageId
                                }
                                let invoice = TelegramMediaInvoice(title: "", description: "", photo: nil, receiptMessageId: nil, currency: "XTR", totalAmount: paidContent.amount, startParam: "", extendedMedia: .preview(dimensions: dimensions, immediateThumbnailData: immediateThumbnailData, videoDuration: nil), flags: [], version: 0)
                                let controller = strongSelf.context.sharedContext.makeStarsTransferScreen(context: strongSelf.context, starsContext: starsContext, invoice: invoice, source: .message(messageId), extendedMedia: paidContent.extendedMedia, inputData: starsInputData, completion: { _ in })
                                strongSelf.push(controller)
                                
                                progressDisposable.dispose()
                            })
                        }
                    } else if let invoice = media as? TelegramMediaInvoice {
                        strongSelf.chatDisplayNode.dismissInput()
                        if let receiptMessageId = invoice.receiptMessageId {
                            if invoice.currency == "XTR" {
                                let _ = (strongSelf.context.engine.payments.requestBotPaymentReceipt(messageId: receiptMessageId)
                                |> deliverOnMainQueue).start(next: { [weak self] receipt in
                                    guard let strongSelf = self else {
                                        return
                                    }
                                    strongSelf.push(strongSelf.context.sharedContext.makeStarsReceiptScreen(context: strongSelf.context, receipt: receipt))
                                })
                            } else {
                                strongSelf.present(BotReceiptController(context: strongSelf.context, messageId: receiptMessageId), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
                            }
                        } else {
                            let inputData = Promise<BotCheckoutController.InputData?>()
                            inputData.set(BotCheckoutController.InputData.fetch(context: strongSelf.context, source: .message(message.id))
                            |> map(Optional.init)
                            |> `catch` { _ -> Signal<BotCheckoutController.InputData?, NoError> in
                                return .single(nil)
                            })
                            if invoice.currency == "XTR", let starsContext = strongSelf.context.starsContext {
                                let starsInputData = combineLatest(
                                    inputData.get(),
                                    starsContext.state
                                )
                                |> map { data, state -> (StarsContext.State, BotPaymentForm, EnginePeer?, EnginePeer?)? in
                                    if let data, let state {
                                        return (state, data.form, data.botPeer, nil)
                                    } else {
                                        return nil
                                    }
                                }
                                let _ = (starsInputData |> filter { $0 != nil } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in
                                    guard let strongSelf = self else {
                                        return
                                    }
                                    let controller = strongSelf.context.sharedContext.makeStarsTransferScreen(context: strongSelf.context, starsContext: starsContext, invoice: invoice, source: .message(messageId), extendedMedia: [], inputData: starsInputData, completion: { _ in })
                                    strongSelf.push(controller)
                                })
                            } else {
                                strongSelf.present(BotCheckoutController(context: strongSelf.context, invoice: invoice, source: .message(messageId), inputData: inputData, completed: { currencyValue, receiptMessageId in
                                    guard let strongSelf = self else {
                                        return
                                    }
                                    strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .paymentSent(currencyValue: currencyValue, itemTitle: invoice.title), elevatedLayout: false, action: { action in
                                        guard let strongSelf = self, let receiptMessageId = receiptMessageId else {
                                            return false
                                        }
                                        
                                        if case .info = action {
                                            strongSelf.present(BotReceiptController(context: strongSelf.context, messageId: receiptMessageId), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
                                            return true
                                        }
                                        return false
                                    }), in: .current)
                                }), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
                            }
                        }
                    }
                }
            })
        }, openSearch: {
        }, setupReply: { [weak self] messageId in
            self?.interfaceInteraction?.setupReplyMessage(messageId, { _, f in f() })
        }, canSetupReply: { [weak self] message in
            if message.adAttribute != nil {
                return .none
            }
            if !message.flags.contains(.Incoming) {
                if !message.flags.intersection([.Failed, .Sending, .Unsent]).isEmpty {
                    return .none
                }
            }
            if let strongSelf = self {
                if case let .replyThread(replyThreadMessage) = strongSelf.chatLocation, replyThreadMessage.effectiveMessageId == message.id {
                    return .none
                }
                if case let .replyThread(replyThreadMessage) = strongSelf.chatLocation, replyThreadMessage.peerId == strongSelf.context.account.peerId {
                    if replyThreadMessage.threadId != strongSelf.context.account.peerId.toInt64() {
                        return .none
                    }
                }
                if case .peer = strongSelf.chatLocation, let channel = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.flags.contains(.isForum) {
                    if message.threadId == nil {
                        return .none
                    }
                }
                
                if canReplyInChat(strongSelf.presentationInterfaceState, accountPeerId: strongSelf.context.account.peerId) {
                    return .reply
                } else if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info {
                }
            }
            return .none
        }, canSendMessages: { [weak self] in
            guard let self else {
                return false
            }
            return canSendMessagesToChat(self.presentationInterfaceState)
        }, navigateToFirstDateMessage: { [weak self] timestamp, alreadyThere in
            guard let strongSelf = self else {
                return
            }
            switch strongSelf.chatLocation {
            case let .peer(peerId):
                if alreadyThere {
                    strongSelf.openCalendarSearch(timestamp: timestamp)
                } else {
                    strongSelf.navigateToMessage(from: nil, to: .index(MessageIndex(id: MessageId(peerId: peerId, namespace: 0, id: 0), timestamp: timestamp - Int32(NSTimeZone.local.secondsFromGMT()))), scrollPosition: .bottom(0.0), rememberInStack: false, animated: true, completion: nil)
                }
            case let .replyThread(replyThreadMessage):
                let peerId = replyThreadMessage.peerId
                strongSelf.navigateToMessage(from: nil, to: .index(MessageIndex(id: MessageId(peerId: peerId, namespace: 0, id: 0), timestamp: timestamp - Int32(NSTimeZone.local.secondsFromGMT()))), scrollPosition: .bottom(0.0), rememberInStack: false, forceInCurrentChat: true, animated: true, completion: nil)
            case .customChatContents:
                break
            }
        }, requestRedeliveryOfFailedMessages: { [weak self] id in
            guard let strongSelf = self else {
                return
            }
            if id.namespace == Namespaces.Message.ScheduledCloud {
                let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Messages.MessageGroup(id: id))
                |> deliverOnMainQueue).startStandalone(next: { messages in
                    guard let strongSelf = self, let message = messages.filter({ $0.id == id }).first else {
                        return
                    }
                    
                    var actions: [ContextMenuItem] = []
                    actions.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ScheduledMessages_SendNow, icon: { theme in
                        return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.actionSheet.primaryTextColor)
                    }, action: { [weak self] _, f in
                        if let strongSelf = self {
                            strongSelf.controllerInteraction?.sendScheduledMessagesNow(messages.map { $0.id })
                        }
                        f(.dismissWithoutContent)
                    })))
                    actions.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ScheduledMessages_EditTime, icon: { theme in
                        return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Schedule"), color: theme.actionSheet.primaryTextColor)
                    }, action: { [weak self] _, f in
                        if let strongSelf = self {
                            strongSelf.controllerInteraction?.editScheduledMessagesTime(messages.map { $0.id })
                        }
                        f(.dismissWithoutContent)
                    })))
                    actions.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_ContextMenuDelete, textColor: .destructive, icon: { theme in
                        return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
                    }, action: { [weak self] controller, f in
                        if let strongSelf = self {
                            strongSelf.interfaceInteraction?.deleteMessages(messages.map { $0._asMessage() }, controller, f)
                        }
                    })))
                    
                    strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts()
                    
                    let controller = ContextController(presentationData: strongSelf.presentationData, source: .extracted(ChatMessageContextExtractedContentSource(chatController: strongSelf, chatNode: strongSelf.chatDisplayNode, engine: strongSelf.context.engine, message: message._asMessage(), selectAll: true)), items: .single(ContextController.Items(content: .list(actions))), recognizer: nil)
                    strongSelf.currentContextController = controller
                    strongSelf.forEachController({ controller in
                        if let controller = controller as? TooltipScreen {
                            controller.dismiss()
                        }
                        return true
                    })
                    strongSelf.window?.presentInGlobalOverlay(controller)
                })
            } else {
                let _ = (strongSelf.context.engine.messages.failedMessageGroup(id: id)
                |> deliverOnMainQueue).startStandalone(next: { messages in
                    guard let strongSelf = self else {
                        return
                    }
                    var groups: [UInt32: [Message]] = [:]
                    var notGrouped: [Message] = []
                    for message in messages {
                        if let groupInfo = message.groupInfo {
                            if groups[groupInfo.stableId] == nil {
                                groups[groupInfo.stableId] = []
                            }
                            groups[groupInfo.stableId]?.append(message._asMessage())
                        } else {
                            notGrouped.append(message._asMessage())
                        }
                    }
                    
                    let totalGroupCount = notGrouped.count + groups.count
                    
                    var maybeSelectedGroup: [Message]?
                    for (_, group) in groups {
                        if group.contains(where: { $0.id == id}) {
                            maybeSelectedGroup = group
                            break
                        }
                    }
                    for message in notGrouped {
                        if message.id == id {
                            maybeSelectedGroup = [message]
                        }
                    }
                    
                    guard let selectedGroup = maybeSelectedGroup, let topMessage = selectedGroup.first else {
                        return
                    }
                    
                    var actions: [ContextMenuItem] = []
                    actions.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_MessageDialogRetry, icon: { theme in
                        return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.actionSheet.primaryTextColor)
                    }, action: { [weak self] _, f in
                        if let strongSelf = self {
                            let _ = resendMessages(account: strongSelf.context.account, messageIds: selectedGroup.map({ $0.id })).startStandalone()
                        }
                        f(.dismissWithoutContent)
                    })))
                    if totalGroupCount != 1 {
                        actions.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_MessageDialogRetryAll(totalGroupCount).string, icon: { theme in
                            return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.actionSheet.primaryTextColor)
                        }, action: { [weak self] _, f in
                            if let strongSelf = self {
                                let _ = resendMessages(account: strongSelf.context.account, messageIds: messages.map({ $0.id })).startStandalone()
                            }
                            f(.dismissWithoutContent)
                        })))
                    }
                    actions.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_ContextMenuDelete, textColor: .destructive, icon: { theme in
                        return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
                    }, action: { [weak self] controller, f in
                        if let strongSelf = self {
                            let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: [id], type: .forLocalPeer).startStandalone()
                        }
                        f(.dismissWithoutContent)
                    })))
                    
                    strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts()
                    
                    let controller = ContextController(presentationData: strongSelf.presentationData, source: .extracted(ChatMessageContextExtractedContentSource(chatController: strongSelf, chatNode: strongSelf.chatDisplayNode, engine: strongSelf.context.engine, message: topMessage, selectAll: true)), items: .single(ContextController.Items(content: .list(actions))), recognizer: nil)
                    strongSelf.currentContextController = controller
                    strongSelf.forEachController({ controller in
                        if let controller = controller as? TooltipScreen {
                            controller.dismiss()
                        }
                        return true
                    })
                    strongSelf.window?.presentInGlobalOverlay(controller)
                })
            }
        }, addContact: { [weak self] phoneNumber in
            if let strongSelf = self {
                let _ = strongSelf.presentVoiceMessageDiscardAlert(action: {
                    strongSelf.context.sharedContext.openAddContact(context: strongSelf.context, firstName: "", lastName: "", phoneNumber: phoneNumber, label: defaultContactLabel, present: { [weak self] controller, arguments in
                        self?.present(controller, in: .window(.root), with: arguments)
                    }, pushController: { [weak self] controller in
                        if let strongSelf = self {
                            strongSelf.effectiveNavigationController?.pushViewController(controller)
                        }
                    }, completed: {})
                })
            }
        }, rateCall: { [weak self] message, callId, isVideo in
            if let strongSelf = self {
                let controller = callRatingController(sharedContext: strongSelf.context.sharedContext, account: strongSelf.context.account, callId: callId, userInitiated: true, isVideo: isVideo, present: { [weak self] c, a in
                    if let strongSelf = self {
                        strongSelf.present(c, in: .window(.root), with: a)
                    }
                }, push: { [weak self] c in
                    if let strongSelf = self {
                        strongSelf.push(c)
                    }
                })
                strongSelf.chatDisplayNode.dismissInput()
                strongSelf.present(controller, in: .window(.root))
            }
        }, requestSelectMessagePollOptions: { [weak self] id, opaqueIdentifiers in
            guard let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction else {
                return
            }
            
            guard strongSelf.presentationInterfaceState.subject != .scheduledMessages else {
                strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.ScheduledMessages_PollUnavailable, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
                return
            }
            if controllerInteraction.pollActionState.pollMessageIdsInProgress[id] == nil {
                controllerInteraction.pollActionState.pollMessageIdsInProgress[id] = opaqueIdentifiers
                strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id)
                let disposables: DisposableDict<MessageId>
                if let current = strongSelf.selectMessagePollOptionDisposables {
                    disposables = current
                } else {
                    disposables = DisposableDict()
                    strongSelf.selectMessagePollOptionDisposables = disposables
                }
                let signal = strongSelf.context.engine.messages.requestMessageSelectPollOption(messageId: id, opaqueIdentifiers: opaqueIdentifiers)
                disposables.set((signal
                |> deliverOnMainQueue).startStrict(next: { resultPoll in
                    guard let strongSelf = self, let resultPoll = resultPoll else {
                        return
                    }
                    guard let _ = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(id) else {
                        return
                    }
                    
                    switch resultPoll.kind {
                    case .poll:
                        if strongSelf.selectPollOptionFeedback == nil {
                            strongSelf.selectPollOptionFeedback = HapticFeedback()
                        }
                        strongSelf.selectPollOptionFeedback?.success()
                    case .quiz:
                        if let voters = resultPoll.results.voters {
                            for voter in voters {
                                if voter.selected {
                                    if voter.isCorrect {
                                        if strongSelf.selectPollOptionFeedback == nil {
                                            strongSelf.selectPollOptionFeedback = HapticFeedback()
                                        }
                                        strongSelf.selectPollOptionFeedback?.success()
                                        
                                        strongSelf.chatDisplayNode.animateQuizCorrectOptionSelected()
                                    } else {
                                        var found = false
                                        strongSelf.chatDisplayNode.historyNode.forEachVisibleItemNode { itemNode in
                                            if !found, let itemNode = itemNode as? ChatMessageBubbleItemNode, itemNode.item?.message.id == id {
                                                found = true
                                                if strongSelf.selectPollOptionFeedback == nil {
                                                    strongSelf.selectPollOptionFeedback = HapticFeedback()
                                                }
                                                strongSelf.selectPollOptionFeedback?.error()
                                                
                                                itemNode.animateQuizInvalidOptionSelected()
                                                
                                                if let solution = resultPoll.results.solution {
                                                    for contentNode in itemNode.contentNodes {
                                                        if let contentNode = contentNode as? ChatMessagePollBubbleContentNode {
                                                            let sourceNode = contentNode.solutionTipSourceNode
                                                            strongSelf.displayPollSolution(solution: solution, sourceNode: sourceNode, isAutomatic: true)
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                    break
                                }
                            }
                        }
                    }
                }, error: { _ in
                    guard let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction else {
                        return
                    }
                    if controllerInteraction.pollActionState.pollMessageIdsInProgress.removeValue(forKey: id) != nil {
                        strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id)
                    }
                }, completed: {
                    guard let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction else {
                        return
                    }
                    if controllerInteraction.pollActionState.pollMessageIdsInProgress.removeValue(forKey: id) != nil {
                        Queue.mainQueue().after(1.0, {
                            
                            strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id)
                        })
                    }
                }), forKey: id)
            }
        }, requestOpenMessagePollResults: { [weak self] messageId, pollId in
            guard let strongSelf = self, pollId.namespace == Namespaces.Media.CloudPoll else {
                return
            }
            let _ = strongSelf.presentVoiceMessageDiscardAlert(action: {
                let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: messageId))
                |> deliverOnMainQueue).startStandalone(next: { message in
                    guard let message = message else {
                        return
                    }
                    for media in message.media {
                        if let poll = media as? TelegramMediaPoll, poll.pollId == pollId {
                            strongSelf.push(pollResultsController(context: strongSelf.context, messageId: messageId, message: message, poll: poll))
                            break
                        }
                    }
                })
            }, delay: true)
        }, openAppStorePage: { [weak self] in
            if let strongSelf = self {
                strongSelf.context.sharedContext.applicationBindings.openAppStorePage()
            }
        }, displayMessageTooltip: { [weak self] messageId, text, isFactCheck, node, nodeRect in
            if let strongSelf = self {
                if let node = node {
                    strongSelf.messageTooltipController?.dismiss()
                    
                    let padding: CGFloat
                    let timeout: Double
                    let balancedTextLayout: Bool
                    let alignment: TooltipController.Alignment
                    let innerPadding: UIEdgeInsets
                    if isFactCheck {
                        timeout = 5.0
                        padding = 20.0
                        balancedTextLayout = true
                        alignment = .natural
                        innerPadding = UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0)
                    } else {
                        timeout = 2.0
                        padding = 8.0
                        balancedTextLayout = false
                        alignment = .center
                        innerPadding = .zero
                    }
                    
                    let tooltipController = TooltipController(content: .text(text), baseFontSize: strongSelf.presentationData.listsFontSize.baseDisplaySize, balancedTextLayout: balancedTextLayout, alignment: alignment, isBlurred: true, timeout: timeout, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true, padding: padding, innerPadding: innerPadding)
                    strongSelf.messageTooltipController = tooltipController
                    tooltipController.dismissed = { [weak tooltipController] _ in
                        if let strongSelf = self, let tooltipController = tooltipController, strongSelf.messageTooltipController === tooltipController {
                            strongSelf.messageTooltipController = nil
                        }
                    }
                    strongSelf.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: {
                        if let strongSelf = self {
                            var rect = node.view.convert(node.view.bounds, to: strongSelf.chatDisplayNode.view)
                            if let nodeRect = nodeRect {
                                rect = CGRect(origin: rect.origin.offsetBy(dx: nodeRect.minX, dy: nodeRect.minY - node.bounds.minY), size: nodeRect.size)
                            }
                            return (strongSelf.chatDisplayNode, rect)
                        }
                        return nil
                    }))
                }
            }
        }, seekToTimecode: { [weak self] message, timestamp, forceOpen in
            if let strongSelf = self {
                var found = false
                if !forceOpen {
                    strongSelf.chatDisplayNode.historyNode.forEachVisibleItemNode { itemNode in
                        if !found, let itemNode = itemNode as? ChatMessageItemView, itemNode.item?.message.id == message.id, let (action, _, _, _, _) = itemNode.playMediaWithSound() {
                            if case let .visible(fraction, _) = itemNode.visibility, fraction > 0.7 {
                                action(Double(timestamp))
                            } else {
                                let _ = strongSelf.controllerInteraction?.openMessage(message, OpenMessageParams(mode: .timecode(Double(timestamp))))
                            }
                            found = true
                        }
                    }
                }
                if !found {
                    var messageId = message.id
                    if let forwardInfo = message.forwardInfo, let sourceMessageId = forwardInfo.sourceMessageId, case let .replyThread(threadMessage) = strongSelf.chatLocation, threadMessage.isChannelPost {
                        messageId = sourceMessageId
                    }
                    if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) {
                        let _ = strongSelf.controllerInteraction?.openMessage(message, OpenMessageParams(mode: .timecode(Double(timestamp))))
                    } else {
                        strongSelf.navigateToMessage(messageLocation: .id(messageId, NavigateToMessageParams(timestamp: Double(timestamp), quote: nil)), animated: true, forceInCurrentChat: true)
                    }
                }
            }
        }, scheduleCurrentMessage: { [weak self] params in
            if let strongSelf = self {
                strongSelf.presentScheduleTimePicker(completion: { [weak self] time in
                    if let strongSelf = self {
                        if let _ = strongSelf.presentationInterfaceState.interfaceState.mediaDraftState {
                            strongSelf.sendMediaRecording(scheduleTime: time, messageEffect: (params?.effect).flatMap {
                                return ChatSendMessageEffect(id: $0.id)
                            })
                        } else {
                            let silentPosting = strongSelf.presentationInterfaceState.interfaceState.silentPosting
                            strongSelf.chatDisplayNode.sendCurrentMessage(silentPosting: silentPosting, scheduleTime: time, messageEffect: (params?.effect).flatMap {
                                return ChatSendMessageEffect(id: $0.id)
                            }) { [weak self] in
                                if let strongSelf = self {
                                    strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, saveInterfaceState: strongSelf.presentationInterfaceState.subject != .scheduledMessages, {
                                        $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedForwardMessageIds(nil).withUpdatedForwardOptionsState(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))) }
                                    })
                                    
                                    if strongSelf.presentationInterfaceState.subject != .scheduledMessages && time != scheduleWhenOnlineTimestamp {
                                        strongSelf.openScheduledMessages()
                                    }
                                }
                            }
                        }
                    }
                })
            }
        }, sendScheduledMessagesNow: { [weak self] messageIds in
            if let strongSelf = self {
                if let _ = strongSelf.presentationInterfaceState.slowmodeState {
                    if let rect = strongSelf.chatDisplayNode.frameForInputActionButton() {
                        strongSelf.interfaceInteraction?.displaySlowmodeTooltip(strongSelf.chatDisplayNode.view, rect)
                    }
                    return
                } else {
                    let _ = strongSelf.context.engine.messages.sendScheduledMessageNowInteractively(messageId: messageIds.first!).startStandalone()
                }
            }
        }, editScheduledMessagesTime: { [weak self] messageIds in
            if let strongSelf = self, let messageId = messageIds.first {
                let _ = strongSelf.presentVoiceMessageDiscardAlert(action: {
                    let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: messageId))
                    |> deliverOnMainQueue).startStandalone(next: { [weak self] message in
                        guard let strongSelf = self, let message = message else {
                            return
                        }
                        strongSelf.presentScheduleTimePicker(selectedTime: message.timestamp, completion: { [weak self] time in
                            if let strongSelf = self {
                                var entities: TextEntitiesMessageAttribute?
                                for attribute in message.attributes {
                                    if let attribute = attribute as? TextEntitiesMessageAttribute {
                                        entities = attribute
                                        break
                                    }
                                }
                                
                                let inlineStickers: [MediaId: TelegramMediaFile] = [:]
                                strongSelf.editMessageDisposable.set((strongSelf.context.engine.messages.requestEditMessage(messageId: messageId, text: message.text, media: .keep, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: nil, disableUrlPreview: false, scheduleTime: time) |> deliverOnMainQueue).startStrict(next: { result in
                                }, error: { error in
                                }))
                            }
                        })
                    })
                }, delay: true)
            }
        }, performTextSelectionAction: { [weak self] message, canCopy, text, action in
            guard let strongSelf = self else {
                return
            }
            
            if let performTextSelectionAction = strongSelf.performTextSelectionAction {
                performTextSelectionAction(message, canCopy, text, action)
                return
            }
            
            switch action {
            case .copy:
                storeAttributedTextInPasteboard(text)
            case .share:
                let f = {
                    guard let strongSelf = self else {
                        return
                    }
                    let shareController = ShareController(context: strongSelf.context, subject: .text(text.string), externalShare: true, immediateExternalShare: false, updatedPresentationData: strongSelf.updatedPresentationData)
                    strongSelf.chatDisplayNode.dismissInput()
                    strongSelf.present(shareController, in: .window(.root))
                }
                if let currentContextController = strongSelf.currentContextController {
                    currentContextController.dismiss(completion: {
                        f()
                    })
                } else {
                    f()
                }
            case .lookup:
                let controller = UIReferenceLibraryViewController(term: text.string)
                if let window = strongSelf.effectiveNavigationController?.view.window {
                    controller.popoverPresentationController?.sourceView = window
                    controller.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0))
                    window.rootViewController?.present(controller, animated: true)
                }
            case .speak:
                if let speechHolder = speakText(context: strongSelf.context, text: text.string) {
                    speechHolder.completion = { [weak self, weak speechHolder] in
                        if let strongSelf = self, strongSelf.currentSpeechHolder == speechHolder {
                            strongSelf.currentSpeechHolder = nil
                        }
                    }
                    strongSelf.currentSpeechHolder = speechHolder
                }
            case .translate:
                strongSelf.chatDisplayNode.dismissInput()
                let f = {
                    let _ = (context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.translationSettings])
                    |> take(1)
                    |> deliverOnMainQueue).startStandalone(next: { [weak self] sharedData in
                        guard let strongSelf = self else {
                            return
                        }
                        let translationSettings: TranslationSettings
                        if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.translationSettings]?.get(TranslationSettings.self) {
                            translationSettings = current
                        } else {
                            translationSettings = TranslationSettings.defaultSettings
                        }
                        
                        var showTranslateIfTopical = false
                        if let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer as? TelegramChannel, !(peer.addressName ?? "").isEmpty {
                            showTranslateIfTopical = true
                        }
                        
                        let (_, language) = canTranslateText(context: context, text: text.string, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: showTranslateIfTopical, ignoredLanguages: translationSettings.ignoredLanguages)
                        
                        let _ = ApplicationSpecificNotice.incrementTranslationSuggestion(accountManager: context.sharedContext.accountManager, timestamp: Int32(Date().timeIntervalSince1970)).startStandalone()
                        
                        let controller = TranslateScreen(context: context, text: text.string, canCopy: canCopy, fromLanguage: language, ignoredLanguages: translationSettings.ignoredLanguages)
                        controller.pushController = { [weak self] c in
                            self?.effectiveNavigationController?._keepModalDismissProgress = true
                            self?.push(c)
                        }
                        controller.presentController = { [weak self] c in
                            self?.present(c, in: .window(.root))
                        }
                        strongSelf.present(controller, in: .window(.root))
                    })
                }
                if let currentContextController = strongSelf.currentContextController {
                    currentContextController.dismiss(completion: {
                        f()
                    })
                } else {
                    f()
                }
            case let .quote(range):
                let completion: (ContainedViewLayoutTransition?) -> Void = { transition in
                    guard let self else {
                        return
                    }
                    if let currentContextController = self.currentContextController {
                        self.currentContextController = nil
                        
                        if let transition {
                            currentContextController.dismissWithCustomTransition(transition: transition)
                        } else {
                            currentContextController.dismiss(completion: {})
                        }
                    }
                }
                if let messageId = message?.id, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) ?? message {
                    var quoteData: EngineMessageReplyQuote?
                    
                    let nsRange = NSRange(location: range.lowerBound, length: range.upperBound - range.lowerBound)
                    let quoteText = (message.text as NSString).substring(with: nsRange)
                    
                    let trimmedText = trimStringWithEntities(string: quoteText, entities: messageTextEntitiesInRange(entities: message.textEntitiesAttribute?.entities ?? [], range: nsRange, onlyQuoteable: true), maxLength: quoteMaxLength(appConfig: strongSelf.context.currentAppConfiguration.with({ $0 })))
                    if !trimmedText.string.isEmpty {
                        quoteData = EngineMessageReplyQuote(text: trimmedText.string, offset: nsRange.location, entities: trimmedText.entities, media: nil)
                    }
                    
                    let replySubject = ChatInterfaceState.ReplyMessageSubject(
                        messageId: message.id,
                        quote: quoteData
                    )
                    
                    if canSendMessagesToChat(strongSelf.presentationInterfaceState) {
                        let _ = strongSelf.presentVoiceMessageDiscardAlert(action: {
                            strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(replySubject) }).updatedSearch(nil).updatedShowCommands(false) }, completion: completion)
                            strongSelf.updateItemNodesSearchTextHighlightStates()
                            strongSelf.chatDisplayNode.ensureInputViewFocused()
                        }, alertAction: {
                            completion(nil)
                        }, delay: true)
                    } else {
                        moveReplyMessageToAnotherChat(selfController: strongSelf, replySubject: replySubject)
                        completion(nil)
                    }
                } else {
                    strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) }) }, completion: completion)
                }
            }
        }, displayImportedMessageTooltip: { [weak self] _ in
            guard let strongSelf = self else {
                return
            }
            if let _ = strongSelf.currentImportMessageTooltip {
            } else {
                let controller = UndoOverlayController(presentationData: strongSelf.presentationData, content: .importedMessage(text: strongSelf.presentationData.strings.Conversation_ImportedMessageHint), elevatedLayout: false, action: { _ in return false })
                strongSelf.currentImportMessageTooltip = controller
                strongSelf.present(controller, in: .current)
            }
        }, displaySwipeToReplyHint: {  [weak self] in
            if let strongSelf = self, let validLayout = strongSelf.validLayout, min(validLayout.size.width, validLayout.size.height) > 320.0 {
                strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .swipeToReply(title: strongSelf.presentationData.strings.Conversation_SwipeToReplyHintTitle, text: strongSelf.presentationData.strings.Conversation_SwipeToReplyHintText), elevatedLayout: false, position: .top, action: { _ in return false }), in: .current)
            }
        }, dismissReplyMarkupMessage: { [weak self] message in
            guard let strongSelf = self, strongSelf.presentationInterfaceState.keyboardButtonsMessage?.id == message.id else {
                return
            }
            strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
                return $0.updatedInputMode({ _ in .text }).updatedInterfaceState({
                    $0.withUpdatedMessageActionsState({ value in
                        var value = value
                        value.closedButtonKeyboardMessageId = message.id
                        value.dismissedButtonKeyboardMessageId = message.id
                        return value
                    })
                })
            })
        }, openMessagePollResults: { [weak self] messageId, optionOpaqueIdentifier in
            guard let strongSelf = self else {
                return
            }
            let _ = strongSelf.presentVoiceMessageDiscardAlert(action: {
                let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: messageId))
                |> deliverOnMainQueue).startStandalone(next: { message in
                    guard let message = message else {
                        return
                    }
                    for media in message.media {
                        if let poll = media as? TelegramMediaPoll, poll.pollId.namespace == Namespaces.Media.CloudPoll {
                            strongSelf.push(pollResultsController(context: strongSelf.context, messageId: messageId, message: message, poll: poll, focusOnOptionWithOpaqueIdentifier: optionOpaqueIdentifier))
                            break
                        }
                    }
                })
            })
        }, openPollCreation: { [weak self] isQuiz in
            guard let strongSelf = self else {
                return
            }
            let _ = strongSelf.presentVoiceMessageDiscardAlert(action: {
                if let controller = strongSelf.configurePollCreation(isQuiz: isQuiz) {
                    strongSelf.effectiveNavigationController?.pushViewController(controller)
                }
            })
        }, displayPollSolution: { [weak self] solution, sourceNode in
            self?.displayPollSolution(solution: solution, sourceNode: sourceNode, isAutomatic: false)
        }, displayPsa: { [weak self] type, sourceNode in
            self?.displayPsa(type: type, sourceNode: sourceNode, isAutomatic: false)
        }, displayDiceTooltip: { [weak self] dice in
            self?.displayDiceTooltip(dice: dice)
        }, animateDiceSuccess: { [weak self] haptic, confetti in
            guard let strongSelf = self else {
                return
            }
            if strongSelf.selectPollOptionFeedback == nil {
                strongSelf.selectPollOptionFeedback = HapticFeedback()
            }
            if haptic {
                strongSelf.selectPollOptionFeedback?.success()
            }
            if confetti {
                strongSelf.chatDisplayNode.animateQuizCorrectOptionSelected()
            }
        }, displayPremiumStickerTooltip: { [weak self] file, message in
            self?.displayPremiumStickerTooltip(file: file, message: message)
        }, displayEmojiPackTooltip: { [weak self] file, message in
            self?.displayEmojiPackTooltip(file: file, message: message)
        }, openPeerContextMenu: { [weak self] peer, messageId, node, rect, gesture in
            guard let strongSelf = self else {
                return
            }
            
            if strongSelf.presentationInterfaceState.interfaceState.selectionState != nil {
                return
            }
            
            strongSelf.dismissAllTooltips()
            
            let context = strongSelf.context
            
            let dataSignal: Signal<(EnginePeer?, EngineMessage?), NoError>
            if let messageId = messageId {
                dataSignal = context.engine.data.get(
                    TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id),
                    TelegramEngine.EngineData.Item.Messages.Message(id: messageId)
                )
            } else {
                dataSignal = context.engine.data.get(
                    TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id)
                )
                |> map { peer -> (EnginePeer?, EngineMessage?) in
                    return (peer, nil)
                }
            }
            
            let _ = (dataSignal
            |> deliverOnMainQueue).startStandalone(next: { [weak self] peer, message in
                guard let strongSelf = self, let peer = peer else {
                    return
                }
                
                var isChannel = false
                if case let .channel(peer) = peer, case .broadcast = peer.info {
                    isChannel = true
                }
                var items: [ContextMenuItem] = [
                    .action(ContextMenuActionItem(text: isChannel ? strongSelf.presentationData.strings.Conversation_ContextMenuOpenChannelProfile : strongSelf.presentationData.strings.Conversation_ContextMenuOpenProfile, icon: { theme in
                        return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/User"), color: theme.actionSheet.primaryTextColor)
                    }, action: { _, f in
                        f(.dismissWithoutContent)
                        self?.openPeer(peer: peer, navigation: .info(nil), fromMessage: nil)
                    }))
                ]
                items.append(.action(ContextMenuActionItem(text: isChannel ? strongSelf.presentationData.strings.Conversation_ContextMenuOpenChannel : strongSelf.presentationData.strings.Conversation_ContextMenuSendMessage, icon: { theme in
                    return generateTintedImage(image: UIImage(bundleImageName: isChannel ? "Chat/Context Menu/Channels" : "Chat/Context Menu/Message"), color: theme.actionSheet.primaryTextColor)
                }, action: { _, f in
                    f(.dismissWithoutContent)
                    self?.openPeer(peer: peer, navigation: .chat(textInputState: nil, subject: nil, peekData: nil), fromMessage: nil)
                })))
                if !isChannel && canSendMessagesToChat(strongSelf.presentationInterfaceState) {
                    items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_ContextMenuMention, icon: { theme in
                        return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Mention"), color: theme.actionSheet.primaryTextColor)
                    }, action: { _, f in
                        f(.dismissWithoutContent)
                        
                        guard let strongSelf = self else {
                            return
                        }
                        
                        let _ = strongSelf.presentVoiceMessageDiscardAlert(action: {
                            strongSelf.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in
                                var inputMode = inputMode
                                if inputMode == .none {
                                    inputMode = .text
                                }
                                return (chatTextInputAddMentionAttribute(current, peer: peer), inputMode)
                            }
                        }, delay: true)
                    })))
                }
                if !isChannel {
                    items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_ContextMenuSearchMessages, icon: { theme in
                        return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Search"), color: theme.actionSheet.primaryTextColor)
                    }, action: { _, f in
                        f(.dismissWithoutContent)
                        
                        guard let strongSelf = self else {
                            return
                        }
                        strongSelf.activateSearch(domain: .member(peer._asPeer()))
                    })))
                }
                
                strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts()
                
                strongSelf.canReadHistory.set(false)
                
                let source: ContextContentSource
                if let _ = peer.smallProfileImage {
                    let galleryController = AvatarGalleryController(context: context, peer: peer, remoteEntries: nil, replaceRootController: { controller, ready in
                    }, synchronousLoad: true)
                    galleryController.setHintWillBePresentedInPreviewingContext(true)
                    source = .controller(ChatContextControllerContentSourceImpl(controller: galleryController, sourceNode: node, passthroughTouches: false))
                } else {
                    source = .reference(ChatControllerContextReferenceContentSource(controller: strongSelf, sourceView: node.view, insets: .zero))
                }
                
                let contextController = ContextController(presentationData: strongSelf.presentationData, source: source, items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
                contextController.dismissed = { [weak self] in
                    self?.canReadHistory.set(true)
                }
                strongSelf.presentInGlobalOverlay(contextController)
            })
        }, openMessageReplies: { [weak self] messageId, isChannelPost, displayModalProgress in
            guard let strongSelf = self else {
                return
            }
            
            strongSelf.openMessageReplies(messageId: messageId, displayProgressInMessage: displayModalProgress ? nil : messageId, isChannelPost: isChannelPost, atMessage: nil, displayModalProgress: displayModalProgress)
        }, openReplyThreadOriginalMessage: { [weak self] message in
            guard let strongSelf = self else {
                return
            }
            var threadMessageId: MessageId?
            for attribute in message.attributes {
                if let attribute = attribute as? ReplyMessageAttribute {
                    threadMessageId = attribute.threadMessageId
                    break
                }
            }
            for attribute in message.attributes {
                if let attribute = attribute as? SourceReferenceMessageAttribute {
                    if let threadMessageId = threadMessageId {
                        if let _ = strongSelf.navigationController as? NavigationController {
                            strongSelf.openMessageReplies(messageId: threadMessageId, displayProgressInMessage: message.id, isChannelPost: true, atMessage: attribute.messageId, displayModalProgress: false)
                        }
                    } else {
                        strongSelf.navigateToMessage(from: nil, to: .id(attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: nil)))
                    }
                    break
                }
            }
        }, openMessageStats: { [weak self] id in
            guard let strongSelf = self else {
                return
            }
            
            let _ = strongSelf.presentVoiceMessageDiscardAlert(action: {
                let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: id))
                |> mapToSignal { message -> Signal<EngineMessage.Id?, NoError> in
                    if let message {
                        return .single(message.id)
                    } else {
                        return .complete()
                    }
                }
                |> deliverOnMainQueue).startStandalone(next: { [weak self] messageId in
                    guard let strongSelf = self, let messageId else {
                        return
                    }
                    strongSelf.push(messageStatsController(context: context, subject: .message(id: messageId)))
                })
            }, delay: true)
        }, editMessageMedia: { [weak self] messageId, draw in
            guard let strongSelf = self else {
                return
            }
            
            strongSelf.chatDisplayNode.dismissInput()
            
            if draw {
                let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: messageId))
                |> deliverOnMainQueue).startStandalone(next: { [weak self] message in
                    guard let strongSelf = self, let message = message else {
                        return
                    }
                    
                    var mediaReference: AnyMediaReference?
                    for m in message.media {
                        if let image = m as? TelegramMediaImage {
                            mediaReference = AnyMediaReference.standalone(media: image)
                        }
                    }
                    
                    if let mediaReference = mediaReference, let peer = message.peers[message.id.peerId] {
                        let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText
                        legacyMediaEditor(context: strongSelf.context, peer: peer, threadTitle: strongSelf.threadInfo?.title, media: mediaReference, mode: .draw, initialCaption: inputText, snapshots: [], transitionCompletion: nil, getCaptionPanelView: { [weak self] in
                            return self?.getCaptionPanelView(isFile: true)
                        }, sendMessagesWithSignals: { [weak self] signals, _, _ in
                            if let strongSelf = self {
                                strongSelf.interfaceInteraction?.setupEditMessage(messageId, { _ in })
                                strongSelf.editMessageMediaWithLegacySignals(signals!)
                            }
                        }, present: { [weak self] c, a in
                            self?.present(c, in: .window(.root), with: a)
                        })
                    }
                })
            } else {
                strongSelf.presentOldMediaPicker(fileMode: false, editingMedia: true, completion: { signals, _, _ in
                    self?.interfaceInteraction?.setupEditMessage(messageId, { _ in })
                    self?.editMessageMediaWithLegacySignals(signals)
                })
            }
        }, copyText: { [weak self] text in
            if let strongSelf = self {
                storeMessageTextInPasteboard(text, entities: nil)
                
                let presentationData = context.sharedContext.currentPresentationData.with { $0 }
                strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in
                        return true
                }), in: .current)
            }
        }, displayUndo: { [weak self] content in
            if let strongSelf = self {
                let presentationData = context.sharedContext.currentPresentationData.with { $0 }
                
                strongSelf.window?.forEachController({ controller in
                    if let controller = controller as? UndoOverlayController {
                        controller.dismiss()
                    }
                })
                strongSelf.forEachController({ controller in
                    if let controller = controller as? UndoOverlayController {
                        controller.dismiss()
                    }
                    return true
                })
                
                strongSelf.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, animateInAsReplacement: false, action: { _ in
                        return true
                }), in: .current)
            }
        }, isAnimatingMessage: { [weak self] stableId in
            guard let strongSelf = self else {
                return false
            }
            return strongSelf.chatDisplayNode.messageTransitionNode.isAnimatingMessage(stableId: stableId)
        }, getMessageTransitionNode: { [weak self] in
            guard let strongSelf = self else {
                return nil
            }
            return strongSelf.chatDisplayNode.messageTransitionNode
        }, updateChoosingSticker: { [weak self] value in
            if let strongSelf = self {
                strongSelf.choosingStickerActivityPromise.set(value)
            }
        }, commitEmojiInteraction: { [weak self] messageId, emoji, interaction, file in
            guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer, peer.id != strongSelf.context.account.peerId else {
                return
            }
            
            strongSelf.context.account.updateLocalInputActivity(peerId: PeerActivitySpace(peerId: messageId.peerId, category: .global), activity: .interactingWithEmoji(emoticon: emoji, messageId: messageId, interaction: interaction), isPresent: true)
            
            let currentTimestamp = Int32(Date().timeIntervalSince1970)
            let _ = (ApplicationSpecificNotice.getInteractiveEmojiSyncTip(accountManager: strongSelf.context.sharedContext.accountManager)
            |> deliverOnMainQueue).startStandalone(next: { [weak self] count, timestamp in
                if let strongSelf = self, count < 3 && currentTimestamp > timestamp + 24 * 60 * 60 {
                    strongSelf.interactiveEmojiSyncDisposable.set(
                        (strongSelf.peerInputActivitiesPromise.get()
                        |> filter { activities -> Bool in
                            var found = false
                            for (_, activity) in activities {
                                if case .seeingEmojiInteraction(emoji) = activity {
                                    found = true
                                    break
                                }
                            }
                            return found
                        }
                        |> map { _ -> Bool in
                            return true
                        }
                        |> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(false))).startStrict(next: { [weak self] responded in
                            if let strongSelf = self {
                                if !responded {
                                    strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: file, loop: true, title: nil, text: strongSelf.presentationData.strings.Conversation_InteractiveEmojiSyncTip(EnginePeer(peer).compactDisplayTitle).string, undoText: nil, customAction: nil), elevatedLayout: false, action: { _ in return false }), in: .current)
                                    
                                    let _ = ApplicationSpecificNotice.incrementInteractiveEmojiSyncTip(accountManager: strongSelf.context.sharedContext.accountManager, timestamp: currentTimestamp).startStandalone()
                                }
                            }
                        })
                    )
                }
            })
        }, openLargeEmojiInfo: { [weak self] _, fitz, file in
            guard let strongSelf = self else {
                return
            }
            let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData)
            actionSheet.setItemGroups([ActionSheetItemGroup(items: [
                LargeEmojiActionSheetItem(context: strongSelf.context, text: strongSelf.presentationData.strings.Conversation_LargeEmojiDisabledInfo, fitz: fitz, file: file),
                ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_LargeEmojiEnable, color: .accent, action: { [weak actionSheet, weak self] in
                    actionSheet?.dismissAnimated()
                    guard let strongSelf = self else {
                        return
                    }
                    let _ = updatePresentationThemeSettingsInteractively(accountManager: strongSelf.context.sharedContext.accountManager, { current in
                        return current.withUpdatedLargeEmoji(true)
                    }).startStandalone()
                    
                    strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .emoji(name: "TwoFactorSetupRememberSuccess", text: strongSelf.presentationData.strings.Conversation_LargeEmojiEnabled), elevatedLayout: false, action: { _ in return false }), in: .current)
                })
            ]), ActionSheetItemGroup(items: [
                ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
                    actionSheet?.dismissAnimated()
                })
            ])])
            strongSelf.chatDisplayNode.dismissInput()
            strongSelf.present(actionSheet, in: .window(.root))
        }, openJoinLink: { [weak self] joinHash in
            guard let strongSelf = self else {
                return
            }
            strongSelf.openResolved(result: .join(joinHash), sourceMessageId: nil)
        }, openWebView: { [weak self] buttonText, url, simple, source in
            guard let self else {
                return
            }
            self.openWebApp(buttonText: buttonText, url: url, simple: simple, source: source)
        }, activateAdAction: { [weak self] messageId, progress in
            guard let self, let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId), let adAttribute = message.adAttribute else {
                return
            }
            
            var progress = progress
            if progress == nil {
                self.chatDisplayNode.historyNode.forEachVisibleMessageItemNode { itemView in
                    if itemView.item?.message.id == messageId {
                        progress = itemView.makeProgress()
                    }
                }
            }
            
            self.chatDisplayNode.historyNode.adMessagesContext?.markAction(opaqueId: adAttribute.opaqueId)
            self.controllerInteraction?.openUrl(ChatControllerInteraction.OpenUrl(url: adAttribute.url, concealed: false, external: true, progress: progress))
        }, openRequestedPeerSelection: { [weak self] messageId, peerType, buttonId, maxQuantity in
            guard let self else {
                return
            }
            let botName = self.presentationInterfaceState.renderedPeer?.peer.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? ""
            let context = self.context
            let peerId = self.chatLocation.peerId
            
            let presentConfirmation: (String, Bool, @escaping () -> Void) -> Void = { [weak self] peerName, isChannel, completion in
                guard let strongSelf = self else {
                    return
                }
                
                var attributedTitle: NSAttributedString?
                let attributedText: NSAttributedString
                
                let theme = AlertControllerTheme(presentationData: strongSelf.presentationData)
                if case .user = peerType {
                    attributedTitle = nil
                    attributedText = NSAttributedString(string: strongSelf.presentationData.strings.RequestPeer_SelectionConfirmationTitle(peerName, botName).string, font: Font.medium(17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
                } else {
                    attributedTitle = NSAttributedString(string: strongSelf.presentationData.strings.RequestPeer_SelectionConfirmationTitle(peerName, botName).string, font: Font.semibold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
                    
                    var botAdminRights: TelegramChatAdminRights?
                    switch peerType {
                    case let .group(group):
                        botAdminRights = group.botAdminRights
                    case let .channel(channel):
                        botAdminRights = channel.botAdminRights
                    default:
                        break
                    }
                    if let botAdminRights {
                        if botAdminRights.rights.isEmpty {
                            let stringWithRanges = strongSelf.presentationData.strings.RequestPeer_SelectionConfirmationInviteAdminText(botName, peerName)
                            let formattedString = NSMutableAttributedString(string: stringWithRanges.string, font: Font.regular(strongSelf.presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
                            for range in stringWithRanges.ranges.prefix(2) {
                                formattedString.addAttribute(.font, value: Font.semibold(strongSelf.presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0), range: range.range)
                            }
                            attributedText = formattedString
                        } else {
                            let stringWithRanges = strongSelf.presentationData.strings.RequestPeer_SelectionConfirmationInviteWithRightsText(botName, peerName, stringForAdminRights(strings: strongSelf.presentationData.strings, adminRights: botAdminRights, isChannel: isChannel))
                            let formattedString = NSMutableAttributedString(string: stringWithRanges.string, font: Font.regular(strongSelf.presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
                            for range in stringWithRanges.ranges.prefix(2) {
                                formattedString.addAttribute(.font, value: Font.semibold(strongSelf.presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0), range: range.range)
                            }
                            attributedText = formattedString
                        }
                    } else {
                        if case let .group(group) = peerType, group.botParticipant {
                            let stringWithRanges = strongSelf.presentationData.strings.RequestPeer_SelectionConfirmationInviteText(botName, peerName)
                            let formattedString = NSMutableAttributedString(string: stringWithRanges.string, font: Font.regular(strongSelf.presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
                            for range in stringWithRanges.ranges.prefix(2) {
                                formattedString.addAttribute(.font, value: Font.semibold(strongSelf.presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0), range: range.range)
                            }
                            attributedText = formattedString
                        } else {
                            attributedTitle = nil
                            attributedText = NSAttributedString(string: strongSelf.presentationData.strings.RequestPeer_SelectionConfirmationTitle(peerName, botName).string, font: Font.semibold(strongSelf.presentationData.listsFontSize.baseDisplaySize), textColor: theme.primaryColor, paragraphAlignment: .center)
                        }
                    }
                }
                
                let controller = richTextAlertController(context: context, title: attributedTitle, text: attributedText, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.RequestPeer_SelectionConfirmationSend, action: {
                    completion()
                })])
                strongSelf.present(controller, in: .window(.root))
            }
            
            if case .user = peerType, maxQuantity > 1 {
                let presentationData = self.presentationData
                var reachedLimitImpl: ((Int32) -> Void)?
                let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .requestedUsersSelection, isPeerEnabled: { peer in
                    if case let .user(user) = peer, user.botInfo == nil {
                        return true
                    } else {
                        return false
                    }
                }, limit: maxQuantity, reachedLimit: { limit in
                    reachedLimitImpl?(limit)
                }))
                controller.navigationPresentation = .modal
                reachedLimitImpl = { [weak controller] limit in
                    guard let controller else {
                        return
                    }
                    HapticFeedback().error()
                    controller.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.RequestPeer_ReachedMaximum(limit), timeout: nil, customUndoText: nil), elevatedLayout: true, position: .bottom, animateInAsReplacement: false, action: { _ in return false }), in: .current)
                }
                
                let _ = (controller.result
                |> deliverOnMainQueue).startStandalone(next: { [weak controller] result in
                    guard let controller else {
                        return
                    }
                    var peerIds: [PeerId] = []
                    if case let .result(peerIdsValue, _) = result {
                        peerIds = peerIdsValue.compactMap({ peerId in
                            if case let .peer(peerId) = peerId {
                                return peerId
                            } else {
                                return nil
                            }
                        })
                    }
                    let _ = context.engine.peers.sendBotRequestedPeer(messageId: messageId, buttonId: buttonId, requestedPeerIds: peerIds).startStandalone()
                    controller.dismiss()
                })
                
                self.push(controller)
            } else {
                var createNewGroupImpl: (() -> Void)?
                let controller = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: [.excludeRecent, .doNotSearchMessages], requestPeerType: [peerType], hasContactSelector: false, createNewGroup: {
                    createNewGroupImpl?()
                }, hasCreation: true))
                   
                controller.peerSelected = { [weak self, weak controller] peer, _ in
                    guard let strongSelf = self else {
                        return
                    }
                    if case .user = peerType {
                        let _ = context.engine.peers.sendBotRequestedPeer(messageId: messageId, buttonId: buttonId, requestedPeerIds: [peer.id]).startStandalone()
                        controller?.dismiss()
                    } else {
                        var isChannel = false
                        if case let .channel(channel) = peer, case .broadcast = channel.info {
                            isChannel = true
                        }
                        let peerName = peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)
                        presentConfirmation(peerName, isChannel, {
                            let _ = context.engine.peers.sendBotRequestedPeer(messageId: messageId, buttonId: buttonId, requestedPeerIds: [peer.id]).startStandalone()
                            controller?.dismiss()
                        })
                    }
                }
                createNewGroupImpl = { [weak controller] in
                    switch peerType {
                    case .user:
                        break
                    case let .group(group):
                        let createGroupController = createGroupControllerImpl(context: context, peerIds: group.botParticipant || group.botAdminRights != nil ? (peerId.flatMap { [$0] } ?? []) : [], mode: .requestPeer(group), willComplete: { peerName, complete in
                            presentConfirmation(peerName, false, {
                                complete()
                            })
                        }, completion: { peerId, dismiss in
                            let _ = context.engine.peers.sendBotRequestedPeer(messageId: messageId, buttonId: buttonId, requestedPeerIds: [peerId]).startStandalone()
                            dismiss()
                        })
                        createGroupController.navigationPresentation = .modal
                        controller?.replace(with: createGroupController)
                    case let .channel(channel):
                        let createChannelController = createChannelController(context: context, mode: .requestPeer(channel), willComplete: { peerName, complete in
                            presentConfirmation(peerName, true, {
                                complete()
                            })
                        }, completion: { peerId, dismiss in
                            let _ = context.engine.peers.sendBotRequestedPeer(messageId: messageId, buttonId: buttonId, requestedPeerIds: [peerId]).startStandalone()
                            dismiss()
                        })
                        createChannelController.navigationPresentation = .modal
                        controller?.replace(with: createChannelController)
                    }
                }
                self.push(controller)
            }
        }, saveMediaToFiles: { [weak self] messageId in
            let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: messageId))
            |> deliverOnMainQueue).startStandalone(next: { message in
                guard let self, let message else {
                    return
                }
                var file: TelegramMediaFile?
                var title: String?
                var performer: String?
                for media in message.media {
                    if let mediaFile = media as? TelegramMediaFile, mediaFile.isMusic {
                        file = mediaFile
                        for attribute in mediaFile.attributes {
                            if case let .Audio(_, _, titleValue, performerValue, _) = attribute {
                                if let titleValue, !titleValue.isEmpty {
                                    title = titleValue
                                }
                                if let performerValue, !performerValue.isEmpty {
                                    performer = performerValue
                                }
                            }
                        }
                    }
                }
                guard let file else {
                    return
                }
                
                var signal = fetchMediaData(context: context, postbox: context.account.postbox, userLocation: .other, mediaReference: .message(message: MessageReference(message._asMessage()), media: file))
                
                let disposable: MetaDisposable
                if let current = self.saveMediaDisposable {
                    disposable = current
                } else {
                    disposable = MetaDisposable()
                    self.saveMediaDisposable = disposable
                }
                
                var cancelImpl: (() -> Void)?
                let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
                let progressSignal = Signal<Never, NoError> { [weak self] subscriber in
                    guard let self else {
                        return EmptyDisposable
                    }
                    let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
                        cancelImpl?()
                    }))
                    self.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
                    return ActionDisposable { [weak controller] in
                        Queue.mainQueue().async() {
                            controller?.dismiss()
                        }
                    }
                }
                |> runOn(Queue.mainQueue())
                |> delay(0.15, queue: Queue.mainQueue())
                let progressDisposable = progressSignal.startStrict()
                
                signal = signal
                |> afterDisposed {
                    Queue.mainQueue().async {
                        progressDisposable.dispose()
                    }
                }
                cancelImpl = { [weak disposable] in
                    disposable?.set(nil)
                }
                disposable.set((signal
                |> deliverOnMainQueue).startStrict(next: { [weak self] state, _ in
                    guard let self else {
                        return
                    }
                    switch state {
                    case .progress:
                        break
                    case let .data(data):
                        if data.complete {
                            var symlinkPath = data.path + ".mp3"
                            if fileSize(symlinkPath) != nil {
                                try? FileManager.default.removeItem(atPath: symlinkPath)
                            }
                            let _ = try? FileManager.default.linkItem(atPath: data.path, toPath: symlinkPath)
                            
                            let audioUrl = URL(fileURLWithPath: symlinkPath)
                            let audioAsset = AVURLAsset(url: audioUrl)
                            
                            var fileExtension = "mp3"
                            if let filename = file.fileName {
                                if let dotIndex = filename.lastIndex(of: ".") {
                                    fileExtension = String(filename[filename.index(after: dotIndex)...])
                                }
                            }
                            
                            var nameComponents: [String] = []
                            if let title {
                                if let performer {
                                    nameComponents.append(performer)
                                }
                                nameComponents.append(title)
                            } else {
                                var artist: String?
                                var title: String?
                                for data in audioAsset.commonMetadata {
                                    if data.commonKey == .commonKeyArtist {
                                        artist = data.stringValue
                                    }
                                    if data.commonKey == .commonKeyTitle {
                                        title = data.stringValue
                                    }
                                }
                                if let artist, !artist.isEmpty {
                                    nameComponents.append(artist)
                                }
                                if let title, !title.isEmpty {
                                    nameComponents.append(title)
                                }
                                if nameComponents.isEmpty, var filename = file.fileName {
                                    if let dotIndex = filename.lastIndex(of: ".") {
                                        filename = String(filename[..<dotIndex])
                                    }
                                    nameComponents.append(filename)
                                }
                            }
                            if !nameComponents.isEmpty {
                                try? FileManager.default.removeItem(atPath: symlinkPath)
                                
                                let fileName = "\(nameComponents.joined(separator: " – ")).\(fileExtension)"
                                symlinkPath = symlinkPath.replacingOccurrences(of: audioUrl.lastPathComponent, with: fileName)
                                let _ = try? FileManager.default.linkItem(atPath: data.path, toPath: symlinkPath)
                            }
                            
                            let url = URL(fileURLWithPath: symlinkPath)
                            let controller = legacyICloudFilePicker(theme: self.presentationData.theme, mode: .export, url: url, documentTypes: [], forceDarkTheme: false, dismissed: {}, completion: { _ in
                                
                            })
                            self.present(controller, in: .window(.root))
                        }
                    }
                }))
            })
        }, openNoAdsDemo: { [weak self] in
            guard let self else {
                return
            }
            if self.context.isPremium {
                self.present(UndoOverlayController(presentationData: self.presentationData, content: .actionSucceeded(title: nil, text: self.presentationData.strings.ReportAd_Hidden, cancel: nil, destructive: false), elevatedLayout: false, action: { _ in
                    return true
                }), in: .current)
                
                var adOpaqueId: Data?
                self.chatDisplayNode.historyNode.forEachVisibleMessageItemNode { itemView in
                    if let adAttribute = itemView.item?.message.adAttribute {
                        adOpaqueId = adAttribute.opaqueId
                    }
                }
                let _ = self.context.engine.accountData.updateAdMessagesEnabled(enabled: false).start()
                if let adOpaqueId {
                    self.removeAd(opaqueId: adOpaqueId)
                }
            } else {
                let context = self.context
                var replaceImpl: ((ViewController) -> Void)?
                let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .noAds, forceDark: false, action: {
                    let controller = context.sharedContext.makePremiumIntroController(context: context, source: .ads, forceDark: false, dismissed: nil)
                    replaceImpl?(controller)
                }, dismissed: nil)
                replaceImpl = { [weak controller] c in
                    controller?.replace(with: c)
                }
                self.push(controller)
            }
        }, openAdsInfo: { [weak self] in
            guard let self else {
                return
            }
            self.push(AdsInfoScreen(context: self.context))
        }, displayGiveawayParticipationStatus: { [weak self] messageId in
            guard let self else {
                return
            }
            let disposable: MetaDisposable
            if let current = self.giveawayStatusDisposable {
                disposable = current
            } else {
                disposable = MetaDisposable()
                self.giveawayStatusDisposable = disposable
            }
            disposable.set((self.context.engine.payments.premiumGiveawayInfo(peerId: messageId.peerId, messageId: messageId)
            |> deliverOnMainQueue).start(next: { [weak self] info in
                guard let self, let info else {
                    return
                }
                let content: UndoOverlayContent
                switch info {
                case let .ongoing(_, status):
                    switch status {
                    case .notAllowed:
                        content = .info(title: nil, text: self.presentationData.strings.Chat_Giveaway_Toast_NotAllowed, timeout: nil, customUndoText: self.presentationData.strings.Chat_Giveaway_Toast_LearnMore)
                    case .participating:
                        content = .succeed(text: self.presentationData.strings.Chat_Giveaway_Toast_Participating, timeout: nil, customUndoText: self.presentationData.strings.Chat_Giveaway_Toast_LearnMore)
                    case .notQualified:
                        content = .info(title: nil, text: self.presentationData.strings.Chat_Giveaway_Toast_NotQualified, timeout: nil, customUndoText: self.presentationData.strings.Chat_Giveaway_Toast_LearnMore)
                    case .almostOver:
                        content = .info(title: nil, text: self.presentationData.strings.Chat_Giveaway_Toast_AlmostOver, timeout: nil, customUndoText: self.presentationData.strings.Chat_Giveaway_Toast_LearnMore)
                    }
                    case .finished:
                        content = .info(title: nil, text: self.presentationData.strings.Chat_Giveaway_Toast_Ended, timeout: nil, customUndoText: self.presentationData.strings.Chat_Giveaway_Toast_LearnMore)
                }
                let controller = UndoOverlayController(presentationData: self.presentationData, content: content, elevatedLayout: false, position: .bottom, animateInAsReplacement: false, action: { [weak self] action in
                    if case .undo = action, let self {
                        self.displayGiveawayStatusInfo(messageId: messageId, giveawayInfo: info)
                        return true
                    }
                    return false
                })
                self.present(controller, in: .current)
                
            }))
        }, openPremiumStatusInfo: { [weak self] peerId, sourceView, peerStatus, nameColor in
            guard let self else {
                return
            }
            
            let context = self.context
            let source: Signal<PremiumSource, NoError>
            if let peerStatus {
                source = context.engine.stickers.resolveInlineStickers(fileIds: [peerStatus])
                |> mapToSignal { files in
                    if let file = files[peerStatus] {
                        var reference: StickerPackReference?
                        for attribute in file.attributes {
                            if case let .CustomEmoji(_, _, _, packReference) = attribute, let packReference = packReference {
                                reference = packReference
                                break
                            }
                        }
                        
                        if let reference {
                            return context.engine.stickers.loadedStickerPack(reference: reference, forceActualized: false)
                            |> filter { result in
                                if case .result = result {
                                    return true
                                } else {
                                    return false
                                }
                            }
                            |> take(1)
                            |> mapToSignal { result -> Signal<PremiumSource, NoError> in
                                if case let .result(_, items, _) = result {
                                    return .single(.emojiStatus(peerId, peerStatus, items.first?.file, result))
                                } else {
                                    return .single(.emojiStatus(peerId, peerStatus, nil, nil))
                                }
                            }
                        } else {
                            return .single(.emojiStatus(peerId, peerStatus, nil, nil))
                        }
                    } else {
                        return .single(.emojiStatus(peerId, peerStatus, nil, nil))
                    }
                }
            } else {
                source = .single(.profile(peerId))
            }
            
            let _ = (source
            |> deliverOnMainQueue).startStandalone(next: { [weak self] source in
                guard let self else {
                    return
                }
                let controller = PremiumIntroScreen(context: self.context, source: source)
                controller.sourceView = sourceView
                controller.containerView = self.navigationController?.view
                controller.animationColor = self.context.peerNameColors.get(nameColor, dark: self.presentationData.theme.overallDarkAppearance).main
                self.push(controller)
            })
            
        }, openRecommendedChannelContextMenu: { [weak self] peer, sourceView, gesture in
            guard let self else {
                return
            }
            
            let chatController = self.context.sharedContext.makeChatController(context: self.context, chatLocation: .peer(id: peer.id), subject: nil, botStart: nil, mode: .standard(.previewing), params: nil)
            chatController.canReadHistory.set(false)
            
            var items: [ContextMenuItem] = [
                .action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_LinkDialogOpen, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ImageEnlarge"), color: theme.actionSheet.primaryTextColor) }, action: { [weak self] _, f in
                    f(.dismissWithoutContent)
                    self?.openPeer(peer: peer, navigation: .chat(textInputState: nil, subject: nil, peekData: nil), fromMessage: nil)
                })),
            ]
            items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Chat_SimilarChannels_Join, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.actionSheet.primaryTextColor) }, action: { [weak self] _, f in
                f(.dismissWithoutContent)
                
                guard let self else {
                    return
                }
                let presentationData = self.presentationData
                self.joinChannelDisposable.set((
                    self.context.peerChannelMemberCategoriesContextsManager.join(engine: self.context.engine, peerId: peer.id, hash: nil)
                    |> deliverOnMainQueue
                    |> afterCompleted { [weak self] in
                        Queue.mainQueue().async {
                            if let self {
                                self.present(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.Chat_SimilarChannels_JoinedChannel(peer.compactDisplayTitle).string, timeout: nil, customUndoText: nil), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { _ in return false }), in: .current)
                            }
                        }
                    }
                ).startStrict(error: { [weak self] error in
                    guard let self else {
                        return
                    }
                    let text: String
                    switch error {
                    case .inviteRequestSent:
                        self.present(UndoOverlayController(presentationData: presentationData, content: .inviteRequestSent(title: presentationData.strings.Group_RequestToJoinSent, text: presentationData.strings.Group_RequestToJoinSentDescriptionGroup), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
                        return
                    case .tooMuchJoined:
                        self.push(oldChannelsController(context: context, intent: .join))
                        return
                    case .tooMuchUsers:
                        text = self.presentationData.strings.Conversation_UsersTooMuchError
                    case .generic:
                        text = self.presentationData.strings.Channel_ErrorAccessDenied
                    }
                    self.present(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
                }))
            })))
                      
            self.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts()
            
            self.canReadHistory.set(false)
            
            let contextController = ContextController(presentationData: self.presentationData, source: .controller(ChatContextControllerContentSourceImpl(controller: chatController, sourceView: sourceView, passthroughTouches: true)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
            contextController.dismissed = { [weak self] in
                self?.canReadHistory.set(true)
            }
            self.presentInGlobalOverlay(contextController)
        }, openGroupBoostInfo: { [weak self] userId, count in
            guard let self, let peerId = self.chatLocation.peerId else {
                return
            }
            let _ = combineLatest(queue: Queue.mainQueue(),
                context.engine.peers.getChannelBoostStatus(peerId: peerId),
                context.engine.peers.getMyBoostStatus()
            ).startStandalone(next: { [weak self] boostStatus, myBoostStatus in
                guard let self, let boostStatus, let myBoostStatus else {
                    return
                }
                let boostController = PremiumBoostLevelsScreen(
                    context: self.context,
                    peerId: peerId,
                    mode: userId.flatMap { .user(mode: .groupPeer($0, count)) } ?? .user(mode: .current),
                    status: boostStatus,
                    myBoostStatus: myBoostStatus
                )
                self.push(boostController)
            })
        }, openStickerEditor: { [weak self] in
            guard let self else {
                return
            }
            self.openStickerEditor()
        }, openAgeRestrictedMessageMedia: { [weak self] message, reveal in
            guard let self else {
                return
            }
            let controller = chatAgeRestrictionAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, completion: { [weak self] alwaysShow in
                guard let self else {
                    return
                }
                if alwaysShow {
                    let _ = updateRemoteContentSettingsConfiguration(postbox: context.account.postbox, network: context.account.network, sensitiveContentEnabled: true).start()
                    
                    self.present(UndoOverlayController(presentationData: self.presentationData, content: .info(title: nil, text: self.presentationData.strings.SensitiveContent_SettingsInfo, timeout: nil, customUndoText: nil), elevatedLayout: false, position: .top, action: { [weak self] action in
                        if case .info = action, let self {
                            let controller = self.context.sharedContext.makeDataAndStorageController(context: self.context, sensitiveContent: true)
                            self.push(controller)
                        }
                        return false
                    }), in: .current)
                }
                reveal()
            })
            self.present(controller, in: .window(.root))
        }, playMessageEffect: { [weak self] message in
            guard let self else {
                return
            }
            self.playMessageEffect(message: message)
        }, editMessageFactCheck: { [weak self] messageId in
            guard let self else {
                return
            }
            self.openEditMessageFactCheck(messageId: messageId)
        }, requestMessageUpdate: { [weak self] id, scroll in
            if let self {
                self.chatDisplayNode.historyNode.requestMessageUpdate(id, andScrollToItem: scroll)
            }
        }, cancelInteractiveKeyboardGestures: { [weak self] in
            if let self {
                (self.view.window as? WindowHost)?.cancelInteractiveKeyboardGestures()
                self.chatDisplayNode.cancelInteractiveKeyboardGestures()
            }
        }, dismissTextInput: { [weak self] in
            self?.chatDisplayNode.dismissTextInput()
        }, scrollToMessageId: { [weak self] index in
            self?.chatDisplayNode.historyNode.scrollToMessage(index: index)
        }, navigateToStory: { [weak self] message, storyId in
            guard let self else {
                return
            }
            if let story = message.associatedStories[storyId], story.data.isEmpty {
                self.present(UndoOverlayController(presentationData: self.presentationData, content:  .universal(animation: "story_expired", scale: 0.066, colors: [:], title: nil, text: self.presentationData.strings.Story_TooltipExpired, customUndoText: nil, timeout: nil), elevatedLayout: false, action: { _ in return true }), in: .current)
                return
            }
            
            let storyContent = SingleStoryContentContextImpl(context: self.context, storyId: storyId, readGlobally: true)
            let _ = (storyContent.state
            |> take(1)
            |> deliverOnMainQueue).startStandalone(next: { [weak self] _ in
                guard let self else {
                    return
                }
                
                var transitionIn: StoryContainerScreen.TransitionIn?
                for i in 0 ..< 2 {
                    if transitionIn != nil {
                        break
                    }
                    self.chatDisplayNode.historyNode.forEachItemNode { itemNode in
                        if let itemNode = itemNode as? ChatMessageItemView {
                            if i == 0 {
                                if itemNode.item?.message.id != message.id {
                                    return
                                }
                            }
                            
                            if let result = itemNode.targetForStoryTransition(id: storyId) {
                                transitionIn = StoryContainerScreen.TransitionIn(
                                    sourceView: result,
                                    sourceRect: result.bounds,
                                    sourceCornerRadius: 6.0,
                                    sourceIsAvatar: false
                                )
                            }
                        }
                    }
                }
                
                let storyContainerScreen = StoryContainerScreen(
                    context: self.context,
                    content: storyContent,
                    transitionIn: transitionIn,
                    transitionOut: { [weak self] peerId, storyIdValue in
                        guard let self, let storyIdId = storyIdValue.base as? Int32 else {
                            return nil
                        }
                        let storyId = StoryId(peerId: peerId, id: storyIdId)
                        
                        var transitionOut: StoryContainerScreen.TransitionOut?
                        for i in 0 ..< 2 {
                            if transitionOut != nil {
                                break
                            }
                            self.chatDisplayNode.historyNode.forEachItemNode { itemNode in
                                if let itemNode = itemNode as? ChatMessageItemView {
                                    if i == 0 {
                                        if itemNode.item?.message.id != message.id {
                                            return
                                        }
                                    }
                                    
                                    if let result = itemNode.targetForStoryTransition(id: storyId) {
                                        result.isHidden = true
                                        transitionOut = StoryContainerScreen.TransitionOut(
                                            destinationView: result,
                                            transitionView: StoryContainerScreen.TransitionView(
                                                makeView: { [weak result] in
                                                    let parentView = UIView()
                                                    if let copyView = result?.snapshotContentTree(unhide: true) {
                                                        parentView.addSubview(copyView)
                                                    }
                                                    return parentView
                                                },
                                                updateView: { copyView, state, transition in
                                                    guard let view = copyView.subviews.first else {
                                                        return
                                                    }
                                                    let size = state.sourceSize.interpolate(to: state.destinationSize, amount: state.progress)
                                                    transition.setPosition(view: view, position: CGPoint(x: size.width * 0.5, y: size.height * 0.5))
                                                    transition.setScale(view: view, scale: size.width / state.destinationSize.width)
                                                },
                                                insertCloneTransitionView: nil
                                            ),
                                            destinationRect: result.bounds,
                                            destinationCornerRadius: 2.0,
                                            destinationIsAvatar: false,
                                            completed: { [weak result] in
                                                result?.isHidden = false
                                            }
                                        )
                                    }
                                }
                            }
                        }
                        
                        return transitionOut
                    }
                )
                self.push(storyContainerScreen)
            })
        }, attemptedNavigationToPrivateQuote: { [weak self] peer in
            guard let self else {
                return
            }
            let text: String
            if let peer = peer as? TelegramChannel {
                if case .broadcast = peer.info {
                    text = self.presentationData.strings.Chat_ToastQuoteChatUnavailbalePrivateChannel
                } else {
                    text = self.presentationData.strings.Chat_ToastQuoteChatUnavailbalePrivateGroup
                }
            } else if peer is TelegramGroup {
                text = self.presentationData.strings.Chat_ToastQuoteChatUnavailbalePrivateGroup
            } else {
                text = self.presentationData.strings.Chat_ToastQuoteChatUnavailbalePrivateChat
            }
            self.controllerInteraction?.displayUndo(.info(title: nil, text: text, timeout: nil, customUndoText: nil))
        }, forceUpdateWarpContents: { [weak self] in
            guard let self else {
                return
            }
            self.chatDisplayNode.forceUpdateWarpContents()
        }, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: self.stickerSettings, presentationContext: ChatPresentationContext(context: context, backgroundNode: self.chatBackgroundNode))
        controllerInteraction.enableFullTranslucency = context.sharedContext.energyUsageSettings.fullTranslucency
        
        self.controllerInteraction = controllerInteraction
        
        //if chatLocation.threadId == nil {
            if let peerId = chatLocation.peerId, peerId != context.account.peerId {
                switch subject {
                case .pinnedMessages, .scheduledMessages, .messageOptions:
                    break
                default:
                    self.navigationBar?.userInfo = PeerInfoNavigationSourceTag(peerId: peerId)
                }
            }
            self.navigationBar?.allowsCustomTransition = { [weak self] in
                guard let strongSelf = self else {
                    return false
                }
                if strongSelf.navigationBar?.userInfo == nil {
                    return false
                }
                if strongSelf.navigationBar?.contentNode != nil {
                    return false
                }
                return true
            }
        //}
        
        self.chatTitleView = ChatTitleView(context: self.context, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, animationCache: controllerInteraction.presentationContext.animationCache, animationRenderer: controllerInteraction.presentationContext.animationRenderer)
        
        if case .messageOptions = self.subject {
            self.chatTitleView?.disableAnimations = true
        }
        
        self.navigationItem.titleView = self.chatTitleView
        self.chatTitleView?.longPressed = { [weak self] in
            if let strongSelf = self, let peerView = strongSelf.peerView, let peer = peerView.peers[peerView.peerId], peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil && !strongSelf.presentationInterfaceState.isNotAccessible {
                if case .standard(.previewing) = strongSelf.mode {
                } else {
                    strongSelf.interfaceInteraction?.beginMessageSearch(.everything, "")
                }
            }
        }
        
        let chatInfoButtonItem: UIBarButtonItem
        switch chatLocation {
        case .peer, .replyThread:
            let avatarNode = ChatAvatarNavigationNode()
            avatarNode.contextAction = { [weak self] node, gesture in
                guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer else {
                    return
                }
                
                let items: Signal<[ContextMenuItem], NoError>
                switch chatLocation {
                case .peer:
                    items = context.engine.data.get(
                        TelegramEngine.EngineData.Item.Peer.CanViewStats(id: peer.id)
                    )
                    |> map { canViewStats -> [ContextMenuItem] in
                        var items: [ContextMenuItem] = []
                        
                        let openText = strongSelf.presentationData.strings.Conversation_ContextMenuOpenProfile
                        items.append(.action(ContextMenuActionItem(text: openText, icon: { theme in
                            return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.actionSheet.primaryTextColor)
                        }, action: { _, f in
                            f(.dismissWithoutContent)
                            self?.navigationButtonAction(.openChatInfo(expandAvatar: true, recommendedChannels: false))
                        })))
                        
                        if canViewStats {
                            items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChannelInfo_Stats, icon: { theme in
                                return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Statistics"), color: theme.actionSheet.primaryTextColor)
                            }, action: { _, f in
                                f(.dismissWithoutContent)
                                guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer else {
                                    return
                                }
                                strongSelf.view.endEditing(true)
                                
                                let statsController: ViewController
                                if let channel = peer as? TelegramChannel, case .group = channel.info {
                                    statsController = groupStatsController(context: context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peer.id)
                                } else {
                                    statsController = channelStatsController(context: context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peer.id)
                                }
                                strongSelf.push(statsController)
                            })))
                        }
                        items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_Search, icon: { theme in
                            return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Search"), color: theme.actionSheet.primaryTextColor)
                        }, action: { _, f in
                            f(.dismissWithoutContent)
                            self?.interfaceInteraction?.beginMessageSearch(.everything, "")
                        })))
                                                
                        return items
                    }
                case let .replyThread(message):
                    let peerId = peer.id
                    let threadId = message.threadId
                    
                    items = context.engine.data.get(
                        TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: peerId),
                        TelegramEngine.EngineData.Item.Peer.ThreadData(id: peer.id, threadId: threadId),
                        TelegramEngine.EngineData.Item.NotificationSettings.Global()
                    )
                    |> map { peerNotificationSettings, threadData, globalNotificationSettings -> [ContextMenuItem] in
                        guard let channel = peer as? TelegramChannel else {
                            return []
                        }
                        guard let threadData = threadData else {
                            return []
                        }
                        
                        var items: [ContextMenuItem] = []
                        
                        var isMuted = false
                        switch threadData.notificationSettings.muteState {
                        case .muted:
                            isMuted = true
                        case .unmuted:
                            isMuted = false
                        case .default:
                            var peerIsMuted = false
                            if case let .muted(until) = peerNotificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) {
                                peerIsMuted = true
                            } else if case .default = peerNotificationSettings.muteState {
                                if case .group = channel.info {
                                    peerIsMuted = !globalNotificationSettings.groupChats.enabled
                                }
                            }
                            isMuted = peerIsMuted
                        }
                        
                        if !"".isEmpty {
                            items.append(.action(ContextMenuActionItem(text: isMuted ? presentationData.strings.ChatList_Context_Unmute : presentationData.strings.ChatList_Context_Mute, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in
                                if isMuted {
                                    let _ = (context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: 0)
                                             |> deliverOnMainQueue).startStandalone(completed: {
                                        f(.default)
                                    })
                                } else {
                                    var items: [ContextMenuItem] = []
                                    
                                    items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_MuteFor, icon: { theme in
                                        return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Mute2d"), color: theme.contextMenu.primaryColor)
                                    }, action: { c, _ in
                                        var subItems: [ContextMenuItem] = []
                                        
                                        let presetValues: [Int32] = [
                                            1 * 60 * 60,
                                            8 * 60 * 60,
                                            1 * 24 * 60 * 60,
                                            7 * 24 * 60 * 60
                                        ]
                                        
                                        for value in presetValues {
                                            subItems.append(.action(ContextMenuActionItem(text: muteForIntervalString(strings: presentationData.strings, value: value), icon: { _ in
                                                return nil
                                            }, action: { _, f in
                                                f(.default)
                                                
                                                let _ = context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: value).startStandalone()
                                                
                                                self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_mute_for", scale: 0.066, colors: [:], title: nil, text: presentationData.strings.PeerInfo_TooltipMutedFor(mutedForTimeIntervalString(strings: presentationData.strings, value: value)).string, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
                                            })))
                                        }
                                        
                                        subItems.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_MuteForCustom, icon: { _ in
                                            return nil
                                        }, action: { _, f in
                                            f(.default)
                                            
                                            //                                        if let chatListController = chatListController {
                                            //                                            openCustomMute(context: context, peerId: peerId, threadId: threadId, baseController: chatListController)
                                            //                                        }
                                        })))
                                        
                                        c?.setItems(.single(ContextController.Items(content: .list(subItems))), minHeight: nil, animated: true)
                                    })))
                                    
                                    items.append(.separator)
                                    
                                    var isSoundEnabled = true
                                    switch threadData.notificationSettings.messageSound {
                                    case .none:
                                        isSoundEnabled = false
                                    default:
                                        break
                                    }
                                    
                                    if case .muted = threadData.notificationSettings.muteState {
                                        items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_ButtonUnmute, icon: { theme in
                                            return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/SoundOn"), color: theme.contextMenu.primaryColor)
                                        }, action: { _, f in
                                            f(.default)
                                            
                                            let _ = context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: nil).startStandalone()
                                            
                                            let iconColor: UIColor = .white
                                            self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_profileunmute", scale: 0.075, colors: [
                                                "Middle.Group 1.Fill 1": iconColor,
                                                "Top.Group 1.Fill 1": iconColor,
                                                "Bottom.Group 1.Fill 1": iconColor,
                                                "EXAMPLE.Group 1.Fill 1": iconColor,
                                                "Line.Group 1.Stroke 1": iconColor
                                            ], title: nil, text: presentationData.strings.PeerInfo_TooltipUnmuted, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
                                        })))
                                    } else if !isSoundEnabled {
                                        items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_EnableSound, icon: { theme in
                                            return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/SoundOn"), color: theme.contextMenu.primaryColor)
                                        }, action: { _, f in
                                            f(.default)
                                            
                                            let _ = context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: threadId, sound: .default).startStandalone()
                                            
                                            self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_sound_on", scale: 0.056, colors: [:], title: nil, text: presentationData.strings.PeerInfo_TooltipSoundEnabled, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
                                        })))
                                    } else {
                                        items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_DisableSound, icon: { theme in
                                            return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/SoundOff"), color: theme.contextMenu.primaryColor)
                                        }, action: { _, f in
                                            f(.default)
                                            
                                            let _ = context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: threadId, sound: .none).startStandalone()
                                            
                                            self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_sound_off", scale: 0.056, colors: [:], title: nil, text: presentationData.strings.PeerInfo_TooltipSoundDisabled, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
                                        })))
                                    }
                                    
                                    items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_NotificationsCustomize, icon: { theme in
                                        return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Customize"), color: theme.contextMenu.primaryColor)
                                    }, action: { _, f in
                                        f(.dismissWithoutContent)
                                        
                                        let _ = (context.engine.data.get(
                                            TelegramEngine.EngineData.Item.NotificationSettings.Global()
                                        )
                                                 |> deliverOnMainQueue).startStandalone(next: { globalSettings in
                                            let updatePeerSound: (PeerId, PeerMessageSound) -> Signal<Void, NoError> = { peerId, sound in
                                                return context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: threadId, sound: sound) |> deliverOnMainQueue
                                            }
                                            
                                            let updatePeerNotificationInterval: (PeerId, Int32?) -> Signal<Void, NoError> = { peerId, muteInterval in
                                                return context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: muteInterval) |> deliverOnMainQueue
                                            }
                                            
                                            let updatePeerDisplayPreviews: (PeerId, PeerNotificationDisplayPreviews) -> Signal<Void, NoError> = {
                                                peerId, displayPreviews in
                                                return context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peerId, threadId: threadId, displayPreviews: displayPreviews) |> deliverOnMainQueue
                                            }
                                            
                                            let updatePeerStoriesMuted: (PeerId, PeerStoryNotificationSettings.Mute) -> Signal<Void, NoError> = {
                                                peerId, mute in
                                                return context.engine.peers.updatePeerStoriesMutedSetting(peerId: peerId, mute: mute) |> deliverOnMainQueue
                                            }
                                            
                                            let updatePeerStoriesHideSender: (PeerId, PeerStoryNotificationSettings.HideSender) -> Signal<Void, NoError> = {
                                                peerId, hideSender in
                                                return context.engine.peers.updatePeerStoriesHideSenderSetting(peerId: peerId, hideSender: hideSender) |> deliverOnMainQueue
                                            }
                                            
                                            let updatePeerStorySound: (PeerId, PeerMessageSound) -> Signal<Void, NoError> = { peerId, sound in
                                                return context.engine.peers.updatePeerStorySoundInteractive(peerId: peerId, sound: sound) |> deliverOnMainQueue
                                            }
                                            
                                            let defaultSound: PeerMessageSound
                                            
                                            if case .broadcast = channel.info {
                                                defaultSound = globalSettings.channels.sound._asMessageSound()
                                            } else {
                                                defaultSound = globalSettings.groupChats.sound._asMessageSound()
                                            }
                                            
                                            let canRemove = false
                                            
                                            let exceptionController = notificationPeerExceptionController(context: context, updatedPresentationData: nil, peer: .channel(channel), threadId: threadId, isStories: nil, canRemove: canRemove, defaultSound: defaultSound, defaultStoriesSound: defaultSound, edit: true, updatePeerSound: { peerId, sound in
                                                let _ = (updatePeerSound(peerId, sound)
                                                         |> deliverOnMainQueue).startStandalone(next: { _ in
                                                })
                                            }, updatePeerNotificationInterval: { [weak self] peerId, muteInterval in
                                                let _ = (updatePeerNotificationInterval(peerId, muteInterval)
                                                         |> deliverOnMainQueue).startStandalone(next: { _ in
                                                    if let muteInterval = muteInterval, muteInterval == Int32.max {
                                                        let iconColor: UIColor = .white
                                                        self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_profilemute", scale: 0.075, colors: [
                                                            "Middle.Group 1.Fill 1": iconColor,
                                                            "Top.Group 1.Fill 1": iconColor,
                                                            "Bottom.Group 1.Fill 1": iconColor,
                                                            "EXAMPLE.Group 1.Fill 1": iconColor,
                                                            "Line.Group 1.Stroke 1": iconColor
                                                        ], title: nil, text: presentationData.strings.PeerInfo_TooltipMutedForever, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
                                                    }
                                                })
                                            }, updatePeerDisplayPreviews: { peerId, displayPreviews in
                                                let _ = (updatePeerDisplayPreviews(peerId, displayPreviews)
                                                         |> deliverOnMainQueue).startStandalone(next: { _ in
                                                    
                                                })
                                            }, updatePeerStoriesMuted: { peerId, mute in
                                                let _ = (updatePeerStoriesMuted(peerId, mute)
                                                         |> deliverOnMainQueue).startStandalone()
                                            }, updatePeerStoriesHideSender: { peerId, hideSender in
                                                let _ = (updatePeerStoriesHideSender(peerId, hideSender)
                                                         |> deliverOnMainQueue).startStandalone()
                                            }, updatePeerStorySound: { peerId, sound in
                                                let _ = (updatePeerStorySound(peerId, sound)
                                                         |> deliverOnMainQueue).startStandalone()
                                            }, removePeerFromExceptions: {
                                            }, modifiedPeer: {
                                            })
                                            exceptionController.navigationPresentation = .modal
                                            self?.push(exceptionController)
                                        })
                                    })))
                                    
                                    items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_MuteForever, textColor: .destructive, icon: { theme in
                                        return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Muted"), color: theme.contextMenu.destructiveColor)
                                    }, action: { _, f in
                                        f(.default)
                                        
                                        let _ = context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: Int32.max).startStandalone()
                                        
                                        let iconColor: UIColor = .white
                                        self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_profilemute", scale: 0.075, colors: [
                                            "Middle.Group 1.Fill 1": iconColor,
                                            "Top.Group 1.Fill 1": iconColor,
                                            "Bottom.Group 1.Fill 1": iconColor,
                                            "EXAMPLE.Group 1.Fill 1": iconColor,
                                            "Line.Group 1.Stroke 1": iconColor
                                        ], title: nil, text: presentationData.strings.PeerInfo_TooltipMutedForever, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
                                    })))
                                    
                                    c?.setItems(.single(ContextController.Items(content: .list(items))), minHeight: nil, animated: true)
                                }
                            })))
                        }
                        
                        items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_Search, icon: { theme in
                            return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Search"), color: theme.actionSheet.primaryTextColor)
                        }, action: { _, f in
                            f(.dismissWithoutContent)
                            self?.interfaceInteraction?.beginMessageSearch(.everything, "")
                        })))
                        
                        if threadId != 1 {
                            var canOpenClose = false
                            if channel.flags.contains(.isCreator) {
                                canOpenClose = true
                            } else if channel.hasPermission(.manageTopics) {
                                canOpenClose = true
                            } else if threadData.isOwnedByMe {
                                canOpenClose = true
                            }
                            if canOpenClose {
                                items.append(.action(ContextMenuActionItem(text: threadData.isClosed ? presentationData.strings.ChatList_Context_ReopenTopic : presentationData.strings.ChatList_Context_CloseTopic, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: threadData.isClosed ? "Chat/Context Menu/Play": "Chat/Context Menu/Pause"), color: theme.contextMenu.primaryColor) }, action: { _, f in
                                    f(.default)
                                    
                                    let _ = context.engine.peers.setForumChannelTopicClosed(id: peer.id, threadId: threadId, isClosed: !threadData.isClosed).startStandalone()
                                })))
                            }
                        }

                        return items
                    }
                default:
                    items = .single([])
                }
                
                strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts()
                
                strongSelf.canReadHistory.set(false)
                
                let source: ContextContentSource
                if let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer, peer.smallProfileImage != nil {
                    let galleryController = AvatarGalleryController(context: strongSelf.context, peer: EnginePeer(peer), remoteEntries: nil, replaceRootController: { controller, ready in
                    }, synchronousLoad: true)
                    galleryController.setHintWillBePresentedInPreviewingContext(true)
                    source = .controller(ChatContextControllerContentSourceImpl(controller: galleryController, sourceNode: node, passthroughTouches: false))
                } else {
                    source = .reference(ChatControllerContextReferenceContentSource(controller: strongSelf, sourceView: node.view, insets: .zero))
                }
                
                let contextController = ContextController(presentationData: strongSelf.presentationData, source: source, items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
                contextController.dismissed = { [weak self] in
                    self?.canReadHistory.set(true)
                }
                strongSelf.presentInGlobalOverlay(contextController)
            }
            
            chatInfoButtonItem = UIBarButtonItem(customDisplayNode: avatarNode)!
            self.avatarNode = avatarNode
        case .customChatContents:
            chatInfoButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
        }
        chatInfoButtonItem.target = self
        chatInfoButtonItem.action = #selector(self.rightNavigationButtonAction)
        self.chatInfoNavigationButton = ChatNavigationButton(action: .openChatInfo(expandAvatar: true, recommendedChannels: false), buttonItem: chatInfoButtonItem)
        
        self.moreBarButton.setContent(.more(MoreHeaderButton.optionsCircleImage(color: self.presentationData.theme.rootController.navigationBar.buttonColor)))
        self.moreInfoNavigationButton = ChatNavigationButton(action: .toggleInfoPanel, buttonItem: UIBarButtonItem(customDisplayNode: self.moreBarButton)!)
        self.moreBarButton.contextAction = { [weak self] sourceNode, gesture in
            guard let self else {
                return
            }
            guard case let .peer(peerId) = self.chatLocation else {
                return
            }
            
            if peerId == self.context.account.peerId {
                PeerInfoScreenImpl.openSavedMessagesMoreMenu(context: self.context, sourceController: self, isViewingAsTopics: false, sourceView: sourceNode.view, gesture: gesture)
            } else {
                ChatListControllerImpl.openMoreMenu(context: self.context, peerId: peerId, sourceController: self, isViewingAsTopics: false, sourceView: sourceNode.view, gesture: gesture)
            }
        }
        self.moreBarButton.addTarget(self, action: #selector(self.moreButtonPressed), forControlEvents: .touchUpInside)
        
        self.navigationItem.titleView = self.chatTitleView
        self.chatTitleView?.pressed = { [weak self] in
            self?.navigationButtonAction(.openChatInfo(expandAvatar: false, recommendedChannels: false))
        }
        
        self.updateChatPresentationInterfaceState(animated: false, interactive: false, { state in
            if let botStart = botStart, case .interactive = botStart.behavior {
                return state.updatedBotStartPayload(botStart.payload)
            } else {
                return state
            }
        })
        
        let chatLocationPeerId: PeerId? = chatLocation.peerId
        
        self.accountPeerDisposable = (context.account.postbox.peerView(id: context.account.peerId)
        |> deliverOnMainQueue).startStrict(next: { [weak self] peerView in
            if let strongSelf = self {
                let isPremium = peerView.peers[peerView.peerId]?.isPremium ?? false
                strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: false, { state in
                    return state.updatedIsPremium(isPremium)
                })
            }
        })
        
        if let chatPeerId = chatLocation.peerId {
            self.nameColorDisposable = (context.engine.data.subscribe(
                TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId),
                TelegramEngine.EngineData.Item.Peer.Peer(id: chatPeerId)
            )
            |> deliverOnMainQueue).start(next: { [weak self] accountPeer, chatPeer in
                guard let self, let accountPeer, let chatPeer else {
                    return
                }
                var nameColor: PeerNameColor?
                if case let .channel(channel) = chatPeer, case .broadcast = channel.info {
                    nameColor = chatPeer.nameColor
                } else {
                    nameColor = accountPeer.nameColor
                }
                var accountPeerColor: ChatPresentationInterfaceState.AccountPeerColor?
                if let nameColor {
                    let colors = self.context.peerNameColors.get(nameColor)
                    var style: ChatPresentationInterfaceState.AccountPeerColor.Style = .solid
                    if colors.tertiary != nil {
                        style = .tripleDashed
                    } else if colors.secondary != nil {
                        style = .doubleDashed
                    }
                    accountPeerColor = ChatPresentationInterfaceState.AccountPeerColor(style: style)
                }
                self.updateChatPresentationInterfaceState(animated: false, interactive: false, { state in
                    return state.updatedAccountPeerColor(accountPeerColor)
                })
            })
        }
        
        let managingBot: Signal<ChatManagingBot?, NoError>
        if let peerId = self.chatLocation.peerId, peerId.namespace == Namespaces.Peer.CloudUser {
            managingBot = self.context.engine.data.subscribe(
                TelegramEngine.EngineData.Item.Peer.ChatManagingBot(id: peerId)
            )
            |> mapToSignal { result -> Signal<ChatManagingBot?, NoError> in
                guard let result else {
                    return .single(nil)
                }
                return context.engine.data.subscribe(
                    TelegramEngine.EngineData.Item.Peer.Peer(id: result.id)
                )
                |> map { botPeer -> ChatManagingBot? in
                    guard let botPeer else {
                        return nil
                    }
                    
                    return ChatManagingBot(bot: botPeer, isPaused: result.isPaused, canReply: result.canReply, settingsUrl: result.manageUrl)
                }
            }
            |> distinctUntilChanged
        } else {
            managingBot = .single(nil)
        }
        
        do {
            let peerId = chatLocationPeerId
            if case let .peer(peerView) = self.chatLocationInfoData, let peerId = peerId {
                peerView.set(context.account.viewTracker.peerView(peerId))
                var onlineMemberCount: Signal<Int32?, NoError> = .single(nil)
                var hasScheduledMessages: Signal<Bool, NoError> = .single(false)
                
                if peerId.namespace == Namespaces.Peer.CloudChannel {
                    let recentOnlineSignal: Signal<Int32?, NoError> = peerView.get()
                    |> map { view -> Bool? in
                        if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel {
                            if case .broadcast = peer.info {
                                return nil
                            } else if let memberCount = cachedData.participantsSummary.memberCount, memberCount > 50 {
                                return true
                            } else {
                                return false
                            }
                        } else {
                            return false
                        }
                    }
                    |> distinctUntilChanged
                    |> mapToSignal { isLarge -> Signal<Int32?, NoError> in
                        if let isLarge = isLarge {
                            if isLarge {
                                return context.peerChannelMemberCategoriesContextsManager.recentOnline(account: context.account, accountPeerId: context.account.peerId, peerId: peerId)
                                |> map(Optional.init)
                            } else {
                                return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId)
                                |> map(Optional.init)
                            }
                        } else {
                            return .single(nil)
                        }
                    }
                    onlineMemberCount = recentOnlineSignal
                    
                    self.reportIrrelvantGeoNoticePromise.set(context.engine.data.get(TelegramEngine.EngineData.Item.Notices.Notice(key: ApplicationSpecificNotice.irrelevantPeerGeoReportKey(peerId: peerId)))
                    |> map { entry -> Bool? in
                        if let _ = entry?.get(ApplicationSpecificBoolNotice.self) {
                            return true
                        } else {
                            return false
                        }
                    })
                } else {
                    self.reportIrrelvantGeoNoticePromise.set(.single(nil))
                }
                
                var isScheduledOrPinnedMessages = false
                switch subject {
                case .scheduledMessages, .pinnedMessages, .messageOptions:
                    isScheduledOrPinnedMessages = true
                default:
                    break
                }
                
                if chatLocation.peerId != nil, !isScheduledOrPinnedMessages, peerId.namespace != Namespaces.Peer.SecretChat {
                    let chatLocationContextHolder = self.chatLocationContextHolder
                    hasScheduledMessages = peerView.get()
                    |> take(1)
                    |> mapToSignal { view -> Signal<Bool, NoError> in
                        if let peer = peerViewMainPeer(view) as? TelegramChannel, !peer.hasPermission(.sendSomething) {
                            return .single(false)
                        } else {
                            return context.account.viewTracker.scheduledMessagesViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder))
                            |> map { view, _, _ in
                                return !view.entries.isEmpty
                            }
                        }
                    }
                }
                
                var displayedCountSignal: Signal<Int?, NoError> = .single(nil)
                var subtitleTextSignal: Signal<String?, NoError> = .single(nil)
                if case .pinnedMessages = subject {
                    displayedCountSignal = self.topPinnedMessageSignal(latest: true)
                    |> map { message -> Int? in
                        return message?.totalCount
                    }
                    |> distinctUntilChanged
                } else if case let .messageOptions(peerIds, messageIds, info) = subject {
                    displayedCountSignal = self.presentationInterfaceStatePromise.get()
                    |> map { state -> Int? in
                        if let selectionState = state.interfaceState.selectionState {
                            return selectionState.selectedIds.count
                        } else {
                            return messageIds.count
                        }
                    }
                    |> distinctUntilChanged
                    
                    let peers = self.context.account.postbox.multiplePeersView(peerIds)
                    |> take(1)
                    
                    let presentationData = self.presentationData
                    
                    switch info {
                    case let .forward(forward):
                        subtitleTextSignal = combineLatest(peers, forward.options, displayedCountSignal)
                        |> map { peersView, options, count in
                            let peers = peersView.peers.values
                            if !peers.isEmpty {
                                if peers.count == 1, let peer = peers.first {
                                    if let peer = peer as? TelegramUser {
                                        let displayName = EnginePeer(peer).compactDisplayTitle
                                        if count == 1 {
                                            if options.hideNames {
                                                return presentationData.strings.Conversation_ForwardOptions_UserMessageForwardHidden(displayName).string
                                            } else {
                                                return presentationData.strings.Conversation_ForwardOptions_UserMessageForwardVisible(displayName).string
                                            }
                                        } else {
                                            if options.hideNames {
                                                return presentationData.strings.Conversation_ForwardOptions_UserMessagesForwardHidden(displayName).string
                                            } else {
                                                return presentationData.strings.Conversation_ForwardOptions_UserMessagesForwardVisible(displayName).string
                                            }
                                        }
                                    } else if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
                                        if count == 1 {
                                            if options.hideNames {
                                                return presentationData.strings.Conversation_ForwardOptions_ChannelMessageForwardHidden
                                            } else {
                                                return presentationData.strings.Conversation_ForwardOptions_ChannelMessageForwardVisible
                                            }
                                        } else {
                                            if options.hideNames {
                                                return presentationData.strings.Conversation_ForwardOptions_ChannelMessagesForwardHidden
                                            } else {
                                                return presentationData.strings.Conversation_ForwardOptions_ChannelMessagesForwardVisible
                                            }
                                        }
                                    } else {
                                        if count == 1 {
                                            if options.hideNames {
                                                return presentationData.strings.Conversation_ForwardOptions_GroupMessageForwardHidden
                                            } else {
                                                return presentationData.strings.Conversation_ForwardOptions_GroupMessageForwardVisible
                                            }
                                        } else {
                                            if options.hideNames {
                                                return presentationData.strings.Conversation_ForwardOptions_GroupMessagesForwardHidden
                                            } else {
                                                return presentationData.strings.Conversation_ForwardOptions_GroupMessagesForwardVisible
                                            }
                                        }
                                    }
                                } else {
                                    if count == 1 {
                                        if options.hideNames {
                                            return presentationData.strings.Conversation_ForwardOptions_RecipientsMessageForwardHidden
                                        } else {
                                            return presentationData.strings.Conversation_ForwardOptions_RecipientsMessageForwardVisible
                                        }
                                    } else {
                                        if options.hideNames {
                                            return presentationData.strings.Conversation_ForwardOptions_RecipientsMessagesForwardHidden
                                        } else {
                                            return presentationData.strings.Conversation_ForwardOptions_RecipientsMessagesForwardVisible
                                        }
                                    }
                                }
                            } else {
                                return nil
                            }
                        }
                    case let .reply(reply):
                        subtitleTextSignal = reply.selectionState.get()
                        |> map { selectionState -> String? in
                            if !selectionState.canQuote {
                                return nil
                            }
                            return presentationData.strings.Chat_SubtitleQuoteSelectionTip
                        }
                    case let .link(link):
                        subtitleTextSignal = link.options
                        |> map { options -> String? in
                            if options.hasAlternativeLinks {
                                return presentationData.strings.Chat_SubtitleLinkListTip
                            } else {
                                return nil
                            }
                        }
                        |> distinctUntilChanged
                    }
                }
                
                let hasPeerInfo: Signal<Bool, NoError>
                if peerId == context.account.peerId {
                    hasPeerInfo = .single(true)
                    |> then(
                        hasAvailablePeerInfoMediaPanes(context: context, peerId: peerId)
                    )
                } else {
                    hasPeerInfo = .single(true)
                }
                
                enum MessageOptionsTitleInfo {
                    case reply(hasQuote: Bool)
                }
                let messageOptionsTitleInfo: Signal<MessageOptionsTitleInfo?, NoError>
                if case let .messageOptions(_, _, info) = self.subject {
                    switch info {
                    case .forward, .link:
                        messageOptionsTitleInfo = .single(nil)
                    case let .reply(reply):
                        messageOptionsTitleInfo = reply.selectionState.get()
                        |> map { selectionState -> Bool in
                            return selectionState.quote != nil
                        }
                        |> distinctUntilChanged
                        |> map { hasQuote -> MessageOptionsTitleInfo in
                            return .reply(hasQuote: hasQuote)
                        }
                    }
                } else {
                    messageOptionsTitleInfo = .single(nil)
                }
                                  
                self.titleDisposable.set((combineLatest(queue: Queue.mainQueue(), peerView.get(), onlineMemberCount, displayedCountSignal, subtitleTextSignal, self.presentationInterfaceStatePromise.get(), hasPeerInfo, messageOptionsTitleInfo)
                |> deliverOnMainQueue).startStrict(next: { [weak self] peerView, onlineMemberCount, displayedCount, subtitleText, presentationInterfaceState, hasPeerInfo, messageOptionsTitleInfo in
                    if let strongSelf = self {
                        var isScheduledMessages = false
                        if case .scheduledMessages = presentationInterfaceState.subject {
                            isScheduledMessages = true
                        }
                        
                        if case let .messageOptions(_, _, info) = presentationInterfaceState.subject {
                            if case .reply = info {
                                let titleContent: ChatTitleContent
                                if case let .reply(hasQuote) = messageOptionsTitleInfo, hasQuote {
                                    titleContent = .custom(presentationInterfaceState.strings.Chat_TitleQuoteSelection, subtitleText, false)
                                } else {
                                    titleContent = .custom(presentationInterfaceState.strings.Chat_TitleReply, subtitleText, false)
                                }
                                if strongSelf.chatTitleView?.titleContent != titleContent {
                                    if strongSelf.chatTitleView?.titleContent != nil {
                                        strongSelf.chatTitleView?.animateLayoutTransition()
                                    }
                                    strongSelf.chatTitleView?.titleContent = titleContent
                                }
                            } else if case .link = info {
                                strongSelf.chatTitleView?.titleContent = .custom(presentationInterfaceState.strings.Chat_TitleLinkOptions, subtitleText, false)
                            } else if displayedCount == 1 {
                                strongSelf.chatTitleView?.titleContent = .custom(presentationInterfaceState.strings.Conversation_ForwardOptions_ForwardTitleSingle, subtitleText, false)
                            } else {
                                strongSelf.chatTitleView?.titleContent = .custom(presentationInterfaceState.strings.Conversation_ForwardOptions_ForwardTitle(Int32(displayedCount ?? 1)), subtitleText, false)
                            }
                        } else if let selectionState = presentationInterfaceState.interfaceState.selectionState {
                            if selectionState.selectedIds.count > 0 {
                                strongSelf.chatTitleView?.titleContent = .custom(presentationInterfaceState.strings.Conversation_SelectedMessages(Int32(selectionState.selectedIds.count)), nil, false)
                            } else {
                                if let reportReason = presentationInterfaceState.reportReason {
                                    let title: String
                                    switch reportReason {
                                        case .spam:
                                            title = presentationInterfaceState.strings.ReportPeer_ReasonSpam
                                        case .fake:
                                            title = presentationInterfaceState.strings.ReportPeer_ReasonFake
                                        case .violence:
                                            title = presentationInterfaceState.strings.ReportPeer_ReasonViolence
                                        case .porno:
                                            title = presentationInterfaceState.strings.ReportPeer_ReasonPornography
                                        case .childAbuse:
                                            title = presentationInterfaceState.strings.ReportPeer_ReasonChildAbuse
                                        case .copyright:
                                            title = presentationInterfaceState.strings.ReportPeer_ReasonCopyright
                                        case .illegalDrugs:
                                            title = presentationInterfaceState.strings.ReportPeer_ReasonIllegalDrugs
                                        case .personalDetails:
                                            title = presentationInterfaceState.strings.ReportPeer_ReasonPersonalDetails
                                        case .custom:
                                            title = presentationInterfaceState.strings.ReportPeer_ReasonOther
                                        case .irrelevantLocation:
                                            title = ""
                                    }
                                    strongSelf.chatTitleView?.titleContent = .custom(title, presentationInterfaceState.strings.Conversation_SelectMessages, false)
                                } else {
                                    strongSelf.chatTitleView?.titleContent = .custom(presentationInterfaceState.strings.Conversation_SelectMessages, nil, false)
                                }
                            }
                        } else if let peer = peerViewMainPeer(peerView) {
                            if case .pinnedMessages = presentationInterfaceState.subject {
                                strongSelf.chatTitleView?.titleContent = .custom(presentationInterfaceState.strings.Chat_TitlePinnedMessages(Int32(displayedCount ?? 1)), nil, false)
                            } else {
                                strongSelf.chatTitleView?.titleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages, isMuted: nil, customMessageCount: nil, isEnabled: hasPeerInfo)
                                let imageOverride: AvatarNodeImageOverride?
                                if strongSelf.context.account.peerId == peer.id {
                                    imageOverride = .savedMessagesIcon
                                } else if peer.id.isReplies {
                                    imageOverride = .repliesIcon
                                } else if peer.id.isAnonymousSavedMessages {
                                    imageOverride = .anonymousSavedMessagesIcon(isColored: true)
                                } else if peer.isDeleted {
                                    imageOverride = .deletedIcon
                                } else {
                                    imageOverride = nil
                                }
                                (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.setPeer(context: strongSelf.context, theme: strongSelf.presentationData.theme, peer: EnginePeer(peer), overrideImage: imageOverride)
                                if case .standard(.previewing) = strongSelf.mode {
                                    (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = false
                                } else {
                                    (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil
                                }
                                strongSelf.chatInfoNavigationButton?.buttonItem.accessibilityLabel = presentationInterfaceState.strings.Conversation_ContextMenuOpenProfile
                                
                                strongSelf.storyStats = peerView.storyStats
                                if let avatarNode = strongSelf.avatarNode {
                                    avatarNode.avatarNode.setStoryStats(storyStats: peerView.storyStats.flatMap { storyStats -> AvatarNode.StoryStats? in
                                        if storyStats.totalCount == 0 {
                                            return nil
                                        }
                                        if storyStats.unseenCount == 0 {
                                            return nil
                                        }
                                        return AvatarNode.StoryStats(
                                            totalCount: storyStats.totalCount,
                                            unseenCount: storyStats.unseenCount,
                                            hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriends
                                        )
                                    }, presentationParams: AvatarNode.StoryPresentationParams(
                                        colors: AvatarNode.Colors(theme: strongSelf.presentationData.theme),
                                        lineWidth: 1.5,
                                        inactiveLineWidth: 1.5
                                    ), transition: .immediate)
                                }
                            }
                        }
                    }
                }))
                
                let threadInfo: Signal<EngineMessageHistoryThread.Info?, NoError>
                if let threadId = self.chatLocation.threadId {
                    let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: peerId, threadId: threadId)
                    threadInfo = context.account.postbox.combinedView(keys: [viewKey])
                    |> map { views -> EngineMessageHistoryThread.Info? in
                        guard let view = views.views[viewKey] as? MessageHistoryThreadInfoView else {
                            return nil
                        }
                        guard let data = view.info?.data.get(MessageHistoryThreadData.self) else {
                            return nil
                        }
                        return data.info
                    }
                    |> distinctUntilChanged
                } else {
                    threadInfo = .single(nil)
                }
                
                let hasSearchTags: Signal<Bool, NoError>
                if let peerId = self.chatLocation.peerId, peerId == context.account.peerId {
                    hasSearchTags = context.engine.data.subscribe(
                        TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: self.chatLocation.threadId)
                    )
                    |> map { tags -> Bool in
                        return !tags.isEmpty
                    }
                    |> distinctUntilChanged
                } else {
                    hasSearchTags = .single(false)
                }
                
                let hasSavedChats: Signal<Bool, NoError>
                if case .peer(context.account.peerId) = self.chatLocation {
                    hasSavedChats = context.engine.messages.savedMessagesHasPeersOtherThanSaved()
                } else {
                    hasSavedChats = .single(false)
                }
                
                let isPremiumRequiredForMessaging: Signal<Bool, NoError>
                if let peerId = self.chatLocation.peerId {
                    isPremiumRequiredForMessaging = context.engine.peers.subscribeIsPremiumRequiredForMessaging(id: peerId)
                    |> distinctUntilChanged
                } else {
                    isPremiumRequiredForMessaging = .single(false)
                }
                
                self.peerDisposable.set(combineLatest(
                    queue: Queue.mainQueue(),
                    peerView.get(),
                    context.engine.data.subscribe(TelegramEngine.EngineData.Item.NotificationSettings.Global()),
                    onlineMemberCount,
                    hasScheduledMessages,
                    self.reportIrrelvantGeoNoticePromise.get(),
                    displayedCountSignal,
                    threadInfo,
                    hasSearchTags,
                    hasSavedChats,
                    isPremiumRequiredForMessaging,
                    managingBot
                ).startStrict(next: { [weak self] peerView, globalNotificationSettings, onlineMemberCount, hasScheduledMessages, peerReportNotice, pinnedCount, threadInfo, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging, managingBot in
                    if let strongSelf = self {
                        if strongSelf.peerView === peerView && strongSelf.reportIrrelvantGeoNotice == peerReportNotice && strongSelf.hasScheduledMessages == hasScheduledMessages && strongSelf.threadInfo == threadInfo && strongSelf.presentationInterfaceState.hasSearchTags == hasSearchTags && strongSelf.presentationInterfaceState.hasSavedChats == hasSavedChats && strongSelf.presentationInterfaceState.isPremiumRequiredForMessaging == isPremiumRequiredForMessaging && managingBot == strongSelf.presentationInterfaceState.contactStatus?.managingBot {
                            return
                        }
                        
                        strongSelf.reportIrrelvantGeoNotice = peerReportNotice
                        strongSelf.hasScheduledMessages = hasScheduledMessages
                        
                        var upgradedToPeerId: PeerId?
                        var movedToForumTopics = false
                        if let previous = strongSelf.peerView, let group = previous.peers[previous.peerId] as? TelegramGroup, group.migrationReference == nil, let updatedGroup = peerView.peers[peerView.peerId] as? TelegramGroup, let migrationReference = updatedGroup.migrationReference {
                            upgradedToPeerId = migrationReference.peerId
                        }
                        if let previous = strongSelf.peerView, let channel = previous.peers[previous.peerId] as? TelegramChannel, !channel.flags.contains(.isForum), let updatedChannel = peerView.peers[peerView.peerId] as? TelegramChannel, updatedChannel.flags.contains(.isForum) {
                            movedToForumTopics = true
                        }
                        
                        var shouldDismiss = false
                        if let previous = strongSelf.peerView, let group = previous.peers[previous.peerId] as? TelegramGroup, group.membership != .Removed, let updatedGroup = peerView.peers[peerView.peerId] as? TelegramGroup, updatedGroup.membership == .Removed {
                            shouldDismiss = true
                        } else if let previous = strongSelf.peerView, let channel = previous.peers[previous.peerId] as? TelegramChannel, channel.participationStatus != .kicked, let updatedChannel = peerView.peers[peerView.peerId] as? TelegramChannel, updatedChannel.participationStatus == .kicked {
                            shouldDismiss = true
                        } else if let previous = strongSelf.peerView, let secretChat = previous.peers[previous.peerId] as? TelegramSecretChat, case .active = secretChat.embeddedState, let updatedSecretChat = peerView.peers[peerView.peerId] as? TelegramSecretChat, case .terminated = updatedSecretChat.embeddedState {
                            shouldDismiss = true
                        }
                        
                        var wasGroupChannel: Bool?
                        if let previousPeerView = strongSelf.peerView, let info = (previousPeerView.peers[previousPeerView.peerId] as? TelegramChannel)?.info {
                            if case .group = info {
                                wasGroupChannel = true
                            } else {
                                wasGroupChannel = false
                            }
                        }
                        var isGroupChannel: Bool?
                        if let info = (peerView.peers[peerView.peerId] as? TelegramChannel)?.info {
                            if case .group = info {
                                isGroupChannel = true
                            } else {
                                isGroupChannel = false
                            }
                        }
                        let firstTime = strongSelf.peerView == nil
                        strongSelf.peerView = peerView
                        strongSelf.threadInfo = threadInfo
                        if wasGroupChannel != isGroupChannel {
                            if let isGroupChannel = isGroupChannel, isGroupChannel {
                                let (recentDisposable, _) = strongSelf.context.peerChannelMemberCategoriesContextsManager.recent(engine: strongSelf.context.engine, postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in })
                                let (adminsDisposable, _) = strongSelf.context.peerChannelMemberCategoriesContextsManager.admins(engine: strongSelf.context.engine, postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in })
                                let disposable = DisposableSet()
                                disposable.add(recentDisposable)
                                disposable.add(adminsDisposable)
                                strongSelf.chatAdditionalDataDisposable.set(disposable)
                            } else {
                                strongSelf.chatAdditionalDataDisposable.set(nil)
                            }
                        }
                        if strongSelf.isNodeLoaded {
                            strongSelf.chatDisplayNode.overlayTitle = strongSelf.overlayTitle
                        }
                        var peerIsMuted = false
                        if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings {
                            if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) {
                                peerIsMuted = true
                            } else if case .default = notificationSettings.muteState {
                                if let peer = peerView.peers[peerView.peerId] {
                                    if peer is TelegramUser {
                                        peerIsMuted = !globalNotificationSettings.privateChats.enabled
                                    } else if peer is TelegramGroup {
                                        peerIsMuted = !globalNotificationSettings.groupChats.enabled
                                    } else if let channel = peer as? TelegramChannel {
                                        switch channel.info {
                                        case .group:
                                            peerIsMuted = !globalNotificationSettings.groupChats.enabled
                                        case .broadcast:
                                            peerIsMuted = !globalNotificationSettings.channels.enabled
                                        }
                                    }
                                }
                            }
                        }
                        var peerDiscussionId: PeerId?
                        var peerGeoLocation: PeerGeoLocation?
                        if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData {
                            if case .broadcast = peer.info {
                                if case let .known(value) = cachedData.linkedDiscussionPeerId {
                                    peerDiscussionId = value
                                }
                            } else {
                                peerGeoLocation = cachedData.peerGeoLocation
                            }
                        }
                        var renderedPeer: RenderedPeer?
                        var contactStatus: ChatContactStatus?
                        var businessIntro: TelegramBusinessIntro?
                        if let peer = peerView.peers[peerView.peerId] {
                            if let cachedData = peerView.cachedData as? CachedUserData {
                                contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil, managingBot: managingBot)
                                if case let .known(value) = cachedData.businessIntro {
                                    businessIntro = value
                                }
                            } else if let cachedData = peerView.cachedData as? CachedGroupData {
                                var invitedBy: Peer?
                                if let invitedByPeerId = cachedData.invitedBy {
                                    if let peer = peerView.peers[invitedByPeerId] {
                                        invitedBy = peer
                                    }
                                }
                                contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot)
                            } else if let cachedData = peerView.cachedData as? CachedChannelData {
                                var canReportIrrelevantLocation = true
                                if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.participationStatus == .member {
                                    canReportIrrelevantLocation = false
                                }
                                if let peerReportNotice = peerReportNotice, peerReportNotice {
                                    canReportIrrelevantLocation = false
                                }
                                var invitedBy: Peer?
                                if let invitedByPeerId = cachedData.invitedBy {
                                    if let peer = peerView.peers[invitedByPeerId] {
                                        invitedBy = peer
                                    }
                                }
                                contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: canReportIrrelevantLocation, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot)
                            }
                            
                            var peers = SimpleDictionary<PeerId, Peer>()
                            peers[peer.id] = peer
                            if let associatedPeerId = peer.associatedPeerId, let associatedPeer = peerView.peers[associatedPeerId] {
                                peers[associatedPeer.id] = associatedPeer
                            }
                            renderedPeer = RenderedPeer(peerId: peer.id, peers: peers, associatedMedia: peerView.media)
                        }
                        
                        var isNotAccessible: Bool = false
                        if let cachedChannelData = peerView.cachedData as? CachedChannelData {
                            isNotAccessible = cachedChannelData.isNotAccessible
                        }
                        
                        if firstTime && isNotAccessible {
                            strongSelf.context.account.viewTracker.forceUpdateCachedPeerData(peerId: peerView.peerId)
                        }
                        
                        var hasBots: Bool = false
                        var hasBotCommands: Bool = false
                        var botMenuButton: BotMenuButton = .commands
                        var currentSendAsPeerId: PeerId?
                        var autoremoveTimeout: Int32?
                        var copyProtectionEnabled: Bool = false
                        var hasBirthdayToday = false
                        if let peer = peerView.peers[peerView.peerId] {
                            copyProtectionEnabled = peer.isCopyProtectionEnabled
                            if let cachedGroupData = peerView.cachedData as? CachedGroupData {
                                if !cachedGroupData.botInfos.isEmpty {
                                    hasBots = true
                                }
                                let botCommands = cachedGroupData.botInfos.reduce(into: [], { result, info in
                                    result.append(contentsOf: info.botInfo.commands)
                                })
                                if !botCommands.isEmpty {
                                    hasBotCommands = true
                                }
                                if case let .known(value) = cachedGroupData.autoremoveTimeout {
                                    autoremoveTimeout = value?.effectiveValue
                                }
                            } else if let cachedChannelData = peerView.cachedData as? CachedChannelData {
                                currentSendAsPeerId = cachedChannelData.sendAsPeerId
                                if let channel = peer as? TelegramChannel, case .group = channel.info {
                                    if !cachedChannelData.botInfos.isEmpty {
                                        hasBots = true
                                    }
                                    let botCommands = cachedChannelData.botInfos.reduce(into: [], { result, info in
                                        result.append(contentsOf: info.botInfo.commands)
                                    })
                                    if !botCommands.isEmpty {
                                        hasBotCommands = true
                                    }
                                }
                                if case let .known(value) = cachedChannelData.autoremoveTimeout {
                                    autoremoveTimeout = value?.effectiveValue
                                }
                            } else if let cachedUserData = peerView.cachedData as? CachedUserData {
                                botMenuButton = cachedUserData.botInfo?.menuButton ?? .commands
                                if case let .known(value) = cachedUserData.autoremoveTimeout {
                                    autoremoveTimeout = value?.effectiveValue
                                }
                                if let botInfo = cachedUserData.botInfo, !botInfo.commands.isEmpty {
                                    hasBotCommands = true
                                }
                                if let birthday = cachedUserData.birthday {
                                    let today = Calendar.current.dateComponents(Set([.day, .month]), from: Date())
                                    if today.day == Int(birthday.day) && today.month == Int(birthday.month) {
                                        hasBirthdayToday = true
                                    }
                                }
                            }
                        }
                        
                        let isArchived: Bool = peerView.groupId == Namespaces.PeerGroup.archive
                        
                        var explicitelyCanPinMessages: Bool = false
                        if let cachedUserData = peerView.cachedData as? CachedUserData {
                            explicitelyCanPinMessages = cachedUserData.canPinMessages
                        } else if peerView.peerId == context.account.peerId {
                            explicitelyCanPinMessages = true
                        }
                                                
                        var animated = false
                        if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat, let updated = renderedPeer?.peer as? TelegramSecretChat, peer.embeddedState != updated.embeddedState {
                            animated = true
                        }
                        if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, let updated = renderedPeer?.peer as? TelegramChannel {
                            if peer.participationStatus != updated.participationStatus {
                                animated = true
                            }
                        }
                        
                        var didDisplayActionsPanel = false
                        if let contactStatus = strongSelf.presentationInterfaceState.contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings {
                            if !peerStatusSettings.flags.isEmpty {
                                if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) {
                                    didDisplayActionsPanel = true
                                } else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) {
                                    didDisplayActionsPanel = true
                                } else if peerStatusSettings.contains(.canShareContact) {
                                    didDisplayActionsPanel = true
                                } else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) {
                                    didDisplayActionsPanel = true
                                } else if peerStatusSettings.contains(.suggestAddMembers) {
                                    didDisplayActionsPanel = true
                                }
                            }
                        }
                        if let contactStatus = strongSelf.presentationInterfaceState.contactStatus, contactStatus.managingBot != nil {
                            didDisplayActionsPanel = true
                        }
                        if strongSelf.presentationInterfaceState.search != nil && strongSelf.presentationInterfaceState.hasSearchTags {
                            didDisplayActionsPanel = true
                        }
                        
                        var displayActionsPanel = false
                        if let contactStatus = contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings {
                            if !peerStatusSettings.flags.isEmpty {
                                if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) {
                                    displayActionsPanel = true
                                } else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) {
                                    displayActionsPanel = true
                                } else if peerStatusSettings.contains(.canShareContact) {
                                    displayActionsPanel = true
                                } else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) {
                                    displayActionsPanel = true
                                } else if peerStatusSettings.contains(.suggestAddMembers) {
                                    displayActionsPanel = true
                                }
                            }
                        }
                        if let contactStatus, contactStatus.managingBot != nil {
                            displayActionsPanel = true
                        }
                        if strongSelf.presentationInterfaceState.search != nil && hasSearchTags {
                            displayActionsPanel = true
                        }
                        
                        if displayActionsPanel != didDisplayActionsPanel {
                            animated = true
                        }
                        
                        if strongSelf.preloadHistoryPeerId != peerDiscussionId {
                            strongSelf.preloadHistoryPeerId = peerDiscussionId
                            if let peerDiscussionId = peerDiscussionId {
                                let combinedDisposable = DisposableSet()
                                strongSelf.preloadHistoryPeerIdDisposable.set(combinedDisposable)
                                combinedDisposable.add(strongSelf.context.account.viewTracker.polledChannel(peerId: peerDiscussionId).startStrict())
                                combinedDisposable.add(strongSelf.context.account.addAdditionalPreloadHistoryPeerId(peerId: peerDiscussionId))
                            } else {
                                strongSelf.preloadHistoryPeerIdDisposable.set(nil)
                            }
                        }
                        
                        var appliedBoosts: Int32?
                        var boostsToUnrestrict: Int32?
                        if let cachedChannelData = peerView.cachedData as? CachedChannelData {
                            appliedBoosts = cachedChannelData.appliedBoosts
                            boostsToUnrestrict = cachedChannelData.boostsToUnrestrict
                        }
                        
                        strongSelf.updateChatPresentationInterfaceState(animated: animated, interactive: false, {
                            return $0.updatedPeer { _ in
                                return renderedPeer
                            }.updatedIsNotAccessible(isNotAccessible).updatedContactStatus(contactStatus).updatedHasBots(hasBots).updatedHasBotCommands(hasBotCommands).updatedBotMenuButton(botMenuButton).updatedIsArchived(isArchived).updatedPeerIsMuted(peerIsMuted).updatedPeerDiscussionId(peerDiscussionId).updatedPeerGeoLocation(peerGeoLocation).updatedExplicitelyCanPinMessages(explicitelyCanPinMessages).updatedHasScheduledMessages(hasScheduledMessages)
                                .updatedAutoremoveTimeout(autoremoveTimeout)
                                .updatedCurrentSendAsPeerId(currentSendAsPeerId)
                                .updatedCopyProtectionEnabled(copyProtectionEnabled)
                                .updatedHasSearchTags(hasSearchTags)
                                .updatedIsPremiumRequiredForMessaging(isPremiumRequiredForMessaging)
                                .updatedHasSavedChats(hasSavedChats)
                                .updatedAppliedBoosts(appliedBoosts)
                                .updatedBoostsToUnrestrict(boostsToUnrestrict)
                                .updatedHasBirthdayToday(hasBirthdayToday)
                                .updatedBusinessIntro(businessIntro)
                                .updatedInterfaceState { interfaceState in
                                    var interfaceState = interfaceState
                                    
                                    if let channel = renderedPeer?.peer as? TelegramChannel {
                                        if channel.hasBannedPermission(.banSendVoice) != nil && channel.hasBannedPermission(.banSendInstantVideos) != nil {
                                            interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio)
                                        } else if channel.hasBannedPermission(.banSendVoice) != nil {
                                            if channel.hasBannedPermission(.banSendInstantVideos) == nil {
                                                interfaceState = interfaceState.withUpdatedMediaRecordingMode(.video)
                                            }
                                        } else if channel.hasBannedPermission(.banSendInstantVideos) != nil {
                                            if channel.hasBannedPermission(.banSendVoice) == nil {
                                                interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio)
                                            }
                                        }
                                    } else if let group = renderedPeer?.peer as? TelegramGroup {
                                        if group.hasBannedPermission(.banSendVoice) && group.hasBannedPermission(.banSendInstantVideos) {
                                            interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio)
                                        } else if group.hasBannedPermission(.banSendVoice) {
                                            if !group.hasBannedPermission(.banSendInstantVideos) {
                                                interfaceState = interfaceState.withUpdatedMediaRecordingMode(.video)
                                            }
                                        } else if group.hasBannedPermission(.banSendInstantVideos) {
                                            if !group.hasBannedPermission(.banSendVoice) {
                                                interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio)
                                            }
                                        }
                                    }
                                    
                                    return interfaceState
                                }
                        })

                        if case .standard(.default) = mode, let channel = renderedPeer?.chatMainPeer as? TelegramChannel, case .broadcast = channel.info {
                            var isRegularChat = false
                            if let subject = subject {
                                if case .message = subject {
                                    isRegularChat = true
                                }
                            } else {
                                isRegularChat = true
                            }
                            if strongSelf.nextChannelToReadDisposable == nil, let peerId = strongSelf.chatLocation.peerId, let customChatNavigationStack = strongSelf.customChatNavigationStack, !SGSimpleSettings.shared.disableScrollToNextChannel {
                                if let index = customChatNavigationStack.firstIndex(of: peerId), index != customChatNavigationStack.count - 1 {
                                    let nextPeerId = customChatNavigationStack[index + 1]
                                    strongSelf.nextChannelToReadDisposable = (combineLatest(queue: .mainQueue(),
                                        strongSelf.context.engine.data.subscribe(
                                            TelegramEngine.EngineData.Item.Peer.Peer(id: nextPeerId)
                                        ),
                                        ApplicationSpecificNotice.getNextChatSuggestionTip(accountManager: strongSelf.context.sharedContext.accountManager)
                                    )
                                    |> then(.complete() |> delay(1.0, queue: .mainQueue()))
                                    |> restart).startStrict(next: { nextPeer, nextChatSuggestionTip in
                                        guard let strongSelf = self else {
                                            return
                                        }

                                        strongSelf.offerNextChannelToRead = true
                                        strongSelf.chatDisplayNode.historyNode.nextChannelToRead = nextPeer.flatMap { nextPeer -> (peer: EnginePeer, threadData: (id: Int64, data: MessageHistoryThreadData)?, unreadCount: Int, location: TelegramEngine.NextUnreadChannelLocation) in
                                            return (peer: nextPeer, threadData: nil, unreadCount: 0, location: .same)
                                        }
                                        strongSelf.chatDisplayNode.historyNode.nextChannelToReadDisplayName = nextChatSuggestionTip >= 3

                                        let nextPeerId = nextPeer?.id

                                        if strongSelf.preloadNextChatPeerId != nextPeerId {
                                            strongSelf.preloadNextChatPeerId = nextPeerId
                                            if let nextPeerId = nextPeerId {
                                                let combinedDisposable = DisposableSet()
                                                strongSelf.preloadNextChatPeerIdDisposable.set(combinedDisposable)
                                                combinedDisposable.add(strongSelf.context.account.viewTracker.polledChannel(peerId: nextPeerId).startStrict())
                                                combinedDisposable.add(strongSelf.context.account.addAdditionalPreloadHistoryPeerId(peerId: nextPeerId))
                                            } else {
                                                strongSelf.preloadNextChatPeerIdDisposable.set(nil)
                                            }
                                        }
                                        
                                        strongSelf.updateNextChannelToReadVisibility()
                                    })
                                }
                            } else if isRegularChat, strongSelf.nextChannelToReadDisposable == nil, !SGSimpleSettings.shared.disableScrollToNextChannel {
                                //TODO:loc optimize
                                let accountPeerId = strongSelf.context.account.peerId
                                strongSelf.nextChannelToReadDisposable = (combineLatest(queue: .mainQueue(),
                                    strongSelf.context.engine.peers.getNextUnreadChannel(peerId: channel.id, chatListFilterId: strongSelf.currentChatListFilter, getFilterPredicate: { data in
                                    return chatListFilterPredicate(filter: data, accountPeerId: accountPeerId)
                                }),
                                    ApplicationSpecificNotice.getNextChatSuggestionTip(accountManager: strongSelf.context.sharedContext.accountManager)
                                )
                                |> then(.complete() |> delay(1.0, queue: .mainQueue()))
                                |> restart).startStrict(next: { nextPeer, nextChatSuggestionTip in
                                    guard let strongSelf = self else {
                                        return
                                    }

                                    strongSelf.offerNextChannelToRead = true
                                    strongSelf.chatDisplayNode.historyNode.nextChannelToRead = nextPeer.flatMap { nextPeer -> (peer: EnginePeer, threadData: (id: Int64, data: MessageHistoryThreadData)?, unreadCount: Int, location: TelegramEngine.NextUnreadChannelLocation) in
                                        return (peer: nextPeer.peer, threadData: nil, unreadCount: nextPeer.unreadCount, location: nextPeer.location)
                                    }
                                    strongSelf.chatDisplayNode.historyNode.nextChannelToReadDisplayName = nextChatSuggestionTip >= 3

                                    let nextPeerId = nextPeer?.peer.id

                                    if strongSelf.preloadNextChatPeerId != nextPeerId {
                                        strongSelf.preloadNextChatPeerId = nextPeerId
                                        if let nextPeerId = nextPeerId {
                                            let combinedDisposable = DisposableSet()
                                            strongSelf.preloadNextChatPeerIdDisposable.set(combinedDisposable)
                                            combinedDisposable.add(strongSelf.context.account.viewTracker.polledChannel(peerId: nextPeerId).startStrict())
                                            combinedDisposable.add(strongSelf.context.account.addAdditionalPreloadHistoryPeerId(peerId: nextPeerId))
                                        } else {
                                            strongSelf.preloadNextChatPeerIdDisposable.set(nil)
                                        }
                                    }
                                    
                                    strongSelf.updateNextChannelToReadVisibility()
                                })
                            }
                        }

                        if !strongSelf.didSetChatLocationInfoReady {
                            strongSelf.didSetChatLocationInfoReady = true
                            strongSelf._chatLocationInfoReady.set(.single(true))
                        }
                        strongSelf.updateReminderActivity()
                        if let upgradedToPeerId = upgradedToPeerId {
                            if let navigationController = strongSelf.effectiveNavigationController {
                                var viewControllers = navigationController.viewControllers
                                if let index = viewControllers.firstIndex(where: { $0 === strongSelf }) {
                                    viewControllers[index] = ChatControllerImpl(context: strongSelf.context, chatLocation: .peer(id: upgradedToPeerId))
                                    navigationController.setViewControllers(viewControllers, animated: false)
                                }
                            }
                        } else if movedToForumTopics {
                            if let navigationController = strongSelf.effectiveNavigationController {
                                let chatListController = strongSelf.context.sharedContext.makeChatListController(context: strongSelf.context, location: .forum(peerId: peerView.peerId), controlsHistoryPreload: false, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: false)
                                navigationController.replaceController(strongSelf, with: chatListController, animated: true)
                            }
                        } else if shouldDismiss {
                            strongSelf.dismiss()
                        }
                    }
                }))
                
                if peerId == context.account.peerId {
                    self.preloadSavedMessagesChatsDisposable = context.engine.messages.savedMessagesPeerListHead().start()
                }
            } else if case let .replyThread(messagePromise) = self.chatLocationInfoData, let peerId = peerId {
                self.reportIrrelvantGeoNoticePromise.set(.single(nil))
                                
                let replyThreadType: ChatTitleContent.ReplyThreadType
                var replyThreadId: Int64?
                switch chatLocation {
                case .peer:
                    replyThreadType = .replies
                case let .replyThread(replyThreadMessage):
                    if replyThreadMessage.peerId == context.account.peerId {
                        replyThreadId = replyThreadMessage.threadId
                        replyThreadType = .replies
                    } else {
                        replyThreadId = replyThreadMessage.threadId
                        if replyThreadMessage.isChannelPost {
                            replyThreadType = .comments
                        } else {
                            replyThreadType = .replies
                        }
                    }
                case .customChatContents:
                    replyThreadType = .replies
                }
                
                let peerView = context.account.viewTracker.peerView(peerId)
                
                let messageAndTopic = messagePromise.get()
                |> mapToSignal { message -> Signal<(message: Message?, threadData: MessageHistoryThreadData?, messageCount: Int), NoError> in
                    guard let replyThreadId = replyThreadId else {
                        return .single((message, nil, 0))
                    }
                    let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: peerId, threadId: replyThreadId)
                    let countViewKey: PostboxViewKey = .historyTagSummaryView(tag: MessageTags(), peerId: peerId, threadId: replyThreadId, namespace: Namespaces.Message.Cloud, customTag: nil)
                    let localCountViewKey: PostboxViewKey = .historyTagSummaryView(tag: MessageTags(), peerId: peerId, threadId: replyThreadId, namespace: Namespaces.Message.Local, customTag: nil)
                    return context.account.postbox.combinedView(keys: [viewKey, countViewKey, localCountViewKey])
                    |> map { views -> (message: Message?, threadData: MessageHistoryThreadData?, messageCount: Int) in
                        guard let view = views.views[viewKey] as? MessageHistoryThreadInfoView else {
                            return (message, nil, 0)
                        }
                        var messageCount = 0
                        if let summaryView = views.views[countViewKey] as? MessageHistoryTagSummaryView, let count = summaryView.count {
                            if replyThreadId == 1 {
                                messageCount += Int(count)
                            } else {
                                messageCount += max(Int(count) - 1, 0)
                            }
                        }
                        if let summaryView = views.views[localCountViewKey] as? MessageHistoryTagSummaryView, let count = summaryView.count {
                            messageCount += Int(count)
                        }
                        return (message, view.info?.data.get(MessageHistoryThreadData.self), messageCount)
                    }
                }
                
                let savedMessagesPeerId: PeerId?
                if case let .replyThread(replyThreadMessage) = chatLocation, replyThreadMessage.peerId == context.account.peerId {
                    savedMessagesPeerId = PeerId(replyThreadMessage.threadId)
                } else {
                    savedMessagesPeerId = nil
                }
                
                let savedMessagesPeer: Signal<(peer: EnginePeer?, messageCount: Int)?, NoError>
                if let savedMessagesPeerId {
                    let threadPeerId = savedMessagesPeerId
                    let basicPeerKey: PostboxViewKey = .basicPeer(threadPeerId)
                    let countViewKey: PostboxViewKey = .historyTagSummaryView(tag: MessageTags(), peerId: peerId, threadId: savedMessagesPeerId.toInt64(), namespace: Namespaces.Message.Cloud, customTag: nil)
                    savedMessagesPeer = context.account.postbox.combinedView(keys: [basicPeerKey, countViewKey])
                    |> map { views -> (peer: EnginePeer?, messageCount: Int)? in
                        let peer = ((views.views[basicPeerKey] as? BasicPeerView)?.peer).flatMap(EnginePeer.init)
                        
                        var messageCount = 0
                        if let summaryView = views.views[countViewKey] as? MessageHistoryTagSummaryView, let count = summaryView.count {
                            messageCount += Int(count)
                        }
                        
                        return (peer, messageCount)
                    }
                } else {
                    savedMessagesPeer = .single(nil)
                }
                
                var isScheduledOrPinnedMessages = false
                switch subject {
                case .scheduledMessages, .pinnedMessages, .messageOptions:
                    isScheduledOrPinnedMessages = true
                default:
                    break
                }
                
                var hasScheduledMessages: Signal<Bool, NoError> = .single(false)
                if chatLocation.peerId != nil, !isScheduledOrPinnedMessages, peerId.namespace != Namespaces.Peer.SecretChat {
                    let chatLocationContextHolder = self.chatLocationContextHolder
                    hasScheduledMessages = peerView
                    |> take(1)
                    |> mapToSignal { view -> Signal<Bool, NoError> in
                        if let peer = peerViewMainPeer(view) as? TelegramChannel, !peer.hasPermission(.sendSomething) {
                            return .single(false)
                        } else {
                            if case let .replyThread(message) = chatLocation, message.peerId == context.account.peerId {
                                return context.account.viewTracker.scheduledMessagesViewForLocation(context.chatLocationInput(for: .peer(id: context.account.peerId), contextHolder: Atomic(value: nil)))
                                |> map { view, _, _ in
                                    return !view.entries.isEmpty
                                }
                                |> distinctUntilChanged
                            } else {
                                return context.account.viewTracker.scheduledMessagesViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder))
                                |> map { view, _, _ in
                                    return !view.entries.isEmpty
                                }
                                |> distinctUntilChanged
                            }
                        }
                    }
                }
                
                var onlineMemberCount: Signal<Int32?, NoError> = .single(nil)
                if peerId.namespace == Namespaces.Peer.CloudChannel {
                    let recentOnlineSignal: Signal<Int32?, NoError> = peerView
                    |> map { view -> Bool? in
                        if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel {
                            if case .broadcast = peer.info {
                                return nil
                            } else if let memberCount = cachedData.participantsSummary.memberCount, memberCount > 50 {
                                return true
                            } else {
                                return false
                            }
                        } else {
                            return false
                        }
                    }
                    |> distinctUntilChanged
                    |> mapToSignal { isLarge -> Signal<Int32?, NoError> in
                        if let isLarge = isLarge {
                            if isLarge {
                                return context.peerChannelMemberCategoriesContextsManager.recentOnline(account: context.account, accountPeerId: context.account.peerId, peerId: peerId)
                                |> map(Optional.init)
                            } else {
                                return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId)
                                |> map(Optional.init)
                            }
                        } else {
                            return .single(nil)
                        }
                    }
                    onlineMemberCount = recentOnlineSignal
                }
                
                let hasSearchTags: Signal<Bool, NoError>
                if let peerId = self.chatLocation.peerId, peerId == context.account.peerId {
                    hasSearchTags = context.engine.data.subscribe(
                        TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: self.chatLocation.threadId)
                    )
                    |> map { tags -> Bool in
                        return !tags.isEmpty
                    }
                    |> distinctUntilChanged
                } else {
                    hasSearchTags = .single(false)
                }
                
                let hasSavedChats: Signal<Bool, NoError>
                if case .peer(context.account.peerId) = self.chatLocation {
                    hasSavedChats = context.engine.messages.savedMessagesHasPeersOtherThanSaved()
                } else {
                    hasSavedChats = .single(false)
                }
                
                let isPremiumRequiredForMessaging: Signal<Bool, NoError>
                if let peerId = self.chatLocation.peerId {
                    isPremiumRequiredForMessaging = context.engine.peers.subscribeIsPremiumRequiredForMessaging(id: peerId)
                    |> distinctUntilChanged
                } else {
                    isPremiumRequiredForMessaging = .single(false)
                }
                
                self.titleDisposable.set(nil)
                self.peerDisposable.set((combineLatest(queue: Queue.mainQueue(),
                    peerView,
                    messageAndTopic,
                    savedMessagesPeer,
                    onlineMemberCount,
                    hasScheduledMessages,
                    hasSearchTags,
                    hasSavedChats,
                    isPremiumRequiredForMessaging,
                    managingBot
                )
                |> deliverOnMainQueue).startStrict(next: { [weak self] peerView, messageAndTopic, savedMessagesPeer, onlineMemberCount, hasScheduledMessages, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging, managingBot in
                    if let strongSelf = self {
                        strongSelf.hasScheduledMessages = hasScheduledMessages
                        
                        var renderedPeer: RenderedPeer?
                        var contactStatus: ChatContactStatus?
                        var copyProtectionEnabled: Bool = false
                        var businessIntro: TelegramBusinessIntro?
                        if let peer = peerView.peers[peerView.peerId] {
                            copyProtectionEnabled = peer.isCopyProtectionEnabled
                            if let cachedData = peerView.cachedData as? CachedUserData {
                                contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil, managingBot: managingBot)
                                if case let .known(value) = cachedData.businessIntro {
                                    businessIntro = value
                                }
                            } else if let cachedData = peerView.cachedData as? CachedGroupData {
                                var invitedBy: Peer?
                                if let invitedByPeerId = cachedData.invitedBy {
                                    if let peer = peerView.peers[invitedByPeerId] {
                                        invitedBy = peer
                                    }
                                }
                                contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot)
                            } else if let cachedData = peerView.cachedData as? CachedChannelData {
                                var canReportIrrelevantLocation = true
                                if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.participationStatus == .member {
                                    canReportIrrelevantLocation = false
                                }
                                canReportIrrelevantLocation = false
                                var invitedBy: Peer?
                                if let invitedByPeerId = cachedData.invitedBy {
                                    if let peer = peerView.peers[invitedByPeerId] {
                                        invitedBy = peer
                                    }
                                }
                                contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: canReportIrrelevantLocation, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot)
                            }
                            
                            var peers = SimpleDictionary<PeerId, Peer>()
                            peers[peer.id] = peer
                            if let associatedPeerId = peer.associatedPeerId, let associatedPeer = peerView.peers[associatedPeerId] {
                                peers[associatedPeer.id] = associatedPeer
                            }
                            renderedPeer = RenderedPeer(peerId: peer.id, peers: peers, associatedMedia: peerView.media)
                        }
                        
                        if let savedMessagesPeerId {
                            let mappedPeerData = ChatTitleContent.PeerData(
                                peerId: savedMessagesPeerId,
                                peer: savedMessagesPeer?.peer?._asPeer(),
                                isContact: true,
                                isSavedMessages: true,
                                notificationSettings: nil,
                                peerPresences: [:],
                                cachedData: nil
                            )
                            strongSelf.chatTitleView?.titleContent = .peer(peerView: mappedPeerData, customTitle: nil, onlineMemberCount: nil, isScheduledMessages: false, isMuted: false, customMessageCount: savedMessagesPeer?.messageCount ?? 0, isEnabled: true)
                            
                            strongSelf.peerView = peerView
                            
                            let imageOverride: AvatarNodeImageOverride?
                            if strongSelf.context.account.peerId == savedMessagesPeerId {
                                imageOverride = .myNotesIcon
                            } else if savedMessagesPeerId.isReplies {
                                imageOverride = .repliesIcon
                            } else if savedMessagesPeerId.isAnonymousSavedMessages {
                                imageOverride = .anonymousSavedMessagesIcon(isColored: true)
                            } else if let peer = savedMessagesPeer?.peer, peer.isDeleted {
                                imageOverride = .deletedIcon
                            } else {
                                imageOverride = nil
                            }
                            
                            if strongSelf.isNodeLoaded {
                                strongSelf.chatDisplayNode.overlayTitle = strongSelf.overlayTitle
                            }
                            
                            let animated = false
                            strongSelf.updateChatPresentationInterfaceState(animated: animated, interactive: false, {
                                return $0.updatedPeer { _ in
                                    return renderedPeer
                                }.updatedSavedMessagesTopicPeer(savedMessagesPeer?.peer)
                                .updatedHasSearchTags(hasSearchTags)
                                .updatedHasSavedChats(hasSavedChats)
                                .updatedHasScheduledMessages(hasScheduledMessages)
                            })
                            
                            (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.setPeer(context: strongSelf.context, theme: strongSelf.presentationData.theme, peer: savedMessagesPeer?.peer, overrideImage: imageOverride)
                            (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = false
                            strongSelf.chatInfoNavigationButton?.buttonItem.accessibilityLabel = strongSelf.presentationData.strings.Conversation_ContextMenuOpenProfile
                        } else {
                            let message = messageAndTopic.message
                            
                            var count = 0
                            if let message = message {
                                for attribute in message.attributes {
                                    if let attribute = attribute as? ReplyThreadMessageAttribute {
                                        count = Int(attribute.count)
                                        break
                                    }
                                }
                            }
                            
                            var peerIsMuted = false
                            if let threadData = messageAndTopic.threadData {
                                if case let .muted(until) = threadData.notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) {
                                    peerIsMuted = true
                                }
                            } else if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings {
                                if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) {
                                    peerIsMuted = true
                                }
                            }
                            
                            if let threadInfo = messageAndTopic.threadData?.info {
                                strongSelf.chatTitleView?.titleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: threadInfo.title, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: peerIsMuted, customMessageCount: messageAndTopic.messageCount == 0 ? nil : messageAndTopic.messageCount, isEnabled: true)
                                
                                let avatarContent: EmojiStatusComponent.Content
                                if strongSelf.chatLocation.threadId == 1 {
                                    avatarContent = .image(image: PresentationResourcesChat.chatGeneralThreadIcon(strongSelf.presentationData.theme))
                                } else if let fileId = threadInfo.icon {
                                    avatarContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 48.0, height: 48.0), placeholderColor: strongSelf.presentationData.theme.list.mediaPlaceholderColor, themeColor: strongSelf.presentationData.theme.list.itemAccentColor, loopMode: .count(1))
                                } else {
                                    avatarContent = .topic(title: String(threadInfo.title.prefix(1)), color: threadInfo.iconColor, size: CGSize(width: 32.0, height: 32.0))
                                }
                                (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.setStatus(context: strongSelf.context, content: avatarContent)
                            } else {
                                strongSelf.chatTitleView?.titleContent = .replyThread(type: replyThreadType, count: count)
                            }
                            
                            var wasGroupChannel: Bool?
                            if let previousPeerView = strongSelf.peerView, let info = (previousPeerView.peers[previousPeerView.peerId] as? TelegramChannel)?.info {
                                if case .group = info {
                                    wasGroupChannel = true
                                } else {
                                    wasGroupChannel = false
                                }
                            }
                            var isGroupChannel: Bool?
                            if let info = (peerView.peers[peerView.peerId] as? TelegramChannel)?.info {
                                if case .group = info {
                                    isGroupChannel = true
                                } else {
                                    isGroupChannel = false
                                }
                            }
                            let firstTime = strongSelf.peerView == nil
                            
                            if wasGroupChannel != isGroupChannel {
                                if let isGroupChannel = isGroupChannel, isGroupChannel {
                                    let (recentDisposable, _) = strongSelf.context.peerChannelMemberCategoriesContextsManager.recent(engine: strongSelf.context.engine, postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in })
                                    let (adminsDisposable, _) = strongSelf.context.peerChannelMemberCategoriesContextsManager.admins(engine: strongSelf.context.engine, postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in })
                                    let disposable = DisposableSet()
                                    disposable.add(recentDisposable)
                                    disposable.add(adminsDisposable)
                                    strongSelf.chatAdditionalDataDisposable.set(disposable)
                                } else {
                                    strongSelf.chatAdditionalDataDisposable.set(nil)
                                }
                            }
                            
                            strongSelf.peerView = peerView
                            strongSelf.threadInfo = messageAndTopic.threadData?.info
                            
                            if strongSelf.isNodeLoaded {
                                strongSelf.chatDisplayNode.overlayTitle = strongSelf.overlayTitle
                            }
                            if case .standard(.previewing) = strongSelf.mode {
                                (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = false
                            } else {
                                (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = true
                            }
                            
                            var peerDiscussionId: PeerId?
                            var peerGeoLocation: PeerGeoLocation?
                            var currentSendAsPeerId: PeerId?
                            if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData {
                                currentSendAsPeerId = cachedData.sendAsPeerId
                                if case .broadcast = peer.info {
                                    if case let .known(value) = cachedData.linkedDiscussionPeerId {
                                        peerDiscussionId = value
                                    }
                                } else {
                                    peerGeoLocation = cachedData.peerGeoLocation
                                }
                            }
                            
                            var isNotAccessible: Bool = false
                            if let cachedChannelData = peerView.cachedData as? CachedChannelData {
                                isNotAccessible = cachedChannelData.isNotAccessible
                            }
                            
                            if firstTime && isNotAccessible {
                                strongSelf.context.account.viewTracker.forceUpdateCachedPeerData(peerId: peerView.peerId)
                            }
                            
                            var hasBots: Bool = false
                            if let peer = peerView.peers[peerView.peerId] {
                                if let cachedGroupData = peerView.cachedData as? CachedGroupData {
                                    if !cachedGroupData.botInfos.isEmpty {
                                        hasBots = true
                                    }
                                } else if let cachedChannelData = peerView.cachedData as? CachedChannelData, let channel = peer as? TelegramChannel, case .group = channel.info {
                                    if !cachedChannelData.botInfos.isEmpty {
                                        hasBots = true
                                    }
                                }
                            }
                            
                            let isArchived: Bool = peerView.groupId == Namespaces.PeerGroup.archive
                            
                            var explicitelyCanPinMessages: Bool = false
                            if let cachedUserData = peerView.cachedData as? CachedUserData {
                                explicitelyCanPinMessages = cachedUserData.canPinMessages
                            } else if peerView.peerId == context.account.peerId {
                                explicitelyCanPinMessages = true
                            }
                            
                            var animated = false
                            if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat, let updated = renderedPeer?.peer as? TelegramSecretChat, peer.embeddedState != updated.embeddedState {
                                animated = true
                            }
                            if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, let updated = renderedPeer?.peer as? TelegramChannel {
                                if peer.participationStatus != updated.participationStatus {
                                    animated = true
                                }
                            }
                            
                            var didDisplayActionsPanel = false
                            if let contactStatus = strongSelf.presentationInterfaceState.contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings {
                                if !peerStatusSettings.flags.isEmpty {
                                    if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) {
                                        didDisplayActionsPanel = true
                                    } else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) {
                                        didDisplayActionsPanel = true
                                    } else if peerStatusSettings.contains(.canShareContact) {
                                        didDisplayActionsPanel = true
                                    } else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) {
                                        didDisplayActionsPanel = true
                                    } else if peerStatusSettings.contains(.suggestAddMembers) {
                                        didDisplayActionsPanel = true
                                    }
                                }
                            }
                            if let contactStatus = strongSelf.presentationInterfaceState.contactStatus, contactStatus.managingBot != nil {
                                didDisplayActionsPanel = true
                            }
                            
                            var displayActionsPanel = false
                            if let contactStatus = contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings {
                                if !peerStatusSettings.flags.isEmpty {
                                    if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) {
                                        displayActionsPanel = true
                                    } else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) {
                                        displayActionsPanel = true
                                    } else if peerStatusSettings.contains(.canShareContact) {
                                        displayActionsPanel = true
                                    } else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) {
                                        displayActionsPanel = true
                                    } else if peerStatusSettings.contains(.suggestAddMembers) {
                                        displayActionsPanel = true
                                    }
                                }
                            }
                            if let contactStatus, contactStatus.managingBot != nil {
                                displayActionsPanel = true
                            }
                            
                            if displayActionsPanel != didDisplayActionsPanel {
                                animated = true
                            }
                            
                            if strongSelf.preloadHistoryPeerId != peerDiscussionId {
                                strongSelf.preloadHistoryPeerId = peerDiscussionId
                                if let peerDiscussionId = peerDiscussionId {
                                    strongSelf.preloadHistoryPeerIdDisposable.set(strongSelf.context.account.addAdditionalPreloadHistoryPeerId(peerId: peerDiscussionId))
                                } else {
                                    strongSelf.preloadHistoryPeerIdDisposable.set(nil)
                                }
                            }
                            
                            var appliedBoosts: Int32?
                            var boostsToUnrestrict: Int32?
                            if let cachedChannelData = peerView.cachedData as? CachedChannelData {
                                appliedBoosts = cachedChannelData.appliedBoosts
                                boostsToUnrestrict = cachedChannelData.boostsToUnrestrict
                            }
                            
                            strongSelf.updateChatPresentationInterfaceState(animated: animated, interactive: false, {
                                return $0.updatedPeer { _ in
                                    return renderedPeer
                                }.updatedIsNotAccessible(isNotAccessible).updatedContactStatus(contactStatus).updatedHasBots(hasBots).updatedIsArchived(isArchived).updatedPeerIsMuted(peerIsMuted).updatedPeerDiscussionId(peerDiscussionId).updatedPeerGeoLocation(peerGeoLocation).updatedExplicitelyCanPinMessages(explicitelyCanPinMessages).updatedHasScheduledMessages(hasScheduledMessages).updatedCurrentSendAsPeerId(currentSendAsPeerId)
                                    .updatedCopyProtectionEnabled(copyProtectionEnabled)
                                    .updatedHasSearchTags(hasSearchTags)
                                    .updatedIsPremiumRequiredForMessaging(isPremiumRequiredForMessaging)
                                    .updatedHasSavedChats(hasSavedChats)
                                    .updatedAppliedBoosts(appliedBoosts)
                                    .updatedBoostsToUnrestrict(boostsToUnrestrict)
                                    .updatedBusinessIntro(businessIntro)
                                    .updatedInterfaceState { interfaceState in
                                        var interfaceState = interfaceState
                                        
                                        if let channel = renderedPeer?.peer as? TelegramChannel {
                                            if channel.hasBannedPermission(.banSendVoice) != nil && channel.hasBannedPermission(.banSendInstantVideos) != nil {
                                                interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio)
                                            } else if channel.hasBannedPermission(.banSendVoice) != nil {
                                                if channel.hasBannedPermission(.banSendInstantVideos) == nil {
                                                    interfaceState = interfaceState.withUpdatedMediaRecordingMode(.video)
                                                }
                                            } else if channel.hasBannedPermission(.banSendInstantVideos) != nil {
                                                if channel.hasBannedPermission(.banSendVoice) == nil {
                                                    interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio)
                                                }
                                            }
                                        } else if let group = renderedPeer?.peer as? TelegramGroup {
                                            if group.hasBannedPermission(.banSendVoice) && group.hasBannedPermission(.banSendInstantVideos) {
                                                interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio)
                                            } else if group.hasBannedPermission(.banSendVoice) {
                                                if !group.hasBannedPermission(.banSendInstantVideos) {
                                                    interfaceState = interfaceState.withUpdatedMediaRecordingMode(.video)
                                                }
                                            } else if group.hasBannedPermission(.banSendInstantVideos) {
                                                if !group.hasBannedPermission(.banSendVoice) {
                                                    interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio)
                                                }
                                            }
                                        }
                                        
                                        return interfaceState
                                    }
                            })
                            
                            if let replyThreadId, let channel = renderedPeer?.peer as? TelegramChannel, channel.isForum, strongSelf.nextChannelToReadDisposable == nil, !SGSimpleSettings.shared.disableScrollToNextTopic {
                                strongSelf.nextChannelToReadDisposable = (combineLatest(queue: .mainQueue(),
                                strongSelf.context.engine.peers.getNextUnreadForumTopic(peerId: channel.id, topicId: Int32(clamping: replyThreadId)),
                                    ApplicationSpecificNotice.getNextChatSuggestionTip(accountManager: strongSelf.context.sharedContext.accountManager)
                                )
                                |> then(.complete() |> delay(1.0, queue: .mainQueue()))
                                |> restart).startStrict(next: { nextThreadData, nextChatSuggestionTip in
                                    guard let strongSelf = self else {
                                        return
                                    }

                                    strongSelf.offerNextChannelToRead = true
                                    strongSelf.chatDisplayNode.historyNode.nextChannelToRead = nextThreadData.flatMap { nextThreadData -> (peer: EnginePeer, threadData: (id: Int64, data: MessageHistoryThreadData)?, unreadCount: Int, location: TelegramEngine.NextUnreadChannelLocation) in
                                        return (peer: EnginePeer(channel), threadData: nextThreadData, unreadCount: Int(nextThreadData.data.incomingUnreadCount), location: .same)
                                    }
                                    strongSelf.chatDisplayNode.historyNode.nextChannelToReadDisplayName = nextChatSuggestionTip >= 3
                                    
                                    strongSelf.updateNextChannelToReadVisibility()
                                })
                            }
                        }
                        if !strongSelf.didSetChatLocationInfoReady {
                            strongSelf.didSetChatLocationInfoReady = true
                            strongSelf._chatLocationInfoReady.set(.single(true))
                        }
                    }
                }))
            } else if case .customChatContents = self.chatLocationInfoData {
                self.reportIrrelvantGeoNoticePromise.set(.single(nil))
                self.titleDisposable.set(nil)
                
                if case let .customChatContents(customChatContents) = self.subject {
                    switch customChatContents.kind {
                    case .hashTagSearch:
                        break
                    case let .quickReplyMessageInput(shortcut, shortcutType):
                        switch shortcutType {
                        case .generic:
                            self.chatTitleView?.titleContent = .custom("\(shortcut)", nil, false)
                        case .greeting:
                            self.chatTitleView?.titleContent = .custom(self.presentationData.strings.QuickReply_TitleGreetingMessage, nil, false)
                        case .away:
                            self.chatTitleView?.titleContent = .custom(self.presentationData.strings.QuickReply_TitleAwayMessage, nil, false)
                        }
                    case let .businessLinkSetup(link):
                        let linkUrl: String
                        if link.url.hasPrefix("https://") {
                            linkUrl = String(link.url[link.url.index(link.url.startIndex, offsetBy: "https://".count)...])
                        } else {
                            linkUrl = link.url
                        }
                        
                        self.chatTitleView?.titleContent = .custom(link.title ?? self.presentationData.strings.Business_Links_EditLinkTitle, linkUrl, false)
                    }
                } else {
                    self.chatTitleView?.titleContent = .custom(" ", nil, false)
                }
                
                if !self.didSetChatLocationInfoReady {
                    self.didSetChatLocationInfoReady = true
                    self._chatLocationInfoReady.set(.single(true))
                }
            }
        }
        
        self.botCallbackAlertMessageDisposable = (self.botCallbackAlertMessage.get()
        |> deliverOnMainQueue).startStrict(next: { [weak self] message in
                if let strongSelf = self {
                    strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
                        return $0.updatedTitlePanelContext {
                            if let message = message {
                                if let index = $0.firstIndex(where: {
                                    switch $0 {
                                        case .toastAlert:
                                            return true
                                        default:
                                            return false
                                    }
                                }) {
                                    if $0[index] != ChatTitlePanelContext.toastAlert(message) {
                                        var updatedContexts = $0
                                        updatedContexts[index] = .toastAlert(message)
                                        return updatedContexts
                                    } else {
                                        return $0
                                    }
                                } else {
                                    var updatedContexts = $0
                                    updatedContexts.append(.toastAlert(message))
                                    return updatedContexts.sorted()
                                }
                            } else {
                                if let index = $0.firstIndex(where: {
                                    switch $0 {
                                        case .toastAlert:
                                            return true
                                        default:
                                            return false
                                    }
                                }) {
                                    var updatedContexts = $0
                                    updatedContexts.remove(at: index)
                                    return updatedContexts
                                } else {
                                    return $0
                                }
                            }
                        }
                    })
                }
            })
        
        self.audioRecorderDisposable = (self.audioRecorder.get()
        |> deliverOnMainQueue).startStrict(next: { [weak self] audioRecorder in
            if let strongSelf = self {
                if strongSelf.audioRecorderValue !== audioRecorder {
                    strongSelf.audioRecorderValue = audioRecorder
                    strongSelf.lockOrientation = audioRecorder != nil
                    
                    strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
                        $0.updatedInputTextPanelState { panelState in
                            let isLocked = strongSelf.lockMediaRecordingRequestId == strongSelf.beginMediaRecordingRequestId
                            if let audioRecorder = audioRecorder {
                                if panelState.mediaRecordingState == nil {
                                    return panelState.withUpdatedMediaRecordingState(.audio(recorder: audioRecorder, isLocked: isLocked))
                                }
                            } else {
                                if case .waitingForPreview = panelState.mediaRecordingState {
                                    return panelState
                                }
                                return panelState.withUpdatedMediaRecordingState(nil)
                            }
                            return panelState
                        }
                    })
                    strongSelf.audioRecorderStatusDisposable?.dispose()
                    
                    if let audioRecorder = audioRecorder {
                        if !audioRecorder.beginWithTone {
                            strongSelf.recorderFeedback?.impact(.light)
                        }
                        audioRecorder.start()
                        strongSelf.audioRecorderStatusDisposable = (audioRecorder.recordingState
                        |> deliverOnMainQueue).startStrict(next: { value in
                            if case .stopped = value {
                                self?.stopMediaRecorder()
                            }
                        })
                    } else {
                        strongSelf.audioRecorderStatusDisposable = nil
                    }
                    strongSelf.updateDownButtonVisibility()
                }
            }
        })
        
        self.videoRecorderDisposable = (self.videoRecorder.get()
        |> deliverOnMainQueue).startStrict(next: { [weak self] videoRecorder in
            if let strongSelf = self {
                if strongSelf.videoRecorderValue !== videoRecorder {
                    let previousVideoRecorderValue = strongSelf.videoRecorderValue
                    strongSelf.videoRecorderValue = videoRecorder
                    
                    strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
                        $0.updatedInputTextPanelState { panelState in
                            if let videoRecorder = videoRecorder {
                                if panelState.mediaRecordingState == nil {
                                    let recordingStatus = videoRecorder.recordingStatus
                                    return panelState.withUpdatedMediaRecordingState(.video(status: .recording(InstantVideoControllerRecordingStatus(micLevel: recordingStatus.micLevel, duration: recordingStatus.duration)), isLocked: strongSelf.lockMediaRecordingRequestId == strongSelf.beginMediaRecordingRequestId))
                                }
                            } else {
                                return panelState.withUpdatedMediaRecordingState(nil)
                            }
                            return panelState
                        }
                    })
                    
                    if let videoRecorder = videoRecorder {
                        strongSelf.recorderFeedback?.impact(.light)
                        
                        videoRecorder.onStop = {
                            if let strongSelf = self {
                                strongSelf.dismissMediaRecorder(.pause)
                            }
                        }
                        strongSelf.present(videoRecorder, in: .window(.root))
                        
                        if strongSelf.lockMediaRecordingRequestId == strongSelf.beginMediaRecordingRequestId {
                            videoRecorder.lockVideoRecording()
                        }
                    }
                    strongSelf.updateDownButtonVisibility()
                    
                    if let previousVideoRecorderValue = previousVideoRecorderValue {
                        previousVideoRecorderValue.discardVideo()
                    }
                }
            }
        })
        
        if let botStart = botStart, case .automatic = botStart.behavior {
            self.startBot(botStart.payload)
        }
        
        let activitySpace: PeerActivitySpace?
        switch self.chatLocation {
        case let .peer(peerId):
            activitySpace = PeerActivitySpace(peerId: peerId, category: .global)
        case let .replyThread(replyThreadMessage):
            activitySpace = PeerActivitySpace(peerId: replyThreadMessage.peerId, category: .thread(replyThreadMessage.threadId))
        case .customChatContents:
            activitySpace = nil
        }
        
        if let activitySpace = activitySpace {
            self.inputActivityDisposable = (self.typingActivityPromise.get()
            |> deliverOnMainQueue).startStrict(next: { [weak self] value in
                if let strongSelf = self, strongSelf.presentationInterfaceState.interfaceState.editMessage == nil && strongSelf.presentationInterfaceState.subject != .scheduledMessages && strongSelf.presentationInterfaceState.currentSendAsPeerId == nil {
                    strongSelf.context.account.updateLocalInputActivity(peerId: activitySpace, activity: .typingText, isPresent: value)
                }
            })
        
            self.choosingStickerActivityDisposable = (self.choosingStickerActivityPromise.get()
            |> mapToSignal { value -> Signal<Bool, NoError> in
                if value {
                    return .single(true)
                } else {
                    return .single(false) |> delay(2.0, queue: Queue.mainQueue())
                }
            }
            |> deliverOnMainQueue).startStrict(next: { [weak self] value in
                if let strongSelf = self, strongSelf.presentationInterfaceState.interfaceState.editMessage == nil && strongSelf.presentationInterfaceState.subject != .scheduledMessages && strongSelf.presentationInterfaceState.currentSendAsPeerId == nil {
                    if value {
                        strongSelf.context.account.updateLocalInputActivity(peerId: activitySpace, activity: .typingText, isPresent: false)
                    }
                    strongSelf.context.account.updateLocalInputActivity(peerId: activitySpace, activity: .choosingSticker, isPresent: value)
                }
            })
            
            self.recordingActivityDisposable = (self.recordingActivityPromise.get()
            |> deliverOnMainQueue).startStrict(next: { [weak self] value in
                if let strongSelf = self, strongSelf.presentationInterfaceState.interfaceState.editMessage == nil && strongSelf.presentationInterfaceState.subject != .scheduledMessages && strongSelf.presentationInterfaceState.currentSendAsPeerId == nil {
                    strongSelf.acquiredRecordingActivityDisposable?.dispose()
                    switch value {
                        case .voice:
                            strongSelf.acquiredRecordingActivityDisposable = strongSelf.context.account.acquireLocalInputActivity(peerId: activitySpace, activity: .recordingVoice)
                        case .instantVideo:
                            strongSelf.acquiredRecordingActivityDisposable = strongSelf.context.account.acquireLocalInputActivity(peerId: activitySpace, activity: .recordingInstantVideo)
                        case .none:
                            strongSelf.acquiredRecordingActivityDisposable = nil
                    }
                }
            })
        }
        
        let themeEmoticon: Signal<String?, NoError> = self.chatThemeEmoticonPromise.get()
        |> distinctUntilChanged
    
        let uploadingChatWallpaper: Signal<TelegramWallpaper?, NoError>
        if let peerId = self.chatLocation.peerId {
            uploadingChatWallpaper = self.context.account.pendingPeerMediaUploadManager.uploadingPeerMedia
            |> map { uploadingPeerMedia -> TelegramWallpaper? in
                if let item = uploadingPeerMedia[peerId], case let .wallpaper(wallpaper, _) = item.content {
                    return wallpaper
                } else {
                    return nil
                }
            }
            |> distinctUntilChanged
        } else {
            uploadingChatWallpaper = .single(nil)
        }
        
        let chatWallpaper: Signal<TelegramWallpaper?, NoError> = combineLatest(self.chatWallpaperPromise.get(), uploadingChatWallpaper)
        |> map { chatWallpaper, uploadingChatWallpaper in
            return uploadingChatWallpaper ?? chatWallpaper
        }
        |> distinctUntilChanged
        
        let themeSettings = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings])
        |> map { sharedData -> PresentationThemeSettings in
            let themeSettings: PresentationThemeSettings
            if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings]?.get(PresentationThemeSettings.self) {
                themeSettings = current
            } else {
                themeSettings = PresentationThemeSettings.defaultSettings
            }
            return themeSettings
        }
        
        let accountManager = context.sharedContext.accountManager
        let currentThemeEmoticon = Atomic<(String?, Bool)?>(value: nil)
        self.presentationDataDisposable = combineLatest(
            queue: Queue.mainQueue(),
            context.sharedContext.presentationData,
            themeSettings,
            context.engine.themes.getChatThemes(accountManager: accountManager, onlyCached: true),
            themeEmoticon,
            self.themeEmoticonAndDarkAppearancePreviewPromise.get(),
            chatWallpaper
        ).startStrict(next: { [weak self] presentationData, themeSettings, chatThemes, themeEmoticon, themeEmoticonAndDarkAppearance, chatWallpaper in
            if let strongSelf = self {
                let (themeEmoticonPreview, darkAppearancePreview) = themeEmoticonAndDarkAppearance
                
                var chatWallpaper = chatWallpaper
                
                let previousTheme = strongSelf.presentationData.theme
                let previousStrings = strongSelf.presentationData.strings
                let previousChatWallpaper = strongSelf.presentationData.chatWallpaper
                
                var themeEmoticon = themeEmoticon
                if let themeEmoticonPreview = themeEmoticonPreview {
                    if !themeEmoticonPreview.isEmpty {
                        if themeEmoticon?.strippedEmoji != themeEmoticonPreview.strippedEmoji {
                            chatWallpaper = nil
                            themeEmoticon = themeEmoticonPreview
                        }
                    } else {
                        themeEmoticon = nil
                    }
                }
                if strongSelf.chatLocation.peerId == strongSelf.context.account.peerId {
                    themeEmoticon = nil
                }
                                
                var presentationData = presentationData
                var useDarkAppearance = presentationData.theme.overallDarkAppearance

                if let forcedTheme = strongSelf.forcedTheme {
                    presentationData = presentationData.withUpdated(theme: forcedTheme)
                } else {
                    if let wallpaper = chatWallpaper, case let .emoticon(wallpaperEmoticon) = wallpaper, let theme = chatThemes.first(where: { $0.emoticon?.strippedEmoji == wallpaperEmoticon.strippedEmoji }) {
                        let themeSettings: TelegramThemeSettings?
                        if let matching = theme.settings?.first(where: { $0.baseTheme == presentationData.theme.referenceTheme.baseTheme }) {
                            themeSettings = matching
                        } else {
                            themeSettings = theme.settings?.first
                        }
                        if let themeWallpaper = themeSettings?.wallpaper {
                            chatWallpaper = themeWallpaper
                        }
                    }
                    if let themeEmoticon = themeEmoticon, let theme = chatThemes.first(where: { $0.emoticon?.strippedEmoji == themeEmoticon.strippedEmoji }) {
                        if let darkAppearancePreview = darkAppearancePreview {
                            useDarkAppearance = darkAppearancePreview
                        }
                        if let theme = makePresentationTheme(cloudTheme: theme, dark: useDarkAppearance) {
                            theme.forceSync = true
                            presentationData = presentationData.withUpdated(theme: theme).withUpdated(chatWallpaper: theme.chat.defaultWallpaper)
                            
                            Queue.mainQueue().after(1.0, {
                                theme.forceSync = false
                            })
                        }
                    } else if let darkAppearancePreview = darkAppearancePreview {
                        useDarkAppearance = darkAppearancePreview
                        let lightTheme: PresentationTheme
                        let lightWallpaper: TelegramWallpaper
                        
                        let darkTheme: PresentationTheme
                        let darkWallpaper: TelegramWallpaper
                        
                        if presentationData.autoNightModeTriggered {
                            darkTheme = presentationData.theme
                            darkWallpaper = presentationData.chatWallpaper
                            
                            var currentColors = themeSettings.themeSpecificAccentColors[themeSettings.theme.index]
                            if let colors = currentColors, colors.baseColor == .theme {
                                currentColors = nil
                            }
                            
                            let themeSpecificWallpaper = (themeSettings.themeSpecificChatWallpapers[coloredThemeIndex(reference: themeSettings.theme, accentColor: currentColors)] ?? themeSettings.themeSpecificChatWallpapers[themeSettings.theme.index])
                            
                            if let themeSpecificWallpaper = themeSpecificWallpaper {
                                lightWallpaper = themeSpecificWallpaper
                            } else {
                                let theme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: themeSettings.theme, accentColor: currentColors?.color, bubbleColors: currentColors?.customBubbleColors ?? [], wallpaper: currentColors?.wallpaper, baseColor: currentColors?.baseColor, preview: true) ?? defaultPresentationTheme
                                lightWallpaper = theme.chat.defaultWallpaper
                            }
                            
                            var preferredBaseTheme: TelegramBaseTheme?
                            if let baseTheme = themeSettings.themePreferredBaseTheme[themeSettings.theme.index], [.classic, .day].contains(baseTheme) {
                                preferredBaseTheme = baseTheme
                            }
                            
                            lightTheme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: themeSettings.theme, baseTheme: preferredBaseTheme, accentColor: currentColors?.color, bubbleColors: currentColors?.customBubbleColors ?? [], wallpaper: currentColors?.wallpaper, baseColor: currentColors?.baseColor, serviceBackgroundColor: defaultServiceBackgroundColor) ?? defaultPresentationTheme
                        } else {
                            lightTheme = presentationData.theme
                            lightWallpaper = presentationData.chatWallpaper
                            
                            let automaticTheme = themeSettings.automaticThemeSwitchSetting.theme
                            let effectiveColors = themeSettings.themeSpecificAccentColors[automaticTheme.index]
                            let themeSpecificWallpaper = (themeSettings.themeSpecificChatWallpapers[coloredThemeIndex(reference: automaticTheme, accentColor: effectiveColors)] ?? themeSettings.themeSpecificChatWallpapers[automaticTheme.index])
                            
                            var preferredBaseTheme: TelegramBaseTheme?
                            if let baseTheme = themeSettings.themePreferredBaseTheme[automaticTheme.index], [.night, .tinted].contains(baseTheme) {
                                preferredBaseTheme = baseTheme
                            } else {
                                preferredBaseTheme = .night
                            }
                            
                            darkTheme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: automaticTheme, baseTheme: preferredBaseTheme, accentColor: effectiveColors?.color, bubbleColors: effectiveColors?.customBubbleColors ?? [], wallpaper: effectiveColors?.wallpaper, baseColor: effectiveColors?.baseColor, serviceBackgroundColor: defaultServiceBackgroundColor) ?? defaultPresentationTheme
                            
                            if let themeSpecificWallpaper = themeSpecificWallpaper {
                                darkWallpaper = themeSpecificWallpaper
                            } else {
                                switch lightWallpaper {
                                case .builtin, .color, .gradient:
                                    darkWallpaper = darkTheme.chat.defaultWallpaper
                                case .file:
                                    if lightWallpaper.isPattern {
                                        darkWallpaper = darkTheme.chat.defaultWallpaper
                                    } else {
                                        darkWallpaper = lightWallpaper
                                    }
                                default:
                                    darkWallpaper = lightWallpaper
                                }
                            }
                        }
                        
                        if darkAppearancePreview {
                            darkTheme.forceSync = true
                            Queue.mainQueue().after(1.0, {
                                darkTheme.forceSync = false
                            })
                            presentationData = presentationData.withUpdated(theme: darkTheme).withUpdated(chatWallpaper: darkWallpaper)
                        } else {
                            lightTheme.forceSync = true
                            Queue.mainQueue().after(1.0, {
                                lightTheme.forceSync = false
                            })
                            presentationData = presentationData.withUpdated(theme: lightTheme).withUpdated(chatWallpaper: lightWallpaper)
                        }
                    }
                }
                
                if let forcedWallpaper = strongSelf.forcedWallpaper {
                    presentationData = presentationData.withUpdated(chatWallpaper: forcedWallpaper)
                } else if let chatWallpaper {
                    presentationData = presentationData.withUpdated(chatWallpaper: chatWallpaper)
                }
                
                let isFirstTime = !strongSelf.didSetPresentationData
                strongSelf.presentationData = presentationData
                strongSelf.didSetPresentationData = true
                
                let previousThemeEmoticon = currentThemeEmoticon.swap((themeEmoticon, useDarkAppearance))
                
                if isFirstTime || previousTheme != presentationData.theme || previousStrings !== presentationData.strings || presentationData.chatWallpaper != previousChatWallpaper {
                    strongSelf.themeAndStringsUpdated()
                    
                    controllerInteraction.updatedPresentationData = strongSelf.updatedPresentationData
                    strongSelf.presentationDataPromise.set(.single(strongSelf.presentationData))
                    
                    if !isFirstTime && (previousThemeEmoticon?.0 != themeEmoticon || previousThemeEmoticon?.1 != useDarkAppearance) {
                        strongSelf.presentCrossfadeSnapshot()
                    }
                }
                strongSelf.presentationReady.set(.single(true))
            }
        })
        
        self.automaticMediaDownloadSettingsDisposable = (context.sharedContext.automaticMediaDownloadSettings
        |> deliverOnMainQueue).startStrict(next: { [weak self] downloadSettings in
            if let strongSelf = self, strongSelf.automaticMediaDownloadSettings != downloadSettings {
                strongSelf.automaticMediaDownloadSettings = downloadSettings
                strongSelf.controllerInteraction?.automaticMediaDownloadSettings = downloadSettings
                if strongSelf.isNodeLoaded {
                    strongSelf.chatDisplayNode.updateAutomaticMediaDownloadSettings(downloadSettings)
                }
            }
        })
        
        self.stickerSettingsDisposable = combineLatest(queue: Queue.mainQueue(), context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.stickerSettings]), self.disableStickerAnimationsPromise.get()).startStrict(next: { [weak self] sharedData, disableStickerAnimations in
            var stickerSettings = StickerSettings.defaultSettings
            if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.stickerSettings]?.get(StickerSettings.self) {
                stickerSettings = value
            }
            
            let chatStickerSettings = ChatInterfaceStickerSettings(stickerSettings: stickerSettings)
            if let strongSelf = self, strongSelf.stickerSettings != chatStickerSettings || strongSelf.disableStickerAnimationsValue != disableStickerAnimations {
                strongSelf.stickerSettings = chatStickerSettings
                strongSelf.disableStickerAnimationsValue = disableStickerAnimations
                strongSelf.controllerInteraction?.stickerSettings = chatStickerSettings
                if strongSelf.isNodeLoaded {
                    strongSelf.chatDisplayNode.updateStickerSettings(chatStickerSettings, forceStopAnimations: disableStickerAnimations)
                }
            }
        })
        
        var wasInForeground = true
        self.applicationInForegroundDisposable = (context.sharedContext.applicationBindings.applicationInForeground
        |> distinctUntilChanged
        |> deliverOn(Queue.mainQueue())).startStrict(next: { [weak self] value in
            if let strongSelf = self, strongSelf.isNodeLoaded {
                if !value {
                    strongSelf.saveInterfaceState()
                    strongSelf.raiseToListen?.applicationResignedActive()
                    
                    strongSelf.stopMediaRecorder()
                } else {
                    if !wasInForeground {
                        strongSelf.chatDisplayNode.recursivelyEnsureDisplaySynchronously(true)
                    }
                }
                wasInForeground = value
            }
        })
        
        if case let .peer(peerId) = chatLocation, peerId.namespace == Namespaces.Peer.SecretChat {
            self.applicationInFocusDisposable = (context.sharedContext.applicationBindings.applicationIsActive
            |> distinctUntilChanged
            |> deliverOn(Queue.mainQueue())).startStrict(next: { [weak self] value in
                guard let strongSelf = self, strongSelf.isNodeLoaded else {
                    return
                }
                strongSelf.chatDisplayNode.updateIsBlurred(!value)
            })
        }
        

        
        self.canReadHistoryDisposable = (combineLatest(
            context.sharedContext.applicationBindings.applicationInForeground,
            self.canReadHistory.get(),
            self.hasBrowserOrAppInFront.get()
        ) |> map { inForeground, globallyEnabled, hasBrowserOrWebAppInFront in
            return inForeground && globallyEnabled && !hasBrowserOrWebAppInFront
        } |> deliverOnMainQueue).startStrict(next: { [weak self] value in
            if let strongSelf = self, strongSelf.canReadHistoryValue != value {
                strongSelf.canReadHistoryValue = value
                strongSelf.raiseToListen?.enabled = value
                strongSelf.isReminderActivityEnabled = value
                strongSelf.updateReminderActivity()
            }
        })
        
        self.networkStateDisposable = (context.account.networkState |> deliverOnMainQueue).startStrict(next: { [weak self] state in
            if let strongSelf = self, case .standard(.default) = strongSelf.presentationInterfaceState.mode {
                strongSelf.chatTitleView?.networkState = state
            }
        })
        
        if case let .messageOptions(_, messageIds, _) = self.subject, messageIds.count > 1 {
            self.updateChatPresentationInterfaceState(interactive: false, { state in
                return state.updatedInterfaceState({ $0.withUpdatedSelectedMessages(messageIds) })
            })
        }
    }
    
    required public init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    deinit {
        let _ = ChatControllerCount.modify { value in
            return value - 1
        }
                
        let deallocate: () -> Void = {
            self.historyStateDisposable?.dispose()
            self.messageIndexDisposable.dispose()
            self.navigationActionDisposable.dispose()
            self.galleryHiddenMesageAndMediaDisposable.dispose()
            self.temporaryHiddenGalleryMediaDisposable.dispose()
            self.peerDisposable.dispose()
            self.accountPeerDisposable?.dispose()
            self.titleDisposable.dispose()
            self.messageContextDisposable.dispose()
            self.controllerNavigationDisposable.dispose()
            self.sentMessageEventsDisposable.dispose()
            self.failedMessageEventsDisposable.dispose()
            self.sentPeerMediaMessageEventsDisposable.dispose()
            self.messageActionCallbackDisposable.dispose()
            self.messageActionUrlAuthDisposable.dispose()
            self.editMessageDisposable.dispose()
            self.editMessageErrorsDisposable.dispose()
            self.enqueueMediaMessageDisposable.dispose()
            self.resolvePeerByNameDisposable?.dispose()
            self.shareStatusDisposable?.dispose()
            self.clearCacheDisposable?.dispose()
            self.bankCardDisposable?.dispose()
            self.botCallbackAlertMessageDisposable?.dispose()
            self.selectMessagePollOptionDisposables?.dispose()
            for (_, info) in self.contextQueryStates {
                info.1.dispose()
            }
            self.urlPreviewQueryState?.1.dispose()
            self.editingUrlPreviewQueryState?.1.dispose()
            self.replyMessageState?.1.dispose()
            self.audioRecorderDisposable?.dispose()
            self.audioRecorderStatusDisposable?.dispose()
            self.videoRecorderDisposable?.dispose()
            self.buttonKeyboardMessageDisposable?.dispose()
            self.cachedDataDisposable?.dispose()
            self.resolveUrlDisposable?.dispose()
            self.chatUnreadCountDisposable?.dispose()
            self.buttonUnreadCountDisposable?.dispose()
            self.chatUnreadMentionCountDisposable?.dispose()
            self.peerInputActivitiesDisposable?.dispose()
            self.interactiveEmojiSyncDisposable.dispose()
            self.recentlyUsedInlineBotsDisposable?.dispose()
            self.unpinMessageDisposable?.dispose()
            self.inputActivityDisposable?.dispose()
            self.recordingActivityDisposable?.dispose()
            self.acquiredRecordingActivityDisposable?.dispose()
            self.presentationDataDisposable?.dispose()
            self.searchDisposable?.dispose()
            self.applicationInForegroundDisposable?.dispose()
            self.applicationInFocusDisposable?.dispose()
            self.canReadHistoryDisposable?.dispose()
            self.networkStateDisposable?.dispose()
            self.chatAdditionalDataDisposable.dispose()
            self.shareStatusDisposable?.dispose()
            self.context.sharedContext.mediaManager.galleryHiddenMediaManager.removeTarget(self)
            self.preloadHistoryPeerIdDisposable.dispose()
            self.preloadNextChatPeerIdDisposable.dispose()
            self.reportIrrelvantGeoDisposable?.dispose()
            self.reminderActivity?.invalidate()
            self.updateSlowmodeStatusDisposable.dispose()
            self.keepPeerInfoScreenDataHotDisposable.dispose()
            self.preloadAvatarDisposable.dispose()
            self.peekTimerDisposable.dispose()
            self.hasActiveGroupCallDisposable?.dispose()
            self.createVoiceChatDisposable.dispose()
            self.checksTooltipDisposable.dispose()
            self.peerSuggestionsDisposable.dispose()
            self.peerSuggestionsDismissDisposable.dispose()
            self.selectAddMemberDisposable.dispose()
            self.addMemberDisposable.dispose()
            self.joinChannelDisposable.dispose()
            self.nextChannelToReadDisposable?.dispose()
            self.inviteRequestsDisposable.dispose()
            self.sendAsPeersDisposable?.dispose()
            self.preloadAttachBotIconsDisposables?.dispose()
            self.keepMessageCountersSyncrhonizedDisposable?.dispose()
            self.keepSavedMessagesSyncrhonizedDisposable?.dispose()
            self.translationStateDisposable?.dispose()
            self.chatLanguagePredictionDisposable?.dispose()
            self.premiumGiftSuggestionDisposable?.dispose()
            self.powerSavingMonitoringDisposable?.dispose()
            self.saveMediaDisposable?.dispose()
            self.giveawayStatusDisposable?.dispose()
            self.nameColorDisposable?.dispose()
            self.choosingStickerActivityDisposable?.dispose()
            self.automaticMediaDownloadSettingsDisposable?.dispose()
            self.stickerSettingsDisposable?.dispose()
            self.searchQuerySuggestionState?.1.dispose()
            self.preloadSavedMessagesChatsDisposable?.dispose()
            self.recorderDataDisposable.dispose()
            self.displaySendWhenOnlineTipDisposable.dispose()
            self.networkSpeedEventsDisposable?.dispose()
        }
        deallocate()
    }
    
    public func updatePresentationMode(_ mode: ChatControllerPresentationMode) {
        self.updateChatPresentationInterfaceState(animated: false, interactive: false, {
            return $0.updatedMode(mode)
        })
    }
    
    var chatDisplayNode: ChatControllerNode {
        get {
            return super.displayNode as! ChatControllerNode
        }
    }
    
    func updateStatusBarPresentation(animated: Bool = false) {
        if !self.galleryPresentationContext.controllers.isEmpty, let statusBarStyle = (self.galleryPresentationContext.controllers.last?.0 as? ViewController)?.statusBar.statusBarStyle {
            self.statusBar.updateStatusBarStyle(statusBarStyle, animated: animated)
        } else {
            switch self.presentationInterfaceState.mode {
            case let .standard(standardMode):
                switch standardMode {
                case .embedded:
                    self.statusBar.statusBarStyle = .Ignore
                default:
                    self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
                    self.deferScreenEdgeGestures = []
                }
            case .overlay:
                self.statusBar.statusBarStyle = .Hide
                self.deferScreenEdgeGestures = [.top]
            case .inline:
                self.statusBar.statusBarStyle = .Ignore
            }
        }
    }
    
    func themeAndStringsUpdated() {
        self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
        self.updateStatusBarPresentation()
        self.updateNavigationBarPresentation()
        self.updateChatPresentationInterfaceState(animated: false, interactive: false, { state in
            var state = state
            state = state.updatedPresentationReady(self.didSetPresentationData)
            state = state.updatedTheme(self.presentationData.theme)
            state = state.updatedStrings(self.presentationData.strings)
            state = state.updatedDateTimeFormat(self.presentationData.dateTimeFormat)
            state = state.updatedChatWallpaper(self.presentationData.chatWallpaper)
            state = state.updatedBubbleCorners(self.presentationData.chatBubbleCorners)
            return state
        })
        
        self.currentContextController?.updateTheme(presentationData: self.presentationData)
    }
    
    func updateNavigationBarPresentation() {
        let navigationBarTheme: NavigationBarTheme
        
        let presentationTheme: PresentationTheme
        if let forcedNavigationBarTheme = self.forcedNavigationBarTheme {
            presentationTheme = forcedNavigationBarTheme
            navigationBarTheme = NavigationBarTheme(rootControllerTheme: forcedNavigationBarTheme, hideBackground: false, hideBadge: true)
        } else if self.hasEmbeddedTitleContent {
            presentationTheme = self.presentationData.theme
            navigationBarTheme = NavigationBarTheme(rootControllerTheme: defaultDarkPresentationTheme, hideBackground: self.context.sharedContext.immediateExperimentalUISettings.playerEmbedding ? true : false, hideBadge: true)
        } else {
            presentationTheme = self.presentationData.theme
            navigationBarTheme = NavigationBarTheme(rootControllerTheme: self.presentationData.theme, hideBackground: self.context.sharedContext.immediateExperimentalUISettings.playerEmbedding ? true : false, hideBadge: false)
        }
        
        self.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)))
        
        self.chatTitleView?.updateThemeAndStrings(theme: presentationTheme, strings: self.presentationData.strings, hasEmbeddedTitleContent: self.hasEmbeddedTitleContent)
    }
    
    func topPinnedMessageSignal(latest: Bool) -> Signal<ChatPinnedMessage?, NoError> {
        var pinnedPeerId: EnginePeer.Id?
        let threadId = self.chatLocation.threadId
        let loadState: Signal<Bool, NoError> = self.chatDisplayNode.historyNode.historyState.get()
        |> map { state -> Bool in
            switch state {
            case .loading:
                return false
            default:
                return true
            }
        }
        |> distinctUntilChanged
        
        switch self.chatLocation {
        case let .peer(id):
            pinnedPeerId = id
        case let .replyThread(message):
            if message.isForumPost {
                pinnedPeerId = self.chatLocation.peerId
            }
        default:
            break
        }
        
        if let peerId = pinnedPeerId {
            let topPinnedMessage: Signal<ChatPinnedMessage?, NoError>
            
            enum ReferenceMessage {
                struct Loaded {
                    var id: MessageId
                    var minId: MessageId
                    var isScrolled: Bool
                }
                
                case ready(Loaded)
                case loading
            }
            
            let referenceMessage: Signal<ReferenceMessage?, NoError>
            if latest {
                referenceMessage = .single(nil)
            } else {
                referenceMessage = combineLatest(
                    queue: Queue.mainQueue(),
                    self.scrolledToMessageId.get(),
                    self.chatDisplayNode.historyNode.topVisibleMessageRange.get()
                )
                |> map { scrolledToMessageId, topVisibleMessageRange -> ReferenceMessage? in
                    if let topVisibleMessageRange = topVisibleMessageRange, topVisibleMessageRange.isLoading {
                        return .loading
                    }
                    
                    let bottomVisibleMessage = topVisibleMessageRange?.lowerBound.id
                    let topVisibleMessage = topVisibleMessageRange?.upperBound.id
                    
                    if let scrolledToMessageId = scrolledToMessageId {
                        if let topVisibleMessage, let bottomVisibleMessage {
                            if scrolledToMessageId.allowedReplacementDirection.contains(.up) && topVisibleMessage < scrolledToMessageId.id {
                                return .ready(ReferenceMessage.Loaded(id: topVisibleMessage, minId: bottomVisibleMessage, isScrolled: false))
                            }
                        }
                        return .ready(ReferenceMessage.Loaded(id: scrolledToMessageId.id, minId: scrolledToMessageId.id, isScrolled: true))
                    } else if let topVisibleMessage, let bottomVisibleMessage {
                        return .ready(ReferenceMessage.Loaded(id: topVisibleMessage, minId: bottomVisibleMessage, isScrolled: false))
                    } else {
                        return nil
                    }
                }
            }
            
            let context = self.context
            
            func pinnedHistorySignal(anchorMessageId: MessageId?, count: Int) -> Signal<ChatHistoryViewUpdate, NoError> {
                let location: ChatHistoryLocation
                if let anchorMessageId = anchorMessageId {
                    location = .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: .id(anchorMessageId), quote: nil), count: count, highlight: false, setupReply: false)
                } else {
                    location = .Initial(count: count)
                }
                
                let chatLocation: ChatLocation
                if let threadId {
                    chatLocation = .replyThread(message: ChatReplyThreadMessage(peerId: peerId, threadId: threadId, channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false))
                } else {
                    chatLocation = .peer(id: peerId)
                }
                
                return (chatHistoryViewForLocation(ChatHistoryLocationInput(content: location, id: 0), ignoreMessagesInTimestampRange: nil, ignoreMessageIds: Set(), context: context, chatLocation: chatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>(value: nil), scheduled: false, fixedCombinedReadStates: nil, tag: .tag(MessageTags.pinned), appendMessagesFromTheSameGroup: false, additionalData: [], orderStatistics: .combinedLocation)
                |> castError(Bool.self)
                |> mapToSignal { update -> Signal<ChatHistoryViewUpdate, Bool> in
                    switch update {
                    case let .Loading(_, type):
                        if case .Generic(.FillHole) = type {
                            return .fail(true)
                        }
                    case let .HistoryView(_, type, _, _, _, _, _):
                        if case .Generic(.FillHole) = type {
                            return .fail(true)
                        }
                    }
                    return .single(update)
                })
                |> restartIfError
            }
            
            struct TopMessage {
                var message: Message
                var index: Int
            }
            
            let topMessage = pinnedHistorySignal(anchorMessageId: nil, count: 10)
            |> map { update -> TopMessage? in
                switch update {
                case .Loading:
                    return nil
                case let .HistoryView(viewValue, _, _, _, _, _, _):
                    if let entry = viewValue.entries.last {
                        let index: Int
                        if let location = entry.location {
                            index = location.index
                        } else {
                            index = viewValue.entries.count - 1
                        }
                        
                        return TopMessage(
                            message: entry.message,
                            index: index
                        )
                    } else {
                        return nil
                    }
                }
            }
            
            let loadCount = 10
            
            struct PinnedHistory {
                struct PinnedMessage {
                    var message: Message
                    var index: Int
                }
                
                var messages: [PinnedMessage]
                var totalCount: Int
            }
            
            let adjustedReplyHistory: Signal<PinnedHistory, NoError>
            if latest {
                adjustedReplyHistory = pinnedHistorySignal(anchorMessageId: nil, count: loadCount)
                |> map { view -> PinnedHistory in
                    switch view {
                    case .Loading:
                        return PinnedHistory(messages: [], totalCount: 0)
                    case let .HistoryView(viewValue, _, _, _, _, _, _):
                        var messages: [PinnedHistory.PinnedMessage] = []
                        var totalCount = viewValue.entries.count
                        for i in 0 ..< viewValue.entries.count {
                            let index: Int
                            if !viewValue.holeEarlier && viewValue.earlierId == nil {
                                index = i
                            } else if let location = viewValue.entries[i].location {
                                index = location.index
                                totalCount = location.count
                            } else {
                                index = i
                            }
                            messages.append(PinnedHistory.PinnedMessage(
                                message: viewValue.entries[i].message,
                                index: index
                            ))
                        }
                        return PinnedHistory(messages: messages, totalCount: totalCount)
                    }
                }
            } else {
                adjustedReplyHistory = (Signal<PinnedHistory, NoError> { subscriber in
                    var referenceMessageValue: ReferenceMessage?
                    var view: ChatHistoryViewUpdate?
                    
                    let updateState: () -> Void = {
                        guard let view = view else {
                            return
                        }
                        guard case let .HistoryView(viewValue, _, _, _, _, _, _) = view else {
                            subscriber.putNext(PinnedHistory(messages: [], totalCount: 0))
                            return
                        }
                        
                        var messages: [PinnedHistory.PinnedMessage] = []
                        for i in 0 ..< viewValue.entries.count {
                            messages.append(PinnedHistory.PinnedMessage(
                                message: viewValue.entries[i].message,
                                index: i
                            ))
                        }
                        let result = PinnedHistory(messages: messages, totalCount: messages.count)
                        
                        if case let .ready(loaded) = referenceMessageValue {
                            let referenceId = loaded.id
                            
                            if viewValue.entries.count < loadCount {
                                subscriber.putNext(result)
                            } else if referenceId < viewValue.entries[1].message.id {
                                if viewValue.earlierId != nil {
                                    subscriber.putCompletion()
                                } else {
                                    subscriber.putNext(result)
                                }
                            } else if referenceId > viewValue.entries[viewValue.entries.count - 2].message.id {
                                if viewValue.laterId != nil {
                                    subscriber.putCompletion()
                                } else {
                                    subscriber.putNext(result)
                                }
                            } else {
                                subscriber.putNext(result)
                            }
                        } else {
                            if viewValue.isLoading {
                                subscriber.putNext(result)
                            } else  if viewValue.holeLater || viewValue.laterId != nil {
                                subscriber.putCompletion()
                            } else {
                                subscriber.putNext(result)
                            }
                        }
                    }
                    
                    var initializedView = false
                    let viewDisposable = MetaDisposable()
                    
                    let referenceDisposable = (referenceMessage
                    |> deliverOnMainQueue).startStrict(next: { referenceMessage in
                        referenceMessageValue = referenceMessage
                        if !initializedView {
                            initializedView = true
                            //print("reload at \(String(describing: referenceMessage?.id)) disposable \(unsafeBitCast(viewDisposable, to: UInt64.self))")
                            var referenceId: MessageId?
                            if case let .ready(loaded) = referenceMessage {
                                referenceId = loaded.id
                            }
                            viewDisposable.set((pinnedHistorySignal(anchorMessageId: referenceId, count: loadCount)
                            |> deliverOnMainQueue).startStrict(next: { next in
                                view = next
                                updateState()
                            }))
                        }
                        updateState()
                    })
                    
                    return ActionDisposable {
                        //print("dispose \(unsafeBitCast(viewDisposable, to: UInt64.self))")
                        referenceDisposable.dispose()
                        viewDisposable.dispose()
                    }
                }
                |> runOn(.mainQueue()))
                |> restart
            }
            
            topPinnedMessage = combineLatest(queue: .mainQueue(),
                adjustedReplyHistory,
                topMessage,
                referenceMessage,
                loadState
            )
            |> map { pinnedMessages, topMessage, referenceMessage, loadState -> ChatPinnedMessage? in
                if !loadState {
                    return nil
                }
                
                var message: ChatPinnedMessage?
                
                let topMessageId: MessageId
                if pinnedMessages.messages.isEmpty {
                    return nil
                }
                topMessageId = topMessage?.message.id ?? pinnedMessages.messages[pinnedMessages.messages.count - 1].message.id
                
                if case let .ready(referenceMessage) = referenceMessage, referenceMessage.isScrolled, !pinnedMessages.messages.isEmpty, referenceMessage.id == pinnedMessages.messages[0].message.id, let topMessage = topMessage {
                    var index = topMessage.index
                    for message in pinnedMessages.messages {
                        if message.message.id == topMessage.message.id {
                            index = message.index
                            break
                        }
                    }
                    
                    if threadId != nil {
                        if referenceMessage.minId <= topMessage.message.id {
                            return nil
                        }
                    }
                    return ChatPinnedMessage(message: topMessage.message, index: index, totalCount: pinnedMessages.totalCount, topMessageId: topMessageId)
                }
                
                //print("reference: \(String(describing: referenceMessage?.id.id)) entries: \(view.entries.map(\.index.id.id))")
                for i in 0 ..< pinnedMessages.messages.count {
                    let entry = pinnedMessages.messages[i]
                    var matches = false
                    if message == nil {
                        matches = true
                    } else if case let .ready(referenceMessage) = referenceMessage {
                        if referenceMessage.isScrolled {
                            if entry.message.id < referenceMessage.id {
                                matches = true
                            }
                        } else {
                            if entry.message.id <= referenceMessage.id {
                                matches = true
                            }
                        }
                    } else {
                        matches = true
                    }
                    if matches {
                        if threadId != nil, case let .ready(referenceMessage) = referenceMessage {
                            if referenceMessage.minId <= entry.message.id {
                                continue
                            }
                        }
                        message = ChatPinnedMessage(message: entry.message, index: entry.index, totalCount: pinnedMessages.totalCount, topMessageId: topMessageId)
                    }
                }

                return message
            }
            |> distinctUntilChanged
            
            return topPinnedMessage
        } else {
            return .single(nil)
        }
    }

    var storedAnimateFromSnapshotState: ChatControllerNode.SnapshotState?

    func animateFromPreviousController(snapshotState: ChatControllerNode.SnapshotState) {
        self.storedAnimateFromSnapshotState = snapshotState
    }
    
    override public func loadDisplayNode() {
        self.loadDisplayNodeImpl()
        self.galleryPresentationContext.view = self.view
        self.galleryPresentationContext.controllersUpdated = { [weak self] _ in
            guard let self else {
                return
            }
            self.updateStatusBarPresentation()
        }
    }
    
    override public func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
                
        if self.willAppear {
            self.chatDisplayNode.historyNode.refreshPollActionsForVisibleMessages()
        } else {
            self.willAppear = true
            
            // Limit this to reply threads just to be safe now
            if case .replyThread = self.chatLocation {
                self.chatDisplayNode.historyNode.refocusOnUnreadMessagesIfNeeded()
            }
        }
        
        if case let .replyThread(message) = self.chatLocation, message.isForumPost {
            if self.keepMessageCountersSyncrhonizedDisposable == nil {
                self.keepMessageCountersSyncrhonizedDisposable = self.context.engine.messages.keepMessageCountersSyncrhonized(peerId: message.peerId, threadId: message.threadId).startStrict()
            }
        } else if self.chatLocation.peerId == self.context.account.peerId {
            if self.keepMessageCountersSyncrhonizedDisposable == nil {
                if let threadId = self.chatLocation.threadId {
                    self.keepMessageCountersSyncrhonizedDisposable = self.context.engine.messages.keepMessageCountersSyncrhonized(peerId: self.context.account.peerId, threadId: threadId).startStrict()
                } else {
                    self.keepMessageCountersSyncrhonizedDisposable = self.context.engine.messages.keepMessageCountersSyncrhonized(peerId: self.context.account.peerId).startStrict()
                }
            }
            if self.keepSavedMessagesSyncrhonizedDisposable == nil {
                self.keepSavedMessagesSyncrhonizedDisposable = self.context.engine.stickers.refreshSavedMessageTags(subPeerId: self.chatLocation.threadId.flatMap(PeerId.init)).startStrict()
            }
        }
        
        if let scheduledActivateInput = scheduledActivateInput, case .text = scheduledActivateInput {
            self.scheduledActivateInput = nil
            
            self.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in
                return state.updatedInputMode({ _ in
                    switch scheduledActivateInput {
                    case .text:
                        return .text
                    case .entityInput:
                        return .media(mode: .other, expanded: nil, focused: false)
                    }
                })
            })
        }
        
        var chatNavigationStack: [ChatNavigationStackItem] = self.chatNavigationStack
        if let peerId = self.chatLocation.peerId {
            if let summary = self.customNavigationDataSummary as? ChatControllerNavigationDataSummary {
                chatNavigationStack.removeAll()
                chatNavigationStack = summary.peerNavigationItems.filter({ $0 != ChatNavigationStackItem(peerId: peerId, threadId: self.chatLocation.threadId) })
            }
            if let _ = self.chatLocation.threadId {
                if !chatNavigationStack.contains(ChatNavigationStackItem(peerId: peerId, threadId: nil)) {
                    chatNavigationStack.append(ChatNavigationStackItem(peerId: peerId, threadId: nil))
                }
            }
        }
        
        if !chatNavigationStack.isEmpty {
            self.chatDisplayNode.navigationBar?.backButtonNode.isGestureEnabled = true
            self.chatDisplayNode.navigationBar?.backButtonNode.activated = { [weak self] gesture, _ in
                guard let strongSelf = self, let backButtonNode = strongSelf.chatDisplayNode.navigationBar?.backButtonNode, let navigationController = strongSelf.effectiveNavigationController else {
                    gesture.cancel()
                    return
                }
                let nextFolderId: Int32? = strongSelf.currentChatListFilter
                PeerInfoScreenImpl.displayChatNavigationMenu(
                    context: strongSelf.context,
                    chatNavigationStack: chatNavigationStack,
                    nextFolderId: nextFolderId,
                    parentController: strongSelf,
                    backButtonView: backButtonNode.view,
                    navigationController: navigationController,
                    gesture: gesture
                )
            }
        }
        
        if case .standard(.default) = self.mode, !"".isEmpty {
            let hasBrowserOrWebAppInFront: Signal<Bool, NoError> = .single([])
            |> then(
                self.effectiveNavigationController?.viewControllersSignal ?? .single([])
            )
            |> map { controllers in
                if controllers.last is BrowserScreen || controllers.last is AttachmentController {
                    return true
                } else {
                    return false
                }
            }
            self.hasBrowserOrAppInFront.set(hasBrowserOrWebAppInFront)
        }
    }
    
    var returnInputViewFocus = false
    
    override public func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        self.didAppear = true
        
        self.chatDisplayNode.historyNode.experimentalSnapScrollToItem = false
        self.chatDisplayNode.historyNode.canReadHistory.set(self.computedCanReadHistoryPromise.get())
        
        self.chatDisplayNode.loadInputPanels(theme: self.presentationInterfaceState.theme, strings: self.presentationInterfaceState.strings, fontSize: self.presentationInterfaceState.fontSize)
        
        if self.recentlyUsedInlineBotsDisposable == nil {
            self.recentlyUsedInlineBotsDisposable = (self.context.engine.peers.recentlyUsedInlineBots() |> deliverOnMainQueue).startStrict(next: { [weak self] peers in
                self?.recentlyUsedInlineBotsValue = peers.filter({ $0.1 >= 0.14 }).map({ $0.0._asPeer() })
            })
        }
        
        if case .standard(.default) = self.presentationInterfaceState.mode, self.raiseToListen == nil {
            self.raiseToListen = RaiseToListenManager(shouldActivate: { [weak self] in
                if let strongSelf = self, strongSelf.isNodeLoaded && strongSelf.canReadHistoryValue, strongSelf.presentationInterfaceState.interfaceState.editMessage == nil, strongSelf.playlistStateAndType == nil {
                    if !strongSelf.context.sharedContext.currentMediaInputSettings.with({ $0.enableRaiseToSpeak }) {
                        return false
                    }
                    
                    if strongSelf.effectiveNavigationController?.topViewController !== strongSelf {
                        return false
                    }
                    
                    if strongSelf.presentationInterfaceState.inputTextPanelState.mediaRecordingState != nil {
                        return false
                    }
                    
                    if !strongSelf.traceVisibility() {
                        return false
                    }
                    if strongSelf.currentContextController != nil {
                        return false
                    }
                    if !isTopmostChatController(strongSelf) {
                        return false
                    }
                    
                    if strongSelf.firstLoadedMessageToListen() != nil || strongSelf.chatDisplayNode.isTextInputPanelActive {
                        if strongSelf.context.sharedContext.immediateHasOngoingCall {
                            return false
                        }
                        
                        if case .media = strongSelf.presentationInterfaceState.inputMode {
                            return false
                        }
                        return true
                    }
                }
                return false
            }, activate: { [weak self] in
                self?.activateRaiseGesture()
            }, deactivate: { [weak self] in
                self?.deactivateRaiseGesture()
            })
            self.raiseToListen?.enabled = self.canReadHistoryValue
            self.tempVoicePlaylistEnded = { [weak self] in
                guard let strongSelf = self else {
                    return
                }
                if !canSendMessagesToChat(strongSelf.presentationInterfaceState) {
                    return
                }
                
                if let raiseToListen = strongSelf.raiseToListen {
                    strongSelf.voicePlaylistDidEndTimestamp = CACurrentMediaTime()
                    raiseToListen.activateBasedOnProximity(delay: 0.0)
                }
                
                if strongSelf.returnInputViewFocus {
                    strongSelf.returnInputViewFocus = false
                    strongSelf.chatDisplayNode.ensureInputViewFocused()
                }
            }
            self.tempVoicePlaylistItemChanged = { [weak self] previousItem, currentItem in
                guard let strongSelf = self else {
                    return
                }
                
                strongSelf.chatDisplayNode.historyNode.voicePlaylistItemChanged(previousItem, currentItem)
            }
        }
        
        if let arguments = self.presentationArguments as? ChatControllerOverlayPresentationData {
            //TODO clear arguments
            self.chatDisplayNode.animateInAsOverlay(from: arguments.expandData.0, completion: {
                arguments.expandData.1()
            })
        }
        
        if !self.didSetup3dTouch {
            self.didSetup3dTouch = true
            if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
                let dropInteraction = UIDropInteraction(delegate: self)
                self.chatDisplayNode.view.addInteraction(dropInteraction)
            }
        }
        
        if !self.checkedPeerChatServiceActions {
            self.checkedPeerChatServiceActions = true
            
            if case let .peer(peerId) = self.chatLocation, self.screenCaptureManager == nil {
                if peerId.namespace == Namespaces.Peer.SecretChat {
                    self.screenCaptureManager = ScreenCaptureDetectionManager(check: { [weak self] in
                        if let strongSelf = self, strongSelf.traceVisibility() {
                            if strongSelf.canReadHistoryValue {
                                let _ = strongSelf.context.engine.messages.addSecretChatMessageScreenshot(peerId: peerId).startStandalone()
                            }
                            return true
                        } else {
                            return false
                        }
                    })
                } else if peerId.namespace == Namespaces.Peer.CloudUser && peerId.id._internalGetInt64Value() == 777000 {
                    self.screenCaptureManager = ScreenCaptureDetectionManager(check: { [weak self] in
                        if let strongSelf = self, strongSelf.traceVisibility() {
                            let loginCodeRegex = try? NSRegularExpression(pattern: "[\\d\\-]{5,7}", options: [])
                            var loginCodesToInvalidate: [String] = []
                            strongSelf.chatDisplayNode.historyNode.forEachVisibleMessageItemNode({ itemNode in
                                if let text = itemNode.item?.message.text, let matches = loginCodeRegex?.matches(in: text, options: [], range: NSMakeRange(0, (text as NSString).length)), let match = matches.first {
                                    loginCodesToInvalidate.append((text as NSString).substring(with: match.range))
                                }
                            })
                            if !loginCodesToInvalidate.isEmpty {
                                let _ = strongSelf.context.engine.auth.invalidateLoginCodes(codes: loginCodesToInvalidate).startStandalone()
                            }
                            return true
                        } else {
                            return false
                        }
                    })
                } else if peerId.namespace == Namespaces.Peer.CloudUser {
                    self.screenCaptureManager = ScreenCaptureDetectionManager(check: { [weak self] in
                        guard let self else {
                            return false
                        }
                        
                        let _ = (self.context.sharedContext.mediaManager.globalMediaPlayerState
                        |> take(1)
                        |> deliverOnMainQueue).startStandalone(next: { [weak self] playlistStateAndType in
                            if let self, let (_, playbackState, _) = playlistStateAndType, case let .state(state) = playbackState {
                                if let source = state.item.playbackData?.source, case let .telegramFile(_, _, isViewOnce) = source, isViewOnce {
                                    self.context.sharedContext.mediaManager.setPlaylist(nil, type: .voice, control: .playback(.pause))
                                }
                            }
                        })
                        return true
                    })
                }
            }
            
            if case let .peer(peerId) = self.chatLocation {
                let _ = self.context.engine.peers.checkPeerChatServiceActions(peerId: peerId).startStandalone()
            }
            
            if self.chatLocation.peerId != nil && self.chatDisplayNode.frameForInputActionButton() != nil {
                let inputText = self.presentationInterfaceState.interfaceState.effectiveInputState.inputText.string
                if !inputText.isEmpty {
                    if inputText.count > 4 {
                        let _ = (ApplicationSpecificNotice.getChatMessageOptionsTip(accountManager: self.context.sharedContext.accountManager)
                        |> deliverOnMainQueue).startStandalone(next: { [weak self] counter in
                            if let strongSelf = self, counter < 3 {
                                let _ = ApplicationSpecificNotice.incrementChatMessageOptionsTip(accountManager: strongSelf.context.sharedContext.accountManager).startStandalone()
                                strongSelf.displaySendingOptionsTooltip()
                            }
                        })
                    }
                } else if self.presentationInterfaceState.interfaceState.mediaRecordingMode == .audio {
                    var canSendMedia = false
                    if let channel = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel {
                        if channel.hasBannedPermission(.banSendMedia) == nil && channel.hasBannedPermission(.banSendVoice) == nil {
                            canSendMedia = true
                        }
                    } else if let group = self.presentationInterfaceState.renderedPeer?.peer as? TelegramGroup {
                        if !group.hasBannedPermission(.banSendMedia) && !group.hasBannedPermission(.banSendVoice) {
                            canSendMedia = true
                        }
                    } else {
                        canSendMedia = true
                    }
                    if canSendMedia && self.presentationInterfaceState.voiceMessagesAvailable {
                        let _ = (ApplicationSpecificNotice.getChatMediaMediaRecordingTips(accountManager: self.context.sharedContext.accountManager)
                        |> deliverOnMainQueue).startStandalone(next: { [weak self] counter in
                            guard let strongSelf = self else {
                                return
                            }
                            var displayTip = false
                            if counter == 0 {
                                displayTip = true
                            } else if counter < 3 && arc4random_uniform(4) == 1 {
                                displayTip = true
                            }
                            if displayTip {
                                let _ = ApplicationSpecificNotice.incrementChatMediaMediaRecordingTips(accountManager: strongSelf.context.sharedContext.accountManager).startStandalone()
                                strongSelf.displayMediaRecordingTooltip()
                            }
                        })
                    }
                }
            }
            
            self.editMessageErrorsDisposable.set((self.context.account.pendingUpdateMessageManager.errors
            |> deliverOnMainQueue).startStrict(next: { [weak self] (_, error) in
                guard let strongSelf = self else {
                    return
                }
                
                let text: String
                switch error {
                case .generic, .textTooLong, .invalidGrouping:
                    text = strongSelf.presentationData.strings.Channel_EditMessageErrorGeneric
                case .restricted:
                    text = strongSelf.presentationData.strings.Group_ErrorSendRestrictedMedia
                }
                strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
                })]), in: .window(.root))
            }))
            
            if case let .peer(peerId) = self.chatLocation {
                let context = self.context
                self.keepPeerInfoScreenDataHotDisposable.set(keepPeerInfoScreenDataHot(context: context, peerId: peerId, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder).startStrict())
                
                if peerId.namespace == Namespaces.Peer.CloudUser {
                    self.preloadAvatarDisposable.set((peerInfoProfilePhotosWithCache(context: context, peerId: peerId)
                    |> mapToSignal { (complete, result) -> Signal<Never, NoError> in
                        var signals: [Signal<Never, NoError>] = [.complete()]
                        for i in 0 ..< min(1, result.count) {
                            if let video = result[i].videoRepresentations.first {
                                let duration: Double = (video.representation.startTimestamp ?? 0.0) + (i == 0 ? 4.0 : 2.0)
                                signals.append(preloadVideoResource(postbox: context.account.postbox, userLocation: .other, userContentType: .video, resourceReference: video.reference, duration: duration))
                            }
                        }
                        return combineLatest(signals) |> mapToSignal { _ in
                            return .never()
                        }
                    }).startStrict())
                }
            }
            
            self.preloadAttachBotIconsDisposables = AttachmentController.preloadAttachBotIcons(context: self.context)
        }
        
        if let _ = self.focusOnSearchAfterAppearance {
            self.focusOnSearchAfterAppearance = nil
            if let searchNode = self.navigationBar?.contentNode as? ChatSearchNavigationContentNode {
                searchNode.activate()
            }
        }
        
        if let peekData = self.peekData, case let .peer(peerId) = self.chatLocation {
            let timestamp = Int32(Date().timeIntervalSince1970)
            let remainingTime = max(1, peekData.deadline - timestamp)
            self.peekTimerDisposable.set((
                combineLatest(
                    self.context.account.postbox.peerView(id: peerId),
                    Signal<Bool, NoError>.single(true)
                    |> suspendAwareDelay(Double(remainingTime), granularity: 2.0, queue: .mainQueue())
                )
                |> deliverOnMainQueue
            ).startStrict(next: { [weak self] peerView, _ in
                guard let strongSelf = self, let peer = peerViewMainPeer(peerView) else {
                    return
                }
                if let peer = peer as? TelegramChannel {
                    switch peer.participationStatus {
                    case .member:
                        return
                    default:
                        break
                    }
                }
                strongSelf.present(textAlertController(
                    context: strongSelf.context,
                    title: strongSelf.presentationData.strings.Conversation_PrivateChannelTimeLimitedAlertTitle,
                    text: strongSelf.presentationData.strings.Conversation_PrivateChannelTimeLimitedAlertText,
                    actions: [
                        TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Conversation_PrivateChannelTimeLimitedAlertJoin, action: {
                            guard let strongSelf = self else {
                                return
                            }
                            strongSelf.peekTimerDisposable.set(
                                (strongSelf.context.engine.peers.joinChatInteractively(with: peekData.linkData)
                                |> deliverOnMainQueue).startStrict(next: { peerId in
                                    guard let strongSelf = self else {
                                        return
                                    }
                                    if peerId == nil {
                                        strongSelf.dismiss()
                                    }
                                }, error: { _ in
                                    guard let strongSelf = self else {
                                        return
                                    }
                                    strongSelf.dismiss()
                                })
                            )
                        }),
                        TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {
                            guard let strongSelf = self else {
                                return
                            }
                            strongSelf.dismiss()
                        })
                    ],
                    actionLayout: .vertical,
                    dismissOnOutsideTap: false
                ), in: .window(.root))
            }))
        }
        
        self.checksTooltipDisposable.set((self.context.engine.notices.getServerProvidedSuggestions()
        |> deliverOnMainQueue).startStrict(next: { [weak self] values in
            guard let strongSelf = self, strongSelf.chatLocation.peerId != strongSelf.context.account.peerId else {
                return
            }
            if !values.contains(.newcomerTicks) {
                return
            }
            strongSelf.shouldDisplayChecksTooltip = true
        }))
        
        if case let .peer(peerId) = self.chatLocation {
            self.peerSuggestionsDisposable.set((self.context.engine.notices.getPeerSpecificServerProvidedSuggestions(peerId: peerId)
            |> deliverOnMainQueue).startStrict(next: { [weak self] values in
                guard let strongSelf = self else {
                    return
                }
                
                if !strongSelf.traceVisibility() || strongSelf.navigationController?.topViewController != strongSelf {
                    return
                }
                
                if values.contains(.convertToGigagroup) && !strongSelf.displayedConvertToGigagroupSuggestion {
                    strongSelf.displayedConvertToGigagroupSuggestion = true
                    
                    let attributedTitle = NSAttributedString(string: strongSelf.presentationData.strings.BroadcastGroups_LimitAlert_Title, font: Font.semibold(strongSelf.presentationData.listsFontSize.baseDisplaySize), textColor: strongSelf.presentationData.theme.actionSheet.primaryTextColor, paragraphAlignment: .center)
                    let body = MarkdownAttributeSet(font: Font.regular(strongSelf.presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0), textColor: strongSelf.presentationData.theme.actionSheet.primaryTextColor)
                    let bold = MarkdownAttributeSet(font: Font.semibold(strongSelf.presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0), textColor: strongSelf.presentationData.theme.actionSheet.primaryTextColor)
                    
                    let participantsLimit = strongSelf.context.currentLimitsConfiguration.with { $0 }.maxSupergroupMemberCount
                    let text = strongSelf.presentationData.strings.BroadcastGroups_LimitAlert_Text(presentationStringsFormattedNumber(participantsLimit, strongSelf.presentationData.dateTimeFormat.groupingSeparator)).string
                    let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .center)
                    
                    let controller = richTextAlertController(context: strongSelf.context, title: attributedTitle, text: attributedText, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {
                        strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .info(title: nil, text: strongSelf.presentationData.strings.BroadcastGroups_LimitAlert_SettingsTip, timeout: nil, customUndoText: nil), elevatedLayout: false, action: { _ in return false }), in: .current)
                    }), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.BroadcastGroups_LimitAlert_LearnMore, action: {
                        
                        let context = strongSelf.context
                        let presentationData = strongSelf.presentationData
                        let controller = PermissionController(context: context, splashScreen: true)
                        controller.navigationPresentation = .modal
                        controller.setState(.custom(icon: .animation("BroadcastGroup"), title: presentationData.strings.BroadcastGroups_IntroTitle, subtitle: nil, text: presentationData.strings.BroadcastGroups_IntroText, buttonTitle: presentationData.strings.BroadcastGroups_Convert, secondaryButtonTitle: presentationData.strings.BroadcastGroups_Cancel, footerText: nil), animated: false)
                        controller.proceed = { [weak controller] result in
                            let attributedTitle = NSAttributedString(string: presentationData.strings.BroadcastGroups_ConfirmationAlert_Title, font: Font.semibold(presentationData.listsFontSize.baseDisplaySize), textColor: presentationData.theme.actionSheet.primaryTextColor, paragraphAlignment: .center)
                            let body = MarkdownAttributeSet(font: Font.regular(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0), textColor: presentationData.theme.actionSheet.primaryTextColor)
                            let bold = MarkdownAttributeSet(font: Font.semibold(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0), textColor: presentationData.theme.actionSheet.primaryTextColor)
                            let attributedText = parseMarkdownIntoAttributedString(presentationData.strings.BroadcastGroups_ConfirmationAlert_Text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .center)
                            
                            let alertController = richTextAlertController(context: context, title: attributedTitle, text: attributedText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
                                let _ = context.engine.notices.dismissPeerSpecificServerProvidedSuggestion(peerId: peerId, suggestion: .convertToGigagroup).startStandalone()
                            }), TextAlertAction(type: .defaultAction, title: presentationData.strings.BroadcastGroups_ConfirmationAlert_Convert, action: { [weak controller] in
                                controller?.dismiss()
                                
                                let _ = context.engine.notices.dismissPeerSpecificServerProvidedSuggestion(peerId: peerId, suggestion: .convertToGigagroup).startStandalone()
                                
                                let _ = (convertGroupToGigagroup(account: context.account, peerId: peerId)
                                |> deliverOnMainQueue).startStandalone(completed: {
                                    let participantsLimit = context.currentLimitsConfiguration.with { $0 }.maxSupergroupMemberCount
                                    strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .gigagroupConversion(text: presentationData.strings.BroadcastGroups_Success(presentationStringsFormattedNumber(participantsLimit, presentationData.dateTimeFormat.decimalSeparator)).string), elevatedLayout: false, action: { _ in return false }), in: .current)
                                })
                            })])
                            controller?.present(alertController, in: .window(.root))
                        }
                        strongSelf.push(controller)
                    })])
                    strongSelf.present(controller, in: .window(.root))
                }
            }))
        }
        
        if let scheduledActivateInput = self.scheduledActivateInput {
            self.scheduledActivateInput = nil
            
            switch scheduledActivateInput {
            case .text:
                self.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in
                    return state.updatedInputMode({ _ in
                        return .text
                    })
                })
            case .entityInput:
                self.chatDisplayNode.openStickers(beginWithEmoji: true)
            }
        }

        if let snapshotState = self.storedAnimateFromSnapshotState {
            self.storedAnimateFromSnapshotState = nil

            if let titleViewSnapshotState = snapshotState.titleViewSnapshotState {
                self.chatTitleView?.animateFromSnapshot(titleViewSnapshotState)
            }
            if let avatarSnapshotState = snapshotState.avatarSnapshotState {
                (self.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.animateFromSnapshot(avatarSnapshotState)
            }
            self.chatDisplayNode.animateFromSnapshot(snapshotState, completion: { [weak self] in
                guard let strongSelf = self else {
                    return
                }
                strongSelf.chatDisplayNode.historyNode.preloadPages = true
            })
        } else {
            self.chatDisplayNode.historyNode.preloadPages = true
        }
        
        if let attachBotStart = self.attachBotStart {
            self.attachBotStart = nil
            self.presentAttachmentBot(botId: attachBotStart.botId, payload: attachBotStart.payload, justInstalled: attachBotStart.justInstalled)
        }
        
        if self.powerSavingMonitoringDisposable == nil {
            self.powerSavingMonitoringDisposable = (self.context.sharedContext.automaticMediaDownloadSettings
            |> mapToSignal { settings -> Signal<Bool, NoError> in
                return automaticEnergyUsageShouldBeOn(settings: settings)
            }
            |> distinctUntilChanged).startStrict(next: { [weak self] isPowerSavingEnabled in
                guard let self else {
                    return
                }
                var previousValueValue: Bool?
                
                previousValueValue = ChatListControllerImpl.sharedPreviousPowerSavingEnabled
                ChatListControllerImpl.sharedPreviousPowerSavingEnabled = isPowerSavingEnabled
                
                /*#if DEBUG
                previousValueValue = false
                #endif*/
                
                if isPowerSavingEnabled != previousValueValue && previousValueValue != nil && isPowerSavingEnabled {
                    let batteryLevel = UIDevice.current.batteryLevel
                    if batteryLevel > 0.0 && self.view.window != nil {
                        let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
                        let batteryPercentage = Int(batteryLevel * 100.0)
                        
                        self.dismissAllUndoControllers()
                        self.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "lowbattery_30", scale: 1.0, colors: [:], title: presentationData.strings.PowerSaving_AlertEnabledTitle, text: presentationData.strings.PowerSaving_AlertEnabledText("\(batteryPercentage)").string, customUndoText: presentationData.strings.PowerSaving_AlertEnabledAction, timeout: 5.0), elevatedLayout: false, action: { [weak self] action in
                            if case .undo = action, let self {
                                let _ = updateMediaDownloadSettingsInteractively(accountManager: self.context.sharedContext.accountManager, { settings in
                                    var settings = settings
                                    settings.energyUsageSettings.activationThreshold = 4
                                    return settings
                                }).startStandalone()
                            }
                            return false
                        }), in: .current)
                    }
                }
            })
        }
    }
    
    override public func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        
        UIView.performWithoutAnimation {
            self.view.endEditing(true)
        }
        
        self.chatDisplayNode.historyNode.canReadHistory.set(.single(false))
        self.saveInterfaceState()
        
        self.dismissAllTooltips()
        
        self.sendMessageActionsController?.dismiss()
        self.themeScreen?.dismiss()
        
        self.attachmentController?.dismiss()
        
        self.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts()
        
        if let _ = self.peekData {
            self.peekTimerDisposable.set(nil)
        }
        
        if case .standard(.default) = self.mode {
            self.hasBrowserOrAppInFront.set(.single(false))
        }
    }
    
    func saveInterfaceState(includeScrollState: Bool = true) {
        if case .messageOptions = self.subject {
            return
        }
        
        var includeScrollState = includeScrollState
        
        var peerId: PeerId
        var threadId: Int64?
        switch self.chatLocation {
        case let .peer(peerIdValue):
            peerId = peerIdValue
        case let .replyThread(replyThreadMessage):
            if replyThreadMessage.peerId == self.context.account.peerId && replyThreadMessage.threadId == self.context.account.peerId.toInt64() {
                peerId = replyThreadMessage.peerId
                threadId = nil
                includeScrollState = true
                
                let scrollState = self.chatDisplayNode.historyNode.immediateScrollState()
                let _ = ChatInterfaceState.update(engine: self.context.engine, peerId: peerId, threadId: replyThreadMessage.threadId, { current in
                    return current.withUpdatedHistoryScrollState(scrollState)
                }).startStandalone()
            } else {
                peerId = replyThreadMessage.peerId
                threadId = replyThreadMessage.threadId
            }
        case .customChatContents:
            return
        }
        
        let timestamp = Int32(Date().timeIntervalSince1970)
        var interfaceState = self.presentationInterfaceState.interfaceState.withUpdatedTimestamp(timestamp)
        if includeScrollState {
            let scrollState = self.chatDisplayNode.historyNode.immediateScrollState()
            interfaceState = interfaceState.withUpdatedHistoryScrollState(scrollState)
        }
        interfaceState = interfaceState.withUpdatedInputLanguage(self.chatDisplayNode.currentTextInputLanguage)
        if case .peer = self.chatLocation, let channel = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.flags.contains(.isForum) {
            interfaceState = interfaceState.withUpdatedComposeInputState(ChatTextInputState()).withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil)
        }
        let _ = ChatInterfaceState.update(engine: self.context.engine, peerId: peerId, threadId: threadId, { _ in
            return interfaceState
        }).startStandalone()
    }
        
    override public func viewWillLeaveNavigation() {
        self.chatDisplayNode.willNavigateAway()
    }
    
    override public func inFocusUpdated(isInFocus: Bool) {
        self.disableStickerAnimationsPromise.set(!isInFocus)
        self.chatDisplayNode.inFocusUpdated(isInFocus: isInFocus)
    }
    
    func canManagePin() -> Bool {
        guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
            return false
        }
        
        var canManagePin = false
        if let channel = peer as? TelegramChannel {
            canManagePin = channel.hasPermission(.pinMessages)
        } else if let group = peer as? TelegramGroup {
            switch group.role {
                case .creator, .admin:
                    canManagePin = true
                default:
                    if let defaultBannedRights = group.defaultBannedRights {
                        canManagePin = !defaultBannedRights.flags.contains(.banPinMessages)
                    } else {
                        canManagePin = true
                    }
            }
        } else if let _ = peer as? TelegramUser, self.presentationInterfaceState.explicitelyCanPinMessages {
            canManagePin = true
        }
        
        return canManagePin
    }

    var suspendNavigationBarLayout: Bool = false
    var suspendedNavigationBarLayout: ContainerViewLayout?
    var additionalNavigationBarBackgroundHeight: CGFloat = 0.0
    var additionalNavigationBarHitTestSlop: CGFloat = 0.0

    override public func updateNavigationBarLayout(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
        if self.suspendNavigationBarLayout {
            self.suspendedNavigationBarLayout = layout
            return
        }
        self.applyNavigationBarLayout(layout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, transition: transition)
    }
    
    override public func preferredContentSizeForLayout(_ layout: ContainerViewLayout) -> CGSize? {
        return nil
    }
    
    public func updateIsScrollingLockedAtTop(isScrollingLockedAtTop: Bool) {
        self.chatDisplayNode.isScrollingLockedAtTop = isScrollingLockedAtTop
    }
    
    override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
        self.suspendNavigationBarLayout = true
        super.containerLayoutUpdated(layout, transition: transition)
        
        self.validLayout = layout
        self.chatTitleView?.layout = layout
        
        switch self.presentationInterfaceState.mode {
        case .standard, .inline:
            break
        case .overlay:
            if case .Ignore = self.statusBar.statusBarStyle {
            } else if layout.safeInsets.top.isZero {
                self.statusBar.statusBarStyle = .Hide
            } else {
                self.statusBar.statusBarStyle = .Ignore
            }
        }
        
        var layout = layout
        if case .compact = layout.metrics.widthClass, let attachmentController = self.attachmentController, attachmentController.window != nil {
            layout = layout.withUpdatedInputHeight(nil)
        }
                
        var navigationBarTransition = transition
        self.chatDisplayNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition, listViewTransaction: { updateSizeAndInsets, additionalScrollDistance, scrollToTop, completion in
            self.chatDisplayNode.historyNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, additionalScrollDistance: additionalScrollDistance, scrollToTop: scrollToTop, completion: completion)
        }, updateExtraNavigationBarBackgroundHeight: { value, hitTestSlop, extraNavigationTransition in
            navigationBarTransition = extraNavigationTransition
            self.additionalNavigationBarBackgroundHeight = value
            self.additionalNavigationBarHitTestSlop = hitTestSlop
        })
        
        if case .compact = layout.metrics.widthClass {
            let hasOverlayNodes = self.context.sharedContext.mediaManager.overlayMediaManager.controller?.hasNodes ?? false
            if self.validLayout != nil && layout.size.width > layout.size.height && !hasOverlayNodes && self.traceVisibility() && isTopmostChatController(self) {
                var completed = false
                self.chatDisplayNode.historyNode.forEachVisibleItemNode { itemNode in
                    if !completed, let itemNode = itemNode as? ChatMessageItemView, let message = itemNode.item?.message, let (_, soundEnabled, _, _, _) = itemNode.playMediaWithSound(), soundEnabled {
                        let _ = self.controllerInteraction?.openMessage(message, OpenMessageParams(mode: .landscape))
                        completed = true
                    }
                }
            }
        }

        self.suspendNavigationBarLayout = false
        if let suspendedNavigationBarLayout = self.suspendedNavigationBarLayout {
            self.suspendedNavigationBarLayout = suspendedNavigationBarLayout
            self.applyNavigationBarLayout(suspendedNavigationBarLayout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, transition: navigationBarTransition)
        }
        self.navigationBar?.additionalContentNode.hitTestSlop = UIEdgeInsets(top: 0.0, left: 0.0, bottom: self.additionalNavigationBarHitTestSlop, right: 0.0)
    }
    
    func updateChatPresentationInterfaceState(animated: Bool = true, interactive: Bool, saveInterfaceState: Bool = false, _ f: (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState, completion: @escaping (ContainedViewLayoutTransition) -> Void = { _ in }) {
        self.updateChatPresentationInterfaceState(transition: animated ? .animated(duration: 0.4, curve: .spring) : .immediate, interactive: interactive, saveInterfaceState: saveInterfaceState, f, completion: completion)
    }
    
    func updateChatPresentationInterfaceState(transition: ContainedViewLayoutTransition, interactive: Bool, saveInterfaceState: Bool = false, _ f: (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState, completion: @escaping (ContainedViewLayoutTransition) -> Void = { _ in }) {
        updateChatPresentationInterfaceStateImpl(
            selfController: self,
            transition: transition,
            interactive: interactive,
            saveInterfaceState: saveInterfaceState,
            f,
            completion: completion
        )
    }
    
    func updateItemNodesSelectionStates(animated: Bool) {
        self.chatDisplayNode.historyNode.forEachItemNode { itemNode in
            if let itemNode = itemNode as? ChatMessageItemView {
                itemNode.updateSelectionState(animated: animated)
            }
        }

        self.chatDisplayNode.historyNode.forEachItemHeaderNode{ itemHeaderNode in
            if let avatarNode = itemHeaderNode as? ChatMessageAvatarHeaderNode {
                avatarNode.updateSelectionState(animated: animated)
            }
        }
    }
    
    func updatePollTooltipMessageState(animated: Bool) {
        self.chatDisplayNode.historyNode.forEachItemNode { itemNode in
            if let itemNode = itemNode as? ChatMessageBubbleItemNode {
                for contentNode in itemNode.contentNodes {
                    if let contentNode = contentNode as? ChatMessagePollBubbleContentNode {
                        contentNode.updatePollTooltipMessageState(animated: animated)
                    }
                }
                itemNode.updatePsaTooltipMessageState(animated: animated)
            }
        }
    }
    
    func updateItemNodesSearchTextHighlightStates() {
        var searchString: String?
        var resultsMessageIndices: [MessageIndex]?
        if let search = self.presentationInterfaceState.search, let resultsState = search.resultsState, !resultsState.messageIndices.isEmpty {
            searchString = search.query
            resultsMessageIndices = resultsState.messageIndices
        }
        if searchString != self.controllerInteraction?.searchTextHighightState?.0 || resultsMessageIndices?.count != self.controllerInteraction?.searchTextHighightState?.1.count {
            var searchTextHighightState: (String, [MessageIndex])?
            if let searchString = searchString, let resultsMessageIndices = resultsMessageIndices {
                searchTextHighightState = (searchString, resultsMessageIndices)
            }
            self.controllerInteraction?.searchTextHighightState = searchTextHighightState
            self.chatDisplayNode.historyNode.forEachItemNode { itemNode in
                if let itemNode = itemNode as? ChatMessageItemView {
                    itemNode.updateSearchTextHighlightState()
                }
            }
        }
    }
    
    func updateItemNodesHighlightedStates(animated: Bool) {
        self.chatDisplayNode.historyNode.forEachItemNode { itemNode in
            if let itemNode = itemNode as? ChatMessageItemView {
                itemNode.updateHighlightedState(animated: animated)
            }
        }
    }
    
    @objc func leftNavigationButtonAction() {
        if let button = self.leftNavigationButton {
            self.navigationButtonAction(button.action)
        }
    }
    
    @objc func rightNavigationButtonAction() {
        if let button = self.rightNavigationButton {
            if case .standard(.previewing) = self.mode {
                self.navigationButtonAction(button.action)
            } else if case let .peer(peerId) = self.chatLocation, case .openChatInfo(expandAvatar: true, _) = button.action, let storyStats = self.storyStats, storyStats.unseenCount != 0, let avatarNode = self.avatarNode {
                self.openStories(peerId: peerId, avatarHeaderNode: nil, avatarNode: avatarNode.avatarNode)
            } else {
                self.navigationButtonAction(button.action)
            }
        }
    }
    
    @objc func secondaryRightNavigationButtonAction() {
        if let button = self.secondaryRightNavigationButton {
            self.navigationButtonAction(button.action)
        }
    }
    
    @objc func moreButtonPressed() {
        self.moreBarButton.play()
        self.moreBarButton.contextAction?(self.moreBarButton.containerNode, nil)
    }
    
    public func beginClearHistory(type: InteractiveHistoryClearingType) {
        guard case let .peer(peerId) = self.chatLocation else {
            return
        }
        self.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } })
        self.chatDisplayNode.historyNode.historyAppearsCleared = true
        
        let statusText: String
        if case .scheduledMessages = self.presentationInterfaceState.subject {
            statusText = self.presentationData.strings.Undo_ScheduledMessagesCleared
        } else if case .forEveryone = type {
            if peerId.namespace == Namespaces.Peer.CloudUser {
                statusText = self.presentationData.strings.Undo_ChatClearedForBothSides
            } else {
                statusText = self.presentationData.strings.Undo_ChatClearedForEveryone
            }
        } else {
            statusText = self.presentationData.strings.Undo_ChatCleared
        }
        
        self.present(UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: statusText, text: nil), elevatedLayout: false, action: { [weak self] value in
            guard let strongSelf = self else {
                return false
            }
            if value == .commit {
                let _ = strongSelf.context.engine.messages.clearHistoryInteractively(peerId: peerId, threadId: nil, type: type).startStandalone(completed: {
                    self?.chatDisplayNode.historyNode.historyAppearsCleared = false
                })
                return true
            } else if value == .undo {
                strongSelf.chatDisplayNode.historyNode.historyAppearsCleared = false
                return true
            }
            return false
        }), in: .current)
    }
    
    public func cancelSelectingMessages() {
        self.navigationButtonAction(.cancelMessageSelection)
    }
    
    func editMessageMediaWithMessages(_ messages: [EnqueueMessage]) {
        if let message = messages.first, case let .message(text, attributes, _, maybeMediaReference, _, _, _, _, _, _) = message, let mediaReference = maybeMediaReference {
            self.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in
                var entities: [MessageTextEntity] = []
                for attribute in attributes {
                    if let entitiesAttrbute = attribute as? TextEntitiesMessageAttribute {
                        entities = entitiesAttrbute.entities
                    }
                }
                let attributedText = chatInputStateStringWithAppliedEntities(text, entities: entities)
                
                var state = state
                if let editMessageState = state.editMessageState, case let .media(options) = editMessageState.content, !options.isEmpty {
                    state = state.updatedEditMessageState(ChatEditInterfaceMessageState(content: editMessageState.content, mediaReference: mediaReference))
                }
                if !text.isEmpty {
                    state = state.updatedInterfaceState { state in
                        if let editMessage = state.editMessage {
                            return state.withUpdatedEditMessage(editMessage.withUpdatedInputState(ChatTextInputState(inputText: attributedText)))
                        }
                        return state
                    }
                }
                return state
            })
            self.interfaceInteraction?.editMessage()
        }
    }
    
    func editMessageMediaWithLegacySignals(_ signals: [Any]) {
        let _ = (legacyAssetPickerEnqueueMessages(context: self.context, account: self.context.account, signals: signals)
        |> deliverOnMainQueue).startStandalone(next: { [weak self] messages in
            self?.editMessageMediaWithMessages(messages.map { $0.message })
        })
    }
    
    public func presentAttachmentBot(botId: PeerId, payload: String?, justInstalled: Bool) {
        self.attachmentController?.dismiss(animated: true, completion: nil)
        self.presentAttachmentMenu(subject: .bot(id: botId, payload: payload, justInstalled: justInstalled))
    }
    
    func displayPollSolution(solution: TelegramMediaPollResults.Solution, sourceNode: ASDisplayNode, isAutomatic: Bool) {
        var maybeFoundItemNode: ChatMessageItemView?
        self.chatDisplayNode.historyNode.forEachItemNode { itemNode in
            if let itemNode = itemNode as? ChatMessageItemView {
                if sourceNode.view.isDescendant(of: itemNode.view) {
                    maybeFoundItemNode = itemNode
                }
            }
        }
        guard let foundItemNode = maybeFoundItemNode, let item = foundItemNode.item else {
            return
        }
        
        var found = false
        self.forEachController({ controller in
            if let controller = controller as? TooltipScreen {
                if controller.text == .entities(text: solution.text, entities: solution.entities) {
                    found = true
                    controller.dismiss()
                    return false
                }
            }
            return true
        })
        if found {
            return
        }
        
        let tooltipScreen = TooltipScreen(context: self.context, account: self.context.account, sharedContext: self.context.sharedContext, text: .entities(text: solution.text, entities: solution.entities), icon: .animation(name: "anim_infotip", delay: 0.2, tintColor: nil), location: .top, shouldDismissOnTouch: { point, _ in
            return .ignore
        }, openActiveTextItem: { [weak self] item, action in
            guard let strongSelf = self else {
                return
            }
            switch item {
            case let .url(url, concealed):
                switch action {
                case .tap:
                    strongSelf.openUrl(url, concealed: concealed)
                case .longTap:
                    strongSelf.controllerInteraction?.longTap(.url(url), nil)
                }
            case let .mention(peerId, mention):
                switch action {
                case .tap:
                    let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
                    |> deliverOnMainQueue).startStandalone(next: { peer in
                        if let strongSelf = self, let peer = peer {
                            strongSelf.controllerInteraction?.openPeer(peer, .default, nil, .default)
                        }
                    })
                case .longTap:
                    strongSelf.controllerInteraction?.longTap(.peerMention(peerId, mention), nil)
                }
            case let .textMention(mention):
                switch action {
                case .tap:
                    strongSelf.controllerInteraction?.openPeerMention(mention, nil)
                case .longTap:
                    strongSelf.controllerInteraction?.longTap(.mention(mention), nil)
                }
            case let .botCommand(command):
                switch action {
                case .tap:
                    strongSelf.controllerInteraction?.sendBotCommand(nil, command)
                case .longTap:
                    strongSelf.controllerInteraction?.longTap(.command(command), nil)
                }
            case let .hashtag(hashtag):
                switch action {
                case .tap:
                    strongSelf.controllerInteraction?.openHashtag(nil, hashtag)
                case .longTap:
                    strongSelf.controllerInteraction?.longTap(.hashtag(hashtag), nil)
                }
            }
        })
        
        let messageId = item.message.id
        self.controllerInteraction?.currentPollMessageWithTooltip = messageId
        self.updatePollTooltipMessageState(animated: !isAutomatic)
        
        tooltipScreen.willBecomeDismissed = { [weak self] tooltipScreen in
            guard let strongSelf = self else {
                return
            }
            if strongSelf.controllerInteraction?.currentPollMessageWithTooltip == messageId {
                strongSelf.controllerInteraction?.currentPollMessageWithTooltip = nil
                strongSelf.updatePollTooltipMessageState(animated: true)
            }
        }
        
        self.forEachController({ controller in
            if let controller = controller as? TooltipScreen {
                controller.dismiss()
            }
            return true
        })
        
        self.present(tooltipScreen, in: .current)
    }
    
    public func displayPromoAnnouncement(text: String) {
        let psaText: String = text
        let psaEntities: [MessageTextEntity] = generateTextEntities(psaText, enabledTypes: .allUrl)
        
        var found = false
        self.forEachController({ controller in
            if let controller = controller as? TooltipScreen {
                if controller.text == .plain(text: psaText) {
                    found = true
                    controller.dismiss()
                    return false
                }
            }
            return true
        })
        if found {
            return
        }
        
        let tooltipScreen = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .entities(text: psaText, entities: psaEntities), icon: .animation(name: "anim_infotip", delay: 0.2, tintColor: nil), location: .top, displayDuration: .custom(10.0), shouldDismissOnTouch: { point, _ in
            return .ignore
        }, openActiveTextItem: { [weak self] item, action in
            guard let strongSelf = self else {
                return
            }
            switch item {
            case let .url(url, concealed):
                switch action {
                case .tap:
                    strongSelf.openUrl(url, concealed: concealed)
                case .longTap:
                    strongSelf.controllerInteraction?.longTap(.url(url), nil)
                }
            case let .mention(peerId, mention):
                switch action {
                case .tap:
                    let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
                    |> deliverOnMainQueue).startStandalone(next: { peer in
                        if let strongSelf = self, let peer = peer {
                            strongSelf.controllerInteraction?.openPeer(peer, .default, nil, .default)
                        }
                    })
                case .longTap:
                    strongSelf.controllerInteraction?.longTap(.peerMention(peerId, mention), nil)
                }
            case let .textMention(mention):
                switch action {
                case .tap:
                    strongSelf.controllerInteraction?.openPeerMention(mention, nil)
                case .longTap:
                    strongSelf.controllerInteraction?.longTap(.mention(mention), nil)
                }
            case let .botCommand(command):
                switch action {
                case .tap:
                    strongSelf.controllerInteraction?.sendBotCommand(nil, command)
                case .longTap:
                    strongSelf.controllerInteraction?.longTap(.command(command), nil)
                }
            case let .hashtag(hashtag):
                switch action {
                case .tap:
                    strongSelf.controllerInteraction?.openHashtag(nil, hashtag)
                case .longTap:
                    strongSelf.controllerInteraction?.longTap(.hashtag(hashtag), nil)
                }
            }
        })
        
        self.forEachController({ controller in
            if let controller = controller as? TooltipScreen {
                controller.dismiss()
            }
            return true
        })
        
        self.present(tooltipScreen, in: .current)
    }
    
    func displayPsa(type: String, sourceNode: ASDisplayNode, isAutomatic: Bool) {
        var maybeFoundItemNode: ChatMessageItemView?
        self.chatDisplayNode.historyNode.forEachItemNode { itemNode in
            if let itemNode = itemNode as? ChatMessageItemView {
                if sourceNode.view.isDescendant(of: itemNode.view) {
                    maybeFoundItemNode = itemNode
                }
            }
        }
        guard let foundItemNode = maybeFoundItemNode, let item = foundItemNode.item else {
            return
        }
        
        var psaText = self.presentationData.strings.Chat_GenericPsaTooltip
        let key = "Chat.PsaTooltip.\(type)"
        if let string = self.presentationData.strings.primaryComponent.dict[key] {
            psaText = string
        } else if let string = self.presentationData.strings.secondaryComponent?.dict[key] {
            psaText = string
        }
        
        let psaEntities: [MessageTextEntity] = generateTextEntities(psaText, enabledTypes: .allUrl)
        
        let messageId = item.message.id
        
        var found = false
        self.forEachController({ controller in
            if let controller = controller as? TooltipScreen {
                if controller.text == .plain(text: psaText) {
                    found = true
                    controller.resetDismissTimeout()
                    
                    controller.willBecomeDismissed = { [weak self] tooltipScreen in
                        guard let strongSelf = self else {
                            return
                        }
                        if strongSelf.controllerInteraction?.currentPsaMessageWithTooltip == messageId {
                            strongSelf.controllerInteraction?.currentPsaMessageWithTooltip = nil
                            strongSelf.updatePollTooltipMessageState(animated: true)
                        }
                    }
                    
                    return false
                }
            }
            return true
        })
        if found {
            self.controllerInteraction?.currentPsaMessageWithTooltip = messageId
            self.updatePollTooltipMessageState(animated: !isAutomatic)
            
            return
        }
        
        let tooltipScreen = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .entities(text: psaText, entities: psaEntities), icon: .animation(name: "anim_infotip", delay: 0.2, tintColor: nil), location: .top, displayDuration: .custom(10.0), shouldDismissOnTouch: { point, _ in
            return .ignore
        }, openActiveTextItem: { [weak self] item, action in
            guard let strongSelf = self else {
                return
            }
            switch item {
            case let .url(url, concealed):
                switch action {
                case .tap:
                    strongSelf.openUrl(url, concealed: concealed)
                case .longTap:
                    strongSelf.controllerInteraction?.longTap(.url(url), nil)
                }
            case let .mention(peerId, mention):
                switch action {
                case .tap:
                    let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
                    |> deliverOnMainQueue).startStandalone(next: { peer in
                        if let strongSelf = self, let peer = peer {
                            strongSelf.controllerInteraction?.openPeer(peer, .default, nil, .default)
                        }
                    })
                case .longTap:
                    strongSelf.controllerInteraction?.longTap(.peerMention(peerId, mention), nil)
                }
            case let .textMention(mention):
                switch action {
                case .tap:
                    strongSelf.controllerInteraction?.openPeerMention(mention, nil)
                case .longTap:
                    strongSelf.controllerInteraction?.longTap(.mention(mention), nil)
                }
            case let .botCommand(command):
                switch action {
                case .tap:
                    strongSelf.controllerInteraction?.sendBotCommand(nil, command)
                case .longTap:
                    strongSelf.controllerInteraction?.longTap(.command(command), nil)
                }
            case let .hashtag(hashtag):
                switch action {
                case .tap:
                    strongSelf.controllerInteraction?.openHashtag(nil, hashtag)
                case .longTap:
                    strongSelf.controllerInteraction?.longTap(.hashtag(hashtag), nil)
                }
            }
        })
        
        self.controllerInteraction?.currentPsaMessageWithTooltip = messageId
        self.updatePollTooltipMessageState(animated: !isAutomatic)
        
        tooltipScreen.willBecomeDismissed = { [weak self] tooltipScreen in
            guard let strongSelf = self else {
                return
            }
            if strongSelf.controllerInteraction?.currentPsaMessageWithTooltip == messageId {
                strongSelf.controllerInteraction?.currentPsaMessageWithTooltip = nil
                strongSelf.updatePollTooltipMessageState(animated: true)
            }
        }
        
        self.forEachController({ controller in
            if let controller = controller as? TooltipScreen {
                controller.dismiss()
            }
            return true
        })
        
        self.present(tooltipScreen, in: .current)
    }
        
    func configurePollCreation(isQuiz: Bool? = nil) -> ViewController? {
        guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
            return nil
        }
        return createPollController(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: EnginePeer(peer), isQuiz: isQuiz, completion: { [weak self] poll in
            guard let strongSelf = self else {
                return
            }
            let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject
            strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
                if let strongSelf = self {
                    strongSelf.chatDisplayNode.collapseInput()
                    
                    strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
                        $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) }
                    })
                }
            }, nil)
            let message: EnqueueMessage = .message(
                text: "",
                attributes: [],
                inlineStickers: [:],
                mediaReference: .standalone(media: TelegramMediaPoll(
                    pollId: MediaId(namespace: Namespaces.Media.LocalPoll, id: Int64.random(in: Int64.min ... Int64.max)),
                    publicity: poll.publicity,
                    kind: poll.kind,
                    text: poll.text.string,
                    textEntities: poll.text.entities,
                    options: poll.options,
                    correctAnswers: poll.correctAnswers,
                    results: poll.results,
                    isClosed: false,
                    deadlineTimeout: poll.deadlineTimeout
                )),
                threadId: strongSelf.chatLocation.threadId,
                replyToMessageId: nil,
                replyToStoryId: nil,
                localGroupingKey: nil,
                correlationId: nil,
                bubbleUpEmojiOrStickersets: []
            )
            strongSelf.sendMessages([message.withUpdatedReplyToMessageId(replyMessageSubject?.subjectModel)])
        })
    }
    
    func transformEnqueueMessages(_ messages: [EnqueueMessage]) -> [EnqueueMessage] {
        let silentPosting = self.presentationInterfaceState.interfaceState.silentPosting
        return transformEnqueueMessages(messages, silentPosting: silentPosting)
    }
    
    @discardableResult func dismissAllUndoControllers() -> UndoOverlayController? {
        var currentOverlayController: UndoOverlayController?
        
        self.window?.forEachController({ controller in
            if let controller = controller as? UndoOverlayController {
                currentOverlayController = controller
            }
        })
        self.forEachController({ controller in
            if let controller = controller as? UndoOverlayController {
                currentOverlayController = controller
            }
            return true
        })
        
        return currentOverlayController
    }
    
    func displayPremiumStickerTooltip(file: TelegramMediaFile, message: Message) {
        let premiumConfiguration = PremiumConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 })
        guard !premiumConfiguration.isPremiumDisabled else {
            return
        }
        
        let currentOverlayController: UndoOverlayController? = self.dismissAllUndoControllers()
        
        if let currentOverlayController = currentOverlayController {
            if case .sticker = currentOverlayController.content {
                return
            }
            currentOverlayController.dismissWithCommitAction()
        }
        
        var stickerPackReference: StickerPackReference?
        for attribute in file.attributes {
            if case let .Sticker(_, packReference, _) = attribute, let packReference = packReference {
                stickerPackReference = packReference
                break
            }
        }
        
        if let stickerPackReference = stickerPackReference {
            let _ = (self.context.engine.stickers.loadedStickerPack(reference: stickerPackReference, forceActualized: false)
            |> deliverOnMainQueue).startStandalone(next: { [weak self] stickerPack in
                if let strongSelf = self, case let .result(info, _, _) = stickerPack {
                    strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: file, loop: true, title: info.title, text: strongSelf.presentationData.strings.Stickers_PremiumPackInfoText, undoText: strongSelf.presentationData.strings.Stickers_PremiumPackView, customAction: nil), elevatedLayout: false, action: { [weak self] action in
                        if let strongSelf = self, action == .undo {
                            let _ = strongSelf.controllerInteraction?.openMessage(message, OpenMessageParams(mode: .default))
                        }
                        return false
                    }), in: .current)
                }
            })
        }
    }
    
    func displayEmojiPackTooltip(file: TelegramMediaFile, message: Message) {
        let premiumConfiguration = PremiumConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 })
        guard !premiumConfiguration.isPremiumDisabled else {
            return
        }
                
        var currentOverlayController: UndoOverlayController?
        
        self.window?.forEachController({ controller in
            if let controller = controller as? UndoOverlayController {
                currentOverlayController = controller
            }
        })
        self.forEachController({ controller in
            if let controller = controller as? UndoOverlayController {
                currentOverlayController = controller
            }
            return true
        })
        
        if let currentOverlayController = currentOverlayController {
            if case .sticker = currentOverlayController.content {
                return
            }
            currentOverlayController.dismissWithCommitAction()
        }
        
        var stickerPackReference: StickerPackReference?
        for attribute in file.attributes {
            if case let .CustomEmoji(_, _, _, packReference) = attribute {
                stickerPackReference = packReference
                break
            }
        }
        
        if let stickerPackReference = stickerPackReference {
            self.presentEmojiList(references: [stickerPackReference])
            
            /*let _ = (self.context.engine.stickers.loadedStickerPack(reference: stickerPackReference, forceActualized: false)
            |> deliverOnMainQueue).startStandalone(next: { [weak self] stickerPack in
                if let strongSelf = self, case let .result(info, _, _) = stickerPack {
                    strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: file, loop: true, title: nil, text: strongSelf.presentationData.strings.Stickers_EmojiPackInfoText(info.title).string, undoText: strongSelf.presentationData.strings.Stickers_PremiumPackView, customAction: nil), elevatedLayout: false, action: { [weak self] action in
                        if let strongSelf = self, action == .undo {
                            strongSelf.presentEmojiList(references: [stickerPackReference])
                        }
                        return false
                    }), in: .current)
                }
            })*/
        }
    }
    
    func displayDiceTooltip(dice: TelegramMediaDice) {
        guard let _ = dice.value else {
            return
        }
        self.window?.forEachController({ controller in
            if let controller = controller as? UndoOverlayController {
                controller.dismissWithCommitAction()
            }
        })
        self.forEachController({ controller in
            if let controller = controller as? UndoOverlayController {
                controller.dismissWithCommitAction()
            }
            return true
        })
        
        let value: String?
        let emoji = dice.emoji.strippedEmoji
        switch emoji {
            case "🎲":
                value = self.presentationData.strings.Conversation_Dice_u1F3B2
            case "🎯":
                value = self.presentationData.strings.Conversation_Dice_u1F3AF
            case "🏀":
                value = self.presentationData.strings.Conversation_Dice_u1F3C0
            case "⚽":
                value = self.presentationData.strings.Conversation_Dice_u26BD
            case "🎰":
                value = self.presentationData.strings.Conversation_Dice_u1F3B0
            case "🎳":
                value = self.presentationData.strings.Conversation_Dice_u1F3B3
            default:
                let emojiHex = emoji.unicodeScalars.map({ String(format:"%02x", $0.value) }).joined().uppercased()
                let key = "Conversation.Dice.u\(emojiHex)"
                if let string = self.presentationData.strings.primaryComponent.dict[key] {
                    value = string
                } else if let string = self.presentationData.strings.secondaryComponent?.dict[key] {
                    value = string
                } else {
                    value = nil
                }
        }
        if let value = value {
            self.present(UndoOverlayController(presentationData: self.presentationData, content: .dice(dice: dice, context: self.context, text: value, action: canSendMessagesToChat(self.presentationInterfaceState) ? self.presentationData.strings.Conversation_SendDice : nil), elevatedLayout: false, action: { [weak self] action in
                if let strongSelf = self, canSendMessagesToChat(strongSelf.presentationInterfaceState), action == .undo {
                    strongSelf.sendMessages([.message(text: "", attributes: [], inlineStickers: [:], mediaReference: AnyMediaReference.standalone(media: TelegramMediaDice(emoji: dice.emoji)), threadId: strongSelf.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])])
                }
                return false
            }), in: .current)
        }
    }
    
    func transformEnqueueMessages(_ messages: [EnqueueMessage], silentPosting: Bool, scheduleTime: Int32? = nil) -> [EnqueueMessage] {
        var defaultReplyMessageSubject: EngineMessageReplySubject?
        switch self.chatLocation {
        case .peer:
            break
        case let .replyThread(replyThreadMessage):
            if let effectiveMessageId = replyThreadMessage.effectiveMessageId {
                defaultReplyMessageSubject = EngineMessageReplySubject(messageId: effectiveMessageId, quote: nil)
            }
        case .customChatContents:
            break
        }
        
        return messages.map { message in
            var message = message
            
            if let defaultReplyMessageSubject = defaultReplyMessageSubject {
                switch message {
                case let .message(text, attributes, inlineStickers, mediaReference, threadId, replyToMessageId, replyToStoryId, localGroupingKey, correlationId, bubbleUpEmojiOrStickersets):
                    if replyToMessageId == nil {
                        message = .message(text: text, attributes: attributes, inlineStickers: inlineStickers, mediaReference: mediaReference, threadId: threadId, replyToMessageId: defaultReplyMessageSubject, replyToStoryId: replyToStoryId, localGroupingKey: localGroupingKey, correlationId: correlationId, bubbleUpEmojiOrStickersets: bubbleUpEmojiOrStickersets)
                    }
                case .forward:
                    break
                }
            }
            
            if case let .replyThread(replyThreadMessage) = self.chatLocation, replyThreadMessage.peerId == self.context.account.peerId {
                switch message {
                case let .message(text, attributes, inlineStickers, mediaReference, threadId, replyToMessageId, replyToStoryId, localGroupingKey, correlationId, bubbleUpEmojiOrStickersets):
                    message = .message(text: text, attributes: attributes, inlineStickers: inlineStickers, mediaReference: mediaReference, threadId: threadId ?? replyThreadMessage.threadId, replyToMessageId: replyToMessageId, replyToStoryId: replyToStoryId, localGroupingKey: localGroupingKey, correlationId: correlationId, bubbleUpEmojiOrStickersets: bubbleUpEmojiOrStickersets)
                case .forward:
                    break
                }
            }
            
            return message.withUpdatedAttributes { attributes in
                var attributes = attributes
                if silentPosting || scheduleTime != nil {
                    for i in (0 ..< attributes.count).reversed() {
                        if attributes[i] is NotificationInfoMessageAttribute {
                            attributes.remove(at: i)
                        } else if let _ = scheduleTime, attributes[i] is OutgoingScheduleInfoMessageAttribute {
                            attributes.remove(at: i)
                        }
                    }
                    if silentPosting {
                        attributes.append(NotificationInfoMessageAttribute(flags: .muted))
                    }
                    if let scheduleTime = scheduleTime {
                         attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime))
                    }
                }
                if let sendAsPeerId = self.presentationInterfaceState.currentSendAsPeerId {
                    if attributes.first(where: { $0 is SendAsMessageAttribute }) == nil {
                        attributes.append(SendAsMessageAttribute(peerId: sendAsPeerId))
                    }
                }
                if let sendMessageEffect = self.presentationInterfaceState.interfaceState.sendMessageEffect {
                    if attributes.first(where: { $0 is EffectMessageAttribute }) == nil {
                        attributes.append(EffectMessageAttribute(id: sendMessageEffect))
                    }
                }
                return attributes
            }
        }
    }
    
    func sendMessages(_ messages: [EnqueueMessage], media: Bool = false, commit: Bool = false) {
        if case let .customChatContents(customChatContents) = self.subject {
            customChatContents.enqueueMessages(messages: messages)
            return
        }
        
        guard let peerId = self.chatLocation.peerId else {
            return
        }
        
        var isScheduledMessages = false
        if case .scheduledMessages = self.presentationInterfaceState.subject {
            isScheduledMessages = true
        }
        
        if commit || !isScheduledMessages {
            self.commitPurposefulAction()
            
            let _ = (enqueueMessages(account: self.context.account, peerId: peerId, messages: self.transformEnqueueMessages(messages))
            |> deliverOnMainQueue).startStandalone(next: { [weak self] _ in
                if let strongSelf = self, strongSelf.presentationInterfaceState.subject != .scheduledMessages {
                    strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory()
                }
            })
            
            donateSendMessageIntent(account: self.context.account, sharedContext: self.context.sharedContext, intentContext: .chat, peerIds: [peerId])
            
            self.updateChatPresentationInterfaceState(interactive: true, { $0.updatedShowCommands(false) })
        } else {
            self.presentScheduleTimePicker(style: media ? .media : .default, dismissByTapOutside: false, completion: { [weak self] time in
                if let strongSelf = self {
                    strongSelf.sendMessages(strongSelf.transformEnqueueMessages(messages, silentPosting: false, scheduleTime: time), commit: true)
                }
            })
        }
    }
    
    func enqueueMediaMessages(signals: [Any]?, silentPosting: Bool, scheduleTime: Int32? = nil, parameters: ChatSendMessageActionSheetController.SendParameters? = nil, getAnimatedTransitionSource: ((String) -> UIView?)? = nil, completion: @escaping () -> Void = {}) {
        self.enqueueMediaMessageDisposable.set((legacyAssetPickerEnqueueMessages(context: self.context, account: self.context.account, signals: signals!)
        |> deliverOnMainQueue).startStrict(next: { [weak self] items in
            if let strongSelf = self {
                var completionImpl: (() -> Void)? = completion

                var usedCorrelationId: Int64?

                var mappedMessages: [EnqueueMessage] = []
                var addedTransitions: [(Int64, [String], () -> Void)] = []
                
                var groupedCorrelationIds: [Int64: Int64] = [:]
                
                var skipAddingTransitions = false
                
                for item in items {
                    var message = item.message
                    if message.groupingKey != nil {
                        if items.count > 10 {
                            skipAddingTransitions = true
                        }
                    } else if items.count > 3 {
                        skipAddingTransitions = true
                    }
                    
                    if let uniqueId = item.uniqueId, !item.isFile && !skipAddingTransitions {
                        let correlationId: Int64
                        var addTransition = scheduleTime == nil
                        if let groupingKey = message.groupingKey {
                            if let existing = groupedCorrelationIds[groupingKey] {
                                correlationId = existing
                                addTransition = false
                            } else {
                                correlationId = Int64.random(in: 0 ..< Int64.max)
                                groupedCorrelationIds[groupingKey] = correlationId
                            }
                        } else {
                            correlationId = Int64.random(in: 0 ..< Int64.max)
                        }
                        message = message.withUpdatedCorrelationId(correlationId)

                        if addTransition {
                            addedTransitions.append((correlationId, [uniqueId], addedTransitions.isEmpty ? completion : {}))
                        } else {
                            if let index = addedTransitions.firstIndex(where: { $0.0 == correlationId }) {
                                var (correlationId, uniqueIds, completion) = addedTransitions[index]
                                uniqueIds.append(uniqueId)
                                addedTransitions[index] = (correlationId, uniqueIds, completion)
                            }
                        }
                        
                        usedCorrelationId = correlationId
                        completionImpl = nil
                    }
                    
                    if let parameters {
                        if let effect = parameters.effect {
                            message = message.withUpdatedAttributes { attributes in
                                var attributes = attributes
                                attributes.append(EffectMessageAttribute(id: effect.id))
                                return attributes
                            }
                        }
                        if parameters.textIsAboveMedia {
                            message = message.withUpdatedAttributes { attributes in
                                var attributes = attributes
                                attributes.append(InvertMediaMessageAttribute())
                                return attributes
                            }
                        }
                    }
                    
                    mappedMessages.append(message)
                }
                        
                if addedTransitions.count > 1 {
                    var transitions: [(Int64, ChatMessageTransitionNodeImpl.Source, () -> Void)] = []
                    for (correlationId, uniqueIds, initiated) in addedTransitions {
                        var source: ChatMessageTransitionNodeImpl.Source?
                        if uniqueIds.count > 1 {
                            source = .groupedMediaInput(ChatMessageTransitionNodeImpl.Source.GroupedMediaInput(extractSnapshots: {
                                return uniqueIds.compactMap({ getAnimatedTransitionSource?($0) })
                            }))
                        } else if let uniqueId = uniqueIds.first {
                            source = .mediaInput(ChatMessageTransitionNodeImpl.Source.MediaInput(extractSnapshot: {
                                return getAnimatedTransitionSource?(uniqueId)
                            }))
                        }
                        if let source = source {
                            transitions.append((correlationId, source, initiated))
                        }
                    }
                    strongSelf.chatDisplayNode.messageTransitionNode.add(grouped: transitions)
                } else if let (correlationId, uniqueIds, initiated) = addedTransitions.first {
                    var source: ChatMessageTransitionNodeImpl.Source?
                    if uniqueIds.count > 1 {
                        source = .groupedMediaInput(ChatMessageTransitionNodeImpl.Source.GroupedMediaInput(extractSnapshots: {
                            return uniqueIds.compactMap({ getAnimatedTransitionSource?($0) })
                        }))
                    } else if let uniqueId = uniqueIds.first {
                        source = .mediaInput(ChatMessageTransitionNodeImpl.Source.MediaInput(extractSnapshot: {
                            return getAnimatedTransitionSource?(uniqueId)
                        }))
                    }
                    if let source = source {
                        strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: source, initiated: {
                            initiated()
                        })
                    }
                }
                
                if case let .customChatContents(customChatContents) = strongSelf.presentationInterfaceState.subject, let messageLimit = customChatContents.messageLimit {
                    if let originalHistoryView = strongSelf.chatDisplayNode.historyNode.originalHistoryView, originalHistoryView.entries.count + mappedMessages.count > messageLimit {
                        strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.Chat_QuickReplyMediaMessageLimitReachedText(Int32(messageLimit)), actions: [
                            TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {})
                        ]), in: .window(.root))
                        return
                    }
                }
                                                    
                let messages = strongSelf.transformEnqueueMessages(mappedMessages, silentPosting: silentPosting, scheduleTime: scheduleTime)
                let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject
                strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
                    if let strongSelf = self {
                        strongSelf.chatDisplayNode.collapseInput()
                        
                        strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
                            $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) }
                        })
                    }
                    completionImpl?()
                }, usedCorrelationId)

                strongSelf.sendMessages(messages.map { $0.withUpdatedReplyToMessageId(replyMessageSubject?.subjectModel) }, media: true)
                
                if let _ = scheduleTime {
                    completion()
                }
            }
        }))
    }

    func enqueueChatContextResult(_ results: ChatContextResultCollection, _ result: ChatContextResult, hideVia: Bool = false, closeMediaInput: Bool = false, silentPosting: Bool = false, resetTextInputState: Bool = true) {
        if !canSendMessagesToChat(self.presentationInterfaceState) {
            return
        }
        
        guard let peerId = self.chatLocation.peerId else {
            return
        }
        
        var isScheduledMessages = false
        if case .scheduledMessages = self.presentationInterfaceState.subject {
            isScheduledMessages = true
        }

        let sendMessage: (Int32?) -> Void = { [weak self] scheduleTime in
            guard let self else {
                return
            }
            let replyMessageSubject = self.presentationInterfaceState.interfaceState.replyMessageSubject
            if self.context.engine.messages.enqueueOutgoingMessageWithChatContextResult(to: peerId, threadId: self.chatLocation.threadId, botId: results.botId, result: result, replyToMessageId: replyMessageSubject?.subjectModel, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime) {
                self.chatDisplayNode.setupSendActionOnViewUpdate({ [weak self] in
                    if let strongSelf = self {
                        strongSelf.chatDisplayNode.collapseInput()
                        
                        strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in
                            var state = state
                            if resetTextInputState {
                                state = state.updatedInterfaceState { interfaceState in
                                    var interfaceState = interfaceState
                                    interfaceState = interfaceState.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil)
                                    interfaceState = interfaceState.withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: "")))
                                    interfaceState = interfaceState.withUpdatedComposeDisableUrlPreviews([])
                                    return interfaceState
                                }
                            }
                            state = state.updatedInputMode { current in
                                if case let .media(mode, maybeExpanded, focused) = current, maybeExpanded != nil  {
                                    return .media(mode: mode, expanded: nil, focused: focused)
                                }
                                return current
                            }
                            return state
                        })
                    }
                }, nil)
            }
        }
        
        if isScheduledMessages {
            self.presentScheduleTimePicker(style: .default, dismissByTapOutside: false, completion: { time in
                sendMessage(time)
            })
        } else {
            sendMessage(nil)
        }
    }
    
    func firstLoadedMessageToListen() -> Message? {
        var messageToListen: Message?
        self.chatDisplayNode.historyNode.forEachMessageInCurrentHistoryView { message in
            if message.flags.contains(.Incoming) && message.tags.contains(.voiceOrInstantVideo) {
                for attribute in message.attributes {
                    if let attribute = attribute as? ConsumableContentMessageAttribute, !attribute.consumed {
                        messageToListen = message
                        return false
                    }
                }
            }
            return true
        }
        return messageToListen
    }
    
    var raiseToListenActivateRecordingTimer: SwiftSignalKit.Timer?
    
    func activateRaiseGesture() {
        self.raiseToListenActivateRecordingTimer?.invalidate()
        self.raiseToListenActivateRecordingTimer = nil
        if let messageToListen = self.firstLoadedMessageToListen() {
            let _ = self.controllerInteraction?.openMessage(messageToListen, OpenMessageParams(mode: .default))
        } else {
            let timeout = (self.voicePlaylistDidEndTimestamp + 1.0) - CACurrentMediaTime()
            self.raiseToListenActivateRecordingTimer = SwiftSignalKit.Timer(timeout: max(0.0, timeout), repeat: false, completion: { [weak self] in
                self?.requestAudioRecorder(beginWithTone: true)
            }, queue: .mainQueue())
            self.raiseToListenActivateRecordingTimer?.start()
        }
    }
    
    func deactivateRaiseGesture() {
        self.raiseToListenActivateRecordingTimer?.invalidate()
        self.raiseToListenActivateRecordingTimer = nil
        self.dismissMediaRecorder(.pause)
    }
    
    func updateDownButtonVisibility() {
        let recordingMediaMessage = self.audioRecorderValue != nil || self.videoRecorderValue != nil || self.presentationInterfaceState.interfaceState.mediaDraftState != nil
        
        var ignoreSearchState = false
        if case let .customChatContents(contents) = self.subject, case .hashTagSearch = contents.kind {
            ignoreSearchState = true
        }
        
        if !ignoreSearchState, let search = self.presentationInterfaceState.search, let results = search.resultsState, results.messageIndices.count != 0 {
            var resultIndex: Int?
            if let currentId = results.currentId, let index = results.messageIndices.firstIndex(where: { $0.id == currentId }) {
                resultIndex = index
            } else {
                resultIndex = nil
            }
            if let resultIndex {
                self.chatDisplayNode.navigateButtons.directionButtonState = ChatHistoryNavigationButtons.DirectionState(
                    up: ChatHistoryNavigationButtons.ButtonState(isEnabled: resultIndex != 0),
                    down: ChatHistoryNavigationButtons.ButtonState(isEnabled: resultIndex != Int(results.totalCount) - 1 || (self.shouldDisplayDownButton && !recordingMediaMessage))
                )
            } else {
                self.chatDisplayNode.navigateButtons.directionButtonState = ChatHistoryNavigationButtons.DirectionState(
                    up: ChatHistoryNavigationButtons.ButtonState(isEnabled: false),
                    down: ChatHistoryNavigationButtons.ButtonState(isEnabled: false)
                )
            }
        } else {
            self.chatDisplayNode.navigateButtons.directionButtonState = ChatHistoryNavigationButtons.DirectionState(
                up: nil,
                down: (self.shouldDisplayDownButton && !recordingMediaMessage) ? ChatHistoryNavigationButtons.ButtonState(isEnabled: true) : nil
            )
        }
    }
    
    func updateTextInputState(_ textInputState: ChatTextInputState) {
        self.updateChatPresentationInterfaceState(interactive: false, { state in
            state.updatedInterfaceState({ state in
                state.withUpdatedComposeInputState(textInputState)
            })
        })
    }
    
    public func navigateToMessage(messageLocation: NavigateToMessageLocation, animated: Bool, forceInCurrentChat: Bool = false, dropStack: Bool = false, completion: (() -> Void)? = nil, customPresentProgress: ((ViewController, Any?) -> Void)? = nil) {
        let scrollPosition: ListViewScrollPosition
        if case .upperBound = messageLocation {
            scrollPosition = .top(0.0)
        } else {
            scrollPosition = .center(.bottom)
        }
        self.navigateToMessage(from: nil, to: messageLocation, scrollPosition: scrollPosition, rememberInStack: false, forceInCurrentChat: forceInCurrentChat, dropStack: dropStack, animated: animated, completion: completion, customPresentProgress: customPresentProgress)
    }
    
    func openStories(peerId: EnginePeer.Id, avatarHeaderNode: ChatMessageAvatarHeaderNodeImpl?, avatarNode: AvatarNode?) {
        if let avatarNode = avatarHeaderNode?.avatarNode ?? avatarNode {
            StoryContainerScreen.openPeerStories(context: self.context, peerId: peerId, parentController: self, avatarNode: avatarNode)
        }
    }
    
    func openPeerMention(_ name: String, navigation: ChatControllerInteractionNavigateToPeer = .default, sourceMessageId: MessageId? = nil, progress: Promise<Bool>? = nil) {
        let _ = self.presentVoiceMessageDiscardAlert(action: {
            let disposable: MetaDisposable
            if let resolvePeerByNameDisposable = self.resolvePeerByNameDisposable {
                disposable = resolvePeerByNameDisposable
            } else {
                disposable = MetaDisposable()
                self.resolvePeerByNameDisposable = disposable
            }
            var resolveSignal = self.context.engine.peers.resolvePeerByName(name: name, ageLimit: 10)
            
            var cancelImpl: (() -> Void)?
            let presentationData = self.presentationData
            let progressSignal = Signal<Never, NoError> { [weak self] subscriber in
                if progress != nil {
                    return ActionDisposable {
                    }
                } else {
                    let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
                        cancelImpl?()
                    }))
                    self?.present(controller, in: .window(.root))
                    return ActionDisposable { [weak controller] in
                        Queue.mainQueue().async() {
                            controller?.dismiss()
                        }
                    }
                }
            }
            |> runOn(Queue.mainQueue())
            |> delay(0.15, queue: Queue.mainQueue())
            let progressDisposable = progressSignal.start()
            
            resolveSignal = resolveSignal
            |> afterDisposed {
                Queue.mainQueue().async {
                    progressDisposable.dispose()
                }
            }
            cancelImpl = { [weak self] in
                self?.resolvePeerByNameDisposable?.set(nil)
            }
            disposable.set((resolveSignal
            |> deliverOnMainQueue).start(next: { [weak self] result in
                guard let self else {
                    return
                }
                switch result {
                case .progress:
                    progress?.set(.single(true))
                case let .result(peer):
                    progress?.set(.single(false))
                    
                    if let peer {
                        var navigation = navigation
                        if case .default = navigation {
                            if case let .user(user) = peer, user.botInfo != nil {
                                navigation = .chat(textInputState: nil, subject: nil, peekData: nil)
                            }
                        }
                        self.openResolved(result: .peer(peer._asPeer(), navigation), sourceMessageId: sourceMessageId)
                    } else {
                        self.present(textAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, title: nil, text: self.presentationData.strings.Resolve_ErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
                    }
                }
            }))
        })
    }
    
    func openHashtag(_ hashtag: String, peerName: String?) {
        let _ = self.presentVoiceMessageDiscardAlert(action: {
            if self.resolvePeerByNameDisposable == nil {
                self.resolvePeerByNameDisposable = MetaDisposable()
            }
            var resolveSignal: Signal<Peer?, NoError>
            if let peerName = peerName {
                resolveSignal = self.context.engine.peers.resolvePeerByName(name: peerName)
                |> mapToSignal { result -> Signal<EnginePeer?, NoError> in
                    guard case let .result(result) = result else {
                        return .complete()
                    }
                    return .single(result)
                }
                |> mapToSignal { peer -> Signal<Peer?, NoError> in
                    if let peer = peer {
                        return .single(peer._asPeer())
                    } else {
                        return .single(nil)
                    }
                }
            } else if let peerId = self.chatLocation.peerId {
                resolveSignal = self.context.account.postbox.loadedPeerWithId(peerId)
                |> map(Optional.init)
            } else {
                resolveSignal = .single(nil)
            }
            var cancelImpl: (() -> Void)?
            let presentationData = self.presentationData
            let progressSignal = Signal<Never, NoError> { [weak self] subscriber in
                let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
                    cancelImpl?()
                }))
                self?.present(controller, in: .window(.root))
                return ActionDisposable { [weak controller] in
                    Queue.mainQueue().async() {
                        controller?.dismiss()
                    }
                }
            }
            |> runOn(Queue.mainQueue())
            |> delay(0.25, queue: Queue.mainQueue())
            let progressDisposable = progressSignal.start()
            
            resolveSignal = resolveSignal
            |> afterDisposed {
                Queue.mainQueue().async {
                    progressDisposable.dispose()
                }
            }
            cancelImpl = { [weak self] in
                self?.resolvePeerByNameDisposable?.set(nil)
            }
            self.resolvePeerByNameDisposable?.set((resolveSignal
            |> deliverOnMainQueue).start(next: { [weak self] peer in
                if let self, !hashtag.isEmpty {
                    var publicPosts = false
                    if let peer = self.presentationInterfaceState.renderedPeer, let channel = peer.peer as? TelegramChannel, case .broadcast = channel.info, !(channel.addressName ?? "").isEmpty {
                        publicPosts = true
                    } else if case let .customChatContents(contents) = self.subject, case let .hashTagSearch(publicPostsValue) = contents.kind {
                        publicPosts = publicPostsValue
                    }
                    let searchController = HashtagSearchController(context: self.context, peer: peer.flatMap(EnginePeer.init), query: hashtag, publicPosts: publicPosts)
                    self.effectiveNavigationController?.pushViewController(searchController)
                }
            }))
        })
    }
    
    func shareAccountContact() {
        let _ = (self.context.account.postbox.loadedPeerWithId(self.context.account.peerId)
        |> deliverOnMainQueue).startStandalone(next: { [weak self] accountPeer in
            guard let strongSelf = self else {
                return
            }
            guard let user = accountPeer as? TelegramUser, let phoneNumber = user.phone else {
                return
            }
            guard let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer as? TelegramUser else {
                return
            }
            
            let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData)
            var items: [ActionSheetItem] = []
            items.append(ActionSheetTextItem(title: strongSelf.presentationData.strings.Conversation_ShareMyPhoneNumberConfirmation(formatPhoneNumber(context: strongSelf.context, number: phoneNumber), EnginePeer(peer).compactDisplayTitle).string))
            items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_ShareMyPhoneNumber, action: { [weak actionSheet] in
                actionSheet?.dismissAnimated()
                guard let strongSelf = self else {
                    return
                }
                let _ = (strongSelf.context.engine.contacts.acceptAndShareContact(peerId: peer.id)
                |> deliverOnMainQueue).startStandalone(error: { _ in
                    guard let strongSelf = self else {
                        return
                    }
                    strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
                }, completed: {
                    guard let strongSelf = self else {
                        return
                    }
                    strongSelf.present(OverlayStatusController(theme: strongSelf.presentationData.theme, type: .genericSuccess(strongSelf.presentationData.strings.Conversation_ShareMyPhoneNumber_StatusSuccess(EnginePeer(peer).compactDisplayTitle).string, true)), in: .window(.root))
                })
            }))
            
            actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
                ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
                    actionSheet?.dismissAnimated()
                })
            ])])
            strongSelf.chatDisplayNode.dismissInput()
            strongSelf.present(actionSheet, in: .window(.root))
        })
    }
    
    func addPeerContact() {
        if let peer = self.presentationInterfaceState.renderedPeer?.chatMainPeer as? TelegramUser, let peerStatusSettings = self.presentationInterfaceState.contactStatus?.peerStatusSettings, let contactData = DeviceContactExtendedData(peer: EnginePeer(peer)) {
            self.present(context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: self.context), environment: ShareControllerAppEnvironment(sharedContext: self.context.sharedContext), subject: .create(peer: peer, contactData: contactData, isSharing: true, shareViaException: peerStatusSettings.contains(.addExceptionWhenAddingContact), completion: { [weak self] peer, stableId, contactData in
                guard let strongSelf = self else {
                    return
                }
                if let peer = peer as? TelegramUser {
                    if let phone = peer.phone, !phone.isEmpty {
                    }
                    
                    self?.present(OverlayStatusController(theme: strongSelf.presentationData.theme, type: .genericSuccess(strongSelf.presentationData.strings.AddContact_StatusSuccess(EnginePeer(peer).compactDisplayTitle).string, true)), in: .window(.root))
                }
            }), completed: nil, cancelled: nil), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
        }
    }
    
    func dismissPeerContactOptions() {
        guard case let .peer(peerId) = self.chatLocation else {
            return
        }
        let dismissPeerId: PeerId
        if let peer = self.presentationInterfaceState.renderedPeer?.chatMainPeer as? TelegramUser {
            dismissPeerId = peer.id
        } else {
            dismissPeerId = peerId
        }
        self.editMessageDisposable.set((self.context.engine.peers.dismissPeerStatusOptions(peerId: dismissPeerId)
        |> afterDisposed({
            Queue.mainQueue().async {
            }
        })).startStrict())
    }
    
    func deleteChat(reportChatSpam: Bool) {
        guard case let .peer(peerId) = self.chatLocation else {
            return
        }
        self.commitPurposefulAction()
        self.chatDisplayNode.historyNode.disconnect()
        let _ = self.context.engine.peers.removePeerChat(peerId: peerId, reportChatSpam: reportChatSpam).startStandalone()
        self.effectiveNavigationController?.popToRoot(animated: true)
        
        let _ = self.context.engine.privacy.requestUpdatePeerIsBlocked(peerId: peerId, isBlocked: true).startStandalone()
    }
    
    func startBot(_ payload: String?) {
        guard case let .peer(peerId) = self.chatLocation else {
            return
        }
        
        let startingBot = self.startingBot
        startingBot.set(true)
        self.editMessageDisposable.set((self.context.engine.messages.requestStartBot(botPeerId: peerId, payload: payload) |> deliverOnMainQueue |> afterDisposed({
            startingBot.set(false)
        })).startStrict(completed: { [weak self] in
            if let strongSelf = self {
                strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedBotStartPayload(nil) })
            }
        }))
    }
    
    func openResolved(result: ResolvedUrl, sourceMessageId: MessageId?, progress: Promise<Bool>? = nil, forceExternal: Bool = false, concealed: Bool = false, commit: @escaping () -> Void = {}) {
        let urlContext: OpenURLContext
        
        let message = sourceMessageId.flatMap { self.chatDisplayNode.historyNode.messageInCurrentHistoryView($0) }
        if let peerId = self.chatLocation.peerId {
            urlContext = .chat(peerId: peerId, message: message, updatedPresentationData: self.updatedPresentationData)
        } else {
            urlContext = .generic
        }
        self.context.sharedContext.openResolvedUrl(result, context: self.context, urlContext: urlContext, navigationController: self.effectiveNavigationController, forceExternal: forceExternal, openPeer: { [weak self] peerId, navigation in
            guard let strongSelf = self else {
                return
            }
            
            let dismissWebAppControllers: () -> Void = {
            }
            
            switch navigation {
                case let .chat(textInputState, subject, peekData):
                    dismissWebAppControllers()
                    if case .peer(peerId.id) = strongSelf.chatLocation {
                        if let subject = subject, case let .message(messageSubject, _, timecode, _) = subject {
                            if case let .id(messageId) = messageSubject {
                                strongSelf.navigateToMessage(from: sourceMessageId, to: .id(messageId, NavigateToMessageParams(timestamp: timecode, quote: nil)))
                            }
                        } else {
                            self?.playShakeAnimation()
                        }
                    } else if let navigationController = strongSelf.effectiveNavigationController {
                        if case let .channel(channel) = peerId, channel.flags.contains(.isForum) {
                            strongSelf.context.sharedContext.navigateToForumChannel(context: strongSelf.context, peerId: peerId.id, navigationController: navigationController)
                        } else {
                            strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), subject: subject, updateTextInputState: !peerId.id.isGroupOrChannel ? textInputState : nil, keepStack: .always, peekData: peekData))
                        }
                    }
                    commit()
                case .info:
                    dismissWebAppControllers()
                    strongSelf.navigationActionDisposable.set((strongSelf.context.account.postbox.loadedPeerWithId(peerId.id)
                    |> take(1)
                    |> deliverOnMainQueue).startStrict(next: { [weak self] peer in
                        if let strongSelf = self, peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil {
                            if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
                                strongSelf.effectiveNavigationController?.pushViewController(infoController)
                            }
                        }
                    }))
                    commit()
                case let .withBotStartPayload(startPayload):
                    dismissWebAppControllers()
                    if case .peer(peerId.id) = strongSelf.chatLocation {
                        strongSelf.startBot(startPayload.payload)
                    } else if let navigationController = strongSelf.effectiveNavigationController {
                        strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), botStart: startPayload, keepStack: .always))
                    }
                    commit()
                case let .withAttachBot(attachBotStart):
                    dismissWebAppControllers()
                    if let navigationController = strongSelf.effectiveNavigationController {
                        strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), attachBotStart: attachBotStart))
                    }
                    commit()
                case let .withBotApp(botAppStart):
                    let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId.id))
                    |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in
                        if let strongSelf = self, let peer {
                            if let botApp = botAppStart.botApp {
                                strongSelf.presentBotApp(botApp: botApp, botPeer: peer, payload: botAppStart.payload, compact: botAppStart.compact, concealed: concealed, commit: {
                                    dismissWebAppControllers()
                                    commit()
                                })
                            } else {
                                strongSelf.context.sharedContext.openWebApp(context: strongSelf.context, parentController: strongSelf, updatedPresentationData: strongSelf.updatedPresentationData, peer: peer, threadId: nil, buttonText: "", url: "", simple: true, source: .generic, skipTermsOfService: false)
                                commit()
                            }
                        }
                    })
                default:
                    break
                }
        }, sendFile: nil, sendSticker: { [weak self] f, sourceView, sourceRect in
            return self?.interfaceInteraction?.sendSticker(f, true, sourceView, sourceRect, nil, []) ?? false
        }, sendEmoji: { [weak self] text, attribute in
            guard let self, canSendMessagesToChat(self.presentationInterfaceState) else {
                return
            }
            self.controllerInteraction?.sendEmoji(text, attribute, false)
        },
        requestMessageActionUrlAuth: { [weak self] subject in
            if case let .url(url) = subject {
                self?.controllerInteraction?.requestMessageActionUrlAuth(url, subject)
            }
        }, joinVoiceChat: { [weak self] peerId, invite, call in
            self?.joinGroupCall(peerId: peerId, invite: invite, activeCall: EngineGroupCallDescription(call))
        }, present: { [weak self] c, a in
            if c is UndoOverlayController {
                self?.present(c, in: .current)
            } else {
                self?.present(c, in: .window(.root), with: a)
            }
        }, dismissInput: { [weak self] in
            self?.chatDisplayNode.dismissInput()
        }, contentContext: nil, progress: progress, completion: nil)
    }
    
    func openUrl(_ url: String, concealed: Bool, forceExternal: Bool = false, skipUrlAuth: Bool = false, skipConcealedAlert: Bool = false, message: Message? = nil, allowInlineWebpageResolution: Bool = false, progress: Promise<Bool>? = nil, commit: @escaping () -> Void = {}) {
        self.commitPurposefulAction()
        
        if allowInlineWebpageResolution, let message, let webpage = message.media.first(where: { $0 is TelegramMediaWebpage }) as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content, content.url == url {
            if content.instantPage != nil {
                if let navigationController = self.navigationController as? NavigationController {
                    switch instantPageType(of: content) {
                    case .album:
                        break
                    default:
                        progress?.set(.single(false))
                        if let controller = self.context.sharedContext.makeInstantPageController(context: self.context, message: message, sourcePeerType: nil) {
                            navigationController.pushViewController(controller)
                        }
                        return
                    }
                }
            } else if content.file == nil, (content.image == nil || content.isMediaLargeByDefault == true || content.isMediaLargeByDefault == nil), let embedUrl = content.embedUrl, !embedUrl.isEmpty {
                progress?.set(.single(false))
                if let controllerInteraction = self.controllerInteraction {
                    if controllerInteraction.openMessage(message, OpenMessageParams(mode: .default)) {
                        return
                    }
                }
            }
        }
        
        let _ = self.presentVoiceMessageDiscardAlert(action: { [weak self] in
            guard let self else {
                return
            }
            let disposable = openUserGeneratedUrl(context: self.context, peerId: self.peerView?.peerId, url: url, concealed: concealed, skipUrlAuth: skipUrlAuth, skipConcealedAlert: skipConcealedAlert, present: { [weak self] c in
                self?.present(c, in: .window(.root))
            }, openResolved: { [weak self] resolved in
                self?.openResolved(result: resolved, sourceMessageId: message?.id, progress: progress, forceExternal: forceExternal, concealed: concealed, commit: commit)
            }, progress: progress)
            self.navigationActionDisposable.set(disposable)
        }, performAction: true)
    }
    
    func openUrlIn(_ url: String) {
        let actionSheet = OpenInActionSheetController(context: self.context, updatedPresentationData: self.updatedPresentationData, item: .url(url: url), openUrl: { [weak self] url in
            if let strongSelf = self, let navigationController = strongSelf.effectiveNavigationController {
                strongSelf.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: url, forceExternal: true, presentationData: strongSelf.presentationData, navigationController: navigationController, dismissInput: {
                    self?.chatDisplayNode.dismissInput()
                })
            }
        })
        self.chatDisplayNode.dismissInput()
        self.present(actionSheet, in: .window(.root))
    }
    
    @available(iOSApplicationExtension 11.0, iOS 11.0, *)
    public func dropInteraction(_ interaction: UIDropInteraction, canHandle session: UIDropSession) -> Bool {
        return session.hasItemsConforming(toTypeIdentifiers: [kUTTypeImage as String])
    }
    
    @available(iOSApplicationExtension 11.0, iOS 11.0, *)
    public func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession) -> UIDropProposal {
        if !canSendMessagesToChat(self.presentationInterfaceState) {
            return UIDropProposal(operation: .cancel)
        }
        
        //let dropLocation = session.location(in: self.chatDisplayNode.view)
        self.chatDisplayNode.updateDropInteraction(isActive: true)
        
        let operation: UIDropOperation
        operation = .copy
        return UIDropProposal(operation: operation)
    }
    
    @available(iOSApplicationExtension 11.0, iOS 11.0, *)
    public func dropInteraction(_ interaction: UIDropInteraction, performDrop session: UIDropSession) {
        session.loadObjects(ofClass: UIImage.self) { [weak self] imageItems in
            guard let strongSelf = self, !imageItems.isEmpty else {
                return
            }
            let images = imageItems as! [UIImage]
            
            strongSelf.chatDisplayNode.updateDropInteraction(isActive: false)
            if images.count == 1, let image = images.first {
                let maxSide = max(image.size.width, image.size.height)
                if maxSide.isZero {
                    return
                }
                let aspectRatio = min(image.size.width, image.size.height) / maxSide
                if (imageHasTransparency(image) && aspectRatio > 0.2) {
                    strongSelf.enqueueStickerImage(image, isMemoji: false)
                    return
                }
            }
            strongSelf.chatDisplayNode.updateDropInteraction(isActive: false)
            strongSelf.displayPasteMenu(images.map { .image($0) })
        }
    }
    
    @available(iOSApplicationExtension 11.0, iOS 11.0, *)
    public func dropInteraction(_ interaction: UIDropInteraction, sessionDidExit session: UIDropSession) {
        self.chatDisplayNode.updateDropInteraction(isActive: false)
    }
    
    @available(iOSApplicationExtension 11.0, iOS 11.0, *)
    public func dropInteraction(_ interaction: UIDropInteraction, sessionDidEnd session: UIDropSession) {
        self.chatDisplayNode.updateDropInteraction(isActive: false)
    }
    
    public func beginMessageSearch(_ query: String) {
        self.interfaceInteraction?.beginMessageSearch(.everything, query)
    }
    
    public func beginReportSelection(reason: ReportReason) {
        self.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedReportReason(reason).updatedInterfaceState { $0.withUpdatedSelectedMessages([]) } })
    }
    
    func displayMediaRecordingTooltip() {
        guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
            return
        }
        
        if self.birthdayTooltipController != nil {
            return
        }
        
        let rect: CGRect? = self.chatDisplayNode.frameForInputActionButton()
        
        let updatedMode: ChatTextInputMediaRecordingButtonMode = self.presentationInterfaceState.interfaceState.mediaRecordingMode
        
        let text: String
        
        var canSwitch = true
        if let channel = peer as? TelegramChannel {
            if channel.hasBannedPermission(.banSendVoice) != nil && channel.hasBannedPermission(.banSendInstantVideos) != nil {
                canSwitch = false
            } else if channel.hasBannedPermission(.banSendVoice) != nil {
                if channel.hasBannedPermission(.banSendInstantVideos) == nil {
                    canSwitch = false
                }
            } else if channel.hasBannedPermission(.banSendInstantVideos) != nil {
                if channel.hasBannedPermission(.banSendVoice) == nil {
                    canSwitch = false
                }
            }
        } else if let group = peer as? TelegramGroup {
            if group.hasBannedPermission(.banSendVoice) && group.hasBannedPermission(.banSendInstantVideos) {
                canSwitch = false
            } else if group.hasBannedPermission(.banSendVoice) {
                if !group.hasBannedPermission(.banSendInstantVideos) {
                    canSwitch = false
                }
            } else if group.hasBannedPermission(.banSendInstantVideos) {
                if !group.hasBannedPermission(.banSendVoice) {
                    canSwitch = false
                }
            }
        }
        
        if updatedMode == .audio {
            if canSwitch {
                text = self.presentationData.strings.Conversation_HoldForAudio
            } else {
                text = self.presentationData.strings.Conversation_HoldForAudioOnly
            }
        } else {
            if canSwitch {
                text = self.presentationData.strings.Conversation_HoldForVideo
            } else {
                text = self.presentationData.strings.Conversation_HoldForVideoOnly
            }
        }
        
        self.silentPostTooltipController?.dismiss()
        
        if let tooltipController = self.mediaRecordingModeTooltipController {
            tooltipController.updateContent(.text(text), animated: true, extendTimer: true)
        } else if let rect = rect {
            let tooltipController = TooltipController(content: .text(text), baseFontSize: self.presentationData.listsFontSize.baseDisplaySize, padding: 2.0)
            self.mediaRecordingModeTooltipController = tooltipController
            tooltipController.dismissed = { [weak self, weak tooltipController] _ in
                if let strongSelf = self, let tooltipController = tooltipController, strongSelf.mediaRecordingModeTooltipController === tooltipController {
                    strongSelf.mediaRecordingModeTooltipController = nil
                }
            }
            self.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: { [weak self] in
                if let strongSelf = self {
                    return (strongSelf.chatDisplayNode, rect)
                }
                return nil
            }))
        }
    }
    
    func displaySendWhenOnlineTooltip() {
        guard let rect = self.chatDisplayNode.frameForInputActionButton(), self.effectiveNavigationController?.topViewController === self, let peerId = self.chatLocation.peerId else {
            return
        }
        let inputText = self.presentationInterfaceState.interfaceState.effectiveInputState.inputText.string
        guard !inputText.isEmpty else {
            return
        }
        
        self.sendingOptionsTooltipController?.dismiss()
        
        let _ = (ApplicationSpecificNotice.getSendWhenOnlineTip(accountManager: self.context.sharedContext.accountManager)
        |> deliverOnMainQueue).startStandalone(next: { [weak self] counter in
            if let strongSelf = self, counter < 3 {
                let _ = (strongSelf.context.account.viewTracker.peerView(peerId)
                |> take(1)
                |> deliverOnMainQueue).startStandalone(next: { [weak self] peerView in
                    guard let strongSelf = self, let peer = peerViewMainPeer(peerView) else {
                        return
                    }
                    var sendWhenOnlineAvailable = false
                    if peer.id != strongSelf.context.account.peerId, let presence = peerView.peerPresences[peer.id] as? TelegramUserPresence, case let .present(until) = presence.status, until != .max {
                        let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
                        let (_, _, _, hours, _) = getDateTimeComponents(timestamp: currentTime)
                        if currentTime > until + 60 * 30 && hours >= 0 && hours <= 8 {
                            sendWhenOnlineAvailable = true
                        }
                    }
                    if peer.id.namespace == Namespaces.Peer.CloudUser && peer.id.id._internalGetInt64Value() == 777000 {
                        sendWhenOnlineAvailable = false
                    }
                    
                    if sendWhenOnlineAvailable {
                        let _ = ApplicationSpecificNotice.incrementSendWhenOnlineTip(accountManager: strongSelf.context.sharedContext.accountManager).startStandalone()
                        
                        let tooltipController = TooltipController(content: .text(strongSelf.presentationData.strings.Conversation_SendWhenOnlineTooltip), baseFontSize: strongSelf.presentationData.listsFontSize.baseDisplaySize, timeout: 3.0, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true, padding: 2.0)
                        strongSelf.sendingOptionsTooltipController = tooltipController
                        tooltipController.dismissed = { [weak self, weak tooltipController] _ in
                            if let strongSelf = self, let tooltipController = tooltipController, strongSelf.sendingOptionsTooltipController === tooltipController {
                                strongSelf.sendingOptionsTooltipController = nil
                            }
                        }
                        strongSelf.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: { [weak self] in
                            if let strongSelf = self {
                                return (strongSelf.chatDisplayNode, rect)
                            }
                            return nil
                        }))
                    }
                })
            }
        })
    }
    
    func displaySendingOptionsTooltip() {
        guard let rect = self.chatDisplayNode.frameForInputActionButton(), self.effectiveNavigationController?.topViewController === self else {
            return
        }
        self.sendingOptionsTooltipController?.dismiss()
        let tooltipController = TooltipController(content: .text(self.presentationData.strings.Conversation_SendingOptionsTooltip), baseFontSize: self.presentationData.listsFontSize.baseDisplaySize, timeout: 3.0, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true, padding: 2.0)
        self.sendingOptionsTooltipController = tooltipController
        tooltipController.dismissed = { [weak self, weak tooltipController] _ in
            if let strongSelf = self, let tooltipController = tooltipController, strongSelf.sendingOptionsTooltipController === tooltipController {
                strongSelf.sendingOptionsTooltipController = nil
            }
        }
        self.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: { [weak self] in
            if let strongSelf = self {
                return (strongSelf.chatDisplayNode, rect)
            }
            return nil
        }))
    }
    
    func displayEmojiTooltip() {
        guard let rect = self.chatDisplayNode.frameForEmojiButton(), self.effectiveNavigationController?.topViewController === self else {
            return
        }
        self.emojiTooltipController?.dismiss()
        let tooltipController = TooltipController(content: .text(self.presentationData.strings.Conversation_EmojiTooltip), baseFontSize: self.presentationData.listsFontSize.baseDisplaySize, timeout: 3.0, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true, padding: 2.0)
        self.emojiTooltipController = tooltipController
        tooltipController.dismissed = { [weak self, weak tooltipController] _ in
            if let strongSelf = self, let tooltipController = tooltipController, strongSelf.emojiTooltipController === tooltipController {
                strongSelf.emojiTooltipController = nil
            }
        }
        self.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: { [weak self] in
            if let strongSelf = self {
                return (strongSelf.chatDisplayNode, rect.offsetBy(dx: 0.0, dy: -3.0))
            }
            return nil
        }))
    }
    
    func displayGroupEmojiTooltip() {
        guard let rect = self.chatDisplayNode.frameForEmojiButton(), self.effectiveNavigationController?.topViewController === self else {
            return
        }
        guard let peerId = self.chatLocation.peerId, let emojiPack = (self.peerView?.cachedData as? CachedChannelData)?.emojiPack, let thumbnailFileId = emojiPack.thumbnailFileId else {
            return
        }
        
        let _ = (ApplicationSpecificNotice.groupEmojiPackSuggestion(accountManager: self.context.sharedContext.accountManager, peerId: peerId)
        |> deliverOnMainQueue).start(next: { [weak self] counter in
            guard let self, counter == 0 else {
                return
            }
            
            let _ = (self.context.engine.stickers.resolveInlineStickers(fileIds: [thumbnailFileId])
            |> deliverOnMainQueue).start(next: { [weak self] files in
                guard let self, let emojiFile = files.values.first else {
                    return
                }
                
                let textFont = Font.regular(self.presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0)
                let boldTextFont = Font.bold(self.presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0)
                let textColor = UIColor.white
                let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: textColor), linkAttribute: { _ in
                    return nil
                })
                
                let text = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(self.presentationData.strings.Chat_GroupEmojiTooltip(emojiPack.title).string, attributes: markdownAttributes))
                
                let range = (text.string as NSString).range(of: "#")
                if range.location != NSNotFound {
                    text.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: emojiFile.fileId.id, file: emojiFile), range: range)
                }
                
                let tooltipScreen = TooltipScreen(
                    context: self.context,
                    account: self.context.account,
                    sharedContext: self.context.sharedContext,
                    text: .attributedString(text: text),
                    location: .point(rect.offsetBy(dx: 0.0, dy: -3.0), .bottom),
                    displayDuration: .default,
                    cornerRadius: 10.0,
                    shouldDismissOnTouch: { _, _ in
                        return .ignore
                    }
                )
                self.present(tooltipScreen, in: .current)
                self.emojiPackTooltipController = tooltipScreen
                
                let _ = ApplicationSpecificNotice.incrementGroupEmojiPackSuggestion(accountManager: self.context.sharedContext.accountManager, peerId: peerId).startStandalone()
            })
        })
    }
    
    private var didDisplayBirthdayTooltip = false
    func displayBirthdayTooltip() {
        guard !self.didDisplayBirthdayTooltip else {
            return
        }
        if let birthday = (self.peerView?.cachedData as? CachedUserData)?.birthday {
            PeerInfoScreenImpl.preloadBirthdayAnimations(context: self.context, birthday: birthday)
        }
        guard let rect = self.chatDisplayNode.frameForGiftButton(), self.effectiveNavigationController?.topViewController === self, let peer = self.presentationInterfaceState.renderedPeer?.peer.flatMap({ EnginePeer($0) }) else {
            return
        }
        
        self.didDisplayBirthdayTooltip = true
        
        let _ = (ApplicationSpecificNotice.dismissedBirthdayPremiumGiftTip(accountManager: self.context.sharedContext.accountManager, peerId: peer.id)
        |> take(1)
        |> deliverOnMainQueue).startStandalone(next: { [weak self] timestamp in
            if let self {
                let currentTime = Int32(Date().timeIntervalSince1970)
                if let timestamp, currentTime < timestamp + 60 * 60 * 24 {
                    return
                }
                
                let peerName = peer.compactDisplayTitle
                let text = self.presentationData.strings.Chat_BirthdayTooltip(peerName, peerName).string
                
                let tooltipScreen = TooltipScreen(
                    context: self.context,
                    account: self.context.account,
                    sharedContext: self.context.sharedContext,
                    text: .markdown(text: text),
                    location: .point(rect.offsetBy(dx: 0.0, dy: -3.0), .bottom),
                    displayDuration: .custom(6.0),
                    cornerRadius: 10.0,
                    shouldDismissOnTouch: { _, _ in
                        return .dismiss(consume: false)
                    }
                )
                self.birthdayTooltipController = tooltipScreen
                Queue.mainQueue().after(0.35) {
                    self.present(tooltipScreen, in: .current)
                }
                
                let _ = ApplicationSpecificNotice.incrementDismissedBirthdayPremiumGiftTip(accountManager: self.context.sharedContext.accountManager, peerId: peer.id, timestamp: Int32(Date().timeIntervalSince1970)).startStandalone()
            }
        })
    }
    
    func displayChecksTooltip() {
        self.checksTooltipController?.dismiss()
        
        var latestNode: (Int32, ASDisplayNode)?
        self.chatDisplayNode.historyNode.forEachVisibleItemNode { itemNode in
            if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item, let statusNode = itemNode.getStatusNode() {
                if !item.content.effectivelyIncoming(self.context.account.peerId) {
                    if let (latestTimestamp, _) = latestNode {
                        if item.message.timestamp > latestTimestamp {
                            latestNode = (item.message.timestamp, statusNode)
                        }
                    } else {
                        latestNode = (item.message.timestamp, statusNode)
                    }
                }
            }
        }
        
        if let (_, latestStatusNode) = latestNode {
            let bounds = latestStatusNode.view.convert(latestStatusNode.view.bounds, to: self.chatDisplayNode.view)
            let location = CGPoint(x: bounds.maxX - 7.0, y: bounds.minY - 11.0)
            
            let contentNode = ChatStatusChecksTooltipContentNode(presentationData: self.presentationData)
            let tooltipController = TooltipController(content: .custom(contentNode), baseFontSize: self.presentationData.listsFontSize.baseDisplaySize, timeout: 3.5, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true)
            self.checksTooltipController = tooltipController
            tooltipController.dismissed = { [weak self, weak tooltipController] _ in
                if let strongSelf = self, let tooltipController = tooltipController, strongSelf.checksTooltipController === tooltipController {
                    strongSelf.checksTooltipController = nil
                }
            }
            self.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: { [weak self] in
                if let strongSelf = self {
                    return (strongSelf.chatDisplayNode, CGRect(origin: location, size: CGSize()))
                }
                return nil
            }))
        }
    }
    
    func dismissAllTooltips() {
        self.emojiTooltipController?.dismiss()
        self.sendingOptionsTooltipController?.dismiss()
        self.searchResultsTooltipController?.dismiss()
        self.messageTooltipController?.dismiss()
        self.videoUnmuteTooltipController?.dismiss()
        self.silentPostTooltipController?.dismiss()
        self.mediaRecordingModeTooltipController?.dismiss()
        self.mediaRestrictedTooltipController?.dismiss()
        self.checksTooltipController?.dismiss()
        self.copyProtectionTooltipController?.dismiss()
        
        self.window?.forEachController({ controller in
            if let controller = controller as? UndoOverlayController {
                controller.dismissWithCommitAction()
            }
        })
        self.forEachController({ controller in
            if let controller = controller as? UndoOverlayController {
                controller.dismissWithCommitAction()
            }
            if let controller = controller as? TooltipScreen, !controller.alwaysVisible {
                controller.dismiss()
            }
            return true
        })
    }
    
    func commitPurposefulAction() {
        if let purposefulAction = self.purposefulAction {
            self.purposefulAction = nil
            purposefulAction()
        }
    }
    
    public override var keyShortcuts: [KeyShortcut] {
        return self.keyShortcutsInternal
    }
    
    public override func joinGroupCall(peerId: PeerId, invite: String?, activeCall: EngineGroupCallDescription) {
        let proceed = {
            super.joinGroupCall(peerId: peerId, invite: invite, activeCall: activeCall)
        }
        
        let _ = self.presentVoiceMessageDiscardAlert(action: {
            proceed()
        })
    }
    
    public func getTransitionInfo(messageId: MessageId, media: Media) -> ((UIView) -> Void, ASDisplayNode, () -> (UIView?, UIView?))? {
        var selectedNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?
        self.chatDisplayNode.historyNode.forEachItemNode { itemNode in
            if let itemNode = itemNode as? ChatMessageItemView {
                if let result = itemNode.transitionNode(id: messageId, media: media, adjustRect: false) {
                    selectedNode = result
                }
            }
        }
        if let (node, _, get) = selectedNode {
            return ({ [weak self] view in
                guard let strongSelf = self else {
                    return
                }
                strongSelf.chatDisplayNode.historyNode.view.superview?.insertSubview(view, aboveSubview: strongSelf.chatDisplayNode.historyNode.view)
            }, node, get)
        } else {
            return nil
        }
    }
    
    public func activateInput(type: ChatControllerActivateInput) {
        if self.didAppear {
            switch type {
            case .text:
                self.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in
                    return state.updatedInputMode({ _ in
                        switch type {
                        case .text:
                            return .text
                        case .entityInput:
                            return .media(mode: .other, expanded: nil, focused: false)
                        }
                    })
                })
            case .entityInput:
                self.chatDisplayNode.openStickers(beginWithEmoji: true)
            }
        } else {
            self.scheduledActivateInput = type
        }
    }
    
    func clearInputText() {
        self.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in
            if !state.interfaceState.effectiveInputState.inputText.string.isEmpty {
                return state.updatedInterfaceState { interfaceState in
                    let effectiveInputState = ChatTextInputState(inputText: NSAttributedString(string: ""))
                    return interfaceState.withUpdatedEffectiveInputState(effectiveInputState)
                }
            } else {
                return state
            }
        })
    }
    
    func updateReminderActivity() {
        if self.isReminderActivityEnabled && false {
            if #available(iOS 9.0, *) {
                if self.reminderActivity == nil, case let .peer(peerId) = self.chatLocation, let peer = self.presentationInterfaceState.renderedPeer?.chatMainPeer {
                    let reminderActivity = NSUserActivity(activityType: "RemindAboutChatIntent")
                    self.reminderActivity = reminderActivity
                    if peer is TelegramGroup {
                        reminderActivity.title = self.presentationData.strings.Activity_RemindAboutGroup(EnginePeer(peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)).string
                    } else if let channel = peer as? TelegramChannel {
                        if case .broadcast = channel.info {
                            reminderActivity.title = self.presentationData.strings.Activity_RemindAboutChannel(EnginePeer(peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)).string
                        } else {
                            reminderActivity.title = self.presentationData.strings.Activity_RemindAboutGroup(EnginePeer(peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)).string
                        }
                    } else {
                        reminderActivity.title = self.presentationData.strings.Activity_RemindAboutUser(EnginePeer(peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)).string
                    }
                    reminderActivity.userInfo = ["peerId": peerId.toInt64(), "peerTitle": EnginePeer(peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)]
                    reminderActivity.isEligibleForHandoff = true
                    reminderActivity.becomeCurrent()
                }
            }
        } else if let reminderActivity = self.reminderActivity {
            self.reminderActivity = nil
            reminderActivity.invalidate()
        }
    }
    
    func updateSlowmodeStatus() {
        if let slowmodeState = self.presentationInterfaceState.slowmodeState, case let .timestamp(slowmodeActiveUntilTimestamp) = slowmodeState.variant {
            let timestamp = Int32(Date().timeIntervalSince1970)
            let remainingTime = max(0, slowmodeActiveUntilTimestamp - timestamp)
            if remainingTime == 0 {
                self.updateSlowmodeStatusTimerValue = nil
                self.updateSlowmodeStatusDisposable.set(nil)
                self.updateChatPresentationInterfaceState(interactive: false, {
                    $0.updatedSlowmodeState(nil)
                })
            } else {
                if self.updateSlowmodeStatusTimerValue != slowmodeActiveUntilTimestamp {
                    self.updateSlowmodeStatusTimerValue = slowmodeActiveUntilTimestamp
                    self.updateSlowmodeStatusDisposable.set((Signal<Never, NoError>.complete()
                    |> suspendAwareDelay(Double(remainingTime), granularity: 1.0, queue: .mainQueue())
                    |> deliverOnMainQueue).startStrict(completed: { [weak self] in
                        guard let strongSelf = self else {
                            return
                        }
                        strongSelf.updateSlowmodeStatusTimerValue = nil
                        strongSelf.updateSlowmodeStatus()
                    }))
                }
            }
        } else if let _ = self.updateSlowmodeStatusTimerValue {
            self.updateSlowmodeStatusTimerValue = nil
            self.updateSlowmodeStatusDisposable.set(nil)
        }
    }
    
    func openScheduledMessages() {
        guard let navigationController = self.effectiveNavigationController, navigationController.topViewController == self else {
            return
        }
        
        var mappedChatLocation = self.chatLocation
        if case let .replyThread(message) = self.chatLocation, message.peerId == self.context.account.peerId {
            mappedChatLocation = .peer(id: self.context.account.peerId)
        }
        
        let controller = ChatControllerImpl(context: self.context, chatLocation: mappedChatLocation, subject: .scheduledMessages)
        controller.navigationPresentation = .modal
        navigationController.pushViewController(controller)
    }
    
    func openPinnedMessages(at messageId: MessageId?) {
        let _ = self.presentVoiceMessageDiscardAlert(action: { [weak self] in
            guard let self, let navigationController = self.effectiveNavigationController, navigationController.topViewController == self else {
                return
            }
            let controller = ChatControllerImpl(context: self.context, chatLocation: self.chatLocation, subject: .pinnedMessages(id: messageId))
            controller.navigationPresentation = .modal
            controller.updatedClosedPinnedMessageId = { [weak self] pinnedMessageId in
                guard let strongSelf = self else {
                    return
                }
                strongSelf.performUpdatedClosedPinnedMessageId(pinnedMessageId: pinnedMessageId)
            }
            controller.requestedUnpinAllMessages = { [weak self] count, pinnedMessageId in
                guard let strongSelf = self else {
                    return
                }
                strongSelf.performRequestedUnpinAllMessages(count: count, pinnedMessageId: pinnedMessageId)
            }
            navigationController.pushViewController(controller)
        })
    }
    
    func performUpdatedClosedPinnedMessageId(pinnedMessageId: MessageId) {
        let previousClosedPinnedMessageId = self.presentationInterfaceState.interfaceState.messageActionsState.closedPinnedMessageId
        
        self.updateChatPresentationInterfaceState(animated: true, interactive: true, {
            return $0.updatedInterfaceState({ $0.withUpdatedMessageActionsState({ value in
                var value = value
                value.closedPinnedMessageId = pinnedMessageId
                return value
            }) })
        })
        
        self.present(
            UndoOverlayController(
                presentationData: self.presentationData,
                content: .messagesUnpinned(
                    title: self.presentationData.strings.Chat_PinnedMessagesHiddenTitle,
                    text: self.presentationData.strings.Chat_PinnedMessagesHiddenText,
                    undo: true,
                    isHidden: true
                ),
                elevatedLayout: false,
                action: { [weak self] action in
                    guard let strongSelf = self else {
                        return true
                    }
                    
                    switch action {
                    case .commit:
                        break
                    case .undo:
                        strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
                            return $0.updatedInterfaceState({ $0.withUpdatedMessageActionsState({ value in
                                var value = value
                                value.closedPinnedMessageId = previousClosedPinnedMessageId
                                return value
                            }) })
                        })
                    default:
                        break
                    }
                    return true
                }
            ),
            in: .current
        )
    }
    
    func performRequestedUnpinAllMessages(count: Int, pinnedMessageId: MessageId) {
        guard let peerId = self.chatLocation.peerId else {
            return
        }
        self.chatDisplayNode.historyNode.pendingUnpinnedAllMessages = true
        self.updateChatPresentationInterfaceState(animated: true, interactive: true, {
            return $0.updatedPendingUnpinnedAllMessages(true)
        })
        
        self.present(
            UndoOverlayController(
                presentationData: self.presentationData,
                content: .messagesUnpinned(
                    title: self.presentationData.strings.Chat_MessagesUnpinned(Int32(count)),
                    text: "",
                    undo: true,
                    isHidden: false
                ),
                elevatedLayout: false,
                action: { [weak self] action in
                    guard let strongSelf = self else {
                        return true
                    }
                    
                    switch action {
                    case .commit:
                        let _ = (strongSelf.context.engine.messages.requestUnpinAllMessages(peerId: peerId, threadId: strongSelf.chatLocation.threadId)
                        |> deliverOnMainQueue).startStandalone(error: { _ in
                        }, completed: {
                            guard let strongSelf = self else {
                                return
                            }
                            
                            strongSelf.chatDisplayNode.historyNode.pendingUnpinnedAllMessages = false
                            strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
                                return $0.updatedPendingUnpinnedAllMessages(false)
                            })
                        })
                    case .undo:
                        strongSelf.chatDisplayNode.historyNode.pendingUnpinnedAllMessages = false
                        strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
                            return $0.updatedPendingUnpinnedAllMessages(false)
                        })
                    default:
                        break
                    }
                    return true
                }
            ),
            in: .current
        )
    }
    
    func presentScheduleTimePicker(style: ChatScheduleTimeControllerStyle = .default, selectedTime: Int32? = nil, dismissByTapOutside: Bool = true, completion: @escaping (Int32) -> Void) {
        guard let peerId = self.chatLocation.peerId else {
            return
        }
        let _ = (self.context.account.viewTracker.peerView(peerId)
        |> take(1)
        |> deliverOnMainQueue).startStandalone(next: { [weak self] peerView in
            guard let strongSelf = self, let peer = peerViewMainPeer(peerView) else {
                return
            }
            var sendWhenOnlineAvailable = false
            if let presence = peerView.peerPresences[peer.id] as? TelegramUserPresence, case .present = presence.status {
                sendWhenOnlineAvailable = true
            }
            if peer.id.namespace == Namespaces.Peer.CloudUser && peer.id.id._internalGetInt64Value() == 777000 {
                sendWhenOnlineAvailable = false
            }
            
            let mode: ChatScheduleTimeControllerMode
            if peerId == strongSelf.context.account.peerId {
                mode = .reminders
            } else {
                mode = .scheduledMessages(sendWhenOnlineAvailable: sendWhenOnlineAvailable)
            }
            let controller = ChatScheduleTimeController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peerId, mode: mode, style: style, currentTime: selectedTime, minimalTime: strongSelf.presentationInterfaceState.slowmodeState?.timeout, dismissByTapOutside: dismissByTapOutside, completion: { time in
                completion(time)
            })
            strongSelf.chatDisplayNode.dismissInput()
            strongSelf.present(controller, in: .window(.root))
        })
    }
    
    func presentTimerPicker(style: ChatTimerScreenStyle = .default, selectedTime: Int32? = nil, dismissByTapOutside: Bool = true, completion: @escaping (Int32) -> Void) {
        guard case .peer = self.chatLocation else {
            return
        }
        let controller = ChatTimerScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, style: style, currentTime: selectedTime, dismissByTapOutside: dismissByTapOutside, completion: { time in
            completion(time)
        })
        self.chatDisplayNode.dismissInput()
        self.present(controller, in: .window(.root))
    }
    
    func presentVoiceMessageDiscardAlert(action: @escaping () -> Void = {}, alertAction: (() -> Void)? = nil, delay: Bool = false, performAction: Bool = true) -> Bool {
        if let _ = self.presentationInterfaceState.inputTextPanelState.mediaRecordingState {
            alertAction?()
            Queue.mainQueue().after(delay ? 0.2 : 0.0) {
                self.present(textAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, title: nil, text: self.presentationData.strings.Conversation_DiscardVoiceMessageDescription, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Conversation_DiscardVoiceMessageAction, action: { [weak self] in
                    self?.stopMediaRecorder()
                    Queue.mainQueue().after(0.1) {
                        action()
                    }
                })]), in: .window(.root))
            }
            
            return true
        } else if performAction {
            action()
        }
        return false
    }
    
    func presentRecordedVoiceMessageDiscardAlert(action: @escaping () -> Void = {}, alertAction: (() -> Void)? = nil, delay: Bool = false, performAction: Bool = true) -> Bool {
        if let _ = self.presentationInterfaceState.interfaceState.mediaDraftState {
            alertAction?()
            Queue.mainQueue().after(delay ? 0.2 : 0.0) {
                self.present(textAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, title: nil, text: self.presentationData.strings.Conversation_DiscardRecordedVoiceMessageDescription, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Conversation_DiscardRecordedVoiceMessageAction, action: { [weak self] in
                    self?.stopMediaRecorder()
                    Queue.mainQueue().after(0.1) {
                        action()
                    }
                })]), in: .window(.root))
            }
            
            return true
        } else if performAction {
            action()
        }
        return false
    }
    
    func presentAutoremoveSetup() {
        guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
            return
        }
        
        let controller = ChatTimerScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, style: .default, mode: .autoremove, currentTime: self.presentationInterfaceState.autoremoveTimeout, dismissByTapOutside: true, completion: { [weak self] value in
            guard let strongSelf = self else {
                return
            }
            
            let _ = (strongSelf.context.engine.peers.setChatMessageAutoremoveTimeoutInteractively(peerId: peer.id, timeout: value == 0 ? nil : value)
            |> deliverOnMainQueue).startStandalone(completed: {
                guard let strongSelf = self else {
                    return
                }
                
                var isOn: Bool = true
                var text: String?
                if value != 0 {
                    text = strongSelf.presentationData.strings.Conversation_AutoremoveChanged("\(timeIntervalString(strings: strongSelf.presentationData.strings, value: value))").string
                } else {
                    isOn = false
                    text = strongSelf.presentationData.strings.Conversation_AutoremoveOff
                }
                if let text = text {
                    strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .autoDelete(isOn: isOn, title: nil, text: text, customUndoText: nil), elevatedLayout: false, action: { _ in return false }), in: .current)
                }
            })
        })
        self.chatDisplayNode.dismissInput()
        self.present(controller, in: .window(.root))
    }
    
    func presentChatRequestAdminInfo() {
        if let requestChatTitle = self.presentationInterfaceState.contactStatus?.peerStatusSettings?.requestChatTitle, let requestDate = self.presentationInterfaceState.contactStatus?.peerStatusSettings?.requestChatDate {
            let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
            
            let controller = ActionSheetController(presentationData: presentationData)
            var items: [ActionSheetItem] = []
            
            let text = presentationData.strings.Conversation_InviteRequestInfo(requestChatTitle, stringForDate(timestamp: requestDate, strings: presentationData.strings))
            
            items.append(ActionSheetTextItem(title: text.string))
            items.append(ActionSheetButtonItem(title: self.presentationData.strings.Conversation_InviteRequestInfoConfirm, color: .accent, action: { [weak self, weak controller] in
                controller?.dismissAnimated()
                self?.interfaceInteraction?.dismissReportPeer()
            }))
            controller.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
                ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak controller] in
                    controller?.dismissAnimated()
                })
            ])])
            self.chatDisplayNode.dismissInput()
            self.present(controller, in: .window(.root))
        }
    }
    
    var crossfading = false
    func presentCrossfadeSnapshot() {
        guard !self.crossfading, let snapshotView = self.view.snapshotView(afterScreenUpdates: false) else {
            return
        }
        self.crossfading = true
        self.view.addSubview(snapshotView)

        snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: ChatThemeScreen.themeCrossfadeDuration, delay: ChatThemeScreen.themeCrossfadeDelay, timingFunction: CAMediaTimingFunctionName.linear.rawValue, removeOnCompletion: false, completion: { [weak self, weak snapshotView] _ in
            self?.crossfading = false
            snapshotView?.removeFromSuperview()
        })
    }
    
    public func hintPlayNextOutgoingGift() {
        self.controllerInteraction?.playNextOutgoingGift = true
    }
    
    var effectiveNavigationController: NavigationController? {
        if let navigationController = self.navigationController as? NavigationController {
            return navigationController
        } else if case let .inline(navigationController) = self.presentationInterfaceState.mode {
            return navigationController
        } else if case let .overlay(navigationController) = self.presentationInterfaceState.mode {
            return navigationController
        } else {
            if let navigationController = self.customNavigationController {
                return navigationController
            }
            return nil
        }
    }
    
    public func activateSearch(domain: ChatSearchDomain = .everything, query: String = "") {
        self.focusOnSearchAfterAppearance = (domain, query)
        self.interfaceInteraction?.beginMessageSearch(domain, query)
    }
    
    override public func updatePossibleControllerDropContent(content: NavigationControllerDropContent?) {
        //self.chatDisplayNode.updateEmbeddedTitlePeekContent(content: content)
    }
    
    override public func acceptPossibleControllerDropContent(content: NavigationControllerDropContent) -> Bool {
        //return self.chatDisplayNode.acceptEmbeddedTitlePeekContent(content: content)
        return false
    }
    
    public var isSendButtonVisible: Bool {
        if self.presentationInterfaceState.interfaceState.editMessage != nil || self.presentationInterfaceState.interfaceState.forwardMessageIds != nil || self.presentationInterfaceState.interfaceState.composeInputState.inputText.string.count > 0 {
            return true
        } else {
            return false
        }
    }
    
    public func playShakeAnimation() {
        if self.shakeFeedback == nil {
            self.shakeFeedback = HapticFeedback()
        }
        self.shakeFeedback?.error()
        
        self.chatDisplayNode.historyNodeContainer.layer.addShakeAnimation(amplitude: -6.0, decay: true)
    }
    
    public func updatePushedTransition(_ fraction: CGFloat, transition: ContainedViewLayoutTransition) {
        if !transition.isAnimated {
            self.chatDisplayNode.historyNodeContainer.layer.removeAllAnimations()
        }
        let scale: CGFloat = 1.0 - 0.06 * fraction
        transition.updateTransformScale(node: self.chatDisplayNode.historyNodeContainer, scale: scale)
    }
    
    func restrictedSendingContentsText() -> String {
        guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
            return self.presentationData.strings.Chat_SendNotAllowedText
        }
        
        var itemList: [String] = []
        
        let order: [TelegramChatBannedRightsFlags] = [
            .banSendText,
            .banSendPhotos,
            .banSendVideos,
            .banSendVoice,
            .banSendInstantVideos,
            .banSendFiles,
            .banSendMusic,
            .banSendStickers
        ]
        
        for right in order {
            if let channel = peer as? TelegramChannel {
                if channel.hasBannedPermission(right) != nil {
                    continue
                }
            } else if let group = peer as? TelegramGroup {
                if group.hasBannedPermission(right) {
                    continue
                }
            }
            
            var title: String?
            switch right {
            case .banSendText:
                title = self.presentationData.strings.Chat_SendAllowedContentTypeText
            case .banSendPhotos:
                title = self.presentationData.strings.Chat_SendAllowedContentTypePhoto
            case .banSendVideos:
                title = self.presentationData.strings.Chat_SendAllowedContentTypeVideo
            case .banSendVoice:
                title = self.presentationData.strings.Chat_SendAllowedContentTypeVoiceMessage
            case .banSendInstantVideos:
                title = self.presentationData.strings.Chat_SendAllowedContentTypeVideoMessage
            case .banSendFiles:
                title = self.presentationData.strings.Chat_SendAllowedContentTypeFile
            case .banSendMusic:
                title = self.presentationData.strings.Chat_SendAllowedContentTypeMusic
            case .banSendStickers:
                title = self.presentationData.strings.Chat_SendAllowedContentTypeSticker
            default:
                break
            }
            if let title {
                itemList.append(title)
            }
        }
        
        if itemList.isEmpty {
            return self.presentationData.strings.Chat_SendNotAllowedText
        }
        
        var itemListString = ""
        if #available(iOS 13.0, *) {
            let listFormatter = ListFormatter()
            listFormatter.locale = localeWithStrings(presentationData.strings)
            if let value = listFormatter.string(from: itemList) {
                itemListString = value
            }
        }
        
        if itemListString.isEmpty {
            for i in 0 ..< itemList.count {
                if i != 0 {
                    itemListString.append(", ")
                }
                itemListString.append(itemList[i])
            }
        }
        
        return self.presentationData.strings.Chat_SendAllowedContentText(itemListString).string
    }
    
    func updateNextChannelToReadVisibility() {
        self.chatDisplayNode.historyNode.offerNextChannelToRead = self.offerNextChannelToRead && self.presentationInterfaceState.interfaceState.selectionState == nil
    }
    
    func displayGiveawayStatusInfo(messageId: EngineMessage.Id, giveawayInfo: PremiumGiveawayInfo) {
        presentGiveawayInfoController(context: self.context, updatedPresentationData: self.updatedPresentationData, messageId: messageId, giveawayInfo: giveawayInfo, present: { [weak self] c in
            guard let self else {
                return
            }
            self.present(c, in: .window(.root))
        }, openLink: { [weak self] slug in
            guard let self else {
                return
            }
            self.openResolved(result: .premiumGiftCode(slug: slug), sourceMessageId: messageId)
        })
    }
    
    public func transferScrollingVelocity(_ velocity: CGFloat) {
        self.chatDisplayNode.historyNode.transferVelocity(velocity)
    }
    
    public func performScrollToTop() -> Bool {
        let offset = self.chatDisplayNode.historyNode.visibleContentOffset()
        switch offset {
        case let .known(value) where value <= CGFloat.ulpOfOne:
            return false
        default:
            self.chatDisplayNode.historyNode.scrollToEndOfHistory()
            return true
        }
    }
}