//
//  SearchViewModel+LoadOldestState.swift
//  Mastodon
//
//  Created by sxiaojian on 2021/4/6.
//

import Foundation
import GameplayKit
import MastodonSDK
import os.log

extension SearchViewModel {
    class LoadOldestState: GKState {
        weak var viewModel: SearchViewModel?
        
        init(viewModel: SearchViewModel) {
            self.viewModel = viewModel
        }
        
        override func didEnter(from previousState: GKState?) {
            os_log("%{public}s[%{public}ld], %{public}s: enter %s, previous: %s", (#file as NSString).lastPathComponent, #line, #function, debugDescription, previousState.debugDescription)
            viewModel?.loadOldestStateMachinePublisher.send(self)
        }
    }
}

extension SearchViewModel.LoadOldestState {
    class Initial: SearchViewModel.LoadOldestState {
        override func isValidNextState(_ stateClass: AnyClass) -> Bool {
            guard let viewModel = viewModel else { return false }
            guard viewModel.searchResult.value != nil else { return false }
            return stateClass == Loading.self
        }
    }
    
    class Loading: SearchViewModel.LoadOldestState {
        override func isValidNextState(_ stateClass: AnyClass) -> Bool {
            stateClass == Fail.self || stateClass == Idle.self || stateClass == NoMore.self
        }
        
        override func didEnter(from previousState: GKState?) {
            super.didEnter(from: previousState)
            guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
            guard let activeMastodonAuthenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else {
                assertionFailure()
                stateMachine.enter(Fail.self)
                return
            }
            guard let oldSearchResult = viewModel.searchResult.value else {
                stateMachine.enter(Fail.self)
                return
            }
            var offset = 0
            switch viewModel.searchScope.value {
            case Mastodon.API.V2.Search.SearchType.accounts:
                offset = oldSearchResult.accounts.count
            case Mastodon.API.V2.Search.SearchType.hashtags:
                offset = oldSearchResult.hashtags.count
            default:
                return
            }
            let query = Mastodon.API.V2.Search.Query(q: viewModel.searchText.value,
                                                  type: viewModel.searchScope.value,
                                                  accountID: nil,
                                                  maxID: nil,
                                                  minID: nil,
                                                  excludeUnreviewed: nil,
                                                  resolve: nil,
                                                  limit: nil,
                                                  offset: offset,
                                                  following: nil)

            viewModel.context.apiService.search(domain: activeMastodonAuthenticationBox.domain, query: query, mastodonAuthenticationBox: activeMastodonAuthenticationBox)
                .sink { completion in
                    switch completion {
                    case .failure(let error):
                        os_log("%{public}s[%{public}ld], %{public}s: load oldest search failed. %s", (#file as NSString).lastPathComponent, #line, #function, error.localizedDescription)
                    case .finished:
                        // handle isFetchingLatestTimeline in fetch controller delegate
                        break
                    }
                } receiveValue: { result in
                    switch viewModel.searchScope.value {
                    case Mastodon.API.V2.Search.SearchType.accounts:
                        if result.value.accounts.isEmpty {
                            stateMachine.enter(NoMore.self)
                        } else {
                            var newAccounts = [Mastodon.Entity.Account]()
                            newAccounts.append(contentsOf: oldSearchResult.accounts)
                            newAccounts.append(contentsOf: result.value.accounts)
                            newAccounts.removeDuplicates()
                            viewModel.searchResult.value = Mastodon.Entity.SearchResult(accounts: newAccounts, statuses: oldSearchResult.statuses, hashtags: oldSearchResult.hashtags)
                            stateMachine.enter(Idle.self)
                        }
                    case Mastodon.API.V2.Search.SearchType.hashtags:
                        if result.value.hashtags.isEmpty {
                            stateMachine.enter(NoMore.self)
                        } else {
                            var newTags = [Mastodon.Entity.Tag]()
                            newTags.append(contentsOf: oldSearchResult.hashtags)
                            newTags.append(contentsOf: result.value.hashtags)
                            newTags.removeDuplicates()
                            viewModel.searchResult.value = Mastodon.Entity.SearchResult(accounts: oldSearchResult.accounts, statuses: oldSearchResult.statuses, hashtags: newTags)
                            stateMachine.enter(Idle.self)
                        }
                    default:
                        return
                    }
                }
                .store(in: &viewModel.disposeBag)
        }
    }
    
    class Fail: SearchViewModel.LoadOldestState {
        override func isValidNextState(_ stateClass: AnyClass) -> Bool {
            stateClass == Loading.self || stateClass == Idle.self
        }
    }
    
    class Idle: SearchViewModel.LoadOldestState {
        override func isValidNextState(_ stateClass: AnyClass) -> Bool {
            stateClass == Loading.self
        }
    }

    class NoMore: SearchViewModel.LoadOldestState {
        override func isValidNextState(_ stateClass: AnyClass) -> Bool {
            // reset state if needs
            stateClass == Idle.self
        }
        
        override func didEnter(from previousState: GKState?) {
            guard let viewModel = viewModel else { return }
            guard let diffableDataSource = viewModel.searchResultDiffableDataSource else {
                assertionFailure()
                return
            }
            DispatchQueue.main.async {
                var snapshot = diffableDataSource.snapshot()
                snapshot.deleteItems([.bottomLoader])
                diffableDataSource.apply(snapshot)
            }
        }
    }
}
