import Foundation
import UIKit
import AsyncDisplayKit
import SwiftSignalKit
import Display
import TelegramCore
import TelegramPresentationData
import AccountContext
import AppBundle
import ContextUI
import TextFormat
import EmojiTextAttachmentView

private let leftInset: CGFloat = 16.0
private let rightInset: CGFloat = 16.0

private enum ChatSendMessageActionIcon {
    case sendWithoutSound
    case sendWhenOnline
    case schedule
    
    func image(theme: PresentationTheme) -> UIImage? {
        let imageName: String
        switch self {
        case .sendWithoutSound:
            imageName = "Chat/Input/Menu/SilentIcon"
        case .sendWhenOnline:
            imageName = "Chat/Input/Menu/WhenOnlineIcon"
        case .schedule:
            imageName = "Chat/Input/Menu/ScheduleIcon"
        }
        return generateTintedImage(image: UIImage(bundleImageName: imageName), color: theme.contextMenu.primaryColor)
    }
}

private final class ActionSheetItemNode: ASDisplayNode {
    private let title: String
    private let icon: ChatSendMessageActionIcon
    let action: () -> Void
    
    private let separatorNode: ASDisplayNode
    private let backgroundNode: ASDisplayNode
    private let highlightedBackgroundNode: ASDisplayNode
    private let buttonNode: HighlightTrackingButtonNode
    private let iconNode: ASImageNode
    private let titleNode: ImmediateTextNode
    
    private var maxWidth: CGFloat?
    
    init(theme: PresentationTheme, title: String, icon: ChatSendMessageActionIcon, hasSeparator: Bool, action: @escaping () -> Void) {
        self.title = title
        self.icon = icon
        self.action = action
        
        self.separatorNode = ASDisplayNode()
        self.separatorNode.backgroundColor = theme.contextMenu.itemSeparatorColor
        
        self.backgroundNode = ASDisplayNode()
        self.backgroundNode.isAccessibilityElement = false
        self.backgroundNode.backgroundColor = theme.contextMenu.itemBackgroundColor
        
        self.highlightedBackgroundNode = ASDisplayNode()
        self.highlightedBackgroundNode.isAccessibilityElement = false
        self.highlightedBackgroundNode.backgroundColor = theme.contextMenu.itemHighlightedBackgroundColor
        self.highlightedBackgroundNode.alpha = 0.0
        
        self.buttonNode = HighlightTrackingButtonNode()
        self.buttonNode.isAccessibilityElement = true
        self.buttonNode.accessibilityLabel = title
        
        self.titleNode = ImmediateTextNode()
        self.titleNode.isAccessibilityElement = false
        self.titleNode.maximumNumberOfLines = 1
        self.titleNode.attributedText = NSAttributedString(string: title, font: Font.regular(17.0), textColor: theme.contextMenu.primaryColor)
        self.titleNode.isUserInteractionEnabled = false
        self.titleNode.displaysAsynchronously = false
        
        self.iconNode = ASImageNode()
        self.iconNode.image = icon.image(theme: theme)
        self.iconNode.contentMode = .center
        self.iconNode.isAccessibilityElement = false
        self.iconNode.displaysAsynchronously = false
        self.iconNode.displayWithoutProcessing = true
        self.iconNode.isUserInteractionEnabled = false
        
        super.init()
        
        self.addSubnode(self.highlightedBackgroundNode)
        self.addSubnode(self.titleNode)
        self.addSubnode(self.iconNode)
        self.addSubnode(self.buttonNode)
        if hasSeparator {
            self.addSubnode(self.separatorNode)
        }
        
        self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
        self.buttonNode.highligthedChanged = { [weak self] highlighted in
            if let strongSelf = self {
                strongSelf.setHighlighted(highlighted, animated: true)
            }
        }
    }
    
    func setHighlighted(_ highlighted: Bool, animated: Bool) {
        if highlighted == (self.highlightedBackgroundNode.alpha == 1.0) {
            return
        }
        
        if highlighted {
            self.highlightedBackgroundNode.layer.removeAnimation(forKey: "opacity")
            self.highlightedBackgroundNode.alpha = 1.0
        } else {
            self.highlightedBackgroundNode.alpha = 0.0
            if animated {
                self.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
            }
        }
    }
    
    func updateTheme(_ theme: PresentationTheme) {
        self.separatorNode.backgroundColor = theme.contextMenu.itemSeparatorColor
        self.backgroundNode.backgroundColor = theme.contextMenu.itemBackgroundColor
        self.highlightedBackgroundNode.backgroundColor = theme.contextMenu.itemHighlightedBackgroundColor
        self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.regular(17.0), textColor: theme.contextMenu.primaryColor)
        self.iconNode.image = self.icon.image(theme: theme)
        
        if let maxWidth = self.maxWidth {
            let _ = self.titleNode.updateLayout(CGSize(width: maxWidth - leftInset - rightInset, height: .greatestFiniteMagnitude))
        }
    }
    
    func updateLayout(maxWidth: CGFloat) -> (CGFloat, CGFloat, (CGFloat) -> Void) {
        self.maxWidth = maxWidth
        
        let titleSize = self.titleNode.updateLayout(CGSize(width: maxWidth - leftInset - rightInset, height: .greatestFiniteMagnitude))
        let height: CGFloat = 44.0
        
        return (titleSize.width + leftInset + rightInset, height, { width in
            self.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - titleSize.height) / 2.0)), size: titleSize)
            
            if let image = self.iconNode.image {
                self.iconNode.frame = CGRect(origin: CGPoint(x: width - image.size.width - 12.0, y: floor((height - image.size.height) / 2.0)), size: image.size)
            }
            
            self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: height - UIScreenPixel), size: CGSize(width: width, height: UIScreenPixel))
            self.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: height))
            self.buttonNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: height))
        })
    }
    
    @objc private func buttonPressed() {
        self.buttonNode.isUserInteractionEnabled = false
        self.action()
    }
}

final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode, UIScrollViewDelegate {
    private let context: AccountContext
    private var presentationData: PresentationData
    private let sourceSendButton: ASDisplayNode
    private let textFieldFrame: CGRect
    private let textInputNode: EditableTextNode
    private let attachment: Bool
    private let forwardedCount: Int?
    private let hasEntityKeyboard: Bool
    
    private let send: (() -> Void)?
    private let cancel: (() -> Void)?
    
    private let effectView: UIVisualEffectView
    private let dimNode: ASDisplayNode
    
    private let contentContainerNode: ASDisplayNode
    private let contentNodes: [ActionSheetItemNode]
    private let sendButtonNode: HighlightableButtonNode
    
    private let messageClipNode: ASDisplayNode
    private let messageBackgroundNode: ASImageNode
    private let fromMessageTextNode: EditableTextNode
    private let toMessageTextNode: EditableTextNode
    private let scrollNode: ASScrollNode
    
    private var fromCustomEmojiContainerView: CustomEmojiContainerView?
    private var toCustomEmojiContainerView: CustomEmojiContainerView?
    
    private var validLayout: ContainerViewLayout?
    
    private var sendButtonFrame: CGRect {
        return self.sourceSendButton.view.convert(self.sourceSendButton.bounds, to: nil)
    }
    
    private var animateInputField = false
    
    private var emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?
    
    init(context: AccountContext, presentationData: PresentationData, reminders: Bool, gesture: ContextGesture, sourceSendButton: ASDisplayNode, textInputNode: EditableTextNode, attachment: Bool, canSendWhenOnline: Bool, forwardedCount: Int?, hasEntityKeyboard: Bool, emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?, send: (() -> Void)?, sendSilently: (() -> Void)?, sendWhenOnline: (() -> Void)?, schedule: (() -> Void)?, cancel: (() -> Void)?) {
        self.context = context
        self.presentationData = presentationData
        self.sourceSendButton = sourceSendButton
        self.textFieldFrame = textInputNode.convert(textInputNode.bounds, to: nil)
        self.textInputNode = textInputNode
        self.attachment = attachment
        self.forwardedCount = forwardedCount
        self.hasEntityKeyboard = hasEntityKeyboard
        self.emojiViewProvider = emojiViewProvider
        
        self.send = send
        self.cancel = cancel
                
        self.effectView = UIVisualEffectView()
        
        self.dimNode = ASDisplayNode()
        self.dimNode.alpha = 1.0
        self.dimNode.backgroundColor = self.presentationData.theme.contextMenu.dimColor
        
        self.sendButtonNode = HighlightableButtonNode()
        self.sendButtonNode.imageNode.displayWithoutProcessing = false
        self.sendButtonNode.imageNode.displaysAsynchronously = false
        self.sendButtonNode.accessibilityLabel = self.presentationData.strings.MediaPicker_Send
        
        self.messageClipNode = ASDisplayNode()
        self.messageClipNode.clipsToBounds = true
        self.messageClipNode.transform = CATransform3DMakeScale(1.0, -1.0, 1.0)
        self.messageBackgroundNode = ASImageNode()
        self.messageBackgroundNode.isUserInteractionEnabled = true
        self.fromMessageTextNode = EditableTextNode()
        self.fromMessageTextNode.isUserInteractionEnabled = false
        self.toMessageTextNode = EditableTextNode()
        self.toMessageTextNode.alpha = 0.0
        self.toMessageTextNode.isUserInteractionEnabled = false
        
        self.scrollNode = ASScrollNode()
        self.scrollNode.transform = CATransform3DMakeScale(1.0, -1.0, 1.0)
        
        self.contentContainerNode = ASDisplayNode()
        self.contentContainerNode.backgroundColor = self.presentationData.theme.contextMenu.backgroundColor
        self.contentContainerNode.cornerRadius = 14.0
        self.contentContainerNode.clipsToBounds = true
        
        var contentNodes: [ActionSheetItemNode] = []
        if !reminders {
            contentNodes.append(ActionSheetItemNode(theme: self.presentationData.theme, title: self.presentationData.strings.Conversation_SendMessage_SendSilently, icon: .sendWithoutSound, hasSeparator: true, action: {
                sendSilently?()
            }))
            if canSendWhenOnline {
                contentNodes.append(ActionSheetItemNode(theme: self.presentationData.theme, title: self.presentationData.strings.Conversation_SendMessage_SendWhenOnline, icon: .sendWhenOnline, hasSeparator: true, action: {
                    sendWhenOnline?()
                }))
            }
        }
        if let _ = schedule {
            contentNodes.append(ActionSheetItemNode(theme: self.presentationData.theme, title: reminders ? self.presentationData.strings.Conversation_SendMessage_SetReminder: self.presentationData.strings.Conversation_SendMessage_ScheduleMessage, icon: .schedule, hasSeparator: false, action: {
                schedule?()
            }))
        }
        self.contentNodes = contentNodes
        
        super.init()
                        
        self.sendButtonNode.addTarget(self, action: #selector(self.sendButtonPressed), forControlEvents: .touchUpInside)
        
        if let attributedText = textInputNode.attributedText, !attributedText.string.isEmpty {
            self.animateInputField = true
            self.fromMessageTextNode.attributedText = attributedText
            
            if let toAttributedText = self.fromMessageTextNode.attributedText?.mutableCopy() as? NSMutableAttributedString {
                toAttributedText.addAttribute(NSAttributedString.Key.foregroundColor, value: self.presentationData.theme.chat.message.outgoing.primaryTextColor, range: NSMakeRange(0, (toAttributedText.string as NSString).length))
                self.toMessageTextNode.attributedText = toAttributedText
            }
        } else {
            if let _ = forwardedCount {
                self.animateInputField = true
            }
            self.fromMessageTextNode.attributedText = NSAttributedString(string: self.attachment ? self.presentationData.strings.MediaPicker_AddCaption : self.presentationData.strings.Conversation_InputTextPlaceholder, attributes: [NSAttributedString.Key.foregroundColor: self.presentationData.theme.chat.inputPanel.inputPlaceholderColor, NSAttributedString.Key.font: Font.regular(self.presentationData.chatFontSize.baseDisplaySize)])
        
            self.toMessageTextNode.attributedText = NSAttributedString(string: self.presentationData.strings.ForwardedMessages(Int32(forwardedCount ?? 0)), attributes: [NSAttributedString.Key.foregroundColor: self.presentationData.theme.chat.message.outgoing.primaryTextColor, NSAttributedString.Key.font: Font.regular(self.presentationData.chatFontSize.baseDisplaySize)])
        }
        self.messageBackgroundNode.contentMode = .scaleToFill
        
        let outgoing: PresentationThemeBubbleColorComponents = self.presentationData.chatWallpaper.isEmpty ? self.presentationData.theme.chat.message.outgoing.bubble.withoutWallpaper : self.presentationData.theme.chat.message.outgoing.bubble.withWallpaper
        
        let maxCornerRadius = self.presentationData.chatBubbleCorners.mainRadius
        self.messageBackgroundNode.image = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: maxCornerRadius, incoming: false, fillColor: outgoing.fill.last ?? outgoing.fill[0], strokeColor: outgoing.fill.count > 1 ? outgoing.stroke : .clear, neighbors: .none, theme: self.presentationData.theme.chat, wallpaper: self.presentationData.chatWallpaper, knockout: false)
        
        self.view.addSubview(self.effectView)
        self.addSubnode(self.dimNode)
        
        self.addSubnode(self.contentContainerNode)
        self.addSubnode(self.scrollNode)
        
        self.addSubnode(self.sendButtonNode)
        self.scrollNode.addSubnode(self.messageClipNode)
        self.messageClipNode.addSubnode(self.messageBackgroundNode)
        self.messageClipNode.addSubnode(self.fromMessageTextNode)
        self.messageClipNode.addSubnode(self.toMessageTextNode)
        
        self.contentNodes.forEach(self.contentContainerNode.addSubnode)
        
        gesture.externalUpdated = { [weak self] view, location in
            guard let strongSelf = self else {
                return
            }
            for contentNode in strongSelf.contentNodes {
                let localPoint = contentNode.view.convert(location, from: view)
                if contentNode.bounds.contains(localPoint) {
                    contentNode.setHighlighted(true, animated: false)
                } else {
                    contentNode.setHighlighted(false, animated: false)
                }
            }
        }
        
        gesture.externalEnded = { [weak self] viewAndLocation in
            guard let strongSelf = self else {
                return
            }
            for contentNode in strongSelf.contentNodes {
                if let (view, location) = viewAndLocation {
                    let localPoint = contentNode.view.convert(location, from: view)
                    if contentNode.bounds.contains(localPoint) {
                        contentNode.action()
                    } else {
                        contentNode.setHighlighted(false, animated: false)
                    }
                } else {
                    contentNode.setHighlighted(false, animated: false)
                }
            }
        }
    }
    
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        let result = super.hitTest(point, with: event)
        if result != self.scrollNode.view {
            return result
        } else {
            return self.dimNode.view
        }
    }
    
    override func didLoad() {
        super.didLoad()
        
        self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
        
        self.scrollNode.view.showsVerticalScrollIndicator = false
        self.scrollNode.view.delaysContentTouches = false
        self.scrollNode.view.delegate = self
        self.scrollNode.view.alwaysBounceVertical = true
        if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
            self.scrollNode.view.contentInsetAdjustmentBehavior = .never
        }
        
        if let snapshotView = self.sourceSendButton.view.snapshotView(afterScreenUpdates: false) {
            self.sendButtonNode.view.addSubview(snapshotView)
        }
    }
    
    func updateTextContents() {
        var customEmojiRects: [(CGRect, ChatTextInputTextCustomEmojiAttribute)] = []
        
        let textInputNode = self.fromMessageTextNode
        if let attributedText = textInputNode.attributedText {
            let beginning = textInputNode.textView.beginningOfDocument
            attributedText.enumerateAttributes(in: NSMakeRange(0, attributedText.length), options: [], using: { attributes, range, _ in
                if let value = attributes[ChatTextInputAttributes.customEmoji] as? ChatTextInputTextCustomEmojiAttribute {
                    if let start = textInputNode.textView.position(from: beginning, offset: range.location), let end = textInputNode.textView.position(from: start, offset: range.length), let textRange = textInputNode.textView.textRange(from: start, to: end) {
                        let textRects = textInputNode.textView.selectionRects(for: textRange)
                        for textRect in textRects {
                            customEmojiRects.append((textRect.rect, value))
                            break
                        }
                    }
                }
            })
        }
        
        self.updateTextContents(rects: customEmojiRects, textInputNode: self.fromMessageTextNode, from: true)
        self.updateTextContents(rects: customEmojiRects, textInputNode: self.toMessageTextNode, from: false)
    }
    
    func updateTextContents(rects: [(CGRect, ChatTextInputTextCustomEmojiAttribute)], textInputNode: EditableTextNode, from: Bool) {
        if !rects.isEmpty {
            let customEmojiContainerView: CustomEmojiContainerView
            if from, let current = self.fromCustomEmojiContainerView {
                customEmojiContainerView = current
            } else if !from, let current = self.toCustomEmojiContainerView {
                customEmojiContainerView = current
            } else {
                customEmojiContainerView = CustomEmojiContainerView(emojiViewProvider: { [weak self] emoji in
                    guard let strongSelf = self, let emojiViewProvider = strongSelf.emojiViewProvider else {
                        return nil
                    }
                    return emojiViewProvider(emoji)
                })
                customEmojiContainerView.isUserInteractionEnabled = false
                textInputNode.textView.addSubview(customEmojiContainerView)
                if from {
                    self.fromCustomEmojiContainerView = customEmojiContainerView
                } else {
                    self.toCustomEmojiContainerView = customEmojiContainerView
                }
            }
            
            customEmojiContainerView.update(emojiRects: rects)
        } else {
            if from, let customEmojiContainerView = self.fromCustomEmojiContainerView {
                customEmojiContainerView.removeFromSuperview()
                self.fromCustomEmojiContainerView = nil
            } else if !from, let customEmojiContainerView = self.toCustomEmojiContainerView {
                customEmojiContainerView.removeFromSuperview()
                self.fromCustomEmojiContainerView = nil
            }
        }
    }
    
    func updatePresentationData(_ presentationData: PresentationData) {
        guard presentationData.theme !== self.presentationData.theme else {
            return
        }
        self.presentationData = presentationData
        
        self.effectView.effect = makeCustomZoomBlurEffect(isLight: self.presentationData.theme.rootController.keyboardColor == .light)
        
        self.dimNode.backgroundColor = presentationData.theme.contextMenu.dimColor
        
        self.contentContainerNode.backgroundColor = self.presentationData.theme.contextMenu.backgroundColor
        
        if let toAttributedText = self.textInputNode.attributedText?.mutableCopy() as? NSMutableAttributedString {
            toAttributedText.addAttribute(NSAttributedString.Key.foregroundColor, value: self.presentationData.theme.chat.message.outgoing.primaryTextColor, range: NSMakeRange(0, (toAttributedText.string as NSString).length))
            self.toMessageTextNode.attributedText = toAttributedText
        }
        
        let outgoing: PresentationThemeBubbleColorComponents = self.presentationData.chatWallpaper.isEmpty ? self.presentationData.theme.chat.message.outgoing.bubble.withoutWallpaper : self.presentationData.theme.chat.message.outgoing.bubble.withWallpaper
        let maxCornerRadius = self.presentationData.chatBubbleCorners.mainRadius
        self.messageBackgroundNode.image = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: maxCornerRadius, incoming: false, fillColor: outgoing.fill.last ?? outgoing.fill[0], strokeColor: outgoing.fill.count > 1 ? outgoing.stroke : .clear, neighbors: .none, theme: self.presentationData.theme.chat, wallpaper: self.presentationData.chatWallpaper, knockout: false)
        
        for node in self.contentNodes {
            node.updateTheme(presentationData.theme)
        }
    }
    
    func animateIn() {
        guard let layout = self.validLayout else {
            return
        }
        
        self.textInputNode.textView.setContentOffset(self.textInputNode.textView.contentOffset, animated: false)
        
        UIView.animate(withDuration: 0.2, animations: {
            self.effectView.effect = makeCustomZoomBlurEffect(isLight: self.presentationData.theme.rootController.keyboardColor == .light)
        }, completion: { _ in })
        self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
        self.contentContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
        self.messageBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
        
        self.sourceSendButton.isHidden = true
        if self.animateInputField {
            self.fromMessageTextNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
            self.toMessageTextNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, removeOnCompletion: false)
        } else {
            self.messageBackgroundNode.isHidden = true
            self.fromMessageTextNode.isHidden = true
            self.toMessageTextNode.isHidden = true
        }
        
        let duration = 0.4
        self.sendButtonNode.layer.animateScale(from: 0.75, to: 1.0, duration: 0.2, timingFunction: CAMediaTimingFunctionName.linear.rawValue)
        self.sendButtonNode.layer.animatePosition(from: self.sendButtonFrame.center, to: self.sendButtonNode.position, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
        
        var initialWidth = self.textFieldFrame.width + 32.0
        if self.textInputNode.textView.attributedText.string.isEmpty {
            initialWidth = ceil(layout.size.width - self.textFieldFrame.origin.x - self.sendButtonFrame.width - layout.safeInsets.left - layout.safeInsets.right + 21.0)
        }
        
        let fromFrame = CGRect(origin: CGPoint(), size: CGSize(width: initialWidth, height: self.textFieldFrame.height + 2.0))
        let delta = (fromFrame.height - self.messageClipNode.bounds.height) / 2.0
        
        var inputHeight = layout.inputHeight ?? 0.0
        if self.hasEntityKeyboard {
            inputHeight = layout.standardInputHeight
        }
        
        var clipDelta = delta
        if inputHeight.isZero || layout.isNonExclusive {
            clipDelta -= self.contentContainerNode.frame.height + 16.0
        }
        
        self.messageClipNode.layer.animateBounds(from: fromFrame, to: self.messageClipNode.bounds, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
        self.messageClipNode.layer.animatePosition(from: CGPoint(x: (self.messageClipNode.bounds.width - initialWidth) / 2.0, y: clipDelta), to: CGPoint(), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, additive: true, completion: { [weak self] _ in
            if let strongSelf = self {
                strongSelf.insertSubnode(strongSelf.contentContainerNode, aboveSubnode: strongSelf.scrollNode)
            }
        })
        
        self.messageBackgroundNode.layer.animateBounds(from: fromFrame, to: self.messageBackgroundNode.bounds, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
        self.messageBackgroundNode.layer.animatePosition(from: CGPoint(x: (initialWidth - self.messageClipNode.bounds.width) / 2.0, y: delta), to: CGPoint(), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
       
        var textXOffset: CGFloat = 0.0
        let textYOffset = self.textInputNode.textView.contentSize.height - self.textInputNode.textView.contentOffset.y - self.textInputNode.textView.frame.height
        if self.textInputNode.textView.numberOfLines == 1 && self.textInputNode.isRTL {
            textXOffset = initialWidth - self.messageClipNode.bounds.width
        }
        self.fromMessageTextNode.layer.animatePosition(from: CGPoint(x: textXOffset, y: delta * 2.0 + textYOffset), to: CGPoint(), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
        self.toMessageTextNode.layer.animatePosition(from: CGPoint(x: textXOffset, y: delta * 2.0 + textYOffset), to: CGPoint(), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
        
        let contentOffset = CGPoint(x:  self.sendButtonFrame.midX - self.contentContainerNode.frame.midX, y:  self.sendButtonFrame.midY - self.contentContainerNode.frame.midY)
    
        let springDuration: Double = 0.42
        let springDamping: CGFloat = 104.0
        self.contentContainerNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: springDuration, initialVelocity: 0.0, damping: springDamping)
        self.contentContainerNode.layer.animateSpring(from: NSValue(cgPoint: contentOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, additive: true)
        
        Queue.mainQueue().after(0.01, {
            if self.animateInputField {
                self.textInputNode.isHidden = true
            }
            self.updateTextContents()
        })
    }
    
    func animateOut(cancel: Bool, completion: @escaping () -> Void) {
        guard let layout = self.validLayout else {
            return
        }
        
        self.isUserInteractionEnabled = false
        
        self.scrollNode.view.setContentOffset(self.scrollNode.view.contentOffset, animated: false)
        
        var completedEffect = false
        var completedButton = false
        var completedBubble = false
        var completedAlpha = false
        
        let intermediateCompletion: () -> Void = { [weak self] in
            if completedEffect && completedButton && completedBubble && completedAlpha {
                self?.textInputNode.isHidden = false
                self?.sourceSendButton.isHidden = false
                completion()
            }
        }
        
        UIView.animate(withDuration: 0.2, animations: {
            self.effectView.effect = nil
        }, completion: { _ in
            completedEffect = true
            intermediateCompletion()
        })
        
        self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
        self.contentContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in })
        
        if self.animateInputField {
            if cancel {
                self.fromMessageTextNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, delay: 0.15, removeOnCompletion: false)
                self.toMessageTextNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, delay: 0.15, removeOnCompletion: false)
                self.messageBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, delay: 0.15, removeOnCompletion: false, completion: { _ in
                    completedAlpha = true
                    intermediateCompletion()
                })
            } else {
                self.textInputNode.isHidden = false
                self.messageClipNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in
                    completedAlpha = true
                    intermediateCompletion()
                })
            }
        } else {
            completedAlpha = true
        }
        
        let duration = 0.4
        self.sendButtonNode.layer.animatePosition(from: self.sendButtonNode.position, to: self.sendButtonFrame.center, duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
            completedButton = true
            intermediateCompletion()
        })
        
        if !cancel {
            self.sourceSendButton.isHidden = false
            self.sendButtonNode.layer.animateScale(from: 1.0, to: 0.2, duration: 0.2, timingFunction: CAMediaTimingFunctionName.linear.rawValue, removeOnCompletion: false)
            self.sendButtonNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, timingFunction: CAMediaTimingFunctionName.linear.rawValue, removeOnCompletion: false)
        }
        
        var initialWidth = self.textFieldFrame.width + 32.0
        if self.textInputNode.textView.attributedText.string.isEmpty {
            initialWidth = ceil(layout.size.width - self.textFieldFrame.origin.x - self.sendButtonFrame.width - layout.safeInsets.left - layout.safeInsets.right + 21.0)
        }
        
        let toFrame = CGRect(origin: CGPoint(), size: CGSize(width: initialWidth, height: self.textFieldFrame.height + 1.0))
        let delta = (toFrame.height - self.messageClipNode.bounds.height) / 2.0
                
        if cancel && self.animateInputField {
            var inputHeight = layout.inputHeight ?? 0.0
            if self.hasEntityKeyboard {
                inputHeight = layout.standardInputHeight
            }
            
            var clipDelta = delta
            if inputHeight.isZero || layout.isNonExclusive {
                clipDelta -= self.contentContainerNode.frame.height + 16.0
            }
            
            self.messageClipNode.layer.animateBounds(from: self.messageClipNode.bounds, to: toFrame, duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
                completedBubble = true
                intermediateCompletion()
            })
            self.messageClipNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: (self.messageClipNode.bounds.width - initialWidth) / 2.0, y: clipDelta + self.scrollNode.view.contentOffset.y), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
            
            self.messageBackgroundNode.layer.animateBounds(from: self.messageBackgroundNode.bounds, to: toFrame, duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
            self.messageBackgroundNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: (initialWidth - self.messageClipNode.bounds.width) / 2.0, y: delta), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
            
            var textXOffset: CGFloat = 0.0
            let textYOffset = self.textInputNode.textView.contentSize.height - self.textInputNode.textView.contentOffset.y - self.textInputNode.textView.frame.height
            if self.textInputNode.textView.numberOfLines == 1 && self.textInputNode.isRTL {
                textXOffset = initialWidth - self.messageClipNode.bounds.width
            }
            self.fromMessageTextNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: textXOffset, y: delta * 2.0 + textYOffset), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
            self.toMessageTextNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: textXOffset, y: delta * 2.0 + textYOffset), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
        } else {
            completedBubble = true
        }
        
        let contentOffset = CGPoint(x:  self.sendButtonFrame.midX - self.contentContainerNode.frame.midX, y:  self.sendButtonFrame.midY - self.contentContainerNode.frame.midY)
        
        self.contentContainerNode.layer.animatePosition(from: CGPoint(), to: contentOffset, duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
        self.contentContainerNode.layer.animateScale(from: 1.0, to: 0.1, duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
    }
    
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if let layout = self.validLayout {
            self.containerLayoutUpdated(layout, transition: .immediate)
        }
    }
    
    func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
        self.validLayout = layout
                
        transition.updateFrame(view: self.effectView, frame: CGRect(origin: CGPoint(), size: layout.size))
        transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
        
        let sideInset: CGFloat = self.sendButtonFrame.width - 1.0
        
        var contentSize = CGSize()
        contentSize.width = min(layout.size.width - 40.0, 250.0)
        var applyNodes: [(ASDisplayNode, CGFloat, (CGFloat) -> Void)] = []
        for itemNode in self.contentNodes {
            let (width, height, apply) = itemNode.updateLayout(maxWidth: layout.size.width - 16.0 * 2.0)
            applyNodes.append((itemNode, height, apply))
            contentSize.width = max(contentSize.width, width)
            contentSize.height += height
        }
        
        let menuHeightWithInset = contentSize.height + 16.0
        
        var insets = layout.insets(options: [.statusBar, .input])
        var inputHeight = layout.inputHeight ?? 0.0
        if self.hasEntityKeyboard {
            insets.bottom = max(insets.bottom, layout.standardInputHeight)
            inputHeight = layout.standardInputHeight
        }
        
        let contentOffset = self.scrollNode.view.contentOffset.y
        let initialSendButtonFrame = self.sendButtonFrame
        
        var contentOrigin: CGPoint
        if initialSendButtonFrame.width > initialSendButtonFrame.height * 1.2 {
            contentOrigin = CGPoint(x: layout.size.width - contentSize.width - layout.safeInsets.right - 5.0, y: initialSendButtonFrame.minY - contentSize.height)
        } else {
            contentOrigin = CGPoint(x: layout.size.width - sideInset - contentSize.width - layout.safeInsets.right, y: layout.size.height - 6.0 - insets.bottom - contentSize.height)
        }
        if inputHeight > 0.0 && !layout.isNonExclusive && self.animateInputField {
            contentOrigin.y += menuHeightWithInset
        }
        contentOrigin.y = min(contentOrigin.y + contentOffset, layout.size.height - 6.0 - layout.intrinsicInsets.bottom - contentSize.height)
        
        transition.updateFrame(node: self.contentContainerNode, frame: CGRect(origin: contentOrigin, size: contentSize))
        var nextY: CGFloat = 0.0
        for (itemNode, height, apply) in applyNodes {
            transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: nextY), size: CGSize(width: contentSize.width, height: height)))
            apply(contentSize.width)
            nextY += height
        }
        
        var sendButtonFrame = CGRect(origin: CGPoint(x: layout.size.width - initialSendButtonFrame.width + 1.0 - UIScreenPixel - layout.safeInsets.right, y: layout.size.height - insets.bottom - initialSendButtonFrame.height), size: initialSendButtonFrame.size)
        if (inputHeight.isZero || layout.isNonExclusive) && self.animateInputField {
            sendButtonFrame.origin.y -= menuHeightWithInset
        }
        sendButtonFrame.origin.y = min(sendButtonFrame.origin.y + contentOffset, layout.size.height - layout.intrinsicInsets.bottom - initialSendButtonFrame.height)
        transition.updateFrameAsPositionAndBounds(node: self.sendButtonNode, frame: sendButtonFrame)
        
        var messageFrame = self.textFieldFrame
        messageFrame.size.width += 32.0
        messageFrame.origin.x -= 13.0
        messageFrame.origin.y = layout.size.height - messageFrame.origin.y - messageFrame.size.height - 1.0
        if inputHeight.isZero || layout.isNonExclusive {
            messageFrame.origin.y += menuHeightWithInset
        }
        
        if self.textInputNode.textView.attributedText.string.isEmpty {
            messageFrame.size.width = ceil(layout.size.width - messageFrame.origin.x - sendButtonFrame.width - layout.safeInsets.left - layout.safeInsets.right + 8.0)
        }
        
        var messageOriginDelta: CGFloat = 0.0
        if self.textInputNode.textView.numberOfLines == 1 || self.textInputNode.textView.attributedText.string.isEmpty {
            let textWidth = min(self.toMessageTextNode.textView.sizeThatFits(layout.size).width + 36.0, messageFrame.width)
            messageOriginDelta = messageFrame.width - textWidth
            messageFrame.origin.x += messageOriginDelta
            messageFrame.size.width = textWidth
        }
        
        let messageHeight = max(messageFrame.size.height, self.textInputNode.textView.contentSize.height + 2.0)
        messageFrame.size.height = messageHeight
        
        transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: layout.size))
        
        var scrollContentSize = CGSize(width: layout.size.width, height: messageHeight + max(0.0, messageFrame.origin.y))
        if messageHeight > layout.size.height - messageFrame.origin.y {
            scrollContentSize.height += insets.top + 16.0
        }
        self.scrollNode.view.contentSize = scrollContentSize
        
        let clipFrame = messageFrame
        transition.updateFrame(node: self.messageClipNode, frame: clipFrame)
        
        let backgroundFrame = CGRect(origin: CGPoint(), size: messageFrame.size)
        transition.updateFrame(node: self.messageBackgroundNode, frame: backgroundFrame)
        
        var textFrame = self.textFieldFrame
        textFrame.origin = CGPoint(x: 13.0, y: 6.0 - UIScreenPixel)
        textFrame.size.height = self.textInputNode.textView.contentSize.height
        textFrame.size.width -= self.textInputNode.textContainerInset.right
        
        if self.textInputNode.isRTL {
            textFrame.origin.x -= messageOriginDelta
        }
        
        self.fromMessageTextNode.frame = textFrame
        self.toMessageTextNode.frame = textFrame
    }
    
    @objc private func dimTapGesture(_ recognizer: UITapGestureRecognizer) {
        if case .ended = recognizer.state {
            self.cancel?()
        }
    }
    
    @objc private func sendButtonPressed() {
        self.sendButtonNode.isUserInteractionEnabled = false
        self.send?()
    }
}

final class CustomEmojiContainerView: UIView {
    private let emojiViewProvider: (ChatTextInputTextCustomEmojiAttribute) -> UIView?
    
    private var emojiLayers: [InlineStickerItemLayer.Key: UIView] = [:]
    
    init(emojiViewProvider: @escaping (ChatTextInputTextCustomEmojiAttribute) -> UIView?) {
        self.emojiViewProvider = emojiViewProvider
        
        super.init(frame: CGRect())
    }
    
    required init(coder: NSCoder) {
        preconditionFailure()
    }
    
    func update(emojiRects: [(CGRect, ChatTextInputTextCustomEmojiAttribute)]) {
        var nextIndexById: [Int64: Int] = [:]
        
        var validKeys = Set<InlineStickerItemLayer.Key>()
        for (rect, emoji) in emojiRects {
            let index: Int
            if let nextIndex = nextIndexById[emoji.fileId] {
                index = nextIndex
            } else {
                index = 0
            }
            nextIndexById[emoji.fileId] = index + 1
            
            let key = InlineStickerItemLayer.Key(id: emoji.fileId, index: index)
            
            let view: UIView
            if let current = self.emojiLayers[key] {
                view = current
            } else if let newView = self.emojiViewProvider(emoji) {
                view = newView
                self.addSubview(newView)
                self.emojiLayers[key] = view
            } else {
                continue
            }
            
            let size = CGSize(width: 24.0, height: 24.0)
            
            view.frame = CGRect(origin: CGPoint(x: floor(rect.midX - size.width / 2.0), y: floor(rect.midY - size.height / 2.0)), size: size)
            
            validKeys.insert(key)
        }
        
        var removeKeys: [InlineStickerItemLayer.Key] = []
        for (key, view) in self.emojiLayers {
            if !validKeys.contains(key) {
                removeKeys.append(key)
                view.removeFromSuperview()
            }
        }
        for key in removeKeys {
            self.emojiLayers.removeValue(forKey: key)
        }
    }
}