import Foundation
import UIKit
import AsyncDisplayKit
import Display
import SwiftSignalKit
import TelegramCore
import Postbox
import TelegramPresentationData
import TextFormat
import Markdown
import RadialStatusNode
import AppBundle
import AnimatedStickerNode
import TelegramAnimatedStickerNode
import SlotMachineAnimationNode
import AnimationUI
import StickerResources
import AvatarNode
import AccountContext

final class UndoOverlayControllerNode: ViewControllerTracingNode {
    private let elevatedLayout: Bool
    private let placementPosition: UndoOverlayController.Position
    private var statusNode: RadialStatusNode?
    private let timerTextNode: ImmediateTextNode
    private let avatarNode: AvatarNode?
    private let iconNode: ASImageNode?
    private var iconImageSize: CGSize?
    private let iconCheckNode: RadialStatusNode?
    private let animationNode: AnimationNode?
    private var animatedStickerNode: AnimatedStickerNode?
    private var slotMachineNode: SlotMachineAnimationNode?
    private var stillStickerNode: TransformImageNode?
    private var stickerImageSize: CGSize?
    private var stickerOffset: CGPoint?
    private let titleNode: ImmediateTextNode
    private let textNode: ImmediateTextNode
    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 effectView: UIView
    
    private let animationBackgroundColor: UIColor
        
    private var originalRemainingSeconds: Double
    private var remainingSeconds: Double
    private var timer: SwiftSignalKit.Timer?
    
    private var validLayout: ContainerViewLayout?
    
    private var fetchResourceDisposable: Disposable?
    
    init(presentationData: PresentationData, content: UndoOverlayContent, elevatedLayout: Bool, placementPosition: UndoOverlayController.Position, action: @escaping (UndoOverlayAction) -> Bool, dismiss: @escaping () -> Void) {
        self.elevatedLayout = elevatedLayout
        self.placementPosition = placementPosition
        self.content = content
        
        self.action = action
        self.dismiss = dismiss
        
        self.timerTextNode = ImmediateTextNode()
        self.timerTextNode.displaysAsynchronously = false
        
        self.titleNode = ImmediateTextNode()
        self.titleNode.displaysAsynchronously = false
        self.titleNode.maximumNumberOfLines = 0
        
        self.textNode = ImmediateTextNode()
        self.textNode.displaysAsynchronously = false
        self.textNode.maximumNumberOfLines = 0
        
        self.buttonNode = HighlightTrackingButtonNode()
        
        var displayUndo = true
        var undoText = presentationData.strings.Undo_Undo
        var undoTextColor = UIColor(rgb: 0x5ac8fa)
        undoTextColor = presentationData.theme.list.itemAccentColor.withMultiplied(hue: 1.0, saturation: 0.64, brightness: 1.08)
        
        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(text):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = nil
                self.animatedStickerNode = nil
                self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(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)
                self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
                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):
                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)
                }
                self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
                displayUndo = false
                self.originalRemainingSeconds = 3.5
            case let .succeed(text):
                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
                displayUndo = false
                self.originalRemainingSeconds = 3
            case let .info(title, text):
                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.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 = 2
                displayUndo = false
                self.originalRemainingSeconds = Double(max(5, min(8, text.count / 14)))
            
                if text.contains("](") {
                    isUserInteractionEnabled = true
                }
            case let .actionSucceeded(title, text, cancel):
                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
                
                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)
                self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
                self.textNode.attributedText = attributedText
                displayUndo = true
                undoText = cancel
                self.originalRemainingSeconds = 5
            case let .linkCopied(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
            
                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 .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 .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 = 2
                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 info.flags.contains(.isAnimated) || info.flags.contains(.isVideo) {
                        thumbnailItem = .animated(EngineMediaResource(thumbnail.resource), thumbnail.dimensions, info.flags.contains(.isVideo))
                    } 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))
                        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, 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, text):
                self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 15.0))
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = nil
                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.textNode.attributedText = attributedText
                
                self.avatarNode?.setPeer(context: context, theme: presentationData.theme, peer: peer, overrideImage: nil, emptyColor: presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: true)
                
                displayUndo = false
                self.originalRemainingSeconds = 3
            case let .audioRate(slowdown, text):
                self.avatarNode = nil
                self.iconNode = nil
                self.iconCheckNode = nil
                self.animationNode = AnimationNode(animation: slowdown ? "anim_voicespeedstop" : "anim_voicespeed", 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(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 .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, 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))
                    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, 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.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 = 2
            
                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, mode: .cached)
                    }
                }
            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 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 .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):
                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 {
                    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
                displayUndo = false
            case let .image(image, text):
                self.avatarNode = nil
                self.iconNode = ASImageNode()
                self.iconNode?.clipsToBounds = true
                self.iconNode?.contentMode = .scaleAspectFill
                self.iconNode?.image = image
                self.iconNode?.cornerRadius = 4.0
                self.iconImageSize = CGSize(width: 32.0, height: 32.0)
                self.iconCheckNode = nil
                self.animationNode = nil
                self.animatedStickerNode = nil
                self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
                displayUndo = true
                self.originalRemainingSeconds = 5
        }
        
        self.remainingSeconds = self.originalRemainingSeconds
        
        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.panelNode.backgroundColor = presentationData.theme.rootController.tabBar.backgroundColor
        } else {
            self.panelNode.backgroundColor = .clear
        }
        self.panelNode.clipsToBounds = true
        self.panelNode.cornerRadius = 9.0
        
        self.panelWrapperNode = ASDisplayNode()
        
        self.effectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
        
        super.init()
        
        switch content {
        case .removedChat:
            self.panelWrapperNode.addSubnode(self.timerTextNode)
        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:
            if self.textNode.tapAttributeAction != nil || displayUndo {
                self.isUserInteractionEnabled = true
            } else {
                self.isUserInteractionEnabled = false
            }
        case .sticker:
            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.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)
        self.animatedStickerNode.flatMap(self.panelWrapperNode.addSubnode)
        self.slotMachineNode.flatMap(self.panelWrapperNode.addSubnode)
        self.avatarNode.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
        }
    }
    
    deinit {
        self.fetchResourceDisposable?.dispose()
    }
    
    override func didLoad() {
        super.didLoad()
        
        self.panelNode.view.addSubview(self.effectView)
    }
    
    @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)
            }
        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 {
            if Int(self.remainingSeconds) != previousRemainingSeconds || (self.timerTextNode.attributedText?.string ?? "").isEmpty {
                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()
                    })
                }
                self.timerTextNode.attributedText = NSAttributedString(string: "\(Int(self.remainingSeconds))", font: Font.regular(16.0), textColor: .white)
                if let validLayout = self.validLayout {
                    self.containerLayoutUpdated(layout: validLayout, transition: .immediate)
                }
            }
            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.checkTimer()
    }
    
    func updateContent(_ content: UndoOverlayContent) {
        self.content = content
        
        switch content {
            case let .image(image, text):
                self.iconNode?.image = image
                self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
            default:
                break
        }
        
        self.renewWithCurrentContent()
        
        if let validLayout = self.validLayout {
            self.containerLayoutUpdated(layout: validLayout, transition: .immediate)
        }
    }
    
    func containerLayoutUpdated(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
        let firstLayout = self.validLayout == nil
        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 iconSize = preferredSize {
            if iconSize.width > leftInset {
                leftInset = iconSize.width - 8.0
            }
        }
        let rightInset: CGFloat = 16.0
        var contentHeight: CGFloat = 20.0
        
        let margin: CGFloat = 12.0
        
        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 - margin * 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 - margin, height: .greatestFiniteMagnitude))
        let textSize = self.textNode.updateLayout(CGSize(width: buttonMinX - 8.0 - leftInset - layout.safeInsets.left - margin, height: .greatestFiniteMagnitude))
        
        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:
            insets.top = layout.statusBarHeight ?? 0.0
        case .bottom:
            if self.elevatedLayout {
                insets.bottom += 49.0
            }
        }
        
        var panelFrame = CGRect(origin: CGPoint(x: margin + layout.safeInsets.left, y: layout.size.height - contentHeight - insets.bottom - margin), size: CGSize(width: layout.size.width - margin * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: contentHeight))
        var panelWrapperFrame = CGRect(origin: CGPoint(x: margin + layout.safeInsets.left, y: layout.size.height - contentHeight - insets.bottom - margin), size: CGSize(width: layout.size.width - margin * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: contentHeight))
        
        if case .top = self.placementPosition {
            panelFrame.origin.y = insets.top
            panelWrapperFrame.origin.y = insets.top
        }
        
        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 - margin * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: contentHeight)
        
        let buttonTextFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset - buttonTextSize.width - margin * 2.0, y: floor((contentHeight - buttonTextSize.height) / 2.0)), size: buttonTextSize)
        transition.updateFrame(node: self.undoButtonTextNode, frame: buttonTextFrame)
        
        let undoButtonFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset - buttonTextSize.width - 8.0 - margin * 2.0, y: 0.0), size: CGSize(width: layout.safeInsets.right + rightInset + buttonTextSize.width + 8.0 + margin, height: contentHeight))
        self.undoButtonNode.frame = undoButtonFrame
        
        self.buttonNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(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)
        transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: leftInset, y: textContentOrigin), size: titleSize))
        transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: leftInset, y: textContentOrigin + textOffset), size: textSize))
        
        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()
            }
            
            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: 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()
                let imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: stickerImageSize, boundingSize: stickerImageSize, intrinsicInsets: UIEdgeInsets()))
                let _ = imageApply()
                transition.updateFrame(node: stillStickerNode, 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))
        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
            transition.updateFrame(node: statusNode, frame: CGRect(origin: CGPoint(x: floor((leftInset - statusSize) / 2.0), y: floor((contentHeight - statusSize) / 2.0)), size: CGSize(width: statusSize, height: statusSize)))
            if firstLayout {
                statusNode.transitionToState(.secretTimeout(color: .white, icon: nil, beginTime: CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, timeout: Double(self.remainingSeconds), sparks: false), completion: {})
            }
        }
        
        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)))
        }
    }
    
    func animateIn(asReplacement: Bool) {
        if asReplacement {
            let 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)
        }
        
        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
        
        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()
        }
    }
    
    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 !self.panelNode.frame.insetBy(dx: -60.0, dy: 0.0).contains(point) {
            return nil
        }
        return super.hitTest(point, with: event)
    }
}