import Foundation
import SwiftSignalKit
import TelegramCore
import TextFieldComponent
import ChatContextQuery
import AccountContext
import TelegramUIPreferences
import SearchPeerMembers

func textInputStateContextQueryRangeAndType(inputState: TextFieldComponent.InputState) -> [(NSRange, PossibleContextQueryTypes, NSRange?)] {
    return textInputStateContextQueryRangeAndType(inputText: inputState.inputText, selectionRange: inputState.selectionRange)
}

func inputContextQueries(_ inputState: TextFieldComponent.InputState) -> [ChatPresentationInputQuery] {
    let inputString: NSString = inputState.inputText.string as NSString
    var result: [ChatPresentationInputQuery] = []
    for (possibleQueryRange, possibleTypes, additionalStringRange) in textInputStateContextQueryRangeAndType(inputText: inputState.inputText, selectionRange: inputState.selectionRange) {
        let query = inputString.substring(with: possibleQueryRange)
        if possibleTypes == [.emoji] {
            result.append(.emoji(query.basicEmoji.0))
        } else if possibleTypes == [.hashtag] {
            result.append(.hashtag(query))
        } else if possibleTypes == [.mention] {
            let types: ChatInputQueryMentionTypes = [.members]
//            if possibleQueryRange.lowerBound == 1 {
//                types.insert(.contextBots)
//            }
            result.append(.mention(query: query, types: types))
        } else if possibleTypes == [.command] {
            result.append(.command(query))
        } else if possibleTypes == [.contextRequest], let additionalStringRange = additionalStringRange {
            let additionalString = inputString.substring(with: additionalStringRange)
            result.append(.contextRequest(addressName: query, query: additionalString))
        }
//        else if possibleTypes == [.emojiSearch], !query.isEmpty, let inputLanguage = chatPresentationInterfaceState.interfaceState.inputLanguage {
//            result.append(.emojiSearch(query: query, languageCode: inputLanguage, range: possibleQueryRange))
//        }
    }
    return result
}

func contextQueryResultState(context: AccountContext, inputState: TextFieldComponent.InputState, availableTypes: [ChatPresentationInputQueryKind], chatLocation: ChatLocation?, currentQueryStates: inout [ChatPresentationInputQueryKind: (ChatPresentationInputQuery, Disposable)]) -> [ChatPresentationInputQueryKind: ChatContextQueryUpdate] {
    let inputQueries = inputContextQueries(inputState).filter({ query in
        return availableTypes.contains(query.kind)
    })
    
    var updates: [ChatPresentationInputQueryKind: ChatContextQueryUpdate] = [:]
    
    for query in inputQueries {
        let previousQuery = currentQueryStates[query.kind]?.0
        if previousQuery != query {
            let signal = updatedContextQueryResultStateForQuery(context: context, chatLocation: chatLocation, inputQuery: query, previousQuery: previousQuery)
            updates[query.kind] = .update(query, signal)
        }
    }
    
    for currentQueryKind in currentQueryStates.keys {
        var found = false
        inner: for query in inputQueries {
            if query.kind == currentQueryKind {
                found = true
                break inner
            }
        }
        if !found {
            updates[currentQueryKind] = .remove
        }
    }
    
    return updates
}

private func updatedContextQueryResultStateForQuery(context: AccountContext, chatLocation: ChatLocation?, inputQuery: ChatPresentationInputQuery, previousQuery: ChatPresentationInputQuery?) -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> {
    switch inputQuery {
    case let .emoji(query):
        var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = .complete()
        if let previousQuery = previousQuery {
            switch previousQuery {
                case .emoji:
                    break
                default:
                    signal = .single({ _ in return .stickers([]) })
            }
        } else {
            signal = .single({ _ in return .stickers([]) })
        }
        
        let stickerConfiguration = context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
        |> map { preferencesView -> StickersSearchConfiguration in
            let appConfiguration: AppConfiguration = preferencesView.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? .defaultValue
            return StickersSearchConfiguration.with(appConfiguration: appConfiguration)
        }
        let stickerSettings = context.sharedContext.accountManager.transaction { transaction -> StickerSettings in
            let stickerSettings: StickerSettings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.stickerSettings)?.get(StickerSettings.self) ?? .defaultSettings
            return stickerSettings
        }

        let stickers: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = combineLatest(stickerConfiguration, stickerSettings)
        |> castError(ChatContextQueryError.self)
        |> mapToSignal { stickerConfiguration, stickerSettings -> Signal<[FoundStickerItem], ChatContextQueryError> in
            let scope: SearchStickersScope
            switch stickerSettings.emojiStickerSuggestionMode {
                case .none:
                    scope = []
                case .all:
                    if stickerConfiguration.disableLocalSuggestions {
                        scope = [.remote]
                    } else {
                        scope = [.installed, .remote]
                    }
                case .installed:
                    scope = [.installed]
            }
            return context.engine.stickers.searchStickers(query: nil, emoticon: [query.basicEmoji.0], scope: scope)
            |> map { items -> [FoundStickerItem] in
                return items.items
            }
            |> castError(ChatContextQueryError.self)
        }
        |> map { stickers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
            return { _ in
                return .stickers(stickers)
            }
        }
        return signal |> then(stickers)
    case let .hashtag(query):
        var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = .complete()
        if let previousQuery = previousQuery {
            switch previousQuery {
                case .hashtag:
                    break
                default:
                    signal = .single({ _ in return .hashtags([], query) })
            }
        } else {
            signal = .single({ _ in return .hashtags([], query) })
        }
        
        let hashtags: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = context.engine.messages.recentlyUsedHashtags()
        |> map { hashtags -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
            let normalizedQuery = query.lowercased()
            var result: [String] = []
            for hashtag in hashtags {
                if hashtag.lowercased().hasPrefix(normalizedQuery) {
                    result.append(hashtag)
                }
            }
            return { _ in return .hashtags(result, query) }
        }
        |> castError(ChatContextQueryError.self)
        
        return signal |> then(hashtags)
    case let .mention(query, types):
        var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = .complete()
        if let previousQuery = previousQuery {
            switch previousQuery {
                case .mention:
                    break
                default:
                    signal = .single({ _ in return .mentions([]) })
            }
        } else {
            signal = .single({ _ in return .mentions([]) })
        }
        
        let normalizedQuery = query.lowercased()
        
        if let chatLocation, let peerId = chatLocation.peerId {
            let inlineBots: Signal<[(EnginePeer, Double)], NoError> = types.contains(.contextBots) ? context.engine.peers.recentlyUsedInlineBots() : .single([])
            let strings = context.sharedContext.currentPresentationData.with({ $0 }).strings
            let participants = combineLatest(inlineBots, searchPeerMembers(context: context, peerId: peerId, chatLocation: chatLocation, query: query, scope: .mention))
            |> map { inlineBots, peers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
                let filteredInlineBots = inlineBots.sorted(by: { $0.1 > $1.1 }).filter { peer, rating in
                    if rating < 0.14 {
                        return false
                    }
                    if peer.indexName.matchesByTokens(normalizedQuery) {
                        return true
                    }
                    if let addressName = peer.addressName, addressName.lowercased().hasPrefix(normalizedQuery) {
                        return true
                    }
                    return false
                }.map { $0.0 }
                
                let inlineBotPeerIds = Set(filteredInlineBots.map { $0.id })
                
                let filteredPeers = peers.filter { peer in
                    if inlineBotPeerIds.contains(peer.id) {
                        return false
                    }
                    if !types.contains(.accountPeer) && peer.id == context.account.peerId {
                        return false
                    }
                    return true
                }
                var sortedPeers = filteredInlineBots
                sortedPeers.append(contentsOf: filteredPeers.sorted(by: { lhs, rhs in
                    let result = lhs.indexName.stringRepresentation(lastNameFirst: true).compare(rhs.indexName.stringRepresentation(lastNameFirst: true))
                    return result == .orderedAscending
                }))
                sortedPeers = sortedPeers.filter { peer in
                    return !peer.displayTitle(strings: strings, displayOrder: .firstLast).isEmpty
                }
                return { _ in return .mentions(sortedPeers) }
            }
            |> castError(ChatContextQueryError.self)
            
            return signal |> then(participants)
        } else {
            if normalizedQuery.isEmpty {
                let peers: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = context.engine.peers.recentPeers()
                |> map { recentPeers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
                    if case let .peers(peers) = recentPeers {
                        let peers = peers.filter { peer in
                            return peer.addressName != nil
                        }.compactMap { EnginePeer($0) }
                        return { _ in return .mentions(peers) }
                    } else {
                        return { _ in return .mentions([]) }
                    }
                }
                |> castError(ChatContextQueryError.self)
                return signal |> then(peers)
            } else {
                let peers: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = context.engine.contacts.searchLocalPeers(query: normalizedQuery)
                |> map { peersAndPresences -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
                    let peers = peersAndPresences.filter { peer in
                        if let peer = peer.peer, case .user = peer, peer.addressName != nil {
                            return true
                        } else {
                            return false
                        }
                    }.compactMap { $0.peer }
                    return { _ in return .mentions(peers) }
                }
                |> castError(ChatContextQueryError.self)
                return signal |> then(peers)
            }
        }
    case let .emojiSearch(query, languageCode, range):
        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
    
        if query.isSingleEmoji {
            return combineLatest(
                context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000),
                hasPremium
            )
            |> map { view, hasPremium -> [(String, TelegramMediaFile?, String)] in
                var result: [(String, TelegramMediaFile?, String)] = []
                
                for entry in view.entries {
                    guard let item = entry.item as? StickerPackItem, !item.file.isPremiumEmoji || hasPremium else {
                        continue
                    }
                    let stringRepresentations = item.getStringRepresentationsOfIndexKeys()
                    for stringRepresentation in stringRepresentations {
                        if stringRepresentation == query {
                            result.append((stringRepresentation, item.file._parse(), stringRepresentation))
                            break
                        }
                    }
                }
                return result
            }
            |> map { result -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
                return { _ in return .emojis(result, range) }
            }
            |> castError(ChatContextQueryError.self)
        } else {
            var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query, completeMatch: query.count < 2)
            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
                        }
                    )
                }
            }
            
            return signal
            |> castError(ChatContextQueryError.self)
            |> mapToSignal { keywords -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> in
                return combineLatest(
                    context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000),
                    hasPremium
                )
                |> map { view, hasPremium -> [(String, TelegramMediaFile?, String)] 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, !item.file.isPremiumEmoji || hasPremium else {
                            continue
                        }
                        let stringRepresentations = item.getStringRepresentationsOfIndexKeys()
                        for stringRepresentation in stringRepresentations {
                            if let keyword = allEmoticons[stringRepresentation] {
                                result.append((stringRepresentation, item.file._parse(), keyword))
                                break
                            }
                        }
                    }
                    
                    for keyword in keywords {
                        for emoticon in keyword.emoticons {
                            result.append((emoticon, nil, keyword.keyword))
                        }
                    }
                    return result
                }
                |> map { result -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
                    return { _ in return .emojis(result, range) }
                }
                |> castError(ChatContextQueryError.self)
            }
        }
    default:
        return .complete()
    }
}