import Foundation
import UIKit
import Display
import AsyncDisplayKit
import ComponentFlow
import SwiftSignalKit
import AnimationCache
import MultiAnimationRenderer
import EntityKeyboard
import ComponentDisplayAdapters
import TelegramPresentationData
import AccountContext
import PagerComponent
import TelegramCore
import Lottie
import EmojiTextAttachmentView
import TextFormat
import AppBundle
import GZip
import EmojiStatusComponent
import Postbox

private func randomGenericReactionEffect(context: AccountContext) -> Signal<String?, NoError> {
    return context.engine.stickers.loadedStickerPack(reference: .emojiGenericAnimations, forceActualized: false)
    |> map { result -> [TelegramMediaFile]? in
        switch result {
        case let .result(_, items, _):
            return items.map(\.file)
        default:
            return nil
        }
    }
    |> take(1)
    |> mapToSignal { items -> Signal<String?, NoError> in
        guard let items = items else {
            return .single(nil)
        }
        guard let file = items.randomElement() else {
            return .single(nil)
        }
        return Signal { subscriber in
            let fetchDisposable = freeMediaFileInteractiveFetched(account: context.account, userLocation: .other, fileReference: .standalone(media: file)).start()
            let dataDisposable = (context.account.postbox.mediaBox.resourceData(file.resource)
            |> filter(\.complete)
            |> take(1)).start(next: { data in
                subscriber.putNext(data.path)
                subscriber.putCompletion()
            })
            
            return ActionDisposable {
                fetchDisposable.dispose()
                dataDisposable.dispose()
            }
        }
    }
}

public final class EmojiStatusSelectionComponent: Component {
    public typealias EnvironmentType = Empty
    
    public let theme: PresentationTheme
    public let strings: PresentationStrings
    public let deviceMetrics: DeviceMetrics
    public let emojiContent: EmojiPagerContentComponent
    public let backgroundColor: UIColor
    public let separatorColor: UIColor
    public let hideTopPanel: Bool
    public let hideTopPanelUpdated: (Bool, Transition) -> Void
    
    public init(
        theme: PresentationTheme,
        strings: PresentationStrings,
        deviceMetrics: DeviceMetrics,
        emojiContent: EmojiPagerContentComponent,
        backgroundColor: UIColor,
        separatorColor: UIColor,
        hideTopPanel: Bool,
        hideTopPanelUpdated: @escaping (Bool, Transition) -> Void
    ) {
        self.theme = theme
        self.strings = strings
        self.deviceMetrics = deviceMetrics
        self.emojiContent = emojiContent
        self.backgroundColor = backgroundColor
        self.separatorColor = separatorColor
        self.hideTopPanel = hideTopPanel
        self.hideTopPanelUpdated = hideTopPanelUpdated
    }
    
    public static func ==(lhs: EmojiStatusSelectionComponent, rhs: EmojiStatusSelectionComponent) -> Bool {
        if lhs.theme !== rhs.theme {
            return false
        }
        if lhs.strings != rhs.strings {
            return false
        }
        if lhs.deviceMetrics != rhs.deviceMetrics {
            return false
        }
        if lhs.emojiContent != rhs.emojiContent {
            return false
        }
        if lhs.backgroundColor != rhs.backgroundColor {
            return false
        }
        if lhs.separatorColor != rhs.separatorColor {
            return false
        }
        if lhs.hideTopPanel != rhs.hideTopPanel {
            return false
        }
        return true
    }
    
    public final class View: UIView {
        private let keyboardView: ComponentView<Empty>
        private let keyboardClippingView: UIView
        private let panelHostView: PagerExternalTopPanelContainer
        private let panelBackgroundView: BlurredBackgroundView
        private let panelSeparatorView: UIView
        
        private var component: EmojiStatusSelectionComponent?
        private weak var state: EmptyComponentState?
        
        override init(frame: CGRect) {
            self.keyboardView = ComponentView<Empty>()
            self.keyboardClippingView = UIView()
            self.panelHostView = PagerExternalTopPanelContainer()
            self.panelBackgroundView = BlurredBackgroundView(color: .clear, enableBlur: true)
            self.panelSeparatorView = UIView()
            
            super.init(frame: frame)
            
            self.addSubview(self.keyboardClippingView)
            self.addSubview(self.panelBackgroundView)
            self.addSubview(self.panelSeparatorView)
            self.addSubview(self.panelHostView)
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        deinit {
        }
        
        func update(component: EmojiStatusSelectionComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> 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 = component.hideTopPanel ? 0.0 : 42.0
            
            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, left: 0.0, bottom: 0.0, right: 0.0),
                    topPanelInsets: UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0),
                    emojiContent: component.emojiContent,
                    stickerContent: nil,
                    maskContent: nil,
                    gifContent: nil,
                    hasRecentGifs: false,
                    availableGifSearchEmojies: [],
                    defaultToEmojiTab: true,
                    externalTopPanelContainer: self.panelHostView,
                    externalBottomPanelContainer: nil,
                    displayTopPanelBackground: .blur,
                    topPanelExtensionUpdated: { _, _ in },
                    hideInputUpdated: { _, _, _ in },
                    hideTopPanelUpdated: { [weak self] hideTopPanel, transition in
                        guard let strongSelf = self else {
                            return
                        }
                        strongSelf.component?.hideTopPanelUpdated(hideTopPanel, transition)
                    },
                    switchToTextInput: {},
                    switchToGifSubject: { _ in },
                    reorderItems: { _, _ in },
                    makeSearchContainerNode: { _ in return nil },
                    contentIdUpdated: { _ in },
                    deviceMetrics: component.deviceMetrics,
                    hiddenInputHeight: 0.0,
                    inputHeight: 0.0,
                    displayBottomPanel: false,
                    isExpanded: false,
                    clipContentToTopPanel: false,
                    useExternalSearchContainer: false
                )),
                environment: {},
                containerSize: availableSize
            )
            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), size: CGSize(width: availableSize.width, height: availableSize.height - topPanelHeight)))
                
                transition.setFrame(view: keyboardComponentView, frame: CGRect(origin: CGPoint(x: 0.0, y: -topPanelHeight), size: keyboardSize))
                transition.setFrame(view: self.panelHostView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight - 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)))
                self.panelBackgroundView.update(size: self.panelBackgroundView.bounds.size, transition: transition.containedViewLayoutTransition)
                
                transition.setFrame(view: self.panelSeparatorView, frame: CGRect(origin: CGPoint(x: 0.0, y: component.hideTopPanel ? -UIScreenPixel : topPanelHeight), size: CGSize(width: keyboardSize.width, height: UIScreenPixel)))
                transition.setAlpha(view: self.panelSeparatorView, alpha: component.hideTopPanel ? 0.0 : 1.0)
            }
            
            return availableSize
        }
    }

    public func makeView() -> View {
        return View(frame: CGRect())
    }
    
    public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
        return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
    }
}

public final class EmojiStatusSelectionController: ViewController {
    private final class Node: ViewControllerTracingNode {
        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 weak var controller: EmojiStatusSelectionController?
        private let context: AccountContext
        private weak var sourceView: UIView?
        private var globalSourceRect: CGRect?
        
        private let componentHost: ComponentView<Empty>
        private let componentShadowLayer: SimpleLayer
        
        private let cloudLayer0: SimpleLayer
        private let cloudShadowLayer0: SimpleLayer
        private let cloudLayer1: SimpleLayer
        private let cloudShadowLayer1: SimpleLayer
        
        private var presentationData: PresentationData
        private var validLayout: ContainerViewLayout?
        
        private let currentSelection: Int64?
        
        private var emojiContentDisposable: Disposable?
        private var emojiContent: EmojiPagerContentComponent?
        private var freezeUpdates: Bool = false
        private var scheduledEmojiContentAnimationHint: EmojiPagerContentComponent.ContentAnimation?
        
        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 var emptyResultEmojis: [TelegramMediaFile] = []
        private var stableEmptyResultEmoji: TelegramMediaFile?
        private let stableEmptyResultEmojiDisposable = MetaDisposable()
        
        private var previewItem: (groupId: AnyHashable, item: EmojiPagerContentComponent.Item)?
        private var dismissedPreviewItem: (groupId: AnyHashable, item: EmojiPagerContentComponent.Item)?
        private var previewScreenView: ComponentView<Empty>?
        
        private var availableReactions: AvailableReactions?
        private var availableReactionsDisposable: Disposable?
        
        private var genericReactionEffectDisposable: Disposable?
        private var genericReactionEffect: String?
        
        private var hapticFeedback: HapticFeedback?
        
        private var isAnimatingOut: Bool = false
        private var isDismissed: Bool = false
        
        private var isReactionSearchActive: Bool = false
        
        init(controller: EmojiStatusSelectionController, context: AccountContext, sourceView: UIView?, emojiContent: Signal<EmojiPagerContentComponent, NoError>, currentSelection: Int64?) {
            self.controller = controller
            self.context = context
            self.currentSelection = currentSelection
            
            if let sourceView = sourceView {
                self.globalSourceRect = sourceView.convert(sourceView.bounds, to: nil)
            }
            
            self.presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
            
            self.componentHost = ComponentView<Empty>()
            self.componentShadowLayer = SimpleLayer()
            self.componentShadowLayer.shadowOpacity = 0.12
            self.componentShadowLayer.shadowColor = UIColor(white: 0.0, alpha: 1.0).cgColor
            self.componentShadowLayer.shadowOffset = CGSize(width: 0.0, height: 2.0)
            self.componentShadowLayer.shadowRadius = 16.0
            
            self.cloudLayer0 = SimpleLayer()
            self.cloudShadowLayer0 = SimpleLayer()
            self.cloudShadowLayer0.shadowOpacity = 0.12
            self.cloudShadowLayer0.shadowColor = UIColor(white: 0.0, alpha: 1.0).cgColor
            self.cloudShadowLayer0.shadowOffset = CGSize(width: 0.0, height: 2.0)
            self.cloudShadowLayer0.shadowRadius = 16.0
            
            self.cloudLayer1 = SimpleLayer()
            self.cloudShadowLayer1 = SimpleLayer()
            self.cloudShadowLayer1.shadowOpacity = 0.12
            self.cloudShadowLayer1.shadowColor = UIColor(white: 0.0, alpha: 1.0).cgColor
            self.cloudShadowLayer1.shadowOffset = CGSize(width: 0.0, height: 2.0)
            self.cloudShadowLayer1.shadowRadius = 16.0
            
            super.init()
            
            self.layer.addSublayer(self.componentShadowLayer)
            self.layer.addSublayer(self.cloudShadowLayer0)
            self.layer.addSublayer(self.cloudShadowLayer1)
            
            self.layer.addSublayer(self.cloudLayer0)
            self.layer.addSublayer(self.cloudLayer1)
            
            self.stableEmptyResultEmojiDisposable.set((self.context.engine.data.get(
                TelegramEngine.EngineData.Item.Collections.FeaturedEmojiPacks()
            )
            |> deliverOnMainQueue).start(next: { [weak self] featuredEmojiPacks in
                guard let strongSelf = self else {
                    return
                }
                var filteredFiles: [TelegramMediaFile] = []
                let filterList: [String] = ["😖", "😫", "🫠", "😨", "❓"]
                for featuredEmojiPack in featuredEmojiPacks {
                    for item in featuredEmojiPack.topItems {
                        for attribute in item.file.attributes {
                            switch attribute {
                            case let .CustomEmoji(_, _, alt, _):
                                if filterList.contains(alt) {
                                    filteredFiles.append(item.file)
                                }
                            default:
                                break
                            }
                        }
                    }
                }
                strongSelf.emptyResultEmojis = filteredFiles
            }))
            
            self.emojiContentDisposable = (combineLatest(queue: .mainQueue(),
                emojiContent,
                self.emojiSearchState.get()
            )
            |> deliverOnMainQueue).start(next: { [weak self] emojiContent, emojiSearchState in
                guard let strongSelf = self else {
                    return
                }
                strongSelf.controller?._ready.set(.single(true))
                                
                var emojiContent = emojiContent
                if let emojiSearchResult = emojiSearchState.result {
                    var emptySearchResults: EmojiPagerContentComponent.EmptySearchResults?
                    if !emojiSearchResult.groups.contains(where: { !$0.items.isEmpty || $0.fillWithLoadingPlaceholders }) {
                        if strongSelf.stableEmptyResultEmoji == nil {
                            strongSelf.stableEmptyResultEmoji = strongSelf.emptyResultEmojis.randomElement()
                        }
                        emptySearchResults = EmojiPagerContentComponent.EmptySearchResults(
                            text: strongSelf.presentationData.strings.EmojiSearch_SearchStatusesEmptyResult,
                            iconFile: strongSelf.stableEmptyResultEmoji
                        )
                    } else {
                        strongSelf.stableEmptyResultEmoji = nil
                    }
                    emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: emojiSearchResult.id, version: emojiSearchResult.version), emptySearchResults: emptySearchResults, searchState: emojiSearchState.isSearching ? .searching : .active)
                } else {
                    strongSelf.stableEmptyResultEmoji = nil
                }
                
                if strongSelf.emojiContent == nil || !strongSelf.freezeUpdates {
                    strongSelf.emojiContent = emojiContent
                }
                
                emojiContent.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction(
                    performItemAction: { groupId, item, _, _, _, isPreview in
                        guard let strongSelf = self else {
                            return
                        }
                        strongSelf.applyItem(groupId: groupId, item: item, isPreview: isPreview)
                    },
                    deleteBackwards: {
                    },
                    openStickerSettings: {
                    },
                    openFeatured: {
                    },
                    openSearch: {
                    },
                    addGroupAction: { groupId, isPremiumLocked, _ in
                        guard let strongSelf = self, let collectionId = groupId.base as? ItemCollectionId else {
                            return
                        }
                        
                        let _ = (strongSelf.context.engine.data.get(
                            TelegramEngine.EngineData.Item.Collections.FeaturedEmojiPacks()
                        )
                        |> deliverOnMainQueue).start(next: { featuredEmojiPacks in
                            guard let strongSelf = self else {
                                return
                            }
                            for featuredEmojiPack in featuredEmojiPacks {
                                if featuredEmojiPack.info.id == collectionId {
                                    if let strongSelf = self {
                                        strongSelf.scheduledEmojiContentAnimationHint = EmojiPagerContentComponent.ContentAnimation(type: .groupInstalled(id: collectionId, scrollToGroup: true))
                                    }
                                    let _ = strongSelf.context.engine.stickers.addStickerPackInteractively(info: featuredEmojiPack.info, items: featuredEmojiPack.topItems).start()
                                    
                                    break
                                }
                            }
                        })
                    },
                    clearGroup: { groupId in
                    },
                    pushController: { c in
                    },
                    presentController: { c in
                    },
                    presentGlobalOverlayController: { c in
                    },
                    navigationController: {
                        return nil
                    },
                    requestUpdate: { _ in
                    },
                    updateSearchQuery: { query in
                        guard let self = self else {
                            return
                        }
                        
                        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 {
                                let context = self.context
                                
                                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 = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
                                |> map { peer -> Bool in
                                    guard case let .user(user) = peer else {
                                        return false
                                    }
                                    return user.isPremium
                                }
                                |> distinctUntilChanged
                                
                                let resultSignal = signal
                                |> mapToSignal { keywords -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in
                                    return combineLatest(
                                        context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000),
                                        context.engine.stickers.availableReactions(),
                                        hasPremium
                                    )
                                    |> take(1)
                                    |> map { view, availableReactions, hasPremium -> [EmojiPagerContentComponent.ItemGroup] in
                                        var result: [(String, TelegramMediaFile?, String)] = []
                                        
                                        var allEmoticons: [String: String] = [:]
                                        for keyword in keywords {
                                            for emoticon in keyword.emoticons {
                                                allEmoticons[emoticon] = keyword.keyword
                                            }
                                        }
                                        
                                        for entry in view.entries {
                                            guard let item = entry.item as? StickerPackItem else {
                                                continue
                                            }
                                            for attribute in item.file.attributes {
                                                switch attribute {
                                                case let .CustomEmoji(_, _, alt, _):
                                                    if !item.file.isPremiumEmoji || hasPremium {
                                                        if !alt.isEmpty, let keyword = allEmoticons[alt] {
                                                            result.append((alt, item.file, keyword))
                                                        } else if alt == query {
                                                            result.append((alt, item.file, alt))
                                                        }
                                                    }
                                                default:
                                                    break
                                                }
                                            }
                                        }
                                        
                                        var items: [EmojiPagerContentComponent.Item] = []
                                        
                                        var existingIds = Set<MediaId>()
                                        for item in result {
                                            if let itemFile = item.1 {
                                                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 [EmojiPagerContentComponent.ItemGroup(
                                            supergroupId: "search",
                                            groupId: "search",
                                            title: nil,
                                            subtitle: nil,
                                            actionButtonTitle: nil,
                                            isFeatured: false,
                                            isPremiumLocked: false,
                                            isEmbedded: false,
                                            hasClear: 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 = self.context.engine.stickers.searchEmoji(emojiString: value)
                            |> mapToSignal { files, isFinalResult -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in
                                var items: [EmojiPagerContentComponent.Item] = []
                                
                                var existingIds = Set<EngineMedia.Id>()
                                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,
                                    actionButtonTitle: nil,
                                    isFeatured: false,
                                    isPremiumLocked: false,
                                    isEmbedded: false,
                                    hasClear: 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.isSearching = true
                                    self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: [
                                        EmojiPagerContentComponent.ItemGroup(
                                            supergroupId: "search",
                                            groupId: "search",
                                            title: nil,
                                            subtitle: nil,
                                            actionButtonTitle: nil,
                                            isFeatured: false,
                                            isPremiumLocked: false,
                                            isEmbedded: false,
                                            hasClear: false,
                                            collapsedLineCount: nil,
                                            displayPremiumBadges: false,
                                            headerItem: nil,
                                            fillWithLoadingPlaceholders: true,
                                            items: []
                                        )
                                    ], id: AnyHashable(value), version: version, isPreset: true), isSearching: false)
                                    return
                                }
                                
                                self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value), version: version, isPreset: true), isSearching: false)
                                version += 1
                            }))
                        }
                    },
                    updateScrollingToItemGroup: {
                    },
                    onScroll: {},
                    chatPeerId: nil,
                    peekBehavior: nil,
                    customLayout: nil,
                    externalBackground: nil,
                    externalExpansionView: nil,
                    useOpaqueTheme: true,
                    hideBackground: false,
                    stateContext: nil,
                    addImage: nil
                )
                
                strongSelf.refreshLayout(transition: .immediate)
            })
            
            self.availableReactionsDisposable = (context.engine.stickers.availableReactions()
            |> take(1)
            |> deliverOnMainQueue).start(next: { [weak self] availableReactions in
                guard let strongSelf = self else {
                    return
                }
                strongSelf.availableReactions = availableReactions
            })
            
            self.genericReactionEffectDisposable = (randomGenericReactionEffect(context: context)
            |> deliverOnMainQueue).start(next: { [weak self] path in
                self?.genericReactionEffect = path
            })
        }
        
        deinit {
            self.emojiContentDisposable?.dispose()
            self.availableReactionsDisposable?.dispose()
            self.genericReactionEffectDisposable?.dispose()
            self.emojiSearchDisposable.dispose()
        }
        
        private func refreshLayout(transition: Transition) {
            guard let layout = self.validLayout else {
                return
            }
            self.containerLayoutUpdated(layout: layout, transition: transition)
        }
        
        func animateOut(completion: @escaping () -> Void, fromBackground: Bool) {
            if self.isAnimatingOut {
                return
            }
            self.isAnimatingOut = true
            
            let duration: Double = fromBackground ? 0.1 : 0.25
            
            self.componentShadowLayer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false)
            self.componentHost.view?.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false, completion: { _ in
                completion()
            })
            
            self.cloudLayer0.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false)
            self.cloudShadowLayer0.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false)
            self.cloudLayer1.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false)
            self.cloudShadowLayer1.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false)
        }
        
        func animateOutToStatus(item: EmojiPagerContentComponent.Item, sourceLayer: CALayer, customEffectFile: String?, destinationView: UIView, fromBackground: Bool) {
            self.isUserInteractionEnabled = false
            destinationView.isHidden = true
            
            let hapticFeedback: HapticFeedback
            if let current = self.hapticFeedback {
                hapticFeedback = current
            } else {
                hapticFeedback = HapticFeedback()
                self.hapticFeedback = hapticFeedback
            }
            
            hapticFeedback.prepareTap()
            
            var itemCompleted = false
            var contentCompleted = false
            var effectCompleted = false
            let completion: () -> Void = { [weak self] in
                guard let strongSelf = self, itemCompleted, contentCompleted, effectCompleted else {
                    return
                }
                strongSelf.controller?.dismissNow()
            }
            
            var effectView: AnimationView?
            
            if let customEffectFile = customEffectFile, let data = try? Data(contentsOf: URL(fileURLWithPath: customEffectFile)), let composition = try? Animation.from(data: TGGUnzipData(data, 2 * 1024 * 1024) ?? data) {
                let view = AnimationView(animation: composition, configuration: LottieConfiguration(renderingEngine: .mainThread, decodingStrategy: .codable))
                view.animationSpeed = 1.0
                view.backgroundColor = nil
                view.isOpaque = false
                
                effectView = view
            } else if let itemFile = item.itemFile {
                var useCleanEffect = false
                if itemFile.isCustomTemplateEmoji {
                    useCleanEffect = true
                }
                for attribute in itemFile.attributes {
                    if case let .CustomEmoji(_, _, _, packReference) = attribute {
                        switch packReference {
                        case let .id(id, _):
                            if id == 773947703670341676 || id == 2964141614563343 {
                                useCleanEffect = true
                            }
                        default:
                            break
                        }
                    }
                }
                
                var effectData: Data?
                if useCleanEffect {
                    if let url = getAppBundle().url(forResource: "generic_reaction_avatar_effect", withExtension: "json") {
                        effectData = try? Data(contentsOf: url)
                    }
                } else if let genericReactionEffect = self.genericReactionEffect, let data = try? Data(contentsOf: URL(fileURLWithPath: genericReactionEffect)) {
                    effectData = TGGUnzipData(data, 5 * 1024 * 1024) ?? data
                } else {
                    if let url = getAppBundle().url(forResource: "generic_reaction_small_effect", withExtension: "json") {
                        effectData = try? Data(contentsOf: url)
                    }
                }
                
                if let effectData = effectData, let composition = try? Animation.from(data: effectData) {
                    let view = AnimationView(animation: composition, configuration: LottieConfiguration(renderingEngine: .mainThread, decodingStrategy: .codable))
                    view.animationSpeed = 1.0
                    view.backgroundColor = nil
                    view.isOpaque = false
                    
                    let animationCache = self.context.animationCache
                    let animationRenderer = self.context.animationRenderer
                    
                    for i in 1 ... 7 {
                        let allLayers = view.allLayers(forKeypath: AnimationKeypath(keypath: "placeholder_\(i)"))
                        for animationLayer in allLayers {
                            let baseItemLayer = InlineStickerItemLayer(
                                context: self.context,
                                userLocation: .other,
                                attemptSynchronousLoad: false,
                                emoji: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: itemFile.fileId.id, file: itemFile),
                                file: item.itemFile,
                                cache: animationCache,
                                renderer: animationRenderer,
                                placeholderColor: UIColor(white: 0.0, alpha: 0.0),
                                pointSize: CGSize(width: 32.0, height: 32.0),
                                dynamicColor: self.presentationData.theme.list.itemAccentColor
                            )
                            switch item.tintMode {
                            case .accent:
                                baseItemLayer.contentTintColor = self.presentationData.theme.list.itemAccentColor
                            case .primary:
                                baseItemLayer.contentTintColor = self.presentationData.theme.list.itemPrimaryTextColor
                            case .none:
                                break
                            }
                            
                            if let sublayers = animationLayer.sublayers {
                                for sublayer in sublayers {
                                    sublayer.isHidden = true
                                }
                            }
                            
                            baseItemLayer.isVisibleForAnimations = true
                            baseItemLayer.frame = CGRect(origin: CGPoint(x: -0.0, y: -0.0), size: CGSize(width: 500.0, height: 500.0))
                            animationLayer.addSublayer(baseItemLayer)
                        }
                    }
                    
                    effectView = view
                }
            }
            
            if let sourceCopyLayer = sourceLayer.snapshotContentTree() {
                self.layer.addSublayer(sourceCopyLayer)
                sourceCopyLayer.frame = sourceLayer.convert(sourceLayer.bounds, to: self.layer)
                sourceLayer.isHidden = true
                
                let previousSourceCopyFrame = sourceCopyLayer.frame
                
                let localDestinationFrame = destinationView.convert(destinationView.bounds, to: self.view)
                let destinationSize = max(localDestinationFrame.width, localDestinationFrame.height)
                let effectFrame = localDestinationFrame.insetBy(dx: -destinationSize * 2.0, dy: -destinationSize * 2.0)
                let destinationNormalScale = localDestinationFrame.width / previousSourceCopyFrame.width
                
                let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .linear)
                sourceCopyLayer.position = localDestinationFrame.center
                
                var midPointY: CGFloat = localDestinationFrame.center.y - 30.0
                if let layout = self.validLayout {
                    if midPointY < layout.safeInsets.top + 8.0 {
                        midPointY = max(localDestinationFrame.center.y, layout.safeInsets.top + 20.0)
                    }
                }
                
                transition.animatePositionWithKeyframes(layer: sourceCopyLayer, keyframes: generateParabollicMotionKeyframes(from: previousSourceCopyFrame.center, to: localDestinationFrame.center, midPointY: midPointY), completion: { [weak self, weak sourceCopyLayer, weak destinationView] _ in
                    guard let strongSelf = self else {
                        return
                    }
                    
                    itemCompleted = true
                    sourceCopyLayer?.isHidden = true
                    if let destinationView = destinationView {
                        destinationView.isHidden = false
                        destinationView.layer.animateScale(from: 0.3, to: 1.0, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring)
                    }
                    
                    hapticFeedback.tap()
                    
                    if let effectView = effectView {
                        effectView.frame = effectFrame
                        strongSelf.view.addSubview(effectView)
                        effectView.play(completion: { _ in
                            effectCompleted = true
                            completion()
                        })
                    } else {
                        effectCompleted = true
                    }
                    
                    completion()
                })
                let scaleKeyframes: [CGFloat] = [
                    1.0,
                    1.4,
                    1.0,
                    destinationNormalScale * 0.3
                ]
                sourceCopyLayer.transform = CATransform3DMakeScale(scaleKeyframes[scaleKeyframes.count - 1], scaleKeyframes[scaleKeyframes.count - 1], 1.0)
                sourceCopyLayer.animateKeyframes(values: scaleKeyframes.map({ $0 as NSNumber }), duration: 0.2, keyPath: "transform.scale", timingFunction: CAMediaTimingFunctionName.linear.rawValue)
            } else {
                itemCompleted = true
                destinationView.isHidden = false
            }
            
            if let previewScreenView = self.previewScreenView {
                self.previewItem = nil
                self.dismissedPreviewItem = nil
                self.previewScreenView = nil
                
                if let previewScreenComponentView = previewScreenView.view as? EmojiStatusPreviewScreenComponent.View {
                    previewScreenComponentView.animateOut(targetLayer: nil, completion: { [weak previewScreenComponentView] in
                        previewScreenComponentView?.removeFromSuperview()
                    })
                } else {
                    previewScreenView.view?.removeFromSuperview()
                }
            }
            
            self.animateOut(completion: {
                contentCompleted = true
                completion()
            }, fromBackground: fromBackground)
        }
        
        func containerLayoutUpdated(layout: ContainerViewLayout, transition: Transition) {
            self.validLayout = layout
            
            var transition = transition
            
            guard let emojiContent = self.emojiContent else {
                return
            }
            
            let listBackgroundColor: UIColor
            let separatorColor: UIColor
            if self.presentationData.theme.overallDarkAppearance {
                listBackgroundColor = self.presentationData.theme.list.itemBlocksBackgroundColor
                separatorColor = self.presentationData.theme.list.itemBlocksSeparatorColor
                self.componentShadowLayer.shadowOpacity = 0.32
                self.cloudShadowLayer0.shadowOpacity = 0.32
                self.cloudShadowLayer1.shadowOpacity = 0.32
            } else {
                listBackgroundColor = self.presentationData.theme.list.plainBackgroundColor
                separatorColor = self.presentationData.theme.list.itemPlainSeparatorColor.withMultipliedAlpha(0.5)
                self.componentShadowLayer.shadowOpacity = 0.12
                self.cloudShadowLayer0.shadowOpacity = 0.12
                self.cloudShadowLayer1.shadowOpacity = 0.12
            }
            
            self.cloudLayer0.backgroundColor = listBackgroundColor.cgColor
            self.cloudLayer1.backgroundColor = listBackgroundColor.cgColor
            
            let sideInset: CGFloat = 16.0
            
            if let scheduledEmojiContentAnimationHint = self.scheduledEmojiContentAnimationHint {
                self.scheduledEmojiContentAnimationHint = nil
                let contentAnimation = scheduledEmojiContentAnimationHint
                transition = Transition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(contentAnimation)
            }
            
            var componentWidth = layout.size.width - sideInset * 2.0
            let nativeItemSize: CGFloat = 40.0
            let minSpacing: CGFloat = 9.0
            let itemsPerRow = Int((componentWidth + minSpacing) / (nativeItemSize + minSpacing))
            if itemsPerRow > 8 {
                componentWidth = min(componentWidth, 480.0)
            }
            
            let componentSize = self.componentHost.update(
                transition: transition,
                component: AnyComponent(EmojiStatusSelectionComponent(
                    theme: self.presentationData.theme,
                    strings: self.presentationData.strings,
                    deviceMetrics: layout.deviceMetrics,
                    emojiContent: emojiContent,
                    backgroundColor: listBackgroundColor,
                    separatorColor: separatorColor,
                    hideTopPanel: self.isReactionSearchActive,
                    hideTopPanelUpdated: { [weak self] hideTopPanel, transition in
                        guard let strongSelf = self else {
                            return
                        }
                        strongSelf.isReactionSearchActive = hideTopPanel
                        strongSelf.refreshLayout(transition: transition)
                    }
                )),
                environment: {},
                containerSize: CGSize(width: componentWidth, height: min(308.0, layout.size.height))
            )
            if let componentView = self.componentHost.view {
                var animateIn = false
                if componentView.superview == nil {
                    self.view.addSubview(componentView)
                    animateIn = true
                    
                    componentView.clipsToBounds = true
                    componentView.layer.cornerRadius = 24.0
                }
                
                var sourceOrigin: CGPoint
                if let sourceView = self.sourceView {
                    let sourceRect = sourceView.convert(sourceView.bounds, to: self.view)
                    sourceOrigin = CGPoint(x: sourceRect.midX, y: sourceRect.maxY)
                } else if let globalSourceRect = self.globalSourceRect {
                    let sourceRect = self.view.convert(globalSourceRect, from: nil)
                    sourceOrigin = CGPoint(x: sourceRect.midX, y: sourceRect.maxY)
                } else {
                    sourceOrigin = CGPoint(x: layout.size.width / 2.0, y: floor(layout.size.height / 2.0 - componentSize.height))
                }
                
                if sourceOrigin.y + 5.0 + componentSize.height > layout.size.height - layout.insets(options: []).bottom {
                    sourceOrigin.y = layout.size.height - layout.insets(options: []).bottom - componentSize.height - 5.0
                }
                
                let componentFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - componentSize.width) / 2.0), y: sourceOrigin.y + 5.0), size: componentSize)
                
                if self.componentShadowLayer.bounds.size != componentFrame.size {
                    let componentShadowPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: componentFrame.size), cornerRadius: 24.0).cgPath
                    self.componentShadowLayer.shadowPath = componentShadowPath
                }
                transition.setFrame(layer: self.componentShadowLayer, frame: componentFrame)
                
                let cloudOffset0: CGFloat = 30.0
                let cloudSize0: CGFloat = 16.0
                var cloudFrame0 = CGRect(origin: CGPoint(x: floor(sourceOrigin.x + cloudOffset0 - cloudSize0 / 2.0), y: componentFrame.minY - cloudSize0 / 2.0), size: CGSize(width: cloudSize0, height: cloudSize0))
                var invertX = false
                if cloudFrame0.maxX >= layout.size.width - layout.safeInsets.right - 32.0 {
                    cloudFrame0.origin.x = floor(sourceOrigin.x - cloudSize0 - cloudOffset0 + cloudSize0 / 2.0)
                    invertX = true
                }
                
                transition.setFrame(layer: self.cloudLayer0, frame: cloudFrame0)
                if self.cloudShadowLayer0.bounds.size != cloudFrame0.size {
                    let cloudShadowPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: cloudFrame0.size), cornerRadius: 24.0).cgPath
                    self.cloudShadowLayer0.shadowPath = cloudShadowPath
                }
                transition.setFrame(layer: self.cloudShadowLayer0, frame: cloudFrame0)
                transition.setCornerRadius(layer: self.cloudLayer0, cornerRadius: cloudFrame0.width / 2.0)
                
                let cloudOffset1 = CGPoint(x: -9.0, y: -14.0)
                let cloudSize1: CGFloat = 8.0
                var cloudFrame1 = CGRect(origin: CGPoint(x: floor(cloudFrame0.midX + cloudOffset1.x - cloudSize1 / 2.0), y: floor(cloudFrame0.midY + cloudOffset1.y - cloudSize1 / 2.0)), size: CGSize(width: cloudSize1, height: cloudSize1))
                if invertX {
                    cloudFrame1.origin.x = floor(cloudFrame0.midX - cloudSize1 - cloudOffset1.x + cloudSize1 / 2.0)
                }
                transition.setFrame(layer: self.cloudLayer1, frame: cloudFrame1)
                if self.cloudShadowLayer1.bounds.size != cloudFrame1.size {
                    let cloudShadowPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: cloudFrame1.size), cornerRadius: 24.0).cgPath
                    self.cloudShadowLayer1.shadowPath = cloudShadowPath
                }
                transition.setFrame(layer: self.cloudShadowLayer1, frame: cloudFrame1)
                transition.setCornerRadius(layer: self.cloudLayer1, cornerRadius: cloudFrame1.width / 2.0)
                
                transition.setFrame(view: componentView, frame: CGRect(origin: componentFrame.origin, size: CGSize(width: componentFrame.width, height: componentFrame.height)))
                
                if animateIn {
                    self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1, completion: { [weak self] _ in
                        self?.allowsGroupOpacity = false
                    })
                    
                    let contentDuration: Double = 0.3
                    let contentDelay: Double = 0.14
                    let initialContentFrame = CGRect(origin: CGPoint(x: cloudFrame0.midX - 24.0, y: componentFrame.minY), size: CGSize(width: 24.0 * 2.0, height: 24.0 * 2.0))
                    
                    if let emojiView = self.componentHost.findTaggedView(tag: EmojiPagerContentComponent.Tag(id: AnyHashable("emoji"))) as? EmojiPagerContentComponent.View {
                        emojiView.animateIn(fromLocation: self.view.convert(initialContentFrame.center, to: emojiView))
                    }
                    
                    componentView.layer.animatePosition(from: initialContentFrame.center, to: componentFrame.center, duration: contentDuration, delay: contentDelay, timingFunction: kCAMediaTimingFunctionSpring)
                    componentView.layer.animateBounds(from: CGRect(origin: CGPoint(x: -(componentFrame.minX - initialContentFrame.minX), y: -(componentFrame.minY - initialContentFrame.minY)), size: initialContentFrame.size), to: CGRect(origin: CGPoint(), size: componentFrame.size), duration: contentDuration, delay: contentDelay, timingFunction: kCAMediaTimingFunctionSpring)
                    self.componentShadowLayer.animateFrame(from: CGRect(origin: CGPoint(x: cloudFrame0.midX - 24.0, y: componentFrame.minY), size: CGSize(width: 24.0 * 2.0, height: 24.0 * 2.0)), to: componentView.frame, duration: contentDuration, delay: contentDelay, timingFunction: kCAMediaTimingFunctionSpring)
                    componentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.04, delay: contentDelay)
                    self.componentShadowLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.04, delay: contentDelay)
                    
                    let initialComponentShadowPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: initialContentFrame.size), cornerRadius: 24.0).cgPath
                    self.componentShadowLayer.animate(from: initialComponentShadowPath, to: self.componentShadowLayer.shadowPath!, keyPath: "shadowPath", timingFunction: kCAMediaTimingFunctionSpring, duration: contentDuration, delay: contentDelay)
                    
                    self.cloudLayer0.animateScale(from: 0.01, to: 1.0, duration: 0.4, delay: 0.05, timingFunction: kCAMediaTimingFunctionSpring)
                    self.cloudShadowLayer0.animateScale(from: 0.01, to: 1.0, duration: 0.4, delay: 0.05, timingFunction: kCAMediaTimingFunctionSpring)
                    
                    self.cloudLayer1.animateScale(from: 0.01, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
                    self.cloudShadowLayer1.animateScale(from: 0.01, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
                }
            }
            
            if let previewItem = self.previewItem, let itemFile = previewItem.item.itemFile {
                let previewScreenView: ComponentView<Empty>
                var previewScreenTransition = transition
                if let current = self.previewScreenView {
                    previewScreenView = current
                } else {
                    previewScreenTransition = Transition(animation: .none)
                    if let emojiView = self.componentHost.findTaggedView(tag: EmojiPagerContentComponent.Tag(id: AnyHashable("emoji"))) as? EmojiPagerContentComponent.View, let sourceLayer = emojiView.layerForItem(groupId: previewItem.groupId, item: previewItem.item) {
                        previewScreenTransition = previewScreenTransition.withUserData(EmojiStatusPreviewScreenComponent.TransitionAnimation(
                            transitionType: .animateIn(sourceLayer: sourceLayer)
                        ))
                    }
                    previewScreenView = ComponentView<Empty>()
                    self.previewScreenView = previewScreenView
                }
                let _ = previewScreenView.update(
                    transition: previewScreenTransition,
                    component: AnyComponent(EmojiStatusPreviewScreenComponent(
                        theme: self.presentationData.theme,
                        strings: self.presentationData.strings,
                        bottomInset: layout.insets(options: []).bottom,
                        item: EmojiStatusComponent(
                            context: self.context,
                            animationCache: self.context.animationCache,
                            animationRenderer: self.context.animationRenderer,
                            content: .animation(
                                content: .file(file: itemFile),
                                size: CGSize(width: 128.0, height: 128.0),
                                placeholderColor: self.presentationData.theme.list.plainBackgroundColor.withMultipliedAlpha(0.1),
                                themeColor: self.presentationData.theme.list.itemAccentColor,
                                loopMode: .forever
                            ),
                            isVisibleForAnimations: true,
                            useSharedAnimation: false,
                            action: nil
                        ),
                        dismiss: { [weak self] result in
                            guard let strongSelf = self else {
                                return
                            }
                            
                            if let result = result, let previewItem = strongSelf.previewItem {
                                var emojiString: String?
                                if let itemFile = previewItem.item.itemFile {
                                    attributeLoop: for attribute in itemFile.attributes {
                                        switch attribute {
                                        case let .CustomEmoji(_, _, alt, _):
                                            emojiString = alt
                                            break attributeLoop
                                        default:
                                            break
                                        }
                                    }
                                }
                                
                                let context = strongSelf.context
                                let _ = (context.engine.stickers.availableReactions()
                                |> take(1)
                                |> mapToSignal { availableReactions -> Signal<String?, NoError> in
                                    guard let emojiString = emojiString, let availableReactions = availableReactions else {
                                        return .single(nil)
                                    }
                                    for reaction in availableReactions.reactions {
                                        if case let .builtin(value) = reaction.value, value == emojiString {
                                            if let aroundAnimation = reaction.aroundAnimation {
                                                return context.account.postbox.mediaBox.resourceData(aroundAnimation.resource)
                                                |> take(1)
                                                |> map { data -> String? in
                                                    if data.complete {
                                                        return data.path
                                                    } else {
                                                        return nil
                                                    }
                                                }
                                            } else {
                                                return .single(nil)
                                            }
                                        }
                                    }
                                    return .single(nil)
                                }
                                |> deliverOnMainQueue).start(next: { filePath in
                                    guard let strongSelf = self, let previewItem = strongSelf.previewItem, let destinationView = strongSelf.controller?.destinationItemView() else {
                                        return
                                    }
                                    
                                    let expirationDate: Int32? = result.timestamp
                            
                                    let _ = (strongSelf.context.engine.accountData.setEmojiStatus(file: previewItem.item.itemFile, expirationDate: expirationDate)
                                    |> deliverOnMainQueue).start()
                                    
                                    strongSelf.animateOutToStatus(item: previewItem.item, sourceLayer: result.sourceView.layer, customEffectFile: filePath, destinationView: destinationView, fromBackground: true)
                                })
                            } else {
                                strongSelf.dismissedPreviewItem = strongSelf.previewItem
                                strongSelf.previewItem = nil
                                strongSelf.refreshLayout(transition: .immediate)
                            }
                        }
                    )),
                    environment: {},
                    containerSize: layout.size
                )
                if let view = previewScreenView.view {
                    if view.superview == nil {
                        self.view.addSubview(view)
                    }
                    transition.setFrame(view: view, frame: CGRect(origin: CGPoint(), size: layout.size))
                }
            } else if let previewScreenView = self.previewScreenView {
                self.previewScreenView = nil
                
                if let previewScreenComponentView = previewScreenView.view as? EmojiStatusPreviewScreenComponent.View {
                    var targetLayer: CALayer?
                    if let previewItem = self.dismissedPreviewItem, let emojiView = self.componentHost.findTaggedView(tag: EmojiPagerContentComponent.Tag(id: AnyHashable("emoji"))) as? EmojiPagerContentComponent.View, let sourceLayer = emojiView.layerForItem(groupId: previewItem.groupId, item: previewItem.item) {
                        targetLayer = sourceLayer
                    }
                    
                    previewScreenComponentView.animateOut(targetLayer: targetLayer, completion: { [weak previewScreenComponentView] in
                        previewScreenComponentView?.removeFromSuperview()
                    })
                } else {
                    previewScreenView.view?.removeFromSuperview()
                }
            }
        }
        
        override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
            if let result = super.hitTest(point, with: event) {
                if self.isDismissed {
                    return self.view
                }
                
                if result === self.view {
                    self.isDismissed = true
                    self.controller?.dismiss()
                }
                
                return result
            }
            return nil
        }
        
        private func applyItem(groupId: AnyHashable, item: EmojiPagerContentComponent.Item?, isPreview: Bool) {
            guard let controller = self.controller else {
                return
            }
            
            if isPreview {
                guard let item = item else {
                    return
                }
                self.previewItem = (groupId, item)
                self.view.endEditing(true)
                self.refreshLayout(transition: .immediate)
            } else {
                self.freezeUpdates = true
                
                if case .statusSelection = controller.mode, let item = item, let currentSelection = self.currentSelection, item.itemFile?.fileId.id == currentSelection {
                    let _ = (self.context.engine.accountData.setEmojiStatus(file: nil, expirationDate: nil)
                    |> deliverOnMainQueue).start()
                    controller.dismiss()
                    return
                }
                
                if let _ = item, let destinationView = controller.destinationItemView() {
                    if let snapshotView = destinationView.snapshotView(afterScreenUpdates: false) {
                        snapshotView.frame = destinationView.frame
                        destinationView.superview?.insertSubview(snapshotView, belowSubview: destinationView)
                        snapshotView.layer.animateScale(from: 1.0, to: 0.001, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in
                            snapshotView?.removeFromSuperview()
                        })
                    }
                    destinationView.isHidden = true
                }
                
                switch controller.mode {
                case .statusSelection:
                    let _ = (self.context.engine.accountData.setEmojiStatus(file: item?.itemFile, expirationDate: nil)
                    |> deliverOnMainQueue).start()
                case let .quickReactionSelection(completion):
                    if let item = item, let itemFile = item.itemFile {
                        var selectedReaction: MessageReaction.Reaction?
                        
                        if let availableReactions = self.availableReactions {
                            for reaction in availableReactions.reactions {
                                if reaction.selectAnimation.fileId == itemFile.fileId {
                                    selectedReaction = reaction.value
                                    break
                                }
                            }
                        }
                        
                        if selectedReaction == nil {
                            selectedReaction = .custom(itemFile.fileId.id)
                        }
                        
                        if let selectedReaction = selectedReaction {
                            let _ = context.engine.stickers.updateQuickReaction(reaction: selectedReaction).start()
                        }
                    }
                    
                    completion()
                }
                
                if let item = item, let destinationView = controller.destinationItemView() {
                    var emojiString: String?
                    if let itemFile = item.itemFile {
                        attributeLoop: for attribute in itemFile.attributes {
                            switch attribute {
                            case let .CustomEmoji(_, _, alt, _):
                                emojiString = alt
                                break attributeLoop
                            default:
                                break
                            }
                        }
                    }
                    
                    let context = self.context
                    let _ = (context.engine.stickers.availableReactions()
                    |> take(1)
                    |> mapToSignal { availableReactions -> Signal<String?, NoError> in
                        guard let emojiString = emojiString, let availableReactions = availableReactions else {
                            return .single(nil)
                        }
                        for reaction in availableReactions.reactions {
                            if case let .builtin(value) = reaction.value, value == emojiString {
                                if let aroundAnimation = reaction.aroundAnimation {
                                    return context.account.postbox.mediaBox.resourceData(aroundAnimation.resource)
                                    |> take(1)
                                    |> map { data -> String? in
                                        if data.complete {
                                            return data.path
                                        } else {
                                            return nil
                                        }
                                    }
                                } else {
                                    return .single(nil)
                                }
                            }
                        }
                        return .single(nil)
                    }
                    |> deliverOnMainQueue).start(next: { [weak self] filePath in
                        guard let strongSelf = self else {
                            return
                        }
                        guard let emojiView = strongSelf.componentHost.findTaggedView(tag: EmojiPagerContentComponent.Tag(id: AnyHashable("emoji"))) as? EmojiPagerContentComponent.View, let sourceLayer = emojiView.layerForItem( groupId: groupId, item: item) else {
                            strongSelf.controller?.dismiss()
                            return
                        }
                        
                        strongSelf.animateOutToStatus(item: item, sourceLayer: sourceLayer, customEffectFile: filePath, destinationView: destinationView, fromBackground: false)
                    })
                } else {
                    controller.dismiss()
                }
            }
        }
    }
    
    public enum Mode {
        case statusSelection
        case quickReactionSelection(completion: () -> Void)
    }
    
    private let context: AccountContext
    private weak var sourceView: UIView?
    private let emojiContent: Signal<EmojiPagerContentComponent, NoError>
    private let currentSelection: Int64?
    private let mode: Mode
    private let destinationItemView: () -> UIView?
    
    fileprivate let _ready = Promise<Bool>()
    override public var ready: Promise<Bool> {
        return self._ready
    }
    
    override public var overlayWantsToBeBelowKeyboard: Bool {
        return true
    }
    
    public init(context: AccountContext, mode: Mode, sourceView: UIView, emojiContent: Signal<EmojiPagerContentComponent, NoError>, currentSelection: Int64?, destinationItemView: @escaping () -> UIView?) {
        self.context = context
        self.mode = mode
        self.sourceView = sourceView
        self.emojiContent = emojiContent
        self.currentSelection = currentSelection
        self.destinationItemView = destinationItemView
        
        super.init(navigationBarPresentationData: nil)
        
        self.lockOrientation = true
        
        self.statusBar.statusBarStyle = .Ignore
    }
    
    required public init(coder: NSCoder) {
        preconditionFailure()
    }
    
    override public func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
    }
    
    private func dismissNow() {
        self.presentingViewController?.dismiss(animated: false, completion: nil)
    }
    
    override public func dismiss(completion: (() -> Void)? = nil) {
        (self.displayNode as! Node).animateOut(completion: { [weak self] in
            self?.presentingViewController?.dismiss(animated: false, completion: nil)
            completion?()
        }, fromBackground: false)
    }
    
    override public func loadDisplayNode() {
        self.displayNode = Node(controller: self, context: self.context, sourceView: self.sourceView, emojiContent: self.emojiContent, currentSelection: self.currentSelection)

        super.displayNodeDidLoad()
    }

    override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
        super.containerLayoutUpdated(layout, transition: transition)

        (self.displayNode as! Node).containerLayoutUpdated(layout: layout, transition: Transition(transition))
    }
}

private func generateParabollicMotionKeyframes(from sourcePoint: CGPoint, to targetPosition: CGPoint, midPointY: CGFloat) -> [CGPoint] {
    let midPoint = CGPoint(x: (sourcePoint.x + targetPosition.x) / 2.0, y: midPointY)
    
    let x1 = sourcePoint.x
    let y1 = sourcePoint.y
    let x2 = midPoint.x
    let y2 = midPoint.y
    let x3 = targetPosition.x
    let y3 = targetPosition.y
    
    var keyframes: [CGPoint] = []
    if abs(y1 - y3) < 5.0 && abs(x1 - x3) < 5.0 {
        for i in 0 ..< 10 {
            let k = CGFloat(i) / CGFloat(10 - 1)
            let x = sourcePoint.x * (1.0 - k) + targetPosition.x * k
            let y = sourcePoint.y * (1.0 - k) + targetPosition.y * k
            keyframes.append(CGPoint(x: x, y: y))
        }
    } else {
        let a = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) / ((x1 - x2) * (x1 - x3) * (x2 - x3))
        let b = (x1 * x1 * (y2 - y3) + x3 * x3 * (y1 - y2) + x2 * x2 * (y3 - y1)) / ((x1 - x2) * (x1 - x3) * (x2 - x3))
        let c = (x2 * x2 * (x3 * y1 - x1 * y3) + x2 * (x1 * x1 * y3 - x3 * x3 * y1) + x1 * x3 * (x3 - x1) * y2) / ((x1 - x2) * (x1 - x3) * (x2 - x3))
        
        for i in 0 ..< 10 {
            let k = CGFloat(i) / CGFloat(10 - 1)
            let x = sourcePoint.x * (1.0 - k) + targetPosition.x * k
            let y = a * x * x + b * x + c
            keyframes.append(CGPoint(x: x, y: y))
        }
    }
    
    return keyframes
}