import Defaults
import Foundation
import Siesta
import SwiftUI

final class PlaylistsModel: ObservableObject {
    static var shared = PlaylistsModel()

    @Published var isLoading = false
    @Published var playlists = [Playlist]()
    @Published var reloadPlaylists = false
    @Published var error: RequestError?

    var accounts = AccountsModel.shared

    init(_ playlists: [Playlist] = [Playlist]()) {
        self.playlists = playlists
    }

    var all: [Playlist] {
        playlists.sorted { $0.title.lowercased() < $1.title.lowercased() }
    }

    var editable: [Playlist] {
        all.filter(\.editable)
    }

    var lastUsed: Playlist? {
        find(id: Defaults[.lastUsedPlaylistID])
    }

    func find(id: Playlist.ID?) -> Playlist? {
        if id.isNil {
            return nil
        }

        return playlists.first { $0.id == id! }
    }

    var isEmpty: Bool {
        playlists.isEmpty
    }

    func load(force: Bool = false, onSuccess: @escaping () -> Void = {}) {
        guard accounts.app.supportsUserPlaylists, let account = accounts.current else {
            playlists = []
            return
        }

        loadCachedPlaylists(account)

        guard accounts.signedIn else { return }

        DispatchQueue.main.async { [weak self] in
            guard let self else { return }
            let request = force ? self.resource?.load() : self.resource?.loadIfNeeded()

            guard !request.isNil else {
                onSuccess()
                return
            }

            self.isLoading = true

            request?
                .onCompletion { [weak self] _ in
                    self?.isLoading = false
                }
                .onSuccess { resource in
                    self.error = nil
                    if let playlists: [Playlist] = resource.typedContent() {
                        DispatchQueue.main.async { [weak self] in
                            guard let self else { return }
                            self.playlists = playlists
                        }
                        PlaylistsCacheModel.shared.storePlaylist(account: account, playlists: playlists)
                        onSuccess()
                    }
                }
                .onFailure { self.error = $0 }
        }
    }

    private func loadCachedPlaylists(_ account: Account) {
        let cache = PlaylistsCacheModel.shared.retrievePlaylists(account: account)
        if !cache.isEmpty {
            DispatchQueue.main.async(qos: .userInteractive) {
                self.playlists = cache
            }
        }
    }

    func addVideo(
        playlistID: Playlist.ID,
        videoID: Video.ID,
        onSuccess: @escaping () -> Void = {},
        onFailure: ((RequestError) -> Void)? = nil
    ) {
        accounts.api.addVideoToPlaylist(
            videoID,
            playlistID,
            onFailure: onFailure ?? { requestError in
                NavigationModel.shared.presentAlert(
                    title: "Error when adding to playlist",
                    message: "(\(requestError.httpStatusCode ?? -1)) \(requestError.userMessage)"
                )
            }
        ) {
            self.load(force: true) {
                Defaults[.lastUsedPlaylistID] = playlistID
                self.reloadPlaylists.toggle()
                onSuccess()
            }
        }
    }

    func removeVideo(index: String, playlistID: Playlist.ID, onSuccess: @escaping () -> Void = {}) {
        accounts.api.removeVideoFromPlaylist(index, playlistID, onFailure: { _ in }) {
            self.load(force: true) {
                self.reloadPlaylists.toggle()
                onSuccess()
            }
        }
    }

    private var resource: Resource? {
        accounts.api.playlists
    }

    func onAccountChange() {
        error = nil
        playlists = []
        load()
    }
}