import Foundation
import UIKit
import Display
import AccountContext
import TelegramCore
import Postbox
import SwiftSignalKit
import TelegramPresentationData
import ComponentFlow
import ComponentDisplayAdapters
import AppBundle
import ViewControllerComponent
import EntityKeyboard
import MultilineTextComponent
import UndoUI
import BundleIconComponent
import AnimatedTextComponent
import AudioToolbox
import ListSectionComponent
import PeerAllowedReactionsScreen
import AttachmentUI
import ListMultilineTextFieldItemComponent
import ListActionItemComponent
import ChatEntityKeyboardInputNode
import ChatPresentationInterfaceState
import EmojiSuggestionsComponent
import TextFormat
import TextFieldComponent

final class ComposePollScreenComponent: Component {
    typealias EnvironmentType = ViewControllerComponentContainer.Environment
    
    let context: AccountContext
    let peer: EnginePeer
    let isQuiz: Bool?
    let initialData: ComposePollScreen.InitialData
    let completion: (ComposedPoll) -> Void

    init(
        context: AccountContext,
        peer: EnginePeer,
        isQuiz: Bool?,
        initialData: ComposePollScreen.InitialData,
        completion: @escaping (ComposedPoll) -> Void
    ) {
        self.context = context
        self.peer = peer
        self.isQuiz = isQuiz
        self.initialData = initialData
        self.completion = completion
    }

    static func ==(lhs: ComposePollScreenComponent, rhs: ComposePollScreenComponent) -> Bool {
        return true
    }
    
    private final class PollOption {
        let id: Int
        let textInputState = TextFieldComponent.ExternalState()
        let textFieldTag = NSObject()
        var resetText: String?
        
        init(id: Int) {
            self.id = id
        }
    }
    
    final class View: UIView, UIScrollViewDelegate {
        private let scrollView: UIScrollView
        private var reactionInput: ComponentView<Empty>?
        private let pollTextSection = ComponentView<Empty>()
        private let quizAnswerSection = ComponentView<Empty>()
        
        private let pollOptionsSectionHeader = ComponentView<Empty>()
        private let pollOptionsSectionFooterContainer = UIView()
        private var pollOptionsSectionFooter = ComponentView<Empty>()
        private var pollOptionsSectionContainer: ListSectionContentView
        
        private let pollSettingsSection = ComponentView<Empty>()
        private let actionButton = ComponentView<Empty>()
        
        private var reactionSelectionControl: ComponentView<Empty>?
        
        private var isUpdating: Bool = false
        private var ignoreScrolling: Bool = false
        private var previousHadInputHeight: Bool = false
        
        private var component: ComposePollScreenComponent?
        private(set) weak var state: EmptyComponentState?
        private var environment: EnvironmentType?
        
        private let pollTextInputState = TextFieldComponent.ExternalState()
        private let pollTextFieldTag = NSObject()
        private var resetPollText: String?
        
        private var quizAnswerTextInputState = TextFieldComponent.ExternalState()
        private let quizAnswerTextInputTag = NSObject()
        private var resetQuizAnswerText: String?
        
        private var nextPollOptionId: Int = 0
        private var pollOptions: [PollOption] = []
        private var currentPollOptionsLimitReached: Bool = false
        
        private var isAnonymous: Bool = true
        private var isMultiAnswer: Bool = false
        private var isQuiz: Bool = false
        private var selectedQuizOptionId: Int?
        
        private var currentInputMode: ListComposePollOptionComponent.InputMode = .keyboard
        
        private var inputMediaNodeData: ChatEntityKeyboardInputNode.InputData?
        private var inputMediaNodeDataDisposable: Disposable?
        private var inputMediaNodeStateContext = ChatEntityKeyboardInputNode.StateContext()
        private var inputMediaInteraction: ChatEntityKeyboardInputNode.Interaction?
        private var inputMediaNode: ChatEntityKeyboardInputNode?
        private var inputMediaNodeBackground = SimpleLayer()
        private var inputMediaNodeTargetTag: AnyObject?
        
        private let inputMediaNodeDataPromise = Promise<ChatEntityKeyboardInputNode.InputData>()
        
        private var currentEmojiSuggestionView: ComponentHostView<Empty>?
        
        private var currentEditingTag: AnyObject?
        
        override init(frame: CGRect) {
            self.scrollView = UIScrollView()
            self.scrollView.showsVerticalScrollIndicator = true
            self.scrollView.showsHorizontalScrollIndicator = false
            self.scrollView.scrollsToTop = false
            self.scrollView.delaysContentTouches = false
            self.scrollView.canCancelContentTouches = true
            self.scrollView.contentInsetAdjustmentBehavior = .never
            self.scrollView.alwaysBounceVertical = true
            
            self.pollOptionsSectionContainer = ListSectionContentView(frame: CGRect())
            
            super.init(frame: frame)
            
            self.scrollView.delegate = self
            self.addSubview(self.scrollView)
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        deinit {
            self.inputMediaNodeDataDisposable?.dispose()
        }

        func scrollToTop() {
            self.scrollView.setContentOffset(CGPoint(), animated: true)
        }
        
        func validatedInput() -> ComposedPoll? {
            if self.pollTextInputState.text.length == 0 {
                return nil
            }
            
            let mappedKind: TelegramMediaPollKind
            if self.isQuiz {
                mappedKind = .quiz
            } else {
                mappedKind = .poll(multipleAnswers: self.isMultiAnswer)
            }
            
            var mappedOptions: [TelegramMediaPollOption] = []
            var selectedQuizOption: Data?
            for pollOption in self.pollOptions {
                if pollOption.textInputState.text.length == 0 {
                    continue
                }
                let optionData = "\(mappedOptions.count)".data(using: .utf8)!
                if self.selectedQuizOptionId == pollOption.id {
                    selectedQuizOption = optionData
                }
                var entities: [MessageTextEntity] = []
                for entity in generateChatInputTextEntities(pollOption.textInputState.text) {
                    switch entity.type {
                    case .CustomEmoji:
                        entities.append(entity)
                    default:
                        break
                    }
                }
                
                mappedOptions.append(TelegramMediaPollOption(
                    text: pollOption.textInputState.text.string,
                    entities: entities,
                    opaqueIdentifier: optionData
                ))
            }
            
            if mappedOptions.count < 2 {
                return nil
            }
            
            var mappedCorrectAnswers: [Data]?
            if self.isQuiz {
                if let selectedQuizOption {
                    mappedCorrectAnswers = [selectedQuizOption]
                } else {
                    return nil
                }
            }
            
            var mappedSolution: (String, [MessageTextEntity])?
            if self.isQuiz && self.quizAnswerTextInputState.text.length != 0 {
                var solutionTextEntities: [MessageTextEntity] = []
                for entity in generateChatInputTextEntities(self.quizAnswerTextInputState.text) {
                    switch entity.type {
                    case .CustomEmoji:
                        solutionTextEntities.append(entity)
                    default:
                        break
                    }
                }
                
                mappedSolution = (self.quizAnswerTextInputState.text.string, solutionTextEntities)
            }
            
            var textEntities: [MessageTextEntity] = []
            for entity in generateChatInputTextEntities(self.pollTextInputState.text) {
                switch entity.type {
                case .CustomEmoji:
                    textEntities.append(entity)
                default:
                    break
                }
            }
            
            let usedCustomEmojiFiles: [Int64: TelegramMediaFile] = [:]
            
            return ComposedPoll(
                publicity: self.isAnonymous ? .anonymous : .public,
                kind: mappedKind,
                text: ComposedPoll.Text(string: self.pollTextInputState.text.string, entities: textEntities),
                options: mappedOptions,
                correctAnswers: mappedCorrectAnswers,
                results: TelegramMediaPollResults(
                    voters: nil,
                    totalVoters: nil,
                    recentVoters: [],
                    solution: mappedSolution.flatMap { mappedSolution in
                        return TelegramMediaPollResults.Solution(text: mappedSolution.0, entities: mappedSolution.1)
                    }
                ),
                deadlineTimeout: nil,
                usedCustomEmojiFiles: usedCustomEmojiFiles
            )
        }
        
        func attemptNavigation(complete: @escaping () -> Void) -> Bool {
            guard let component = self.component else {
                return true
            }
            
            let _ = component
            
            return true
        }
        
        func scrollViewDidScroll(_ scrollView: UIScrollView) {
            if !self.ignoreScrolling {
                self.updateScrolling(transition: .immediate)
            }
        }
        
        private func updateScrolling(transition: ComponentTransition) {
            let navigationAlphaDistance: CGFloat = 16.0
            let navigationAlpha: CGFloat = max(0.0, min(1.0, self.scrollView.contentOffset.y / navigationAlphaDistance))
            if let controller = self.environment?.controller(), let navigationBar = controller.navigationBar {
                transition.setAlpha(layer: navigationBar.backgroundNode.layer, alpha: navigationAlpha)
                transition.setAlpha(layer: navigationBar.stripeNode.layer, alpha: navigationAlpha)
            }
        }
        
        func isPanGestureEnabled() -> Bool {
            if self.inputMediaNode != nil {
                return false
            }
            
            for (_, state) in self.collectTextInputStates() {
                if state.isEditing {
                    return false
                }
            }
            
            return true
        }
        
        private func updateInputMediaNode(
            component: ComposePollScreenComponent,
            availableSize: CGSize,
            bottomInset: CGFloat,
            inputHeight: CGFloat,
            effectiveInputHeight: CGFloat,
            metrics: LayoutMetrics,
            deviceMetrics: DeviceMetrics,
            transition: ComponentTransition
        ) -> CGFloat {
            let bottomInset: CGFloat = bottomInset + 8.0
            let bottomContainerInset: CGFloat = 0.0
            let needsInputActivation: Bool = !"".isEmpty
            
            var height: CGFloat = 0.0
            if case .emoji = self.currentInputMode, let inputData = self.inputMediaNodeData {
                if let updatedTag = self.collectTextInputStates().first(where: { $1.isEditing })?.view.currentTag {
                    self.inputMediaNodeTargetTag = updatedTag
                }
                
                let inputMediaNode: ChatEntityKeyboardInputNode
                var inputMediaNodeTransition = transition
                var animateIn = false
                if let current = self.inputMediaNode {
                    inputMediaNode = current
                } else {
                    animateIn = true
                    inputMediaNodeTransition = inputMediaNodeTransition.withAnimation(.none)
                    inputMediaNode = ChatEntityKeyboardInputNode(
                        context: component.context,
                        currentInputData: inputData,
                        updatedInputData: self.inputMediaNodeDataPromise.get(),
                        defaultToEmojiTab: true,
                        opaqueTopPanelBackground: false,
                        useOpaqueTheme: true,
                        interaction: self.inputMediaInteraction,
                        chatPeerId: nil,
                        stateContext: self.inputMediaNodeStateContext
                    )
                    inputMediaNode.clipsToBounds = true
                    
                    inputMediaNode.externalTopPanelContainerImpl = nil
                    inputMediaNode.useExternalSearchContainer = true
                    if inputMediaNode.view.superview == nil {
                        self.inputMediaNodeBackground.removeAllAnimations()
                        self.layer.addSublayer(self.inputMediaNodeBackground)
                        self.addSubview(inputMediaNode.view)
                    }
                    self.inputMediaNode = inputMediaNode
                }
                
                let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
                let presentationInterfaceState = ChatPresentationInterfaceState(
                    chatWallpaper: .builtin(WallpaperSettings()),
                    theme: presentationData.theme,
                    strings: presentationData.strings,
                    dateTimeFormat: presentationData.dateTimeFormat,
                    nameDisplayOrder: presentationData.nameDisplayOrder,
                    limitsConfiguration: component.context.currentLimitsConfiguration.with { $0 },
                    fontSize: presentationData.chatFontSize,
                    bubbleCorners: presentationData.chatBubbleCorners,
                    accountPeerId: component.context.account.peerId,
                    mode: .standard(.default),
                    chatLocation: .peer(id: component.context.account.peerId),
                    subject: nil,
                    peerNearbyData: nil,
                    greetingData: nil,
                    pendingUnpinnedAllMessages: false,
                    activeGroupCallInfo: nil,
                    hasActiveGroupCall: false,
                    importState: nil,
                    threadData: nil,
                    isGeneralThreadClosed: nil,
                    replyMessage: nil,
                    accountPeerColor: nil,
                    businessIntro: nil,
                    starGiftsAvailable: false
                )
                
                self.inputMediaNodeBackground.backgroundColor = presentationData.theme.rootController.navigationBar.opaqueBackgroundColor.cgColor
                
                let heightAndOverflow = inputMediaNode.updateLayout(width: availableSize.width, leftInset: 0.0, rightInset: 0.0, bottomInset: bottomInset, standardInputHeight: deviceMetrics.standardInputHeight(inLandscape: false), inputHeight: inputHeight < 100.0 ? inputHeight - bottomContainerInset : inputHeight, maximumHeight: availableSize.height, inputPanelHeight: 0.0, transition: .immediate, interfaceState: presentationInterfaceState, layoutMetrics: metrics, deviceMetrics: deviceMetrics, isVisible: true, isExpanded: false)
                let inputNodeHeight = heightAndOverflow.0
                let inputNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - inputNodeHeight), size: CGSize(width: availableSize.width, height: inputNodeHeight))
                
                let inputNodeBackgroundFrame = CGRect(origin: CGPoint(x: inputNodeFrame.minX, y: inputNodeFrame.minY - 6.0), size: CGSize(width: inputNodeFrame.width, height: inputNodeFrame.height + 6.0))
                
                if needsInputActivation {
                    let inputNodeFrame = inputNodeFrame.offsetBy(dx: 0.0, dy: inputNodeHeight)
                    ComponentTransition.immediate.setFrame(layer: inputMediaNode.layer, frame: inputNodeFrame)
                    ComponentTransition.immediate.setFrame(layer: self.inputMediaNodeBackground, frame: inputNodeBackgroundFrame)
                }
                
                if animateIn {
                    var targetFrame = inputNodeFrame
                    targetFrame.origin.y = availableSize.height
                    inputMediaNodeTransition.setFrame(layer: inputMediaNode.layer, frame: targetFrame)
                    
                    let inputNodeBackgroundTargetFrame = CGRect(origin: CGPoint(x: targetFrame.minX, y: targetFrame.minY - 6.0), size: CGSize(width: targetFrame.width, height: targetFrame.height + 6.0))
                    
                    inputMediaNodeTransition.setFrame(layer: self.inputMediaNodeBackground, frame: inputNodeBackgroundTargetFrame)
                    
                    transition.setFrame(layer: inputMediaNode.layer, frame: inputNodeFrame)
                    transition.setFrame(layer: self.inputMediaNodeBackground, frame: inputNodeBackgroundFrame)
                } else {
                    inputMediaNodeTransition.setFrame(layer: inputMediaNode.layer, frame: inputNodeFrame)
                    inputMediaNodeTransition.setFrame(layer: self.inputMediaNodeBackground, frame: inputNodeBackgroundFrame)
                }
                
                height = heightAndOverflow.0
            } else {
                self.inputMediaNodeTargetTag = nil
                
                if let inputMediaNode = self.inputMediaNode {
                    self.inputMediaNode = nil
                    var targetFrame = inputMediaNode.frame
                    targetFrame.origin.y = availableSize.height
                    transition.setFrame(view: inputMediaNode.view, frame: targetFrame, completion: { [weak inputMediaNode] _ in
                        if let inputMediaNode {
                            Queue.mainQueue().after(0.3) {
                                inputMediaNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.35, removeOnCompletion: false, completion: { [weak inputMediaNode] _ in
                                    inputMediaNode?.view.removeFromSuperview()
                                })
                            }
                        }
                    })
                    transition.setFrame(layer: self.inputMediaNodeBackground, frame: targetFrame, completion: { [weak self] _ in
                        Queue.mainQueue().after(0.3) {
                            guard let self else {
                                return
                            }
                            if self.currentInputMode == .keyboard {
                                self.inputMediaNodeBackground.animateAlpha(from: 1.0, to: 0.0, duration: 0.35, removeOnCompletion: false, completion: { [weak self] finished in
                                    guard let self else {
                                        return
                                    }
                                    
                                    if finished {
                                        self.inputMediaNodeBackground.removeFromSuperlayer()
                                    }
                                    self.inputMediaNodeBackground.removeAllAnimations()
                                })
                            }
                        }
                    })
                }
            }
            
            /*if needsInputActivation {
                needsInputActivation = false
                Queue.mainQueue().justDispatch {
                    inputPanelView.activateInput()
                }
            }*/
            
            if let controller = self.environment?.controller() as? ComposePollScreen {
                let isTabBarVisible = self.inputMediaNode == nil
                DispatchQueue.main.async { [weak controller] in
                    controller?.updateTabBarVisibility(isTabBarVisible, transition.containedViewLayoutTransition)
                }
            }
            
            return height
        }
        
        private func collectTextInputStates() -> [(view: ListComposePollOptionComponent.View, state: TextFieldComponent.ExternalState)] {
            var textInputStates: [(view: ListComposePollOptionComponent.View, state: TextFieldComponent.ExternalState)] = []
            if let textInputView = self.pollTextSection.findTaggedView(tag: self.pollTextFieldTag) as? ListComposePollOptionComponent.View {
                textInputStates.append((textInputView, self.pollTextInputState))
            }
            for pollOption in self.pollOptions {
                if let textInputView = findTaggedComponentViewImpl(view: self.pollOptionsSectionContainer, tag: pollOption.textFieldTag) as? ListComposePollOptionComponent.View {
                    textInputStates.append((textInputView, pollOption.textInputState))
                }
            }
            if self.isQuiz {
                if let textInputView = self.quizAnswerSection.findTaggedView(tag: self.quizAnswerTextInputTag) as? ListComposePollOptionComponent.View {
                    textInputStates.append((textInputView, self.quizAnswerTextInputState))
                }
            }
            
            return textInputStates
        }
        
        func update(component: ComposePollScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
            self.isUpdating = true
            defer {
                self.isUpdating = false
            }
            
            var alphaTransition = transition
            if !transition.animation.isImmediate {
                alphaTransition = alphaTransition.withAnimation(.curve(duration: 0.25, curve: .easeInOut))
            }
            
            let environment = environment[EnvironmentType.self].value
            let themeUpdated = self.environment?.theme !== environment.theme
            self.environment = environment
            
            if self.component == nil {
                self.isQuiz = component.isQuiz ?? false
                
                self.pollOptions.append(ComposePollScreenComponent.PollOption(
                    id: self.nextPollOptionId
                ))
                self.nextPollOptionId += 1
                self.pollOptions.append(ComposePollScreenComponent.PollOption(
                    id: self.nextPollOptionId
                ))
                self.nextPollOptionId += 1
                
                self.inputMediaNodeDataPromise.set(
                    ChatEntityKeyboardInputNode.inputData(
                        context: component.context,
                        chatPeerId: nil,
                        areCustomEmojiEnabled: true,
                        hasTrending: false,
                        hasSearch: true,
                        hasStickers: false,
                        hasGifs: false,
                        hideBackground: true,
                        sendGif: nil
                    )
                )
                self.inputMediaNodeDataDisposable = (self.inputMediaNodeDataPromise.get()
                |> deliverOnMainQueue).start(next: { [weak self] value in
                    guard let self else {
                        return
                    }
                    self.inputMediaNodeData = value
                })
                
                self.inputMediaInteraction = ChatEntityKeyboardInputNode.Interaction(
                    sendSticker: { _, _, _, _, _, _, _, _, _ in
                        return false
                    },
                    sendEmoji: { _, _, _ in
                        let _ = self
                    },
                    sendGif: { _, _, _, _, _ in
                        return false
                    },
                    sendBotContextResultAsGif: { _, _ , _, _, _, _ in
                        return false
                    },
                    updateChoosingSticker: { _ in
                    },
                    switchToTextInput: { [weak self] in
                        guard let self else {
                            return
                        }
                        self.currentInputMode = .keyboard
                        self.state?.updated(transition: .spring(duration: 0.4))
                    },
                    dismissTextInput: {
                    },
                    insertText: { [weak self] text in
                        guard let self else {
                            return
                        }
                        
                        var found = false
                        for (textInputView, externalState) in self.collectTextInputStates() {
                            if externalState.isEditing {
                                textInputView.insertText(text: text)
                                found = true
                                break
                            }
                        }
                        if !found, let inputMediaNodeTargetTag = self.inputMediaNodeTargetTag {
                            for (textInputView, _) in self.collectTextInputStates() {
                                if textInputView.currentTag === inputMediaNodeTargetTag {
                                    textInputView.insertText(text: text)
                                    found = true
                                    break
                                }
                            }
                        }
                    },
                    backwardsDeleteText: { [weak self] in
                        guard let self else {
                            return
                        }
                        var found = false
                        for (textInputView, externalState) in self.collectTextInputStates() {
                            if externalState.isEditing {
                                textInputView.backwardsDeleteText()
                                found = true
                                break
                            }
                        }
                        if !found, let inputMediaNodeTargetTag = self.inputMediaNodeTargetTag {
                            for (textInputView, _) in self.collectTextInputStates() {
                                if textInputView.currentTag === inputMediaNodeTargetTag {
                                    textInputView.backwardsDeleteText()
                                    found = true
                                    break
                                }
                            }
                        }
                    },
                    openStickerEditor: {
                    },
                    presentController: { [weak self] c, a in
                        guard let self else {
                            return
                        }
                        self.environment?.controller()?.present(c, in: .window(.root), with: a)
                    },
                    presentGlobalOverlayController: { [weak self] c, a in
                        guard let self else {
                            return
                        }
                        self.environment?.controller()?.presentInGlobalOverlay(c, with: a)
                    },
                    getNavigationController: { [weak self] () -> NavigationController? in
                        guard let self else {
                            return nil
                        }
                        guard let controller = self.environment?.controller() as? ComposePollScreen else {
                            return nil
                        }
                        
                        if let navigationController = controller.navigationController as? NavigationController {
                            return navigationController
                        }
                        if let parentController = controller.parentController() {
                            return parentController.navigationController as? NavigationController
                        }
                        return nil
                    },
                    requestLayout: { [weak self] transition in
                        guard let self else {
                            return
                        }
                        if !self.isUpdating {
                            self.state?.updated(transition: ComponentTransition(transition))
                        }
                    }
                )
            }
            
            self.component = component
            self.state = state
            
            let topInset: CGFloat = 24.0
            let bottomInset: CGFloat = 8.0
            let sideInset: CGFloat = 16.0 + environment.safeInsets.left
            let sectionSpacing: CGFloat = 24.0
            
            if themeUpdated {
                self.backgroundColor = environment.theme.list.blocksBackgroundColor
            }
            
            let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
            
            var contentHeight: CGFloat = 0.0
            contentHeight += environment.navigationHeight
            contentHeight += topInset
            
            var pollTextSectionItems: [AnyComponentWithIdentity<Empty>] = []
            pollTextSectionItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListComposePollOptionComponent(
                externalState: self.pollTextInputState,
                context: component.context,
                theme: environment.theme,
                strings: environment.strings,
                resetText: self.resetPollText.flatMap { resetText in
                    return ListComposePollOptionComponent.ResetText(value: NSAttributedString(string: resetText))
                },
                assumeIsEditing: self.inputMediaNodeTargetTag === self.pollTextFieldTag,
                characterLimit: component.initialData.maxPollTextLength,
                emptyLineHandling: .allowed,
                returnKeyAction: { [weak self] in
                    guard let self else {
                        return
                    }
                    if !self.pollOptions.isEmpty {
                        if let pollOptionView = self.pollOptionsSectionContainer.itemViews[self.pollOptions[0].id] {
                            if let pollOptionComponentView = pollOptionView.contents.view as? ListComposePollOptionComponent.View {
                                pollOptionComponentView.activateInput()
                            }
                        }
                    }
                },
                backspaceKeyAction: nil,
                selection: nil,
                inputMode: self.currentInputMode,
                toggleInputMode: { [weak self] in
                    guard let self else {
                        return
                    }
                    switch self.currentInputMode {
                    case .keyboard:
                        self.currentInputMode = .emoji
                    case .emoji:
                        self.currentInputMode = .keyboard
                    }
                    self.state?.updated(transition: .spring(duration: 0.4))
                },
                tag: self.pollTextFieldTag
            ))))
            self.resetPollText = nil
            
            let pollTextSectionSize = self.pollTextSection.update(
                transition: transition,
                component: AnyComponent(ListSectionComponent(
                    theme: environment.theme,
                    header: AnyComponent(MultilineTextComponent(
                        text: .plain(NSAttributedString(
                            string: environment.strings.CreatePoll_TextHeader,
                            font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
                            textColor: environment.theme.list.freeTextColor
                        )),
                        maximumNumberOfLines: 0
                    )),
                    footer: nil,
                    items: pollTextSectionItems
                )),
                environment: {},
                containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
            )
            let pollTextSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: pollTextSectionSize)
            if let pollTextSectionView = self.pollTextSection.view as? ListSectionComponent.View {
                if pollTextSectionView.superview == nil {
                    self.scrollView.addSubview(pollTextSectionView)
                    self.pollTextSection.parentState = state
                }
                transition.setFrame(view: pollTextSectionView, frame: pollTextSectionFrame)
                
                if let itemView = pollTextSectionView.itemView(id: 0) as? ListComposePollOptionComponent.View {
                    itemView.updateCustomPlaceholder(value: environment.strings.CreatePoll_TextPlaceholder, size: itemView.bounds.size, transition: .immediate)
                }
            }
            contentHeight += pollTextSectionSize.height
            contentHeight += sectionSpacing
            
            var pollOptionsSectionItems: [AnyComponentWithIdentity<Empty>] = []
            
            var pollOptionsSectionReadyItems: [ListSectionContentView.ReadyItem] = []
            
            let processPollOptionItem: (Int) -> Void = { i in
                let pollOption = self.pollOptions[i]
                
                let optionId = pollOption.id
                
                var optionSelection: ListComposePollOptionComponent.Selection?
                if self.isQuiz {
                    optionSelection = ListComposePollOptionComponent.Selection(isSelected: self.selectedQuizOptionId == optionId, toggle: { [weak self] in
                        guard let self else {
                            return
                        }
                        self.selectedQuizOptionId = optionId
                        self.state?.updated(transition: .spring(duration: 0.35))
                    })
                }
                
                pollOptionsSectionItems.append(AnyComponentWithIdentity(id: pollOption.id, component: AnyComponent(ListComposePollOptionComponent(
                    externalState: pollOption.textInputState,
                    context: component.context,
                    theme: environment.theme,
                    strings: environment.strings,
                    resetText: pollOption.resetText.flatMap { resetText in
                        return ListComposePollOptionComponent.ResetText(value: NSAttributedString(string: resetText))
                    },
                    assumeIsEditing: self.inputMediaNodeTargetTag === pollOption.textFieldTag,
                    characterLimit: component.initialData.maxPollOptionLength,
                    emptyLineHandling: .notAllowed,
                    returnKeyAction: { [weak self] in
                        guard let self else {
                            return
                        }
                        if let index = self.pollOptions.firstIndex(where: { $0.id == optionId }) {
                            if index == self.pollOptions.count - 1 {
                                self.endEditing(true)
                            } else {
                                if let pollOptionView = self.pollOptionsSectionContainer.itemViews[self.pollOptions[index + 1].id] {
                                    if let pollOptionComponentView = pollOptionView.contents.view as? ListComposePollOptionComponent.View {
                                        pollOptionComponentView.activateInput()
                                    }
                                }
                            }
                        }
                    },
                    backspaceKeyAction: { [weak self] in
                        guard let self else {
                            return
                        }
                        if let index = self.pollOptions.firstIndex(where: { $0.id == optionId }) {
                            if index == 0 {
                                if let textInputView = self.pollTextSection.findTaggedView(tag: self.pollTextFieldTag) as? ListComposePollOptionComponent.View {
                                    textInputView.activateInput()
                                }
                            } else {
                                if let pollOptionView = self.pollOptionsSectionContainer.itemViews[self.pollOptions[index - 1].id] {
                                    if let pollOptionComponentView = pollOptionView.contents.view as? ListComposePollOptionComponent.View {
                                        pollOptionComponentView.activateInput()
                                    }
                                }
                            }
                        }
                    },
                    selection: optionSelection,
                    inputMode: self.currentInputMode,
                    toggleInputMode: { [weak self] in
                        guard let self else {
                            return
                        }
                        switch self.currentInputMode {
                        case .keyboard:
                            self.currentInputMode = .emoji
                        case .emoji:
                            self.currentInputMode = .keyboard
                        }
                        self.state?.updated(transition: .spring(duration: 0.4))
                    },
                    tag: pollOption.textFieldTag
                ))))
                
                let item = pollOptionsSectionItems[i]
                let itemId = item.id
                
                let itemView: ListSectionContentView.ItemView
                var itemTransition = transition
                if let current = self.pollOptionsSectionContainer.itemViews[itemId] {
                    itemView = current
                } else {
                    itemTransition = itemTransition.withAnimation(.none)
                    itemView = ListSectionContentView.ItemView()
                    self.pollOptionsSectionContainer.itemViews[itemId] = itemView
                    itemView.contents.parentState = state
                }
                
                let itemSize = itemView.contents.update(
                    transition: itemTransition,
                    component: item.component,
                    environment: {},
                    containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height)
                )
                
                pollOptionsSectionReadyItems.append(ListSectionContentView.ReadyItem(
                    id: itemId,
                    itemView: itemView,
                    size: itemSize,
                    transition: itemTransition
                ))
            }
            
            for i in 0 ..< self.pollOptions.count {
                processPollOptionItem(i)
            }
            
            if self.pollOptions.count > 2 {
                let lastOption = self.pollOptions[self.pollOptions.count - 1]
                let secondToLastOption = self.pollOptions[self.pollOptions.count - 2]
                
                if !lastOption.textInputState.isEditing && lastOption.textInputState.text.length == 0 && secondToLastOption.textInputState.text.length == 0 {
                    self.pollOptions.removeLast()
                    pollOptionsSectionItems.removeLast()
                    pollOptionsSectionReadyItems.removeLast()
                }
            }
            
            if self.pollOptions.count < 10, let lastOption = self.pollOptions.last {
                if lastOption.textInputState.text.length != 0 {
                    self.pollOptions.append(PollOption(id: self.nextPollOptionId))
                    self.nextPollOptionId += 1
                    processPollOptionItem(self.pollOptions.count - 1)
                }
            }
            
            for i in 0 ..< pollOptionsSectionReadyItems.count {
                let placeholder: String
                if i == pollOptionsSectionReadyItems.count - 1 {
                    placeholder = environment.strings.CreatePoll_AddOption
                } else {
                    placeholder = environment.strings.CreatePoll_OptionPlaceholder
                }
                
                if let itemView = pollOptionsSectionReadyItems[i].itemView.contents.view as? ListComposePollOptionComponent.View {
                    itemView.updateCustomPlaceholder(value: placeholder, size: pollOptionsSectionReadyItems[i].size, transition: pollOptionsSectionReadyItems[i].transition)
                }
            }
            
            let pollOptionsSectionUpdateResult = self.pollOptionsSectionContainer.update(
                configuration: ListSectionContentView.Configuration(
                    theme: environment.theme,
                    displaySeparators: true,
                    extendsItemHighlightToSection: false,
                    background: .all
                ),
                width: availableSize.width - sideInset * 2.0,
                leftInset: 0.0,
                readyItems: pollOptionsSectionReadyItems,
                transition: transition
            )
            
            let sectionHeaderSideInset: CGFloat = 16.0
            let pollOptionsSectionHeaderSize = self.pollOptionsSectionHeader.update(
                transition: .immediate,
                component: AnyComponent(MultilineTextComponent(
                    text: .plain(NSAttributedString(
                        string: environment.strings.CreatePoll_OptionsHeader,
                        font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
                        textColor: environment.theme.list.freeTextColor
                    )),
                    maximumNumberOfLines: 0
                )),
                environment: {},
                containerSize: CGSize(width: availableSize.width - sideInset * 2.0 - sectionHeaderSideInset * 2.0, height: 1000.0)
            )
            let pollOptionsSectionHeaderFrame = CGRect(origin: CGPoint(x: sideInset + sectionHeaderSideInset, y: contentHeight), size: pollOptionsSectionHeaderSize)
            if let pollOptionsSectionHeaderView = self.pollOptionsSectionHeader.view {
                if pollOptionsSectionHeaderView.superview == nil {
                    pollOptionsSectionHeaderView.layer.anchorPoint = CGPoint()
                    self.scrollView.addSubview(pollOptionsSectionHeaderView)
                }
                transition.setPosition(view: pollOptionsSectionHeaderView, position: pollOptionsSectionHeaderFrame.origin)
                pollOptionsSectionHeaderView.bounds = CGRect(origin: CGPoint(), size: pollOptionsSectionHeaderFrame.size)
            }
            contentHeight += pollOptionsSectionHeaderSize.height
            contentHeight += 7.0
            
            let pollOptionsSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: pollOptionsSectionUpdateResult.size)
            if self.pollOptionsSectionContainer.superview == nil {
                self.scrollView.addSubview(self.pollOptionsSectionContainer.externalContentBackgroundView)
                self.scrollView.addSubview(self.pollOptionsSectionContainer)
            }
            transition.setFrame(view: self.pollOptionsSectionContainer, frame: pollOptionsSectionFrame)
            transition.setFrame(view: self.pollOptionsSectionContainer.externalContentBackgroundView, frame: pollOptionsSectionUpdateResult.backgroundFrame.offsetBy(dx: pollOptionsSectionFrame.minX, dy: pollOptionsSectionFrame.minY))
            contentHeight += pollOptionsSectionUpdateResult.size.height
            
            contentHeight += 7.0
            
            let pollOptionsLimitReached = self.pollOptions.count >= 10
            var animatePollOptionsFooterIn = false
            var pollOptionsFooterTransition = transition
            if self.currentPollOptionsLimitReached != pollOptionsLimitReached {
                self.currentPollOptionsLimitReached = pollOptionsLimitReached
                if let pollOptionsSectionFooterView = self.pollOptionsSectionFooter.view {
                    animatePollOptionsFooterIn = true
                    pollOptionsFooterTransition = pollOptionsFooterTransition.withAnimation(.none)
                    alphaTransition.setAlpha(view: pollOptionsSectionFooterView, alpha: 0.0, completion: { [weak pollOptionsSectionFooterView] _ in
                        pollOptionsSectionFooterView?.removeFromSuperview()
                    })
                    self.pollOptionsSectionFooter = ComponentView()
                }
            }
            
            let pollOptionsComponent: AnyComponent<Empty>
            if pollOptionsLimitReached {
                pollOptionsFooterTransition = pollOptionsFooterTransition.withAnimation(.none)
                pollOptionsComponent = AnyComponent(MultilineTextComponent(
                    text: .plain(NSAttributedString(string: environment.strings.CreatePoll_AllOptionsAdded, font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: environment.theme.list.freeTextColor)),
                    maximumNumberOfLines: 0
                ))
            } else {
                let remainingCount = 10 - self.pollOptions.count
                let rawString = environment.strings.CreatePoll_OptionCountFooterFormat(Int32(remainingCount))
                
                var pollOptionsFooterItems: [AnimatedTextComponent.Item] = []
                if let range = rawString.range(of: "{count}") {
                    if range.lowerBound != rawString.startIndex {
                        pollOptionsFooterItems.append(AnimatedTextComponent.Item(
                            id: 0,
                            isUnbreakable: true,
                            content: .text(String(rawString[rawString.startIndex ..< range.lowerBound]))
                        ))
                    }
                    pollOptionsFooterItems.append(AnimatedTextComponent.Item(
                        id: 1,
                        isUnbreakable: true,
                        content: .number(remainingCount, minDigits: 1)
                    ))
                    if range.upperBound != rawString.endIndex {
                        pollOptionsFooterItems.append(AnimatedTextComponent.Item(
                            id: 2,
                            isUnbreakable: true,
                            content: .text(String(rawString[range.upperBound ..< rawString.endIndex]))
                        ))
                    }
                }
                
                pollOptionsComponent = AnyComponent(AnimatedTextComponent(
                    font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
                    color: environment.theme.list.freeTextColor,
                    items: pollOptionsFooterItems
                ))
            }
            
            let pollOptionsSectionFooterSize = self.pollOptionsSectionFooter.update(
                transition: pollOptionsFooterTransition,
                component: pollOptionsComponent,
                environment: {},
                containerSize: CGSize(width: availableSize.width - sideInset * 2.0 - sectionHeaderSideInset * 2.0, height: 1000.0)
            )
            let pollOptionsSectionFooterFrame = CGRect(origin: CGPoint(x: sideInset + sectionHeaderSideInset, y: contentHeight), size: pollOptionsSectionFooterSize)
            
            if self.pollOptionsSectionFooterContainer.superview == nil {
                self.scrollView.addSubview(self.pollOptionsSectionFooterContainer)
            }
            transition.setFrame(view: self.pollOptionsSectionFooterContainer, frame: pollOptionsSectionFooterFrame)
            
            if let pollOptionsSectionFooterView = self.pollOptionsSectionFooter.view {
                if pollOptionsSectionFooterView.superview == nil {
                    pollOptionsSectionFooterView.layer.anchorPoint = CGPoint()
                    self.pollOptionsSectionFooterContainer.addSubview(pollOptionsSectionFooterView)
                }
                pollOptionsFooterTransition.setPosition(view: pollOptionsSectionFooterView, position: CGPoint())
                pollOptionsSectionFooterView.bounds = CGRect(origin: CGPoint(), size: pollOptionsSectionFooterFrame.size)
                if animatePollOptionsFooterIn && !transition.animation.isImmediate {
                    alphaTransition.animateAlpha(view: pollOptionsSectionFooterView, from: 0.0, to: 1.0)
                }
            }
            contentHeight += pollOptionsSectionFooterSize.height
            contentHeight += sectionSpacing
            
            var canBePublic = true
            if case let .channel(channel) = component.peer, case .broadcast = channel.info {
                canBePublic = false
            }
            
            var pollSettingsSectionItems: [AnyComponentWithIdentity<Empty>] = []
            if canBePublic {
                pollSettingsSectionItems.append(AnyComponentWithIdentity(id: "anonymous", component: AnyComponent(ListActionItemComponent(
                    theme: environment.theme,
                    title: AnyComponent(VStack([
                        AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
                            text: .plain(NSAttributedString(
                                string: environment.strings.CreatePoll_Anonymous,
                                font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
                                textColor: environment.theme.list.itemPrimaryTextColor
                            )),
                            maximumNumberOfLines: 1
                        ))),
                    ], alignment: .left, spacing: 2.0)),
                    accessory: .toggle(ListActionItemComponent.Toggle(style: .regular, isOn: self.isAnonymous, action: { [weak self] _ in
                        guard let self else {
                            return
                        }
                        self.isAnonymous = !self.isAnonymous
                        self.state?.updated(transition: .spring(duration: 0.4))
                    })),
                    action: nil
                ))))
            }
            pollSettingsSectionItems.append(AnyComponentWithIdentity(id: "multiAnswer", component: AnyComponent(ListActionItemComponent(
                theme: environment.theme,
                title: AnyComponent(VStack([
                    AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
                        text: .plain(NSAttributedString(
                            string: environment.strings.CreatePoll_MultipleChoice,
                            font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
                            textColor: environment.theme.list.itemPrimaryTextColor
                        )),
                        maximumNumberOfLines: 1
                    ))),
                ], alignment: .left, spacing: 2.0)),
                accessory: .toggle(ListActionItemComponent.Toggle(style: .regular, isOn: self.isMultiAnswer, action: { [weak self] _ in
                    guard let self else {
                        return
                    }
                    self.isMultiAnswer = !self.isMultiAnswer
                    if self.isMultiAnswer {
                        self.isQuiz = false
                    }
                    self.state?.updated(transition: .spring(duration: 0.4))
                })),
                action: nil
            ))))
            pollSettingsSectionItems.append(AnyComponentWithIdentity(id: "quiz", component: AnyComponent(ListActionItemComponent(
                theme: environment.theme,
                title: AnyComponent(VStack([
                    AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
                        text: .plain(NSAttributedString(
                            string: environment.strings.CreatePoll_Quiz,
                            font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
                            textColor: environment.theme.list.itemPrimaryTextColor
                        )),
                        maximumNumberOfLines: 1
                    ))),
                ], alignment: .left, spacing: 2.0)),
                accessory: .toggle(ListActionItemComponent.Toggle(style: .regular, isOn: self.isQuiz, action: { [weak self] _ in
                    guard let self else {
                        return
                    }
                    self.isQuiz = !self.isQuiz
                    if self.isQuiz {
                        self.isMultiAnswer = false
                    }
                    self.state?.updated(transition: .spring(duration: 0.4))
                })),
                action: nil
            ))))
            
            let pollSettingsSectionSize = self.pollSettingsSection.update(
                transition: transition,
                component: AnyComponent(ListSectionComponent(
                    theme: environment.theme,
                    header: nil,
                    footer: AnyComponent(MultilineTextComponent(
                        text: .plain(NSAttributedString(
                            string: environment.strings.CreatePoll_QuizInfo,
                            font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
                            textColor: environment.theme.list.freeTextColor
                        )),
                        maximumNumberOfLines: 0
                    )),
                    items: pollSettingsSectionItems
                )),
                environment: {},
                containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
            )
            let pollSettingsSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: pollSettingsSectionSize)
            if let pollSettingsSectionView = self.pollSettingsSection.view {
                if pollSettingsSectionView.superview == nil {
                    self.scrollView.addSubview(pollSettingsSectionView)
                    self.pollSettingsSection.parentState = state
                }
                transition.setFrame(view: pollSettingsSectionView, frame: pollSettingsSectionFrame)
            }
            contentHeight += pollSettingsSectionSize.height
            
            var quizAnswerSectionHeight: CGFloat = 0.0
            quizAnswerSectionHeight += sectionSpacing
            let quizAnswerSectionSize = self.quizAnswerSection.update(
                transition: transition,
                component: AnyComponent(ListSectionComponent(
                    theme: environment.theme,
                    header: AnyComponent(MultilineTextComponent(
                        text: .plain(NSAttributedString(
                            string: environment.strings.CreatePoll_ExplanationHeader,
                            font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
                            textColor: environment.theme.list.freeTextColor
                        )),
                        maximumNumberOfLines: 0
                    )),
                    footer: AnyComponent(MultilineTextComponent(
                        text: .plain(NSAttributedString(
                            string: environment.strings.CreatePoll_ExplanationInfo,
                            font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
                            textColor: environment.theme.list.freeTextColor
                        )),
                        maximumNumberOfLines: 0
                    )),
                    items: [
                        AnyComponentWithIdentity(id: 0, component: AnyComponent(ListComposePollOptionComponent(
                            externalState: self.quizAnswerTextInputState,
                            context: component.context,
                            theme: environment.theme,
                            strings: environment.strings,
                            resetText: self.resetQuizAnswerText.flatMap { resetText in
                                return ListComposePollOptionComponent.ResetText(value: NSAttributedString(string: resetText))
                            },
                            assumeIsEditing: self.inputMediaNodeTargetTag === self.quizAnswerTextInputTag,
                            characterLimit: component.initialData.maxPollTextLength,
                            emptyLineHandling: .allowed,
                            returnKeyAction: { [weak self] in
                                guard let self else {
                                    return
                                }
                                self.endEditing(true)
                            },
                            backspaceKeyAction: nil,
                            selection: nil,
                            inputMode: self.currentInputMode,
                            toggleInputMode: { [weak self] in
                                guard let self else {
                                    return
                                }
                                switch self.currentInputMode {
                                case .keyboard:
                                    self.currentInputMode = .emoji
                                case .emoji:
                                    self.currentInputMode = .keyboard
                                }
                                self.state?.updated(transition: .spring(duration: 0.4))
                            },
                            tag: self.quizAnswerTextInputTag
                        )))
                    ]
                )),
                environment: {},
                containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
            )
            self.resetQuizAnswerText = nil
            let quizAnswerSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight + quizAnswerSectionHeight), size: quizAnswerSectionSize)
            if let quizAnswerSectionView = self.quizAnswerSection.view as? ListSectionComponent.View {
                if quizAnswerSectionView.superview == nil {
                    self.scrollView.addSubview(quizAnswerSectionView)
                    self.quizAnswerSection.parentState = state
                }
                transition.setFrame(view: quizAnswerSectionView, frame: quizAnswerSectionFrame)
                transition.setAlpha(view: quizAnswerSectionView, alpha: self.isQuiz ? 1.0 : 0.0)
                
                if let itemView = quizAnswerSectionView.itemView(id: 0) as? ListComposePollOptionComponent.View {
                    itemView.updateCustomPlaceholder(value: environment.strings.CreatePoll_Explanation, size: itemView.bounds.size, transition: .immediate)
                }
            }
            quizAnswerSectionHeight += quizAnswerSectionSize.height
            
            if self.isQuiz {
                contentHeight += quizAnswerSectionHeight
            }
            
            var inputHeight: CGFloat = 0.0
            inputHeight += self.updateInputMediaNode(
                component: component,
                availableSize: availableSize,
                bottomInset: environment.safeInsets.bottom,
                inputHeight: 0.0,
                effectiveInputHeight: environment.deviceMetrics.standardInputHeight(inLandscape: false),
                metrics: environment.metrics,
                deviceMetrics: environment.deviceMetrics,
                transition: transition
            )
            if self.inputMediaNode == nil {
                inputHeight = environment.inputHeight
            }
            
            let textInputStates = self.collectTextInputStates()
            
            let previousEditingTag = self.currentEditingTag
            let isEditing: Bool
            if let index = textInputStates.firstIndex(where: { $0.state.isEditing }) {
                isEditing = true
                self.currentEditingTag = textInputStates[index].view.currentTag
            } else {
                isEditing = false
                self.currentEditingTag = nil
            }
            
            if let (_, suggestionTextInputState) = textInputStates.first(where: { $0.state.isEditing && $0.state.currentEmojiSuggestion != nil }), let emojiSuggestion = suggestionTextInputState.currentEmojiSuggestion, emojiSuggestion.disposable == nil {
                emojiSuggestion.disposable = (EmojiSuggestionsComponent.suggestionData(context: component.context, isSavedMessages: false, query: emojiSuggestion.position.value)
                |> deliverOnMainQueue).start(next: { [weak self, weak suggestionTextInputState, weak emojiSuggestion] result in
                    guard let self, let suggestionTextInputState, let emojiSuggestion, suggestionTextInputState.currentEmojiSuggestion === emojiSuggestion else {
                        return
                    }
                    
                    emojiSuggestion.value = result
                    self.state?.updated()
                })
            }
            
            for (_, suggestionTextInputState) in textInputStates {
                var hasTrackingView = suggestionTextInputState.hasTrackingView
                if let currentEmojiSuggestion = suggestionTextInputState.currentEmojiSuggestion, let value = currentEmojiSuggestion.value as? [TelegramMediaFile], value.isEmpty {
                    hasTrackingView = false
                }
                if !suggestionTextInputState.isEditing {
                    hasTrackingView = false
                }
                
                if !hasTrackingView {
                    if let currentEmojiSuggestion = suggestionTextInputState.currentEmojiSuggestion {
                        suggestionTextInputState.currentEmojiSuggestion = nil
                        currentEmojiSuggestion.disposable?.dispose()
                    }
                    
                    if let currentEmojiSuggestionView = self.currentEmojiSuggestionView {
                        self.currentEmojiSuggestionView = nil
                        
                        currentEmojiSuggestionView.alpha = 0.0
                        currentEmojiSuggestionView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak currentEmojiSuggestionView] _ in
                            currentEmojiSuggestionView?.removeFromSuperview()
                        })
                    }
                }
            }
            
            if let (suggestionTextInputView, suggestionTextInputState) = textInputStates.first(where: { $0.state.isEditing && $0.state.currentEmojiSuggestion != nil }), let emojiSuggestion = suggestionTextInputState.currentEmojiSuggestion, let value = emojiSuggestion.value as? [TelegramMediaFile] {
                let currentEmojiSuggestionView: ComponentHostView<Empty>
                if let current = self.currentEmojiSuggestionView {
                    currentEmojiSuggestionView = current
                } else {
                    currentEmojiSuggestionView = ComponentHostView<Empty>()
                    self.currentEmojiSuggestionView = currentEmojiSuggestionView
                    self.addSubview(currentEmojiSuggestionView)
                    
                    currentEmojiSuggestionView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
                }
            
                let globalPosition: CGPoint
                if let textView = suggestionTextInputView.textFieldView {
                    globalPosition = textView.convert(emojiSuggestion.localPosition, to: self)
                } else {
                    globalPosition = .zero
                }
                
                let sideInset: CGFloat = 7.0
                
                let viewSize = currentEmojiSuggestionView.update(
                    transition: .immediate,
                    component: AnyComponent(EmojiSuggestionsComponent(
                        context: component.context,
                        userLocation: .other,
                        theme: EmojiSuggestionsComponent.Theme(theme: environment.theme),
                        animationCache: component.context.animationCache,
                        animationRenderer: component.context.animationRenderer,
                        files: value,
                        action: { [weak self, weak suggestionTextInputView, weak suggestionTextInputState] file in
                            guard let self, let suggestionTextInputView, let suggestionTextInputState, let textView = suggestionTextInputView.textFieldView, let currentEmojiSuggestion = suggestionTextInputState.currentEmojiSuggestion else {
                                return
                            }
                            
                            let _ = self
                            
                            AudioServicesPlaySystemSound(0x450)
                            
                            let inputState = textView.getInputState()
                            let inputText = NSMutableAttributedString(attributedString: inputState.inputText)
                            
                            var text: String?
                            var emojiAttribute: ChatTextInputTextCustomEmojiAttribute?
                            loop: for attribute in file.attributes {
                                switch attribute {
                                case let .CustomEmoji(_, _, displayText, _):
                                    text = displayText
                                    emojiAttribute = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: file.fileId.id, file: file)
                                    break loop
                                default:
                                    break
                                }
                            }
                            
                            if let emojiAttribute = emojiAttribute, let text = text {
                                let replacementText = NSAttributedString(string: text, attributes: [ChatTextInputAttributes.customEmoji: emojiAttribute])
                                
                                let range = currentEmojiSuggestion.position.range
                                let previousText = inputText.attributedSubstring(from: range)
                                inputText.replaceCharacters(in: range, with: replacementText)
                                
                                var replacedUpperBound = range.lowerBound
                                while true {
                                    if inputText.attributedSubstring(from: NSRange(location: 0, length: replacedUpperBound)).string.hasSuffix(previousText.string) {
                                        let replaceRange = NSRange(location: replacedUpperBound - previousText.length, length: previousText.length)
                                        if replaceRange.location < 0 {
                                            break
                                        }
                                        let adjacentString = inputText.attributedSubstring(from: replaceRange)
                                        if adjacentString.string != previousText.string || adjacentString.attribute(ChatTextInputAttributes.customEmoji, at: 0, effectiveRange: nil) != nil {
                                            break
                                        }
                                        inputText.replaceCharacters(in: replaceRange, with: NSAttributedString(string: text, attributes: [ChatTextInputAttributes.customEmoji: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: emojiAttribute.interactivelySelectedFromPackId, fileId: emojiAttribute.fileId, file: emojiAttribute.file)]))
                                        replacedUpperBound = replaceRange.lowerBound
                                    } else {
                                        break
                                    }
                                }
                                
                                let selectionPosition = range.lowerBound + (replacementText.string as NSString).length
                                textView.updateText(inputText, selectionRange: selectionPosition ..< selectionPosition)
                            }
                        }
                    )),
                    environment: {},
                    containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0)
                )
                
                let viewFrame = CGRect(origin: CGPoint(x: min(availableSize.width - sideInset - viewSize.width, max(sideInset, floor(globalPosition.x - viewSize.width / 2.0))), y: globalPosition.y - 4.0 - viewSize.height), size: viewSize)
                currentEmojiSuggestionView.frame = viewFrame
                if let componentView = currentEmojiSuggestionView.componentView as? EmojiSuggestionsComponent.View {
                    componentView.adjustBackground(relativePositionX: floor(globalPosition.x + 10.0))
                }
            }
            
            let combinedBottomInset: CGFloat
            combinedBottomInset = bottomInset + max(environment.safeInsets.bottom, 8.0 + inputHeight)
            contentHeight += combinedBottomInset
            
            var recenterOnTag: AnyObject?
            if let hint = transition.userData(TextFieldComponent.AnimationHint.self), let targetView = hint.view {
                var matches = false
                switch hint.kind {
                case .textChanged:
                    matches = true
                case let .textFocusChanged(isFocused):
                    if isFocused {
                        matches = true
                    }
                }
                
                if matches {
                    for (textView, _) in self.collectTextInputStates() {
                        if targetView.isDescendant(of: textView) {
                            recenterOnTag = textView.currentTag
                            break
                        }
                    }
                }
            }
            if recenterOnTag == nil && self.previousHadInputHeight != (inputHeight > 0.0) {
                for (textView, state) in self.collectTextInputStates() {
                    if state.isEditing {
                        recenterOnTag = textView.currentTag
                        break
                    }
                }
            }
            self.previousHadInputHeight = (inputHeight > 0.0)
            
            self.ignoreScrolling = true
            let previousBounds = self.scrollView.bounds
            let contentSize = CGSize(width: availableSize.width, height: contentHeight)
            if self.scrollView.frame != CGRect(origin: CGPoint(), size: availableSize) {
                self.scrollView.frame = CGRect(origin: CGPoint(), size: availableSize)
            }
            if self.scrollView.contentSize != contentSize {
                self.scrollView.contentSize = contentSize
            }
            let scrollInsets = UIEdgeInsets(top: environment.navigationHeight, left: 0.0, bottom: environment.safeInsets.bottom, right: 0.0)
            if self.scrollView.scrollIndicatorInsets != scrollInsets {
                self.scrollView.scrollIndicatorInsets = scrollInsets
            }
            
            if let recenterOnTag {
                if let targetView = self.collectTextInputStates().first(where: { $0.view.currentTag === recenterOnTag })?.view {
                    let caretRect = targetView.convert(targetView.bounds, to: self.scrollView)
                    var scrollViewBounds = self.scrollView.bounds
                    let minButtonDistance: CGFloat = 16.0
                    if -scrollViewBounds.minY + caretRect.maxY > availableSize.height - combinedBottomInset - minButtonDistance {
                        scrollViewBounds.origin.y = -(availableSize.height - combinedBottomInset - minButtonDistance - caretRect.maxY)
                        if scrollViewBounds.origin.y < 0.0 {
                            scrollViewBounds.origin.y = 0.0
                        }
                    }
                    if self.scrollView.bounds != scrollViewBounds {
                        self.scrollView.bounds = scrollViewBounds
                    }
                }
            }
            if !previousBounds.isEmpty, !transition.animation.isImmediate {
                let bounds = self.scrollView.bounds
                if bounds.maxY != previousBounds.maxY {
                    let offsetY = previousBounds.maxY - bounds.maxY
                    transition.animateBoundsOrigin(view: self.scrollView, from: CGPoint(x: 0.0, y: offsetY), to: CGPoint(), additive: true)
                }
            }
            self.ignoreScrolling = false
            
            self.updateScrolling(transition: transition)
            
            if isEditing {
                if let controller = environment.controller() as? ComposePollScreen {
                    DispatchQueue.main.async { [weak controller] in
                        controller?.requestAttachmentMenuExpansion()
                    }
                }
            }
            
            let isValid = self.validatedInput() != nil
            if let controller = environment.controller() as? ComposePollScreen, let sendButtonItem = controller.sendButtonItem {
                if sendButtonItem.isEnabled != isValid {
                    sendButtonItem.isEnabled = isValid
                }
                
                let controllerTitle = self.isQuiz ? presentationData.strings.CreatePoll_QuizTitle : presentationData.strings.CreatePoll_Title
                if controller.title != controllerTitle {
                    controller.title = controllerTitle
                }
            }
            
            if let currentEditingTag = self.currentEditingTag, previousEditingTag !== currentEditingTag, self.currentInputMode != .keyboard {
                DispatchQueue.main.async { [weak self] in
                    guard let self else {
                        return
                    }
                    self.currentInputMode = .keyboard
                    self.state?.updated(transition: .spring(duration: 0.4))
                }
            }
            
            return availableSize
        }
    }
    
    func makeView() -> View {
        return View()
    }
    
    func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
        return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
    }
}

public class ComposePollScreen: ViewControllerComponentContainer, AttachmentContainable {
    public final class InitialData {
        fileprivate let maxPollTextLength: Int
        fileprivate let maxPollOptionLength: Int
        
        fileprivate init(
            maxPollTextLength: Int,
            maxPollOptionLength: Int
        ) {
            self.maxPollTextLength = maxPollTextLength
            self.maxPollOptionLength = maxPollOptionLength
        }
    }
    
    private let context: AccountContext
    private let completion: (ComposedPoll) -> Void
    private var isDismissed: Bool = false
    
    fileprivate private(set) var sendButtonItem: UIBarButtonItem?
    
    public var isMinimized: Bool = false
    
    public var requestAttachmentMenuExpansion: () -> Void = {
    }
    public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in
    }
    public var parentController: () -> ViewController? = {
        return nil
    }
    public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in
    }
    public var updateTabBarVisibility: (Bool, ContainedViewLayoutTransition) -> Void = { _, _ in
    }
    public var cancelPanGesture: () -> Void = {
    }
    public var isContainerPanning: () -> Bool = {
        return false
    }
    public var isContainerExpanded: () -> Bool = {
        return false
    }
    public var mediaPickerContext: AttachmentMediaPickerContext?
    
    public var isPanGestureEnabled: (() -> Bool)? {
        return { [weak self] in
            guard let self, let componentView = self.node.hostView.componentView as? ComposePollScreenComponent.View else {
                return true
            }
            return componentView.isPanGestureEnabled()
        }
    }
    
    public init(
        context: AccountContext,
        initialData: InitialData,
        peer: EnginePeer,
        isQuiz: Bool?,
        completion: @escaping (ComposedPoll) -> Void
    ) {
        self.context = context
        self.completion = completion
        
        super.init(context: context, component: ComposePollScreenComponent(
            context: context,
            peer: peer,
            isQuiz: isQuiz,
            initialData: initialData,
            completion: completion
        ), navigationBarAppearance: .default, theme: .default)
        
        let presentationData = context.sharedContext.currentPresentationData.with { $0 }
        
        self.title = isQuiz == true ? presentationData.strings.CreatePoll_QuizTitle : presentationData.strings.CreatePoll_Title
        
        self.navigationItem.setLeftBarButton(UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)), animated: false)
        
        let sendButtonItem = UIBarButtonItem(title: presentationData.strings.CreatePoll_Create, style: .done, target: self, action: #selector(self.sendPressed))
        self.sendButtonItem = sendButtonItem
        self.navigationItem.setRightBarButton(sendButtonItem, animated: false)
        sendButtonItem.isEnabled = false
        
        self.scrollToTop = { [weak self] in
            guard let self, let componentView = self.node.hostView.componentView as? ComposePollScreenComponent.View else {
                return
            }
            componentView.scrollToTop()
        }
        
        self.attemptNavigation = { [weak self] complete in
            guard let self, let componentView = self.node.hostView.componentView as? ComposePollScreenComponent.View else {
                return true
            }
            
            return componentView.attemptNavigation(complete: complete)
        }
    }
    
    required public init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    deinit {
    }
    
    public static func initialData(context: AccountContext) -> InitialData {
        return InitialData(
            maxPollTextLength: Int(200),
            maxPollOptionLength: 100
        )
    }
    
    @objc private func cancelPressed() {
        self.dismiss()
    }
    
    @objc private func sendPressed() {
        guard let componentView = self.node.hostView.componentView as? ComposePollScreenComponent.View else {
            return
        }
        if let input = componentView.validatedInput() {
            self.completion(input)
        }
        self.dismiss()
    }
    
    override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
        super.containerLayoutUpdated(layout, transition: transition)
    }
    
    public func isContainerPanningUpdated(_ panning: Bool) {
    }
    
    public func resetForReuse() {
    }
    
    public func prepareForReuse() {
    }
    
    public func requestDismiss(completion: @escaping () -> Void) {
        completion()
    }
    
    public func shouldDismissImmediately() -> Bool {
        return true
    }
}