//
//  UserActivityManager.swift
//  Tusker
//
//  Created by Shadowfacts on 10/19/18.
//  Copyright © 2018 Shadowfacts. All rights reserved.
//

import UIKit
import Intents
import Pachyderm
import OSLog
import UserAccounts
import ComposeUI
import TuskerPreferences

private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "UserActivityManager")

@MainActor
class UserActivityManager {
    
    private let scene: UIWindowScene
    private let context: any UserActivityHandlingContext
    
    init(scene: UIWindowScene, context: any UserActivityHandlingContext) {
        self.scene = scene
        self.context = context
    }
    
    // MARK: - Utils
    private static let encoder = PropertyListEncoder()
    private static let decoder = PropertyListDecoder()
    
    private var mastodonController: MastodonController {
        scene.session.mastodonController!
    }
    
    static func getAccount(from activity: NSUserActivity) -> UserAccountInfo? {
        guard let id = activity.userInfo?["accountID"] as? String else {
            return nil
        }
        return UserAccountsManager.shared.getAccount(id: id)
    }
    
    // MARK: - Main Scene
    static func mainSceneActivity(accountID: String?) -> NSUserActivity {
        let activity = NSUserActivity(activityType: UserActivityType.mainScene.rawValue)
        if let accountID {
            activity.userInfo = [
                "accountID": accountID,
            ]
        }
        return activity
    }

    // MARK: - New Post
    static func newPostActivity(mentioning: Account? = nil, accountID: String) -> NSUserActivity {
        // todo: update to use managed objects
        let activity = NSUserActivity(type: .newPost, accountID: accountID)
        activity.isEligibleForPrediction = true
        if let mentioning = mentioning {
            activity.userInfo!["mentioning"] = mentioning.acct
            activity.title = "Send a message to \(mentioning.displayName)"
            activity.suggestedInvocationPhrase = "Send a message to \(mentioning.displayName)"
        } else {
            activity.title = "New Post"
            activity.suggestedInvocationPhrase = "Post in Tusker"
        }
        return activity
    }
    
    func handleNewPost(activity: NSUserActivity) {
        if let draft = Self.getDraft(from: activity) {
            context.compose(editing: draft)
        } else {
            let mentioning = activity.userInfo?["mentioning"] as? String
            let draft = mastodonController.createDraft(mentioningAcct: mentioning)
            context.compose(editing: draft)
        }
    }
    
    static func editDraftActivity(id: UUID, accountID: String) -> NSUserActivity {
        let activity = NSUserActivity(type: .newPost, accountID: accountID)
        activity.addUserInfoEntries(from: [
            "draftID": id.uuidString,
        ])
        return activity
    }
    
    static func addDuckedDraft(to activity: NSUserActivity?, draft: Draft) -> NSUserActivity {
        if let activity {
            activity.addUserInfoEntries(from: [
                "duckedDraftID": draft.id.uuidString
            ])
            return activity
        } else {
            return editDraftActivity(id: draft.id, accountID: draft.accountID)
        }
    }
    
    static func getDraft(from activity: NSUserActivity) -> Draft? {
        guard let idStr = activity.userInfo?["draftID"] as? String,
              let uuid = UUID(uuidString: idStr) else {
            return nil
        }
        return DraftsPersistentContainer.shared.getDraft(id: uuid)
    }
    
    static func getDuckedDraft(from activity: NSUserActivity) -> Draft? {
        guard let idStr = activity.userInfo?["duckedDraftID"] as? String,
              let uuid = UUID(uuidString: idStr) else {
            return nil
        }
        return DraftsPersistentContainer.shared.getDraft(id: uuid)
    }
    
    // MARK: - Check Notifications
    static func checkNotificationsActivity(mode: NotificationsMode = .allNotifications, accountID: String) -> NSUserActivity {
        let activity = NSUserActivity(type: .checkNotifications, accountID: accountID)
        activity.isEligibleForPrediction = true
        activity.isEligibleForHandoff = true
        activity.addUserInfoEntries(from: [
            "notificationsMode": mode.rawValue
        ])
        switch mode {
        case .allNotifications:
            activity.title = NSLocalizedString("Check Notifications", comment: "check notifications shortcut title")
            activity.suggestedInvocationPhrase = NSLocalizedString("Check my Tusker notifications", comment: "check notifications shortcut invocation phrase")
        case .mentionsOnly:
            activity.title = NSLocalizedString("Check Mentions", comment: "check mentions shortcut title")
            activity.suggestedInvocationPhrase = NSLocalizedString("Check my mentions", comment: "check mentions shortcut invocation phrase")
        }
        return activity
    }
    
    func handleCheckNotifications(activity: NSUserActivity) {
        context.select(route: .notifications)
        context.popToRoot()
        if let notificationsPageController = context.topViewController as? NotificationsPageViewController {
            notificationsPageController.loadViewIfNeeded()
            notificationsPageController.selectMode(Self.getNotificationsMode(from: activity) ?? Preferences.shared.defaultNotificationsMode)
        }
    }
    
    static func getNotificationsMode(from activity: NSUserActivity) -> NotificationsMode? {
        guard let str = activity.userInfo?["notificationsMode"] as? String else {
            return nil
        }
        return NotificationsMode(rawValue: str)
    }
    
    // MARK: - Show Timeline
    static func showTimelineActivity(timeline: Timeline, accountID: String) -> NSUserActivity? {
        guard let timelineData = try? encoder.encode(timeline) else { return nil }
        
        let activity = NSUserActivity(type: .showTimeline, accountID: accountID)
        activity.isEligibleForPrediction = true
        activity.isEligibleForHandoff = true
        activity.addUserInfoEntries(from: [
            "timelineData": timelineData,
        ])
        switch timeline {
        case .home:
            activity.title = NSLocalizedString("Show Home Timeline", comment: "home timeline shortcut title")
            activity.suggestedInvocationPhrase = NSLocalizedString("Show my home timeline", comment: "home timeline shortcut invocation phrase")
        case  .public(local: true):
            activity.title = NSLocalizedString("Show Local Timeline", comment: "local timeline shortcut title")
            activity.suggestedInvocationPhrase = NSLocalizedString("Show my local timeline", comment: "local timeline shortcut invocation phrase")
        case .public(local: false):
            activity.title = NSLocalizedString("Show Federated Timeline", comment: "federated timeline shortcut title")
            activity.suggestedInvocationPhrase = NSLocalizedString("Show my federated timeline", comment: "federated timeline invocation phrase")
        case let .tag(hashtag):
            activity.title = String(format: NSLocalizedString("Show #%@", comment: "show hashtag shortcut title"), hashtag)
            activity.suggestedInvocationPhrase = String(format: NSLocalizedString("Show the %@ hashtag", comment: "hashtag shortcut invocation phrase"), hashtag)
        case .list:
            // todo: add title to list
            activity.title = NSLocalizedString("Show List", comment: "list timeline shortcut title")
            activity.suggestedInvocationPhrase = NSLocalizedString("Show my list", comment: "list timeline invocation phrase")
        case .direct:
            activity.title = NSLocalizedString("Show Direct Messages", comment: "direct message timeline shortcut title")
            activity.suggestedInvocationPhrase = NSLocalizedString("Show my direct messages", comment: "direct message timeline invocation phrase")
        }
        return activity
    }
    
    static func addTimelinePositionInfo(to activity: NSUserActivity, statusIDs: [String], centerStatusID: String) {
        activity.addUserInfoEntries(from: [
            "statusIDs": statusIDs,
            "centerStatusID": centerStatusID
        ])
    }
    
    static func getTimeline(from activity: NSUserActivity) -> (Timeline, (statusIDs: [String], centerStatusID: String)?)? {
        guard activity.activityType == UserActivityType.showTimeline.rawValue,
              let data = activity.userInfo?["timelineData"] as? Data,
              let timeline = try? UserActivityManager.decoder.decode(Timeline.self, from: data) else {
            return nil
        }
        var positionInfo: ([String], String)?
        if let ids = activity.userInfo?["statusIDs"] as? [String],
           let center = activity.userInfo?["centerStatusID"] as? String {
            positionInfo = (ids, center)
        }
        return (timeline, positionInfo)
    }
    
    func handleShowTimeline(activity: NSUserActivity) {
        guard let (timeline, positionInfo) = Self.getTimeline(from: activity) else { return }
        
        var timelineVC: TimelineViewController?
        if let pinned = PinnedTimeline(timeline: timeline),
           mastodonController.accountPreferences.pinnedTimelines.contains(pinned) {
            context.select(route: .timelines)
            context.popToRoot()
            let pageController = context.topViewController as! TimelinesPageViewController
            pageController.selectTimeline(pinned, animated: false)
            timelineVC = pageController.currentViewController as? TimelineViewController
        } else if case .list(let id) = timeline {
            context.select(route: .list(id: id))
            timelineVC = context.topViewController as? TimelineViewController
        } else {
            context.select(route: .explore)
            context.popToRoot()
            timelineVC = TimelineViewController(for: timeline, mastodonController: mastodonController)
            context.push(timelineVC!)
        }
        
        if let timelineVC,
           let positionInfo,
           context.isHandoff {
            Task {
                await timelineVC.restoreStateFromHandoff(statusIDs: positionInfo.statusIDs, centerStatusID: positionInfo.centerStatusID)
            }
        }
    }
    
    // MARK: - Show Conversation
    static func showConversationActivity(mainStatusID: String, accountID: String, isEligibleForPrediction: Bool = false) -> NSUserActivity {
        let activity = NSUserActivity(type: .showConversation, accountID: accountID)
        activity.addUserInfoEntries(from: [
            "mainStatusID": mainStatusID,
        ])
        activity.isEligibleForPrediction = isEligibleForPrediction
        activity.isEligibleForHandoff = true
        return activity
    }
    
    static func getConversationStatus(from activity: NSUserActivity) -> String? {
        return activity.userInfo?["mainStatusID"] as? String
    }
    
    func handleShowConversation(activity: NSUserActivity) {
        guard let mainStatusID = Self.getConversationStatus(from: activity) else {
            return
        }
        context.select(route: .timelines)
        context.push(ConversationViewController(for: mainStatusID, state: .unknown, mastodonController: mastodonController))
    }
    
    // MARK: - Explore

    static func searchActivity(query: String?, accountID: String) -> NSUserActivity {
        let activity = NSUserActivity(type: .search, accountID: accountID)
        if let query {
            activity.userInfo!["query"] = query
        }
        activity.isEligibleForPrediction = true
        activity.title = NSLocalizedString("Search", comment: "search shortcut title")
        activity.suggestedInvocationPhrase = NSLocalizedString("Search the fediverse", comment: "search shortcut invocation phrase")
        return activity
    }
    
    static func getSearchQuery(from activity: NSUserActivity) -> String? {
        return activity.userInfo?["query"] as? String
    }
    
    func handleSearch(activity: NSUserActivity) {
        context.select(route: .explore)
        context.popToRoot()
        
        let searchController: UISearchController
        let resultsController: SearchResultsViewController
        if let explore = context.topViewController as? ExploreViewController {
            explore.loadViewIfNeeded()
            explore.searchControllerStatusOnAppearance = true
            searchController = explore.searchController
            resultsController = explore.resultsController
        } else if let inlineTrends = context.topViewController as? InlineTrendsViewController {
            inlineTrends.loadViewIfNeeded()
            inlineTrends.searchControllerStatusOnAppearance = true
            searchController = inlineTrends.searchController
            resultsController = inlineTrends.resultsController
        } else {
            return
        }
        
        if let query = Self.getSearchQuery(from: activity),
           !query.isEmpty {
            searchController.searchBar.text = query
            resultsController.performSearch(query: query)
        } else {
            searchController.searchBar.becomeFirstResponder()
        }
    }

    static func bookmarksActivity(accountID: String) -> NSUserActivity {
        let activity = NSUserActivity(type: .bookmarks, accountID: accountID)
        activity.isEligibleForPrediction = true
        activity.title = NSLocalizedString("View Bookmarks", comment: "bookmarks shortcut title")
        activity.suggestedInvocationPhrase = NSLocalizedString("Show my bookmarks in Tusker", comment: "bookmarks shortcut invocation phrase")
        return activity
    }
    
    func handleBookmarks(activity: NSUserActivity) {
        context.select(route: .bookmarks)
    }
    
    // MARK: - My Profile
    static func myProfileActivity(accountID: String) -> NSUserActivity {
        let activity = NSUserActivity(type: .myProfile, accountID: accountID)
        activity.isEligibleForPrediction = true
        activity.isEligibleForHandoff = true
        activity.title = NSLocalizedString("My Profile", comment: "my profile shortcut title")
        activity.suggestedInvocationPhrase = NSLocalizedString("Show my Mastodon profile", comment: "my profile shortuct invocation phrase")
        return activity
    }
    
    func handleMyProfile(activity: NSUserActivity) {
        context.select(route: .myProfile)
    }
    
    // MARK: - Show Profile
    static func showProfileActivity(id profileID: String, accountID: String) -> NSUserActivity {
        let activity = NSUserActivity(type: .showProfile, accountID: accountID)
        activity.addUserInfoEntries(from: [
            "profileID": profileID,
        ])
        activity.isEligibleForPrediction = true
        activity.isEligibleForHandoff = true
        return activity
    }
    
    static func getProfile(from activity: NSUserActivity) -> String? {
        return activity.userInfo?["profileID"] as? String
    }
    
    func handleShowProfile(activity: NSUserActivity) {
        guard let accountID = Self.getProfile(from: activity) else {
            return
        }
        context.select(route: .timelines)
        context.push(ProfileViewController(accountID: accountID, mastodonController: mastodonController))
    }
    
}
