import Foundation
import UIKit
import Display
import AsyncDisplayKit
import SwiftSignalKit
import Postbox
import TelegramCore
import TelegramPresentationData
import TextFormat
import UrlEscaping
import PhotoResources
import AccountContext
import WallpaperBackgroundNode
import ChatControllerInteraction
import ChatMessageBubbleContentNode
import CountrySelectionUI
import TelegramStringFormatting
import MergedAvatarsNode
import ChatControllerInteraction
import TextNodeWithEntities

public final class ChatUserInfoItem: ListViewItem {
    fileprivate let peer: EnginePeer
    fileprivate let verification: PeerVerification?
    fileprivate let registrationDate: String?
    fileprivate let phoneCountry: String?
    fileprivate let groupsInCommonCount: Int32
    fileprivate let controllerInteraction: ChatControllerInteraction
    fileprivate let presentationData: ChatPresentationData
    fileprivate let context: AccountContext
    
    public init(
        peer: EnginePeer,
        verification: PeerVerification?,
        registrationDate: String?,
        phoneCountry: String?,
        groupsInCommonCount: Int32,
        controllerInteraction: ChatControllerInteraction,
        presentationData: ChatPresentationData,
        context: AccountContext
    ) {
        self.peer = peer
        self.verification = verification
        self.registrationDate = registrationDate
        self.phoneCountry = phoneCountry
        self.groupsInCommonCount = groupsInCommonCount
        self.controllerInteraction = controllerInteraction
        self.presentationData = presentationData
        self.context = context
    }
    
    public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
        let configure = {
            let node = ChatUserInfoItemNode()
            
            let nodeLayout = node.asyncLayout()
            let (layout, apply) = nodeLayout(self, params)
            
            node.contentSize = layout.contentSize
            node.insets = layout.insets
            
            Queue.mainQueue().async {
                completion(node, {
                    return (nil, { _ in apply(.None) })
                })
            }
        }
        if Thread.isMainThread {
            async {
                configure()
            }
        } else {
            configure()
        }
    }
    
    public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
        Queue.mainQueue().async {
            if let nodeValue = node() as? ChatUserInfoItemNode {
                let nodeLayout = nodeValue.asyncLayout()
                
                async {
                    let (layout, apply) = nodeLayout(self, params)
                    Queue.mainQueue().async {
                        completion(layout, { _ in
                            apply(animation)
                        })
                    }
                }
            }
        }
    }
}

public final class ChatUserInfoItemNode: ListViewItemNode, ASGestureRecognizerDelegate {
    public var controllerInteraction: ChatControllerInteraction?
    
    public let offsetContainer: ASDisplayNode
    public let titleNode: TextNode
    public let subtitleNode: TextNode
    
    private let registrationDateTitleTextNode: TextNode
    private let registrationDateValueTextNode: TextNode
    private var registrationDateText: String?
    
    private let phoneCountryTitleTextNode: TextNode
    private let phoneCountryValueTextNode: TextNode
    private var phoneCountryText: String?
    
    private let groupsTitleTextNode: TextNode
    private let groupsValueTextNode: TextNode
    private let groupsButtonNode: HighlightTrackingButtonNode
    private let groupsAvatarsNode: MergedAvatarsNode
    private let groupsArrowNode: ASImageNode
    
    private var groupsInCommonContext: GroupsInCommonContext?
    private var groupsInCommonDisposable: Disposable?
    private var groupsInCommon: [Peer] = []
    
    private let disclaimerTextNode: TextNodeWithEntities
    
    private var theme: ChatPresentationThemeData?
    
    private var wallpaperBackgroundNode: WallpaperBackgroundNode?
    private var backgroundContent: WallpaperBubbleBackgroundNode?
    
    private var absolutePosition: (CGRect, CGSize)?
    
    private var item: ChatUserInfoItem?
    
    public init() {
        self.offsetContainer = ASDisplayNode()
        
        self.titleNode = TextNode()
        self.titleNode.isUserInteractionEnabled = false
        self.titleNode.displaysAsynchronously = false
        
        self.subtitleNode = TextNode()
        self.subtitleNode.isUserInteractionEnabled = false
        self.subtitleNode.displaysAsynchronously = false
        
        self.registrationDateTitleTextNode = TextNode()
        self.registrationDateTitleTextNode.isUserInteractionEnabled = false
        self.registrationDateTitleTextNode.displaysAsynchronously = false
        self.registrationDateValueTextNode = TextNode()
        self.registrationDateValueTextNode.isUserInteractionEnabled = false
        self.registrationDateValueTextNode.displaysAsynchronously = false
        
        self.phoneCountryTitleTextNode = TextNode()
        self.phoneCountryTitleTextNode.isUserInteractionEnabled = false
        self.phoneCountryTitleTextNode.displaysAsynchronously = false
        self.phoneCountryValueTextNode = TextNode()
        self.phoneCountryValueTextNode.isUserInteractionEnabled = false
        self.phoneCountryValueTextNode.displaysAsynchronously = false
        
        self.groupsTitleTextNode = TextNode()
        self.groupsTitleTextNode.isUserInteractionEnabled = false
        self.groupsTitleTextNode.displaysAsynchronously = false
        self.groupsValueTextNode = TextNode()
        self.groupsValueTextNode.isUserInteractionEnabled = false
        self.groupsValueTextNode.displaysAsynchronously = false
        
        self.groupsAvatarsNode = MergedAvatarsNode()
        
        self.groupsArrowNode = ASImageNode()
        self.groupsArrowNode.displaysAsynchronously = false
        
        self.groupsButtonNode = HighlightTrackingButtonNode()
        
        self.disclaimerTextNode = TextNodeWithEntities()
        self.disclaimerTextNode.textNode.isUserInteractionEnabled = false
        self.disclaimerTextNode.textNode.displaysAsynchronously = false
        
        super.init(layerBacked: false, dynamicBounce: true, rotated: true)
        
        self.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
        
        self.addSubnode(self.offsetContainer)
        self.offsetContainer.addSubnode(self.titleNode)
        self.offsetContainer.addSubnode(self.subtitleNode)
        self.offsetContainer.addSubnode(self.disclaimerTextNode.textNode)
        self.offsetContainer.addSubnode(self.groupsAvatarsNode)
        self.offsetContainer.addSubnode(self.groupsArrowNode)
        self.offsetContainer.addSubnode(self.groupsButtonNode)
        self.wantsTrailingItemSpaceUpdates = true
        
        self.groupsButtonNode.highligthedChanged = { [weak self] highlighted in
            if let self {
                if highlighted {
                    self.groupsValueTextNode.layer.removeAnimation(forKey: "opacity")
                    self.groupsValueTextNode.alpha = 0.4
                    
                    self.groupsAvatarsNode.layer.removeAnimation(forKey: "opacity")
                    self.groupsAvatarsNode.alpha = 0.4
                    
                    self.groupsArrowNode.layer.removeAnimation(forKey: "opacity")
                    self.groupsArrowNode.alpha = 0.4
                } else {
                    self.groupsValueTextNode.alpha = 1.0
                    self.groupsValueTextNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
                    
                    self.groupsAvatarsNode.alpha = 1.0
                    self.groupsAvatarsNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
                    
                    self.groupsArrowNode.alpha = 1.0
                    self.groupsArrowNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
                }
            }
        }
        self.groupsButtonNode.addTarget(self, action: #selector(self.groupsPressed), forControlEvents: .touchUpInside)
    }
                
    override public func didLoad() {
        super.didLoad()
        
        let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
        tapRecognizer.delegate = self.wrappedGestureRecognizerDelegate
        self.offsetContainer.view.addGestureRecognizer(tapRecognizer)
    }
    
    public override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        if gestureRecognizer.view === self.offsetContainer.view {
            let location = gestureRecognizer.location(in: self.offsetContainer.view)
            if let backgroundContent = self.backgroundContent, backgroundContent.frame.contains(location) {
                return true
            }
            return false
        }
        return true
    }
    
    @objc private func tapGesture(_ gestureRecognizer: UITapGestureRecognizer) {
        guard let item = self.item else {
            return
        }
        item.controllerInteraction.openPeer(item.peer, .info(nil), nil, .default)
    }
    
    override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
        super.updateAbsoluteRect(rect, within: containerSize)
        
        self.absolutePosition = (rect, containerSize)
        if let backgroundContent = self.backgroundContent {
            var backgroundFrame = backgroundContent.frame
            backgroundFrame.origin.x += rect.minX
            backgroundFrame.origin.y += containerSize.height - rect.minY
            backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate)
        }
    }
    
    @objc private func groupsPressed() {
        guard let item = self.item else {
            return
        }
        item.controllerInteraction.openPeer(item.peer, .info(ChatControllerInteractionNavigateToPeer.InfoParams(switchToGroupsInCommon: true)), nil, .default)
    }
    
    public func asyncLayout() -> (_ item: ChatUserInfoItem, _ width: ListViewItemLayoutParams) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) {
        let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
        let makeSubtitleLayout = TextNode.asyncLayout(self.subtitleNode)
        let makeRegistrationDateTitleLayout = TextNode.asyncLayout(self.registrationDateTitleTextNode)
        let makeRegistrationDateValueLayout = TextNode.asyncLayout(self.registrationDateValueTextNode)
        let makePhoneCountryTitleLayout = TextNode.asyncLayout(self.phoneCountryTitleTextNode)
        let makePhoneCountryValueLayout = TextNode.asyncLayout(self.phoneCountryValueTextNode)
        let makeGroupsTitleLayout = TextNode.asyncLayout(self.groupsTitleTextNode)
        let makeGroupsValueLayout = TextNode.asyncLayout(self.groupsValueTextNode)
        let makeDisclaimerLayout = TextNodeWithEntities.asyncLayout(self.disclaimerTextNode)

        let currentItem = self.item
        let currentRegistrationDateText = self.registrationDateText
        let currentPhoneCountryText = self.phoneCountryText
        
        return { [weak self] item, params in
            let themeUpdated = item.presentationData.theme !== currentItem?.presentationData.theme
                            
            var backgroundSize = CGSize(width: 240.0, height: 0.0)
            
            let verticalItemInset: CGFloat = 10.0
            let horizontalInset: CGFloat = 10.0 + params.leftInset
            let horizontalContentInset: CGFloat = 16.0
            let verticalInset: CGFloat = 17.0
            let verticalSpacing: CGFloat = 6.0
            let paragraphSpacing: CGFloat = 3.0
            let attributeSpacing: CGFloat = 10.0
            
            let primaryTextColor = serviceMessageColorComponents(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper).primaryText
            let subtitleColor = primaryTextColor.withAlphaComponent(item.presentationData.theme.theme.overallDarkAppearance ? 0.7 : 0.8)
            
            backgroundSize.height += verticalInset
            
            let constrainedWidth = params.width - (horizontalInset + horizontalContentInset) * 2.0
            let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.peer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) + item.peer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) + item.peer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder), font: Font.semibold(15.0), textColor: primaryTextColor), backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: constrainedWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
            backgroundSize.height += titleLayout.size.height
            backgroundSize.height += verticalSpacing
            
            let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Chat_NonContactUser_Subtitle, font: Font.regular(13.0), textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: constrainedWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
            backgroundSize.height += subtitleLayout.size.height
            backgroundSize.height += verticalSpacing + paragraphSpacing
            
            let infoConstrainedSize = CGSize(width: floor(constrainedWidth * 0.7), height: CGFloat.greatestFiniteMagnitude)
            
            var maxTitleWidth: CGFloat = 0.0
            var maxValueWidth: CGFloat = 0.0
            
            var phoneCountryText: String?
            let phoneCountryTitleLayoutAndApply: (TextNodeLayout, () -> TextNode)?
            let phoneCountryValueLayoutAndApply: (TextNodeLayout, () -> TextNode)?
            if let phoneCountry = item.phoneCountry {
                if let currentPhoneCountryText {
                    phoneCountryText = currentPhoneCountryText
                } else {
                    var countryName = ""
                    let countriesConfiguration = item.context.currentCountriesConfiguration.with { $0 }
                    if phoneCountry == "FT" {
                        countryName = item.presentationData.strings.Chat_NonContactUser_AnonymousNumber
                    } else if let country = countriesConfiguration.countries.first(where: { $0.id == phoneCountry }) {
                        countryName = country.localizedName ?? country.name
                    } else if phoneCountry == "TS" {
                        countryName = "Test"
                    }
                    phoneCountryText = emojiFlagForISOCountryCode(phoneCountry) + " " + countryName
                }
                phoneCountryTitleLayoutAndApply = makePhoneCountryTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Chat_NonContactUser_PhoneNumber, font: Font.regular(13.0), textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: infoConstrainedSize, alignment: .center, cutout: nil, insets: UIEdgeInsets()))
                phoneCountryValueLayoutAndApply = makePhoneCountryValueLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: phoneCountryText ?? "", font: Font.semibold(13.0), textColor: primaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: infoConstrainedSize, alignment: .center, cutout: nil, insets: UIEdgeInsets()))
                backgroundSize.height += verticalSpacing
                backgroundSize.height += phoneCountryValueLayoutAndApply?.0.size.height ?? 0
                
                maxTitleWidth = max(maxTitleWidth, (phoneCountryTitleLayoutAndApply?.0.size.width ?? 0))
                maxValueWidth = max(maxValueWidth, (phoneCountryValueLayoutAndApply?.0.size.width ?? 0))
            } else {
                phoneCountryTitleLayoutAndApply = nil
                phoneCountryValueLayoutAndApply = nil
            }
            
            var registrationDateText: String?
            let registrationDateTitleLayoutAndApply: (TextNodeLayout, () -> TextNode)?
            let registrationDateValueLayoutAndApply: (TextNodeLayout, () -> TextNode)?
            if let registrationDate = item.registrationDate {
                if let currentRegistrationDateText {
                    registrationDateText = currentRegistrationDateText
                } else {
                    let components = registrationDate.components(separatedBy: ".")
                    if components.count == 2, let first = Int32(components[0]), let second = Int32(components[1]) {
                        let month = first - 1
                        let year = second - 1900
                        registrationDateText = stringForMonth(strings: item.presentationData.strings, month: month, ofYear: year)
                    } else {
                        registrationDateText = ""
                    }
                }
                registrationDateTitleLayoutAndApply = makeRegistrationDateTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Chat_NonContactUser_Registration, font: Font.regular(13.0), textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: infoConstrainedSize, alignment: .center, cutout: nil, insets: UIEdgeInsets()))
                registrationDateValueLayoutAndApply = makeRegistrationDateValueLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: registrationDateText ?? "", font: Font.semibold(13.0), textColor: primaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: infoConstrainedSize, alignment: .center, cutout: nil, insets: UIEdgeInsets()))
                backgroundSize.height += verticalSpacing
                backgroundSize.height += registrationDateValueLayoutAndApply?.0.size.height ?? 0
                
                maxTitleWidth = max(maxTitleWidth, (registrationDateTitleLayoutAndApply?.0.size.width ?? 0))
                maxValueWidth = max(maxValueWidth, (registrationDateValueLayoutAndApply?.0.size.width ?? 0))
            } else {
                registrationDateTitleLayoutAndApply = nil
                registrationDateValueLayoutAndApply = nil
            }
            
            let avatarImageSize: CGFloat = 18.0
            let avatarSpacing: CGFloat = 9.0
            let avatarBorder: CGFloat = 1.0
                        
            let groupsValueText: NSMutableAttributedString
            let groupsInCommonCount = item.groupsInCommonCount
            var estimatedValueOffset: CGFloat = 0.0
            if groupsInCommonCount > 0 {
                groupsValueText = NSMutableAttributedString(string: item.presentationData.strings.Chat_NonContactUser_GroupsCount(groupsInCommonCount), font: Font.semibold(13.0), textColor: primaryTextColor)
                estimatedValueOffset = avatarImageSize + CGFloat(min(2, max(0, item.groupsInCommonCount - 1))) * avatarSpacing + 4.0 + 10.0
            } else {
                groupsValueText = NSMutableAttributedString(string: "", font: Font.semibold(13.0), textColor: primaryTextColor)
            }
                
            let groupsTitleLayoutAndApply: (TextNodeLayout, () -> TextNode)?
            let groupsValueLayoutAndApply: (TextNodeLayout, () -> TextNode)?
            if !groupsValueText.string.isEmpty {
                groupsTitleLayoutAndApply = makeGroupsTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Chat_NonContactUser_Groups, font: Font.regular(13.0), textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: infoConstrainedSize, alignment: .center, cutout: nil, insets: UIEdgeInsets()))
                groupsValueLayoutAndApply = makeGroupsValueLayout(TextNodeLayoutArguments(attributedString: groupsValueText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: infoConstrainedSize, alignment: .center, cutout: nil, insets: UIEdgeInsets()))
                backgroundSize.height += verticalSpacing
                backgroundSize.height += groupsValueLayoutAndApply?.0.size.height ?? 0.0
                
                maxTitleWidth = max(maxTitleWidth, groupsTitleLayoutAndApply?.0.size.width ?? 0)
                maxValueWidth = max(maxValueWidth, (groupsValueLayoutAndApply?.0.size.width ?? 0) + estimatedValueOffset)
            } else {
                groupsTitleLayoutAndApply = nil
                groupsValueLayoutAndApply = nil
            }

            backgroundSize.width = horizontalContentInset * 2.0 + max(titleLayout.size.width, maxTitleWidth + attributeSpacing + maxValueWidth)
            
            let disclaimerText: NSMutableAttributedString
            if let verification = item.verification {
                disclaimerText = NSMutableAttributedString(string: " #  \(verification.description)", font: Font.regular(13.0), textColor: subtitleColor)
                if let range = disclaimerText.string.range(of: "#") {
                    disclaimerText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: verification.iconFileId, file: nil), range: NSRange(range, in: disclaimerText.string))
                    disclaimerText.addAttribute(.foregroundColor, value: subtitleColor, range: NSRange(range, in: disclaimerText.string))
                    disclaimerText.addAttribute(.baselineOffset, value: 2.0, range: NSRange(range, in: disclaimerText.string))
                }
            } else {
                disclaimerText = NSMutableAttributedString(string: " #   \(item.presentationData.strings.Chat_NonContactUser_Disclaimer)", font: Font.regular(13.0), textColor: subtitleColor)
                if let range = disclaimerText.string.range(of: "#") {
                    disclaimerText.addAttribute(.attachment, value: PresentationResourcesChat.chatUserInfoWarningIcon(item.presentationData.theme.theme)!, range: NSRange(range, in: disclaimerText.string))
                    disclaimerText.addAttribute(.foregroundColor, value: subtitleColor, range: NSRange(range, in: disclaimerText.string))
                    disclaimerText.addAttribute(.baselineOffset, value: 2.0, range: NSRange(range, in: disclaimerText.string))
                }
            }
            
            let (disclaimerLayout, disclaimerApply) = makeDisclaimerLayout(TextNodeLayoutArguments(attributedString: disclaimerText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: backgroundSize.width - horizontalContentInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
            backgroundSize.height += verticalSpacing * 2.0 + paragraphSpacing
            backgroundSize.height += disclaimerLayout.size.height
            
            backgroundSize.height += verticalInset
         
            let backgroundFrame = CGRect(origin: CGPoint(x: floor((params.width - backgroundSize.width) / 2.0), y: verticalItemInset + 4.0), size: backgroundSize)

            let itemLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: backgroundSize.height + verticalItemInset * 2.0), insets: UIEdgeInsets())
            return (itemLayout, { _ in
                if let strongSelf = self {
                    strongSelf.item = item
                    strongSelf.theme = item.presentationData.theme
                    
                    if themeUpdated {
                        strongSelf.groupsArrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Contact List/SubtitleArrow"), color: primaryTextColor)
                    }
                                        
                    if item.groupsInCommonCount > 0 {
                        if strongSelf.groupsInCommonContext == nil {
                            let groupsInCommonContext = GroupsInCommonContext(account: item.context.account, peerId: item.peer.id)
                            strongSelf.groupsInCommonContext = groupsInCommonContext
                            strongSelf.groupsInCommonDisposable = (groupsInCommonContext.state
                            |> deliverOnMainQueue).start(next: { [weak self] state in
                                guard let self, let item = self.item else {
                                    return
                                }
                                self.groupsInCommon = Array(state.peers.compactMap { $0.peer }.prefix(3))
                                self.groupsAvatarsNode.update(context: item.context, peers: self.groupsInCommon, synchronousLoad: true, imageSize: avatarImageSize, imageSpacing: avatarSpacing, borderWidth: avatarBorder)
                            })
                        }
                    }
                    
                    if item.presentationData.theme.theme.overallDarkAppearance {
                        strongSelf.registrationDateTitleTextNode.layer.compositingFilter = nil
                        strongSelf.phoneCountryTitleTextNode.layer.compositingFilter = nil
                        strongSelf.groupsTitleTextNode.layer.compositingFilter = nil
                        strongSelf.subtitleNode.layer.compositingFilter = nil
                        strongSelf.disclaimerTextNode.textNode.layer.compositingFilter = nil
                    } else {
                        strongSelf.registrationDateTitleTextNode.layer.compositingFilter = "overlayBlendMode"
                        strongSelf.phoneCountryTitleTextNode.layer.compositingFilter = "overlayBlendMode"
                        strongSelf.groupsTitleTextNode.layer.compositingFilter = "overlayBlendMode"
                        strongSelf.subtitleNode.layer.compositingFilter = "overlayBlendMode"
                        strongSelf.disclaimerTextNode.textNode.layer.compositingFilter = "overlayBlendMode"
                    }
                    
                    strongSelf.registrationDateText = registrationDateText
                    strongSelf.phoneCountryText = phoneCountryText
                                        
                    strongSelf.controllerInteraction = item.controllerInteraction
                    
                    strongSelf.offsetContainer.frame = CGRect(origin: CGPoint(), size: itemLayout.contentSize)
                    
                    let _ = titleApply()
                    var contentOriginY = backgroundFrame.origin.y + verticalInset
                    let titleFrame = CGRect(origin: CGPoint(x: backgroundFrame.origin.x + floor((backgroundSize.width - titleLayout.size.width) / 2.0), y: contentOriginY), size: titleLayout.size)
                    strongSelf.titleNode.frame = titleFrame
                    contentOriginY += titleLayout.size.height
                    contentOriginY += verticalSpacing - paragraphSpacing
                    
                    let _ = subtitleApply()
                    let subtitleFrame = CGRect(origin: CGPoint(x: backgroundFrame.origin.x + floor((backgroundSize.width - subtitleLayout.size.width) / 2.0), y: contentOriginY), size: subtitleLayout.size)
                    strongSelf.subtitleNode.frame = subtitleFrame
                    contentOriginY += subtitleLayout.size.height
                    contentOriginY += verticalSpacing * 2.0 + paragraphSpacing
                    
                    var attributeMidpoints: [CGFloat] = []
                    
                    func appendAttributeMidpoint(titleLayout: TextNodeLayout?, valueLayout: TextNodeLayout?, valueOffset: CGFloat = 0.0) {
                        if let valueLayout {
                            let midpoint = backgroundSize.width - horizontalContentInset - valueLayout.size.width - valueOffset - attributeSpacing / 2.0
                            attributeMidpoints.append(midpoint)
                        }
                    }
                    appendAttributeMidpoint(titleLayout: phoneCountryTitleLayoutAndApply?.0, valueLayout: phoneCountryValueLayoutAndApply?.0)
                    appendAttributeMidpoint(titleLayout: registrationDateTitleLayoutAndApply?.0, valueLayout: registrationDateValueLayoutAndApply?.0)
                    appendAttributeMidpoint(titleLayout: groupsTitleLayoutAndApply?.0, valueLayout: groupsValueLayoutAndApply?.0, valueOffset: estimatedValueOffset)
                    
                    let middleX = floorToScreenPixels(attributeMidpoints.min() ?? backgroundSize.width / 2.0)
                                                      
                    let titleMaxX: CGFloat = backgroundFrame.minX + middleX - attributeSpacing / 2.0
                    let valueMinX: CGFloat = backgroundFrame.minX + middleX + attributeSpacing / 2.0
                  
                    func positionAttributeNodes(
                        titleTextNode: TextNode,
                        valueTextNode: TextNode,
                        valueOffset: CGFloat = 0.0,
                        titleLayoutAndApply: (TextNodeLayout, () -> TextNode)?,
                        valueLayoutAndApply: (TextNodeLayout, () -> TextNode)?
                    ) {
                        if let (titleLayout, titleApply) = titleLayoutAndApply {
                            if titleTextNode.supernode == nil {
                                strongSelf.offsetContainer.addSubnode(titleTextNode)
                            }
                            let _ = titleApply()
                            titleTextNode.frame = CGRect(
                                origin: CGPoint(x: titleMaxX - titleLayout.size.width, y: contentOriginY),
                                size: titleLayout.size
                            )
                        }
                        if let (valueLayout, valueApply) = valueLayoutAndApply {
                            if valueTextNode.supernode == nil {
                                strongSelf.offsetContainer.addSubnode(valueTextNode)
                            }
                            let _ = valueApply()
                            valueTextNode.frame = CGRect(
                                origin: CGPoint(x: valueMinX + valueOffset, y: contentOriginY),
                                size: valueLayout.size
                            )
                            contentOriginY += valueLayout.size.height + verticalSpacing
                        }
                    }
                    
                    positionAttributeNodes(
                        titleTextNode: strongSelf.phoneCountryTitleTextNode,
                        valueTextNode: strongSelf.phoneCountryValueTextNode,
                        titleLayoutAndApply: phoneCountryTitleLayoutAndApply,
                        valueLayoutAndApply: phoneCountryValueLayoutAndApply
                    )
                    positionAttributeNodes(
                        titleTextNode: strongSelf.registrationDateTitleTextNode,
                        valueTextNode: strongSelf.registrationDateValueTextNode,
                        titleLayoutAndApply: registrationDateTitleLayoutAndApply,
                        valueLayoutAndApply: registrationDateValueLayoutAndApply
                    )
                              
                    var valueOffset: CGFloat = 0.0
                    if let groupsValueLayoutAndApply {
                        let avatarsFrame = CGRect(origin: CGPoint(x: valueMinX + groupsValueLayoutAndApply.0.size.width + 4.0, y: contentOriginY + floor((groupsValueLayoutAndApply.0.size.height - avatarImageSize) / 2.0)), size: CGSize(width: avatarImageSize + avatarSpacing * 2.0, height: avatarImageSize))
                        strongSelf.groupsAvatarsNode.frame = avatarsFrame
                        strongSelf.groupsAvatarsNode.updateLayout(size: avatarsFrame.size)
                        strongSelf.groupsAvatarsNode.update(context: item.context, peers: strongSelf.groupsInCommon, synchronousLoad: true, imageSize: avatarImageSize, imageSpacing: avatarSpacing, borderWidth: avatarBorder)
                        
                        if groupsInCommonCount > 0 {
                            valueOffset = avatarImageSize + CGFloat(min(2, max(0, groupsInCommonCount - 1))) * avatarSpacing + 4.0
                            strongSelf.groupsButtonNode.frame = CGRect(origin: CGPoint(x: valueMinX, y: contentOriginY), size: CGSize(width: groupsValueLayoutAndApply.0.size.width + 20.0, height: 18.0))
                            
                            strongSelf.groupsButtonNode.isHidden = false
                            strongSelf.groupsAvatarsNode.isHidden = false
                            strongSelf.groupsArrowNode.isHidden = false
                            
                            if let icon = strongSelf.groupsArrowNode.image {
                                strongSelf.groupsArrowNode.frame = CGRect(origin: CGPoint(x: avatarsFrame.minX + valueOffset, y: contentOriginY + 4.0 - UIScreenPixel), size: icon.size)
                            }
                        } else {
                            strongSelf.groupsAvatarsNode.isHidden = true
                            strongSelf.groupsButtonNode.isHidden = true
                            strongSelf.groupsArrowNode.isHidden = true
                        }
                    }
                    
                    positionAttributeNodes(
                        titleTextNode: strongSelf.groupsTitleTextNode,
                        valueTextNode: strongSelf.groupsValueTextNode,
                        titleLayoutAndApply: groupsTitleLayoutAndApply,
                        valueLayoutAndApply: groupsValueLayoutAndApply
                    )
                    
                    contentOriginY += verticalSpacing + paragraphSpacing
                    let _ = disclaimerApply(TextNodeWithEntities.Arguments(context: item.context, cache: item.context.animationCache, renderer: item.context.animationRenderer, placeholderColor: primaryTextColor.withMultipliedAlpha(0.4), attemptSynchronous: true))
                    let disclaimerFrame = CGRect(origin: CGPoint(x: backgroundFrame.origin.x + floor((backgroundSize.width - disclaimerLayout.size.width) / 2.0), y: contentOriginY), size: disclaimerLayout.size)
                    strongSelf.disclaimerTextNode.textNode.frame = disclaimerFrame
                    
                    if strongSelf.backgroundContent == nil, let backgroundContent = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) {
                        backgroundContent.clipsToBounds = true
                        strongSelf.backgroundContent = backgroundContent
                        strongSelf.offsetContainer.insertSubnode(backgroundContent, at: 0)
                    }
                    
                    if let backgroundContent = strongSelf.backgroundContent {
                        backgroundContent.cornerRadius = item.presentationData.chatBubbleCorners.mainRadius
                        backgroundContent.frame = backgroundFrame
                        if let (rect, containerSize) = strongSelf.absolutePosition {
                            var backgroundFrame = backgroundContent.frame
                            backgroundFrame.origin.x += rect.minX
                            backgroundFrame.origin.y += containerSize.height - rect.minY
                            backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate)
                        }
                    }
                }
            })
        }
    }
    
    override public func updateTrailingItemSpace(_ height: CGFloat, transition: ContainedViewLayoutTransition) {
        if height.isLessThanOrEqualTo(0.0) {
            transition.updateFrame(node: self.offsetContainer, frame: CGRect(origin: CGPoint(), size: self.offsetContainer.bounds.size))
        } else {
            transition.updateFrame(node: self.offsetContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: -floorToScreenPixels(height / 2.0)), size: self.offsetContainer.bounds.size))
        }
    }
    
    override public func animateAdded(_ currentTimestamp: Double, duration: Double) {
        self.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration * 0.5)
    }
    
    override public func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) {
        self.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration * 0.5)
    }
    
    override public func animateRemoved(_ currentTimestamp: Double, duration: Double) {
        self.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration * 0.5, removeOnCompletion: false)
    }
    
    override public func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        let result = super.point(inside: point, with: event)
        let extra = self.offsetContainer.frame.contains(point)
        return result || extra
    }
}