import Foundation
import UIKit
import AsyncDisplayKit
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import TelegramPresentationData
import AccountContext
import ComponentFlow
import ViewControllerComponent
import EntityKeyboard
import PagerComponent
import FeaturedStickersScreen
import TelegramNotices
import ChatEntityKeyboardInputNode
import ContextUI
import ChatPresentationInterfaceState
import MediaEditor
import EntityKeyboardGifContent
import CameraButtonComponent
import BundleIconComponent
import LottieComponent
import LottieComponentResourceContent
import UndoUI
import GalleryUI
import TextLoadingEffect
import TelegramStringFormatting

private final class StickerSelectionComponent: Component {
    typealias EnvironmentType = Empty
    
    let context: AccountContext
    let theme: PresentationTheme
    let strings: PresentationStrings
    let deviceMetrics: DeviceMetrics
    let topInset: CGFloat
    let bottomInset: CGFloat
    let content: StickerPickerInputData
    let backgroundColor: UIColor
    let separatorColor: UIColor
    let getController: () -> StickerPickerScreen?
    
    init(
        context: AccountContext,
        theme: PresentationTheme,
        strings: PresentationStrings,
        deviceMetrics: DeviceMetrics,
        topInset: CGFloat,
        bottomInset: CGFloat,
        content: StickerPickerInputData,
        backgroundColor: UIColor,
        separatorColor: UIColor,
        getController: @escaping () -> StickerPickerScreen?
    ) {
        self.context = context
        self.theme = theme
        self.strings = strings
        self.deviceMetrics = deviceMetrics
        self.topInset = topInset
        self.bottomInset = bottomInset
        self.content = content
        self.backgroundColor = backgroundColor
        self.separatorColor = separatorColor
        self.getController = getController
    }
    
    public static func ==(lhs: StickerSelectionComponent, rhs: StickerSelectionComponent) -> Bool {
        if lhs.theme !== rhs.theme {
            return false
        }
        if lhs.strings != rhs.strings {
            return false
        }
        if lhs.deviceMetrics != rhs.deviceMetrics {
            return false
        }
        if lhs.topInset != rhs.topInset {
            return false
        }
        if lhs.bottomInset != rhs.bottomInset {
            return false
        }
        if lhs.content != rhs.content {
            return false
        }
        if lhs.backgroundColor != rhs.backgroundColor {
            return false
        }
        if lhs.separatorColor != rhs.separatorColor {
            return false
        }
        return true
    }
    
    final class KeyboardClippingView: UIView {
        var hitEdgeInsets: UIEdgeInsets = .zero
        
        override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
            let bounds = self.bounds.inset(by: self.hitEdgeInsets)
            return bounds.contains(point)
        }
    }
    
    public final class View: UIView {
        fileprivate let keyboardView: ComponentView<Empty>
        private let keyboardClippingView: KeyboardClippingView
        private let panelHostView: PagerExternalTopPanelContainer
        private let panelBackgroundView: BlurredBackgroundView
        private let panelSeparatorView: UIView
        
        private var component: StickerSelectionComponent?
        private weak var state: EmptyComponentState?
        
        private var interaction: ChatEntityKeyboardInputNode.Interaction?
        private var inputNodeInteraction: ChatMediaInputNodeInteraction?
        
        private var searchVisible = false
        private var forceUpdate = false
        
        private var ignoreNextZeroScrollingOffset = false
        private var topPanelScrollingOffset: CGFloat = 0.0
        private var keyboardContentId: AnyHashable?
        
        override init(frame: CGRect) {
            self.keyboardView = ComponentView<Empty>()
            self.keyboardClippingView = KeyboardClippingView()
            self.panelHostView = PagerExternalTopPanelContainer()
            self.panelBackgroundView = BlurredBackgroundView(color: .clear, enableBlur: true)
            self.panelBackgroundView.isUserInteractionEnabled = false
            self.panelSeparatorView = UIView()
            
            super.init(frame: frame)
            
            self.addSubview(self.keyboardClippingView)
            self.addSubview(self.panelBackgroundView)
            self.addSubview(self.panelSeparatorView)
            self.addSubview(self.panelHostView)
            
            self.interaction = ChatEntityKeyboardInputNode.Interaction(
                sendSticker: { [weak self] file, silent, schedule, query, clearInput, sourceView, sourceRect, sourceLayer, _ in
                    if let self, let controller = self.component?.getController() {
                        controller.forEachController { c in
                            if let c = c as? (ViewController & StickerPackScreen) {
                                c.dismiss(animated: true)
                            }
                            return true
                        }
                        controller.window?.forEachController({ c in
                            if let c = c as? (ViewController & StickerPackScreen) {
                                c.dismiss(animated: true)
                            }
                        })
                        if controller.completion(.file(file, .sticker)) {
                            controller.dismiss(animated: true)
                        }
                    }
                    return false
                },
                sendEmoji: { _, _, _ in
                },
                sendGif: { [weak self] file, _, _, _, _ in
                    if let self, let controller = self.component?.getController() {
                        if controller.completion(.video(file.media)) {
                            controller.dismiss(animated: true)
                        }
                    }
                    return false
                },
                sendBotContextResultAsGif: { [weak self] collection, result, _, _, _, _ in
                    if let self, let controller = self.component?.getController() {
                        if case let .internalReference(reference) = result {
                            if let file = reference.file {
                                if controller.completion(.video(file)) {
                                    controller.dismiss(animated: true)
                                }
                            }
                        }
                    }
                    return false
                },
                updateChoosingSticker: { _ in },
                switchToTextInput: {},
                dismissTextInput: {},
                insertText: { _ in
                },
                backwardsDeleteText: {},
                openStickerEditor: {},
                presentController: { [weak self] c, a in
                    if let self, let controller = self.component?.getController() {
                        controller.present(c, in: .window(.root), with: a)
                    }
                },
                presentGlobalOverlayController: { [weak self] c, a in
                    if let self, let controller = self.component?.getController() {
                        controller.presentInGlobalOverlay(c, with: a)
                    }
                },
                getNavigationController: {
                    return nil
                },
                requestLayout: { transition in
                    let _ = transition
                }
            )
            
            self.inputNodeInteraction = ChatMediaInputNodeInteraction(
                navigateToCollectionId: { _ in
                },
                navigateBackToStickers: {
                },
                setGifMode: { _ in
                },
                openSettings: {
                },
                openTrending: { _ in
                },
                dismissTrendingPacks: { _ in
                },
                toggleSearch: { _, _, _ in
                },
                openPeerSpecificSettings: {
                },
                dismissPeerSpecificSettings: {
                },
                clearRecentlyUsedStickers: {
                }
            )
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        deinit {
        }
        
        func scrolledToItemGroup() {
            self.topPanelScrollingOffset = 30.0
            self.ignoreNextZeroScrollingOffset = true
            self.state?.updated(transition: .easeInOut(duration: 0.2))
        }
        
        func update(component: StickerSelectionComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
            self.backgroundColor = component.backgroundColor
            let panelBackgroundColor = component.backgroundColor.withMultipliedAlpha(0.85)
            self.panelBackgroundView.updateColor(color: panelBackgroundColor, transition: .immediate)
            self.panelSeparatorView.backgroundColor = component.separatorColor
            
            self.component = component
            self.state = state
            
            let topPanelHeight: CGFloat = 42.0
            let topInset = component.topInset
            
            let controller = component.getController()
            let defaultToEmoji = controller?.defaultToEmoji ?? false
            
            let context = component.context
            let stickerPeekBehavior = EmojiContentPeekBehaviorImpl(
                context: context,
                forceTheme: controller?.forceDark == true ? defaultDarkColorPresentationTheme : nil,
                interaction: nil,
                chatPeerId: nil,
                present: { c, a in
                }
            )
            
            let isFullscreen = controller?.isFullscreen == true
            let keyboardSize = self.keyboardView.update(
                transition: transition.withUserData(EmojiPagerContentComponent.SynchronousLoadBehavior(isDisabled: true)),
                component: AnyComponent(EntityKeyboardComponent(
                    theme: component.theme,
                    strings: component.strings,
                    isContentInFocus: true,
                    containerInsets: UIEdgeInsets(top: topPanelHeight - 34.0 + topInset, left: 0.0, bottom: component.bottomInset, right: 0.0),
                    topPanelInsets: UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0),
                    emojiContent: component.content.emoji,
                    stickerContent: component.content.stickers,
                    maskContent: nil,
                    gifContent: component.content.gifs,
                    hasRecentGifs: !isFullscreen,
                    availableGifSearchEmojies: [],
                    defaultToEmojiTab: defaultToEmoji,
                    externalTopPanelContainer: self.panelHostView,
                    externalBottomPanelContainer: nil,
                    displayTopPanelBackground: .blur,
                    topPanelExtensionUpdated: { _, _ in
                    },
                    topPanelScrollingOffset: { [weak self] offset, transition in
                        if let self {
                            if self.ignoreNextZeroScrollingOffset && offset == 0.0 {
                            } else {
                                self.ignoreNextZeroScrollingOffset = false
                                self.topPanelScrollingOffset = offset
                            }
                        }
                    },
                    hideInputUpdated: { [weak self] _, searchVisible, transition in
                        guard let self else {
                            return
                        }
                        self.forceUpdate = true
                        self.searchVisible = searchVisible
                        self.state?.updated(transition: transition)
                    },
                    hideTopPanelUpdated: { _, _ in
                    },
                    switchToTextInput: {},
                    switchToGifSubject: { _ in },
                    reorderItems: { _, _ in },
                    makeSearchContainerNode: { [weak self] content in
                        guard let self, let interaction = self.interaction, let inputNodeInteraction = self.inputNodeInteraction, let component = self.component, let controller = component.getController() else {
                            return nil
                        }

                        let mappedMode: ChatMediaInputSearchMode
                        switch content {
                        case .stickers:
                            mappedMode = .sticker
                        case .gifs:
                            mappedMode = .gif
                        }
                        
                        var presentationData = context.sharedContext.currentPresentationData.with { $0 }
                        if controller.forceDark == true {
                            presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme)
                        }
                        let searchContainerNode = PaneSearchContainerNode(
                            context: context,
                            theme: presentationData.theme,
                            strings: presentationData.strings,
                            interaction: interaction,
                            inputNodeInteraction: inputNodeInteraction,
                            mode: mappedMode,
                            stickerActionTitle: presentationData.strings.StickerPack_AddSticker,
                            trendingGifsPromise: self.component?.getController()?.node.trendingGifsPromise ?? Promise(nil),
                            cancel: {
                            },
                            peekBehavior: stickerPeekBehavior
                        )
                        searchContainerNode.openGifContextMenu = { [weak self] item, sourceNode, sourceRect, gesture, isSaved in
                            guard let self, let node = self.component?.getController()?.node else {
                                return
                            }
                            node.openGifContextMenu(file: item.file, contextResult: item.contextResult, sourceView: sourceNode.view, sourceRect: sourceRect, gesture: gesture, isSaved: isSaved)
                        }
                        return searchContainerNode
                    },
                    contentIdUpdated: { [weak self] id in
                        guard let self else {
                            return
                        }
                        self.keyboardContentId = id
                    },
                    deviceMetrics: component.deviceMetrics,
                    hiddenInputHeight: 0.0,
                    inputHeight: 0.0,
                    displayBottomPanel: !isFullscreen,
                    isExpanded: true,
                    clipContentToTopPanel: false,
                    useExternalSearchContainer: false
                )),
                environment: {},
                forceUpdate: self.forceUpdate,
                containerSize: availableSize
            )
            self.forceUpdate = false
            if let keyboardComponentView = self.keyboardView.view {
                if keyboardComponentView.superview == nil {
                    self.keyboardClippingView.addSubview(keyboardComponentView)
                }
                
                if panelBackgroundColor.alpha < 0.01 {
                    self.keyboardClippingView.clipsToBounds = true
                } else {
                    self.keyboardClippingView.clipsToBounds = false
                }
                
                transition.setFrame(view: self.keyboardClippingView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight + topInset), size: CGSize(width: availableSize.width, height: availableSize.height - topPanelHeight - topInset)))
                self.keyboardClippingView.hitEdgeInsets = UIEdgeInsets(top: -topPanelHeight - topInset, left: 0.0, bottom: 0.0, right: 0.0)
                
                transition.setFrame(view: keyboardComponentView, frame: CGRect(origin: CGPoint(x: 0.0, y: -topPanelHeight - topInset), size: keyboardSize))
                transition.setFrame(view: self.panelHostView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight + topInset - 34.0), size: CGSize(width: keyboardSize.width, height: 0.0)))
                
                transition.setFrame(view: self.panelBackgroundView, frame: CGRect(origin: CGPoint(), size: CGSize(width: keyboardSize.width, height: topPanelHeight + topInset)))
                self.panelBackgroundView.update(size: self.panelBackgroundView.bounds.size, transition: transition.containedViewLayoutTransition)
                
                let topPanelAlpha: CGFloat
                if self.searchVisible || self.keyboardContentId == AnyHashable("gifs") {
                    topPanelAlpha = 0.0
                    if isFullscreen, let navigationBar = controller?.navigationBar, navigationBar.alpha > 0.0 {
                        transition.setAlpha(view: navigationBar.view, alpha: 0.0)
                    }
                } else if isFullscreen {
                    topPanelAlpha = 1.0
                    if let navigationBar = controller?.navigationBar, navigationBar.alpha < 1.0 {
                        transition.setAlpha(view: navigationBar.view, alpha: 1.0)
                    }
                } else {
                    topPanelAlpha = max(0.0, min(1.0, (self.topPanelScrollingOffset / 20.0)))
                }
            
                transition.setAlpha(view: self.panelBackgroundView, alpha: topPanelAlpha)
                transition.setAlpha(view: self.panelSeparatorView, alpha: topPanelAlpha)
                
                transition.setFrame(view: self.panelSeparatorView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight + topInset - UIScreenPixel), size: CGSize(width: keyboardSize.width, height: UIScreenPixel)))
            }
            
            return availableSize
        }
        
        public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
            let result = super.hitTest(point, with: event)
            
            if self.searchVisible, let keyboardView = self.keyboardView.view, let keyboardResult = keyboardView.hitTest(self.convert(point, to: keyboardView), with: event) {
                return keyboardResult
            }
            
            return result
        }
    }

    public func makeView() -> View {
        return View(frame: CGRect())
    }
    
    public 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 StickerPickerScreen: ViewController {
    final class Node: ViewControllerTracingNode, ASScrollViewDelegate, ASGestureRecognizerDelegate {
        private var presentationData: PresentationData
        private weak var controller: StickerPickerScreen?
        private let theme: PresentationTheme
        
        let dim: ASDisplayNode
        let wrappingView: UIView
        let containerView: UIView
        let hostView: ComponentHostView<Empty>
        
        fileprivate var content: StickerPickerInputData?
        private let contentDisposable = MetaDisposable()
        private var hasRecentGifsDisposable: Disposable?
        fileprivate let trendingGifsPromise = Promise<ChatMediaInputGifPaneTrendingState?>(nil)
        private var scheduledEmojiContentAnimationHint: EmojiPagerContentComponent.ContentAnimation?
        
        private(set) var isExpanded = false
        private var panGestureRecognizer: UIPanGestureRecognizer?
        private var panGestureArguments: (topInset: CGFloat, offset: CGFloat, scrollView: UIScrollView?, listNode: ListView?)?
        
        private var currentIsVisible: Bool = false
        private var currentLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)?
        
        fileprivate var temporaryDismiss = false
        
        private var gifMode: GifPagerContentComponent.Subject? {
            didSet {
                if let gifMode = self.gifMode, gifMode != oldValue {
                    self.reloadGifContext()
                }
            }
        }
        
        private var gifContext: GifContext? {
            didSet {
                if let gifContext = self.gifContext {
                    self.gifComponent.set(gifContext.component)
                }
            }
        }
        private let gifComponent = Promise<EntityKeyboardGifContent>()
        private var gifInputInteraction: GifPagerContentComponent.InputInteraction?
        
        private struct EmojiSearchResult {
            var groups: [EmojiPagerContentComponent.ItemGroup]
            var id: AnyHashable
            var version: Int
            var isPreset: Bool
        }
        
        private struct EmojiSearchState {
            var result: EmojiSearchResult?
            var isSearching: Bool
            
            init(result: EmojiSearchResult?, isSearching: Bool) {
                self.result = result
                self.isSearching = isSearching
            }
        }
        
        private let emojiSearchDisposable = MetaDisposable()
        private let emojiSearchState = Promise<EmojiSearchState>(EmojiSearchState(result: nil, isSearching: false))
        private var emojiSearchStateValue = EmojiSearchState(result: nil, isSearching: false) {
            didSet {
                self.emojiSearchState.set(.single(self.emojiSearchStateValue))
            }
        }
        
        private let stickerSearchDisposable = MetaDisposable()
        private let stickerSearchState = Promise<EmojiSearchState>(EmojiSearchState(result: nil, isSearching: false))
        private var stickerSearchStateValue = EmojiSearchState(result: nil, isSearching: false) {
            didSet {
                self.stickerSearchState.set(.single(self.stickerSearchStateValue))
            }
        }
        
        private var storyStickersContentView: StoryStickersContentView?
                
        init(context: AccountContext, controller: StickerPickerScreen, theme: PresentationTheme) {
            self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
            self.controller = controller
            self.theme = theme
            
            self.dim = ASDisplayNode()
            self.dim.alpha = 0.0
            self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.25)
            
            self.wrappingView = SparseContainerView()
            self.containerView = SparseContainerView()
            self.hostView = ComponentHostView()
            
            super.init()
                        
            self.containerView.clipsToBounds = true
            self.containerView.backgroundColor = .clear
            
            if !controller.isFullscreen {
                self.addSubnode(self.dim)
            }
            
            self.view.addSubview(self.wrappingView)
            self.wrappingView.addSubview(self.containerView)
            self.containerView.addSubview(self.hostView)
            
            if controller.hasInteractiveStickers {
                self.storyStickersContentView = StoryStickersContentView(
                    context: context,
                    weather: controller.weather
                )
                self.storyStickersContentView?.locationAction = { [weak self] in
                    self?.controller?.presentLocationPicker()
                }
                self.storyStickersContentView?.audioAction = { [weak self] in
                    self?.controller?.presentAudioPicker()
                }
                self.storyStickersContentView?.reactionAction = { [weak self] in
                    self?.controller?.addReaction()
                }
                self.storyStickersContentView?.linkAction = { [weak self] in
                    guard let self, let controller = self.controller else {
                        return
                    }
                    if controller.context.isPremium {
                        controller.addLink()
                    } else {
                        self.presentLinkPremiumSuggestion()
                    }
                }
                self.storyStickersContentView?.weatherAction = { [weak self] in
                    self?.controller?.addWeather()
                }
            }
            
            let gifItems: Signal<EntityKeyboardGifContent?, NoError>
            if controller.hasGifs {
                let hasRecentGifs = context.engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: Namespaces.OrderedItemList.CloudRecentGifs))
                |> map { savedGifs -> Bool in
                    return !savedGifs.isEmpty
                }
                
                self.hasRecentGifsDisposable = (hasRecentGifs
                |> deliverOnMainQueue).start(next: { [weak self] hasRecentGifs in
                    guard let strongSelf = self else {
                        return
                    }
                    
                    if let gifMode = strongSelf.gifMode {
                        if !hasRecentGifs, case .recent = gifMode {
                            strongSelf.gifMode = .trending
                        }
                    } else {
                        strongSelf.gifMode = hasRecentGifs ? .recent : .trending
                    }
                }).strict()
                
                self.trendingGifsPromise.set(.single(nil))
                self.trendingGifsPromise.set(paneGifSearchForQuery(context: context, query: "", offset: nil, incompleteResults: true, delayRequest: false, updateActivity: nil)
                |> map { items -> ChatMediaInputGifPaneTrendingState? in
                    if let items = items {
                        return ChatMediaInputGifPaneTrendingState(files: items.files, nextOffset: items.nextOffset)
                    } else {
                        return nil
                    }
                })
                
                let gifInputInteraction = GifPagerContentComponent.InputInteraction(
                    performItemAction: { [weak self] item, view, rect in
                        guard let self, let controller = self.controller else {
                            return
                        }
                        if controller.completion(.video(item.file.media)) {
                            controller.dismiss(animated: true)
                        }
                    },
                    openGifContextMenu: { [weak self] item, sourceView, sourceRect, gesture, isSaved in
                        guard let self else {
                            return
                        }
                        self.openGifContextMenu(file: item.file, contextResult: item.contextResult, sourceView: sourceView, sourceRect: sourceRect, gesture: gesture, isSaved: isSaved)
                    },
                    loadMore: { [weak self] token in
                        guard let strongSelf = self, let gifContext = strongSelf.gifContext else {
                            return
                        }
                        gifContext.loadMore(token: token)
                    },
                    openSearch: { [weak self] in
                        if let self, let componentView = self.hostView.componentView as? StickerSelectionComponent.View {
                            if let pagerView = componentView.keyboardView.view as? EntityKeyboardComponent.View {
                                pagerView.openSearch()
                            }
                            self.update(isExpanded: true, transition: .animated(duration: 0.4, curve: .spring))
                        }
                    },
                    updateSearchQuery: { [weak self] query in
                        guard let self else {
                            return
                        }
                        if let query {
                            self.gifMode = .emojiSearch(query)
                        } else {
                            self.gifMode = .recent
                        }
                    },
                    hideBackground: true,
                    hasSearch: true
                )
                self.gifInputInteraction = gifInputInteraction
                
                gifItems = .single(EntityKeyboardGifContent(
                    hasRecentGifs: true,
                    component: GifPagerContentComponent(
                        context: context,
                        inputInteraction: gifInputInteraction,
                        subject: .recent,
                        items: [],
                        isLoading: false,
                        loadMoreToken: nil,
                        displaySearchWithPlaceholder: nil,
                        searchCategories: nil,
                        searchInitiallyHidden: true,
                        searchState: .empty(hasResults: false),
                        hideBackground: true
                    )
                ))
            } else {
                gifItems = .single(nil)
            }
            
            let data = combineLatest(
                queue: Queue.mainQueue(),
                controller.inputData,
                gifItems |> then(self.gifComponent.get() |> map(Optional.init)),
                self.stickerSearchState.get(),
                self.emojiSearchState.get()
            )
                        
            self.contentDisposable.set(data.start(next: { [weak self] inputData, gifData, stickerSearchState, emojiSearchState in
                if let strongSelf = self {
                    guard var inputData = inputData as? StickerPickerInputData else {
                        return
                    }
                                        
                    let presentationData = strongSelf.presentationData
                    inputData.gifs = gifData?.component
                    
                    if let emoji = inputData.emoji {
                        if let emojiSearchResult = emojiSearchState.result {
                            var emptySearchResults: EmojiPagerContentComponent.EmptySearchResults?
                            if !emojiSearchResult.groups.contains(where: { !$0.items.isEmpty || $0.fillWithLoadingPlaceholders }) {
                                emptySearchResults = EmojiPagerContentComponent.EmptySearchResults(
                                    text: presentationData.strings.EmojiSearch_SearchEmojiEmptyResult,
                                    iconFile: nil
                                )
                            }
                            
                            let defaultSearchState: EmojiPagerContentComponent.SearchState = emojiSearchResult.isPreset ? .active : .empty(hasResults: true)
                            inputData.emoji = emoji.withUpdatedItemGroups(panelItemGroups: emoji.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: emojiSearchResult.id, version: emojiSearchResult.version), emptySearchResults: emptySearchResults, searchState: emojiSearchState.isSearching ? .searching : defaultSearchState)
                        } else if emojiSearchState.isSearching {
                            inputData.emoji = emoji.withUpdatedItemGroups(panelItemGroups: emoji.panelItemGroups, contentItemGroups: emoji.contentItemGroups, itemContentUniqueId: emoji.itemContentUniqueId, emptySearchResults: emoji.emptySearchResults, searchState: .searching)
                        }
                    }
                    
                    if let stickers = inputData.stickers {
                        if let stickerSearchResult = stickerSearchState.result {
                            var stickerSearchResults: EmojiPagerContentComponent.EmptySearchResults?
                            if !stickerSearchResult.groups.contains(where: { !$0.items.isEmpty || $0.fillWithLoadingPlaceholders }) {
                                stickerSearchResults = EmojiPagerContentComponent.EmptySearchResults(
                                    text: presentationData.strings.EmojiSearch_SearchStickersEmptyResult,
                                    iconFile: nil
                                )
                            }
                            
                            let defaultSearchState: EmojiPagerContentComponent.SearchState = stickerSearchResult.isPreset ? .active : .empty(hasResults: true)
                            inputData.stickers = stickers.withUpdatedItemGroups(panelItemGroups: stickers.panelItemGroups, contentItemGroups: stickerSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: stickerSearchResult.id, version: stickerSearchResult.version), emptySearchResults: stickerSearchResults, searchState: stickerSearchState.isSearching ? .searching : defaultSearchState)
                        } else if stickerSearchState.isSearching {
                            inputData.stickers = stickers.withUpdatedItemGroups(panelItemGroups: stickers.panelItemGroups, contentItemGroups: stickers.contentItemGroups, itemContentUniqueId: stickers.itemContentUniqueId, emptySearchResults: stickers.emptySearchResults, searchState: .searching)
                        }
                    }
                    
                    strongSelf.updateContent(inputData)
                }
            }))
        }
        
        deinit {
            self.contentDisposable.dispose()
            self.emojiSearchDisposable.dispose()
            self.stickerSearchDisposable.dispose()
            self.hasRecentGifsDisposable?.dispose()
        }
        
        private func reloadGifContext() {
            if let gifInputInteraction = self.gifInputInteraction, let gifMode = self.gifMode, let context = self.controller?.context {
                self.gifContext = GifContext(context: context, subject: gifMode, gifInputInteraction: gifInputInteraction, trendingGifs: self.trendingGifsPromise.get())
            }
        }
        
        fileprivate func openGifContextMenu(file: FileMediaReference, contextResult: (ChatContextResultCollection, ChatContextResult)?, sourceView: UIView, sourceRect: CGRect, gesture: ContextGesture, isSaved: Bool) {
            guard let controller = self.controller else {
                return
            }
            let context = controller.context
            
            let canSaveGif: Bool
            if file.media.fileId.namespace == Namespaces.Media.CloudFile {
                canSaveGif = true
            } else {
                canSaveGif = false
            }
            
            let _ = (context.engine.stickers.isGifSaved(id: file.media.fileId)
            |> deliverOnMainQueue).start(next: { [weak self] isGifSaved in
                var isGifSaved = isGifSaved
                if !canSaveGif {
                    isGifSaved = false
                }
                let presentationData = context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkPresentationTheme)
                
                let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: PeerId(0), namespace: Namespaces.Message.Local, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [file.media], peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
                
                let gallery = GalleryController(context: context, source: .standaloneMessage(message, nil), streamSingleVideo: true, replaceRootController: { _, _ in
                }, baseNavigationController: nil)
                gallery.setHintWillBePresentedInPreviewingContext(true)
                
                var items: [ContextMenuItem] = []
                items.append(.action(ContextMenuActionItem(text: presentationData.strings.MediaEditor_AddGif, icon: { theme in
                    return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.actionSheet.primaryTextColor)
                }, action: { [weak self] _, f in
                    f(.default)
                    if let self, let controller = self.controller {
                        if isSaved {
                            if controller.completion(.video(file.media)) {
                                self.controller?.dismiss(animated: true)
                            }
                        } else if let (_, result) = contextResult {
                            if case let .internalReference(reference) = result {
                                if let file = reference.file {
                                    if controller.completion(.video(file)) {
                                        self.controller?.dismiss(animated: true)
                                    }
                                }
                            }
                        }
                    }
                })))
                
                if isSaved || isGifSaved {
                    items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_ContextMenuDelete, textColor: .destructive, icon: { theme in
                        return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
                    }, action: { _, f in
                        f(.dismissWithoutContent)
                        
                        let _ = removeSavedGif(postbox: context.account.postbox, mediaId: file.media.fileId).start()
                    })))
                } else if canSaveGif && !isGifSaved {
                    items.append(.action(ContextMenuActionItem(text: presentationData.strings.Preview_SaveGif, icon: { theme in
                        return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.actionSheet.primaryTextColor)
                    }, action: { [weak self] _, f in
                        f(.dismissWithoutContent)
                        
                        guard let self else {
                            return
                        }
                        
                        let presentationData = context.sharedContext.currentPresentationData.with { $0 }
                        let _ = (toggleGifSaved(account: context.account, fileReference: file, saved: true)
                        |> deliverOnMainQueue).start(next: { [weak self] result in
                            guard let controller = self?.controller else {
                                return
                            }
                            switch result {
                            case .generic:
                                controller.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: nil, text: presentationData.strings.Gallery_GifSaved, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
                            case let .limitExceeded(limit, premiumLimit):
                                let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
                                let text: String
                                if limit == premiumLimit || premiumConfiguration.isPremiumDisabled {
                                    text = presentationData.strings.Premium_MaxSavedGifsFinalText
                                } else {
                                    text = presentationData.strings.Premium_MaxSavedGifsText("\(premiumLimit)").string
                                }
                                controller.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: presentationData.strings.Premium_MaxSavedGifsTitle("\(limit)").string, text: text, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { [weak controller] action in
                                    if case .info = action, let controller {
                                        let premiumController = context.sharedContext.makePremiumIntroController(context: context, source: .savedGifs, forceDark: controller.forceDark, dismissed: nil)
                                        controller.pushController(premiumController)
                                        return true
                                    }
                                    return false
                                }), in: .window(.root))
                            }
                        })
                    })))
                }
                
                let contextController = ContextController(presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: gallery, sourceView: sourceView, sourceRect: sourceRect)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
                controller.presentInGlobalOverlay(contextController)
            })
        }
        
        func updateContent(_ content: StickerPickerInputData) {
            self.content = content
            
            guard let controller = self.controller else {
                return
            }
                        
            content.emoji?.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction(
                performItemAction: { [weak self] groupId, item, _, _, _, _ in
                    guard let strongSelf = self, let controller = strongSelf.controller else {
                        return
                    }
                    let context = controller.context
                    if groupId == AnyHashable("featuredTop"), let file = item.itemFile {
                        let _ = (
                        combineLatest(
                            ChatEntityKeyboardInputNode.hasPremium(context: context, chatPeerId: controller.context.account.peerId, premiumIfSavedMessages: true),
                            ChatEntityKeyboardInputNode.hasPremium(context: context, chatPeerId: controller.context.account.peerId, premiumIfSavedMessages: false)
                        )
                        |> take(1)
                        |> deliverOnMainQueue).start(next: { [weak self] hasPremium, hasGlobalPremium in
                            guard let self else {
                                return
                            }
                            
                            let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks)
                            let _ = (combineLatest(
                                context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000),
                                context.account.postbox.combinedView(keys: [viewKey])
                            )
                            |> take(1)
                            |> deliverOnMainQueue).start(next: { [weak self] emojiPacksView, views in
                                guard let view = views.views[viewKey] as? OrderedItemListView else {
                                    return
                                }
                                guard let self else {
                                    return
                                }
                                                                
                                var installedCollectionIds = Set<ItemCollectionId>()
                                for (id, _, _) in emojiPacksView.collectionInfos {
                                    installedCollectionIds.insert(id)
                                }
                                
                                let stickerPacks = view.items.map({ $0.contents.get(FeaturedStickerPackItem.self)! }).filter({
                                    !installedCollectionIds.contains($0.info.id)
                                })
                                
                                for featuredStickerPack in stickerPacks {
                                    if featuredStickerPack.topItems.contains(where: { $0.file.fileId == file.fileId }) {
                                        if let componentView = self.hostView.componentView as? StickerSelectionComponent.View {
                                            if let pagerView = componentView.keyboardView.view as? EntityKeyboardComponent.View, let emojiInputInteraction = self.content?.emoji?.inputInteractionHolder.inputInteraction, let controller = self.controller {
                                                pagerView.openCustomSearch(content: EmojiSearchContent(
                                                    context: context,
                                                    forceTheme: controller.forceDark ? defaultDarkPresentationTheme : nil,
                                                    items: stickerPacks,
                                                    initialFocusId: featuredStickerPack.info.id,
                                                    hasPremiumForUse: hasPremium,
                                                    hasPremiumForInstallation: hasGlobalPremium,
                                                    parentInputInteraction: emojiInputInteraction
                                                ))
                                            }
                                        }
                                        break
                                    }
                                }
                            })
                        })
                    } else if let file = item.itemFile {
                        if controller.completion(.file(.standalone(media: file), .sticker)) {
                            controller.dismiss(animated: true)
                        }
                    } else if case let .staticEmoji(emoji) = item.content {
                        if let image = generateImage(CGSize(width: 256.0, height: 256.0), scale: 1.0, rotatedContext: { size, context in
                            context.clear(CGRect(origin: .zero, size: size))

                            let attributedString = NSAttributedString(string: emoji, attributes: [NSAttributedString.Key.font: Font.regular(200), NSAttributedString.Key.foregroundColor: UIColor.white])
                            
                            let line = CTLineCreateWithAttributedString(attributedString)
                            let lineBounds = CTLineGetBoundsWithOptions(line, [.useOpticalBounds])
                            
                            let lineOffset = CGPoint(x: 1.0 - UIScreenPixel, y: 0.0)
                            let lineOrigin = CGPoint(x: floorToScreenPixels(-lineBounds.origin.x + (size.width - lineBounds.size.width) / 2.0) + lineOffset.x, y: floorToScreenPixels(-lineBounds.origin.y + (size.height - lineBounds.size.height) / 2.0) + lineOffset.y)
                            
                            context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
                            context.scaleBy(x: 1.0, y: -1.0)
                            context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
                            
                            context.translateBy(x: lineOrigin.x, y: lineOrigin.y)
                            CTLineDraw(line, context)
                            context.translateBy(x: -lineOrigin.x, y: -lineOrigin.y)
                        }) {
                            if controller.completion(.image(image, .sticker)) {
                                controller.dismiss(animated: true)
                            }
                        } else {
                            controller.dismiss(animated: true)
                        }
                    }
                },
                deleteBackwards: nil,
                openStickerSettings: nil,
                openFeatured: nil,
                openSearch: {
                },
                addGroupAction: { [weak self] groupId, isPremiumLocked, _ in
                    guard let strongSelf = self, let controller = strongSelf.controller, let collectionId = groupId.base as? ItemCollectionId else {
                        return
                    }
                    let context = controller.context
                    let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks)
                    let _ = (context.account.postbox.combinedView(keys: [viewKey])
                    |> take(1)
                    |> deliverOnMainQueue).start(next: { views in
                        guard let view = views.views[viewKey] as? OrderedItemListView else {
                            return
                        }
                        for featuredStickerPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) {
                            if featuredStickerPack.info.id == collectionId {
                                let _ = (context.engine.stickers.loadedStickerPack(reference: .id(id: featuredStickerPack.info.id.id, accessHash: featuredStickerPack.info.accessHash), forceActualized: false)
                                |> mapToSignal { result -> Signal<Void, NoError> in
                                    switch result {
                                    case let .result(info, items, installed):
                                        if installed {
                                            return .complete()
                                        } else {
                                            return context.engine.stickers.addStickerPackInteractively(info: info, items: items)
                                        }
                                    case .fetching:
                                        break
                                    case .none:
                                        break
                                    }
                                    return .complete()
                                }
                                |> deliverOnMainQueue).start(completed: {
                                })
                                
                                break
                            }
                        }
                    })
                },
                clearGroup: { [weak self] groupId in
                    guard let strongSelf = self, let controller = strongSelf.controller else {
                        return
                    }
                    var presentationData = controller.context.sharedContext.currentPresentationData.with { $0 }
                    if controller.forceDark {
                        presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme)
                    }
                    let context = controller.context
                    if groupId == AnyHashable("recent") {
                        let actionSheet = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme, fontSize: presentationData.listsFontSize))
                        var items: [ActionSheetItem] = []
                        items.append(ActionSheetButtonItem(title: presentationData.strings.Emoji_ClearRecent, color: .destructive, action: { [weak actionSheet] in
                            actionSheet?.dismissAnimated()
                            let _ = context.engine.stickers.clearRecentlyUsedEmoji().start()
                        }))
                        actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
                            ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
                                actionSheet?.dismissAnimated()
                            })
                        ])])
                        context.sharedContext.mainWindow?.presentInGlobalOverlay(actionSheet)
                    } else if groupId == AnyHashable("popular") {
                        let actionSheet = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme, fontSize: presentationData.listsFontSize))
                        var items: [ActionSheetItem] = []
                        items.append(ActionSheetTextItem(title: presentationData.strings.Chat_ClearReactionsAlertText, parseMarkdown: true))
                        items.append(ActionSheetButtonItem(title: presentationData.strings.Chat_ClearReactionsAlertAction, color: .destructive, action: { [weak actionSheet] in
                            actionSheet?.dismissAnimated()
                            guard let strongSelf = self else {
                                return
                            }
                            
                            strongSelf.scheduledEmojiContentAnimationHint = EmojiPagerContentComponent.ContentAnimation(type: .groupRemoved(id: "popular"))
                            let _ = context.engine.stickers.clearRecentlyUsedReactions().start()
                        }))
                        actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
                            ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
                                actionSheet?.dismissAnimated()
                            })
                        ])])
                        context.sharedContext.mainWindow?.presentInGlobalOverlay(actionSheet)
                    }
                },
                editAction: { _ in },
                pushController: { c in
                },
                presentController: { c in
                },
                presentGlobalOverlayController: { c in
                },
                navigationController: { [weak self] in
                    return self?.controller?.navigationController as? NavigationController
                },
                requestUpdate: { [weak self] transition in
                    guard let strongSelf = self else {
                        return
                    }
                    if !transition.animation.isImmediate, let (layout, navigationHeight) = strongSelf.currentLayout {
                        strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: transition)
                    }
                },
                updateSearchQuery: { [weak self] query in
                    guard let self, let controller = self.controller else {
                        return
                    }
                    let context = controller.context
                    
                    switch query {
                    case .none:
                        self.emojiSearchDisposable.set(nil)
                        self.emojiSearchState.set(.single(EmojiSearchState(result: nil, isSearching: false)))
                    case let .text(rawQuery, languageCode):
                        let query = rawQuery.trimmingCharacters(in: .whitespacesAndNewlines)
                        
                        if query.isEmpty {
                            self.emojiSearchDisposable.set(nil)
                            self.emojiSearchState.set(.single(EmojiSearchState(result: nil, isSearching: false)))
                        } else {
                            var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query, completeMatch: false)
                            if !languageCode.lowercased().hasPrefix("en") {
                                signal = signal
                                |> mapToSignal { keywords in
                                    return .single(keywords)
                                    |> then(
                                        context.engine.stickers.searchEmojiKeywords(inputLanguageCode: "en-US", query: query, completeMatch: query.count < 3)
                                        |> map { englishKeywords in
                                            return keywords + englishKeywords
                                        }
                                    )
                                }
                            }
                        
                            let hasPremium: Signal<Bool, NoError> = .single(true)
                            let resultSignal = combineLatest(
                                signal,
                                hasPremium
                            )
                            |> mapToSignal { keywords, hasPremium -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in
                                var allEmoticons: [String: String] = [:]
                                for keyword in keywords {
                                    for emoticon in keyword.emoticons {
                                        allEmoticons[emoticon] = keyword.keyword
                                    }
                                }
                                let remoteSignal: Signal<(items: [TelegramMediaFile], isFinalResult: Bool), NoError>
                                if hasPremium {
                                    remoteSignal = context.engine.stickers.searchEmoji(query: query, emoticon: Array(allEmoticons.keys), inputLanguageCode: languageCode)
                                } else {
                                    remoteSignal = .single(([], true))
                                }
                                return remoteSignal
                                |> mapToSignal { foundEmoji -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in
                                    if foundEmoji.items.isEmpty && !foundEmoji.isFinalResult {
                                        return .complete()
                                    }
                                    var items: [EmojiPagerContentComponent.Item] = []
                                    
                                    let appendUnicodeEmoji = {
                                        for (_, list) in EmojiPagerContentComponent.staticEmojiMapping {
                                            for emojiString in list {
                                                if allEmoticons[emojiString] != nil {
                                                    let item = EmojiPagerContentComponent.Item(
                                                        animationData: nil,
                                                        content: .staticEmoji(emojiString),
                                                        itemFile: nil,
                                                        subgroupId: nil,
                                                        icon: .none,
                                                        tintMode: .none
                                                    )
                                                    items.append(item)
                                                }
                                            }
                                        }
                                    }
                                    
                                    if !hasPremium {
                                        appendUnicodeEmoji()
                                    }
                                    
                                    var existingIds = Set<MediaId>()
                                    for itemFile in foundEmoji.items {
                                        if existingIds.contains(itemFile.fileId) {
                                            continue
                                        }
                                        existingIds.insert(itemFile.fileId)
                                        if itemFile.isPremiumEmoji && !hasPremium {
                                            continue
                                        }
                                        let animationData = EntityKeyboardAnimationData(file: itemFile)
                                        let item = EmojiPagerContentComponent.Item(
                                            animationData: animationData,
                                            content: .animation(animationData),
                                            itemFile: itemFile,
                                            subgroupId: nil,
                                            icon: .none,
                                            tintMode: animationData.isTemplate ? .primary : .none
                                        )
                                        items.append(item)
                                    }
                                    
                                    if hasPremium {
                                        appendUnicodeEmoji()
                                    }
                                
                                    return .single([EmojiPagerContentComponent.ItemGroup(
                                        supergroupId: "search",
                                        groupId: "search",
                                        title: nil,
                                        subtitle: nil,
                                        badge: nil,
                                        actionButtonTitle: nil,
                                        isFeatured: false,
                                        isPremiumLocked: false,
                                        isEmbedded: false,
                                        hasClear: false,
                                        hasEdit: false,
                                        collapsedLineCount: nil,
                                        displayPremiumBadges: false,
                                        headerItem: nil,
                                        fillWithLoadingPlaceholders: false,
                                        items: items
                                    )])
                                }
                            }
                            
                            var version = 0
                            self.emojiSearchStateValue.isSearching = true
                            self.emojiSearchDisposable.set((resultSignal
                            |> delay(0.15, queue: .mainQueue())
                            |> deliverOnMainQueue).start(next: { [weak self] result in
                                guard let self else {
                                    return
                                }
                                
                                self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result, id: AnyHashable(query), version: version, isPreset: false), isSearching: false)
                                version += 1
                            }))
                        }
                    case let .category(value):
                        let resultSignal = context.engine.stickers.searchEmoji(category: value)
                        |> mapToSignal { files, isFinalResult -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in
                            var items: [EmojiPagerContentComponent.Item] = []
                            
                            var existingIds = Set<MediaId>()
                            for itemFile in files {
                                if existingIds.contains(itemFile.fileId) {
                                    continue
                                }
                                existingIds.insert(itemFile.fileId)
                                let animationData = EntityKeyboardAnimationData(file: itemFile)
                                let item = EmojiPagerContentComponent.Item(
                                    animationData: animationData,
                                    content: .animation(animationData),
                                    itemFile: itemFile, subgroupId: nil,
                                    icon: .none,
                                    tintMode: animationData.isTemplate ? .primary : .none
                                )
                                items.append(item)
                            }
                            
                            return .single(([EmojiPagerContentComponent.ItemGroup(
                                supergroupId: "search",
                                groupId: "search",
                                title: nil,
                                subtitle: nil,
                                badge: nil,
                                actionButtonTitle: nil,
                                isFeatured: false,
                                isPremiumLocked: false,
                                isEmbedded: false,
                                hasClear: false,
                                hasEdit: false,
                                collapsedLineCount: nil,
                                displayPremiumBadges: false,
                                headerItem: nil,
                                fillWithLoadingPlaceholders: false,
                                items: items
                            )], isFinalResult))
                        }
                            
                        var version = 0
                        self.emojiSearchDisposable.set((resultSignal
                        |> deliverOnMainQueue).start(next: { [weak self] result in
                            guard let self else {
                                return
                            }
                            
                            guard let group = result.items.first else {
                                return
                            }
                            if group.items.isEmpty && !result.isFinalResult {
                                self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: [
                                    EmojiPagerContentComponent.ItemGroup(
                                        supergroupId: "search",
                                        groupId: "search",
                                        title: nil,
                                        subtitle: nil,
                                        badge: nil,
                                        actionButtonTitle: nil,
                                        isFeatured: false,
                                        isPremiumLocked: false,
                                        isEmbedded: false,
                                        hasClear: false,
                                        hasEdit: false,
                                        collapsedLineCount: nil,
                                        displayPremiumBadges: false,
                                        headerItem: nil,
                                        fillWithLoadingPlaceholders: true,
                                        items: []
                                    )
                                ], id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false)
                                return
                            }
                            self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false)
                            version += 1
                        }))
                    }
                },
                updateScrollingToItemGroup: { [weak self] in
                    if let self, let componentView = self.hostView.componentView as? StickerSelectionComponent.View {
                        componentView.scrolledToItemGroup()
                    }
                    self?.update(isExpanded: true, transition: .animated(duration: 0.4, curve: .spring))
                },
                onScroll: {},
                chatPeerId: nil,
                peekBehavior: nil,
                customLayout: nil,
                externalBackground: nil,
                externalExpansionView: nil,
                customContentView: nil,
                useOpaqueTheme: false,
                hideBackground: true,
                stateContext: nil,
                addImage: controller.hasGifs ? { [weak self] in
                    if let self, let controller = self.controller {
                        let _ = controller.completion(nil)
                        controller.dismiss(animated: true)
                        controller.presentGallery()
                    }
                } : nil
            )
            
            var stickerPeekBehavior: EmojiContentPeekBehaviorImpl?
            if let controller = self.controller {
                stickerPeekBehavior = EmojiContentPeekBehaviorImpl(
                    context: controller.context,
                    forceTheme: controller.forceDark ? defaultDarkColorPresentationTheme : nil,
                    interaction: nil,
                    chatPeerId: nil,
                    present: { [weak controller] c, a in
                        controller?.presentInGlobalOverlay(c, with: a)
                    }
                )
            }
            
            content.stickers?.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction(
                performItemAction: { [weak self] groupId, item, _, _, _, _ in
                    guard let self, let controller = self.controller, let file = item.itemFile else {
                        return
                    }
                    let presentationData = controller.context.sharedContext.currentPresentationData.with { $0 }
                    if groupId == AnyHashable("featuredTop") {
                        let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedStickerPacks)
                        let _ = (controller.context.account.postbox.combinedView(keys: [viewKey])
                        |> take(1)
                        |> deliverOnMainQueue).start(next: { [weak self] views in
                            guard let self, let controller = self.controller, let view = views.views[viewKey] as? OrderedItemListView else {
                                return
                            }
                            for featuredStickerPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) {
                                if featuredStickerPack.topItems.contains(where: { $0.file.fileId == file.fileId }) {
                                    controller.pushController(FeaturedStickersScreen(
                                        context: controller.context,
                                        highlightedPackId: featuredStickerPack.info.id,
                                        forceTheme: defaultDarkColorPresentationTheme,
                                        stickerActionTitle: presentationData.strings.StickerPack_AddSticker,
                                        sendSticker: { [weak self] fileReference, _, _ in
                                            guard let self, let controller = self.controller else {
                                                return false
                                            }
                                            if controller.completion(.file(fileReference, .sticker)) {
                                                controller.dismiss(animated: true)
                                            }
                                            return true
                                        }
                                    ))
                                    
                                    break
                                }
                            }
                        })
                    } else {
                        let reference: FileMediaReference
                        if groupId == AnyHashable("saved") {
                            reference = .savedSticker(media: file)
                        } else if groupId == AnyHashable("recent") {
                            reference = .recentSticker(media: file)
                        } else {
                            reference = .standalone(media: file)
                        }
                        let _ = controller.completion(.file(reference, .sticker))
                        controller.dismiss(animated: true)
                    }
                },
                deleteBackwards: nil,
                openStickerSettings: nil,
                openFeatured: nil,
                openSearch: { [weak self] in
                    if let self, let componentView = self.hostView.componentView as? StickerSelectionComponent.View {
                        if let pagerView = componentView.keyboardView.view as? EntityKeyboardComponent.View {
                            pagerView.openSearch()
                        }
                        self.update(isExpanded: true, transition: .animated(duration: 0.4, curve: .spring))
                    }
                },
                addGroupAction: { [weak self] groupId, isPremiumLocked, _ in
                    guard let strongSelf = self, let controller = strongSelf.controller, let collectionId = groupId.base as? ItemCollectionId else {
                        return
                    }
                    let context = controller.context
                    let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedStickerPacks)
                    let _ = (context.account.postbox.combinedView(keys: [viewKey])
                    |> take(1)
                    |> deliverOnMainQueue).start(next: { views in
                        guard let view = views.views[viewKey] as? OrderedItemListView else {
                            return
                        }
                        for featuredStickerPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) {
                            if featuredStickerPack.info.id == collectionId {
                                let _ = (context.engine.stickers.loadedStickerPack(reference: .id(id: featuredStickerPack.info.id.id, accessHash: featuredStickerPack.info.accessHash), forceActualized: false)
                                |> mapToSignal { result -> Signal<Void, NoError> in
                                    switch result {
                                    case let .result(info, items, installed):
                                        if installed {
                                            return .complete()
                                        } else {
                                            return context.engine.stickers.addStickerPackInteractively(info: info, items: items)
                                        }
                                    case .fetching:
                                        break
                                    case .none:
                                        break
                                    }
                                    return .complete()
                                }
                                |> deliverOnMainQueue).start(completed: {
                                })
                                
                                break
                            }
                        }
                    })
                },
                clearGroup: { [weak self] groupId in
                    guard let strongSelf = self, let controller = strongSelf.controller else {
                        return
                    }
                    let context = controller.context
                    if groupId == AnyHashable("recent") {
                        var presentationData = context.sharedContext.currentPresentationData.with { $0 }
                        if controller.forceDark {
                            presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme)
                        }
                        let actionSheet = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme, fontSize: presentationData.listsFontSize))
                        var items: [ActionSheetItem] = []
                        items.append(ActionSheetButtonItem(title: presentationData.strings.Stickers_ClearRecent, color: .destructive, action: { [weak actionSheet] in
                            actionSheet?.dismissAnimated()
                            let _ = context.engine.stickers.clearRecentlyUsedStickers().start()
                        }))
                        actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
                            ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
                                actionSheet?.dismissAnimated()
                            })
                        ])])
                        context.sharedContext.mainWindow?.presentInGlobalOverlay(actionSheet)
                    } else if groupId == AnyHashable("featuredTop") {
                        let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedStickerPacks)
                        let _ = (context.account.postbox.combinedView(keys: [viewKey])
                        |> take(1)
                        |> deliverOnMainQueue).start(next: { views in
                            guard let view = views.views[viewKey] as? OrderedItemListView else {
                                return
                            }
                            var stickerPackIds: [Int64] = []
                            for featuredStickerPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) {
                                stickerPackIds.append(featuredStickerPack.info.id.id)
                            }
                            let _ = ApplicationSpecificNotice.setDismissedTrendingStickerPacks(accountManager: context.sharedContext.accountManager, values: stickerPackIds).start()
                        })
                    } else if groupId == AnyHashable("peerSpecific") {
                    }
                },
                editAction: { _ in },
                pushController: { c in
                },
                presentController: { c in
                },
                presentGlobalOverlayController: { c in
                },
                navigationController: { [weak self] in
                    return self?.controller?.navigationController as? NavigationController
                },
                requestUpdate: { [weak self] transition in
                    guard let strongSelf = self else {
                        return
                    }
                    if !transition.animation.isImmediate, let (layout, navigationHeight) = strongSelf.currentLayout {
                        strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: transition)
                    }
                },
                updateSearchQuery: { [weak self] query in
                    guard let strongSelf = self, let controller = strongSelf.controller else {
                        return
                    }
                    let context = controller.context
                    
                    switch query {
                    case .none:
                        strongSelf.stickerSearchDisposable.set(nil)
                        strongSelf.stickerSearchStateValue = EmojiSearchState(result: nil, isSearching: false)
                    case .text:
                        strongSelf.stickerSearchDisposable.set(nil)
                        strongSelf.stickerSearchStateValue = EmojiSearchState(result: nil, isSearching: false)
                    case let .category(value):
                        let resultSignal = context.engine.stickers.searchStickers(category: value, scope: [.installed, .remote])
                        |> mapToSignal { files -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in
                            var items: [EmojiPagerContentComponent.Item] = []
                            
                            var existingIds = Set<MediaId>()
                            for item in files.items {
                                let itemFile = item.file
                                if existingIds.contains(itemFile.fileId) {
                                    continue
                                }
                                existingIds.insert(itemFile.fileId)
                                let animationData = EntityKeyboardAnimationData(file: itemFile)
                                let item = EmojiPagerContentComponent.Item(
                                    animationData: animationData,
                                    content: .animation(animationData),
                                    itemFile: itemFile, subgroupId: nil,
                                    icon: .none,
                                    tintMode: animationData.isTemplate ? .primary : .none
                                )
                                items.append(item)
                            }
                            
                            return .single(([EmojiPagerContentComponent.ItemGroup(
                                supergroupId: "search",
                                groupId: "search",
                                title: nil,
                                subtitle: nil,
                                badge: nil,
                                actionButtonTitle: nil,
                                isFeatured: false,
                                isPremiumLocked: false,
                                isEmbedded: false,
                                hasClear: false,
                                hasEdit: false,
                                collapsedLineCount: nil,
                                displayPremiumBadges: false,
                                headerItem: nil,
                                fillWithLoadingPlaceholders: false,
                                items: items
                            )], files.isFinalResult))
                        }
                            
                        var version = 0
                        strongSelf.stickerSearchDisposable.set((resultSignal
                        |> deliverOnMainQueue).start(next: { [weak self] result in
                            guard let strongSelf = self else {
                                return
                            }
                            guard let group = result.items.first else {
                                return
                            }
                            if group.items.isEmpty && !result.isFinalResult {
                                strongSelf.stickerSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: [
                                    EmojiPagerContentComponent.ItemGroup(
                                        supergroupId: "search",
                                        groupId: "search",
                                        title: nil,
                                        subtitle: nil,
                                        badge: nil,
                                        actionButtonTitle: nil,
                                        isFeatured: false,
                                        isPremiumLocked: false,
                                        isEmbedded: false,
                                        hasClear: false,
                                        hasEdit: false,
                                        collapsedLineCount: nil,
                                        displayPremiumBadges: false,
                                        headerItem: nil,
                                        fillWithLoadingPlaceholders: true,
                                        items: []
                                    )
                                ], id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false)
                                return
                            }
                            strongSelf.stickerSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false)
                            version += 1
                        }))
                    }
                },
                updateScrollingToItemGroup: { [weak self] in
                    if let self, let componentView = self.hostView.componentView as? StickerSelectionComponent.View {
                        componentView.scrolledToItemGroup()
                    }
                    self?.update(isExpanded: true, transition: .animated(duration: 0.4, curve: .spring))
                },
                onScroll: {},
                chatPeerId: nil,
                peekBehavior: stickerPeekBehavior,
                customLayout: nil,
                externalBackground: nil,
                externalExpansionView: nil,
                customContentView: self.storyStickersContentView,
                useOpaqueTheme: false,
                hideBackground: true,
                stateContext: nil,
                addImage: controller.hasGifs ? { [weak self] in
                    if let self, let controller = self.controller {
                        let _ = controller.completion(nil)
                        controller.dismiss(animated: true)
                        controller.presentGallery()
                    }
                } : nil
            )
            
            if let (layout, navigationHeight) = self.currentLayout {
                self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate)
            }
        }
        
        override func didLoad() {
            super.didLoad()
            
            let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
            panRecognizer.delegate = self.wrappedGestureRecognizerDelegate
            panRecognizer.delaysTouchesBegan = false
            panRecognizer.cancelsTouchesInView = true
            self.panGestureRecognizer = panRecognizer
            self.wrappingView.addGestureRecognizer(panRecognizer)
            
            self.dim.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
            
            if let controller = self.controller {
                controller.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate)
            }
        }
        
        @objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) {
            if case .ended = recognizer.state {
                let _ = self.controller?.completion(nil)
                self.controller?.dismiss(animated: true)
            }
        }
        
        override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
            guard let controller = self.controller, !controller.isFullscreen else {
                return false
            }
            if let (layout, _) = self.currentLayout {
                if layout.metrics.isTablet {
                    return false
                }
            }
            return true
        }
        
        func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
            if gestureRecognizer is UIPanGestureRecognizer && otherGestureRecognizer is UIPanGestureRecognizer {
                if otherGestureRecognizer is PagerPanGestureRecognizer {
                    return false
                } else if otherGestureRecognizer is UIPanGestureRecognizer, let scrollView = otherGestureRecognizer.view, scrollView.frame.width > scrollView.frame.height {
                    return false
                } else if otherGestureRecognizer is PeekControllerGestureRecognizer {
                    return false
                }
                return true
            }
            return false
        }
        
        private var isDismissing = false
        func animateIn() {
            guard let controller = self.controller, !controller.isFullscreen else {
                return
            }
            ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 1.0)
            
            let targetPosition = self.containerView.center
            let startPosition = targetPosition.offsetBy(dx: 0.0, dy: self.bounds.height)
            
            self.containerView.center = startPosition
            let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
            transition.animateView(allowUserInteraction: true, {
                self.containerView.center = targetPosition
            }, completion: { _ in
            })
        }
        
        func animateOut(completion: @escaping () -> Void = {}) {
            self.isDismissing = true
            
            let positionTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)
            positionTransition.updatePosition(layer: self.containerView.layer, position: CGPoint(x: self.containerView.center.x, y: self.bounds.height + self.containerView.bounds.height / 2.0), completion: { [weak self] _ in
                self?.controller?.dismiss(animated: false, completion: completion)
            })
            let alphaTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)
            alphaTransition.updateAlpha(node: self.dim, alpha: 0.0)
            
            if !self.temporaryDismiss {
                self.controller?.updateModalStyleOverlayTransitionFactor(0.0, transition: positionTransition)
            }
        }
        
        func presentLinkPremiumSuggestion() {
            guard let controller = self.controller else {
                return
            }
            let tooltipController = UndoOverlayController(
                presentationData: self.presentationData,
                content: .linkCopied(
                    title: nil,
                    text: self.presentationData.strings.Story_Editor_TooltipLinkPremium
                ),
                elevatedLayout: true,
                position: .top,
                animateInAsReplacement: false, action: { [weak controller] action in
                    if case .info = action, let controller {
                        let _ = controller.completion(nil)
                        controller.dismiss(animated: true)
                        
                        let premiumController = controller.context.sharedContext.makePremiumIntroController(context: controller.context, source: .storiesLinks, forceDark: controller.forceDark, dismissed: nil)
                        controller.pushController(premiumController)
                        return true
                    }
                    return false
                }
            )
            controller.present(tooltipController, in: .window(.root))
        }
                
        func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ComponentTransition) {
            guard let controller = self.controller else {
                return
            }
            self.currentLayout = (layout, navigationHeight)
                        
            self.dim.frame = CGRect(origin: CGPoint(x: 0.0, y: -layout.size.height), size: CGSize(width: layout.size.width, height: layout.size.height * 3.0))
                        
            let effectiveExpanded = self.isExpanded || layout.metrics.isTablet
            
            let isLandscape = layout.orientation == .landscape
            let edgeTopInset = isLandscape ? 0.0 : self.defaultTopInset
            let topInset: CGFloat
            var bottomInset = layout.intrinsicInsets.bottom
            if let (panInitialTopInset, panOffset, _, _) = self.panGestureArguments {
                if effectiveExpanded {
                    topInset = min(edgeTopInset, panInitialTopInset + max(0.0, panOffset))
                } else {
                    topInset = max(0.0, panInitialTopInset + min(0.0, panOffset))
                }
            } else {
                topInset = effectiveExpanded ? 0.0 : edgeTopInset
            }
            transition.setFrame(view: self.wrappingView, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: layout.size), completion: nil)
            
            var modalProgress = isLandscape ? 0.0 : (1.0 - topInset / self.defaultTopInset)
            if self.isDismissing {
                modalProgress = 0.0
            }
            self.controller?.updateModalStyleOverlayTransitionFactor(modalProgress, transition: transition.containedViewLayoutTransition)
            if self.isDismissing {
                return
            }
            
            let clipFrame: CGRect
            let contentFrame: CGRect
            if controller.isFullscreen {
                clipFrame = CGRect(origin: CGPoint(), size: layout.size)
                contentFrame = clipFrame
            } else if layout.metrics.widthClass == .compact {
                self.dim.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.25)
                if isLandscape {
                    self.containerView.layer.cornerRadius = 0.0
                } else {
                    self.containerView.layer.cornerRadius = 10.0
                }
                
                if #available(iOS 11.0, *) {
                    if layout.safeInsets.bottom.isZero {
                        self.containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
                    } else {
                        self.containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner]
                    }
                }
                
                if isLandscape {
                    clipFrame = CGRect(origin: CGPoint(), size: layout.size)
                    contentFrame = clipFrame
                } else {
                    let coveredByModalTransition: CGFloat = 0.0
                    var containerTopInset: CGFloat = 10.0
                    if let statusBarHeight = layout.statusBarHeight {
                        containerTopInset += statusBarHeight
                    }
                                        
                    let unscaledFrame = CGRect(origin: CGPoint(x: 0.0, y: containerTopInset - coveredByModalTransition * 10.0), size: CGSize(width: layout.size.width, height: layout.size.height - containerTopInset))
                    let maxScale: CGFloat = (layout.size.width - 16.0 * 2.0) / layout.size.width
                    let containerScale = 1.0 * (1.0 - coveredByModalTransition) + maxScale * coveredByModalTransition
                    let maxScaledTopInset: CGFloat = containerTopInset - 10.0
                    let scaledTopInset: CGFloat = containerTopInset * (1.0 - coveredByModalTransition) + maxScaledTopInset * coveredByModalTransition
                    let containerFrame = unscaledFrame.offsetBy(dx: 0.0, dy: scaledTopInset - (unscaledFrame.midY - containerScale * unscaledFrame.height / 2.0))
                    
                    clipFrame = CGRect(x: containerFrame.minX, y: containerFrame.minY, width: containerFrame.width, height: containerFrame.height)
                    contentFrame = CGRect(x: containerFrame.minX, y: containerFrame.minY, width: containerFrame.width, height: containerFrame.height - topInset)
                }
            } else {
                self.dim.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.4)
                self.containerView.layer.cornerRadius = 10.0
                
                let verticalInset: CGFloat = 44.0
                
                let maxSide = max(layout.size.width, layout.size.height)
                let minSide = min(layout.size.width, layout.size.height)
                let containerSize = CGSize(width: floorToScreenPixels(min(layout.size.width - 20.0, floor(maxSide / 2.0)) * 0.66), height: floorToScreenPixels((min(layout.size.height, minSide) - verticalInset * 2.0) * 0.66))
                clipFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - containerSize.width) / 2.0), y: floor((layout.size.height - containerSize.height) / 2.0)), size: containerSize)
                contentFrame = clipFrame
                
                bottomInset = 0.0
            }
            
            transition.setFrame(view: self.containerView, frame: clipFrame)
                        
            if let content = self.content {
                var stickersTransition: ComponentTransition = transition
                if let scheduledEmojiContentAnimationHint = self.scheduledEmojiContentAnimationHint {
                    self.scheduledEmojiContentAnimationHint = nil
                    let contentAnimation = scheduledEmojiContentAnimationHint
                    stickersTransition = ComponentTransition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(contentAnimation)
                }
                
                var contentSize = self.hostView.update(
                    transition: stickersTransition,
                    component: AnyComponent(
                        StickerSelectionComponent(
                            context: controller.context,
                            theme: self.theme,
                            strings: self.presentationData.strings,
                            deviceMetrics: layout.deviceMetrics,
                            topInset: controller.isFullscreen ? navigationHeight : 0.0,
                            bottomInset: bottomInset,
                            content: content,
                            backgroundColor: self.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.85),
                            separatorColor: self.theme.rootController.navigationBar.separatorColor,
                            getController: { [weak self] in
                                if let self {
                                    return self.controller
                                } else {
                                    return nil
                                }
                            }
                        )
                    ),
                    environment: {},
                    forceUpdate: true,
                    containerSize: CGSize(width: contentFrame.size.width, height: contentFrame.height)
                )
                contentSize.height = max(layout.size.height - navigationHeight, contentSize.height)
                transition.setFrame(view: self.hostView, frame: CGRect(origin: CGPoint(), size: contentSize), completion: nil)
            }
        }
        
        private var didPlayAppearAnimation = false
        func updateIsVisible(isVisible: Bool) {
            if self.currentIsVisible == isVisible {
                return
            }
            self.currentIsVisible = isVisible
            
            guard let currentLayout = self.currentLayout else {
                return
            }
            self.containerLayoutUpdated(layout: currentLayout.layout, navigationHeight: currentLayout.navigationHeight, transition: .immediate)
            
            if !self.didPlayAppearAnimation {
                self.didPlayAppearAnimation = true
                self.animateIn()
            }
        }
        
        private var defaultTopInset: CGFloat {
            guard let (layout, _) = self.currentLayout else {
                return 210.0
            }
            
            if let controller = self.controller, controller.isFullscreen {
                return 0.0
            }
            
            if case .compact = layout.metrics.widthClass {
                var factor: CGFloat = 0.2488
                if layout.size.width <= 320.0 {
                    factor = 0.15
                }
                return floor(max(layout.size.width, layout.size.height) * factor)
            } else {
                return 210.0
            }
        }
        
        private func findScrollView(view: UIView?) -> (UIScrollView, ListView?)? {
            if let view = view {
                if let view = view as? PagerExpandableScrollView {
                    return (view, nil)
                }
                if let view = view as? GridNodeScrollerView {
                    return (view, nil)
                }
                if let node = view.asyncdisplaykit_node as? ListView {
                    return (node.scroller, node)
                }
                return findScrollView(view: view.superview)
            } else {
                return nil
            }
        }
        
        @objc func panGesture(_ recognizer: UIPanGestureRecognizer) {
            guard let (layout, navigationHeight) = self.currentLayout else {
                return
            }
            
            guard let controller = self.controller, !controller.isFullscreen else {
                return
            }
            
            let isLandscape = layout.orientation == .landscape
            let edgeTopInset = isLandscape ? 0.0 : defaultTopInset
        
            switch recognizer.state {
                case .began:
                    let point = recognizer.location(in: self.view)
                    let currentHitView = self.hitTest(point, with: nil)
                    
                    var scrollViewAndListNode = self.findScrollView(view: currentHitView)
                    if scrollViewAndListNode?.0.frame.height == self.frame.width {
                        scrollViewAndListNode = nil
                    }
                    let scrollView = scrollViewAndListNode?.0
                    let listNode = scrollViewAndListNode?.1
                
                    let topInset: CGFloat
                    if self.isExpanded {
                        topInset = 0.0
                    } else {
                        topInset = edgeTopInset
                    }
                
                    self.panGestureArguments = (topInset, 0.0, scrollView, listNode)
                case .changed:
                    guard let (topInset, panOffset, scrollView, listNode) = self.panGestureArguments else {
                        return
                    }
                    let visibleContentOffset = listNode?.visibleContentOffset()
                    let contentOffset = scrollView?.contentOffset.y ?? 0.0
                
                    var translation = recognizer.translation(in: self.view).y

                    var currentOffset = topInset + translation
                
                    let epsilon = 1.0
                    if case let .known(value) = visibleContentOffset, value <= epsilon {
                        if let scrollView = scrollView {
                            scrollView.bounces = false
                            scrollView.setContentOffset(CGPoint(x: scrollView.contentOffset.x, y: 0.0), animated: false)
                        }
                    } else if let scrollView = scrollView, contentOffset <= -scrollView.contentInset.top + epsilon {
                        scrollView.bounces = false
                        scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false)
                    } else if let scrollView = scrollView {
                        translation = panOffset
                        currentOffset = topInset + translation
                        if self.isExpanded {
                            recognizer.setTranslation(CGPoint(), in: self.view)
                        } else if currentOffset > 0.0 {
                            scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false)
                        }
                    }
                    
                    self.panGestureArguments = (topInset, translation, scrollView, listNode)
                    
                    if !self.isExpanded {
                        if currentOffset > 0.0, let scrollView = scrollView {
                            scrollView.panGestureRecognizer.setTranslation(CGPoint(), in: scrollView)
                        }
                    }
                
                    var bounds = self.bounds
                    if self.isExpanded {
                        bounds.origin.y = -max(0.0, translation - edgeTopInset)
                    } else {
                        bounds.origin.y = -translation
                    }
                    bounds.origin.y = min(0.0, bounds.origin.y)
                    self.bounds = bounds
                
                    self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate)
                case .ended:
                    guard let (currentTopInset, panOffset, scrollView, listNode) = self.panGestureArguments else {
                        return
                    }
                    self.panGestureArguments = nil
                
                    let visibleContentOffset = listNode?.visibleContentOffset()
                    let contentOffset = scrollView?.contentOffset.y ?? 0.0
                
                    let translation = recognizer.translation(in: self.view).y
                    var velocity = recognizer.velocity(in: self.view)
                    
                    if self.isExpanded {
                        if case let .known(value) = visibleContentOffset, value > 0.1 {
                            velocity = CGPoint()
                        } else if case .unknown = visibleContentOffset {
                            velocity = CGPoint()
                        } else if contentOffset > 0.1 {
                            velocity = CGPoint()
                        }
                    }
                
                    var bounds = self.bounds
                    if self.isExpanded {
                        bounds.origin.y = -max(0.0, translation - edgeTopInset)
                    } else {
                        bounds.origin.y = -translation
                    }
                    bounds.origin.y = min(0.0, bounds.origin.y)
                
                    scrollView?.bounces = true
                
                    let offset = currentTopInset + panOffset
                    let topInset: CGFloat = edgeTopInset

                    var dismissing = false
                    if bounds.minY < -60 || (bounds.minY < 0.0 && velocity.y > 300.0) || (self.isExpanded && bounds.minY.isZero && velocity.y > 1800.0) {
                        let _ = self.controller?.completion(nil)
                        self.controller?.dismiss(animated: true, completion: nil)
                        dismissing = true
                    } else if self.isExpanded {
                        if velocity.y > 300.0 || offset > topInset / 2.0 {
                            self.isExpanded = false
                            if let listNode = listNode {
                                listNode.scroller.setContentOffset(CGPoint(), animated: false)
                            } else if let scrollView = scrollView {
                                scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false)
                            }
                            
                            let distance = topInset - offset
                            let initialVelocity: CGFloat = distance.isZero ? 0.0 : abs(velocity.y / distance)
                            let transition = ContainedViewLayoutTransition.animated(duration: 0.45, curve: .customSpring(damping: 124.0, initialVelocity: initialVelocity))

                            self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: ComponentTransition(transition))
                        } else {
                            self.isExpanded = true
                            
                            self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: ComponentTransition(.animated(duration: 0.3, curve: .easeInOut)))
                        }
                    } else if (velocity.y < -300.0 || offset < topInset / 2.0) {
                        let initialVelocity: CGFloat = offset.isZero ? 0.0 : abs(velocity.y / offset)
                        let transition = ContainedViewLayoutTransition.animated(duration: 0.45, curve: .customSpring(damping: 124.0, initialVelocity: initialVelocity))
                        self.isExpanded = true
                       
                        self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: ComponentTransition(transition))
                    } else {
                        if let listNode = listNode {
                            listNode.scroller.setContentOffset(CGPoint(), animated: false)
                        } else if let scrollView = scrollView {
                            scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false)
                        }
                        
                        self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: ComponentTransition(.animated(duration: 0.3, curve: .easeInOut)))
                    }
                    
                    if !dismissing {
                        var bounds = self.bounds
                        let previousBounds = bounds
                        bounds.origin.y = 0.0
                        self.bounds = bounds
                        self.layer.animateBounds(from: previousBounds, to: self.bounds, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
                    }
                case .cancelled:
                    self.panGestureArguments = nil
                    
                    self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: ComponentTransition(.animated(duration: 0.3, curve: .easeInOut)))
                default:
                    break
            }
        }
        
        func update(isExpanded: Bool, transition: ContainedViewLayoutTransition) {
            guard isExpanded != self.isExpanded else {
                return
            }
            self.isExpanded = isExpanded
            
            guard let (layout, navigationHeight) = self.currentLayout else {
                return
            }
            self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: ComponentTransition(transition))
        }
    }
    
    var node: Node {
        return self.displayNode as! Node
    }
    
    public enum Weather {
        public struct LoadedWeather {
            public let emoji: String
            public let emojiFile: TelegramMediaFile
            public let temperature: Double
            
            public init(emoji: String, emojiFile: TelegramMediaFile, temperature: Double) {
                self.emoji = emoji
                self.emojiFile = emojiFile
                self.temperature = temperature
            }
        }
        
        case none
        case notDetermined
        case notAllowed
        case notPreloaded
        case fetching
        case loaded(StickerPickerScreen.Weather.LoadedWeather)
    }
    
    private let context: AccountContext
    private let theme: PresentationTheme
    let forceDark: Bool
    private let inputData: Signal<StickerPickerInput, NoError>
    let defaultToEmoji: Bool
    let isFullscreen: Bool
    let hasEmoji: Bool
    let hasGifs: Bool
    let hasInteractiveStickers: Bool
    let weather: Signal<StickerPickerScreen.Weather, NoError>
    
    private var currentLayout: ContainerViewLayout?
    
    public var pushController: (ViewController) -> Void = { _ in }
    public var presentController: (ViewController) -> Void = { _ in }
    
    public var completion: (DrawingStickerEntity.Content?) -> Bool = { _ in return true }
    
    public var presentGallery: () -> Void = { }
    public var presentLocationPicker: () -> Void = { }
    public var presentAudioPicker: () -> Void = { }
    public var addReaction: () -> Void = { }
    public var addLink: () -> Void = { }
    public var addWeather: () -> Void = { }
    
    public init(context: AccountContext, inputData: Signal<StickerPickerInput, NoError>, forceDark: Bool = false, expanded: Bool = false, defaultToEmoji: Bool = false, hasEmoji: Bool = true, hasGifs: Bool = false, hasInteractiveStickers: Bool = true, weather: Signal<StickerPickerScreen.Weather, NoError> = .single(.none)) {
        self.context = context
        let presentationData = context.sharedContext.currentPresentationData.with { $0 }
        self.theme = forceDark ? defaultDarkColorPresentationTheme : presentationData.theme
        self.forceDark = forceDark
        self.inputData = inputData
        self.isFullscreen = expanded
        self.defaultToEmoji = defaultToEmoji
        self.hasEmoji = hasEmoji
        self.hasGifs = hasGifs
        self.hasInteractiveStickers = hasInteractiveStickers
        self.weather = weather
        
        super.init(navigationBarPresentationData: expanded ? NavigationBarPresentationData(presentationData: presentationData) : nil)
        
        self.statusBar.statusBarStyle = .Ignore
        
        if expanded {
            self.title = presentationData.strings.Stickers_ChooseSticker_Title
            self.navigationPresentation = .modal
        }
    }
    
    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    public override func loadDisplayNode() {
        self.displayNode = Node(context: self.context, controller: self, theme: self.theme)
        self.displayNodeDidLoad()
        
        self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
    }
    
    public override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
        self.view.endEditing(true)
        if self.isFullscreen {
            super.dismiss(animated: flag, completion: completion)
        } else {
            if flag {
                self.node.animateOut(completion: {
                    super.dismiss(animated: false, completion: {})
                    completion?()
                })
            } else {
                super.dismiss(animated: false, completion: {})
                completion?()
            }
        }
    }
    
    public override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        self.node.updateIsVisible(isVisible: true)
    }
    
    public override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        
        self.node.updateIsVisible(isVisible: false)
    }
        
    public override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
        self.currentLayout = layout
        super.containerLayoutUpdated(layout, transition: transition)
        
        let navigationHeight: CGFloat = 56.0
        
        self.node.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: ComponentTransition(transition))
    }
}

private final class InteractiveStickerButtonContent: Component {
    let context: AccountContext
    let theme: PresentationTheme
    let title: String?
    let iconName: String?
    let iconFile: TelegramMediaFile?
    let useOpaqueTheme: Bool
    weak var tintContainerView: UIView?

    public init(
        context: AccountContext,
        theme: PresentationTheme,
        title: String?,
        iconName: String?,
        iconFile: TelegramMediaFile? = nil,
        useOpaqueTheme: Bool,
        tintContainerView: UIView
    ) {
        self.context = context
        self.theme = theme
        self.title = title
        self.iconName = iconName
        self.iconFile = iconFile
        self.useOpaqueTheme = useOpaqueTheme
        self.tintContainerView = tintContainerView
    }
    
    public static func ==(lhs: InteractiveStickerButtonContent, rhs: InteractiveStickerButtonContent) -> Bool {
        if lhs.theme !== rhs.theme {
            return false
        }
        if lhs.title != rhs.title {
            return false
        }
        if lhs.iconName != rhs.iconName {
            return false
        }
        if lhs.useOpaqueTheme != rhs.useOpaqueTheme {
            return false
        }
        return true
    }
    
    final class View: UIView {
        override public static var layerClass: AnyClass {
            return PassthroughLayer.self
        }
                
        private let backgroundLayer = SimpleLayer()
        let tintBackgroundLayer = SimpleLayer()
        
        private var loadingView: TextLoadingEffectView?
        private var icon: ComponentView<Empty>
        private var title: ComponentView<Empty>
        
        private var component: InteractiveStickerButtonContent?
    
        override init(frame: CGRect) {
            self.icon = ComponentView<Empty>()
            self.title = ComponentView<Empty>()
            
            super.init(frame: frame)
            
            self.isExclusiveTouch = true
            
            self.layer.addSublayer(self.backgroundLayer)
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        func update(component: InteractiveStickerButtonContent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
            self.backgroundLayer.backgroundColor = UIColor(rgb: 0xffffff, alpha: 0.11).cgColor
              
            let iconSize: CGSize
            let buttonSize: CGSize
            if let title = component.title {
                if let iconFile = component.iconFile {
                    iconSize = self.icon.update(
                        transition: .immediate,
                        component: AnyComponent(
                            LottieComponent(
                                content: LottieComponent.ResourceContent(context: component.context, file: iconFile, attemptSynchronously: true, providesPlaceholder: true),
                                color: nil,
                                placeholderColor: UIColor(rgb: 0xffffff, alpha: 0.4),
                                loop: !["🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘"].contains(component.iconName ?? "")
                            )
                        ),
                        environment: {},
                        containerSize: CGSize(width: 20.0, height: 20.0)
                    )
                } else {
                    iconSize = self.icon.update(
                        transition: .immediate,
                        component: AnyComponent(BundleIconComponent(
                            name: component.iconName ?? "",
                            tintColor: .white,
                            maxSize: CGSize(width: 20.0, height: 20.0)
                        )),
                        environment: {},
                        containerSize: availableSize
                    )
                }
                let titleSize = self.title.update(
                    transition: .immediate,
                    component: AnyComponent(Text(
                        text: title.uppercased(),
                        font: Font.with(size: 23.0, design: .camera),
                        color: .white
                    )),
                    environment: {},
                    containerSize: availableSize
                )
                
                let padding: CGFloat = 7.0
                let spacing: CGFloat = 4.0
                buttonSize = CGSize(width: padding + iconSize.width + spacing + titleSize.width + padding, height: 34.0)
                
                if let view = self.icon.view {
                    if view.superview == nil {
                        self.addSubview(view)
                    }
                    transition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: padding, y: floorToScreenPixels((buttonSize.height - iconSize.height) / 2.0)), size: iconSize))
                }
                if let view = self.title.view {
                    if view.superview == nil {
                        self.addSubview(view)
                    }
                    transition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: padding + iconSize.width + spacing, y: floorToScreenPixels((buttonSize.height - titleSize.height) / 2.0)), size: titleSize))
                }
                
                if let loadingView = self.loadingView {
                    self.loadingView = nil
                    loadingView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
                        loadingView.removeFromSuperview()
                    })
                }
            } else {
                buttonSize = CGSize(width: 87.0, height: 34.0)
                
                let loadingView: TextLoadingEffectView
                if let current = self.loadingView {
                    loadingView = current
                } else {
                    loadingView = TextLoadingEffectView()
                    self.addSubview(loadingView)
                    self.loadingView = loadingView
                }
            }
            
            self.backgroundLayer.cornerRadius = 6.0
            self.tintBackgroundLayer.cornerRadius = 6.0
            
            var transition = transition
            if self.backgroundLayer.frame.width.isZero {
                transition = .immediate
            }
            transition.setFrame(layer: self.backgroundLayer, frame: CGRect(origin: .zero, size: buttonSize))
            
            if self.tintBackgroundLayer.superlayer == nil, let tintContainerView = component.tintContainerView {
                Queue.mainQueue().justDispatch {
                    let mappedFrame = self.convert(self.bounds, to: tintContainerView)
                    transition.setFrame(layer: self.tintBackgroundLayer, frame: mappedFrame)
                }
            }
            
            if let loadingView = self.loadingView {
                let loadingSize = CGSize(width: buttonSize.width - 18.0, height: 16.0)
                loadingView.update(color: UIColor.white, rect: CGRect(origin: .zero, size: loadingSize))
                loadingView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((buttonSize.width - loadingSize.width) / 2.0), y: floorToScreenPixels((buttonSize.width - loadingSize.width) / 2.0)), size: loadingSize)
            }
            
            return buttonSize
        }
    }
    
    public func makeView() -> View {
        return View(frame: CGRect())
    }
    
    public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
        view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
    }
}

private final class InteractiveReactionButtonContent: Component {
    let theme: PresentationTheme

    public init(
        theme: PresentationTheme
    ) {
        self.theme = theme
    }
    
    public static func ==(lhs: InteractiveReactionButtonContent, rhs: InteractiveReactionButtonContent) -> Bool {
        if lhs.theme !== rhs.theme {
            return false
        }
        return true
    }
    
    final class View: UIView {
        override public static var layerClass: AnyClass {
            return PassthroughLayer.self
        }
                
        private var icon: ComponentView<Empty>
        
        private var component: InteractiveReactionButtonContent?
    
        override init(frame: CGRect) {
            self.icon = ComponentView<Empty>()
            
            super.init(frame: frame)
            
            self.isExclusiveTouch = true
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        func update(component: InteractiveReactionButtonContent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
            let bounds = CGRect(origin: .zero, size: CGSize(width: 54.0, height: 54.0))
            let iconSize = self.icon.update(
                transition: .immediate,
                component: AnyComponent(BundleIconComponent(
                    name: "Media Editor/Reaction",
                    tintColor: nil,
                    maxSize: CGSize(width: 52.0, height: 52.0)
                )),
                environment: {},
                containerSize: availableSize
            )
            
            if let view = self.icon.view {
                if view.superview == nil {
                    self.addSubview(view)
                }
                transition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: 2.0, y: 0.0), size: iconSize))
            }
 
            return bounds.size
        }
    }
    
    public func makeView() -> View {
        return View(frame: CGRect())
    }
    
    public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
        view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
    }
}

private final class RoundVideoButtonContent: Component {
    let theme: PresentationTheme

    public init(
        theme: PresentationTheme
    ) {
        self.theme = theme
    }
    
    public static func ==(lhs: RoundVideoButtonContent, rhs: RoundVideoButtonContent) -> Bool {
        if lhs.theme !== rhs.theme {
            return false
        }
        return true
    }
    
    final class View: UIView {
        override public static var layerClass: AnyClass {
            return PassthroughLayer.self
        }
                
        private let backgroundLayer = SimpleLayer()
        private var icon: ComponentView<Empty>
        
        private var component: InteractiveReactionButtonContent?
    
        override init(frame: CGRect) {
            self.icon = ComponentView<Empty>()
            
            super.init(frame: frame)
            
            self.isExclusiveTouch = true
            
            self.layer.addSublayer(self.backgroundLayer)
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        func update(component: RoundVideoButtonContent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
            self.backgroundLayer.backgroundColor = UIColor(rgb: 0xffffff, alpha: 0.11).cgColor
            
            let bounds = CGRect(origin: .zero, size: CGSize(width: 54.0, height: 54.0))
            let backgroundSize = CGSize(width: 50.0, height: 50.0)
            self.backgroundLayer.frame = backgroundSize.centered(in: bounds)
            self.backgroundLayer.cornerRadius = backgroundSize.width / 2.0
            
            let iconSize = self.icon.update(
                transition: .immediate,
                component: AnyComponent(BundleIconComponent(
                    name: "Chat List/Tabs/IconCamera",
                    tintColor: nil,
                    maxSize: CGSize(width: 30.0, height: 30.0)
                )),
                environment: {},
                containerSize: availableSize
            )
            
            if let view = self.icon.view {
                if view.superview == nil {
                    self.addSubview(view)
                }
                transition.setFrame(view: view, frame: iconSize.centered(in: bounds))
            }
 
            return bounds.size
        }
    }
    
    public func makeView() -> View {
        return View(frame: CGRect())
    }
    
    public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
        view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
    }
}

final class ItemStack<ChildEnvironment: Equatable>: CombinedComponent {
    typealias EnvironmentType = ChildEnvironment

    private let items: [AnyComponentWithIdentity<ChildEnvironment>]
    private let padding: CGFloat
    private let minSpacing: CGFloat
    private let verticalSpacing: CGFloat
    private let maxHorizontalItems: Int

    init(_ items: [AnyComponentWithIdentity<ChildEnvironment>], padding: CGFloat, minSpacing: CGFloat, verticalSpacing: CGFloat, maxHorizontalItems: Int) {
        self.items = items
        self.padding = padding
        self.minSpacing = minSpacing
        self.verticalSpacing = verticalSpacing
        self.maxHorizontalItems = maxHorizontalItems
    }

    static func ==(lhs: ItemStack<ChildEnvironment>, rhs: ItemStack<ChildEnvironment>) -> Bool {
        if lhs.items != rhs.items {
            return false
        }
        if lhs.padding != rhs.padding {
            return false
        }
        if lhs.minSpacing != rhs.minSpacing {
            return false
        }
        if lhs.verticalSpacing != rhs.verticalSpacing {
            return false
        }
        if lhs.maxHorizontalItems != rhs.maxHorizontalItems {
            return false
        }
        return true
    }

    static var body: Body {
        let children = ChildMap(environment: ChildEnvironment.self, keyedBy: AnyHashable.self)

        return { context in
            let updatedChildren = context.component.items.map { item in
                return children[item.id].update(
                    component: item.component, environment: {
                        context.environment[ChildEnvironment.self]
                    },
                    availableSize: context.availableSize,
                    transition: context.transition
                )
            }

            var groups: [[Int]] = []
            var currentGroup: [Int] = []
            for i in 0 ..< updatedChildren.count {
                var itemsWidth: CGFloat = 0.0
                for j in currentGroup {
                    itemsWidth += updatedChildren[j].size.width
                }
                itemsWidth += updatedChildren[i].size.width
                let rowItemsCount = currentGroup.count + 1
                
                let remainingWidth = context.availableSize.width - itemsWidth - context.component.padding * 2.0
                let spacing = remainingWidth / CGFloat(rowItemsCount - 1)
                if spacing < context.component.minSpacing || currentGroup.count == context.component.maxHorizontalItems {
                    groups.append(currentGroup)
                    currentGroup = []
                }
                currentGroup.append(i)
            }
            if !currentGroup.isEmpty {
                groups.append(currentGroup)
            }
            
            var size = CGSize(width: context.availableSize.width, height: 0.0)
            for group in groups {
                var groupHeight: CGFloat = 0.0
                var spacing = context.component.minSpacing
                var itemsWidth = 0.0
                for i in group {
                    let childSize = updatedChildren[i].size
                    groupHeight = max(groupHeight, childSize.height)
                    itemsWidth += childSize.width
                }
                let remainingWidth = context.availableSize.width - itemsWidth - context.component.padding * 2.0
                spacing = remainingWidth / CGFloat(group.count - 1)
                
                var useCenteredLayout = false
                if spacing > 30.0 || group.count == 1 {
                    spacing = 30.0
                    useCenteredLayout = true
                }
                
                var nextX: CGFloat
                if useCenteredLayout {
                    let totalWidth = itemsWidth + spacing * CGFloat(group.count - 1)
                    nextX = floorToScreenPixels((size.width - totalWidth) / 2.0)
                } else {
                    nextX = context.component.padding
                }
                for i in group {
                    let child = updatedChildren[i]
                    let frame = CGRect(origin: CGPoint(x: nextX, y: size.height + floorToScreenPixels((groupHeight - child.size.height) / 2.0)), size: child.size)
                    
                    context.add(child
                        .position(child.size.centered(in: frame).center)
                    )
                    nextX += child.size.width + spacing
                }
                size.height += groupHeight + context.component.verticalSpacing
            }

            return size
        }
    }
}

final class StoryStickersContentView: UIView, EmojiCustomContentView {
    private let context: AccountContext
    
    let tintContainerView = UIView()
    private let container = ComponentView<Empty>()
        
    private var weatherDisposable: Disposable?
    private var weather: StickerPickerScreen.Weather = .none
    
    var locationAction: () -> Void = {}
    var audioAction: () -> Void = {}
    var reactionAction: () -> Void = {}
    var linkAction: () -> Void = {}
    var weatherAction: () -> Void = {}
        
    private var params: (theme: PresentationTheme, strings: PresentationStrings, useOpaqueTheme: Bool, availableSize: CGSize)?
    
    init(context: AccountContext, weather: Signal<StickerPickerScreen.Weather, NoError>) {
        self.context = context
        
        super.init(frame: .zero)
                
        self.weatherDisposable = (weather
        |> deliverOnMainQueue).start(next: { [weak self] weather in
            guard let self else {
                return
            }
            self.weather = weather
            if let (theme, strings, useOpaqueTheme, availableSize) = self.params {
                let _ = self.update(theme: theme, strings: strings, useOpaqueTheme: useOpaqueTheme, availableSize: availableSize, transition: .easeInOut(duration: 0.25))
            }
        })
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    deinit {
        self.weatherDisposable?.dispose()
    }
    
    func update(theme: PresentationTheme, strings: PresentationStrings, useOpaqueTheme: Bool, availableSize: CGSize, transition: ComponentTransition) -> CGSize {
        self.params = (theme, strings, useOpaqueTheme, availableSize)
        
        let padding: CGFloat = 22.0
        var maxHorizontalItems = 2
        var items: [AnyComponentWithIdentity<Empty>] = []
        items.append(
            AnyComponentWithIdentity(
                id: "link",
                component: AnyComponent(
                    CameraButton(
                        content: AnyComponentWithIdentity(
                            id: "content",
                            component: AnyComponent(
                                InteractiveStickerButtonContent(
                                    context: self.context,
                                    theme: theme,
                                    title: strings.MediaEditor_AddLink,
                                    iconName: self.context.isPremium ? "Media Editor/Link" : "Media Editor/LinkLocked",
                                    useOpaqueTheme: useOpaqueTheme,
                                    tintContainerView: self.tintContainerView
                                )
                            )
                        ),
                        action: { [weak self] in
                            if let self {
                                self.linkAction()
                            }
                        })
                )
            )
        )
        items.append(
            AnyComponentWithIdentity(
                id: "location",
                component: AnyComponent(
                    CameraButton(
                        content: AnyComponentWithIdentity(
                            id: "content",
                            component: AnyComponent(
                                InteractiveStickerButtonContent(
                                    context: self.context,
                                    theme: theme,
                                    title: strings.MediaEditor_AddLocationShort,
                                    iconName: "Chat/Attach Menu/Location",
                                    useOpaqueTheme: useOpaqueTheme,
                                    tintContainerView: self.tintContainerView
                                )
                            )
                        ),
                        action: { [weak self] in
                            if let self {
                                self.locationAction()
                            }
                        })
                )
            )
        )
        
        if case .none = self.weather {
            
        } else {
            maxHorizontalItems = 3
            
            let weatherButtonContent: AnyComponent<Empty>
            switch self.weather {
            case .notAllowed, .notDetermined, .notPreloaded:
                weatherButtonContent = AnyComponent(
                    InteractiveStickerButtonContent(
                        context: self.context,
                        theme: theme,
                        title: stringForTemperature(24),
                        iconName: "☀️",
                        iconFile: self.context.animatedEmojiStickersValue["☀️"]?.first?.file,
                        useOpaqueTheme: useOpaqueTheme,
                        tintContainerView: self.tintContainerView
                    )
                )
            case let .loaded(weather):
                weatherButtonContent = AnyComponent(
                    InteractiveStickerButtonContent(
                        context: self.context,
                        theme: theme,
                        title: stringForTemperature(weather.temperature),
                        iconName: weather.emoji,
                        iconFile: weather.emojiFile,
                        useOpaqueTheme: useOpaqueTheme,
                        tintContainerView: self.tintContainerView
                    )
                )
            case .fetching:
                weatherButtonContent = AnyComponent(
                    InteractiveStickerButtonContent(
                        context: self.context,
                        theme: theme,
                        title: nil,
                        iconName: nil,
                        useOpaqueTheme: useOpaqueTheme,
                        tintContainerView: self.tintContainerView
                    )
                )
            default:
                fatalError()
            }
            items.append(
                AnyComponentWithIdentity(
                    id: "weather",
                    component: AnyComponent(
                        CameraButton(
                            content: AnyComponentWithIdentity(
                                id: "weather",
                                component: weatherButtonContent
                            ),
                            action: { [weak self] in
                                if let self {
                                    self.weatherAction()
                                }
                            })
                    )
                )
            )
        }
        
        items.append(
            AnyComponentWithIdentity(
                id: "audio",
                component: AnyComponent(
                    CameraButton(
                        content: AnyComponentWithIdentity(
                            id: "audio",
                            component: AnyComponent(
                                InteractiveStickerButtonContent(
                                    context: self.context,
                                    theme: theme,
                                    title: strings.MediaEditor_AddAudio,
                                    iconName: "Media Editor/Audio",
                                    useOpaqueTheme: useOpaqueTheme,
                                    tintContainerView: self.tintContainerView
                                )
                            )
                        ),
                        action: { [weak self] in
                            if let self {
                                self.audioAction()
                            }
                        })
                )
            )
        )
        
        items.append(
            AnyComponentWithIdentity(
                id: "reaction",
                component: AnyComponent(
                    CameraButton(
                        content: AnyComponentWithIdentity(
                            id: "reaction",
                            component: AnyComponent(
                                InteractiveReactionButtonContent(theme: theme)
                            )
                        ),
                        action: { [weak self] in
                            if let self {
                                self.reactionAction()
                            }
                        })
                )
            )
        )
        
        let size = self.container.update(
            transition: transition,
            component: AnyComponent(
                ItemStack(
                    items,
                    padding: 18.0,
                    minSpacing: 8.0,
                    verticalSpacing: 12.0,
                    maxHorizontalItems: maxHorizontalItems
                )
            ),
            environment: {},
            containerSize: availableSize
        )
        if let view = self.container.view {
            if view.superview == nil {
                self.addSubview(view)
            }
            view.frame = CGRect(origin: CGPoint(x: 0.0, y: padding), size: size)
        }
        
        return CGSize(width: size.width, height: size.height + padding * 2.0 - 12.0)
    }
}

private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
    let controller: ViewController
    weak var sourceView: UIView?
    let sourceRect: CGRect
    
    let navigationController: NavigationController? = nil
    
    let passthroughTouches: Bool = false
    
    init(controller: ViewController, sourceView: UIView?, sourceRect: CGRect) {
        self.controller = controller
        self.sourceView = sourceView
        self.sourceRect = sourceRect
    }
    
    func transitionInfo() -> ContextControllerTakeControllerInfo? {
        let sourceView = self.sourceView
        let sourceRect = self.sourceRect
        return ContextControllerTakeControllerInfo(contentAreaInScreenSpace: CGRect(origin: CGPoint(), size: CGSize(width: 10.0, height: 10.0)), sourceNode: { [weak sourceView] in
            if let sourceView = sourceView {
                return (sourceView, sourceRect)
            } else {
                return nil
            }
        })
    }
    
    func animatedIn() {
        if let controller = self.controller as? GalleryController {
            controller.viewDidAppear(false)
        }
    }
}