import Foundation
import UIKit
import AsyncDisplayKit
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import TelegramPresentationData
import AppBundle
import LocalizedPeerData
import TelegramStringFormatting
import AccountContext
import ChatPresentationInterfaceState
import WallpaperBackgroundNode
import ComponentFlow
import EmojiStatusComponent
import ChatLoadingNode
import MultilineTextComponent
import BalancedTextComponent
import Markdown
import ReactionSelectionNode
import ChatMediaInputStickerGridItem
import UndoUI
import PremiumUI
import LottieComponent

private protocol ChatEmptyNodeContent {
    func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize
}

private let titleFont = Font.semibold(15.0)
private let messageFont = Font.regular(13.0)

private final class ChatEmptyNodeRegularChatContent: ASDisplayNode, ChatEmptyNodeContent {
    private let textNode: ImmediateTextNode
    
    private var currentTheme: PresentationTheme?
    private var currentStrings: PresentationStrings?
    
    override init() {
        self.textNode = ImmediateTextNode()
        
        super.init()
        
        self.addSubnode(self.textNode)
    }
    
    func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
        if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings {
            self.currentTheme = interfaceState.theme
            self.currentStrings = interfaceState.strings
            
            let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper)
            
            let text: String
            if case .detailsPlaceholder = subject {
                text = interfaceState.strings.ChatList_StartMessaging
            } else {
                switch interfaceState.chatLocation {
                case .peer, .replyThread, .customChatContents:
                    if case .scheduledMessages = interfaceState.subject {
                        text = interfaceState.strings.ScheduledMessages_EmptyPlaceholder
                    } else {
                        text = interfaceState.strings.Conversation_EmptyPlaceholder
                    }
                }
            }
            
            self.textNode.attributedText = NSAttributedString(string: text, font: messageFont, textColor: serviceColor.primaryText)
        }
        
        let insets = UIEdgeInsets(top: 6.0, left: 10.0, bottom: 6.0, right: 10.0)
        
        let textSize = self.textNode.updateLayout(CGSize(width: size.width - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude))
        
        let contentWidth = textSize.width
        let contentHeight = textSize.height
        let contentRect = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: CGSize(width: contentWidth, height: contentHeight))
        
        transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: contentRect.minX + floor((contentRect.width - textSize.width) / 2.0), y: insets.top), size: textSize))
        
        return contentRect.insetBy(dx: -insets.left, dy: -insets.top).size
    }
}

public protocol ChatEmptyNodeStickerContentNode: ASDisplayNode {
    var stickerNode: ChatMediaInputStickerGridItemNode { get }
}

public final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNodeStickerContentNode, ChatEmptyNodeContent, ASGestureRecognizerDelegate {
    private let context: AccountContext
    private let interaction: ChatPanelInterfaceInteraction?
    
    private let titleNode: ImmediateTextNode
    private let textNode: ImmediateTextNode
    
    private var stickerItem: ChatMediaInputStickerGridItem?
    public var stickerNode: ChatMediaInputStickerGridItemNode
    
    private var currentTheme: PresentationTheme?
    private var currentStrings: PresentationStrings?
    
    private var didSetupSticker = false
    private let disposable = MetaDisposable()
    private var currentCustomStickerFile: TelegramMediaFile?
        
    public init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?) {
        self.context = context
        self.interaction = interaction
        
        self.titleNode = ImmediateTextNode()
        self.titleNode.maximumNumberOfLines = 0
        self.titleNode.lineSpacing = 0.15
        self.titleNode.textAlignment = .center
        self.titleNode.isUserInteractionEnabled = false
        self.titleNode.displaysAsynchronously = false
        
        self.textNode = ImmediateTextNode()
        self.textNode.maximumNumberOfLines = 0
        self.textNode.lineSpacing = 0.15
        self.textNode.textAlignment = .center
        self.textNode.isUserInteractionEnabled = false
        self.textNode.displaysAsynchronously = false
        
        self.stickerNode = ChatMediaInputStickerGridItemNode()
        
        super.init()
        
        self.addSubnode(self.titleNode)
        self.addSubnode(self.textNode)
        self.addSubnode(self.stickerNode)
    }
    
    override public func didLoad() {
        super.didLoad()
        
        let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.stickerTapGesture(_:)))
        tapRecognizer.delegate = self.wrappedGestureRecognizerDelegate
        self.stickerNode.view.addGestureRecognizer(tapRecognizer)
    }
    
    deinit {
        self.disposable.dispose()
    }
    
    public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
    
    @objc private func stickerTapGesture(_ gestureRecognizer: UITapGestureRecognizer) {
        guard let stickerItem = self.stickerItem else {
            return
        }
        let _ = self.interaction?.sendSticker(.standalone(media: stickerItem.stickerItem.file), false, self.view, self.stickerNode.bounds, nil, [])
    }
    
    public func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
        let isFirstTime = self.currentTheme == nil
        
        if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings {
            self.currentTheme = interfaceState.theme
            self.currentStrings = interfaceState.strings
        }
        
        var customStickerFile: TelegramMediaFile?
        
        let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper)
        if case let .emptyChat(emptyChat) = subject, case let .customGreeting(stickerFile, title, text) = emptyChat {
            customStickerFile = stickerFile
            self.titleNode.attributedText = NSAttributedString(string: title, font: titleFont, textColor: serviceColor.primaryText)
            self.textNode.attributedText = NSAttributedString(string: text, font: messageFont, textColor: serviceColor.primaryText)
        } else if let businessIntro = interfaceState.businessIntro {
            self.titleNode.attributedText = NSAttributedString(string: !businessIntro.title.isEmpty ? businessIntro.title : interfaceState.strings.Conversation_EmptyPlaceholder, font: titleFont, textColor: serviceColor.primaryText)
            self.textNode.attributedText = NSAttributedString(string: !businessIntro.text.isEmpty ? businessIntro.text : interfaceState.strings.Conversation_GreetingText, font: messageFont, textColor: serviceColor.primaryText)
            customStickerFile = businessIntro.stickerFile
        } else {
            self.titleNode.attributedText = NSAttributedString(string: interfaceState.strings.Conversation_EmptyPlaceholder, font: titleFont, textColor: serviceColor.primaryText)
            self.textNode.attributedText = NSAttributedString(string: interfaceState.strings.Conversation_GreetingText, font: messageFont, textColor: serviceColor.primaryText)
        }
        
        let previousCustomStickerFile = self.currentCustomStickerFile
        self.currentCustomStickerFile = customStickerFile
        
        var stickerSize: CGSize
        let inset: CGFloat
        if size.width == 320.0 {
            stickerSize = CGSize(width: 106.0, height: 106.0)
            inset = 8.0
        } else  {
            stickerSize = CGSize(width: 160.0, height: 160.0)
            inset = 15.0
        }
        
        if let customStickerFile, let dimensions = customStickerFile.dimensions?.cgSize {
            stickerSize = dimensions.aspectFitted(stickerSize)
        }
        
        if let item = self.stickerItem, previousCustomStickerFile == customStickerFile {
            self.stickerNode.updateLayout(item: item, size: stickerSize, isVisible: true, synchronousLoads: true)
        } else if !self.didSetupSticker || previousCustomStickerFile != customStickerFile {
            let sticker: Signal<TelegramMediaFile?, NoError>
            if let customStickerFile {
                sticker = .single(customStickerFile)
            } else if let preloadedSticker = interfaceState.greetingData?.sticker {
                sticker = preloadedSticker
            } else {
                sticker = self.context.engine.stickers.randomGreetingSticker()
                |> map { item -> TelegramMediaFile? in
                    return item?.file
                }
            }
            
            if !isFirstTime, case let .emptyChat(emptyChat) = subject, case .customGreeting = emptyChat {
                let previousStickerNode = self.stickerNode
                previousStickerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak previousStickerNode] _ in
                    previousStickerNode?.removeFromSupernode()
                })
                previousStickerNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, removeOnCompletion: false)
                
                self.stickerNode = ChatMediaInputStickerGridItemNode()
                self.addSubnode(self.stickerNode)
                self.stickerNode.layer.animateSpring(from: 0.001 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5)
                self.stickerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
            }
            
            self.didSetupSticker = true
            self.disposable.set((sticker
            |> deliverOnMainQueue).startStrict(next: { [weak self] sticker in
                if let strongSelf = self, let sticker = sticker {
                    let inputNodeInteraction = ChatMediaInputNodeInteraction(
                        navigateToCollectionId: { _ in
                        },
                        navigateBackToStickers: {
                        },
                        setGifMode: { _ in
                        },
                        openSettings: {
                        },
                        openTrending: { _ in
                        },
                        dismissTrendingPacks: { _ in
                        },
                        toggleSearch: { _, _, _ in
                        },
                        openPeerSpecificSettings: {
                        },
                        dismissPeerSpecificSettings: {
                        },
                        clearRecentlyUsedStickers: {
                        }
                    )
                    inputNodeInteraction.displayStickerPlaceholder = false
                    
                    let index = ItemCollectionItemIndex(index: 0, id: 0)
                    let collectionId = ItemCollectionId(namespace: 0, id: 0)
                    let stickerPackItem = StickerPackItem(index: index, file: sticker, indexKeys: [])
                    let item = ChatMediaInputStickerGridItem(context: strongSelf.context, collectionId: collectionId, stickerPackInfo: nil, index: ItemCollectionViewEntryIndex(collectionIndex: 0, collectionId: collectionId, itemIndex: index), stickerItem: stickerPackItem, canManagePeerSpecificPack: nil, interfaceInteraction: nil, inputNodeInteraction: inputNodeInteraction, hasAccessory: false, theme: interfaceState.theme, large: true, selected: {})
                    strongSelf.stickerItem = item
                    
                    if isFirstTime {
                        
                    }
                    strongSelf.stickerNode.updateLayout(item: item, size: stickerSize, isVisible: true, synchronousLoads: true)
                    strongSelf.stickerNode.isVisibleInGrid = true
                    strongSelf.stickerNode.updateIsPanelVisible(true)
                }
            }))
        }
        
        let insets = UIEdgeInsets(top: inset, left: inset, bottom: inset, right: inset)
        let titleSpacing: CGFloat = 5.0
        let stickerSpacing: CGFloat = 5.0
        
        var contentWidth: CGFloat = 220.0
        var contentHeight: CGFloat = 0.0
                
        let titleSize = self.titleNode.updateLayout(CGSize(width: contentWidth, height: CGFloat.greatestFiniteMagnitude))
        let textSize = self.textNode.updateLayout(CGSize(width: contentWidth, height: CGFloat.greatestFiniteMagnitude))
        
        contentWidth = max(contentWidth, max(titleSize.width, textSize.width))
        
        contentHeight += titleSize.height + titleSpacing + textSize.height + stickerSpacing + stickerSize.height
        
        let contentRect = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: CGSize(width: contentWidth, height: contentHeight))
        
        let titleFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((contentRect.width - titleSize.width) / 2.0), y: contentRect.minY), size: titleSize)
        transition.updateFrame(node: self.titleNode, frame: titleFrame)
       
        let textFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((contentRect.width - textSize.width) / 2.0), y: titleFrame.maxY + titleSpacing), size: textSize)
        transition.updateFrame(node: self.textNode, frame: textFrame)
        
        let stickerFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((contentRect.width - stickerSize.width) / 2.0), y: textFrame.maxY + stickerSpacing), size: stickerSize)
        transition.updateFrame(node: self.stickerNode, frame: stickerFrame)
        
        return contentRect.insetBy(dx: -insets.left, dy: -insets.top).size
    }
}

public final class ChatEmptyNodeNearbyChatContent: ASDisplayNode, ChatEmptyNodeStickerContentNode, ChatEmptyNodeContent, ASGestureRecognizerDelegate {
    private let context: AccountContext
    private let interaction: ChatPanelInterfaceInteraction?
    
    private let titleNode: ImmediateTextNode
    private let textNode: ImmediateTextNode
    
    private var stickerItem: ChatMediaInputStickerGridItem?
    public let stickerNode: ChatMediaInputStickerGridItemNode
    
    private var currentTheme: PresentationTheme?
    private var currentStrings: PresentationStrings?
    
    private var didSetupSticker = false
    private let disposable = MetaDisposable()
    
    public init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?) {
        self.context = context
        self.interaction = interaction
        
        self.titleNode = ImmediateTextNode()
        self.titleNode.maximumNumberOfLines = 0
        self.titleNode.lineSpacing = 0.15
        self.titleNode.textAlignment = .center
        self.titleNode.isUserInteractionEnabled = false
        self.titleNode.displaysAsynchronously = false
        
        self.textNode = ImmediateTextNode()
        self.textNode.maximumNumberOfLines = 0
        self.textNode.lineSpacing = 0.15
        self.textNode.textAlignment = .center
        self.textNode.isUserInteractionEnabled = false
        self.textNode.displaysAsynchronously = false
        
        self.stickerNode = ChatMediaInputStickerGridItemNode()
        
        super.init()
        
        self.addSubnode(self.titleNode)
        self.addSubnode(self.textNode)
        self.addSubnode(self.stickerNode)
    }
    
    override public func didLoad() {
        super.didLoad()
        
        let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.stickerTapGesture(_:)))
        tapRecognizer.delegate = self.wrappedGestureRecognizerDelegate
        self.stickerNode.view.addGestureRecognizer(tapRecognizer)
    }
    
    deinit {
        self.disposable.dispose()
    }
    
    public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
    
    @objc private func stickerTapGesture(_ gestureRecognizer: UITapGestureRecognizer) {
        guard let stickerItem = self.stickerItem else {
            return
        }
        let _ = self.interaction?.sendSticker(.standalone(media: stickerItem.stickerItem.file), false, self.view, self.stickerNode.bounds, nil, [])
    }
    
    public func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
        if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings {
            self.currentTheme = interfaceState.theme
            self.currentStrings = interfaceState.strings
            
            var displayName = ""
            let distance = interfaceState.peerNearbyData?.distance ?? 0
            
            if let renderedPeer = interfaceState.renderedPeer {
                if let chatPeer = renderedPeer.peers[renderedPeer.peerId] {
                    displayName = EnginePeer(chatPeer).compactDisplayTitle
                }
            }

            let titleString = interfaceState.strings.Conversation_PeerNearbyTitle(displayName, shortStringForDistance(strings: interfaceState.strings, distance: distance)).string
            let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper)
            
            self.titleNode.attributedText = NSAttributedString(string: titleString, font: titleFont, textColor: serviceColor.primaryText)
            
            self.textNode.attributedText = NSAttributedString(string: interfaceState.strings.Conversation_PeerNearbyText, font: messageFont, textColor: serviceColor.primaryText)
        }
        
        let stickerSize = CGSize(width: 160.0, height: 160.0)
        if let item = self.stickerItem {
            self.stickerNode.updateLayout(item: item, size: stickerSize, isVisible: true, synchronousLoads: true)
        } else if !self.didSetupSticker {
            let sticker: Signal<TelegramMediaFile?, NoError>
            if let preloadedSticker = interfaceState.greetingData?.sticker {
                sticker = preloadedSticker
            } else {
                sticker = self.context.engine.stickers.randomGreetingSticker()
                |> map { item -> TelegramMediaFile? in
                    return item?.file
                }
            }
            
            self.didSetupSticker = true
            self.disposable.set((sticker
            |> deliverOnMainQueue).startStrict(next: { [weak self] sticker in
                if let strongSelf = self, let sticker = sticker {
                    let inputNodeInteraction = ChatMediaInputNodeInteraction(
                        navigateToCollectionId: { _ in
                        },
                        navigateBackToStickers: {
                        },
                        setGifMode: { _ in
                        },
                        openSettings: {
                        },
                        openTrending: { _ in
                        },
                        dismissTrendingPacks: { _ in
                        },
                        toggleSearch: { _, _, _ in
                        },
                        openPeerSpecificSettings: {
                        },
                        dismissPeerSpecificSettings: {
                        },
                        clearRecentlyUsedStickers: {
                        }
                    )
                    inputNodeInteraction.displayStickerPlaceholder = false
                    
                    let index = ItemCollectionItemIndex(index: 0, id: 0)
                    let collectionId = ItemCollectionId(namespace: 0, id: 0)
                    let stickerPackItem = StickerPackItem(index: index, file: sticker, indexKeys: [])
                    let item = ChatMediaInputStickerGridItem(context: strongSelf.context, collectionId: collectionId, stickerPackInfo: nil, index: ItemCollectionViewEntryIndex(collectionIndex: 0, collectionId: collectionId, itemIndex: index), stickerItem: stickerPackItem, canManagePeerSpecificPack: nil, interfaceInteraction: nil, inputNodeInteraction: inputNodeInteraction, hasAccessory: false, theme: interfaceState.theme, large: true, selected: {})
                    strongSelf.stickerItem = item
                    strongSelf.stickerNode.updateLayout(item: item, size: stickerSize, isVisible: true, synchronousLoads: true)
                    strongSelf.stickerNode.isVisibleInGrid = true
                    strongSelf.stickerNode.updateIsPanelVisible(true)
                }
            }))
        }
        
        let insets = UIEdgeInsets(top: 15.0, left: 15.0, bottom: 15.0, right: 15.0)
        let titleSpacing: CGFloat = 5.0
        let stickerSpacing: CGFloat = 5.0
        
        var contentWidth: CGFloat = 210.0
        var contentHeight: CGFloat = 0.0
                
        let titleSize = self.titleNode.updateLayout(CGSize(width: contentWidth, height: CGFloat.greatestFiniteMagnitude))
        let textSize = self.textNode.updateLayout(CGSize(width: contentWidth, height: CGFloat.greatestFiniteMagnitude))
        
        contentWidth = max(contentWidth, max(titleSize.width, textSize.width))
        
        contentHeight += titleSize.height + titleSpacing + textSize.height + stickerSpacing + stickerSize.height
        
        let contentRect = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: CGSize(width: contentWidth, height: contentHeight))
        
        let titleFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((contentRect.width - titleSize.width) / 2.0), y: contentRect.minY), size: titleSize)
        transition.updateFrame(node: self.titleNode, frame: titleFrame)
      
        let textFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((contentRect.width - textSize.width) / 2.0), y: titleFrame.maxY + titleSpacing), size: textSize)
        transition.updateFrame(node: self.textNode, frame: textFrame)
        
        let stickerFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((contentRect.width - stickerSize.width) / 2.0), y: textFrame.maxY + stickerSpacing), size: stickerSize)
        transition.updateFrame(node: self.stickerNode, frame: stickerFrame)
        
        return contentRect.insetBy(dx: -insets.left, dy: -insets.top).size
    }
}

private final class ChatEmptyNodeSecretChatContent: ASDisplayNode, ChatEmptyNodeContent {
    private let titleNode: ImmediateTextNode
    private let subtitleNode: ImmediateTextNode
    private var lineNodes: [(ASImageNode, ImmediateTextNode)] = []
    
    private var currentTheme: PresentationTheme?
    private var currentStrings: PresentationStrings?
    
    override init() {
        self.titleNode = ImmediateTextNode()
        self.titleNode.maximumNumberOfLines = 0
        self.titleNode.lineSpacing = 0.25
        self.titleNode.textAlignment = .center
        self.titleNode.isUserInteractionEnabled = false
        self.titleNode.displaysAsynchronously = false
        
        self.subtitleNode = ImmediateTextNode()
        self.subtitleNode.maximumNumberOfLines = 0
        self.subtitleNode.lineSpacing = 0.25
        self.subtitleNode.isUserInteractionEnabled = false
        self.subtitleNode.displaysAsynchronously = false
        
        super.init()
        
        self.addSubnode(self.titleNode)
        self.addSubnode(self.subtitleNode)
    }
    
    func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
        if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings {
            self.currentTheme = interfaceState.theme
            self.currentStrings = interfaceState.strings
            
            var title = " "
            var incoming = false
            if let renderedPeer = interfaceState.renderedPeer {
                if let chatPeer = renderedPeer.peers[renderedPeer.peerId] as? TelegramSecretChat {
                    if case .participant = chatPeer.role {
                        incoming = true
                    }
                    if let user = renderedPeer.peers[chatPeer.regularPeerId] {
                        title = EnginePeer(user).compactDisplayTitle
                    }
                }
            }
            
            let titleString: String
            if incoming {
                titleString = interfaceState.strings.Conversation_EncryptedPlaceholderTitleIncoming(title).string
            } else {
                titleString = interfaceState.strings.Conversation_EncryptedPlaceholderTitleOutgoing(title).string
            }
            
            let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper)
            
            self.titleNode.attributedText = NSAttributedString(string: titleString, font: titleFont, textColor: serviceColor.primaryText)
            
            self.subtitleNode.attributedText = NSAttributedString(string: interfaceState.strings.Conversation_EncryptedDescriptionTitle, font: messageFont, textColor: serviceColor.primaryText)
            
            let strings: [String] = [
                interfaceState.strings.Conversation_EncryptedDescription1,
                interfaceState.strings.Conversation_EncryptedDescription2,
                interfaceState.strings.Conversation_EncryptedDescription3,
                interfaceState.strings.Conversation_EncryptedDescription4
            ]
            
            let lines: [NSAttributedString] = strings.map { NSAttributedString(string: $0, font: messageFont, textColor: serviceColor.primaryText) }
            
            let graphics = PresentationResourcesChat.additionalGraphics(interfaceState.theme, wallpaper: interfaceState.chatWallpaper, bubbleCorners: interfaceState.bubbleCorners)
            let lockIcon = graphics.chatEmptyItemLockIcon
            
            for i in 0 ..< lines.count {
                if i >= self.lineNodes.count {
                    let iconNode = ASImageNode()
                    iconNode.isLayerBacked = true
                    iconNode.displaysAsynchronously = false
                    iconNode.displayWithoutProcessing = true
                    let textNode = ImmediateTextNode()
                    textNode.maximumNumberOfLines = 0
                    textNode.isUserInteractionEnabled = false
                    textNode.displaysAsynchronously = false
                    self.addSubnode(iconNode)
                    self.addSubnode(textNode)
                    self.lineNodes.append((iconNode, textNode))
                }
                
                self.lineNodes[i].0.image = lockIcon
                self.lineNodes[i].1.attributedText = lines[i]
            }
        }
        
        let insets = UIEdgeInsets(top: 15.0, left: 15.0, bottom: 15.0, right: 15.0)
        let titleSpacing: CGFloat = 5.0
        let subtitleSpacing: CGFloat = 11.0
        let iconInset: CGFloat = 14.0
        
        var contentWidth: CGFloat = 100.0
        var contentHeight: CGFloat = 0.0
        
        var lineNodes: [(CGSize, ASImageNode, ImmediateTextNode)] = []
        for (iconNode, textNode) in self.lineNodes {
            let textSize = textNode.updateLayout(CGSize(width: size.width - insets.left - insets.right - 10.0, height: CGFloat.greatestFiniteMagnitude))
            contentWidth = max(contentWidth, iconInset + textSize.width)
            contentHeight += textSize.height + subtitleSpacing
            lineNodes.append((textSize, iconNode, textNode))
        }
        
        let titleSize = self.titleNode.updateLayout(CGSize(width: contentWidth, height: CGFloat.greatestFiniteMagnitude))
        let subtitleSize = self.subtitleNode.updateLayout(CGSize(width: contentWidth, height: CGFloat.greatestFiniteMagnitude))
        
        contentWidth = max(contentWidth, max(titleSize.width, subtitleSize.width))
        
        contentHeight += titleSize.height + titleSpacing + subtitleSize.height
        
        let contentRect = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: CGSize(width: contentWidth, height: contentHeight))
        
        let titleFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((contentRect.width - titleSize.width) / 2.0), y: contentRect.minY), size: titleSize)
        transition.updateFrame(node: self.titleNode, frame: titleFrame)
        let subtitleFrame = CGRect(origin: CGPoint(x: contentRect.minX, y: titleFrame.maxY + titleSpacing), size: subtitleSize)
        transition.updateFrame(node: self.subtitleNode, frame: subtitleFrame)
        
        var lineOffset = subtitleFrame.maxY + subtitleSpacing / 2.0
        for (textSize, iconNode, textNode) in lineNodes {
            if let image = iconNode.image {
                transition.updateFrame(node: iconNode, frame: CGRect(origin: CGPoint(x: contentRect.minX, y: lineOffset + 1.0), size: image.size))
            }
            transition.updateFrame(node: textNode, frame: CGRect(origin: CGPoint(x: contentRect.minX + iconInset, y: lineOffset), size: textSize))
            lineOffset += textSize.height + subtitleSpacing
        }
        
        return contentRect.insetBy(dx: -insets.left, dy: -insets.top).size
    }
}

private final class ChatEmptyNodeGroupChatContent: ASDisplayNode, ChatEmptyNodeContent {
    private let titleNode: ImmediateTextNode
    private let subtitleNode: ImmediateTextNode
    private var lineNodes: [(ASImageNode, ImmediateTextNode)] = []
    
    private var currentTheme: PresentationTheme?
    private var currentStrings: PresentationStrings?
    
    override init() {
        self.titleNode = ImmediateTextNode()
        self.titleNode.maximumNumberOfLines = 0
        self.titleNode.lineSpacing = 0.25
        self.titleNode.textAlignment = .center
        self.titleNode.isUserInteractionEnabled = false
        self.titleNode.displaysAsynchronously = false
        
        self.subtitleNode = ImmediateTextNode()
        self.subtitleNode.maximumNumberOfLines = 0
        self.subtitleNode.lineSpacing = 0.25
        self.subtitleNode.isUserInteractionEnabled = false
        self.subtitleNode.displaysAsynchronously = false
        
        super.init()
        
        self.addSubnode(self.titleNode)
        self.addSubnode(self.subtitleNode)
    }
    
    func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
        if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings {
            self.currentTheme = interfaceState.theme
            self.currentStrings = interfaceState.strings
            
            let titleString: String = interfaceState.strings.EmptyGroupInfo_Title
            
            let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper)
            
            self.titleNode.attributedText = NSAttributedString(string: titleString, font: titleFont, textColor: serviceColor.primaryText)
            
            self.subtitleNode.attributedText = NSAttributedString(string: interfaceState.strings.EmptyGroupInfo_Subtitle, font: messageFont, textColor: serviceColor.primaryText)
            
            let strings: [String] = [
                interfaceState.strings.EmptyGroupInfo_Line1("\(interfaceState.limitsConfiguration.maxSupergroupMemberCount)").string,
                interfaceState.strings.EmptyGroupInfo_Line2,
                interfaceState.strings.EmptyGroupInfo_Line3,
                interfaceState.strings.EmptyGroupInfo_Line4
            ]
            
            let lines: [NSAttributedString] = strings.map { NSAttributedString(string: $0, font: messageFont, textColor: serviceColor.primaryText) }
            
            let graphics = PresentationResourcesChat.additionalGraphics(interfaceState.theme, wallpaper: interfaceState.chatWallpaper, bubbleCorners: interfaceState.bubbleCorners)
            let lockIcon = graphics.emptyChatListCheckIcon
            
            for i in 0 ..< lines.count {
                if i >= self.lineNodes.count {
                    let iconNode = ASImageNode()
                    iconNode.isLayerBacked = true
                    iconNode.displaysAsynchronously = false
                    iconNode.displayWithoutProcessing = true
                    let textNode = ImmediateTextNode()
                    textNode.maximumNumberOfLines = 0
                    textNode.isUserInteractionEnabled = false
                    textNode.displaysAsynchronously = false
                    self.addSubnode(iconNode)
                    self.addSubnode(textNode)
                    self.lineNodes.append((iconNode, textNode))
                }
                
                self.lineNodes[i].0.image = lockIcon
                self.lineNodes[i].1.attributedText = lines[i]
            }
        }
        
        let insets = UIEdgeInsets(top: 15.0, left: 15.0, bottom: 15.0, right: 15.0)
        let titleSpacing: CGFloat = 5.0
        let subtitleSpacing: CGFloat = 11.0
        let iconInset: CGFloat = 19.0
        
        var contentWidth: CGFloat = 100.0
        var contentHeight: CGFloat = 0.0
        
        var lineNodes: [(CGSize, ASImageNode, ImmediateTextNode)] = []
        for (iconNode, textNode) in self.lineNodes {
            let textSize = textNode.updateLayout(CGSize(width: size.width - insets.left - insets.right - 10.0, height: CGFloat.greatestFiniteMagnitude))
            contentWidth = max(contentWidth, iconInset + textSize.width)
            contentHeight += textSize.height + subtitleSpacing
            lineNodes.append((textSize, iconNode, textNode))
        }
        
        let titleSize = self.titleNode.updateLayout(CGSize(width: contentWidth, height: CGFloat.greatestFiniteMagnitude))
        let subtitleSize = self.subtitleNode.updateLayout(CGSize(width: contentWidth, height: CGFloat.greatestFiniteMagnitude))
        
        contentWidth = max(contentWidth, max(titleSize.width, subtitleSize.width))
        
        contentHeight += titleSize.height + titleSpacing + subtitleSize.height
        
        let contentRect = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: CGSize(width: contentWidth, height: contentHeight))
        
        let titleFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((contentRect.width - titleSize.width) / 2.0), y: contentRect.minY), size: titleSize)
        transition.updateFrame(node: self.titleNode, frame: titleFrame)
        let subtitleFrame = CGRect(origin: CGPoint(x: contentRect.minX, y: titleFrame.maxY + titleSpacing), size: subtitleSize)
        transition.updateFrame(node: self.subtitleNode, frame: subtitleFrame)
        
        var lineOffset = subtitleFrame.maxY + subtitleSpacing / 2.0
        for (textSize, iconNode, textNode) in lineNodes {
            if let image = iconNode.image {
                transition.updateFrame(node: iconNode, frame: CGRect(origin: CGPoint(x: contentRect.minX, y: lineOffset + 2.0), size: image.size))
            }
            transition.updateFrame(node: textNode, frame: CGRect(origin: CGPoint(x: contentRect.minX + iconInset, y: lineOffset), size: textSize))
            lineOffset += textSize.height + subtitleSpacing
        }
        
        return contentRect.insetBy(dx: -insets.left, dy: -insets.top).size
    }
}

private final class ChatEmptyNodeCloudChatContent: ASDisplayNode, ChatEmptyNodeContent {
    private let iconNode: ASImageNode
    private let titleNode: ImmediateTextNode
    private var lineNodes: [ImmediateTextNode] = []
    
    private var linkTextButton: HighlightTrackingButtonNode?
    private var linkTextNode: ImmediateTextNode?
    private var linkTextHighlightNode: LinkHighlightingNode?
    
    private var currentTheme: PresentationTheme?
    private var currentStrings: PresentationStrings?
    
    private var businessLink: TelegramBusinessChatLinks.Link?
    var shareBusinessLink: ((String) -> Void)?
    
    override init() {
        self.iconNode = ASImageNode()
        self.iconNode.isLayerBacked = true
        self.iconNode.displaysAsynchronously = false
        self.iconNode.displayWithoutProcessing = true
        
        self.titleNode = ImmediateTextNode()
        self.titleNode.maximumNumberOfLines = 0
        self.titleNode.lineSpacing = 0.15
        self.titleNode.textAlignment = .center
        self.titleNode.isUserInteractionEnabled = false
        self.titleNode.displaysAsynchronously = false
        
        super.init()
        
        self.addSubnode(self.iconNode)
        self.addSubnode(self.titleNode)
    }
    
    @objc private func linkTextButtonPressed() {
        guard let businessLink = self.businessLink else {
            return
        }
        self.shareBusinessLink?(businessLink.url)
    }
    
    func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
        var maxWidth: CGFloat = size.width
        var centerText = false
        
        var insets = UIEdgeInsets(top: 15.0, left: 15.0, bottom: 15.0, right: 15.0)
        var imageSpacing: CGFloat = 12.0
        var titleSpacing: CGFloat = 4.0
        
        let businessLinkTextSpacing: CGFloat = 9.0
        
        if case let .customChatContents(customChatContents) = interfaceState.subject {
            maxWidth = min(240.0, maxWidth)
            
            switch customChatContents.kind {
            case .quickReplyMessageInput:
                insets.top = 10.0
                imageSpacing = 5.0
                titleSpacing = 5.0
            case .businessLinkSetup:
                insets.top = -9.0
                imageSpacing = 4.0
                titleSpacing = 5.0
            case .hashTagSearch:
                break
            }
        }
        
        if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings {
            self.currentTheme = interfaceState.theme
            self.currentStrings = interfaceState.strings
            
            let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper)
            
            var iconName = "Chat/Empty Chat/Cloud"
            
            let titleString: String
            let strings: [String]
            var textFontSize: CGFloat = 14.0
            
            var businessLink: String?
            
            if case let .customChatContents(customChatContents) = interfaceState.subject {
                switch customChatContents.kind {
                case let .quickReplyMessageInput(shortcut, shortcutType):
                    switch shortcutType {
                    case .generic:
                        iconName = "Chat/Empty Chat/QuickReplies"
                        centerText = false
                        titleString = interfaceState.strings.Chat_EmptyState_QuickReply_Title
                        strings = [
                            interfaceState.strings.Chat_EmptyState_QuickReply_Text1(shortcut).string,
                            interfaceState.strings.Chat_EmptyState_QuickReply_Text2
                        ]
                    case .greeting:
                        iconName = "Chat/Empty Chat/GreetingShortcut"
                        centerText = true
                        titleString = interfaceState.strings.EmptyState_GreetingMessage_Title
                        strings = [
                            interfaceState.strings.EmptyState_GreetingMessage_Text
                        ]
                    case .away:
                        iconName = "Chat/Empty Chat/AwayShortcut"
                        centerText = true
                        titleString = interfaceState.strings.EmptyState_AwayMessage_Title
                        strings = [
                            interfaceState.strings.EmptyState_AwayMessage_Text
                        ]
                    }
                case let .businessLinkSetup(link):
                    iconName = "Chat/Empty Chat/BusinessLink"
                    centerText = true
                    titleString = interfaceState.strings.Business_Links_PreviewTitle
                    textFontSize = 13.0
                    strings = [
                        interfaceState.strings.Business_Links_PreviewText
                    ]
                    if link.url.hasPrefix("https://") {
                        businessLink = String(link.url[link.url.index(link.url.startIndex, offsetBy: "https://".count)...])
                    } else {
                        businessLink = link.url
                    }
                    
                    self.businessLink = link
                case .hashTagSearch:
                    titleString = ""
                    strings = []
                }
            } else {
                titleString = interfaceState.strings.Conversation_CloudStorageInfo_Title
                strings = [
                    interfaceState.strings.Conversation_ClousStorageInfo_Description1,
                    interfaceState.strings.Conversation_ClousStorageInfo_Description2,
                    interfaceState.strings.Conversation_ClousStorageInfo_Description3,
                    interfaceState.strings.Conversation_ClousStorageInfo_Description4
                ]
            }
            
            self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: iconName), color: serviceColor.primaryText)
            
            self.titleNode.attributedText = NSAttributedString(string: titleString, font: titleFont, textColor: serviceColor.primaryText)
            
            let lines: [NSAttributedString] = strings.map {
                return parseMarkdownIntoAttributedString($0, attributes: MarkdownAttributes(
                    body: MarkdownAttributeSet(font: Font.regular(textFontSize), textColor: serviceColor.primaryText),
                    bold: MarkdownAttributeSet(font: Font.semibold(textFontSize), textColor: serviceColor.primaryText),
                    link: MarkdownAttributeSet(font: Font.regular(textFontSize), textColor: serviceColor.primaryText),
                    linkAttribute: { url in
                        return ("URL", url)
                    }
                ), textAlignment: centerText ? .center : .natural)
            }
            
            for i in 0 ..< lines.count {
                if i >= self.lineNodes.count {
                    let textNode = ImmediateTextNode()
                    textNode.maximumNumberOfLines = 0
                    textNode.isUserInteractionEnabled = false
                    textNode.displaysAsynchronously = false
                    textNode.textAlignment = centerText ? .center : .natural
                    self.addSubnode(textNode)
                    self.lineNodes.append(textNode)
                }
                
                self.lineNodes[i].attributedText = lines[i]
            }
            
            if let businessLink {
                let linkTextButton: HighlightTrackingButtonNode
                if let current = self.linkTextButton {
                    linkTextButton = current
                } else {
                    linkTextButton = HighlightTrackingButtonNode()
                    self.linkTextButton = linkTextButton
                    self.addSubnode(linkTextButton)
                    
                    linkTextButton.addTarget(self, action: #selector(self.linkTextButtonPressed), forControlEvents: .touchUpInside)
                    linkTextButton.highligthedChanged = { [weak linkTextButton] highlighted in
                        if let linkTextButton, linkTextButton.bounds.width > 0.0 {
                            let animateScale = true
                            
                            let topScale: CGFloat = (linkTextButton.bounds.width - 8.0) / linkTextButton.bounds.width
                            let maxScale: CGFloat = (linkTextButton.bounds.width + 2.0) / linkTextButton.bounds.width
                            
                            if highlighted {
                                linkTextButton.layer.removeAnimation(forKey: "transform.scale")
                                
                                if animateScale {
                                    let transition = ComponentTransition(animation: .curve(duration: 0.2, curve: .easeInOut))
                                    transition.setScale(layer: linkTextButton.layer, scale: topScale)
                                }
                            } else {
                                if animateScale {
                                    let transition = ComponentTransition(animation: .none)
                                    transition.setScale(layer: linkTextButton.layer, scale: 1.0)
                                    
                                    linkTextButton.layer.animateScale(from: topScale, to: maxScale, duration: 0.13, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false, completion: { [weak linkTextButton] _ in
                                        guard let linkTextButton else {
                                            return
                                        }
                                        
                                        linkTextButton.layer.animateScale(from: maxScale, to: 1.0, duration: 0.1, timingFunction: CAMediaTimingFunctionName.easeIn.rawValue)
                                    })
                                }
                            }
                        }
                    }
                }
                
                let linkTextNode: ImmediateTextNode
                if let current = self.linkTextNode {
                    linkTextNode = current
                } else {
                    linkTextNode = ImmediateTextNode()
                    linkTextNode.maximumNumberOfLines = 0
                    linkTextNode.textAlignment = .center
                    linkTextNode.lineSpacing = 0.2
                    self.linkTextNode = linkTextNode
                    linkTextButton.addSubnode(linkTextNode)
                }
                
                linkTextNode.attributedText = NSAttributedString(string: businessLink, font: Font.medium(textFontSize), textColor: serviceColor.primaryText)
            } else {
                if let linkTextButton = self.linkTextButton {
                    self.linkTextButton = nil
                    linkTextButton.removeFromSupernode()
                }
                if let linkTextNode = self.linkTextNode {
                    self.linkTextNode = nil
                    linkTextNode.removeFromSupernode()
                }
            }
        }
        
        var contentWidth: CGFloat = 100.0
        var contentHeight: CGFloat = 0.0
        
        if let image = self.iconNode.image {
            contentHeight += image.size.height
            contentHeight += imageSpacing
            contentWidth = max(contentWidth, image.size.width)
        }
        
        var lineNodes: [(CGSize, ImmediateTextNode)] = []
        for textNode in self.lineNodes {
            let textSize = textNode.updateLayout(CGSize(width: maxWidth - insets.left - insets.right - 10.0, height: CGFloat.greatestFiniteMagnitude))
            contentWidth = max(contentWidth, textSize.width)
            contentHeight += textSize.height + titleSpacing
            lineNodes.append((textSize, textNode))
        }
        
        var linkTextLayout: TextNodeLayout?
        if let linkTextNode {
            let linkTextLayoutValue = linkTextNode.updateLayoutFullInfo(CGSize(width: maxWidth - insets.left - insets.right - 10.0, height: CGFloat.greatestFiniteMagnitude))
            linkTextLayout = linkTextLayoutValue
            contentHeight += businessLinkTextSpacing + linkTextLayoutValue.size.height + 20.0
        }
        
        let titleSize = self.titleNode.updateLayout(CGSize(width: contentWidth, height: CGFloat.greatestFiniteMagnitude))
        
        contentWidth = max(contentWidth, titleSize.width)
        
        contentHeight += titleSize.height + titleSpacing
        
        var imageAreaHeight: CGFloat = 0.0
        if let image = self.iconNode.image {
            imageAreaHeight += image.size.height
            imageAreaHeight += imageSpacing
            transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: insets.left + floor((contentWidth - image.size.width) / 2.0), y: insets.top), size: image.size))
        }
        
        let contentRect = CGRect(origin: CGPoint(x: insets.left, y: insets.top + imageAreaHeight), size: CGSize(width: contentWidth, height: contentHeight))
        
        let titleFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((contentRect.width - titleSize.width) / 2.0), y: contentRect.minY), size: titleSize)
        transition.updateFrame(node: self.titleNode, frame: titleFrame)
        
        var lineOffset = titleFrame.maxY + titleSpacing
        var isFirstLine = true
        for (textSize, textNode) in lineNodes {
            if isFirstLine {
                isFirstLine = false
            } else {
                lineOffset += 4.0
            }
            
            let isRTL = textNode.cachedLayout?.hasRTL ?? false
            transition.updateFrame(node: textNode, frame: CGRect(origin: CGPoint(x: isRTL ? contentRect.maxX - textSize.width : contentRect.minX, y: lineOffset), size: textSize))
            lineOffset += textSize.height
        }
        
        if let linkTextButton = self.linkTextButton, let linkTextNode = self.linkTextNode, let linkTextLayout {
            if isFirstLine {
                isFirstLine = false
            } else {
                lineOffset += businessLinkTextSpacing
            }
            
            let linkTextButtonFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((contentRect.width - linkTextLayout.size.width) * 0.5), y: lineOffset), size: linkTextLayout.size)
            let linkTextFrame = CGRect(origin: CGPoint(), size: linkTextButtonFrame.size)
            
            transition.updatePosition(node: linkTextButton, position: linkTextButtonFrame.center)
            transition.updateBounds(node: linkTextButton, bounds: CGRect(origin: CGPoint(), size: linkTextButtonFrame.size))
            transition.updateFrame(node: linkTextNode, frame: linkTextFrame)
            
            let linkTextHighlightNode: LinkHighlightingNode
            if let current = self.linkTextHighlightNode {
                linkTextHighlightNode = current
            } else {
                linkTextHighlightNode = LinkHighlightingNode(color: .black)
                linkTextHighlightNode.inset = 0.0
                linkTextHighlightNode.useModernPathCalculation = true
                self.linkTextHighlightNode = linkTextHighlightNode
                linkTextNode.supernode?.insertSubnode(linkTextHighlightNode, belowSubnode: linkTextNode)
            }
            
            let textLayout = linkTextLayout
            
            var labelRects = textLayout.linesRects()
            if labelRects.count > 1 {
                let sortedIndices = (0 ..< labelRects.count).sorted(by: { labelRects[$0].width > labelRects[$1].width })
                for i in 0 ..< sortedIndices.count {
                    let index = sortedIndices[i]
                    for j in -1 ... 1 {
                        if j != 0 && index + j >= 0 && index + j < sortedIndices.count {
                            if abs(labelRects[index + j].width - labelRects[index].width) < 16.0 {
                                labelRects[index + j].size.width = max(labelRects[index + j].width, labelRects[index].width)
                                labelRects[index].size.width = labelRects[index + j].size.width
                            }
                        }
                    }
                }
            }
            for i in 0 ..< labelRects.count {
                labelRects[i] = labelRects[i].insetBy(dx: -4.0, dy: 0.0)
                if i == 0 {
                    labelRects[i].origin.y -= 1.0
                    labelRects[i].size.height += 1.0
                }
                if i == labelRects.count - 1 {
                    labelRects[i].size.height += 1.0
                } else {
                    let deltaY = labelRects[i + 1].minY - labelRects[i].maxY
                    let topDelta = deltaY * 0.5 - 0.0
                    let bottomDelta = deltaY * 0.5 - 0.0
                    labelRects[i].size.height += topDelta
                    labelRects[i + 1].origin.y -= bottomDelta
                    labelRects[i + 1].size.height += bottomDelta
                }
                labelRects[i].origin.x = floor((textLayout.size.width - labelRects[i].width) / 2.0)
            }
            for i in 0 ..< labelRects.count {
                labelRects[i].origin.y -= 12.0
            }
            
            linkTextHighlightNode.innerRadius = 4.0
            linkTextHighlightNode.outerRadius = 4.0
            
            linkTextHighlightNode.updateRects(labelRects, color: interfaceState.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.1))
            
            linkTextHighlightNode.frame = linkTextFrame.offsetBy(dx: 0.0, dy: 0.0)
        } else {
            if let linkTextHighlightNode = self.linkTextHighlightNode {
                self.linkTextHighlightNode = nil
                linkTextHighlightNode.removeFromSupernode()
            }
        }
        
        return contentRect.insetBy(dx: -insets.left, dy: -insets.top).size
    }
}

public final class ChatEmptyNodeTopicChatContent: ASDisplayNode, ChatEmptyNodeContent, ASGestureRecognizerDelegate {
    private let context: AccountContext
    
    private let titleNode: ImmediateTextNode
    private let textNode: ImmediateTextNode
        
    private var currentTheme: PresentationTheme?
    private var currentStrings: PresentationStrings?
    
    private let iconView: ComponentView<Empty>
            
    public init(context: AccountContext) {
        self.context = context
        
        self.titleNode = ImmediateTextNode()
        self.titleNode.maximumNumberOfLines = 0
        self.titleNode.lineSpacing = 0.15
        self.titleNode.textAlignment = .center
        self.titleNode.isUserInteractionEnabled = false
        self.titleNode.displaysAsynchronously = false
        
        self.textNode = ImmediateTextNode()
        self.textNode.maximumNumberOfLines = 0
        self.textNode.lineSpacing = 0.15
        self.textNode.textAlignment = .center
        self.textNode.isUserInteractionEnabled = false
        self.textNode.displaysAsynchronously = false
        
        self.iconView = ComponentView<Empty>()
                
        super.init()
        
        self.addSubnode(self.titleNode)
        self.addSubnode(self.textNode)
    }
    
    public func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
        let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper)
        if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings {
            self.currentTheme = interfaceState.theme
            self.currentStrings = interfaceState.strings
            
            self.titleNode.attributedText = NSAttributedString(string: interfaceState.strings.Chat_EmptyTopicPlaceholder_Title, font: titleFont, textColor: serviceColor.primaryText)
            self.textNode.attributedText = NSAttributedString(string: interfaceState.strings.Chat_EmptyTopicPlaceholder_Text, font: messageFont, textColor: serviceColor.primaryText)
        }
        
        let inset: CGFloat
        if size.width == 320.0 {
            inset = 8.0
        } else  {
            inset = 15.0
        }
       
        let iconContent: EmojiStatusComponent.Content
        if let fileId = interfaceState.threadData?.icon {
            iconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 96.0, height: 96.0), placeholderColor: .clear, themeColor: serviceColor.primaryText, loopMode: .count(2))
        } else {
            let title = interfaceState.threadData?.title ?? ""
            let iconColor = interfaceState.threadData?.iconColor ?? 0
            iconContent = .topic(title: String(title.prefix(1)), color: iconColor, size: CGSize(width: 64.0, height: 64.0))
        }
        
        let insets = UIEdgeInsets(top: inset, left: inset, bottom: inset, right: inset)
        let titleSpacing: CGFloat = 6.0
        let iconSpacing: CGFloat = 9.0
        
        let iconSize = self.iconView.update(
            transition: .easeInOut(duration: 0.2),
            component: AnyComponent(EmojiStatusComponent(
                context: self.context,
                animationCache: self.context.animationCache,
                animationRenderer: self.context.animationRenderer,
                content: iconContent,
                isVisibleForAnimations: true,
                action: nil
            )),
            environment: {},
            containerSize: CGSize(width: 54.0, height: 54.0)
        )
                
        var contentWidth: CGFloat = 196.0
        var contentHeight: CGFloat = 0.0
                
        let titleSize = self.titleNode.updateLayout(CGSize(width: contentWidth, height: CGFloat.greatestFiniteMagnitude))
        let textSize = self.textNode.updateLayout(CGSize(width: contentWidth, height: CGFloat.greatestFiniteMagnitude))
        
        contentWidth = max(contentWidth, max(titleSize.width, textSize.width))
        
        contentHeight += titleSize.height + titleSpacing + textSize.height + iconSpacing + iconSize.height
        
        let contentRect = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: CGSize(width: contentWidth, height: contentHeight))
        
        let iconFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((contentRect.width - iconSize.width) / 2.0), y: contentRect.minY), size: iconSize)
        
        if let iconComponentView = self.iconView.view {
            if iconComponentView.superview == nil {
                self.view.addSubview(iconComponentView)
            }
            transition.updateFrame(view: iconComponentView, frame: iconFrame)
        }
        
        let titleFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((contentRect.width - titleSize.width) / 2.0), y: iconFrame.maxY + iconSpacing), size: titleSize)
        transition.updateFrame(node: self.titleNode, frame: titleFrame)
       
        let textFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((contentRect.width - textSize.width) / 2.0), y: titleFrame.maxY + titleSpacing), size: textSize)
        transition.updateFrame(node: self.textNode, frame: textFrame)
        
        return contentRect.insetBy(dx: -insets.left, dy: -insets.top).size
    }
}

public final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatEmptyNodeContent {
    private let isPremiumDisabled: Bool
    private let interaction: ChatPanelInterfaceInteraction?
    
    private let iconBackground: SimpleLayer
    private let icon =  ComponentView<Empty>()
    private let text = ComponentView<Empty>()
    private let buttonTitle = ComponentView<Empty>()
    private let button: HighlightTrackingButton
    private let buttonStarsNode: PremiumStarsNode
        
    private var currentTheme: PresentationTheme?
    private var currentStrings: PresentationStrings?
            
    public init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?) {
        let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
        self.isPremiumDisabled = premiumConfiguration.isPremiumDisabled
        
        self.interaction = interaction
        
        self.iconBackground = SimpleLayer()

        self.button = HighlightTrackingButton()
        self.button.clipsToBounds = true
        
        self.buttonStarsNode = PremiumStarsNode()
        self.buttonStarsNode.isUserInteractionEnabled = false
        
        super.init()
        
        self.layer.addSublayer(self.iconBackground)
        
        if !self.isPremiumDisabled {
            self.view.addSubview(self.button)
            
            self.button.addSubnode(self.buttonStarsNode)
            
            self.button.highligthedChanged = { [weak self] highlighted in
                guard let self else {
                    return
                }
                if highlighted {
                    self.button.layer.removeAnimation(forKey: "opacity")
                    self.button.alpha = 0.6
                } else {
                    self.button.alpha = 1.0
                    self.button.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
                }
            }
            self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
        }
    }
    
    @objc private func buttonPressed() {
        if let interaction = self.interaction {
            interaction.openPremiumRequiredForMessaging()
        }
    }
    
    public func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
        let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper)
        
        let maxWidth = min(200.0, size.width)
        
        let sideInset: CGFloat = 22.0
        let topInset: CGFloat = 16.0
        let bottomInset: CGFloat = 16.0
        let iconBackgroundSize: CGFloat = 120.0
        let iconTextSpacing: CGFloat = 16.0
        let textButtonSpacing: CGFloat = 12.0
        
        let peerTitle: String
        if let peer = interfaceState.renderedPeer?.chatMainPeer {
            peerTitle = EnginePeer(peer).compactDisplayTitle
        } else {
            peerTitle = " "
        }
        
        let text: String
        if self.isPremiumDisabled {
            text = interfaceState.strings.Chat_EmptyStateMessagingRestrictedToPremiumDisabled_Text(peerTitle).string
        } else {
            text = interfaceState.strings.Chat_EmptyStateMessagingRestrictedToPremium_Text(peerTitle).string
        }
        let textSize = self.text.update(
            transition: .immediate,
            component: AnyComponent(BalancedTextComponent(
                text: .markdown(text: text, attributes: MarkdownAttributes(
                    body: MarkdownAttributeSet(font: Font.regular(15.0), textColor: serviceColor.primaryText),
                    bold: MarkdownAttributeSet(font: Font.semibold(15.0), textColor: serviceColor.primaryText),
                    link: MarkdownAttributeSet(font: Font.regular(15.0), textColor: serviceColor.primaryText),
                    linkAttribute: { url in
                        return ("URL", url)
                    }
                )),
                horizontalAlignment: .center,
                maximumNumberOfLines: 0
            )),
            environment: {},
            containerSize: CGSize(width: maxWidth - sideInset * 2.0, height: 500.0)
        )
        
        let buttonTitleSize = self.buttonTitle.update(
            transition: .immediate,
            component: AnyComponent(MultilineTextComponent(
                text: .plain(NSAttributedString(string: interfaceState.strings.Chat_EmptyStateMessagingRestrictedToPremium_Action, font: Font.semibold(15.0), textColor: serviceColor.primaryText))
            )),
            environment: {},
            containerSize: CGSize(width: 200.0, height: 100.0)
        )
        let buttonSize = CGSize(width: buttonTitleSize.width + 20.0 * 2.0, height: buttonTitleSize.height + 9.0 * 2.0)
        
        var contentsWidth: CGFloat = 0.0
        contentsWidth = max(contentsWidth, iconBackgroundSize + sideInset * 2.0)
        contentsWidth = max(contentsWidth, textSize.width + sideInset * 2.0)
        
        if !self.isPremiumDisabled {
            contentsWidth = max(contentsWidth, buttonSize.width + sideInset * 2.0)
        }
        
        var contentsHeight: CGFloat = 0.0
        contentsHeight += topInset
        
        let iconBackgroundFrame = CGRect(origin: CGPoint(x: floor((contentsWidth - iconBackgroundSize) * 0.5), y: contentsHeight), size: CGSize(width: iconBackgroundSize, height: iconBackgroundSize))
        transition.updateFrame(layer: self.iconBackground, frame: iconBackgroundFrame)
        transition.updateCornerRadius(layer: self.iconBackground, cornerRadius: iconBackgroundSize * 0.5)
        self.iconBackground.backgroundColor = (interfaceState.theme.overallDarkAppearance ? UIColor(rgb: 0xffffff, alpha: 0.12) : UIColor(rgb: 0x000000, alpha: 0.12)).cgColor
        contentsHeight += iconBackgroundSize
        contentsHeight += iconTextSpacing
        
        let iconSize = self.icon.update(
            transition: .immediate,
            component: AnyComponent(
                LottieComponent(
                    content: LottieComponent.AppBundleContent(name: "PremiumRequired"),
                    color: serviceColor.primaryText,
                    size: CGSize(width: 120.0, height: 120.0),
                    loop: true
                )
            ),
            environment: {},
            containerSize: CGSize(width: maxWidth - sideInset * 2.0, height: 500.0)
        )
        let iconFrame = CGRect(origin: CGPoint(x: iconBackgroundFrame.minX + floor((iconBackgroundFrame.width - iconSize.width) * 0.5), y: iconBackgroundFrame.minY + floor((iconBackgroundFrame.height - iconSize.height) * 0.5)), size: iconSize)
        if let iconView = self.icon.view {
            if iconView.superview == nil {
                iconView.isUserInteractionEnabled = false
                self.view.addSubview(iconView)
            }
            iconView.frame = iconFrame
        }

        let textFrame = CGRect(origin: CGPoint(x: floor((contentsWidth - textSize.width) * 0.5), y: contentsHeight), size: textSize)
        if let textView = self.text.view {
            if textView.superview == nil {
                textView.isUserInteractionEnabled = false
                self.view.addSubview(textView)
            }
            textView.frame = textFrame
        }
        contentsHeight += textSize.height
        
        if self.isPremiumDisabled {
            contentsHeight += bottomInset
        } else {
            contentsHeight += textButtonSpacing
            
            let buttonFrame = CGRect(origin: CGPoint(x: floor((contentsWidth - buttonSize.width) * 0.5), y: contentsHeight), size: buttonSize)
            transition.updateFrame(view: self.button, frame: buttonFrame)
            transition.updateCornerRadius(layer: self.button.layer, cornerRadius: buttonFrame.height * 0.5)
            if let buttonTitleView = self.buttonTitle.view {
                if buttonTitleView.superview == nil {
                    buttonTitleView.isUserInteractionEnabled = false
                    self.button.addSubview(buttonTitleView)
                }
                transition.updateFrame(view: buttonTitleView, frame: CGRect(origin: CGPoint(x: floor((buttonSize.width - buttonTitleSize.width) * 0.5), y: floor((buttonSize.height - buttonTitleSize.height) * 0.5)), size: buttonTitleSize))
            }
            self.button.backgroundColor = interfaceState.theme.overallDarkAppearance ? UIColor(rgb: 0xffffff, alpha: 0.12) : UIColor(rgb: 0x000000, alpha: 0.12)
            self.buttonStarsNode.frame = CGRect(origin: CGPoint(), size: buttonSize)
            contentsHeight += buttonSize.height
            contentsHeight += bottomInset
        }
            
        
        return CGSize(width: contentsWidth, height: contentsHeight)
    }
}

private enum ChatEmptyNodeContentType: Equatable {
    case regular
    case secret
    case group
    case cloud
    case peerNearby
    case greeting
    case topic
    case premiumRequired
}

private final class EmptyAttachedDescriptionNode: HighlightTrackingButtonNode {
    private struct Params: Equatable {
        var theme: PresentationTheme
        var strings: PresentationStrings
        var chatWallpaper: TelegramWallpaper
        var peer: EnginePeer
        var constrainedSize: CGSize
        
        init(theme: PresentationTheme, strings: PresentationStrings, chatWallpaper: TelegramWallpaper, peer: EnginePeer, constrainedSize: CGSize) {
            self.theme = theme
            self.strings = strings
            self.chatWallpaper = chatWallpaper
            self.peer = peer
            self.constrainedSize = constrainedSize
        }
        
        static func ==(lhs: Params, rhs: Params) -> Bool {
            if lhs.theme !== rhs.theme {
                return false
            }
            if lhs.strings !== rhs.strings {
                return false
            }
            if lhs.chatWallpaper != rhs.chatWallpaper {
                return false
            }
            if lhs.constrainedSize != rhs.constrainedSize {
                return false
            }
            return true
        }
    }
    
    private struct Layout {
        var params: Params
        var size: CGSize
        
        init(params: Params, size: CGSize) {
            self.params = params
            self.size = size
        }
    }
    
    private let textNode: ImmediateTextNode
    private var backgroundContent: WallpaperBubbleBackgroundNode?
    private let textMaskNode: LinkHighlightingNode
    
    private let badgeTextNode: ImmediateTextNode
    private let badgeBackgroundView: UIImageView
    
    private var currentLayout: Layout?
    
    var action: (() -> Void)?
    
    override init(pointerStyle: PointerStyle? = nil) {
        self.textNode = ImmediateTextNode()
        self.textNode.textAlignment = .center
        self.textNode.maximumNumberOfLines = 0
        self.textNode.lineSpacing = 0.2
        
        self.textMaskNode = LinkHighlightingNode(color: .white)
        self.textMaskNode.inset = 0.0
        self.textMaskNode.useModernPathCalculation = false
        
        self.badgeTextNode = ImmediateTextNode()
        self.badgeBackgroundView = UIImageView()
        
        super.init(pointerStyle: pointerStyle)
        
        self.addSubnode(self.textNode)
        
        self.view.addSubview(self.badgeBackgroundView)
        self.addSubnode(self.badgeTextNode)
        
        self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside)
        
        self.highligthedChanged = { [weak self] highlighted in
            if let self, self.bounds.width > 0.0 {
                let animateScale = true
                
                let topScale: CGFloat = (self.bounds.width - 8.0) / self.bounds.width
                let maxScale: CGFloat = (self.bounds.width + 2.0) / self.bounds.width
                
                if highlighted {
                    self.layer.removeAnimation(forKey: "transform.scale")
                    
                    if animateScale {
                        let transition = ComponentTransition(animation: .curve(duration: 0.2, curve: .easeInOut))
                        transition.setScale(layer: self.layer, scale: topScale)
                    }
                } else {
                    if animateScale {
                        let transition = ComponentTransition(animation: .none)
                        transition.setScale(layer: self.layer, scale: 1.0)
                        
                        self.layer.animateScale(from: topScale, to: maxScale, duration: 0.13, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false, completion: { [weak self] _ in
                            guard let self else {
                                return
                            }
                            
                            self.layer.animateScale(from: maxScale, to: 1.0, duration: 0.1, timingFunction: CAMediaTimingFunctionName.easeIn.rawValue)
                        })
                    }
                }
            }
        }
    }
    
    @objc private func pressed() {
        self.action?()
    }
    
    func update(
        theme: PresentationTheme,
        strings: PresentationStrings,
        chatWallpaper: TelegramWallpaper,
        peer: EnginePeer,
        wallpaperBackgroundNode: WallpaperBackgroundNode?,
        constrainedSize: CGSize
    ) -> CGSize {
        let params = Params(
            theme: theme,
            strings: strings,
            chatWallpaper: chatWallpaper,
            peer: peer,
            constrainedSize: constrainedSize
        )
        if let currentLayout = self.currentLayout, currentLayout.params == params {
            return currentLayout.size
        } else {
            let size = self.updateInternal(params: params, wallpaperBackgroundNode: wallpaperBackgroundNode)
            self.currentLayout = Layout(params: params, size: size)
            return size
        }
    }
    
    private func updateInternal(params: Params, wallpaperBackgroundNode: WallpaperBackgroundNode?) -> CGSize {
        let serviceColor = serviceMessageColorComponents(theme: params.theme, wallpaper: params.chatWallpaper)
        
        let textString = NSMutableAttributedString()
        textString.append(NSAttributedString(string: params.strings.Chat_EmptyStateIntroFooter(params.peer.compactDisplayTitle).string, font: Font.regular(13.0), textColor: serviceColor.primaryText))
        textString.append(NSAttributedString(string: "  .\(params.strings.Chat_EmptyStateIntroFooterAction)", font: Font.regular(11.0), textColor: .clear))
        self.textNode.attributedText = textString
        
        let maxTextSize = CGSize(width: min(300.0, params.constrainedSize.width - 8.0 * 2.0), height: params.constrainedSize.height - 8.0 * 2.0)
        
        var bestSize: (availableWidth: CGFloat, info: TextNodeLayout)
        let info = self.textNode.updateLayoutFullInfo(maxTextSize)
        bestSize = (maxTextSize.width, info)
        if info.numberOfLines > 1 {
            let measureIncrement = 8.0
            var measureWidth = info.size.width
            measureWidth -= measureIncrement
            while measureWidth > 0.0 {
                let otherInfo = self.textNode.updateLayoutFullInfo(CGSize(width: measureWidth, height: maxTextSize.height))
                if otherInfo.numberOfLines > bestSize.info.numberOfLines {
                    break
                }
                if (otherInfo.size.width - otherInfo.trailingLineWidth) < (bestSize.info.size.width - bestSize.info.trailingLineWidth) {
                    bestSize = (measureWidth, otherInfo)
                }
                
                measureWidth -= measureIncrement
            }
            
            let bestInfo = self.textNode.updateLayoutFullInfo(CGSize(width: bestSize.availableWidth, height: maxTextSize.height))
            bestSize = (maxTextSize.width, bestInfo)
        }
        
        let textLayout = bestSize.info
        
        var labelRects = textLayout.linesRects()
        if labelRects.count > 1 {
            let sortedIndices = (0 ..< labelRects.count).sorted(by: { labelRects[$0].width > labelRects[$1].width })
            for i in 0 ..< sortedIndices.count {
                let index = sortedIndices[i]
                for j in -1 ... 1 {
                    if j != 0 && index + j >= 0 && index + j < sortedIndices.count {
                        if abs(labelRects[index + j].width - labelRects[index].width) < 16.0 {
                            labelRects[index + j].size.width = max(labelRects[index + j].width, labelRects[index].width)
                            labelRects[index].size.width = labelRects[index + j].size.width
                        }
                    }
                }
            }
        }
        for i in 0 ..< labelRects.count {
            labelRects[i] = labelRects[i].insetBy(dx: -6.0, dy: 0.0)
            if i == 0 {
                labelRects[i].origin.y -= 2.0
                labelRects[i].size.height += 2.0
            }
            if i == labelRects.count - 1 {
                labelRects[i].size.height += 3.0
            } else {
                let deltaYHalf = ceil((labelRects[i + 1].minY - labelRects[i].maxY) * 0.5)
                let topDelta = deltaYHalf + 0.0
                let bottomDelta = deltaYHalf - 0.0
                labelRects[i].size.height += topDelta
                labelRects[i + 1].origin.y -= bottomDelta
                labelRects[i + 1].size.height += bottomDelta
            }
            labelRects[i].origin.x = floor((textLayout.size.width - labelRects[i].width) / 2.0)
        }
        for i in 0 ..< labelRects.count {
            labelRects[i].origin.y -= 12.0
        }
        if !labelRects.isEmpty {
            self.textMaskNode.innerRadius = labelRects[0].height * 0.25
            self.textMaskNode.outerRadius = labelRects[0].height * 0.5
        }
        self.textMaskNode.updateRects(labelRects)
        
        let size = CGSize(width: textLayout.size.width + 4.0 * 2.0, height: textLayout.size.height + 4.0 * 2.0)
        let textFrame = CGRect(origin: CGPoint(x: 4.0, y: 4.0), size: textLayout.size)
        self.textNode.frame = textFrame
        
        self.badgeTextNode.attributedText = NSAttributedString(string: params.strings.Chat_EmptyStateIntroFooterAction, font: Font.regular(11.0), textColor: serviceColor.primaryText)
        let badgeTextSize = self.badgeTextNode.updateLayout(CGSize(width: 200.0, height: 100.0))
        if let lastLineFrame = labelRects.last {
            let badgeTextFrame = CGRect(origin: CGPoint(x: lastLineFrame.maxX - badgeTextSize.width - 3.0, y: textFrame.maxY - badgeTextSize.height - 3.0 - UIScreenPixel), size: badgeTextSize)
            self.badgeTextNode.frame = badgeTextFrame
            
            let badgeBackgroundFrame = badgeTextFrame.insetBy(dx: -4.0, dy: -1.0)
            if badgeBackgroundFrame.height != self.badgeBackgroundView.image?.size.height {
                self.badgeBackgroundView.image = generateStretchableFilledCircleImage(diameter: badgeBackgroundFrame.height, color: serviceColor.primaryText.withMultipliedAlpha(0.1))
            }
            self.badgeBackgroundView.frame = badgeBackgroundFrame
        }
        
        self.textMaskNode.frame = textFrame.offsetBy(dx: 3.0, dy: 0.0)
        
        if let wallpaperBackgroundNode {
            if self.backgroundContent == nil, let backgroundContent = wallpaperBackgroundNode.makeBubbleBackground(for: .free) {

                self.backgroundContent = backgroundContent
                backgroundContent.view.mask = self.textMaskNode.view
                self.insertSubnode(backgroundContent, at: 0)
            }
            
            if let backgroundContent = self.backgroundContent {
                backgroundContent.frame = CGRect(origin: CGPoint(x: -4.0, y: 0.0), size: CGSize(width: size.width + 4.0 * 2.0, height: size.height))
            }
        } else if let backgroundContent = self.backgroundContent {
            self.backgroundContent = nil
            backgroundContent.removeFromSupernode()
        }
        
        return size
    }
    
    func updateAbsolutePosition(rect: CGRect, containerSize: CGSize, transition: ContainedViewLayoutTransition) {
        guard let backgroundContent = self.backgroundContent else {
            return
        }
        var backgroundFrame = backgroundContent.frame
        backgroundFrame.origin.x += rect.minX
        backgroundFrame.origin.y += rect.minY
        backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: transition)
    }
}

public final class ChatEmptyNode: ASDisplayNode {
    public enum Subject {
        public enum EmptyType: Equatable {
            case generic
            case joined
            case clearedHistory
            case topic
            case botInfo
            case customGreeting(sticker: TelegramMediaFile?, title: String, text: String)
        }
        
        case emptyChat(EmptyType)
        case detailsPlaceholder
    }
    private let context: AccountContext
    private let interaction: ChatPanelInterfaceInteraction?
    
    private let backgroundNode: NavigationBackgroundNode
    
    private var wallpaperBackgroundNode: WallpaperBackgroundNode?
    private var backgroundContent: WallpaperBubbleBackgroundNode?
    
    private var absolutePosition: (CGRect, CGSize)?
    
    private var currentTheme: PresentationTheme?
    private var currentStrings: PresentationStrings?
    
    private var content: (ChatEmptyNodeContentType, ASDisplayNode & ChatEmptyNodeContent)?
    private var attachedDescriptionNode: EmptyAttachedDescriptionNode?
    
    public init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?) {
        self.context = context
        self.interaction = interaction
        
        self.backgroundNode = NavigationBackgroundNode(color: .clear)
        
        super.init()
        
        self.isUserInteractionEnabled = false
        
        self.addSubnode(self.backgroundNode)
    }
    
    override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        guard let result = super.hitTest(point, with: event) else {
            return nil
        }
        return result
    }
    
    public func animateFromLoadingNode(_ loadingNode: ChatLoadingNode) {
        guard let (_, node) = self.content else {
            return
        }
        
        let duration: Double = 0.3
        node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
        node.layer.animateScale(from: 0.01, to: 1.0, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
        
        let targetCornerRadius = self.backgroundNode.backgroundCornerRadius
        let targetFrame = self.backgroundNode.frame
        let initialFrame = loadingNode.convert(loadingNode.progressFrame, to: self)
        
        let transition = ContainedViewLayoutTransition.animated(duration: duration, curve: .spring)
        self.backgroundNode.layer.animateFrame(from: initialFrame, to: targetFrame, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
        self.backgroundNode.update(size: initialFrame.size, cornerRadius: initialFrame.size.width / 2.0, transition: .immediate)
        self.backgroundNode.update(size: targetFrame.size, cornerRadius: targetCornerRadius, transition: transition)
        
        if let backgroundContent = self.backgroundContent {
            backgroundContent.layer.animateFrame(from: initialFrame, to: targetFrame, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
            backgroundContent.cornerRadius = initialFrame.size.width / 2.0
            transition.updateCornerRadius(layer: backgroundContent.layer, cornerRadius: targetCornerRadius)
        }
        
        if let attachedDescriptionNode = self.attachedDescriptionNode {
            attachedDescriptionNode.layer.animatePosition(from: initialFrame.center, to: attachedDescriptionNode.position, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
            attachedDescriptionNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
            attachedDescriptionNode.layer.animateScale(from: 0.001, to: 1.0, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
        }
    }
    
    public func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: Subject, loadingNode: ChatLoadingNode?, backgroundNode: WallpaperBackgroundNode?, size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) {
        self.wallpaperBackgroundNode = backgroundNode
        
        if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings {
            self.currentTheme = interfaceState.theme
            self.currentStrings = interfaceState.strings

            self.backgroundNode.updateColor(color: selectDateFillStaticColor(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper), enableBlur: self.context.sharedContext.energyUsageSettings.fullTranslucency && dateFillNeedsBlur(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper), transition: .immediate)
        }
    
        var isScheduledMessages = false
        if case .scheduledMessages = interfaceState.subject {
            isScheduledMessages = true
        }
        
        let contentType: ChatEmptyNodeContentType
        var displayAttachedDescription = false
        switch subject {
        case .detailsPlaceholder:
            contentType = .regular
        case let .emptyChat(emptyType):
            if case .customGreeting = emptyType {
                contentType = .greeting
            } else if case .customChatContents = interfaceState.subject {
                contentType = .cloud
            } else if case .replyThread = interfaceState.chatLocation {
                if case .topic = emptyType {
                    contentType = .topic
                } else {
                    contentType = .regular
                }
            } else if let peer = interfaceState.renderedPeer?.peer, !isScheduledMessages {
                 if peer.id == self.context.account.peerId {
                    contentType = .cloud
                } else if let _ = peer as? TelegramSecretChat {
                    contentType = .secret
                } else if let group = peer as? TelegramGroup, case .creator = group.role {
                    contentType = .group
                } else if let channel = peer as? TelegramChannel, case .group = channel.info, channel.flags.contains(.isCreator) && !channel.flags.contains(.isGigagroup) {
                    contentType = .group
                } else if let _ = interfaceState.peerNearbyData {
                    contentType = .peerNearby
                } else if let peer = peer as? TelegramUser {
                    if interfaceState.isPremiumRequiredForMessaging {
                        contentType = .premiumRequired
                    } else {
                        if peer.isDeleted || peer.botInfo != nil || peer.flags.contains(.isSupport) || peer.isScam || interfaceState.peerIsBlocked {
                            contentType = .regular
                        } else {
                            contentType = .greeting
                            if interfaceState.businessIntro != nil {
                                displayAttachedDescription = true
                            }
                        }
                    }
                } else {
                    contentType = .regular
                }
            } else {
                contentType = .regular
            }
        }
      
        var updateGreetingSticker = false
        var contentTransition = transition
        if self.content?.0 != contentType {
            var animateContentIn = false
            if let node = self.content?.1 {
                node.removeFromSupernode()
                if self.content?.0 != nil, case .greeting = contentType, transition.isAnimated {
                    animateContentIn = true
                }
            }
            let node: ASDisplayNode & ChatEmptyNodeContent
            switch contentType {
            case .regular:
                node = ChatEmptyNodeRegularChatContent()
            case .secret:
                node = ChatEmptyNodeSecretChatContent()
            case .group:
                node = ChatEmptyNodeGroupChatContent()
            case .cloud:
                let cloudNode = ChatEmptyNodeCloudChatContent()
                node = cloudNode
                cloudNode.shareBusinessLink = { [weak self] url in
                    guard let self, let interfaceInteraction = self.interaction else {
                        return
                    }
                    
                    UIPasteboard.general.string = url
                    
                    let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 })
                    
                    let controller = UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.GroupInfo_InviteLink_CopyAlert_Success), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { _ in
                        return false
                    })
                    interfaceInteraction.presentControllerInCurrent(controller, nil)
                }
            case .peerNearby:
                node = ChatEmptyNodeNearbyChatContent(context: self.context, interaction: self.interaction)
            case .greeting:
                node = ChatEmptyNodeGreetingChatContent(context: self.context, interaction: self.interaction)
                updateGreetingSticker = true
            case .topic:
                node = ChatEmptyNodeTopicChatContent(context: self.context)
            case .premiumRequired:
                node = ChatEmptyNodePremiumRequiredChatContent(context: self.context, interaction: self.interaction)
            }
            self.content = (contentType, node)
            self.addSubnode(node)
            contentTransition = .immediate
            
            if animateContentIn, case let .animated(duration, curve) = transition {
                node.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
                node.layer.animateScale(from: 0.0, to: 1.0, duration: duration, timingFunction: curve.timingFunction)
            }
        }
        self.isUserInteractionEnabled = [.peerNearby, .greeting, .premiumRequired, .cloud].contains(contentType)
        
        let displayRect = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom))
        
        var contentSize = CGSize()
        if let contentNode = self.content?.1 {
            contentSize = contentNode.updateLayout(interfaceState: interfaceState, subject: subject, size: displayRect.size, transition: contentTransition)
            
            if updateGreetingSticker {
                self.context.prefetchManager?.prepareNextGreetingSticker()
            }
        }
        
        let contentFrame = CGRect(origin: CGPoint(x: displayRect.minX + floor((displayRect.width - contentSize.width) / 2.0), y: displayRect.minY + floor((displayRect.height - contentSize.height) / 2.0)), size: contentSize)
        if let contentNode = self.content?.1 {
            contentTransition.updateFrame(node: contentNode, frame: contentFrame)
        }
        
        transition.updateFrame(node: self.backgroundNode, frame: contentFrame)
        self.backgroundNode.update(size: self.backgroundNode.bounds.size, cornerRadius: min(20.0, self.backgroundNode.bounds.height / 2.0), transition: transition)
        
        if displayAttachedDescription, let peer = interfaceState.renderedPeer?.chatMainPeer {
            let isPremium = interfaceState.isPremium
            let attachedDescriptionNode: EmptyAttachedDescriptionNode
            if let current = self.attachedDescriptionNode {
                attachedDescriptionNode = current
            } else {
                attachedDescriptionNode = EmptyAttachedDescriptionNode()
                self.attachedDescriptionNode = attachedDescriptionNode
                self.addSubnode(attachedDescriptionNode)
                
                let strings = interfaceState.strings
                
                attachedDescriptionNode.action = { [weak self] in
                    guard let self else {
                        return
                    }
                    
                    let context = self.context
                    var replaceImpl: ((ViewController) -> Void)?
                    var dismissImpl: (() -> Void)?
                    let controller = PremiumLimitsListScreen(context: context, subject: .business, source: .other, order: [.business], buttonText: strings.Chat_EmptyStateIntroFooterPremiumActionButton, isPremium: false, forceDark: false)
                    controller.action = {
                        if isPremium {
                            dismissImpl?()
                        } else {
                            let controller = PremiumIntroScreen(context: context, source: .settings, forceDark: false)
                            replaceImpl?(controller)
                        }
                    }
                    replaceImpl = { [weak self, weak controller] c in
                        controller?.dismiss(animated: true, completion: {
                            guard let self else {
                                return
                            }
                            self.interaction?.chatController()?.push(c)
                        })
                    }
                    dismissImpl = { [weak controller] in
                        controller?.dismiss(animated: true, completion: {
                        })
                    }
                    self.interaction?.chatController()?.push(controller)
                }
            }
            
            let attachedDescriptionSize = attachedDescriptionNode.update(
                theme: interfaceState.theme,
                strings: interfaceState.strings,
                chatWallpaper: interfaceState.chatWallpaper,
                peer: EnginePeer(peer),
                wallpaperBackgroundNode: backgroundNode,
                constrainedSize: CGSize(width: size.width - insets.left - insets.right, height: 200.0)
            )
            let attachedDescriptionFrame = CGRect(origin: CGPoint(x: floor((size.width - attachedDescriptionSize.width) * 0.5), y: contentFrame.maxY + 4.0), size: attachedDescriptionSize)
            transition.updateFrame(node: attachedDescriptionNode, frame: attachedDescriptionFrame)
            
            if let (rect, containerSize) = self.absolutePosition {
                var backgroundFrame = attachedDescriptionNode.frame
                backgroundFrame.origin.x += rect.minX
                backgroundFrame.origin.y += rect.minY
                attachedDescriptionNode.updateAbsolutePosition(rect: backgroundFrame, containerSize: containerSize, transition: .immediate)
            }
        } else if let attachedDescriptionNode = self.attachedDescriptionNode {
            self.attachedDescriptionNode = nil
            attachedDescriptionNode.removeFromSupernode()
        }
    
        if backgroundNode?.hasExtraBubbleBackground() == true {
            if self.backgroundContent == nil, let backgroundContent = backgroundNode?.makeBubbleBackground(for: .free) {
                backgroundContent.clipsToBounds = true

                self.backgroundContent = backgroundContent
                self.insertSubnode(backgroundContent, at: 0)
            }
        } else {
            self.backgroundContent?.removeFromSupernode()
            self.backgroundContent = nil
        }
        
        if let backgroundContent = self.backgroundContent {
            self.backgroundNode.isHidden = true
            backgroundContent.cornerRadius = min(20.0, self.backgroundNode.bounds.height / 2.0)            
            transition.updateFrame(node: backgroundContent, frame: contentFrame)

            if let (rect, containerSize) = self.absolutePosition {
                var backgroundFrame = backgroundContent.frame
                backgroundFrame.origin.x += rect.minX
                backgroundFrame.origin.y += rect.minY
                backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate)
            }
        } else {
            self.backgroundNode.isHidden = false
        }
        
        if let loadingNode = loadingNode {
            self.animateFromLoadingNode(loadingNode)
        }
    }
    
    
    public func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition = .immediate) {
        self.absolutePosition = (rect, containerSize)
        if let backgroundContent = self.backgroundContent {
            var backgroundFrame = backgroundContent.frame
            backgroundFrame.origin.x += rect.minX
            backgroundFrame.origin.y += rect.minY
            backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: transition)
        }
        
        if let attachedDescriptionNode = self.attachedDescriptionNode {
            var backgroundFrame = attachedDescriptionNode.frame
            backgroundFrame.origin.x += rect.minX
            backgroundFrame.origin.y += rect.minY
            attachedDescriptionNode.updateAbsolutePosition(rect: backgroundFrame, containerSize: containerSize, transition: transition)
        }
    }
}