//
//  TrendingLinksViewController.swift
//  Tusker
//
//  Created by Shadowfacts on 4/2/22.
//  Copyright © 2022 Shadowfacts. All rights reserved.
//

import UIKit
import Pachyderm
import WebURLFoundationExtras
import SafariServices
import Combine

class TrendingLinksViewController: UIViewController, CollectionViewController {
    
    weak var mastodonController: MastodonController!
    
    var collectionView: UICollectionView!
    private var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
    
    private var state = State.unloaded
    private let 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 Links", comment: "trending links screen title")
        
        let layout = UICollectionViewCompositionalLayout { [unowned self] sectionIndex, environment in
            switch dataSource.sectionIdentifier(for: sectionIndex) {
            case nil:
                fatalError()
                
            case .loadingIndicator:
                var config = UICollectionLayoutListConfiguration(appearance: .grouped)
                config.backgroundColor = .appGroupedBackground
                config.showsSeparators = false
                return .list(using: config, layoutEnvironment: environment)
                
            case .links:
                let size = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(280))
                let group: NSCollectionLayoutGroup
                if let maximumReadableWidth = environment.maximumReadableWidth,
                   environment.container.contentSize.width >= maximumReadableWidth {
                    let width = (environment.container.contentSize.width - 48) / 2
                    group = .horizontal(layoutSize: size, subitems: [
                        NSCollectionLayoutItem(layoutSize: .init(widthDimension: .absolute(width), heightDimension: .estimated(280))),
                        NSCollectionLayoutItem(layoutSize: .init(widthDimension: .absolute(width), heightDimension: .estimated(280))),
                    ])
                } else {
                    group = .vertical(layoutSize: size, subitems: [NSCollectionLayoutItem(layoutSize: size)])
                }
                group.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16)
                group.interItemSpacing = .fixed(16)
                let section = NSCollectionLayoutSection(group: group)
                section.interGroupSpacing = 16
                section.contentInsets = NSDirectionalEdgeInsets(top: 16, leading: 0, bottom: 16, trailing: 0)
                return section
            }
        }
        collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        collectionView.delegate = self
        collectionView.dragDelegate = self
        collectionView.backgroundColor = .appGroupedBackground
        collectionView.allowsFocus = true
        view.addSubview(collectionView)
        NSLayoutConstraint.activate([
            collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            collectionView.topAnchor.constraint(equalTo: view.topAnchor),
            collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
        ])
        
        dataSource = createDataSource()
    }
    
    private func createDataSource() -> UICollectionViewDiffableDataSource<Section, Item> {
        let loadingCell = UICollectionView.CellRegistration<LoadingCollectionViewCell, Void> { cell, indexPath, itemIdentifier in
            cell.indicator.startAnimating()
        }
        let linkCell = UICollectionView.CellRegistration<TrendingLinkCardCollectionViewCell, Card>(cellNib: UINib(nibName: "TrendingLinkCardCollectionViewCell", bundle: .main)) { cell, indexPath, item in
            cell.verticalSize = .compact
            cell.updateUI(card: item)
        }
        let confirmLoadMoreCell = UICollectionView.CellRegistration<ConfirmLoadMoreCollectionViewCell, Bool> { cell, indexPath, isLoading in
            cell.confirmLoadMore = self.confirmLoadMore
            cell.isLoading = isLoading
        }
        return UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in
            switch itemIdentifier {
            case .loadingIndicator:
                return collectionView.dequeueConfiguredReusableCell(using: loadingCell, for: indexPath, item: ())
            case .link(let card):
                return collectionView.dequeueConfiguredReusableCell(using: linkCell, for: indexPath, item: card)
            case .confirmLoadMore(let loading):
                return collectionView.dequeueConfiguredReusableCell(using: confirmLoadMoreCell, for: indexPath, item: loading)
            }
        }
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        Task {
            await loadInitial()
        }
    }
    
    @MainActor
    private func loadInitial() async {
        guard case .unloaded = state else {
            return
        }
        state = .loading
        
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        snapshot.appendSections([.loadingIndicator])
        snapshot.appendItems([.loadingIndicator])
        await dataSource.apply(snapshot)
        
        do {
            let request = Client.getTrendingLinks()
            let (links, _) = try await mastodonController.run(request)
            snapshot.deleteSections([.loadingIndicator])
            snapshot.appendSections([.links])
            snapshot.appendItems(links.map { .link($0) })
            state = .loaded
            await dataSource.apply(snapshot)
        } catch {
            await dataSource.apply(NSDiffableDataSourceSnapshot())
            state = .unloaded
            let config = ToastConfiguration(from: error, with: "Error Loading Trending Links", 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.appendSections([.loadingIndicator])
            snapshot.appendItems([.confirmLoadMore(false)], toSection: .loadingIndicator)
            await dataSource.apply(snapshot)
            
            for await _ in confirmLoadMore.values {
                break
            }
            
            snapshot.deleteItems([.confirmLoadMore(false)])
            snapshot.appendItems([.confirmLoadMore(true)], toSection: .loadingIndicator)
            await dataSource.apply(snapshot, animatingDifferences: false)
        } else {
            snapshot.appendSections([.loadingIndicator])
            snapshot.appendItems([.loadingIndicator], toSection: .loadingIndicator)
            await dataSource.apply(snapshot)
        }
        
        do {
            let request = Client.getTrendingLinks(offset: origSnapshot.itemIdentifiers.count)
            let (links, _) = try await mastodonController.run(request)
            var snapshot = origSnapshot
            snapshot.appendItems(links.map { .link($0) }, toSection: .links)
            await dataSource.apply(snapshot)
        } catch {
            await dataSource.apply(origSnapshot)
            let config = ToastConfiguration(from: error, with: "Erorr Loading More Links", in: self) { [weak self] toast in
                toast.dismissToast(animated: true)
                await self?.loadOlder()
            }
            self.showToast(configuration: config, animated: true)
        }
    }
    
}

extension TrendingLinksViewController {
    enum State {
        case unloaded
        case loading
        case loaded
        case loadingOlder
    }
}

extension TrendingLinksViewController {
    enum Section {
        case loadingIndicator
        case links
    }
    enum Item: Hashable {
        case loadingIndicator
        case link(Card)
        case confirmLoadMore(Bool)
        
        static func ==(lhs: Item, rhs: Item) -> Bool {
            switch (lhs, rhs) {
            case (.loadingIndicator, .loadingIndicator):
                return true
            case (.link(let a), .link(let b)):
                return a.url == b.url
            case (.confirmLoadMore(let a), .confirmLoadMore(let b)):
                return a == b
            default:
                return false
            }
        }
        
        func hash(into hasher: inout Hasher) {
            switch self {
            case .loadingIndicator:
                hasher.combine(0)
            case .link(let card):
                hasher.combine(1)
                hasher.combine(card.url)
            case .confirmLoadMore(let loading):
                hasher.combine(2)
                hasher.combine(loading)
            }
        }
        
        var shouldSelect: Bool {
            if case .link(_) = self {
                return true
            } else {
                return false
            }
        }
    }
}

extension TrendingLinksViewController: 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 loadOlder()
            }
        }
    }
    
    func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
        return dataSource.itemIdentifier(for: indexPath)?.shouldSelect ?? false
    }
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        guard case .link(let card) = dataSource.itemIdentifier(for: indexPath),
              let url = URL(card.url) else {
            return
        }
        selected(url: url)
    }
    
    func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
        guard case .link(let card) = dataSource.itemIdentifier(for: indexPath),
              let url = URL(card.url),
              let cell = collectionView.cellForItem(at: indexPath) else {
            return nil
        }
        return UIContextMenuConfiguration {
            let vc = SFSafariViewController(url: url)
            vc.preferredControlTintColor = Preferences.shared.accentColor.color
            return vc
        } actionProvider: { _ in
            UIMenu(children: self.actionsForTrendingLink(card: card, source: .view(cell)))
        }
    }
    
    func collectionView(_ collectionView: UICollectionView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
        MenuPreviewHelper.willPerformPreviewAction(animator: animator, presenter: self)
    }
}

extension TrendingLinksViewController: UICollectionViewDragDelegate {
    func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
        guard case .link(let card) = dataSource.itemIdentifier(for: indexPath),
              let url = URL(card.url) else {
            return []
        }
        return [UIDragItem(itemProvider: NSItemProvider(object: url as NSURL))]
    }
}

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

extension TrendingLinksViewController: ToastableViewController {
}

extension TrendingLinksViewController: MenuActionProvider {
}
