//
//  AccountFollowsListViewController.swift
//  Tusker
//
//  Created by Shadowfacts on 1/18/23.
//  Copyright © 2023 Shadowfacts. All rights reserved.
//

import UIKit
import Pachyderm

class AccountFollowsListViewController: UIViewController, CollectionViewController {
    
    private static let pageSize = 40
    
    let accountID: String
    let mastodonController: MastodonController
    let mode: AccountFollowsViewController.Mode
    
    var collectionView: UICollectionView! {
        view as? UICollectionView
    }
    private var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
    
    private var state: State = .unloaded
    private var newer: RequestRange?
    private var older: RequestRange?
    
    init(accountID: String, mastodonController: MastodonController, mode: AccountFollowsViewController.Mode) {
        self.accountID = accountID
        self.mastodonController = mastodonController
        self.mode = mode
        
        super.init(nibName: nil, bundle: nil)
        
        title = mode.title
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func loadView() {
        var config = UICollectionLayoutListConfiguration(appearance: .plain)
        config.backgroundColor = .appBackground
        config.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 { sectionIndex, environment in
            let section = NSCollectionLayoutSection.list(using: config, 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 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 = .appListPlainCell(for: state)
            }
        }
        let loadingCell = UICollectionView.CellRegistration<LoadingCollectionViewCell, Void> { cell, indexPath, item in
            cell.indicator.startAnimating()
        }
        return UICollectionViewDiffableDataSource(collectionView: collectionView, cellProvider: { collectionView, indexPath, itemIdentifier in
            switch itemIdentifier {
            case .account(let id):
                return collectionView.dequeueConfiguredReusableCell(using: accountCell, for: indexPath, item: id)
            case .loadingIndicator:
                return collectionView.dequeueConfiguredReusableCell(using: loadingCell, for: indexPath, item: ())
            }
        })
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        clearSelectionOnAppear(animated: animated)
        
        if case .unloaded = state {
            Task {
                await loadInitial()
            }
        }
    }
    
    private nonisolated func request(for range: RequestRange) -> Request<[Account]> {
        switch mode {
        case .following:
            return Account.getFollowing(accountID, range: range.withCount(Self.pageSize))
        case .followers:
            return Account.getFollowers(accountID, range: range.withCount(Self.pageSize))
        }
    }
    
    private func apply(snapshot: NSDiffableDataSourceSnapshot<Section, Item>) async {
        await Task { @MainActor in
            self.dataSource.apply(snapshot)
        }.value
    }
    
    @MainActor
    private func loadInitial() async {
        guard case .unloaded = state else {
            return
        }
        
        self.state = .loadingInitial
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        snapshot.appendSections([.accounts])
        snapshot.appendItems([.loadingIndicator])
        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.newer = pagination?.newer
            self.older = pagination?.older
            
            var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
            snapshot.appendSections([.accounts])
            snapshot.appendItems(accounts.map { .account($0.id) })
            await apply(snapshot: snapshot)
            
        } catch {
            self.state = .unloaded
            
            let config = ToastConfiguration(from: error, with: "Error Loading Accounts", in: self) { [unowned 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,
              let older else {
            return
        }
        
        self.state = .loadingOlder
        var snapshot = dataSource.snapshot()
        snapshot.appendItems([.loadingIndicator])
        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) })
            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 AccountFollowsListViewController {
    enum State {
        case unloaded
        case loadingInitial
        case loaded
        case loadingOlder
    }
}

extension AccountFollowsListViewController {
    enum Section {
        case accounts
    }
    enum Item: Hashable {
        case account(String)
        case loadingIndicator
        
        var hideSeparators: Bool {
            switch self {
            case .account(_):
                return false
            case .loadingIndicator:
                return true
            }
        }
    }
}

extension AccountFollowsListViewController: 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) {
        guard case .account(let id) = dataSource.itemIdentifier(for: indexPath) else {
            return
        }
        selected(account: id)
    }
    
    func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
        guard case .account(let id) = dataSource.itemIdentifier(for: indexPath),
              let cell = collectionView.cellForItem(at: indexPath) else {
            return nil
        }
        return UIContextMenuConfiguration {
            ProfileViewController(accountID: id, mastodonController: self.mastodonController)
        } actionProvider: { _ in
            UIMenu(children: self.actionsForProfile(accountID: id, source: .view(cell)))
        }
    }
    
    func collectionView(_ collectionView: UICollectionView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
        MenuPreviewHelper.willPerformPreviewAction(animator: animator, presenter: self)
    }
}

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

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

extension AccountFollowsListViewController: MenuActionProvider {
}

extension AccountFollowsListViewController: ToastableViewController {
}

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