import Foundation
import UIKit
import SwiftSignalKit
import Display
import TelegramCore
import Postbox
import TelegramPresentationData
import ProgressNavigationButtonNode
import AccountContext
import SearchUI
import ChatListUI

public final class PeerSelectionControllerImpl: ViewController, PeerSelectionController {
    private let context: AccountContext
    
    private var presentationData: PresentationData
    private var presentationDataDisposable: Disposable?
    
    private var customTitle: String?
    
    public var peerSelected: ((EnginePeer, Int64?) -> Void)?
    public var multiplePeersSelected: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?, ChatSendMessageActionSheetController.SendParameters?) -> Void)?
    private let filter: ChatListNodePeersFilter
    private let forumPeerId: EnginePeer.Id?
    private let selectForumThreads: Bool
    
    private let attemptSelection: ((EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void)?
    private let createNewGroup: (() -> Void)?
    
    public var inProgress: Bool = false {
        didSet {
            if self.inProgress != oldValue {
                if self.isNodeLoaded {
                    self.peerSelectionNode.inProgress = self.inProgress
                }
                
                if self.inProgress {
                    self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: self.presentationData.theme.rootController.navigationBar.controlColor))
                } else {
                    self.navigationItem.rightBarButtonItem = nil
                }
            }
        }
    }
    
    public var customDismiss: (() -> Void)?
    
    private var peerSelectionNode: PeerSelectionControllerNode {
        return super.displayNode as! PeerSelectionControllerNode
    }
    
    let openMessageFromSearchDisposable: MetaDisposable = MetaDisposable()
    
    private let _ready = Promise<Bool>()
    override public var ready: Promise<Bool> {
        return self._ready
    }
    
    private let hasChatListSelector: Bool
    private let hasContactSelector: Bool
    private let hasFilters: Bool
    private let hasGlobalSearch: Bool
    private let pretendPresentedInModal: Bool
    private let forwardedMessageIds: [EngineMessage.Id]
    private let hasTypeHeaders: Bool
    private let requestPeerType: [ReplyMarkupButtonRequestPeerType]?
    private let hasCreation: Bool
    
    override public var _presentedInModal: Bool {
        get {
            if self.pretendPresentedInModal {
                return true
            } else {
                return super._presentedInModal
            }
        } set(value) {
            if !self.pretendPresentedInModal {
                super._presentedInModal = value
            }
        }
    }
    
    private var searchContentNode: NavigationBarSearchContentNode?
    var tabContainerNode: ChatListFilterTabContainerNode?
    private var tabContainerData: ([ChatListFilterTabEntry], Bool, Int32?)?
    
    private let filterDisposable = MetaDisposable()
    
    private var validLayout: ContainerViewLayout?
    
    public init(_ params: PeerSelectionControllerParams) {
        self.context = params.context
        self.filter = params.filter
        self.forumPeerId = params.forumPeerId
        self.hasFilters = params.hasFilters
        self.hasChatListSelector = params.hasChatListSelector
        self.hasContactSelector = params.hasContactSelector
        self.hasGlobalSearch = params.hasGlobalSearch
        self.presentationData = params.updatedPresentationData?.initial ?? params.context.sharedContext.currentPresentationData.with { $0 }
        self.attemptSelection = params.attemptSelection
        self.createNewGroup = params.createNewGroup
        self.pretendPresentedInModal = params.pretendPresentedInModal
        self.forwardedMessageIds = params.forwardedMessageIds
        self.hasTypeHeaders = params.hasTypeHeaders
        self.selectForumThreads = params.selectForumThreads
        self.requestPeerType = params.requestPeerType
        self.hasCreation = params.hasCreation
        
        super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
        
        self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
        
        self.customTitle = params.title
        
        if let peerTypes = params.requestPeerType {
            if peerTypes.count == 1, let peerType = peerTypes.first {
                switch peerType {
                case let .user(user):
                    if let isBot = user.isBot, isBot {
                        self.customTitle = self.presentationData.strings.RequestPeer_ChooseBotTitle
                    } else {
                        self.customTitle = self.presentationData.strings.RequestPeer_ChooseUserTitle
                    }
                case .group:
                    self.customTitle = self.presentationData.strings.RequestPeer_ChooseGroupTitle
                case .channel:
                    self.customTitle = self.presentationData.strings.RequestPeer_ChooseChannelTitle
                }
            } else {
                self.customTitle = self.presentationData.strings.ChatImport_Title
            }
        }
        
        self.title = self.customTitle ?? self.presentationData.strings.Conversation_ForwardTitle
        
        if params.forumPeerId == nil {
            self.navigationPresentation = .modal
            self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))
        }
        self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
        
        self.scrollToTop = { [weak self] in
            if let strongSelf = self {
                if let searchContentNode = strongSelf.searchContentNode {
                    searchContentNode.updateExpansionProgress(1.0, animated: true)
                }
                strongSelf.peerSelectionNode.scrollToTop()
            }
        }
        
        self.presentationDataDisposable = ((params.updatedPresentationData?.signal ?? self.context.sharedContext.presentationData)
        |> deliverOnMainQueue).start(next: { [weak self] presentationData in
            if let strongSelf = self {
                let previousTheme = strongSelf.presentationData.theme
                let previousStrings = strongSelf.presentationData.strings
                
                strongSelf.presentationData = presentationData
                
                if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
                    strongSelf.updateThemeAndStrings()
                }
            }
        })
        
        self.searchContentNode = NavigationBarSearchContentNode(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search, activate: { [weak self] in
            self?.activateSearch()
        })
        self.navigationBar?.setContentNode(self.searchContentNode, animated: false)
        
        if params.multipleSelection {
            self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Select, style: .plain, target: self, action: #selector(self.beginSelection))
        }
        
        if params.hasFilters {
            self._ready.set(.never())
            
            self.tabContainerNode = ChatListFilterTabContainerNode()
            self.reloadFilters()
            
            self.peerSelectionNode.mainContainerNode?.currentItemFilterUpdated = { [weak self] filter, fraction, transition, force in
                guard let strongSelf = self else {
                    return
                }
                guard let layout = strongSelf.validLayout else {
                    return
                }
                guard let tabContainerData = strongSelf.tabContainerData else {
                    return
                }
                if force {
                    strongSelf.tabContainerNode?.cancelAnimations()
                }
                strongSelf.tabContainerNode?.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: tabContainerData.0, selectedFilter: filter, isReordering: false, isEditing: false, canReorderAllChats: false, filtersLimit: tabContainerData.2, transitionFraction: fraction, presentationData: strongSelf.presentationData, transition: transition)
            }
            
            self.tabContainerNode?.tabSelected = { [weak self] id, isDisabled in
                guard let strongSelf = self else {
                    return
                }
                if isDisabled {
                    let context = strongSelf.context
                    var replaceImpl: ((ViewController) -> Void)?
                    let controller = context.sharedContext.makePremiumLimitController(context: context, subject: .folders, count: strongSelf.tabContainerNode?.filtersCount ?? 0, forceDark: false, cancel: {}, action: {
                        let controller = context.sharedContext.makePremiumIntroController(context: context, source: .folders, forceDark: false, dismissed: nil)
                        replaceImpl?(controller)
                        return true
                    })
                    replaceImpl = { [weak controller] c in
                        controller?.replace(with: c)
                    }
                    strongSelf.push(controller)
                } else {
                    strongSelf.selectTab(id: id)
                }
            }
        }
    }
    
    required public init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    deinit {
        self.openMessageFromSearchDisposable.dispose()
        self.presentationDataDisposable?.dispose()
        self.filterDisposable.dispose()
    }
    
    private func updateThemeAndStrings() {
        self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
        self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))
        self.searchContentNode?.updateThemeAndPlaceholder(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search)
        self.title = self.customTitle ?? self.presentationData.strings.Conversation_ForwardTitle
        self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
        self.peerSelectionNode.updatePresentationData(self.presentationData)
    }
    
    override public func loadDisplayNode() {
        self.displayNode = PeerSelectionControllerNode(context: self.context, controller: self, presentationData: self.presentationData, filter: self.filter, forumPeerId: self.forumPeerId, hasFilters: self.hasFilters, hasChatListSelector: self.hasChatListSelector, hasContactSelector: self.hasContactSelector, hasGlobalSearch: self.hasGlobalSearch, forwardedMessageIds: self.forwardedMessageIds, hasTypeHeaders: self.hasTypeHeaders, requestPeerType: self.requestPeerType, hasCreation: self.hasCreation, createNewGroup: self.createNewGroup, present: { [weak self] c, a in
            self?.present(c, in: .window(.root), with: a)
        }, presentInGlobalOverlay: { [weak self] c, a in
            self?.presentInGlobalOverlay(c, with: a)
        }, dismiss: { [weak self] in
            self?.presentingViewController?.dismiss(animated: false, completion: nil)
        })
        
        self.peerSelectionNode.navigationBar = self.navigationBar
        
        self.peerSelectionNode.requestSend = { [weak self] peers, peerMap, text, mode, forwardOptionsState, messageEffect in
            self?.multiplePeersSelected?(peers, peerMap, text, mode, forwardOptionsState, messageEffect)
        }
        
        self.peerSelectionNode.requestDeactivateSearch = { [weak self] in
            self?.deactivateSearch()
        }
        
        self.peerSelectionNode.requestActivateSearch = { [weak self] in
            self?.activateSearch()
        }
        
        self.peerSelectionNode.requestOpenPeer = { [weak self] peer, threadId in
            if let strongSelf = self, let peerSelected = strongSelf.peerSelected {
                if case let .channel(peer) = peer, peer.flags.contains(.isForum), threadId == nil, strongSelf.selectForumThreads {
                    let controller = PeerSelectionControllerImpl(
                        PeerSelectionControllerParams(
                            context: strongSelf.context,
                            updatedPresentationData: nil,
                            filter: strongSelf.filter,
                            forumPeerId: peer.id,
                            hasFilters: false,
                            hasChatListSelector: false,
                            hasContactSelector: false,
                            hasGlobalSearch: false,
                            title: EnginePeer(peer).compactDisplayTitle,
                            attemptSelection: strongSelf.attemptSelection,
                            createNewGroup: nil,
                            pretendPresentedInModal: false,
                            multipleSelection: false,
                            forwardedMessageIds: [],
                            hasTypeHeaders: false,
                            selectForumThreads: false
                        )
                    )
                    controller.peerSelected = strongSelf.peerSelected
                    strongSelf.push(controller)
                } else {
                    peerSelected(peer, threadId)
                }
            }
        }
        
        self.peerSelectionNode.requestOpenDisabledPeer = { [weak self] peer, threadId, reason in
            if let strongSelf = self {
                strongSelf.attemptSelection?(peer, threadId, reason)
            }
        }
        
        self.peerSelectionNode.requestOpenPeerFromSearch = { [weak self] peer, threadId in
            if let strongSelf = self {
                strongSelf.openMessageFromSearchDisposable.set((_internal_storedMessageFromSearchPeer(postbox: strongSelf.context.account.postbox, peer: peer._asPeer())
                |> deliverOnMainQueue).start(completed: { [weak strongSelf] in
                    if let strongSelf = strongSelf, let peerSelected = strongSelf.peerSelected {
                        if case let .channel(peer) = peer, peer.flags.contains(.isForum), threadId == nil, strongSelf.selectForumThreads {
                            let controller = PeerSelectionControllerImpl(
                                PeerSelectionControllerParams(
                                    context: strongSelf.context,
                                    updatedPresentationData: nil,
                                    filter: strongSelf.filter,
                                    forumPeerId: peer.id,
                                    hasFilters: false,
                                    hasChatListSelector: false,
                                    hasContactSelector: false,
                                    hasGlobalSearch: false,
                                    title: EnginePeer(peer).compactDisplayTitle,
                                    attemptSelection: strongSelf.attemptSelection,
                                    createNewGroup: nil,
                                    pretendPresentedInModal: false,
                                    multipleSelection: false,
                                    forwardedMessageIds: [],
                                    hasTypeHeaders: false,
                                    selectForumThreads: false
                                )
                            )
                            controller.peerSelected = strongSelf.peerSelected
                            strongSelf.push(controller)
                        } else {
                            peerSelected(peer, threadId)
                        }
                    }
                }))
            }
        }
        
        var isProcessingContentOffsetChanged = false
        self.peerSelectionNode.contentOffsetChanged = { [weak self] offset in
            if isProcessingContentOffsetChanged {
                return
            }
            isProcessingContentOffsetChanged = true
            if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode {
                searchContentNode.updateListVisibleContentOffset(offset)
                isProcessingContentOffsetChanged = false
            }
        }
        
        self.peerSelectionNode.contentScrollingEnded = { [weak self] listView in
            if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode {
                return fixNavigationSearchableListNodeScrolling(listView, searchNode: searchContentNode)
            } else {
                return false
            }
        }
        
        self.displayNodeDidLoad()
        
        if !self.hasFilters {
            self._ready.set(self.peerSelectionNode.ready)
        }
    }
    
    public override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        self.peerSelectionNode.mainContainerNode?.updateEnableAdjacentFilterLoading(true)
    }
    
    override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
        self.validLayout = layout
        
        super.containerLayoutUpdated(layout, transition: transition)
        
        self.peerSelectionNode.containerLayoutUpdated(layout, navigationBarHeight: self.cleanNavigationHeight, actualNavigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
        
        if let tabContainerNode = self.tabContainerNode, let mainContainerNode = self.peerSelectionNode.mainContainerNode {
            let tabContainerOffset: CGFloat = 0.0
            let navigationBarHeight = self.navigationBar?.frame.maxY ?? 0.0
            transition.updateFrame(node: tabContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight - self.additionalNavigationBarHeight - 46.0 + tabContainerOffset), size: CGSize(width: layout.size.width, height: 46.0)))
            tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: mainContainerNode.currentItemFilter, isReordering: false, isEditing: false, canReorderAllChats: false, filtersLimit: self.tabContainerData?.2, transitionFraction: mainContainerNode.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring))
        }
    }
    
    @objc private func beginSelection() {
        self.navigationItem.rightBarButtonItem = nil
        self.peerSelectionNode.beginSelection()
    }
    
    @objc func cancelPressed() {
        if let customDismiss = self.customDismiss {
            customDismiss()
        } else {
            self.dismiss()
        }
    }
    
    private func activateSearch() {
        if self.displayNavigationBar {
            if let scrollToTop = self.scrollToTop {
                scrollToTop()
            }
            if let searchContentNode = self.searchContentNode {
                self.peerSelectionNode.activateSearch(placeholderNode: searchContentNode.placeholderNode)
            }
            self.setDisplayNavigationBar(false, transition: .animated(duration: 0.5, curve: .spring))
        }
    }
    
    private func deactivateSearch() {
        if !self.displayNavigationBar {
            if let searchContentNode = self.searchContentNode {
                self.peerSelectionNode.deactivateSearch(placeholderNode: searchContentNode.placeholderNode)
            }
        }
    }
    
    private var initializedFilters = false
    private func reloadFilters(firstUpdate: (() -> Void)? = nil) {
        let filterItems = chatListFilterItems(context: self.context)
        var notifiedFirstUpdate = false
        self.filterDisposable.set((combineLatest(queue: .mainQueue(),
            filterItems,
            self.context.account.postbox.peerView(id: self.context.account.peerId),
            self.context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false))
        )
        |> deliverOnMainQueue).start(next: { [weak self] countAndFilterItems, peerView, limits in
            guard let strongSelf = self else {
                return
            }
            
            let isPremium = peerView.peers[peerView.peerId]?.isPremium
            
            let (_, items) = countAndFilterItems
            var filterItems: [ChatListFilterTabEntry] = []
            
            for (filter, unreadCount, hasUnmutedUnread) in items {
                switch filter {
                    case .allChats:
                        if let isPremium = isPremium, !isPremium && filterItems.count > 0 {
                            filterItems.insert(.all(unreadCount: 0), at: 0)
                        } else {
                            filterItems.append(.all(unreadCount: 0))
                        }
                    case let .filter(id, title, _, _):
                        filterItems.append(.filter(id: id, text: title, unread: ChatListFilterTabEntryUnreadCount(value: unreadCount, hasUnmuted: hasUnmutedUnread)))
                }
            }
            
            let resolvedItems = filterItems
        
            var wasEmpty = false
            if let tabContainerData = strongSelf.tabContainerData {
                wasEmpty = tabContainerData.0.count <= 1 || tabContainerData.1
            } else {
                wasEmpty = true
            }
   
            var selectedEntryId = !strongSelf.initializedFilters ? .all : (strongSelf.peerSelectionNode.mainContainerNode?.currentItemFilter ?? .all)
            var resetCurrentEntry = false
            if !resolvedItems.contains(where: { $0.id == selectedEntryId }) {
                resetCurrentEntry = true
                if let tabContainerData = strongSelf.tabContainerData {
                    var found = false
                    if let index = tabContainerData.0.firstIndex(where: { $0.id == selectedEntryId }) {
                        for i in (0 ..< index - 1).reversed() {
                            if resolvedItems.contains(where: { $0.id == tabContainerData.0[i].id }) {
                                selectedEntryId = tabContainerData.0[i].id
                                found = true
                                break
                            }
                        }
                    }
                    if !found {
                        selectedEntryId = .all
                    }
                } else {
                    selectedEntryId = .all
                }
            }
            let filtersLimit = isPremium == false ? limits.maxFoldersCount : nil
            strongSelf.tabContainerData = (resolvedItems, false, filtersLimit)
            var availableFilters: [ChatListContainerNodeFilter] = []
            var hasAllChats = false
            for item in items {
                switch item.0 {
                    case .allChats:
                        hasAllChats = true
                        if let isPremium = isPremium, !isPremium && availableFilters.count > 0 {
                            availableFilters.insert(.all, at: 0)
                        } else {
                            availableFilters.append(.all)
                        }
                    case .filter:
                        availableFilters.append(.filter(item.0))
                }
            }
            if !hasAllChats {
                availableFilters.insert(.all, at: 0)
            }
            strongSelf.peerSelectionNode.mainContainerNode?.updateAvailableFilters(availableFilters, limit: filtersLimit)
            
            if let mainContainerNode = strongSelf.peerSelectionNode.mainContainerNode {
                if isPremium == nil && items.isEmpty {
                    strongSelf.ready.set(mainContainerNode.currentItemNode.ready)
                } else if !strongSelf.initializedFilters {
                    if selectedEntryId != mainContainerNode.currentItemFilter {
                        mainContainerNode.switchToFilter(id: selectedEntryId, animated: false, completion: { [weak self] in
                            if let strongSelf = self {
                                strongSelf.ready.set(mainContainerNode.currentItemNode.ready)
                            }
                        })
                    } else {
                        strongSelf.ready.set(mainContainerNode.currentItemNode.ready)
                    }
                    strongSelf.initializedFilters = true
                }
            }
            
            let isEmpty = resolvedItems.count <= 1
            
            if wasEmpty != isEmpty, strongSelf.displayNavigationBar {
                strongSelf.navigationBar?.setSecondaryContentNode(isEmpty ? nil : strongSelf.tabContainerNode, animated: false)
            }
            
            if let layout = strongSelf.validLayout {
                if wasEmpty != isEmpty {
                    strongSelf.containerLayoutUpdated(layout, transition: .immediate)
                } else {
                    strongSelf.tabContainerNode?.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: resolvedItems, selectedFilter: selectedEntryId, isReordering: false, isEditing: false, canReorderAllChats: false, filtersLimit: filtersLimit, transitionFraction: 0.0, presentationData: strongSelf.presentationData, transition: .animated(duration: 0.4, curve: .spring))
                }
            }
            
            if !notifiedFirstUpdate {
                notifiedFirstUpdate = true
                firstUpdate?()
            }
            
            if resetCurrentEntry {
                //strongSelf.selectTab(id: selectedEntryId)
            }
        }))
    }
    
    private func selectTab(id: ChatListFilterTabEntryId) {
        let _ = (self.context.engine.peers.currentChatListFilters()
        |> deliverOnMainQueue).start(next: { [weak self] filters in
            guard let strongSelf = self else {
                return
            }
            let updatedFilter: ChatListFilter?
            switch id {
            case .all:
                updatedFilter = nil
            case let .filter(id):
                var found = false
                var foundValue: ChatListFilter?
                for filter in filters {
                    if filter.id == id {
                        foundValue = filter
                        found = true
                        break
                    }
                }
                if found {
                    updatedFilter = foundValue
                } else {
                    updatedFilter = nil
                }
            }
            if strongSelf.peerSelectionNode.mainContainerNode?.currentItemNode.chatListFilter?.id == updatedFilter?.id {
                strongSelf.scrollToTop?()
            } else {
                strongSelf.peerSelectionNode.mainContainerNode?.switchToFilter(id: updatedFilter.flatMap { .filter($0.id) } ?? .all)
            }
        })
    }
}