//
//  TrendingHashtagsViewController.swift
//  Tusker
//
//  Created by Shadowfacts on 2/6/21.
//  Copyright © 2021 Shadowfacts. All rights reserved.
//

import UIKit
import Pachyderm
import WebURLFoundationExtras
import Combine

class TrendingHashtagsViewController: UIViewController {

    weak var mastodonController: MastodonController!
    
    private var collectionView: UICollectionView!
    private var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
    
    private var state = State.unloaded
    private var confirmLoadMore = PassthroughSubject<Void, Never>()
    
    init(mastodonController: MastodonController) {
        self.mastodonController = mastodonController
        
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()

        title = NSLocalizedString("Trending Hashtags", comment: "trending hashtags screen title")
        
        view.backgroundColor = .appGroupedBackground
        
        var config = UICollectionLayoutListConfiguration(appearance: .grouped)
        config.backgroundColor = .appGroupedBackground
        config.itemSeparatorHandler = { [unowned self] indexPath, sectionConfig in
            guard let item = dataSource.itemIdentifier(for: indexPath) else {
                return sectionConfig
            }
            var config = sectionConfig
            if item.hideSeparators {
                config.topSeparatorVisibility = .hidden
                config.bottomSeparatorVisibility = .hidden
            }
            return config
        }
        let layout = UICollectionViewCompositionalLayout.list(using: config)
        collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
        collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        collectionView.delegate = self
        collectionView.dragDelegate = self
        collectionView.allowsFocus = true
        view.addSubview(collectionView)
        
        let loadingCell = UICollectionView.CellRegistration<LoadingCollectionViewCell, Void> { cell, indexPath, itemIdentifier in
            cell.indicator.startAnimating()
        }
        let hashtagCell = UICollectionView.CellRegistration<TrendingHashtagCollectionViewCell, Hashtag> { cell, indexPath, hashtag in
            cell.updateUI(hashtag: hashtag)
        }
        let confirmLoadMoreCell = UICollectionView.CellRegistration<ConfirmLoadMoreCollectionViewCell, Bool> { [unowned self] cell, indexPath, isLoading in
            cell.confirmLoadMore = self.confirmLoadMore
            cell.isLoading = isLoading
        }
        dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) { (collectionView, indexPath, item) in
            switch item {
            case .loadingIndicator:
                return collectionView.dequeueConfiguredReusableCell(using: loadingCell, for: indexPath, item: ())
            case .tag(let hashtag):
                return collectionView.dequeueConfiguredReusableCell(using: hashtagCell, for: indexPath, item: hashtag)
            case .confirmLoadMore(let loading):
                return collectionView.dequeueConfiguredReusableCell(using: confirmLoadMoreCell, for: indexPath, item: loading)
            }
        }
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        Task {
            await loadInitial()
        }
    }
    
    private func request(offset: Int?) -> Request<[Hashtag]> {
        if mastodonController.instanceFeatures.hasMastodonVersion(3, 5, 0) {
            return Client.getTrendingHashtags(offset: offset)
        } else {
            return Client.getTrendingHashtagsDeprecated(offset: offset)
        }
    }
    
    @MainActor
    private func loadInitial() async {
        guard case .unloaded = state else {
            return
        }
        state = .loadingInitial
        
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        snapshot.appendSections([.trendingTags])
        snapshot.appendItems([.loadingIndicator])
        await dataSource.apply(snapshot)
        
        do {
            let request = self.request(offset: nil)
            let (hashtags, _) = try await mastodonController.run(request)
            snapshot.deleteItems([.loadingIndicator])
            snapshot.appendItems(hashtags.map { .tag($0) })
            state = .loaded
            await dataSource.apply(snapshot)
        } catch {
            snapshot.deleteItems([.loadingIndicator])
            await dataSource.apply(snapshot)
            state = .unloaded
            
            let config = ToastConfiguration(from: error, with: "Error Loading Trending Tags", in: self) { [weak self] toast in
                toast.dismissToast(animated: true)
                await self?.loadInitial()
            }
            self.showToast(configuration: config, animated: true)
        }
    }
    
    @MainActor
    private func loadOlder() async {
        guard case .loaded = state else {
            return
        }
        state = .loadingOlder
        
        let origSnapshot = dataSource.snapshot()
        var snapshot = origSnapshot
        if Preferences.shared.disableInfiniteScrolling {
            snapshot.appendItems([.confirmLoadMore(false)])
            await dataSource.apply(snapshot)
            
            for await _ in confirmLoadMore.values {
                break
            }
            
            snapshot.deleteItems([.confirmLoadMore(false)])
            snapshot.appendItems([.confirmLoadMore(true)])
            await dataSource.apply(snapshot, animatingDifferences: false)
        } else {
            snapshot.appendItems([.loadingIndicator])
            await dataSource.apply(snapshot)
        }
        
        do {
            let request = self.request(offset: snapshot.itemIdentifiers.count - 1)
            let (hashtags, _) = try await mastodonController.run(request)
            var snapshot = origSnapshot
            snapshot.appendItems(hashtags.map { .tag($0) })
            await dataSource.apply(snapshot)
        } catch {
            await dataSource.apply(origSnapshot)
            
            let config = ToastConfiguration(from: error, with: "Error Loading More Tags", in: self) { [weak self] toast in
                toast.dismissToast(animated: true)
                await self?.loadOlder()
            }
            self.showToast(configuration: config, animated: true)
        }
        
        state = .loaded
    }
    
}

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

extension TrendingHashtagsViewController {
    enum Section {
        case trendingTags
    }
    enum Item: Hashable {
        case loadingIndicator
        case tag(Hashtag)
        case confirmLoadMore(Bool)
        
        var hideSeparators: Bool {
            switch self {
            case .loadingIndicator:
                return true
            case .tag(_):
                return false
            case .confirmLoadMore(_):
                return false
            }
        }
        
        var shouldSelect: Bool {
            if case .tag(_) = self {
                return true
            } else {
                return false
            }
        }
    }
}

extension TrendingHashtagsViewController: 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, shouldSelectItemAt indexPath: IndexPath) -> Bool {
        return dataSource.itemIdentifier(for: indexPath)?.shouldSelect ?? false
    }
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        guard let item = dataSource.itemIdentifier(for: indexPath),
              case let .tag(hashtag) = item else {
            return
        }
        
        show(HashtagTimelineViewController(for: hashtag, mastodonController: mastodonController), sender: nil)
    }
    
    func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
        guard let item = dataSource.itemIdentifier(for: indexPath),
              case let .tag(hashtag) = item else {
                  return nil
              }
        return UIContextMenuConfiguration(identifier: nil) {
            HashtagTimelineViewController(for: hashtag, mastodonController: self.mastodonController)
        } actionProvider: { (_) in
            UIMenu(children: self.actionsForHashtag(hashtag, source: .view(self.collectionView.cellForItem(at: indexPath))))
        }
    }
}

extension TrendingHashtagsViewController: UICollectionViewDragDelegate {
    func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
        guard let item = dataSource.itemIdentifier(for: indexPath),
              case let .tag(hashtag) = item,
              let url = URL(hashtag.url) else {
                  return []
              }
        let provider = NSItemProvider(object: url as NSURL)
        if let activity = UserActivityManager.showTimelineActivity(timeline: .tag(hashtag: hashtag.name), accountID: mastodonController.accountInfo!.id) {
            activity.displaysAuxiliaryScene = true
            provider.registerObject(activity, visibility: .all)
        }
        return [UIDragItem(itemProvider: provider)]
    }
}

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

extension TrendingHashtagsViewController: ToastableViewController {
}

extension TrendingHashtagsViewController: MenuActionProvider {
}
