//
//  StatusActionAccountListCollectionViewController.swift
//  Tusker
//
//  Created by Shadowfacts on 9/5/19.
//  Copyright © 2019 Shadowfacts. All rights reserved.
//

import UIKit
import Pachyderm

class StatusActionAccountListCollectionViewController: UIViewController, CollectionViewController {
    
    private static let pageSize = 40
    
    private let statusID: String
    private let actionType: StatusActionAccountListViewController.ActionType
    private let mastodonController: MastodonController
    
    private var needsInaccurateCountWarning = false
    
    var collectionView: UICollectionView! {
        view as? UICollectionView
    }
    private var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
    
    private var state: State = .unloaded
    private var older: RequestRange?
    
    /**
     Creates a new view controller showing the accounts that performed the given action on the given status.
     
     - Parameter mastodonController The `MastodonController` instance this view controller uses.
     */
    init(statusID: String, actionType: StatusActionAccountListViewController.ActionType, mastodonController: MastodonController) {
        self.statusID = statusID
        self.actionType = actionType
        self.mastodonController = mastodonController
        
        super.init(nibName: nil, bundle: nil)
        
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func loadView() {
        var accountsConfig = UICollectionLayoutListConfiguration(appearance: .grouped)
        accountsConfig.backgroundColor = .appGroupedBackground
        accountsConfig.itemSeparatorHandler = { [unowned self] indexPath, sectionConfig in
            guard let item = self.dataSource.itemIdentifier(for: indexPath) else {
                return sectionConfig
            }
            var config = sectionConfig
            if item.hideSeparators {
                config.topSeparatorVisibility = .hidden
                config.bottomSeparatorVisibility = .hidden
            }
            return config
        }
        let layout = UICollectionViewCompositionalLayout { [unowned self] sectionIndex, environment in
            let section: NSCollectionLayoutSection
            switch dataSource.sectionIdentifier(for: sectionIndex)! {
            case .status:
                var config = UICollectionLayoutListConfiguration(appearance: .grouped)
                config.footerMode = self.needsInaccurateCountWarning ? .supplementary : .none
                config.backgroundColor = .appGroupedBackground
                config.leadingSwipeActionsConfigurationProvider = { [unowned self] in
                    (collectionView.cellForItem(at: $0) as? TimelineStatusCollectionViewCell)?.leadingSwipeActions()
                }
                config.trailingSwipeActionsConfigurationProvider = { [unowned self] in
                    (collectionView.cellForItem(at: $0) as? TimelineStatusCollectionViewCell)?.trailingSwipeActions()
                }
                section = NSCollectionLayoutSection.list(using: config, layoutEnvironment: environment)
            case .accounts:
                section = NSCollectionLayoutSection.list(using: accountsConfig, layoutEnvironment: environment)
            }
            section.readableContentInset(in: environment)
            return section
        }
        view = UICollectionView(frame: .zero, collectionViewLayout: layout)
        collectionView.delegate = self
        collectionView.dragDelegate = self
        collectionView.allowsFocus = true
        dataSource = createDataSource()
    }
    
    private func createDataSource() -> UICollectionViewDiffableDataSource<Section, Item> {
        let statusCell = UICollectionView.CellRegistration<TimelineStatusCollectionViewCell, (String, CollapseState)> { [unowned self] cell, indexPath, item in
            cell.delegate = self
            cell.updateUI(statusID: item.0, state: item.1, filterResult: .allow, precomputedContent: nil)
            
            cell.configurationUpdateHandler = { cell, state in
                cell.backgroundConfiguration = .appListGroupedCell(for: state)
            }
        }
        let accountCell = UICollectionView.CellRegistration<AccountCollectionViewCell, String> { [unowned self] cell, indexPath, item in
            cell.delegate = self
            cell.updateUI(accountID: item)
            
            cell.configurationUpdateHandler = { cell, state in
                cell.backgroundConfiguration = .appListGroupedCell(for: state)
            }
        }
        let loadingCell = UICollectionView.CellRegistration<LoadingCollectionViewCell, Void> { cell, indexPath, item in
            cell.indicator.startAnimating()
        }
        let dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in
            switch itemIdentifier {
            case .status(let id, let state):
                return collectionView.dequeueConfiguredReusableCell(using: statusCell, for: indexPath, item: (id, state))
            case .account(let id):
                return collectionView.dequeueConfiguredReusableCell(using: accountCell, for: indexPath, item: id)
            case .loadingIndicator:
                return collectionView.dequeueConfiguredReusableCell(using: loadingCell, for: indexPath, item: ())
            }
        }
        let sectionHeaderCell = UICollectionView.SupplementaryRegistration<UICollectionViewListCell>(elementKind: UICollectionView.elementKindSectionFooter) { (headerView, collectionView, indexPath) in
            var config = headerView.defaultContentConfiguration()
            config.text = NSLocalizedString("Favorite and reblog counts for posts originating from instances other than your own may not be accurate.", comment: "shown on lists of status total actions")
            headerView.contentConfiguration = config
        }
        dataSource.supplementaryViewProvider = { collectionView, elementKind, indexPath in
            return collectionView.dequeueConfiguredReusableSupplementary(using: sectionHeaderCell, for: indexPath)
        }
        return dataSource
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        clearSelectionOnAppear(animated: animated)
        
        if case .unloaded = state {
            Task {
                await loadAccounts()
            }
        }
    }
    
    func addStatus(_ status: StatusMO, state: CollapseState, showInaccurateCountWarning: Bool) {
        loadViewIfNeeded()
        
        needsInaccurateCountWarning = showInaccurateCountWarning && status.url?.host != mastodonController.instanceURL.host
        
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        snapshot.appendSections([.status, .accounts])
        snapshot.appendItems([.status(status.id, state)], toSection: .status)
        dataSource.apply(snapshot, animatingDifferences: false)
    }
    
    func setAccounts(_ accountIDs: [String], animated: Bool) {
        guard case .unloaded = state else {
            return
        }
        var snapshot = dataSource.snapshot()
        snapshot.appendItems(accountIDs.map { .account($0) }, toSection: .accounts)
        dataSource.apply(snapshot, animatingDifferences: animated)
        self.state = .loaded
    }
    
    private func request(for range: RequestRange) -> Request<[Account]> {
        switch actionType {
        case .favorite:
            return Status.getFavourites(statusID, range: range.withCount(Self.pageSize))
        case .reblog:
            return Status.getReblogs(statusID, range: range.withCount(Self.pageSize))
        }
    }
    
    func apply(snapshot: NSDiffableDataSourceSnapshot<Section, Item>) async {
        await Task { @MainActor in
            self.dataSource.apply(snapshot)
        }.value
    }
    
    @MainActor
    private func loadAccounts() async {
        guard case .unloaded = state else {
            return
        }
        self.state = .loadingInitial
        
        var snapshot = dataSource.snapshot()
        snapshot.appendItems([.loadingIndicator], toSection: .accounts)
        await apply(snapshot: snapshot)
        
        do {
            let (accounts, pagination) = try await mastodonController.run(request(for: .default))
            await mastodonController.persistentContainer.addAll(accounts: accounts)
            
            guard case .loadingInitial = self.state else {
                return
            }
            self.state = .loaded
            self.older = pagination?.older
            
            var snapshot = dataSource.snapshot()
            snapshot.deleteItems([.loadingIndicator])
            snapshot.appendItems(accounts.map { .account($0.id) }, toSection: .accounts)
            await apply(snapshot: snapshot)
            
        } catch {
            self.state = .unloaded
            
            let config = ToastConfiguration(from: error, with: "Error Loading Accounts", in: self) { toast in
                toast.dismissToast(animated: true)
                await self.loadAccounts()
            }
            self.showToast(configuration: config, animated: true)
        }
    }
    
    @MainActor
    private func loadOlder() async {
        guard case .loaded = state,
              let older else {
            return
        }
        self.state = .loadingOlder
        
        var snapshot = self.dataSource.snapshot()
        snapshot.appendItems([.loadingIndicator], toSection: .accounts)
        await apply(snapshot: snapshot)
        
        do {
            let (accounts, pagination) = try await mastodonController.run(request(for: older))
            await mastodonController.persistentContainer.addAll(accounts: accounts)
            
            guard case .loadingOlder = self.state else {
                return
            }
            self.state = .loaded
            self.older = pagination?.older
            
            var snapshot = dataSource.snapshot()
            snapshot.deleteItems([.loadingIndicator])
            snapshot.appendItems(accounts.map { .account($0.id) }, toSection: .accounts)
            await apply(snapshot: snapshot)
            
        } catch {
            self.state = .loaded
            
            let config = ToastConfiguration(from: error, with: "Error Loading More", in: self) { [unowned self] toast in
                toast.dismissToast(animated: true)
                await self.loadOlder()
            }
            self.showToast(configuration: config, animated: true)
        }
    }
}

extension StatusActionAccountListCollectionViewController {
    enum State {
        case unloaded
        case loadingInitial
        case loaded
        case loadingOlder
    }
}

extension StatusActionAccountListCollectionViewController {
    enum Section {
        case status
        case accounts
    }
    enum Item: Hashable {
        case status(String, CollapseState)
        case account(String)
        case loadingIndicator
        
        var hideSeparators: Bool {
            switch self {
            case .loadingIndicator:
                return true
            default:
                return false
            }
        }
        
        static func ==(lhs: Item, rhs: Item) -> Bool {
            switch (lhs, rhs) {
            case (.status(let a, _), .status(let b, _)):
                return a == b
            case (.account(let a), .account(let b)):
                return a == b
            case (.loadingIndicator, .loadingIndicator):
                return true
            default:
                return false
            }
        }
        
        func hash(into hasher: inout Hasher) {
            switch self {
            case .status(let id, _):
                hasher.combine(0)
                hasher.combine(id)
            case .account(let id):
                hasher.combine(1)
                hasher.combine(id)
            case .loadingIndicator:
                hasher.combine(2)
            }
        }
    }
}

extension StatusActionAccountListCollectionViewController: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
        if indexPath.section == collectionView.numberOfSections - 1,
           indexPath.row == collectionView.numberOfItems(inSection: indexPath.section) - 1 {
            Task {
                await self.loadOlder()
            }
        }
    }
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        switch dataSource.itemIdentifier(for: indexPath) {
        case nil:
            return
        case .status(let id, let state):
            selected(status: id, state: state.copy())
        case .account(let id):
            selected(account: id)
        case .loadingIndicator:
            return
        }
    }
    
    func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
        guard let item = dataSource.itemIdentifier(for: indexPath),
              let cell = collectionView.cellForItem(at: indexPath) else {
            return nil
        }
        switch item {
        case .status:
            return (cell as? TimelineStatusCollectionViewCell)?.contextMenuConfiguration()
        case .account(let id):
            return UIContextMenuConfiguration {
                ProfileViewController(accountID: id, mastodonController: self.mastodonController)
            } actionProvider: { _ in
                UIMenu(children: self.actionsForProfile(accountID: id, source: .view(cell)))
            }
        case .loadingIndicator:
            return nil
        }
    }
    
    func collectionView(_ collectionView: UICollectionView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
        MenuPreviewHelper.willPerformPreviewAction(animator: animator, presenter: self)
    }
}

extension StatusActionAccountListCollectionViewController: UICollectionViewDragDelegate {
    func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
        guard let currentAccountID = mastodonController.accountInfo?.id,
              let item = dataSource.itemIdentifier(for: indexPath) else {
            return []
        }
        let provider: NSItemProvider
        switch item {
        case .status(let id, _):
            guard let status = mastodonController.persistentContainer.status(for: id) else {
                return []
            }
            provider = NSItemProvider(object: status.url! as NSURL)
            let activity = UserActivityManager.showConversationActivity(mainStatusID: id, accountID: currentAccountID)
            activity.displaysAuxiliaryScene = true
            provider.registerObject(activity, visibility: .all)
        case .account(let id):
            guard let account = mastodonController.persistentContainer.account(for: id) else {
                return []
            }
            provider = NSItemProvider(object: account.url as NSURL)
            let activity = UserActivityManager.showProfileActivity(id: account.id, accountID: currentAccountID)
            activity.displaysAuxiliaryScene = true
            provider.registerObject(activity, visibility: .all)
        case .loadingIndicator:
            return []
        }
        return [UIDragItem(itemProvider: provider)]
    }
}

extension StatusActionAccountListCollectionViewController: TuskerNavigationDelegate {
    var apiController: MastodonController! { mastodonController }
}

extension StatusActionAccountListCollectionViewController: MenuActionProvider {
}

extension StatusActionAccountListCollectionViewController: StatusCollectionViewCellDelegate {
    func statusCellNeedsReconfigure(_ cell: StatusCollectionViewCell, animated: Bool, completion: (() -> Void)?) {
        if let indexPath = collectionView.indexPath(for: cell) {
            var snapshot = dataSource.snapshot()
            snapshot.reconfigureItems([dataSource.itemIdentifier(for: indexPath)!])
            dataSource.apply(snapshot, animatingDifferences: animated, completion: completion)
        }
    }
    
    func statusCellShowFiltered(_ cell: StatusCollectionViewCell) {
        fatalError()
    }
}

extension StatusActionAccountListCollectionViewController: StatusBarTappableViewController {
    func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult {
        collectionView.scrollToTop()
        return .stop
    }
}
