import Foundation
import UIKit
import AsyncDisplayKit
import Display
import AppBundle
import ChatInputTextViewImpl
import MessageInlineBlockBackgroundView
import TextFormat

public protocol ChatInputTextNodeDelegate: AnyObject {
    func chatInputTextNodeDidUpdateText()
    func chatInputTextNodeShouldReturn() -> Bool
    func chatInputTextNodeDidChangeSelection(dueToEditing: Bool)
    func chatInputTextNodeDidBeginEditing()
    func chatInputTextNodeDidFinishEditing()
    func chatInputTextNodeBackspaceWhileEmpty()
    
    @available(iOS 13.0, *)
    func chatInputTextNodeMenu(forTextRange textRange: NSRange, suggestedActions: [UIMenuElement]) -> UIMenu
    
    func chatInputTextNode(shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool
    func chatInputTextNodeShouldCopy() -> Bool
    func chatInputTextNodeShouldPaste() -> Bool
    
    func chatInputTextNodeShouldRespondToAction(action: Selector) -> Bool
    func chatInputTextNodeTargetForAction(action: Selector) -> ChatInputTextNode.TargetForAction?
}

open class ChatInputTextNode: ASDisplayNode, UITextViewDelegate {
    public final class TargetForAction {
        public let target: Any?
        
        public init(target: Any?) {
            self.target = target
        }
    }
    
    public weak var delegate: ChatInputTextNodeDelegate? {
        didSet {
            self.textView.customDelegate = self.delegate
        }
    }
    
    private var selectionChangedForEditedText: Bool = false
    
    public var textView: ChatInputTextView {
        return self.view as! ChatInputTextView
    }
    
    public var keyboardAppearance: UIKeyboardAppearance {
        get {
            return self.textView.keyboardAppearance
        }
        set {
            guard newValue != self.keyboardAppearance else {
                return
            }
            self.textView.keyboardAppearance = newValue
            self.textView.reloadInputViews()
        }
    }
    
    public var initialPrimaryLanguage: String? {
        get {
            return self.textView.initialPrimaryLanguage
        } set(value) {
            self.textView.initialPrimaryLanguage = value
        }
    }
    
    public func isCurrentlyEmoji() -> Bool {
        return false
    }
    
    public var textInputMode: UITextInputMode? {
        return self.textView.textInputMode
    }
    
    public var selectedRange: NSRange {
        get {
            return self.textView.selectedRange
        } set(value) {
            if self.textView.selectedRange != value {
                self.textView.selectedRange = value
            }
        }
    }
    
    public var attributedText: NSAttributedString? {
        get {
            return self.textView.attributedText
        } set(value) {
            self.textView.attributedText = value
        }
    }
    
    public var isRTL: Bool {
        return self.textView.isRTL
    }
    
    public var selectionRect: CGRect {
        guard let range = self.textView.selectedTextRange else {
            return self.textView.bounds
        }
        return self.textView.firstRect(for: range)
    }
    
    public var textContainerInset: UIEdgeInsets {
        get {
            return self.textView.defaultTextContainerInset
        } set(value) {
            let targetValue = UIEdgeInsets(top: value.top, left: value.left, bottom: value.bottom, right: value.right)
            if self.textView.defaultTextContainerInset != value {
                self.textView.defaultTextContainerInset = targetValue
            }
        }
    }

    public init(disableTiling: Bool = false) {
        super.init()

        self.setViewBlock({
            return ChatInputTextView(disableTiling: disableTiling)
        })
        
        self.textView.delegate = self
        self.textView.shouldRespondToAction = { [weak self] action in
            guard let self, let action else {
                return false
            }
            if let delegate = self.delegate {
                return delegate.chatInputTextNodeShouldRespondToAction(action: action)
            } else {
                return true
            }
        }
        self.textView.targetForAction = { [weak self] action in
            guard let self, let action else {
                return nil
            }
            if let delegate = self.delegate {
                return delegate.chatInputTextNodeTargetForAction(action: action).flatMap { value in
                    return ChatInputTextViewImplTargetForAction(target: value.target)
                }
            } else {
                return nil
            }
        }
    }
    
    public func resetInitialPrimaryLanguage() {
    }
    
    public func textHeightForWidth(_ width: CGFloat, rightInset: CGFloat) -> CGFloat {
        return self.textView.textHeightForWidth(width, rightInset: rightInset)
    }
    
    @objc public func textViewDidBeginEditing(_ textView: UITextView) {
        self.delegate?.chatInputTextNodeDidBeginEditing()
    }

    @objc public func textViewDidEndEditing(_ textView: UITextView) {
        self.delegate?.chatInputTextNodeDidFinishEditing()
    }

    @objc public func textViewDidChange(_ textView: UITextView) {
        self.selectionChangedForEditedText = true
        
        self.delegate?.chatInputTextNodeDidUpdateText()
        
        self.textView.updateTextContainerInset()
    }

    @objc public func textViewDidChangeSelection(_ textView: UITextView) {
        if self.textView.isPreservingSelection {
            return
        }
        
        self.selectionChangedForEditedText = false
        
        DispatchQueue.main.async { [weak self] in
            guard let self else {
                return
            }
            self.delegate?.chatInputTextNodeDidChangeSelection(dueToEditing: self.selectionChangedForEditedText)
        }
    }

    @available(iOS 16.0, *)
    @objc public func textView(_ textView: UITextView, editMenuForTextIn range: NSRange, suggestedActions: [UIMenuElement]) -> UIMenu? {
        return self.delegate?.chatInputTextNodeMenu(forTextRange: range, suggestedActions: suggestedActions)
    }
    
    @objc public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
        guard let delegate = self.delegate else {
            return true
        }
        if self.textView.isPreservingText {
            return false
        }
        return delegate.chatInputTextNode(shouldChangeTextIn: range, replacementText: text)
    }
    
    public func updateLayout(size: CGSize) {
        self.textView.updateLayout(size: size)
    }
}

private final class ChatInputTextContainer: NSTextContainer {
    var rightInset: CGFloat = 0.0
    
    override var isSimpleRectangularTextContainer: Bool {
        return false
    }
    
    override init(size: CGSize) {
        super.init(size: size)
    }
    
    required init(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func lineFragmentRect(forProposedRect proposedRect: CGRect, at characterIndex: Int, writingDirection baseWritingDirection: NSWritingDirection, remaining remainingRect: UnsafeMutablePointer<CGRect>?) -> CGRect {
        var result = super.lineFragmentRect(forProposedRect: proposedRect, at: characterIndex, writingDirection: baseWritingDirection, remaining: remainingRect)
        
        result.origin.x -= 5.0
        result.size.width -= 5.0
        result.size.width -= self.rightInset
        
        if let textStorage = self.layoutManager?.textStorage {
            let string: NSString = textStorage.string as NSString
            let index = Int(characterIndex)
            if index >= 0 && index < string.length {
                let attributes = textStorage.attributes(at: index, effectiveRange: nil)
                let blockQuote = attributes[NSAttributedString.Key(rawValue: "Attribute__Blockquote")] as? ChatTextInputTextQuoteAttribute
                if let blockQuote {
                    result.origin.x += 9.0
                    result.size.width -= 9.0
                    result.size.width -= 7.0
                    
                    var isFirstLine = false
                    if index == 0 {
                        isFirstLine = true
                    } else {
                        let previousAttributes = textStorage.attributes(at: index - 1, effectiveRange: nil)
                        let previousBlockQuote = previousAttributes[NSAttributedString.Key(rawValue: "Attribute__Blockquote")] as? NSObject
                        if let previousBlockQuote {
                            if !blockQuote.isEqual(previousBlockQuote) {
                                isFirstLine = true
                            }
                        } else {
                            isFirstLine = true
                        }
                    }
                    
                    if isFirstLine, case .quote = blockQuote.kind {
                        result.size.width -= 18.0
                    }
                }
            }
        }
        
        result.size.width = max(1.0, result.size.width)
        
        return result
    }
}

public final class ChatInputTextView: ChatInputTextViewImpl, NSLayoutManagerDelegate, NSTextStorageDelegate {
    public final class Theme: Equatable {
        public final class Quote: Equatable {
            public enum LineStyle: Equatable {
                case solid(color: UIColor)
                case doubleDashed(mainColor: UIColor, secondaryColor: UIColor)
                case tripleDashed(mainColor: UIColor, secondaryColor: UIColor, tertiaryColor: UIColor)
            }
            public let background: UIColor
            public let foreground: UIColor
            public let lineStyle: LineStyle
            
            public init(
                background: UIColor,
                foreground: UIColor,
                lineStyle: LineStyle
            ) {
                self.background = background
                self.foreground = foreground
                self.lineStyle = lineStyle
            }
            
            public static func ==(lhs: Quote, rhs: Quote) -> Bool {
                if !lhs.background.isEqual(rhs.background) {
                    return false
                }
                if !lhs.foreground.isEqual(rhs.foreground) {
                    return false
                }
                if lhs.lineStyle != rhs.lineStyle {
                    return false
                }
                return true
            }
        }
        
        public let quote: Quote
        
        public init(quote: Quote) {
            self.quote = quote
        }
        
        public static func ==(lhs: Theme, rhs: Theme) -> Bool {
            if lhs.quote != rhs.quote {
                return false
            }
            return true
        }
    }
    
    override public var attributedText: NSAttributedString? {
        get {
            return super.attributedText
        } set(value) {
            if self.attributedText != value {
                let selectedRange = self.selectedRange
                let preserveSelectedRange = selectedRange.location != self.textStorage.length
                
                super.attributedText = value ?? NSAttributedString()
                
                if preserveSelectedRange {
                    self.isPreservingSelection = true
                    self.selectedRange = selectedRange
                    self.isPreservingSelection = false
                }
                
                self.updateTextContainerInset()
            }
        }
    }
    
    fileprivate var isPreservingSelection: Bool = false
    fileprivate var isPreservingText: Bool = false
    
    public weak var customDelegate: ChatInputTextNodeDelegate?
    
    public var theme: Theme? {
        didSet {
            if self.theme != oldValue {
                self.updateTextElements()
            }
        }
    }
    
    private let customTextContainer: ChatInputTextContainer
    private let customTextStorage: NSTextStorage
    private let customLayoutManager: NSLayoutManager
    
    private let measurementTextContainer: ChatInputTextContainer
    private let measurementTextStorage: NSTextStorage
    private let measurementLayoutManager: NSLayoutManager
    
    private var validLayoutSize: CGSize?
    private var isUpdatingLayout: Bool = false
    
    private var blockQuotes: [Int: QuoteBackgroundView] = [:]
    
    public var defaultTextContainerInset: UIEdgeInsets = UIEdgeInsets() {
        didSet {
            if self.defaultTextContainerInset != oldValue {
                self.updateTextContainerInset()
            }
        }
    }
    
    private var didInitializePrimaryInputLanguage: Bool = false
    public var initialPrimaryLanguage: String?
    
    override public var textInputMode: UITextInputMode? {
        if !self.didInitializePrimaryInputLanguage {
            self.didInitializePrimaryInputLanguage = true
            if let initialPrimaryLanguage = self.initialPrimaryLanguage {
                for inputMode in UITextInputMode.activeInputModes {
                    if let primaryLanguage = inputMode.primaryLanguage, primaryLanguage == initialPrimaryLanguage {
                        return inputMode
                    }
                }
            }
        }
        return super.textInputMode
    }
    
    override public var bounds: CGRect {
        didSet {
            assert(true)
        }
    }
    
    public init(disableTiling: Bool) {
        self.customTextContainer = ChatInputTextContainer(size: CGSize(width: 100.0, height: 100000.0))
        self.customLayoutManager = NSLayoutManager()
        self.customTextStorage = NSTextStorage()
        self.customTextStorage.addLayoutManager(self.customLayoutManager)
        self.customLayoutManager.addTextContainer(self.customTextContainer)
        
        self.measurementTextContainer = ChatInputTextContainer(size: CGSize(width: 100.0, height: 100000.0))
        self.measurementLayoutManager = NSLayoutManager()
        self.measurementTextStorage = NSTextStorage()
        self.measurementTextStorage.addLayoutManager(self.measurementLayoutManager)
        self.measurementLayoutManager.addTextContainer(self.measurementTextContainer)
        
        super.init(frame: CGRect(), textContainer: self.customTextContainer, disableTiling: disableTiling)
        
        self.textContainerInset = UIEdgeInsets()
        self.backgroundColor = nil
        self.isOpaque = false
        
        self.customTextContainer.widthTracksTextView = false
        self.customTextContainer.heightTracksTextView = false
        
        self.measurementTextContainer.widthTracksTextView = false
        self.measurementTextContainer.heightTracksTextView = false
        
        self.customLayoutManager.delegate = self
        self.measurementLayoutManager.delegate = self
        
        self.customTextStorage.delegate = self
        self.measurementTextStorage.delegate = self
        
        self.dropAutocorrectioniOS16 = { [weak self] in
            guard let self else {
                return
            }
            
            self.isPreservingSelection = true
            self.isPreservingText = true
            
            let rangeCopy = self.selectedRange
            var fakeRange = rangeCopy
            if fakeRange.location != 0 {
                fakeRange.location -= 1
            }
            self.unmarkText()
            self.selectedRange = fakeRange
            self.selectedRange = rangeCopy
            
            self.isPreservingSelection = false
            self.isPreservingText = false
        }
        
        self.shouldCopy = { [weak self] in
            guard let self else {
                return true
            }
            return self.customDelegate?.chatInputTextNodeShouldCopy() ?? true
        }
        self.shouldPaste = { [weak self] in
            guard let self else {
                return true
            }
            return self.customDelegate?.chatInputTextNodeShouldPaste() ?? true
        }
        self.shouldReturn = { [weak self] in
            guard let self else {
                return true
            }
            return self.customDelegate?.chatInputTextNodeShouldReturn() ?? true
        }
        self.backspaceWhileEmpty = { [weak self] in
            guard let self else {
                return
            }
            self.customDelegate?.chatInputTextNodeBackspaceWhileEmpty()
        }
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override public func scrollRectToVisible(_ rect: CGRect, animated: Bool) {
        var rect = rect
        if rect.maxY > self.contentSize.height - 8.0 {
            rect = CGRect(origin: CGPoint(x: rect.minX, y: self.contentSize.height - 1.0), size: CGSize(width: rect.width, height: 1.0))
        }
        
        var animated = animated
        if self.isUpdatingLayout {
            animated = false
        }
        
        super.scrollRectToVisible(rect, animated: animated)
    }
    
    @objc public func layoutManager(_ layoutManager: NSLayoutManager, paragraphSpacingBeforeGlyphAt glyphIndex: Int, withProposedLineFragmentRect rect: CGRect) -> CGFloat {
        guard let textStorage = layoutManager.textStorage else {
            return 0.0
        }
        let characterIndex = Int(layoutManager.characterIndexForGlyph(at: glyphIndex))
        if characterIndex < 0 || characterIndex >= textStorage.length {
            return 0.0
        }
        
        let attributes = textStorage.attributes(at: characterIndex, effectiveRange: nil)
        guard let blockQuote = attributes[NSAttributedString.Key("Attribute__Blockquote")] as? NSObject else {
            return 0.0
        }
        
        if characterIndex != 0 {
            let previousAttributes = textStorage.attributes(at: characterIndex - 1, effectiveRange: nil)
            let previousBlockQuote = previousAttributes[NSAttributedString.Key("Attribute__Blockquote")] as? NSObject
            if let previousBlockQuote, blockQuote.isEqual(previousBlockQuote) {
                return 0.0
            }
        }
        
        return 8.0
    }
    
    @objc public func layoutManager(_ layoutManager: NSLayoutManager, paragraphSpacingAfterGlyphAt glyphIndex: Int, withProposedLineFragmentRect rect: CGRect) -> CGFloat {
        guard let textStorage = layoutManager.textStorage else {
            return 0.0
        }
        var characterIndex = Int(layoutManager.characterIndexForGlyph(at: glyphIndex))
        characterIndex -= 1
        if characterIndex < 0 {
            characterIndex = 0
        }
        if characterIndex < 0 || characterIndex >= textStorage.length {
            return 0.0
        }
        
        let attributes = textStorage.attributes(at: characterIndex, effectiveRange: nil)
        guard let blockQuote = attributes[NSAttributedString.Key("Attribute__Blockquote")] as? NSObject else {
            return 0.0
        }
        
        if characterIndex + 1 < textStorage.length {
            let nextAttributes = textStorage.attributes(at: characterIndex + 1, effectiveRange: nil)
            let nextBlockQuote = nextAttributes[NSAttributedString.Key("Attribute__Blockquote")] as? NSObject
            if let nextBlockQuote, blockQuote.isEqual(nextBlockQuote) {
                return 0.0
            }
        }
        
        return 8.0
    }
    
    public func textStorage(_ textStorage: NSTextStorage, didProcessEditing editedMask: NSTextStorage.EditActions, range editedRange: NSRange, changeInLength delta: Int) {
        if textStorage !== self.customTextStorage {
            return
        }
    }
    
    public func layoutManager(_ layoutManager: NSLayoutManager, didCompleteLayoutFor textContainer: NSTextContainer?, atEnd layoutFinishedFlag: Bool) {
        if textContainer !== self.customTextContainer {
            return
        }
        self.updateTextElements()
    }
    
    public func updateTextContainerInset() {
        var result = self.defaultTextContainerInset
        
        var horizontalInsetsUpdated = false
        if self.customTextContainer.rightInset != result.right {
            horizontalInsetsUpdated = true
            self.customTextContainer.rightInset = result.right
        }
        
        result.left = 0.0
        result.right = 0.0
        
        if self.customTextStorage.length != 0 {
            let topAttributes = self.customTextStorage.attributes(at: 0, effectiveRange: nil)
            let bottomAttributes = self.customTextStorage.attributes(at: self.customTextStorage.length - 1, effectiveRange: nil)
            
            if topAttributes[NSAttributedString.Key("Attribute__Blockquote")] != nil {
                result.top += 7.0
            }
            if bottomAttributes[NSAttributedString.Key("Attribute__Blockquote")] != nil {
                result.bottom += 8.0
            }
        }
        
        if self.textContainerInset != result {
            self.textContainerInset = result
        }
        if horizontalInsetsUpdated {
            self.customLayoutManager.invalidateLayout(forCharacterRange: NSRange(location: 0, length: self.customTextStorage.length), actualCharacterRange: nil)
            self.customLayoutManager.ensureLayout(for: self.customTextContainer)
        }
        
        self.updateTextElements()
    }
    
    public func textHeightForWidth(_ width: CGFloat, rightInset: CGFloat) -> CGFloat {
        let measureSize = CGSize(width: width, height: 1000000.0)
        
        if self.measurementTextStorage != self.attributedText || self.measurementTextContainer.size != measureSize || self.measurementTextContainer.rightInset != rightInset {
            self.measurementTextContainer.rightInset = rightInset
            self.measurementTextStorage.setAttributedString(self.attributedText ?? NSAttributedString())
            self.measurementTextContainer.size = measureSize
            self.measurementLayoutManager.invalidateLayout(forCharacterRange: NSRange(location: 0, length: self.measurementTextStorage.length), actualCharacterRange: nil)
            self.measurementLayoutManager.ensureLayout(for: self.measurementTextContainer)
        }
        
        let textSize = self.measurementLayoutManager.usedRect(for: self.measurementTextContainer).size
        
        return textSize.height + self.textContainerInset.top + self.textContainerInset.bottom
    }
    
    public func updateLayout(size: CGSize) {
        let measureSize = CGSize(width: size.width, height: 1000000.0)
        
        if self.textContainer.size != measureSize {
            self.textContainer.size = measureSize
            self.customLayoutManager.invalidateLayout(forCharacterRange: NSRange(location: 0, length: self.customTextStorage.length), actualCharacterRange: nil)
            self.customLayoutManager.ensureLayout(for: self.customTextContainer)
        }
    }
    
    override public func setNeedsLayout() {
        super.setNeedsLayout()
    }
    
    override public func layoutSubviews() {
        let isLayoutUpdated = self.validLayoutSize != self.bounds.size
        self.validLayoutSize = self.bounds.size
        
        self.isUpdatingLayout = isLayoutUpdated
        
        super.layoutSubviews()
        
        self.isUpdatingLayout = false
    }
    
    public func updateTextElements() {
        var blockQuoteIndex = 0
        var validBlockQuotes: [Int] = []
        
        self.textStorage.enumerateAttribute(NSAttributedString.Key(rawValue: "Attribute__Blockquote"), in: NSRange(location: 0, length: self.textStorage.length), using: { value, range, _ in
            if let value = value as? ChatTextInputTextQuoteAttribute {
                let glyphRange = self.customLayoutManager.glyphRange(forCharacterRange: range, actualCharacterRange: nil)
                if self.customLayoutManager.isValidGlyphIndex(glyphRange.location) && self.customLayoutManager.isValidGlyphIndex(glyphRange.location + glyphRange.length - 1) {
                } else {
                    return
                }
                
                let id = blockQuoteIndex
                
                let blockQuote: QuoteBackgroundView
                if let current = self.blockQuotes[id] {
                    blockQuote = current
                } else {
                    blockQuote = QuoteBackgroundView()
                    self.blockQuotes[id] = blockQuote
                    self.insertSubview(blockQuote, at: 0)
                }
                
                var boundingRect = self.customLayoutManager.boundingRect(forGlyphRange: glyphRange, in: self.customTextContainer)
                
                boundingRect = CGRect()
                var startIndex = glyphRange.lowerBound
                while startIndex < glyphRange.upperBound {
                    var effectiveRange = NSRange(location: NSNotFound, length: 0)
                    let rect = self.customLayoutManager.lineFragmentUsedRect(forGlyphAt: startIndex, effectiveRange: &effectiveRange)
                    if boundingRect.isEmpty {
                        boundingRect = rect
                    } else {
                        boundingRect = boundingRect.union(rect)
                    }
                    if effectiveRange.location != NSNotFound {
                        startIndex = max(startIndex + 1, effectiveRange.upperBound)
                    } else {
                        break
                    }
                }
                
                boundingRect.origin.y += self.defaultTextContainerInset.top
                
                boundingRect.origin.x -= 4.0
                boundingRect.size.width += 4.0
                if case .quote = value.kind {
                    boundingRect.size.width += 18.0
                    boundingRect.size.width = min(boundingRect.size.width, self.bounds.width - 18.0)
                }
                boundingRect.size.width = min(boundingRect.size.width, self.bounds.width)
                
                boundingRect.origin.y -= 4.0
                boundingRect.size.height += 8.0
                
                blockQuote.frame = boundingRect
                if let theme = self.theme {
                    blockQuote.update(value: value, size: boundingRect.size, theme: theme.quote)
                }
                
                validBlockQuotes.append(blockQuoteIndex)
                blockQuoteIndex += 1
            }
        })
        
        var removedBlockQuotes: [Int] = []
        for (id, blockQuote) in self.blockQuotes {
            if !validBlockQuotes.contains(id) {
                removedBlockQuotes.append(id)
                blockQuote.removeFromSuperview()
            }
        }
        for id in removedBlockQuotes {
            self.blockQuotes.removeValue(forKey: id)
        }
    }
    
    override public func caretRect(for position: UITextPosition) -> CGRect {
        var result = super.caretRect(for: position)
        
        if "".isEmpty {
            return result
        }
        
        guard let textStorage = self.customLayoutManager.textStorage else {
            return result
        }
        let _ = textStorage
        
        let index = self.offset(from: self.beginningOfDocument, to: position)
        
        let glyphRange = self.customLayoutManager.glyphRange(forCharacterRange: NSMakeRange(index, 1), actualCharacterRange: nil)
        var boundingRect = self.customLayoutManager.boundingRect(forGlyphRange: glyphRange, in: self.customTextContainer)
        
        boundingRect.origin.y += 5.0
        
        result.origin.y = boundingRect.minY
        result.size.height = boundingRect.height
        
        return result
    }
    
    override public func selectionRects(for range: UITextRange) -> [UITextSelectionRect] {
        let sourceRects = super.selectionRects(for: range)
        
        var result: [UITextSelectionRect] = []
        for rect in sourceRects {
            var mappedRect = rect.rect
            //mappedRect.size.height = 10.0
            mappedRect.size.height += 0.0
            result.append(CustomTextSelectionRect(
                rect: mappedRect,
                writingDirection: rect.writingDirection,
                containsStart: rect.containsStart,
                containsEnd: rect.containsEnd,
                isVertical: rect.isVertical
            ))
        }
        
        return result
    }
    
    override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        let result = super.hitTest(point, with: event)
        return result
    }
}

private final class CustomTextSelectionRect: UITextSelectionRect {
    let rectValue: CGRect
    let writingDirectionValue: NSWritingDirection
    let containsStartValue: Bool
    let containsEndValue: Bool
    let isVerticalValue: Bool
    
    override var rect: CGRect {
        return self.rectValue
    }
    override var writingDirection: NSWritingDirection {
        return self.writingDirectionValue
    }
    override var containsStart: Bool {
        return self.containsStartValue
    }
    override var containsEnd: Bool {
        return self.containsEndValue
    }
    override var isVertical: Bool {
        return self.isVerticalValue
    }
    
    init(rect: CGRect, writingDirection: NSWritingDirection, containsStart: Bool, containsEnd: Bool, isVertical: Bool) {
        self.rectValue = rect
        self.writingDirectionValue = writingDirection
        self.containsStartValue = containsStart
        self.containsEndValue = containsEnd
        self.isVerticalValue = isVertical
    }
}

private let quoteIcon: UIImage = {
    return UIImage(bundleImageName: "Chat/Message/ReplyQuoteIcon")!.precomposed().withRenderingMode(.alwaysTemplate)
}()

private final class QuoteBackgroundView: UIView {
    private let backgroundView: MessageInlineBlockBackgroundView
    private let iconView: UIImageView
    
    private var theme: ChatInputTextView.Theme.Quote?
    
    override init(frame: CGRect) {
        self.backgroundView = MessageInlineBlockBackgroundView()
        self.iconView = UIImageView(image: quoteIcon)
        
        super.init(frame: frame)
        
        self.addSubview(self.backgroundView)
        self.addSubview(self.iconView)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func update(value: ChatTextInputTextQuoteAttribute, size: CGSize, theme: ChatInputTextView.Theme.Quote) {
        if self.theme != theme {
            self.theme = theme
            
            self.iconView.tintColor = theme.foreground
        }
        
        self.iconView.frame = CGRect(origin: CGPoint(x: size.width - 4.0 - quoteIcon.size.width, y: 4.0), size: quoteIcon.size)
        
        var primaryColor: UIColor
        var secondaryColor: UIColor?
        var tertiaryColor: UIColor?
        
        switch value.kind {
        case .quote:
            self.iconView.isHidden = false
            
            switch theme.lineStyle {
            case let .solid(color):
                primaryColor = color
            case let .doubleDashed(mainColor, secondaryColorValue):
                primaryColor = mainColor
                secondaryColor = secondaryColorValue
            case let .tripleDashed(mainColor, secondaryColorValue, tertiaryColorValue):
                primaryColor = mainColor
                secondaryColor = secondaryColorValue
                tertiaryColor = tertiaryColorValue
            }
        case .code:
            self.iconView.isHidden = true
            
            primaryColor = .gray
        }
        
        self.backgroundView.update(
            size: size,
            isTransparent: false,
            primaryColor: primaryColor,
            secondaryColor: secondaryColor,
            thirdColor: tertiaryColor,
            pattern: nil,
            animation: .None
        )
        self.backgroundView.frame = CGRect(origin: CGPoint(), size: size)
    }
}