import Foundation
import UIKit
import SwiftSignalKit
import AsyncDisplayKit
import Display
import TelegramCore
import TelegramPresentationData
import AccountContext
import UrlEscaping
import ActivityIndicator

private class TextField: UITextField, UIScrollViewDelegate {
    let placeholderLabel: ImmediateTextNode
    var placeholderString: NSAttributedString? {
        didSet {
            self.placeholderLabel.attributedText = self.placeholderString
            self.setNeedsLayout()
        }
    }
    
    fileprivate func updatePrefixWidth(_ prefixWidth: CGFloat) {
        let previousPrefixWidth = self.prefixWidth
        guard previousPrefixWidth != prefixWidth else {
            return
        }
        self.prefixWidth = prefixWidth
        if let scrollView = self.scrollView {
            if scrollView.contentInset.left != prefixWidth {
                scrollView.contentInset = UIEdgeInsets(top: 0.0, left: prefixWidth, bottom: 0.0, right: 0.0)
            }
            if prefixWidth.isZero {
                scrollView.contentOffset = CGPoint()
            } else if prefixWidth != previousPrefixWidth {
                scrollView.contentOffset = CGPoint(x: -prefixWidth, y: 0.0)
            }
            self.updatePrefixPosition(transition: .immediate)
        }
    }
    
    private var prefixWidth: CGFloat = 0.0

    let prefixLabel: ImmediateTextNode
    var prefixString: NSAttributedString? {
        didSet {
            self.prefixLabel.attributedText = self.prefixString
            self.setNeedsLayout()
        }
    }
    
    init() {
        self.prefixLabel = ImmediateTextNode()
        self.prefixLabel.isUserInteractionEnabled = false
        self.prefixLabel.displaysAsynchronously = false
        self.prefixLabel.maximumNumberOfLines = 1
        self.prefixLabel.truncationMode = .byTruncatingTail
        
        self.placeholderLabel = ImmediateTextNode()
        self.placeholderLabel.isUserInteractionEnabled = false
        self.placeholderLabel.displaysAsynchronously = false
        self.placeholderLabel.maximumNumberOfLines = 1
        self.placeholderLabel.truncationMode = .byTruncatingTail
                
        super.init(frame: CGRect())
        
        self.addSubnode(self.prefixLabel)
        self.addSubnode(self.placeholderLabel)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func addSubview(_ view: UIView) {
        super.addSubview(view)
        
        if let scrollView = view as? UIScrollView {
            scrollView.delegate = self
        }
    }
    
    private weak var _scrollView: UIScrollView?
    var scrollView: UIScrollView? {
        if let scrollView = self._scrollView {
            return scrollView
        }
        for view in self.subviews {
            if let scrollView = view as? UIScrollView {
                _scrollView = scrollView
                return scrollView
            }
        }
        return nil
    }
    
    override func deleteBackward() {
        super.deleteBackward()
        
        if let scrollView = self.scrollView {
            if scrollView.contentSize.width <= scrollView.frame.width && scrollView.contentOffset.x > -scrollView.contentInset.left {
                scrollView.contentOffset = CGPoint(x: max(scrollView.contentOffset.x - 5.0, -scrollView.contentInset.left), y: 0.0)
                self.updatePrefixPosition()
            }
        }
    }
    
    func selectWhole() {
        guard let _ = self.scrollView else {
            return
        }
//        if scrollView.contentSize.width > scrollView.frame.width - scrollView.contentInset.left {
//            scrollView.contentOffset = CGPoint(x: -scrollView.contentInset.left + scrollView.contentSize.width - (scrollView.frame.width - scrollView.contentInset.left), y: 0.0)
//            self.updatePrefixPosition()
//        }
        self.selectAll(nil)
    }
    
    var fixAutoScroll: CGPoint?
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if let fixAutoScroll = self.fixAutoScroll {
            self.scrollView?.setContentOffset(fixAutoScroll, animated: true)
            self.scrollView?.setContentOffset(fixAutoScroll, animated: false)
            self.fixAutoScroll = nil
        } else {
            self.updatePrefixPosition()
        }
    }
    
    override func becomeFirstResponder() -> Bool {
        if let contentOffset = self.scrollView?.contentOffset {
            self.fixAutoScroll = contentOffset
            Queue.mainQueue().after(0.1) {
                self.fixAutoScroll = nil
            }
        }
        return super.becomeFirstResponder()
    }
    
    private var prefixPosition: CGFloat?
    private func updatePrefixPosition(transition: ContainedViewLayoutTransition = .immediate) {
        if let scrollView = self.scrollView {
            let prefixPosition = -scrollView.contentOffset.x - scrollView.contentInset.left
            self.prefixPosition = prefixPosition
            transition.updateFrame(node: self.prefixLabel, frame: CGRect(origin: CGPoint(x: prefixPosition, y: self.prefixLabel.frame.minY), size: self.prefixLabel.frame.size))
        }
    }
        
    override var keyboardAppearance: UIKeyboardAppearance {
        get {
            return super.keyboardAppearance
        }
        set {
            let resigning = self.isFirstResponder
            if resigning {
                self.resignFirstResponder()
            }
            super.keyboardAppearance = newValue
            if resigning {
                let _ = self.becomeFirstResponder()
            }
        }
    }
    
    override func textRect(forBounds bounds: CGRect) -> CGRect {
        if bounds.size.width.isZero {
            return CGRect(origin: CGPoint(), size: CGSize())
        }
        var rect = bounds.insetBy(dx: 0.0, dy: 4.0)
        if #available(iOS 14.0, *) {
        } else {
            rect.origin.y += 1.0
        }
        if !self.prefixWidth.isZero && self.scrollView?.superview == nil {
            var offset = self.prefixWidth
            if let scrollView = self.scrollView {
                offset = scrollView.contentOffset.x * -1.0
            }
            rect.origin.x += offset
            rect.size.width -= offset
         }
        rect.size.width = max(rect.size.width, 10.0)
        return rect
    }
    
    override func editingRect(forBounds bounds: CGRect) -> CGRect {
        return self.textRect(forBounds: bounds)
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        let bounds = self.bounds
        if bounds.size.width.isZero {
            return
        }
                
        let textRect = self.textRect(forBounds: bounds)

        let labelSize = self.placeholderLabel.updateLayout(textRect.size)
        self.placeholderLabel.frame = CGRect(origin: CGPoint(x: textRect.minX + 3.0, y: floorToScreenPixels((bounds.height - labelSize.height) / 2.0)), size: labelSize)
        
        let prefixSize = self.prefixLabel.updateLayout(CGSize(width: floor(bounds.size.width * 0.7), height: bounds.size.height))
        let prefixBounds = bounds.insetBy(dx: 4.0, dy: 4.0)
        self.prefixLabel.frame = CGRect(origin: CGPoint(x: self.prefixPosition ?? prefixBounds.minX, y: floorToScreenPixels((bounds.height - prefixSize.height) / 2.0)), size: prefixSize)
        self.updatePrefixWidth(prefixSize.width + 3.0)
    }
}

private let validIdentifierSet: CharacterSet = {
    var set = CharacterSet(charactersIn: "a".unicodeScalars.first! ... "z".unicodeScalars.first!)
    set.insert(charactersIn: "A".unicodeScalars.first! ... "Z".unicodeScalars.first!)
    set.insert(charactersIn: "0".unicodeScalars.first! ... "9".unicodeScalars.first!)
    set.insert("_")
    return set
}()

private final class ImportStickerPackTitleInputFieldNode: ASDisplayNode, UITextFieldDelegate {
    private var theme: PresentationTheme
    private let backgroundNode: ASImageNode
    private let textInputNode: TextField
    private let clearButton: HighlightableButtonNode
    
    var updateHeight: (() -> Void)?
    var complete: (() -> Void)?
    var textChanged: ((String) -> Void)?
    
    private let backgroundInsets = UIEdgeInsets(top: 8.0, left: 16.0, bottom: 15.0, right: 16.0)
    private let inputInsets = UIEdgeInsets(top: 8.0, left: 8.0, bottom: 8.0, right: 8.0)
    
    var text: String {
        get {
            return self.textInputNode.attributedText?.string ?? ""
        }
        set {
            self.textInputNode.attributedText = NSAttributedString(string: newValue, font: Font.regular(14.0), textColor: self.theme.actionSheet.inputTextColor)
            self.textInputNode.placeholderLabel.isHidden = !newValue.isEmpty
            if self.textInputNode.isFirstResponder {
                self.clearButton.isHidden = newValue.isEmpty
            } else {
                self.clearButton.isHidden = true
            }
        }
    }
    
    var prefix: String = "" {
        didSet {
            self.textInputNode.prefixString = NSAttributedString(string: self.prefix, font: Font.regular(14.0), textColor: self.theme.actionSheet.inputTextColor)
        }
    }
    
    var disabled: Bool = false {
        didSet {
            self.clearButton.isHidden = true
        }
    }
    
    private let maxLength: Int
    
    init(theme: PresentationTheme, placeholder: String, maxLength: Int, keyboardType: UIKeyboardType = .default, returnKeyType: UIReturnKeyType = .done) {
        self.theme = theme
        self.maxLength = maxLength
        
        self.backgroundNode = ASImageNode()
        self.backgroundNode.displaysAsynchronously = false
        self.backgroundNode.displayWithoutProcessing = true
        self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 12.0, color: theme.actionSheet.inputHollowBackgroundColor, strokeColor: theme.actionSheet.inputBorderColor, strokeWidth: 1.0)
        
        self.textInputNode = TextField()
        self.textInputNode.font = Font.regular(14.0)
        self.textInputNode.typingAttributes = [NSAttributedString.Key.font: Font.regular(14.0), NSAttributedString.Key.foregroundColor: theme.actionSheet.inputTextColor]
        self.textInputNode.clipsToBounds = true
        self.textInputNode.placeholderString = NSAttributedString(string: placeholder, font: Font.regular(14.0), textColor: theme.actionSheet.secondaryTextColor)
        self.textInputNode.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance
        self.textInputNode.keyboardType = keyboardType
        self.textInputNode.autocapitalizationType = .sentences
        self.textInputNode.returnKeyType = returnKeyType
        self.textInputNode.autocorrectionType = .default
        self.textInputNode.tintColor = theme.actionSheet.controlAccentColor
                
        self.clearButton = HighlightableButtonNode()
        self.clearButton.imageNode.displaysAsynchronously = false
        self.clearButton.imageNode.displayWithoutProcessing = true
        self.clearButton.displaysAsynchronously = false
        self.clearButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: theme.actionSheet.inputClearButtonColor), for: [])
        self.clearButton.isHidden = true
        
        super.init()
                
        self.addSubnode(self.backgroundNode)
        self.addSubnode(self.clearButton)
        
        self.clearButton.addTarget(self, action: #selector(self.clearPressed), forControlEvents: .touchUpInside)
    }
    
    override func didLoad() {
        super.didLoad()
        
        self.textInputNode.delegate = self
        self.view.insertSubview(self.textInputNode, aboveSubview: self.backgroundNode.view)
    }
    
    func selectAll() {
        self.textInputNode.selectWhole()
    }
    
    func updateTheme(_ theme: PresentationTheme) {
        self.theme = theme
        
        self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 12.0, color: self.theme.actionSheet.inputHollowBackgroundColor, strokeColor: self.theme.actionSheet.inputBorderColor, strokeWidth: 1.0)
        self.textInputNode.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance
        self.textInputNode.tintColor = self.theme.actionSheet.controlAccentColor
        self.clearButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: theme.actionSheet.inputClearButtonColor), for: [])
    }
    
    func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
        let backgroundInsets = self.backgroundInsets
        let inputInsets = self.inputInsets
        
        let textFieldHeight = self.calculateTextFieldMetrics(width: width)
        let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom
        
        let backgroundFrame = CGRect(origin: CGPoint(x: backgroundInsets.left, y: backgroundInsets.top), size: CGSize(width: width - backgroundInsets.left - backgroundInsets.right, height: panelHeight - backgroundInsets.top - backgroundInsets.bottom))
        transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
        
        transition.updateFrame(view: self.textInputNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.size.width - inputInsets.left - inputInsets.right - 22.0, height: backgroundFrame.size.height)))
        
        if let image = self.clearButton.image(for: []) {
            transition.updateFrame(node: self.clearButton, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX - 8.0 - image.size.width, y: backgroundFrame.minY + floor((backgroundFrame.size.height - image.size.height) / 2.0)), size: image.size))
        }
        
        return panelHeight
    }
    
    func activateInput() {
        let _ = self.textInputNode.becomeFirstResponder()
    }
    
    func deactivateInput() {
        self.textInputNode.resignFirstResponder()
    }
    
    func textFieldDidBeginEditing(_ textField: UITextField) {
        self.clearButton.isHidden = (textField.text ?? "").isEmpty
    }
    
    func textFieldDidEndEditing(_ textField: UITextField) {
        self.clearButton.isHidden = true
    }
    
    func textFieldDidUpdateText(_ text: String) {
        self.updateTextNodeText(animated: true)
        self.textChanged?(text)
        self.clearButton.isHidden = text.isEmpty
        self.textInputNode.placeholderLabel.isHidden = !text.isEmpty
    }
        
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        if self.disabled {
            return false
        }
        let updatedText = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string)
        if updatedText.count > maxLength {
            self.textInputNode.layer.addShakeAnimation()
            return false
        }
        if string == "\n" {
            self.complete?()
            return false
        }
        
        if self.textInputNode.keyboardType == .asciiCapable {
            var cleanString = string.folding(options: .diacriticInsensitive, locale: .current).replacingOccurrences(of: " ", with: "_")
            
            let filtered = cleanString.unicodeScalars.filter { validIdentifierSet.contains($0) }
            let filteredString = String(String.UnicodeScalarView(filtered))
            
            if cleanString != filteredString {
                cleanString = filteredString
                
                self.textInputNode.layer.addShakeAnimation()
                let hapticFeedback = HapticFeedback()
                hapticFeedback.error()
                DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0, execute: {
                    let _ = hapticFeedback
                })
            }
            
            if cleanString != string {
                var text = textField.text ?? ""
                text.replaceSubrange(text.index(text.startIndex, offsetBy: range.lowerBound) ..< text.index(text.startIndex, offsetBy: range.upperBound), with: cleanString)
                textField.text = text
                if let startPosition = textField.position(from: textField.beginningOfDocument, offset: range.lowerBound + cleanString.count) {
                    let selectionRange = textField.textRange(from: startPosition, to: startPosition)
                    DispatchQueue.main.async {
                        textField.selectedTextRange = selectionRange
                    }
                }
                self.textFieldDidUpdateText(text)
                return false
            }
        }
        
        self.textFieldDidUpdateText(updatedText)
        return true
    }
    
    private func calculateTextFieldMetrics(width: CGFloat) -> CGFloat {
        return 33.0
    }
    
    private func updateTextNodeText(animated: Bool) {
        let backgroundInsets = self.backgroundInsets
        
        let textFieldHeight = self.calculateTextFieldMetrics(width: self.bounds.size.width)
        
        let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom
        if !self.bounds.size.height.isEqual(to: panelHeight) {
            self.updateHeight?()
        }
    }
    
    @objc func clearPressed() {
        self.clearButton.isHidden = true
        
        self.textInputNode.attributedText = nil
        self.updateHeight?()
        self.textChanged?("")
    }
}

private final class ImportStickerPackTitleAlertContentNode: AlertContentNode {
    enum InfoText {
        case info
        case checking
        case available
        case taken
        case generating
    }
    private var theme: PresentationTheme
    private var alertTheme: AlertControllerTheme
    private let strings: PresentationStrings
    private let title: String
    private let text: String
    
    var infoText: InfoText? {
        didSet {
            let text: String
            let color: UIColor
            var activity = false
            if let infoText = self.infoText {
                switch infoText {
                    case .info:
                        text = self.strings.ImportStickerPack_ChooseLinkDescription
                        color = self.alertTheme.primaryColor
                    case .checking:
                        text = self.strings.ImportStickerPack_CheckingLink
                        color = self.alertTheme.secondaryColor
                        activity = true
                    case .available:
                        text = self.strings.ImportStickerPack_LinkAvailable
                        color = self.theme.list.freeTextSuccessColor
                    case .taken:
                        text = self.strings.ImportStickerPack_LinkTaken
                        color = self.theme.list.freeTextErrorColor
                    case .generating:
                        text = self.strings.ImportStickerPack_GeneratingLink
                        color = self.alertTheme.secondaryColor
                        activity = true
                }
                self.activityIndicator.isHidden = !activity
            } else {
                text = self.text
                color = self.alertTheme.primaryColor
            }
            self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(13.0), textColor: color)
            if let size = self.validLayout {
                _ = self.updateLayout(size: size, transition: .immediate)
            }
        }
    }
    
    private let titleNode: ASTextNode
    private let textNode: ASTextNode
    private let activityIndicator: ActivityIndicator
    let inputFieldNode: ImportStickerPackTitleInputFieldNode
    
    private let actionNodesSeparator: ASDisplayNode
    fileprivate let actionNodes: [TextAlertContentActionNode]
    private let actionVerticalSeparators: [ASDisplayNode]
    
    private let disposable = MetaDisposable()
    
    private var validLayout: CGSize?
    
    private let hapticFeedback = HapticFeedback()
    
    var complete: (() -> Void)? {
        didSet {
            self.inputFieldNode.complete = self.complete
        }
    }
        
    override var dismissOnOutsideTap: Bool {
        return self.isUserInteractionEnabled
    }
    
    init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], title: String, text: String, placeholder: String, value: String?, maxLength: Int, asciiOnly: Bool = false) {
        self.strings = strings
        self.alertTheme = theme
        self.theme = ptheme
        self.title = title
        self.text = text
        
        self.titleNode = ASTextNode()
        self.titleNode.maximumNumberOfLines = 2
        self.titleNode.displaysAsynchronously = false
        
        self.textNode = ASTextNode()
        self.textNode.maximumNumberOfLines = 8
        self.textNode.displaysAsynchronously = false
        
        self.activityIndicator = ActivityIndicator(type: .custom(ptheme.rootController.navigationBar.secondaryTextColor, 20.0, 1.5, false), speed: .slow)
        self.activityIndicator.isHidden = true
                
        self.inputFieldNode = ImportStickerPackTitleInputFieldNode(theme: ptheme, placeholder: placeholder, maxLength: maxLength, keyboardType: asciiOnly ? .asciiCapable : .default, returnKeyType: asciiOnly ? .done : .next)
        if asciiOnly {
            self.inputFieldNode.prefix = "t.me/addstickers/"
        }
        self.inputFieldNode.text = value ?? ""
        
        self.actionNodesSeparator = ASDisplayNode()
        self.actionNodesSeparator.isLayerBacked = true
        
        self.actionNodes = actions.map { action -> TextAlertContentActionNode in
            return TextAlertContentActionNode(theme: theme, action: action)
        }
        
        var actionVerticalSeparators: [ASDisplayNode] = []
        if actions.count > 1 {
            for _ in 0 ..< actions.count - 1 {
                let separatorNode = ASDisplayNode()
                separatorNode.isLayerBacked = true
                actionVerticalSeparators.append(separatorNode)
            }
        }
        self.actionVerticalSeparators = actionVerticalSeparators
        
        super.init()
        
        self.addSubnode(self.titleNode)
        self.addSubnode(self.textNode)
        self.addSubnode(self.activityIndicator)
        
        self.addSubnode(self.inputFieldNode)

        self.addSubnode(self.actionNodesSeparator)
        
        for actionNode in self.actionNodes {
            self.addSubnode(actionNode)
        }
        
        for separatorNode in self.actionVerticalSeparators {
            self.addSubnode(separatorNode)
        }
        
        self.inputFieldNode.updateHeight = { [weak self] in
            if let strongSelf = self {
                if let _ = strongSelf.validLayout {
                    strongSelf.requestLayout?(.animated(duration: 0.15, curve: .spring))
                }
            }
        }
        
        self.updateTheme(theme)
    }
    
    deinit {
        self.disposable.dispose()
    }
    
    var value: String {
        return self.inputFieldNode.text
    }

    override func updateTheme(_ theme: AlertControllerTheme) {
        self.alertTheme = theme
        
        self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
        self.textNode.attributedText = NSAttributedString(string: self.text, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center)

        self.actionNodesSeparator.backgroundColor = theme.separatorColor
        for actionNode in self.actionNodes {
            actionNode.updateTheme(theme)
        }
        for separatorNode in self.actionVerticalSeparators {
            separatorNode.backgroundColor = theme.separatorColor
        }
        
        if let size = self.validLayout {
            _ = self.updateLayout(size: size, transition: .immediate)
        }
    }
    
    override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
        var size = size
        size.width = min(size.width, 270.0)
        let measureSize = CGSize(width: size.width - 16.0 * 2.0, height: CGFloat.greatestFiniteMagnitude)
        
        let hadValidLayout = self.validLayout != nil
        
        self.validLayout = size
        
        var origin: CGPoint = CGPoint(x: 0.0, y: 20.0)
        let spacing: CGFloat = 5.0
        
        let titleSize = self.titleNode.measure(measureSize)
        transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize))
        origin.y += titleSize.height + 4.0
        
        let activitySize = CGSize(width: 20.0, height: 20.0)
        let textSize = self.textNode.measure(measureSize)
        let activityInset: CGFloat = self.activityIndicator.isHidden ? 0.0 : activitySize.width + 5.0
        let totalWidth = textSize.width + activityInset
        transition.updateFrame(node: self.activityIndicator, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - totalWidth) / 2.0), y: origin.y - 1.0), size: activitySize))
        transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - totalWidth) / 2.0) + activityInset, y: origin.y), size: textSize))
        
        origin.y += textSize.height + 6.0 + spacing
        
        let actionButtonHeight: CGFloat = 44.0
        var minActionsWidth: CGFloat = 0.0
        let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count))
        let actionTitleInsets: CGFloat = 8.0
        
        var effectiveActionLayout = TextAlertContentActionLayout.horizontal
        for actionNode in self.actionNodes {
            let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight))
            if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 {
                effectiveActionLayout = .vertical
            }
            switch effectiveActionLayout {
                case .horizontal:
                    minActionsWidth += actionTitleSize.width + actionTitleInsets
                case .vertical:
                    minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets)
            }
        }
        
        let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 9.0, right: 18.0)
        
        var contentWidth = max(titleSize.width, minActionsWidth)
        contentWidth = max(contentWidth, 234.0)
        
        var actionsHeight: CGFloat = 0.0
        switch effectiveActionLayout {
            case .horizontal:
                actionsHeight = actionButtonHeight
            case .vertical:
                actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count)
        }
        
        let resultWidth = contentWidth + insets.left + insets.right
        
        let inputFieldWidth = resultWidth
        let inputFieldHeight = self.inputFieldNode.updateLayout(width: inputFieldWidth, transition: transition)
        let inputHeight = inputFieldHeight
        transition.updateFrame(node: self.inputFieldNode, frame: CGRect(x: 0.0, y: origin.y, width: resultWidth, height: inputFieldHeight))
        transition.updateAlpha(node: self.inputFieldNode, alpha: inputHeight > 0.0 ? 1.0 : 0.0)
        
        let resultSize = CGSize(width: resultWidth, height: titleSize.height + textSize.height + spacing + inputHeight + actionsHeight  + insets.top + insets.bottom)
        
        transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
        
        var actionOffset: CGFloat = 0.0
        let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count))
        var separatorIndex = -1
        var nodeIndex = 0
        for actionNode in self.actionNodes {
            if separatorIndex >= 0 {
                let separatorNode = self.actionVerticalSeparators[separatorIndex]
                switch effectiveActionLayout {
                    case .horizontal:
                        transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel)))
                    case .vertical:
                        transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
                }
            }
            separatorIndex += 1
            
            let currentActionWidth: CGFloat
            switch effectiveActionLayout {
                case .horizontal:
                    if nodeIndex == self.actionNodes.count - 1 {
                        currentActionWidth = resultSize.width - actionOffset
                    } else {
                        currentActionWidth = actionWidth
                    }
                case .vertical:
                    currentActionWidth = resultSize.width
            }
            
            let actionNodeFrame: CGRect
            switch effectiveActionLayout {
                case .horizontal:
                    actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
                    actionOffset += currentActionWidth
                case .vertical:
                    actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
                    actionOffset += actionButtonHeight
            }
            
            transition.updateFrame(node: actionNode, frame: actionNodeFrame)
            
            nodeIndex += 1
        }
        
        if !hadValidLayout {
            self.inputFieldNode.activateInput()
        }
        
        return resultSize
    }
    
    func animateError() {
        self.inputFieldNode.layer.addShakeAnimation()
        self.hapticFeedback.error()
    }
}

func importStickerPackTitleController(context: AccountContext, title: String, text: String, placeholder: String, value: String?, maxLength: Int, apply: @escaping (String?) -> Void, cancel: @escaping () -> Void) -> AlertController {
    let presentationData = context.sharedContext.currentPresentationData.with { $0 }
    var dismissImpl: ((Bool) -> Void)?
    var applyImpl: (() -> Void)?
    
    let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
        dismissImpl?(true)
        cancel()
    }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Next, action: {
        applyImpl?()
    })]
    
    let contentNode = ImportStickerPackTitleAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, title: title, text: text, placeholder: placeholder, value: value, maxLength: maxLength)
    contentNode.complete = {
        applyImpl?()
    }
    applyImpl = { [weak contentNode] in
        guard let contentNode = contentNode else {
            return
        }
        let newValue = contentNode.value.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !newValue.isEmpty else {
            return
        }
        
        contentNode.infoText = .generating
        contentNode.inputFieldNode.disabled = true
        contentNode.actionNodes.last?.actionEnabled = false
        
        apply(newValue)
    }
    
    let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode)
    let presentationDataDisposable = context.sharedContext.presentationData.start(next: { [weak controller, weak contentNode] presentationData in
        controller?.theme = AlertControllerTheme(presentationData: presentationData)
        contentNode?.inputFieldNode.updateTheme(presentationData.theme)
    })
    contentNode.actionNodes.last?.actionEnabled = false
    contentNode.inputFieldNode.textChanged = { [weak contentNode] title in
        contentNode?.actionNodes.last?.actionEnabled = !title.trimmingTrailingSpaces().isEmpty
    }
    controller.willDismiss = { [weak contentNode] in
        contentNode?.inputFieldNode.deactivateInput()
    }
    controller.dismissed = { _ in
        presentationDataDisposable.dispose()
    }
    dismissImpl = { [weak controller, weak contentNode] animated in
        contentNode?.inputFieldNode.deactivateInput()
        if animated {
            controller?.dismissAnimated()
        } else {
            controller?.dismiss()
        }
    }
    return controller
}


func importStickerPackShortNameController(context: AccountContext, title: String, text: String, placeholder: String, value: String?, maxLength: Int, existingAlertController: AlertController?, apply: @escaping (String?) -> Void) -> AlertController {
    let presentationData = context.sharedContext.currentPresentationData.with { $0 }
    var dismissImpl: ((Bool) -> Void)?
    var applyImpl: (() -> Void)?
    
    let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
        dismissImpl?(true)
    }), TextAlertAction(type: .defaultAction, title: presentationData.strings.ImportStickerPack_Create, action: {
        applyImpl?()
    })]
    
    let contentNode = ImportStickerPackTitleAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, title: title, text: text, placeholder: placeholder, value: value, maxLength: maxLength, asciiOnly: true)
    contentNode.complete = {
        applyImpl?()
    }
    applyImpl = { [weak contentNode] in
        guard let contentNode = contentNode else {
            return
        }
        let newValue = contentNode.value.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !newValue.isEmpty else {
            return
        }
        
        dismissImpl?(true)
        apply(newValue)
    }
    
    let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode, existingAlertController: existingAlertController)
    let presentationDataDisposable = context.sharedContext.presentationData.start(next: { [weak controller, weak contentNode] presentationData in
        controller?.theme = AlertControllerTheme(presentationData: presentationData)
        contentNode?.inputFieldNode.updateTheme(presentationData.theme)
    })
    let checkDisposable = MetaDisposable()
    let value = value ?? ""
    contentNode.actionNodes.last?.actionEnabled = !value.isEmpty
    if !value.isEmpty {
        Queue.mainQueue().after(0.25) {
            contentNode.inputFieldNode.selectAll()
        }
    }
    contentNode.inputFieldNode.textChanged = { [weak contentNode] value in
        if value.isEmpty {
            checkDisposable.set(nil)
            contentNode?.infoText = .info
            contentNode?.actionNodes.last?.actionEnabled = false
        } else {
            checkDisposable.set((context.engine.stickers.validateStickerSetShortNameInteractive(shortName: value)
            |> deliverOnMainQueue).start(next: { [weak contentNode] result in
                switch result {
                    case .checking:
                        contentNode?.infoText = .checking
                        contentNode?.actionNodes.last?.actionEnabled = false
                    case let .availability(availability):
                        switch availability {
                            case .available:
                                contentNode?.infoText = .available
                                contentNode?.actionNodes.last?.actionEnabled = true
                            case .taken:
                                contentNode?.infoText = .taken
                                contentNode?.actionNodes.last?.actionEnabled = false
                            case .invalid, .purchaseAvailable:
                                contentNode?.infoText = .info
                                contentNode?.actionNodes.last?.actionEnabled = false
                        }
                    case .invalidFormat:
                        contentNode?.infoText = .info
                        contentNode?.actionNodes.last?.actionEnabled = false
                }
            }))
        }
    }
    controller.willDismiss = { [weak contentNode] in
        contentNode?.inputFieldNode.deactivateInput()
    }
    controller.dismissed = { _ in
        presentationDataDisposable.dispose()
    }
    dismissImpl = { [weak controller, weak contentNode] animated in
        contentNode?.inputFieldNode.deactivateInput()
        if animated {
            controller?.dismissAnimated()
        } else {
            controller?.dismiss()
        }
    }
    return controller
}