import Foundation
import UIKit
import AsyncDisplayKit
import Display
import SwiftSignalKit
import TelegramCore
import TelegramPresentationData
import TextFormat
import Markdown
import RadialStatusNode
import AppBundle
import AnimatedStickerNode
import TelegramAnimatedStickerNode
import SlotMachineAnimationNode
import AnimationUI
import StickerResources
import AvatarNode
import AccountContext
import AnimatedAvatarSetNode
import ComponentFlow
import EmojiStatusComponent
import TextNodeWithEntities
import BundleIconComponent
import AnimatedTextComponent
import ComponentDisplayAdapters
import PhotoResources

final class UndoOverlayControllerNode: ViewControllerTracingNode {
    private let presentationData: PresentationData
    private let elevatedLayout: Bool
    private let placementPosition: UndoOverlayController.Position
    private var statusNode: RadialStatusNode?
    private var didStartStatusNode: Bool = false
    private let timerTextNode: ImmediateTextNode
    private let avatarNode: AvatarNode?
    private let iconNode: ASImageNode?
    private var multiAvatarsNode: AnimatedAvatarSetNode?
    private var multiAvatarsSize: CGSize?
    private var iconImageSize: CGSize?
    private let iconCheckNode: RadialStatusNode?
    private var animationNode: AnimationNode?
    private var animatedStickerNode: AnimatedStickerNode?
    private var slotMachineNode: SlotMachineAnimationNode?
    private var stillStickerNode: TransformImageNode?
    private var stickerImageSize: CGSize?
    private var stickerSourceSize: CGSize?
    private var stickerOffset: CGPoint?
    private var emojiStatus: ComponentView<Empty>?
    private let titleNode: ImmediateTextNode
    private let textNode: ImmediateTextNodeWithEntities
    private var textComponent: ComponentView<Empty>?
    private var animatedTextItems: [AnimatedTextComponent.Item]?
    private let buttonNode: HighlightTrackingButtonNode
    private let undoButtonTextNode: ImmediateTextNode
    private let undoButtonNode: HighlightTrackingButtonNode
    private let panelNode: ASDisplayNode
    private let panelWrapperNode: ASDisplayNode
    private let action: (UndoOverlayAction) -> Bool
    private let dismiss: () -> Void
    
    private var content: UndoOverlayContent
    private let blurred: Bool
    
    private let additionalView: UndoOverlayControllerAdditionalView?
    
    private let effectView: UIView
    
    private let animationBackgroundColor: UIColor
        
    private var isTimeoutDisabled: Bool = false
    private var originalRemainingSeconds: Double
    private var remainingSeconds: Double
    private let undoTextColor: UIColor
    private var timer: SwiftSignalKit.Timer?
    
    private var validLayout: ContainerViewLayout?
    
    private var fetchResourceDisposable: Disposable?
    
    init(presentationData: PresentationData, content: UndoOverlayContent, elevatedLayout: Bool, placementPosition: UndoOverlayController.Position, blurred: Bool, additionalView: (() -> UndoOverlayControllerAdditionalView?)?, action: @escaping (UndoOverlayAction) -> Bool, dismiss: @escaping () -> Void) {
        self.presentationData = presentationData
        self.elevatedLayout = elevatedLayout
        self.placementPosition = placementPosition
        self.blurred = blurred
        self.content = content
        
        self.additionalView = additionalView?()
        
        self.action = action
        self.dismiss = dismiss
        
        self.timerTextNode = ImmediateTextNode()
        self.timerTextNode.displaysAsynchronously = false
        
        self.titleNode = ImmediateTextNode()
        self.titleNode.layer.anchorPoint = CGPoint()
        self.titleNode.displaysAsynchronously = false
        self.titleNode.maximumNumberOfLines = 0
        
        self.textNode = ImmediateTextNodeWithEntities()
        self.textNode.displaysAsynchronously = false
        self.textNode.maximumNumberOfLines = 0
        
        self.buttonNode = HighlightTrackingButtonNode()
        
        var displayUndo = true
        var undoText = presentationData.strings.Undo_Undo
        var undoTextColor = presentationData.theme.list.itemAccentColor.withMultiplied(hue: 0.933, saturation: 0.61, brightness: 1.0)
        
        if presentationData.theme.overallDarkAppearance {
            self.animationBackgroundColor = presentationData.theme.rootController.tabBar.backgroundColor
        } else {
            self.animationBackgroundColor = UIColor(rgb: 0x474747)
        }
        
        var isUserInteractionEnabled = false
        switch content {
            case let .removedChat(title, text):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = nil
                self.animatedStickerNode = nil
                if let text {
                    self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
                    
                    let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                    let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                    let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
                    self.textNode.attributedText = attributedText
                } else {
                    self.textNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
                }
                displayUndo = true
                self.originalRemainingSeconds = 5
                self.statusNode = RadialStatusNode(backgroundNodeColor: .clear)
            case let .archivedChat(_, title, text, undo):
                self.avatarNode = nil
                if undo {
                    self.iconNode = ASImageNode()
                    self.iconNode?.displayWithoutProcessing = true
                    self.iconNode?.displaysAsynchronously = false
                    self.iconNode?.image = UIImage(bundleImageName: "Chat List/ArchivedUndoIcon")
                    self.iconCheckNode = RadialStatusNode(backgroundNodeColor: .clear)
                    self.iconCheckNode?.frame = CGRect(x: 0.0, y: 0.0, width: 24.0, height: 24.0)
                    self.animationNode = nil
                } else {
                    self.iconNode = nil
                    self.iconCheckNode = nil
                    self.animationNode = AnimationNode(animation: "anim_infotip", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
                }
                self.animatedStickerNode = nil
                self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
            
                let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
                let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
                    return ("URL", contents)
                }), textAlignment: .natural)
                self.textNode.attributedText = attributedText
                displayUndo = undo
                self.originalRemainingSeconds = 5
            case let .hidArchive(title, text, undo):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = AnimationNode(animation: "anim_archiveswipe", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
                self.animatedStickerNode = nil
            
                self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
                self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
                displayUndo = undo
                self.originalRemainingSeconds = 3
            case let .revealedArchive(title, text, undo):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = AnimationNode(animation: "anim_infotip", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
                self.animatedStickerNode = nil
            
                self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
                self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
                displayUndo = undo
                self.originalRemainingSeconds = 3
            case let .autoDelete(isOn, title, text, customUndoText):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = AnimationNode(animation: isOn ? "anim_autoremove_on" : "anim_autoremove_off", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
                self.animatedStickerNode = nil
                
                if let title = title {
                    self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
                }

                let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
                let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
                self.textNode.attributedText = attributedText
                if let customUndoText {
                    undoText = customUndoText
                    displayUndo = true
                } else {
                    displayUndo = false
                }
                self.originalRemainingSeconds = 4.5
            
                if text.contains("](") {
                    isUserInteractionEnabled = true
                }
            case let .succeed(text, timeout, customUndoText):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = AnimationNode(animation: "anim_success", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
                self.animatedStickerNode = nil
                
                let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
                self.textNode.attributedText = attributedText
                self.textNode.maximumNumberOfLines = 5
                if let customUndoText {
                    undoText = customUndoText
                    displayUndo = true
                } else {
                    displayUndo = false
                }
                self.originalRemainingSeconds = timeout ?? 3
            case let .info(title, text, timeout, customUndoText):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = AnimationNode(animation: "anim_infotip", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
                self.animatedStickerNode = nil
            
                if let title = title {
                    self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
                }
            
                let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
                let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
                    return ("URL", contents)
                }), textAlignment: .natural)
                self.textNode.attributedText = attributedText
                self.textNode.maximumNumberOfLines = 10
                if let customUndoText {
                    undoText = customUndoText
                    displayUndo = true
                } else {
                    displayUndo = false
                }
                if let timeout {
                    self.originalRemainingSeconds = timeout
                } else {
                    self.originalRemainingSeconds = Double(max(5, min(8, text.count / 14)))
                }
            
                if text.contains("](") {
                    isUserInteractionEnabled = true
                }
            case let .actionSucceeded(title, text, cancel, destructive):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = AnimationNode(animation: "anim_success", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
                self.animatedStickerNode = nil
                
                if destructive {
                    undoTextColor = UIColor(rgb: 0xff7b74)
                }
            
                let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
                let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
                if let title {
                    self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
                }
                self.textNode.attributedText = attributedText
                if let cancel {
                    displayUndo = true
                    undoText = cancel
                } else {
                    displayUndo = false
                }
                self.originalRemainingSeconds = 5
            
                if text.contains("](") {
                    isUserInteractionEnabled = true
                }
            case let .linkCopied(title, text):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = AnimationNode(animation: "anim_linkcopied", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
                self.animatedStickerNode = nil
            
                if let title {
                    self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
                }
            
                let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
                let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
                self.textNode.attributedText = attributedText
                self.textNode.maximumNumberOfLines = 2
                displayUndo = false
                self.originalRemainingSeconds = 3
            
                if text.contains("](") {
                    isUserInteractionEnabled = true
                }
            case let .banned(text):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = AnimationNode(animation: "anim_banned", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
                self.animatedStickerNode = nil
            
                let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
                self.textNode.attributedText = attributedText
                self.textNode.maximumNumberOfLines = 2
                displayUndo = false
                self.originalRemainingSeconds = 5
            case let .importedMessage(text):
                self.avatarNode = nil
                self.iconNode = ASImageNode()
                self.iconNode?.displayWithoutProcessing = true
                self.iconNode?.displaysAsynchronously = false
                self.iconNode?.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/ImportedMessageTooltipIcon"), color: .white)
                self.iconCheckNode = nil
                self.animationNode = nil
                self.animatedStickerNode = nil
            
                self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
                displayUndo = false
                self.originalRemainingSeconds = 5
            case let .chatAddedToFolder(chatTitle, folderTitle):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = AnimationNode(animation: "anim_success", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
                self.animatedStickerNode = nil
                
                let formattedString = presentationData.strings.ChatList_AddedToFolderTooltip(chatTitle, folderTitle)
                
                let string = NSMutableAttributedString(attributedString: NSAttributedString(string: formattedString.string, font: Font.regular(14.0), textColor: .white))
                for range in formattedString.ranges {
                    string.addAttribute(.font, value: Font.regular(14.0), range: range.range)
                }
                
                self.textNode.attributedText = string
                displayUndo = false
                self.originalRemainingSeconds = 5
            case let .chatRemovedFromFolder(chatTitle, folderTitle):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = AnimationNode(animation: "anim_success", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
                self.animatedStickerNode = nil

                let formattedString = presentationData.strings.ChatList_RemovedFromFolderTooltip(chatTitle, folderTitle)
                
                let string = NSMutableAttributedString(attributedString: NSAttributedString(string: formattedString.string, font: Font.regular(14.0), textColor: .white))
                for range in formattedString.ranges {
                    string.addAttribute(.font, value: Font.regular(14.0), range: range.range)
                }
                
                self.textNode.attributedText = string
                displayUndo = false
                self.originalRemainingSeconds = 5
            case let .paymentSent(currencyValue, itemTitle):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = AnimationNode(animation: "anim_payment", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
                self.animatedStickerNode = nil
                
                let formattedString = presentationData.strings.Checkout_SuccessfulTooltip(currencyValue, itemTitle)

                let string = NSMutableAttributedString(attributedString: NSAttributedString(string: formattedString.string, font: Font.regular(14.0), textColor: .white))
                for range in formattedString.ranges {
                    string.addAttribute(.font, value: Font.semibold(14.0), range: range.range)
                }

                self.textNode.attributedText = string
                displayUndo = false
                self.originalRemainingSeconds = 5
            case let .starsSent(_, title, textItems):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = nil
            
                self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
            
                let imageBoundingSize = CGSize(width: 34.0, height: 34.0)
            
                let emojiStatus = ComponentView<Empty>()
                self.emojiStatus = emojiStatus
                let _ = emojiStatus.update(
                    transition: .immediate,
                    component: AnyComponent(BundleIconComponent(
                        name: "Premium/Stars/StarLarge",
                        tintColor: nil
                    )),
                    environment: {},
                    containerSize: imageBoundingSize
                )
        
                self.stickerImageSize = imageBoundingSize
                
                self.animatedTextItems = textItems
            
                displayUndo = true
                self.originalRemainingSeconds = 4.9
                isUserInteractionEnabled = true
            
                self.statusNode = RadialStatusNode(backgroundNodeColor: .clear)
            case let .messagesUnpinned(title, text, undo, isHidden):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = AnimationNode(animation: isHidden ? "anim_message_hidepin" : "anim_message_unpin", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
                self.animatedStickerNode = nil
                
                let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
                let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
                self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
                if !text.isEmpty {
                    self.textNode.attributedText = attributedText
                }
                
                displayUndo = undo
                self.originalRemainingSeconds = 5
            case let .emoji(name, text):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = nil
                self.animatedStickerNode = DefaultAnimatedStickerNodeImpl()
                self.animatedStickerNode?.visibility = true
                self.animatedStickerNode?.setup(source: AnimatedStickerNodeLocalFileSource(name: name), width: 100, height: 100, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
                
                let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
                self.textNode.attributedText = attributedText
                self.textNode.maximumNumberOfLines = 10
                displayUndo = false
                self.originalRemainingSeconds = 5
            case let .swipeToReply(title, text):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = AnimationNode(animation: "anim_swipereply", colors: [:], scale: 1.0)
                self.animatedStickerNode = nil
                
                self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
                self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
                self.textNode.maximumNumberOfLines = 2
                displayUndo = false
                self.originalRemainingSeconds = 5
            case let .stickersModified(title, text, undo, info, topItem, context):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = nil
                
                let stillStickerNode = TransformImageNode()
                
                self.stillStickerNode = stillStickerNode
                
                enum StickerPackThumbnailItem {
                    case still(TelegramMediaImageRepresentation)
                    case animated(EngineMediaResource, PixelDimensions, Bool)
                }
                
                var thumbnailItem: StickerPackThumbnailItem?
                var resourceReference: MediaResourceReference?
                
                if let thumbnail = info.thumbnail {
                    if thumbnail.typeHint != .generic {
                        thumbnailItem = .animated(EngineMediaResource(thumbnail.resource), thumbnail.dimensions, thumbnail.typeHint == .video)
                    } else {
                        thumbnailItem = .still(thumbnail)
                    }
                    resourceReference = MediaResourceReference.stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource)
                } else if let item = topItem {
                    if item.file.isAnimatedSticker || item.file.isVideoSticker {
                        thumbnailItem = .animated(EngineMediaResource(item.file.resource), item.file.dimensions ?? PixelDimensions(width: 512, height: 512), item.file.isVideoSticker)
                        resourceReference = MediaResourceReference.media(media: .standalone(media: item.file), resource: item.file.resource)
                    } else if let dimensions = item.file.dimensions, let resource = chatMessageStickerResource(file: item.file, small: true) as? TelegramMediaResource {
                        thumbnailItem = .still(TelegramMediaImageRepresentation(dimensions: dimensions, resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false))
                        resourceReference = MediaResourceReference.media(media: .standalone(media: item.file), resource: resource)
                    }
                }
                
                var updatedImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
                var updatedFetchSignal: Signal<Never, EngineMediaResource.Fetch.Error>?
                
                let imageBoundingSize = CGSize(width: 34.0, height: 34.0)
                
                if let thumbnailItem = thumbnailItem {
                    switch thumbnailItem {
                    case let .still(representation):
                        let stillImageSize = representation.dimensions.cgSize.aspectFitted(imageBoundingSize)
                        self.stickerImageSize = stillImageSize
                        
                        updatedImageSignal = chatMessageStickerPackThumbnail(postbox: context.account.postbox, resource: representation.resource)
                    case let .animated(resource, dimensions, _):
                        self.stickerImageSize = dimensions.cgSize.aspectFitted(imageBoundingSize)
                        
                        updatedImageSignal = chatMessageStickerPackThumbnail(postbox: context.account.postbox, resource: resource._asResource(), animated: true)
                    }
                    if let resourceReference = resourceReference {
                        updatedFetchSignal = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .other, reference: resourceReference)
                        |> mapError { _ -> EngineMediaResource.Fetch.Error in
                            return .generic
                        }
                        |> ignoreValues
                    }
                } else {
                    updatedImageSignal = .single({ _ in return nil })
                    updatedFetchSignal = .complete()
                }
                
                self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
                let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
                let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
                self.textNode.attributedText = attributedText
                self.textNode.maximumNumberOfLines = 2
                displayUndo = undo
                self.originalRemainingSeconds = 2
            
                if let updatedFetchSignal = updatedFetchSignal {
                    self.fetchResourceDisposable = updatedFetchSignal.start()
                }
            
                if let updatedImageSignal = updatedImageSignal {
                    stillStickerNode.setSignal(updatedImageSignal)
                }
            
                if let thumbnailItem = thumbnailItem {
                    switch thumbnailItem {
                    case .still:
                        break
                    case let .animated(resource, _, isVideo):
                        let animatedStickerNode = DefaultAnimatedStickerNodeImpl()
                        self.animatedStickerNode = animatedStickerNode
                        animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: context.account, resource: resource._asResource(), isVideo: isVideo), width: 80, height: 80, mode: .direct(cachePathPrefix: nil))
                    }
                }
            case let .dice(dice, context, text, action):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = nil
                                
                let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
                let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
                self.textNode.attributedText = attributedText
                if let action = action {
                    displayUndo = true
                    undoText = action
                } else {
                    displayUndo = false
                }
                self.originalRemainingSeconds = 5
                
                self.stickerImageSize = CGSize(width: 42.0, height: 42.0)
                
                switch dice.emoji {
                    case "🎲":
                        self.stickerOffset = CGPoint(x: 0.0, y: -7.0)
                    default:
                        break
                }
                
                if dice.emoji == "🎰" {
                    let slotMachineNode = SlotMachineAnimationNode(account: context.account, size: CGSize(width: 42.0, height: 42.0))
                    self.slotMachineNode = slotMachineNode
                    
                    slotMachineNode.setState(.rolling)
                    if let value = dice.value {
                        slotMachineNode.setState(.value(value, true))
                    }
                } else {
                    let animatedStickerNode = DefaultAnimatedStickerNodeImpl()
                    self.animatedStickerNode = animatedStickerNode
                    
                    let _ = (context.engine.stickers.loadedStickerPack(reference: .dice(dice.emoji), forceActualized: false)
                    |> deliverOnMainQueue).start(next: { stickerPack in
                        if let value = dice.value {
                            switch stickerPack {
                                case let .result(_, items, _):
                                    let item = items[Int(value)]

                                    animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: context.account, resource: item.file.resource), width: 120, height: 120, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
                                default:
                                    break
                            }
                        }
                    })
                }
            case let .setProximityAlert(title, text, cancelled):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = AnimationNode(animation: cancelled ? "anim_proximity_cancelled" : "anim_proximity_set", colors: [:], scale: 0.45)
                self.animatedStickerNode = nil
                
                let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
                let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
                self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
                if !text.isEmpty {
                    self.textNode.attributedText = attributedText
                }
                
                displayUndo = false
                self.originalRemainingSeconds = 3
            case let .invitedToVoiceChat(context, peer, title, text, action, duration):
                self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 15.0))
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = nil
                self.animatedStickerNode = nil
                
                self.titleNode.attributedText = NSAttributedString(string: title ?? "", font: Font.semibold(14.0), textColor: .white)
            
                let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
                let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
                
                self.textNode.attributedText = attributedText
                self.avatarNode?.setPeer(context: context, theme: presentationData.theme, peer: peer, overrideImage: nil, emptyColor: presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: true)
                
                if let action = action {
                    displayUndo = true
                    undoText = action
                } else {
                    displayUndo = false
                }
                self.originalRemainingSeconds = duration
            case let .audioRate(rate, text):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
            
                let animationName: String
                if rate == .infinity {
                    animationName = "anim_voicefast"
                } else if rate == -.infinity {
                    animationName = "anim_voiceslow"
                } else if rate == 1.5 {
                    animationName = "anim_voice1_5x"
                } else if rate == 2.0 {
                    animationName = "anim_voice2x"
                } else {
                    animationName = "anim_voice1x"
                }
            
                self.animationNode = AnimationNode(animation: animationName, colors: [:], scale: 0.066)
                self.animatedStickerNode = nil
                
                let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
                self.textNode.attributedText = attributedText
                self.textNode.maximumNumberOfLines = 2
                
                displayUndo = false
                self.originalRemainingSeconds = 3
            case let .forward(savedMessages, text):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = AnimationNode(animation: savedMessages ? "anim_savedmessages" : "anim_forward", colors: [:], scale: 0.066)
                self.animatedStickerNode = nil
                
                let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                let bold: MarkdownAttributeSet
                var link = body
                if savedMessages {
                    bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: presentationData.theme.list.itemAccentColor.withMultiplied(hue: 0.933, saturation: 0.61, brightness: 1.0), additionalAttributes: ["URL": ""])
                    link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                } else {
                    bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                }
                let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
                self.textNode.attributedText = attributedText
                self.textNode.maximumNumberOfLines = 2
            
                if savedMessages {
                    isUserInteractionEnabled = true
                }
                
                displayUndo = false
                self.originalRemainingSeconds = 3
            case let .gigagroupConversion(text):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = AnimationNode(animation: "anim_gigagroup", colors: [:], scale: 0.066)
                self.animatedStickerNode = nil
                
                let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
                self.textNode.attributedText = attributedText
                self.textNode.maximumNumberOfLines = 2
                
                displayUndo = false
                self.originalRemainingSeconds = 3
            case let .linkRevoked(text):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = AnimationNode(animation: "anim_linkrevoked", colors: [:], scale: 0.066)
                self.animatedStickerNode = nil
                
                let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
                self.textNode.attributedText = attributedText
                self.textNode.maximumNumberOfLines = 2
                
                displayUndo = false
                self.originalRemainingSeconds = 3
            case let .voiceChatRecording(text):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = AnimationNode(animation: "anim_vcrecord", colors: [:], scale: 0.066)
                self.animatedStickerNode = nil
                
                let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
                self.textNode.attributedText = attributedText
                self.textNode.maximumNumberOfLines = 2
                
                displayUndo = false
                self.originalRemainingSeconds = 3
            case let .voiceChatFlag(text):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = AnimationNode(animation: "anim_vcflag", colors: [:], scale: 0.066)
                self.animatedStickerNode = nil
                
                let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
                self.textNode.attributedText = attributedText
                self.textNode.maximumNumberOfLines = 2
                
                displayUndo = false
                self.originalRemainingSeconds = 3
            case let .voiceChatCanSpeak(text):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = AnimationNode(animation: "anim_vcspeak", colors: [:], scale: 0.066)
                self.animatedStickerNode = nil
                
                let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
                self.textNode.attributedText = attributedText
                self.textNode.maximumNumberOfLines = 2
                
                displayUndo = false
                self.originalRemainingSeconds = 3
            case let .sticker(context, file, loop, title, text, customUndoText, _):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = nil
                
                let stillStickerNode = TransformImageNode()
                
                self.stillStickerNode = stillStickerNode
                
                enum StickerThumbnailItem {
                    case still(TelegramMediaImageRepresentation)
                    case animated(EngineMediaResource)
                }
                
                var thumbnailItem: StickerThumbnailItem?
                var resourceReference: MediaResourceReference?
                
                if file.isAnimatedSticker {
                    thumbnailItem = .animated(EngineMediaResource(file.resource))
                    resourceReference = MediaResourceReference.media(media: .standalone(media: file), resource: file.resource)
                } else if let dimensions = file.dimensions, let resource = chatMessageStickerResource(file: file, small: true) as? TelegramMediaResource {
                    thumbnailItem = .still(TelegramMediaImageRepresentation(dimensions: dimensions, resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false))
                    resourceReference = MediaResourceReference.media(media: .standalone(media: file), resource: resource)
                }
                
                var updatedImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
                var updatedFetchSignal: Signal<Never, EngineMediaResource.Fetch.Error>?
                
                let imageBoundingSize = CGSize(width: 34.0, height: 34.0)
                
                if let thumbnailItem = thumbnailItem {
                    switch thumbnailItem {
                    case let .still(representation):
                        let stillImageSize = representation.dimensions.cgSize.aspectFitted(imageBoundingSize)
                        self.stickerImageSize = stillImageSize
                        
                        updatedImageSignal = chatMessageStickerPackThumbnail(postbox: context.account.postbox, resource: representation.resource)
                    case let .animated(resource):
                        self.stickerImageSize = imageBoundingSize
                        
                        updatedImageSignal = chatMessageStickerPackThumbnail(postbox: context.account.postbox, resource: resource._asResource(), animated: true)
                    }
                    if let resourceReference = resourceReference {
                        updatedFetchSignal = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .other, reference: resourceReference)
                        |> mapError { _ -> EngineMediaResource.Fetch.Error in
                            return .generic
                        }
                        |> ignoreValues
                    }
                } else {
                    updatedImageSignal = .single({ _ in return nil })
                    updatedFetchSignal = .complete()
                }
            
                if let title = title {
                    self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
                } else {
                    self.titleNode.attributedText = nil
                }
                
                let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
                let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
                    return ("URL", contents)
                }), textAlignment: .natural)
                self.textNode.attributedText = attributedText
                self.textNode.maximumNumberOfLines = 5
            
                if text.contains("](") {
                    isUserInteractionEnabled = true
                }
            
                if let customUndoText = customUndoText {
                    undoText = customUndoText
                    displayUndo = true
                } else {
                    displayUndo = false
                }
                self.originalRemainingSeconds = isUserInteractionEnabled ? 5 : 3
            
                if let updatedFetchSignal = updatedFetchSignal {
                    self.fetchResourceDisposable = updatedFetchSignal.start()
                }
            
                if let updatedImageSignal = updatedImageSignal {
                    stillStickerNode.setSignal(updatedImageSignal)
                }
            
                if let thumbnailItem = thumbnailItem {
                    switch thumbnailItem {
                    case .still:
                        break
                    case let .animated(resource):
                        let animatedStickerNode = DefaultAnimatedStickerNodeImpl()
                        self.animatedStickerNode = animatedStickerNode
                        animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: context.account, resource: resource._asResource(), isVideo: file.isVideoSticker), width: 80, height: 80, playbackMode: loop ? .loop : .once, mode: .cached)
                    }
                }
            case let .customEmoji(context, file, loop, title, text, customUndoText, _):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = nil
            
                let imageBoundingSize = CGSize(width: 34.0, height: 34.0)
            
                let emojiStatus = ComponentView<Empty>()
                self.emojiStatus = emojiStatus
                let _ = emojiStatus.update(
                    transition: .immediate,
                    component: AnyComponent(EmojiStatusComponent(
                        context: context,
                        animationCache: context.animationCache,
                        animationRenderer: context.animationRenderer,
                        content: .animation(
                            content: .file(file: file),
                            size: imageBoundingSize,
                            placeholderColor: UIColor(white: 1.0, alpha: 0.1),
                            themeColor: .white,
                            loopMode: loop ? .forever : .count(1)
                        ),
                        isVisibleForAnimations: true,
                        useSharedAnimation: false,
                        action: nil
                    )),
                    environment: {},
                    containerSize: imageBoundingSize
                )
            
                self.stickerImageSize = imageBoundingSize
            
                if let title = title {
                    self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
                } else {
                    self.titleNode.attributedText = nil
                }
                
                let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
                let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
                    return ("URL", contents)
                }), textAlignment: .natural)
                self.textNode.attributedText = attributedText
                self.textNode.maximumNumberOfLines = 5
            
                if text.contains("](") {
                    isUserInteractionEnabled = true
                }
            
                if let customUndoText = customUndoText {
                    undoText = customUndoText
                    displayUndo = true
                } else {
                    displayUndo = false
                }
                self.originalRemainingSeconds = isUserInteractionEnabled ? 5 : 3
            case let .copy(text):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = AnimationNode(animation: "anim_copy", colors: [:], scale: 0.066)
                self.animatedStickerNode = nil
                
                let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
                self.textNode.attributedText = attributedText
                self.textNode.maximumNumberOfLines = 2
                
                displayUndo = false
                self.originalRemainingSeconds = 3
            case let .mediaSaved(text):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = nil
                
                let animatedStickerNode = DefaultAnimatedStickerNodeImpl()
                self.animatedStickerNode = animatedStickerNode
                
                animatedStickerNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "anim_savemedia"), width: 80, height: 80, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
                animatedStickerNode.visibility = true
                
                let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
                let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
                self.textNode.attributedText = attributedText
                self.textNode.maximumNumberOfLines = 2
                
                displayUndo = false
                self.originalRemainingSeconds = 3
            
                if text.contains("](") {
                    isUserInteractionEnabled = true
                }
            case let .inviteRequestSent(title, text):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = AnimationNode(animation: "anim_inviterequest", colors: [:], scale: 0.066)
                self.animatedStickerNode = nil
                self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
                self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
                self.textNode.maximumNumberOfLines = 2
                displayUndo = false
                self.originalRemainingSeconds = 5
            case let .notificationSoundAdded(title, text, action):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = AnimationNode(animation: "anim_notificationsound", colors: [:], scale: 0.066)
                self.animatedStickerNode = nil
                self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
                
                let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
                let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
                    return ("URL", contents)
                }), textAlignment: .natural)
                self.textNode.attributedText = attributedText
            
                self.textNode.maximumNumberOfLines = 5
                displayUndo = false
                self.originalRemainingSeconds = 5
            
                if let action = action {
                    self.textNode.highlightAttributeAction = { attributes in
                        if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] {
                            return NSAttributedString.Key(rawValue: "URL")
                        } else {
                            return nil
                        }
                    }
                    self.textNode.tapAttributeAction = { attributes, _ in
                        if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] {
                            action()
                        }
                    }
                }
            case let .universal(animation, scale, colors, title, text, customUndoText, timeout):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = AnimationNode(animation: animation, colors: colors, scale: scale)
                self.animatedStickerNode = nil
            
                if let title = title, text.isEmpty {
                    self.titleNode.attributedText = nil
                    let body = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                    let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                    let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
                    let attributedText = parseMarkdownIntoAttributedString(title, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
                        return ("URL", contents)
                    }), textAlignment: .natural)
                    self.textNode.attributedText = attributedText
                } else {
                    if let title = title {
                        self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
                    } else {
                        self.titleNode.attributedText = nil
                    }
                    
                    let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                    let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                    let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
                    let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
                        return ("URL", contents)
                    }), textAlignment: .natural)
                    self.textNode.attributedText = attributedText
                }
            
                if text.contains("](") {
                    isUserInteractionEnabled = true
                }
                self.originalRemainingSeconds = timeout ?? (isUserInteractionEnabled ? 5 : 3)
            
                self.textNode.maximumNumberOfLines = 5
                
                if let customUndoText = customUndoText {
                    undoText = customUndoText
                    displayUndo = true
                } else {
                    displayUndo = false
                }
            case let .universalImage(image, size, title, text, customUndoText, timeout):
                self.iconNode = ASImageNode()
                self.iconNode?.displayWithoutProcessing = true
                self.iconNode?.displaysAsynchronously = false
                self.iconNode?.image = image
                self.iconImageSize = size
            
                self.avatarNode = nil
                self.iconCheckNode = nil
                self.animationNode = nil
                self.animatedStickerNode = nil
            
                if let title = title, text.isEmpty {
                    self.titleNode.attributedText = nil
                    let body = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                    let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                    let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
                    let attributedText = parseMarkdownIntoAttributedString(title, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
                        return ("URL", contents)
                    }), textAlignment: .natural)
                    self.textNode.attributedText = attributedText
                } else {
                    if let title = title {
                        self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
                    } else {
                        self.titleNode.attributedText = nil
                    }
                    
                    let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                    let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                    let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
                    let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
                        return ("URL", contents)
                    }), textAlignment: .natural)
                    self.textNode.attributedText = attributedText
                }
            
                if text.contains("](") {
                    isUserInteractionEnabled = true
                }
                self.originalRemainingSeconds = timeout ?? (isUserInteractionEnabled ? 5 : 3)
            
                self.textNode.maximumNumberOfLines = 5
                
                if let customUndoText = customUndoText {
                    undoText = customUndoText
                    displayUndo = true
                } else {
                    displayUndo = false
                }
            case let .premiumPaywall(title, text, customUndoText, timeout, linkAction):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = AnimationNode(animation: "PremiumStar", colors: [:], scale: 0.066)
                self.animatedStickerNode = nil
            
                if let title = title, text.isEmpty {
                    self.titleNode.attributedText = nil
                    let body = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                    let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                    let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
                    let attributedText = parseMarkdownIntoAttributedString(title, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
                        return ("URL", contents)
                    }), textAlignment: .natural)
                    self.textNode.attributedText = attributedText
                } else {
                    if let title = title {
                        self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
                    } else {
                        self.titleNode.attributedText = nil
                    }
                    
                    let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                    let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                    let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
                    let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
                        return ("URL", contents)
                    }), textAlignment: .natural)
                    self.textNode.attributedText = attributedText
                }
            
                if let linkAction {
                    self.textNode.highlightAttributeAction = { attributes in
                        if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] {
                            return NSAttributedString.Key(rawValue: "URL")
                        } else {
                            return nil
                        }
                    }
                    self.textNode.tapAttributeAction = { attributes, _ in
                        if let value = attributes[NSAttributedString.Key(rawValue: "URL")] as? String {
                            linkAction(value)
                        }
                    }
                }
            
                if text.contains("](") {
                    isUserInteractionEnabled = true
                }
                self.originalRemainingSeconds = timeout ?? (isUserInteractionEnabled ? 5 : 3)
            
                self.textNode.maximumNumberOfLines = 5
                
                if let customUndoText = customUndoText {
                    undoText = customUndoText
                    displayUndo = true
                } else {
                    displayUndo = false
                }
            case let .image(image, title, text, round, customUndoText):
                self.avatarNode = nil
                self.iconNode = ASImageNode()
                self.iconNode?.clipsToBounds = true
                self.iconNode?.contentMode = .scaleAspectFill
                self.iconNode?.image = image
                self.iconNode?.cornerRadius = round ? 16.0 : 4.0
                self.iconImageSize = image.size.aspectFitted(CGSize(width: 128.0, height: 32.0))
                self.iconCheckNode = nil
                self.animationNode = nil
                self.animatedStickerNode = nil
                if let title = title {
                    self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
                } else {
                    self.titleNode.attributedText = nil
                }
            
                let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
                let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
                    return ("URL", contents)
                }), textAlignment: .natural)
                self.textNode.attributedText = attributedText

                if let customUndoText = customUndoText {
                    undoText = customUndoText
                    displayUndo = true
                } else {
                    displayUndo = false
                }
                self.originalRemainingSeconds = displayUndo ? 5 : 3
            
                if text.contains("](") {
                    isUserInteractionEnabled = true
                }
            case let .peers(context, peers, title, text, customUndoText):
                self.avatarNode = nil
                let multiAvatarsNode = AnimatedAvatarSetNode()
                self.multiAvatarsNode = multiAvatarsNode
                let avatarsContext = AnimatedAvatarSetContext()
                self.multiAvatarsSize = multiAvatarsNode.update(context: context, content: avatarsContext.update(peers: peers, animated: false), itemSize: CGSize(width: 28.0, height: 28.0), animated: false, synchronousLoad: false)
                
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = nil
                self.animatedStickerNode = nil
                if let title = title {
                    self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
                } else {
                    self.titleNode.attributedText = nil
                }
                
                let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
                let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
                    return ("URL", contents)
                }), textAlignment: .natural)
                self.textNode.attributedText = attributedText
            
                if text.contains("](") {
                    isUserInteractionEnabled = true
                }
                self.originalRemainingSeconds = isUserInteractionEnabled ? 5 : 3
            
                self.textNode.maximumNumberOfLines = 5
                
                if let customUndoText = customUndoText {
                    undoText = customUndoText
                    displayUndo = true
                } else {
                    displayUndo = false
                }
            case let .messageTagged(context, isSingleMessage, customEmoji, isBuiltinReaction, customUndoText):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = AnimationNode(animation: "anim_savedmessages", colors: [:], scale: 0.066)
                self.animatedStickerNode = nil
                
                let rawText = isSingleMessage ? presentationData.strings.Chat_ToastMessageTagged_Text(".") : presentationData.strings.Chat_ToastMessagesTagged_Text(".")
                let attributedText = NSMutableAttributedString(string: rawText.string, font: Font.regular(14.0), textColor: .white)
                for range in rawText.ranges {
                    attributedText.addAttributes([ChatTextInputAttributes.customEmoji: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: customEmoji.fileId.id, file: customEmoji, custom: nil)], range: range.range)
                }
                self.textNode.customItemLayout = { size, _ in
                    if isBuiltinReaction {
                        return CGSize(width: size.width * 2.0, height: size.height * 2.0)
                    }
                    return size
                }
            
                self.textNode.arguments = TextNodeWithEntities.Arguments(
                    context: context,
                    cache: context.animationCache,
                    renderer: context.animationRenderer,
                    placeholderColor: UIColor(white: 1.0, alpha: 0.1),
                    attemptSynchronous: false
                )
                self.textNode.visibility = true
            
                self.textNode.attributedText = attributedText
                self.textNode.maximumNumberOfLines = 2
                
                displayUndo = false
                self.originalRemainingSeconds = 3
            
                if let customUndoText = customUndoText {
                    undoText = customUndoText
                    displayUndo = true
                    isUserInteractionEnabled = true
                } else {
                    displayUndo = false
                }
            case let .media(context, file, title, text, customUndoText, _):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = nil
                
                let stillStickerNode = TransformImageNode()
                
                self.stillStickerNode = stillStickerNode
                
                var updatedImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
                var updatedFetchSignal: Signal<Never, EngineMediaResource.Fetch.Error>?
            
                updatedImageSignal = mediaGridMessageVideo(postbox: context.account.postbox, userLocation: .other, videoReference: file, onlyFullSize: false, useLargeThumbnail: false, autoFetchFullSizeThumbnail: false)
                updatedFetchSignal = nil
                self.stickerImageSize = CGSize(width: 30.0, height: 30.0)
                self.stickerSourceSize = file.media.dimensions?.cgSize
            
                if let title = title {
                    self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
                } else {
                    self.titleNode.attributedText = nil
                }
                
                let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
                let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
                    return ("URL", contents)
                }), textAlignment: .natural)
                self.textNode.attributedText = attributedText
                self.textNode.maximumNumberOfLines = 5
            
                if text.contains("](") {
                    isUserInteractionEnabled = true
                }
            
                if let customUndoText = customUndoText {
                    undoText = customUndoText
                    displayUndo = true
                } else {
                    displayUndo = false
                }
                self.originalRemainingSeconds = isUserInteractionEnabled ? 5 : 3
            
                if let updatedFetchSignal = updatedFetchSignal {
                    self.fetchResourceDisposable = updatedFetchSignal.start()
                }
            
                if let updatedImageSignal = updatedImageSignal {
                    stillStickerNode.setSignal(updatedImageSignal)
                }
            case let .progress(progress, title, text, customUndoText):
                self.avatarNode = nil
                self.iconNode = ASImageNode()
                self.iconNode?.displayWithoutProcessing = true
                self.iconNode?.displaysAsynchronously = false
                self.iconNode?.image = generateTintedImage(image: UIImage(bundleImageName: "Premium/File"), color: .white)
                self.iconImageSize = CGSize(width: 14.0, height: 14.0)
                self.iconCheckNode = nil
                self.animationNode = nil
                self.animatedStickerNode = nil
            
                self.statusNode = RadialStatusNode(backgroundNodeColor: .clear)
            
                self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
                
                let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
                let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
                let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
                let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
                    return ("URL", contents)
                }), textAlignment: .natural)
                self.textNode.attributedText = attributedText
                
            
                if text.contains("](") {
                    isUserInteractionEnabled = true
                }
                self.originalRemainingSeconds = 5
            
                self.textNode.maximumNumberOfLines = 5
                
                if let customUndoText = customUndoText {
                    undoText = customUndoText
                    displayUndo = true
                } else {
                    displayUndo = false
                }
            
                self.statusNode?.transitionToState(.progress(color: .white, lineWidth: nil, value: max(progress, 0.027), cancelEnabled: false, animateRotation: true), completion: {})
        }
        
        self.remainingSeconds = self.originalRemainingSeconds
        self.undoTextColor = undoTextColor
        
        self.undoButtonTextNode = ImmediateTextNode()
        self.undoButtonTextNode.displaysAsynchronously = false
        self.undoButtonTextNode.attributedText = NSAttributedString(string: undoText, font: Font.regular(17.0), textColor: undoTextColor)
        
        self.undoButtonNode = HighlightTrackingButtonNode()
        
        self.panelNode = ASDisplayNode()
        if presentationData.theme.overallDarkAppearance && !self.blurred {
            self.panelNode.backgroundColor = presentationData.theme.rootController.tabBar.backgroundColor
        } else {
            self.panelNode.backgroundColor = .clear
        }
        self.panelNode.clipsToBounds = true
        self.panelNode.cornerRadius = 14.0

        self.panelWrapperNode = ASDisplayNode()
        
        self.effectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
        
        super.init()
        
        switch content {
        case .removedChat:
            self.panelWrapperNode.addSubnode(self.timerTextNode)
        case .starsSent:
            self.panelWrapperNode.addSubnode(self.timerTextNode)
            
            if self.textNode.tapAttributeAction != nil || displayUndo {
                self.isUserInteractionEnabled = true
            } else {
                self.isUserInteractionEnabled = false
            }
        case .archivedChat, .hidArchive, .revealedArchive, .autoDelete, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward, .gigagroupConversion, .linkRevoked, .voiceChatRecording, .voiceChatFlag, .voiceChatCanSpeak, .copy, .mediaSaved, .paymentSent, .image, .inviteRequestSent, .notificationSoundAdded, .universal,. universalImage, .premiumPaywall, .peers, .messageTagged:
            if self.textNode.tapAttributeAction != nil || displayUndo {
                self.isUserInteractionEnabled = true
            } else {
                self.isUserInteractionEnabled = false
            }
        case .sticker, .customEmoji, .media, .progress:
            self.isUserInteractionEnabled = displayUndo
        case .dice:
            self.panelWrapperNode.clipsToBounds = true
        case .info:
            if self.textNode.tapAttributeAction != nil || displayUndo {
                self.isUserInteractionEnabled = true
            } else {
                self.isUserInteractionEnabled = false
            }
        }
        if isUserInteractionEnabled {
            self.isUserInteractionEnabled = true
        }
        
        self.titleNode.isUserInteractionEnabled = false
        self.textNode.isUserInteractionEnabled = self.textNode.tapAttributeAction != nil
        self.iconNode?.isUserInteractionEnabled = false
        self.animationNode?.isUserInteractionEnabled = false
        self.iconCheckNode?.isUserInteractionEnabled = false
        self.avatarNode?.isUserInteractionEnabled = false
        self.multiAvatarsNode?.isUserInteractionEnabled = false
        self.slotMachineNode?.isUserInteractionEnabled = false
        self.animatedStickerNode?.isUserInteractionEnabled = false
        
        self.statusNode.flatMap(self.panelWrapperNode.addSubnode)
        self.iconNode.flatMap(self.panelWrapperNode.addSubnode)
        self.iconCheckNode.flatMap(self.panelWrapperNode.addSubnode)
        self.animationNode.flatMap(self.panelWrapperNode.addSubnode)
        self.stillStickerNode.flatMap(self.panelWrapperNode.addSubnode)
        if let emojiStatusView = self.emojiStatus?.view {
            self.panelWrapperNode.view.addSubview(emojiStatusView)
        }
        self.animatedStickerNode.flatMap(self.panelWrapperNode.addSubnode)
        self.slotMachineNode.flatMap(self.panelWrapperNode.addSubnode)
        self.avatarNode.flatMap(self.panelWrapperNode.addSubnode)
        self.multiAvatarsNode.flatMap(self.panelWrapperNode.addSubnode)
        
        self.panelWrapperNode.addSubnode(self.buttonNode)
        self.panelWrapperNode.addSubnode(self.titleNode)
        self.panelWrapperNode.addSubnode(self.textNode)
        if displayUndo {
            self.panelWrapperNode.addSubnode(self.undoButtonTextNode)
            self.panelWrapperNode.addSubnode(self.undoButtonNode)
        }
        self.addSubnode(self.panelNode)
        self.addSubnode(self.panelWrapperNode)
        
        self.undoButtonNode.highligthedChanged = { [weak self] highlighted in
            if let strongSelf = self {
                if highlighted {
                    strongSelf.undoButtonTextNode.layer.removeAnimation(forKey: "opacity")
                    strongSelf.undoButtonTextNode.alpha = 0.4
                } else {
                    strongSelf.undoButtonTextNode.alpha = 1.0
                    strongSelf.undoButtonTextNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
                }
            }
        }
        self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
        self.undoButtonNode.addTarget(self, action: #selector(self.undoButtonPressed), forControlEvents: .touchUpInside)
        
        self.animatedStickerNode?.started = { [weak self] in
            self?.stillStickerNode?.isHidden = true
        }
        
        if let additionalView = self.additionalView {
            additionalView.interaction = UndoOverlayControllerAdditionalViewInteraction(disableTimeout: { [weak self] in
                guard let self else {
                    return
                }
                self.isTimeoutDisabled = true
                self.timer?.invalidate()
                self.remainingSeconds = self.originalRemainingSeconds
                self.checkTimer()
            }, dismiss: { [weak self] in
                guard let self else {
                    return
                }
                self.dismiss()
            })
            self.view.addSubview(additionalView)
        }
    }
    
    deinit {
        self.fetchResourceDisposable?.dispose()
    }
    
    override func didLoad() {
        super.didLoad()
        
        if #available(iOS 13.0, *) {
            self.panelNode.layer.cornerCurve = .continuous
        }
        
        self.panelNode.view.addSubview(self.effectView)
        
        if self.additionalView != nil {
            self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
        }
    }
    
    @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
        if case .ended = recognizer.state {
            self.dismiss()
        }
    }
    
    @objc private func buttonPressed() {
        if self.action(.info) {
            self.dismiss()
        }
    }
    
    @objc private func undoButtonPressed() {
        switch self.content {
        case let .sticker(_, _, _, _, _, _, customAction):
            if let customAction = customAction {
                customAction()
            } else {
                let _ = self.action(.undo)
            }
        case let .customEmoji(_, _, _, _, _, _, customAction):
            if let customAction = customAction {
                customAction()
            } else {
                let _ = self.action(.undo)
            }
        case let .media(_, _, _, _, _, customAction):
            if let customAction = customAction {
                customAction()
            } else {
                let _ = self.action(.undo)
            }
        default:
            let _ = self.action(.undo)
        }
        self.dismiss()
    }
    
    private func checkTimer() {
        let previousRemainingSeconds = Int(self.remainingSeconds)
        if self.timer != nil {
            self.remainingSeconds -= 0.5
        }
        if self.remainingSeconds <= 0.0 {
            let _ = self.action(.commit)
            self.dismiss()
        } else {
            let remainingSecondsString = "\(Int(self.remainingSeconds))"
            if Int(self.remainingSeconds) != previousRemainingSeconds || self.timerTextNode.attributedText?.string != remainingSecondsString {
                if !self.timerTextNode.bounds.size.width.isZero, let snapshot = self.timerTextNode.view.snapshotContentTree() {
                    self.panelNode.view.insertSubview(snapshot, aboveSubview: self.timerTextNode.view)
                    snapshot.frame = self.timerTextNode.frame
                    self.timerTextNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.12)
                    self.timerTextNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -10.0), to: CGPoint(), duration: 0.12, removeOnCompletion: false, additive: true)
                    snapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false)
                    snapshot.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: 10.0), duration: 0.12, removeOnCompletion: false, additive: true, completion: { [weak snapshot] _ in
                        snapshot?.removeFromSuperview()
                    })
                }
                let timerColor: UIColor
                if case .starsSent = self.content {
                    timerColor = self.undoTextColor
                } else {
                    timerColor = .white
                }
                self.timerTextNode.attributedText = NSAttributedString(string: remainingSecondsString, font: Font.regular(16.0), textColor: timerColor)
                if let validLayout = self.validLayout {
                    self.containerLayoutUpdated(layout: validLayout, transition: .immediate)
                }
            }
            if !self.isTimeoutDisabled {
                let timer = SwiftSignalKit.Timer(timeout: 0.5, repeat: false, completion: { [weak self] in
                    self?.checkTimer()
                }, queue: .mainQueue())
                self.timer = timer
                timer.start()
            }
        }
    }
    
    func renewWithCurrentContent() {
        self.timer?.invalidate()
        self.timer = nil
        self.remainingSeconds = self.originalRemainingSeconds
        self.didStartStatusNode = false
        self.checkTimer()
    }
    
    func updateContent(_ content: UndoOverlayContent) {
        let previousContent = self.content
        self.content = content
        
        var undoTextColor = self.presentationData.theme.list.itemAccentColor.withMultiplied(hue: 0.933, saturation: 0.61, brightness: 1.0)
        
        var transition: ContainedViewLayoutTransition = .immediate
        
        switch content {
        case let .info(title, text, _, _), let .universal(_, _, _, title, text, _, _):
            let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
            let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
            let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
            let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
            if let title {
                self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
            }
            self.textNode.attributedText = attributedText
        case let .image(image, title, text, _, _):
            self.iconNode?.image = image
            if let title = title {
                self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
            } else {
                self.titleNode.attributedText = nil
            }
            self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
            self.renewWithCurrentContent()
        case let .actionSucceeded(title, text, _, destructive):
            if case .progress = previousContent {
                self.remainingSeconds = 5.0
                
                if let snapshotView = self.textNode.view.snapshotView(afterScreenUpdates: false) {
                    snapshotView.frame = self.textNode.frame
                    self.textNode.view.superview?.addSubview(snapshotView)
                    snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
                        snapshotView.removeFromSuperview()
                    })
                    self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
                }
                
                self.animationNode = AnimationNode(animation: "anim_success", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
                self.animationNode.flatMap(self.panelWrapperNode.addSubnode)
                
                if let iconNode = self.iconNode, let statusNode = self.statusNode {
                    iconNode.layer.animateScale(from: 1.0, to: 0.01, duration: 0.25, removeOnCompletion: false)
                    iconNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25)
                    statusNode.layer.animateScale(from: 1.0, to: 0.01, duration: 0.25, removeOnCompletion: false)
                    statusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25)
                }
                
                self.animationNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
                Queue.mainQueue().justDispatch {
                    self.animationNode?.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2)
                    self.animationNode?.play()
                }
            }
            
            if destructive {
                undoTextColor = UIColor(rgb: 0xff7b74)
            }
            let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
            let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
            let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
            let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
            if let title {
                self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
            }
            self.textNode.attributedText = attributedText
        case let .starsSent(_, title, textItems):
            self.animatedTextItems = textItems
        
            self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
            
            self.renewWithCurrentContent()
            transition = .animated(duration: 0.1, curve: .easeInOut)
        case let .progress(progress, title, text, _):
            let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
            let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
            let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
            let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
            self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
            self.textNode.attributedText = attributedText
            
            self.statusNode?.transitionToState(.progress(color: .white, lineWidth: nil, value: max(progress, 0.027), cancelEnabled: false, animateRotation: true), completion: {})
        default:
            break
        }
        
        if let validLayout = self.validLayout {
            self.containerLayoutUpdated(layout: validLayout, transition: transition)
        }
    }
    
    func containerLayoutUpdated(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
        self.validLayout = layout
        
        var preferredSize: CGSize?
        var verticalOffset: CGFloat = 0.0
        if let animationNode = self.animationNode, let iconSize = animationNode.preferredSize() {
            if case .messagesUnpinned = self.content {
                let factor: CGFloat = 0.5
                preferredSize = CGSize(width: floor(iconSize.width * factor), height: floor(iconSize.height * factor))
            } else if case .linkCopied = self.content {
                let factor: CGFloat = 0.08
                preferredSize = CGSize(width: floor(iconSize.width * factor), height: floor(iconSize.height * factor))
            } else if case .banned = self.content {
                let factor: CGFloat = 0.08
                preferredSize = CGSize(width: floor(iconSize.width * factor), height: floor(iconSize.height * factor))
            } else if case .autoDelete = self.content {
                let factor: CGFloat = 0.07
                verticalOffset = -3.0
                preferredSize = CGSize(width: floor(iconSize.width * factor), height: floor(iconSize.height * factor))
            } else if case .paymentSent = self.content {
                let factor: CGFloat = 0.08
                preferredSize = CGSize(width: floor(iconSize.width * factor), height: floor(iconSize.height * factor))
            } else {
                preferredSize = iconSize
            }
        }
        
        var leftInset: CGFloat = 50.0
        if let iconImageSize = self.iconImageSize {
            if case .progress = self.content {
            } else if case .actionSucceeded = self.content {
            } else {
                leftInset = 9.0 + iconImageSize.width + 9.0
            }
        } else if let iconSize = preferredSize {
            if iconSize.width > leftInset {
                leftInset = iconSize.width - 8.0
            }
        } else if let multiAvatarsSize = self.multiAvatarsSize {
            leftInset = 13.0 + multiAvatarsSize.width + 20.0
        }
        
        let rightInset: CGFloat = 16.0
        var contentHeight: CGFloat = 20.0
        
        let margin: CGFloat = 12.0
        let leftMargin = margin + layout.insets(options: []).left
        
        let buttonTextSize = self.undoButtonTextNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude))
        let buttonMinX: CGFloat
        if self.undoButtonNode.supernode != nil {
            buttonMinX = layout.size.width - layout.safeInsets.left - rightInset - buttonTextSize.width - leftMargin * 2.0
        } else {
            buttonMinX = layout.size.width - layout.safeInsets.left - rightInset
        }
        
        let titleSize = self.titleNode.updateLayout(CGSize(width: buttonMinX - 8.0 - leftInset - layout.safeInsets.left - leftMargin, height: .greatestFiniteMagnitude))
        
        let maxTextSize = CGSize(width: buttonMinX - 8.0 - leftInset - layout.safeInsets.left - leftMargin, height: .greatestFiniteMagnitude)
        
        let textSize: CGSize
        if let animatedTextItems = self.animatedTextItems {
            let textComponent: ComponentView<Empty>
            if let current = self.textComponent {
                textComponent = current
            } else {
                textComponent = ComponentView()
                self.textComponent = textComponent
            }
            textSize = textComponent.update(
                transition: ComponentTransition(transition),
                component: AnyComponent(AnimatedTextComponent(
                    font: Font.regular(14.0),
                    color: .white,
                    items: animatedTextItems
                )),
                environment: {},
                containerSize: maxTextSize
            )
            if let textComponentView = textComponent.view {
                if textComponentView.superview == nil {
                    textComponentView.layer.anchorPoint = CGPoint()
                    self.panelWrapperNode.view.addSubview(textComponentView)
                }
            }
        } else {
            if let textComponentView = self.textComponent?.view {
                self.textComponent = nil
                textComponentView.removeFromSuperview()
            }
            textSize = self.textNode.updateLayout(maxTextSize)
        }
        
        if !titleSize.width.isZero {
            contentHeight += titleSize.height + 1.0
        }
        contentHeight += textSize.height
        
        contentHeight = max(49.0, contentHeight)
        
        var insets = layout.insets(options: [.input])
        switch self.placementPosition {
        case .top:
            break
        case .bottom:
            if self.elevatedLayout {
                insets.bottom += 49.0
            }
        }
        
        var panelFrame = CGRect(origin: CGPoint(x: leftMargin + layout.safeInsets.left, y: layout.size.height - contentHeight - insets.bottom - margin), size: CGSize(width: layout.size.width - leftMargin * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: contentHeight))
        var panelWrapperFrame = CGRect(origin: CGPoint(x: leftMargin + layout.safeInsets.left, y: layout.size.height - contentHeight - insets.bottom - margin), size: CGSize(width: layout.size.width - leftMargin * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: contentHeight))
        
        if case .top = self.placementPosition {
            var topInset = insets.top
            if topInset.isZero {
                topInset = layout.statusBarHeight ?? 44.0
            }
            panelFrame.origin.y = topInset + margin
            panelWrapperFrame.origin.y = topInset + margin
        }
        
        transition.updateFrame(node: self.panelNode, frame: panelFrame)
        transition.updateFrame(node: self.panelWrapperNode, frame: panelWrapperFrame)
        self.effectView.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width - leftMargin * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: contentHeight)
        
        var buttonTextFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset - buttonTextSize.width - leftMargin * 2.0, y: floor((contentHeight - buttonTextSize.height) / 2.0)), size: buttonTextSize)
        var undoButtonFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset - buttonTextSize.width - 8.0 - leftMargin * 2.0, y: 0.0), size: CGSize(width: layout.safeInsets.right + rightInset + buttonTextSize.width + 8.0 + leftMargin, height: contentHeight))
        if case .starsSent = self.content {
            let buttonOffset: CGFloat = -34.0
            undoButtonFrame.origin.x += buttonOffset
            buttonTextFrame.origin.x += buttonOffset
        }
        transition.updateFrame(node: self.undoButtonTextNode, frame: buttonTextFrame)
        self.undoButtonNode.frame = undoButtonFrame
        
        self.buttonNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: self.undoButtonNode.supernode == nil ? panelFrame.width : undoButtonFrame.minX, height: contentHeight))
        
        var textContentHeight = textSize.height
        var textOffset: CGFloat = 0.0
        if !titleSize.width.isZero {
            textContentHeight += titleSize.height + 1.0
            textOffset += titleSize.height + 1.0
        }
        
        let textContentOrigin = floor((contentHeight - textContentHeight) / 2.0)
        let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: textContentOrigin), size: titleSize)
        transition.updatePosition(node: self.titleNode, position: titleFrame.origin)
        self.titleNode.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
        
        let textFrame = CGRect(origin: CGPoint(x: leftInset, y: textContentOrigin + textOffset), size: textSize)
        if let textComponentView = self.textComponent?.view {
            transition.updatePosition(layer: textComponentView.layer, position: textFrame.origin)
            textComponentView.bounds = CGRect(origin: CGPoint(), size: textFrame.size)
        } else {
            transition.updateFrame(node: self.textNode, frame: textFrame)
        }
        
        if let iconNode = self.iconNode {
            let iconSize: CGSize
            if let size = self.iconImageSize {
                iconSize = size
            } else if let size = iconNode.image?.size {
                iconSize = size
            } else {
                iconSize = CGSize()
            }
            
            var isProgress = false
            if case .progress = self.content {
                isProgress = true
            } else if case .actionSucceeded = self.content {
                isProgress = true
            }
            
            let iconFrame: CGRect
            if self.iconImageSize != nil && !isProgress {
                iconFrame = CGRect(origin: CGPoint(x: 9.0, y: floor((contentHeight - iconSize.height) / 2.0) + verticalOffset), size: iconSize)
            } else {
                iconFrame = CGRect(origin: CGPoint(x: floor((leftInset - iconSize.width) / 2.0), y: floor((contentHeight - iconSize.height) / 2.0) + verticalOffset), size: iconSize)
            }
            transition.updateFrame(node: iconNode, frame: iconFrame)
            
            if let iconCheckNode = self.iconCheckNode {
                let statusSize: CGFloat = iconCheckNode.frame.width
                var offset: CGFloat = 0.0
                if statusSize < 30.0 {
                    offset = 3.0
                }
                transition.updateFrame(node: iconCheckNode, frame: CGRect(origin: CGPoint(x: iconFrame.minX + floor((iconFrame.width - statusSize) / 2.0), y: iconFrame.minY + floor((iconFrame.height - statusSize) / 2.0) + offset), size: CGSize(width: statusSize, height: statusSize)))
            }
        }
        
        if let animationNode = self.animationNode, let iconSize = preferredSize {
            let iconFrame = CGRect(origin: CGPoint(x: floor((leftInset - iconSize.width) / 2.0), y: floor((contentHeight - iconSize.height) / 2.0) + verticalOffset), size: iconSize)
            transition.updateFrame(node: animationNode, frame: iconFrame)
        }
        
        if let stickerImageSize = self.stickerImageSize {
            let iconSize = stickerImageSize
            var iconFrame = CGRect(origin: CGPoint(x: floor((leftInset - iconSize.width) / 2.0), y: floor((contentHeight - iconSize.height) / 2.0)), size: iconSize)
            
            if let stickerOffset = self.stickerOffset {
                iconFrame = iconFrame.offsetBy(dx: stickerOffset.x, dy: stickerOffset.y)
            }
            
            if let stillStickerNode = self.stillStickerNode {
                let makeImageLayout = stillStickerNode.asyncLayout()
                
                var radius: CGFloat = 0.0
                if case .media = self.content {
                    radius = 6.0
                }
                var stickerImageSourceSize = stickerImageSize
                if let stickerSourceSize = self.stickerSourceSize {
                    stickerImageSourceSize = stickerSourceSize.aspectFilled(stickerImageSourceSize)
                }
                let imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(radius: radius), imageSize: stickerImageSourceSize, boundingSize: stickerImageSize, intrinsicInsets: UIEdgeInsets()))
                let _ = imageApply()
                transition.updateFrame(node: stillStickerNode, frame: iconFrame)
            }
            
            if let emojiStatusView = self.emojiStatus?.view {
                transition.updateFrame(view: emojiStatusView, frame: iconFrame)
            }
            
            if let animatedStickerNode = self.animatedStickerNode {
                animatedStickerNode.updateLayout(size: iconFrame.size)
                transition.updateFrame(node: animatedStickerNode, frame: iconFrame)
            } else if let slotMachineNode = self.slotMachineNode {
                transition.updateFrame(node: slotMachineNode, frame: iconFrame)
            }
        } else if let animatedStickerNode = self.animatedStickerNode {
            let iconSize = CGSize(width: 32.0, height: 32.0)
            let iconFrame = CGRect(origin: CGPoint(x: floor((leftInset - iconSize.width) / 2.0), y: floor((contentHeight - iconSize.height) / 2.0)), size: iconSize)
            animatedStickerNode.updateLayout(size: iconFrame.size)
            transition.updateFrame(node: animatedStickerNode, frame: iconFrame)
        } else if let slotMachineNode = self.slotMachineNode {
            let iconSize = CGSize(width: 32.0, height: 32.0)
            let iconFrame = CGRect(origin: CGPoint(x: floor((leftInset - iconSize.width) / 2.0), y: floor((contentHeight - iconSize.height) / 2.0)), size: iconSize)
            transition.updateFrame(node: slotMachineNode, frame: iconFrame)
        }
   
        let timerTextSize = self.timerTextNode.updateLayout(CGSize(width: 100.0, height: 100.0))
        if case .starsSent = self.content {
            transition.updateFrame(node: self.timerTextNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset + floor((rightInset - timerTextSize.width) * 0.5) - 46.0)), y: floor((contentHeight - timerTextSize.height) / 2.0)), size: timerTextSize))
        } else {
            transition.updateFrame(node: self.timerTextNode, frame: CGRect(origin: CGPoint(x: floor((leftInset - timerTextSize.width) / 2.0), y: floor((contentHeight - timerTextSize.height) / 2.0)), size: timerTextSize))
        }

        if let statusNode = self.statusNode {
            let statusSize: CGFloat = 30.0
            var statusFrame = CGRect(origin: CGPoint(x: floor((leftInset - statusSize) / 2.0), y: floor((contentHeight - statusSize) / 2.0)), size: CGSize(width: statusSize, height: statusSize))
            if case .starsSent = self.content {
                statusFrame.origin.x = layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset - statusSize - 23.0
            }
            transition.updateFrame(node: statusNode, frame: statusFrame)
            if !self.didStartStatusNode {
                if case .progress = self.content {
                    
                } else {
                    let statusColor: UIColor
                    if case .starsSent = self.content {
                        statusColor = self.undoTextColor
                    } else {
                        statusColor = .white
                    }
                    statusNode.transitionToState(.secretTimeout(color: statusColor, icon: .none, beginTime: CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, timeout: Double(self.remainingSeconds), sparks: false), completion: {})
                }
                self.didStartStatusNode = true
            }
        }
        
        if let avatarNode = self.avatarNode {
            let avatarSize: CGFloat = 30.0
            transition.updateFrame(node: avatarNode, frame: CGRect(origin: CGPoint(x: floor((leftInset - avatarSize) / 2.0), y: floor((contentHeight - avatarSize) / 2.0)), size: CGSize(width: avatarSize, height: avatarSize)))
        }
        
        if let multiAvatarsNode = self.multiAvatarsNode, let multiAvatarsSize = self.multiAvatarsSize {
            let avatarsFrame = CGRect(origin: CGPoint(x: 13.0, y: floor((contentHeight - multiAvatarsSize.height) / 2.0) + verticalOffset), size: multiAvatarsSize)
            transition.updateFrame(node: multiAvatarsNode, frame: avatarsFrame)
        }
        
        if let additionalView = self.additionalView {
            let additionalViewFrame = CGRect(origin: CGPoint(x: 0.0, y: panelWrapperFrame.maxY), size: CGSize(width: layout.size.width, height: layout.size.height - insets.bottom - panelWrapperFrame.maxY))
            transition.updateFrame(view: additionalView, frame: additionalViewFrame)
            additionalView.update(size: additionalViewFrame.size, transition: transition)
        }
    }
    
    func animateIn(asReplacement: Bool) {
        if asReplacement {
            let offset: CGFloat
            switch self.placementPosition {
            case .top:
                offset = -self.panelWrapperNode.frame.maxY
            case.bottom:
                offset = self.bounds.height - self.panelWrapperNode.frame.minY
            }
            
            self.panelWrapperNode.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: 0.35, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: nil)
            self.panelNode.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: 0.35, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: nil)
        } else {
            self.panelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
            self.panelWrapperNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
            
            self.panelNode.layer.animateScale(from: 0.96, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
            self.panelWrapperNode.layer.animateScale(from: 0.96, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
        }
        
        if let iconCheckNode = self.iconCheckNode, self.iconNode != nil {
            Queue.mainQueue().after(0.2, { [weak iconCheckNode] in
                iconCheckNode?.transitionToState(.check(self.animationBackgroundColor), completion: {})
            })
        }
        
        if let animationNode = self.animationNode {
            Queue.mainQueue().after(0.2, { [weak animationNode] in
                animationNode?.play()
            })
        }
        
        self.animatedStickerNode?.visibility = true
        
        if let additionalView = self.additionalView {
            additionalView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
        }
        
        self.checkTimer()
    }
    
    var dismissed = false
    func animateOut(completion: @escaping () -> Void) {
        guard !self.dismissed else {
            return
        }
        self.dismissed = true
        self.panelNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, delay: 0.0, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false, completion: { _ in })
        self.panelWrapperNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, delay: 0.0, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false) { _ in
            completion()
        }
        self.panelNode.layer.animateScale(from: 1.0, to: 0.96, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
        self.panelWrapperNode.layer.animateScale(from: 1.0, to: 0.96, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
        
        if let additionalView = self.additionalView {
            additionalView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
        }
    }
    
    func animateOutWithReplacement(completion: @escaping () -> Void) {
        self.panelWrapperNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.3, removeOnCompletion: false, completion: { _ in
            completion()
        })
        self.panelWrapperNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
        self.panelNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.3, removeOnCompletion: false, completion: { _ in
            completion()
        })
        self.panelNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
    }
    
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        if let additionalView = self.additionalView {
            if let result = additionalView.hitTest(self.view.convert(point, to: additionalView), with: event) {
                return result
            }
        }
        
        if !self.panelNode.frame.insetBy(dx: -60.0, dy: 0.0).contains(point) {
            if self.additionalView != nil && self.isTimeoutDisabled {
            } else {
                return nil
            }
        }
        let result = super.hitTest(point, with: event)
        if result == self {
            if self.additionalView != nil && self.isTimeoutDisabled {
                return self.view
            }
        }
        return result
    }
}