import Foundation
import UIKit
import Display
import AsyncDisplayKit
import ComponentFlow
import TelegramPresentationData
import ComponentDisplayAdapters
import SearchUI
import AccountContext
import TelegramCore
import StoryPeerListComponent

public final class ChatListNavigationBar: Component {
    public final class AnimationHint {
        let disableStoriesAnimations: Bool
        let crossfadeStoryPeers: Bool
        
        public init(disableStoriesAnimations: Bool, crossfadeStoryPeers: Bool) {
            self.disableStoriesAnimations = disableStoriesAnimations
            self.crossfadeStoryPeers = crossfadeStoryPeers
        }
    }
    
    public let context: AccountContext
    public let theme: PresentationTheme
    public let strings: PresentationStrings
    public let statusBarHeight: CGFloat
    public let sideInset: CGFloat
    public let isSearchActive: Bool
    public let primaryContent: ChatListHeaderComponent.Content?
    public let secondaryContent: ChatListHeaderComponent.Content?
    public let secondaryTransition: CGFloat
    public let storySubscriptions: EngineStorySubscriptions?
    public let storiesIncludeHidden: Bool
    public let uploadProgress: Float?
    public let tabsNode: ASDisplayNode?
    public let tabsNodeIsSearch: Bool
    public let accessoryPanelContainer: ASDisplayNode?
    public let accessoryPanelContainerHeight: CGFloat
    public let activateSearch: (NavigationBarSearchContentNode) -> Void
    public let openStatusSetup: (UIView) -> Void
    public let allowAutomaticOrder: () -> Void
    
    public init(
        context: AccountContext,
        theme: PresentationTheme,
        strings: PresentationStrings,
        statusBarHeight: CGFloat,
        sideInset: CGFloat,
        isSearchActive: Bool,
        primaryContent: ChatListHeaderComponent.Content?,
        secondaryContent: ChatListHeaderComponent.Content?,
        secondaryTransition: CGFloat,
        storySubscriptions: EngineStorySubscriptions?,
        storiesIncludeHidden: Bool,
        uploadProgress: Float?,
        tabsNode: ASDisplayNode?,
        tabsNodeIsSearch: Bool,
        accessoryPanelContainer: ASDisplayNode?,
        accessoryPanelContainerHeight: CGFloat,
        activateSearch: @escaping (NavigationBarSearchContentNode) -> Void,
        openStatusSetup: @escaping (UIView) -> Void,
        allowAutomaticOrder: @escaping () -> Void
    ) {
        self.context = context
        self.theme = theme
        self.strings = strings
        self.statusBarHeight = statusBarHeight
        self.sideInset = sideInset
        self.isSearchActive = isSearchActive
        self.primaryContent = primaryContent
        self.secondaryContent = secondaryContent
        self.secondaryTransition = secondaryTransition
        self.storySubscriptions = storySubscriptions
        self.storiesIncludeHidden = storiesIncludeHidden
        self.uploadProgress = uploadProgress
        self.tabsNode = tabsNode
        self.tabsNodeIsSearch = tabsNodeIsSearch
        self.accessoryPanelContainer = accessoryPanelContainer
        self.accessoryPanelContainerHeight = accessoryPanelContainerHeight
        self.activateSearch = activateSearch
        self.openStatusSetup = openStatusSetup
        self.allowAutomaticOrder = allowAutomaticOrder
    }

    public static func ==(lhs: ChatListNavigationBar, rhs: ChatListNavigationBar) -> Bool {
        if lhs.context !== rhs.context {
            return false
        }
        if lhs.theme !== rhs.theme {
            return false
        }
        if lhs.strings !== rhs.strings {
            return false
        }
        if lhs.statusBarHeight != rhs.statusBarHeight {
            return false
        }
        if lhs.sideInset != rhs.sideInset {
            return false
        }
        if lhs.isSearchActive != rhs.isSearchActive {
            return false
        }
        if lhs.primaryContent != rhs.primaryContent {
            return false
        }
        if lhs.secondaryContent != rhs.secondaryContent {
            return false
        }
        if lhs.secondaryTransition != rhs.secondaryTransition {
            return false
        }
        if lhs.storySubscriptions != rhs.storySubscriptions {
            return false
        }
        if lhs.storiesIncludeHidden != rhs.storiesIncludeHidden {
            return false
        }
        if lhs.uploadProgress != rhs.uploadProgress {
            return false
        }
        if lhs.tabsNode !== rhs.tabsNode {
            return false
        }
        if lhs.tabsNodeIsSearch != rhs.tabsNodeIsSearch {
            return false
        }
        if lhs.accessoryPanelContainer !== rhs.accessoryPanelContainer {
            return false
        }
        if lhs.accessoryPanelContainerHeight != rhs.accessoryPanelContainerHeight {
            return false
        }
        return true
    }
    
    private struct CurrentLayout {
        var size: CGSize
        
        init(size: CGSize) {
            self.size = size
        }
    }
    
    public static let searchScrollHeight: CGFloat = 52.0
    public static let storiesScrollHeight: CGFloat = 94.0

    public final class View: UIView {
        private let backgroundView: BlurredBackgroundView
        private let separatorLayer: SimpleLayer
        
        public let headerContent = ComponentView<Empty>()
        
        public private(set) var searchContentNode: NavigationBarSearchContentNode?
        
        private var component: ChatListNavigationBar?
        private weak var state: EmptyComponentState?
        
        private var scrollTheme: PresentationTheme?
        private var scrollStrings: PresentationStrings?
        
        private var currentLayout: CurrentLayout?
        private var rawScrollOffset: CGFloat?
        private var currentAllowAvatarsExpansion: Bool = false
        public private(set) var clippedScrollOffset: CGFloat?
        
        public var deferScrollApplication: Bool = false
        private var hasDeferredScrollOffset: Bool = false
        
        public private(set) var storiesUnlocked: Bool = false
        
        private var tabsNode: ASDisplayNode?
        private var tabsNodeIsSearch: Bool = false
        private weak var disappearingTabsView: UIView?
        private var disappearingTabsViewSearch: Bool = false
        
        private var currentHeaderComponent: ChatListHeaderComponent?
        
        override public init(frame: CGRect) {
            self.backgroundView = BlurredBackgroundView(color: .clear, enableBlur: true)
            self.backgroundView.layer.anchorPoint = CGPoint(x: 0.0, y: 1.0)
            self.separatorLayer = SimpleLayer()
            self.separatorLayer.anchorPoint = CGPoint()
            
            super.init(frame: frame)
            
            self.addSubview(self.backgroundView)
            self.layer.addSublayer(self.separatorLayer)
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
            if !self.backgroundView.frame.contains(point) {
                return nil
            }
            
            if self.alpha.isZero {
                return nil
            }
            for view in self.subviews.reversed() {
                if let result = view.hitTest(self.convert(point, to: view), with: event), result.isUserInteractionEnabled {
                    return result
                }
            }
            
            let result = super.hitTest(point, with: event)
            return result
        }
        
        public func applyCurrentScroll(transition: Transition) {
            if let rawScrollOffset = self.rawScrollOffset, self.hasDeferredScrollOffset {
                self.applyScroll(offset: rawScrollOffset, allowAvatarsExpansion: self.currentAllowAvatarsExpansion, transition: transition)
            }
        }
        
        public func applyScroll(offset: CGFloat, allowAvatarsExpansion: Bool, forceUpdate: Bool = false, transition: Transition) {
            let transition = transition
            
            self.rawScrollOffset = offset
            let allowAvatarsExpansionUpdated = self.currentAllowAvatarsExpansion != allowAvatarsExpansion
            self.currentAllowAvatarsExpansion = allowAvatarsExpansion
            
            if self.deferScrollApplication && !forceUpdate {
                self.hasDeferredScrollOffset = true
                return
            }
            
            guard let component = self.component, let currentLayout = self.currentLayout else {
                return
            }
            
            let themeUpdated = component.theme !== self.scrollTheme || component.strings !== self.scrollStrings
            
            self.scrollTheme = component.theme
            self.scrollStrings = component.strings
            
            let searchOffsetDistance: CGFloat = ChatListNavigationBar.searchScrollHeight
            
            let minContentOffset: CGFloat = ChatListNavigationBar.searchScrollHeight
            
            let clippedScrollOffset = min(minContentOffset, offset)
            if self.clippedScrollOffset == clippedScrollOffset && !self.hasDeferredScrollOffset && !forceUpdate && !allowAvatarsExpansionUpdated {
                return
            }
            self.hasDeferredScrollOffset = false
            self.clippedScrollOffset = clippedScrollOffset
            
            let visibleSize = CGSize(width: currentLayout.size.width, height: max(0.0, currentLayout.size.height - clippedScrollOffset))
            
            let previousHeight = self.separatorLayer.position.y
            
            self.backgroundView.update(size: CGSize(width: visibleSize.width, height: 1000.0), transition: transition.containedViewLayoutTransition)
            
            transition.setBounds(view: self.backgroundView, bounds: CGRect(origin: CGPoint(), size: CGSize(width: visibleSize.width, height: 1000.0)))
            transition.animatePosition(view: self.backgroundView, from: CGPoint(x: 0.0, y: -visibleSize.height + self.backgroundView.layer.position.y), to: CGPoint(), additive: true)
            self.backgroundView.layer.position = CGPoint(x: 0.0, y: visibleSize.height)
            
            transition.setFrameWithAdditivePosition(layer: self.separatorLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: visibleSize.height), size: CGSize(width: visibleSize.width, height: UIScreenPixel)))
            
            let searchContentNode: NavigationBarSearchContentNode
            if let current = self.searchContentNode {
                searchContentNode = current
                
                if themeUpdated {
                    let placeholder: String
                    let compactPlaceholder: String
                    
                    placeholder = component.strings.Common_Search
                    compactPlaceholder = component.strings.Common_Search
                    
                    searchContentNode.updateThemeAndPlaceholder(theme: component.theme, placeholder: placeholder, compactPlaceholder: compactPlaceholder)
                }
            } else {
                let placeholder: String
                let compactPlaceholder: String
                
                placeholder = component.strings.Common_Search
                compactPlaceholder = component.strings.Common_Search
                
                //TODO:localize
                searchContentNode = NavigationBarSearchContentNode(
                    theme: component.theme,
                    placeholder: placeholder,
                    compactPlaceholder: compactPlaceholder,
                    activate: { [weak self] in
                        guard let self, let component = self.component, let searchContentNode = self.searchContentNode else {
                            return
                        }
                        component.activateSearch(searchContentNode)
                    }
                )
                searchContentNode.view.layer.anchorPoint = CGPoint()
                self.searchContentNode = searchContentNode
                self.addSubview(searchContentNode.view)
            }
            
            let searchSize = CGSize(width: currentLayout.size.width, height: navigationBarSearchContentHeight)
            var searchFrame = CGRect(origin: CGPoint(x: 0.0, y: visibleSize.height - searchSize.height), size: searchSize)
            if component.tabsNode != nil {
                searchFrame.origin.y -= 40.0
            }
            if !component.isSearchActive {
                searchFrame.origin.y -= component.accessoryPanelContainerHeight
            }
            
            let clippedSearchOffset = max(0.0, min(clippedScrollOffset, searchOffsetDistance))
            let searchOffsetFraction = clippedSearchOffset / searchOffsetDistance
            searchContentNode.expansionProgress = 1.0 - searchOffsetFraction
            
            transition.setFrameWithAdditivePosition(view: searchContentNode.view, frame: searchFrame)
            
            searchContentNode.updateLayout(size: searchSize, leftInset: component.sideInset, rightInset: component.sideInset, transition: transition.containedViewLayoutTransition)
            
            let headerTransition = transition
            
            let storiesOffsetFraction: CGFloat
            let storiesUnlocked: Bool
            if allowAvatarsExpansion {
                storiesOffsetFraction = max(0.0, min(1.0, -offset / ChatListNavigationBar.storiesScrollHeight))
                
                if offset <= -80.0 {
                    storiesUnlocked = true
                } else if offset >= -66.0 {
                    storiesUnlocked = false
                } else {
                    storiesUnlocked = self.storiesUnlocked
                }
            } else {
                storiesOffsetFraction = 0.0
                storiesUnlocked = false
            }
            
            if allowAvatarsExpansion && transition.animation.isImmediate {
                if self.storiesUnlocked != storiesUnlocked {
                    if storiesUnlocked {
                        HapticFeedback().impact(.veryLight)
                    } else {
                        HapticFeedback().impact(.veryLight)
                    }
                }
            }
            if self.storiesUnlocked != storiesUnlocked, !storiesUnlocked {
                component.allowAutomaticOrder()
            }
            self.storiesUnlocked = storiesUnlocked
            
            let headerComponent = ChatListHeaderComponent(
                sideInset: component.sideInset + 16.0,
                primaryContent: component.primaryContent,
                secondaryContent: component.secondaryContent,
                secondaryTransition: component.secondaryTransition,
                networkStatus: nil,
                storySubscriptions: component.storySubscriptions,
                storiesIncludeHidden: component.storiesIncludeHidden,
                storiesFraction: storiesOffsetFraction,
                storiesUnlocked: storiesUnlocked,
                uploadProgress: component.uploadProgress,
                context: component.context,
                theme: component.theme,
                strings: component.strings,
                openStatusSetup: { [weak self] sourceView in
                    guard let self, let component = self.component else {
                        return
                    }
                    component.openStatusSetup(sourceView)
                },
                toggleIsLocked: { [weak self] in
                    guard let self, let component = self.component else {
                        return
                    }
                    component.context.sharedContext.appLockContext.lock()
                }
            )
            
            let animationHint = transition.userData(AnimationHint.self)
            
            var animationDuration: Double?
            if case let .curve(duration, _) = transition.animation {
                animationDuration = duration
            }
            
            self.currentHeaderComponent = headerComponent
            let headerContentSize = self.headerContent.update(
                transition: headerTransition.withUserData(StoryPeerListComponent.AnimationHint(
                    duration: animationDuration,
                    allowAvatarsExpansionUpdated: allowAvatarsExpansionUpdated && allowAvatarsExpansion,
                    bounce: transition.animation.isImmediate,
                    disableAnimations: animationHint?.disableStoriesAnimations ?? false
                )),
                component: AnyComponent(headerComponent),
                environment: {},
                containerSize: CGSize(width: currentLayout.size.width, height: 44.0)
            )
            let headerContentY: CGFloat
            if component.isSearchActive {
                headerContentY = -headerContentSize.height
            } else {
                if component.statusBarHeight < 1.0 {
                    headerContentY = 0.0
                } else {
                    headerContentY = component.statusBarHeight + 12.0
                }
            }
            let headerContentFrame = CGRect(origin: CGPoint(x: 0.0, y: headerContentY), size: headerContentSize)
            if let headerContentView = self.headerContent.view {
                if headerContentView.superview == nil {
                    headerContentView.layer.anchorPoint = CGPoint()
                    self.addSubview(headerContentView)
                }
                transition.setFrameWithAdditivePosition(view: headerContentView, frame: headerContentFrame)
            }
            
            if component.tabsNode !== self.tabsNode {
                if let tabsNode = self.tabsNode {
                    tabsNode.layer.anchorPoint = CGPoint()
                    
                    self.tabsNode = nil
                    let disappearingTabsView = tabsNode.view
                    self.disappearingTabsViewSearch = self.tabsNodeIsSearch
                    self.disappearingTabsView = disappearingTabsView
                    transition.setAlpha(view: tabsNode.view, alpha: 0.0, completion: { [weak self, weak disappearingTabsView] _ in
                        guard let self, let component = self.component, let disappearingTabsView else {
                            return
                        }
                        if disappearingTabsView !== component.tabsNode?.view {
                            disappearingTabsView.removeFromSuperview()
                        }
                    })
                }
            }
            
            var tabsFrame = CGRect(origin: CGPoint(x: 0.0, y: visibleSize.height), size: CGSize(width: visibleSize.width, height: 46.0))
            if !component.isSearchActive {
                tabsFrame.origin.y -= component.accessoryPanelContainerHeight
            }
            if component.tabsNode != nil {
                tabsFrame.origin.y -= 46.0
            }
            
            var accessoryPanelContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: visibleSize.height), size: CGSize(width: visibleSize.width, height: component.accessoryPanelContainerHeight))
            if !component.isSearchActive {
                accessoryPanelContainerFrame.origin.y -= component.accessoryPanelContainerHeight
            }
            
            if let disappearingTabsView = self.disappearingTabsView {
                disappearingTabsView.layer.anchorPoint = CGPoint()
                transition.setFrameWithAdditivePosition(view: disappearingTabsView, frame: tabsFrame.offsetBy(dx: 0.0, dy: self.disappearingTabsViewSearch ? (-currentLayout.size.height + 2.0) : 0.0))
            }
            
            if let tabsNode = component.tabsNode {
                self.tabsNode = tabsNode
                self.tabsNodeIsSearch = component.tabsNodeIsSearch
                
                var tabsNodeTransition = transition
                if tabsNode.view.superview !== self {
                    tabsNode.view.layer.anchorPoint = CGPoint()
                    tabsNodeTransition = .immediate
                    tabsNode.view.alpha = 1.0
                    self.addSubview(tabsNode.view)
                    if !transition.animation.isImmediate {
                        tabsNode.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
                        
                        if component.tabsNodeIsSearch {
                            transition.animatePosition(view: tabsNode.view, from: CGPoint(x: 0.0, y: previousHeight - visibleSize.height + 44.0), to: CGPoint(), additive: true)
                        } else {
                            transition.animatePosition(view: tabsNode.view, from: CGPoint(x: 0.0, y: previousHeight - visibleSize.height), to: CGPoint(), additive: true)
                        }
                    }
                } else {
                    transition.setAlpha(view: tabsNode.view, alpha: 1.0)
                }
                
                tabsNodeTransition.setFrameWithAdditivePosition(view: tabsNode.view, frame: tabsFrame.offsetBy(dx: 0.0, dy: component.tabsNodeIsSearch ? (-currentLayout.size.height + 2.0) : 0.0))
            }
            
            if let accessoryPanelContainer = component.accessoryPanelContainer {
                var tabsNodeTransition = transition
                if accessoryPanelContainer.view.superview !== self {
                    accessoryPanelContainer.view.layer.anchorPoint = CGPoint()
                    accessoryPanelContainer.clipsToBounds = true
                    tabsNodeTransition = .immediate
                    accessoryPanelContainer.view.alpha = 1.0
                    self.addSubview(accessoryPanelContainer.view)
                    if !transition.animation.isImmediate {
                        accessoryPanelContainer.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
                    }
                } else {
                    transition.setAlpha(view: accessoryPanelContainer.view, alpha: 1.0)
                }
                
                tabsNodeTransition.setFrameWithAdditivePosition(view: accessoryPanelContainer.view, frame: accessoryPanelContainerFrame)
            }
        }
        
        public func updateStoryUploadProgress(storyUploadProgress: Float?) {
            guard let component = self.component else {
                return
            }
            if component.uploadProgress != storyUploadProgress {
                self.component = ChatListNavigationBar(
                    context: component.context,
                    theme: component.theme,
                    strings: component.strings,
                    statusBarHeight: component.statusBarHeight,
                    sideInset: component.sideInset,
                    isSearchActive: component.isSearchActive,
                    primaryContent: component.primaryContent,
                    secondaryContent: component.secondaryContent,
                    secondaryTransition: component.secondaryTransition,
                    storySubscriptions: component.storySubscriptions,
                    storiesIncludeHidden: component.storiesIncludeHidden,
                    uploadProgress: storyUploadProgress,
                    tabsNode: component.tabsNode,
                    tabsNodeIsSearch: component.tabsNodeIsSearch,
                    accessoryPanelContainer: component.accessoryPanelContainer,
                    accessoryPanelContainerHeight: component.accessoryPanelContainerHeight,
                    activateSearch: component.activateSearch,
                    openStatusSetup: component.openStatusSetup,
                    allowAutomaticOrder: component.allowAutomaticOrder
                )
                if let currentLayout = self.currentLayout, let headerComponent = self.currentHeaderComponent {
                    let headerComponent = ChatListHeaderComponent(
                        sideInset: headerComponent.sideInset,
                        primaryContent: headerComponent.primaryContent,
                        secondaryContent: headerComponent.secondaryContent,
                        secondaryTransition: headerComponent.secondaryTransition,
                        networkStatus: headerComponent.networkStatus,
                        storySubscriptions: headerComponent.storySubscriptions,
                        storiesIncludeHidden: headerComponent.storiesIncludeHidden,
                        storiesFraction: headerComponent.storiesFraction,
                        storiesUnlocked: headerComponent.storiesUnlocked,
                        uploadProgress: storyUploadProgress,
                        context: headerComponent.context,
                        theme: headerComponent.theme,
                        strings: headerComponent.strings,
                        openStatusSetup: headerComponent.openStatusSetup,
                        toggleIsLocked: headerComponent.toggleIsLocked
                    )
                    self.currentHeaderComponent = headerComponent
                    
                    let _ = self.headerContent.update(
                        transition: .immediate,
                        component: AnyComponent(headerComponent),
                        environment: {},
                        containerSize: CGSize(width: currentLayout.size.width, height: 44.0)
                    )
                }
            }
        }
        
        func update(component: ChatListNavigationBar, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
            let themeUpdated = self.component?.theme !== component.theme
            
            var uploadProgressUpdated = false
            var storySubscriptionsUpdated = false
            if let previousComponent = self.component {
                if previousComponent.uploadProgress != component.uploadProgress {
                    uploadProgressUpdated = true
                }
                if previousComponent.storySubscriptions != component.storySubscriptions {
                    storySubscriptionsUpdated = true
                }
            }
            
            self.component = component
            self.state = state
            
            if themeUpdated {
                self.backgroundView.updateColor(color: component.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate)
                self.separatorLayer.backgroundColor = component.theme.rootController.navigationBar.separatorColor.cgColor
            }
            
            var contentHeight = component.statusBarHeight
            
            if component.statusBarHeight >= 1.0 {
                contentHeight += 10.0
            }
            contentHeight += 44.0
            
            if component.isSearchActive {
                if component.statusBarHeight < 1.0 {
                    contentHeight += 8.0
                }
            } else {
                contentHeight += navigationBarSearchContentHeight
            }
            
            if component.tabsNode != nil {
                contentHeight += 40.0
            }
            
            if component.accessoryPanelContainer != nil && !component.isSearchActive {
                contentHeight += component.accessoryPanelContainerHeight
            }
            
            let size = CGSize(width: availableSize.width, height: contentHeight)
            self.currentLayout = CurrentLayout(size: size)
            
            self.hasDeferredScrollOffset = true
            
            if uploadProgressUpdated || storySubscriptionsUpdated {
                if let rawScrollOffset = self.rawScrollOffset {
                    self.applyScroll(offset: rawScrollOffset, allowAvatarsExpansion: self.currentAllowAvatarsExpansion, forceUpdate: true, transition: transition)
                }
            }
            
            return size
        }
    }
    
    public func makeView() -> View {
        return View(frame: CGRect())
    }

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