//
//  SearchViewModel.swift
//  Padel Tournament
//
//  Created by Razmig Sarkissian on 07/02/2024.
//

import SwiftUI

class DebouncableViewModel: ObservableObject {
    @Published var debouncableText: String = ""
    var debounceTrigger: Double = 0.15
}

class SearchViewModel: ObservableObject, Identifiable {
    let id: UUID = UUID()
    var allowSelection : Int = 0
    var codeClub: String? = nil
    var clubName: String? = nil
    var ligueName: String? = nil
    var showFemaleInMaleAssimilation: Bool = false
    var hidePlayers: [String]?
    
    @Published var debouncableText: String = ""
    @Published var searchText: String = ""
    @Published var task: DispatchWorkItem?
    @Published var computedSearchText: String = ""
    @Published var tokens = [SearchToken]()
    @Published var suggestedTokens = [SearchToken]()
    @Published var dataSet: DataSet = .national
    @Published var filterOption = PlayerFilterOption.all
    @Published var hideAssimilation: Bool = false
    @Published var ascending: Bool = true
    @Published var sortOption: SortOption = .rank
    @Published var selectedPlayers: Set<ImportedPlayer> = Set()
    @Published var filterSelectionEnabled: Bool = false
    @Published var isPresented: Bool = false
    
    var mostRecentDate: Date? = nil
    
    var selectionIsOver: Bool {
        if allowSingleSelection && selectedPlayers.count == 1 {
            return true
        } else if allowMultipleSelection && selectedPlayers.count == allowSelection {
            return true
        }
        
        return false
    }
    
    var allowMultipleSelection: Bool {
        allowSelection > 1 || allowSelection == -1
    }
    
    var allowSingleSelection: Bool {
        allowSelection == 1
    }
    
    var debounceTrigger: Double {
        dataSet == .national ? 0.4 : 0.1
    }
    
    var throttleTrigger: Double {
        dataSet == .national ? 0.15 : 0.1
    }
    
    var contentUnavailableMessage: String {
        var message = ["Vérifiez l'ortographe ou lancez une nouvelle recherche."]
        if tokens.isEmpty {
            message.append("Il est possible que cette personne n'est joué aucun tournoi depuis les 12 derniers mois. Dans ce pas, Padel Club ne pourra pas le trouver.")
        }
        return message.joined(separator: "\n")
    }
    
    func codeClubs() -> [String] {
        let clubs: [Club] = DataStore.shared.user.clubsObjects()
        return clubs.compactMap { $0.code }
    }
    
    func getCodeClub() -> String? {
        if let codeClub { return codeClub }
        if let userCodeClub = DataStore.shared.user.currentPlayerData()?.clubCode { return userCodeClub }
        return nil
    }
    
    func getLigueName() -> String? {
        if let ligueName { return ligueName }
        if let userLigueName = DataStore.shared.user.currentPlayerData()?.ligueName { return userLigueName }
        return nil
    }
    
    func showIndex() -> Bool {
        if dataSet == .national { return false }
        if filterOption == .all { return false }
        return true
    }
    
    func prompt(forDataSet: DataSet) -> String {
        switch forDataSet {
        case .national:
            if let mostRecentDate {
                return "base fédérale \(mostRecentDate.monthYearFormatted)"
            } else {
                return "rechercher"
            }
        case .ligue:
            return "dans cette ligue"
        case .club:
            return "dans ce club"
        case .favoriteClubs, .favoritePlayers:
            return "dans mes favoris"
        }
    }
    
    func label(forDataSet: DataSet) -> String {
        switch forDataSet {
        case .national:
            return "National"
        case .ligue:
            return (ligueName)?.capitalized ?? "Ma ligue"
        case .club:
            return (clubName)?.capitalized ?? "Mon club"
        case .favoriteClubs:
            return "Clubs favoris"
        case .favoritePlayers:
            return "Joueurs favoris"
        }
    }

    func words() -> [String] {
        return searchText.canonicalVersion.trimmed.components(separatedBy: .whitespaces)
    }

    func wordsPredicates() -> NSPredicate? {
        let words = words().filter({ $0.isEmpty == false })
        switch words.count {
        case 2:
            let predicates = [
                NSPredicate(format: "canonicalLastName beginswith[cd] %@ AND canonicalFirstName beginswith[cd] %@", words[0], words[1]),
                NSPredicate(format: "canonicalLastName beginswith[cd] %@ AND canonicalFirstName beginswith[cd] %@", words[1], words[0]),
            ]
            return NSCompoundPredicate(orPredicateWithSubpredicates: predicates)
        default:
            return nil
        }
    }

    func orPredicate() -> NSPredicate? {
        var predicates : [NSPredicate] = []
        let canonicalVersionWithoutPunctuation = searchText.canonicalVersion
        let canonicalVersionWithPunctuation = searchText.canonicalVersionWithPunctuation
        switch tokens.first {
        case .none:
            if canonicalVersionWithoutPunctuation.isEmpty == false {
                let wordsPredicates = wordsPredicates()
                if let wordsPredicates {
                    predicates.append(wordsPredicates)
                } else {
                    predicates.append(NSPredicate(format: "license contains[cd] %@", searchText))
                }
                predicates.append(NSPredicate(format: "canonicalFullName contains[cd] %@", searchText))
            }
        case .ligue:
            if canonicalVersionWithoutPunctuation.isEmpty {
                predicates.append(NSPredicate(format: "ligueName == nil"))
            } else {
                predicates.append(NSPredicate(format: "ligueName contains[cd] %@", canonicalVersionWithoutPunctuation))
            }
        case .club:
            if canonicalVersionWithoutPunctuation.isEmpty {
                predicates.append(NSPredicate(format: "clubName == nil"))
            } else {
                predicates.append(NSPredicate(format: "clubName contains[cd] %@", canonicalVersionWithoutPunctuation))
            }
        case .rankMoreThan:
            if canonicalVersionWithoutPunctuation.isEmpty || Int(canonicalVersionWithoutPunctuation) == 0 {
                predicates.append(NSPredicate(format: "rank == 0"))
            } else {
                predicates.append(NSPredicate(format: "rank >= %@", canonicalVersionWithoutPunctuation))
            }
        case .rankLessThan:
            if canonicalVersionWithoutPunctuation.isEmpty || Int(canonicalVersionWithoutPunctuation) == 0 {
                predicates.append(NSPredicate(format: "rank == 0"))
            } else {
                predicates.append(NSPredicate(format: "rank <= %@", canonicalVersionWithoutPunctuation))
            }
        case .rankBetween:
            let values = canonicalVersionWithPunctuation.components(separatedBy: ",")
            if canonicalVersionWithPunctuation.isEmpty || values.count != 2 {
                predicates.append(NSPredicate(format: "rank == 0"))
            } else {
                predicates.append(NSPredicate(format: "rank BETWEEN {%@,%@}", values.first!, values.last!))
            }
        }
        
        if predicates.isEmpty {
            return nil
        }
        return NSCompoundPredicate(orPredicateWithSubpredicates: predicates)
    }

    func predicate() -> NSPredicate? {
        var predicates : [NSPredicate?] = [
            orPredicate(),
            filterOption == .male ?
                NSPredicate(format: "male == YES") :
                nil,
            filterOption == .female ?
                NSPredicate(format: "male == NO") :
                nil,
        ]
        
        if let mostRecentDate {
            predicates.append(NSPredicate(format: "importDate == %@", mostRecentDate as CVarArg))
        }
        
        if hideAssimilation {
            predicates.append(NSPredicate(format: "assimilation == %@", "Non"))
        }
        
        switch dataSet {
        case .national:
            break
        case .ligue:
            if let ligueName = getLigueName() {
                predicates.append(NSPredicate(format: "ligueName == %@", ligueName))
            } else {
                predicates.append(NSPredicate(format: "ligueName == nil"))
            }
        case .club:
            if let codeClub = getCodeClub() {
                predicates.append(NSPredicate(format: "clubCode == %@", codeClub))
            } else {
                predicates.append(NSPredicate(format: "clubCode == nil"))
            }
        case .favoriteClubs:
            predicates.append(NSPredicate(format: "clubCode IN %@", codeClubs()))
        case .favoritePlayers:
            //todo
            predicates.append(NSPredicate(format: "license == nil"))
        }

        if hidePlayers?.isEmpty == false {
            predicates.append(NSPredicate(format: "NOT (license IN %@)", hidePlayers!))
        }
        
        return NSCompoundPredicate(andPredicateWithSubpredicates: predicates.compactMap({ $0 }))
    }

    
    func sortDescriptors() -> [SortDescriptor<ImportedPlayer>] {
        sortOption.sortDescriptors(ascending, dataSet: dataSet)
    }
    
    func nsSortDescriptors() -> [NSSortDescriptor] {
        sortDescriptors().map { NSSortDescriptor($0) }
    }
}

enum SearchToken: String, CaseIterable, Identifiable {
    case club = "club"
    case ligue = "ligue"
    case rankMoreThan = "rang >"
    case rankLessThan = "rang <"
    case rankBetween = "rang <>"

    var id: String {
        rawValue
    }
    
    var message: String {
        switch self {
        case .club:
            return "Taper le nom d'un club pour y voir tous les joueurs ayant déjà joué un tournoi dans les 12 derniers mois."
        case .ligue:
            return "Taper le nom d'une ligue pour y voir tous les joueurs ayant déjà joué un tournoi dans les 12 derniers mois."
        case .rankMoreThan:
            return "Taper un nombre pour chercher les joueurs ayant un classement supérieur ou égale."
        case .rankLessThan:
            return "Taper un nombre pour chercher les joueurs ayant un classement inférieur ou égale."
        case .rankBetween:
            return "Taper deux nombres séparés par une virgule pour chercher les joueurs dans cette intervalle de classement"
        }
    }
    
    var titleLabel: String {
        switch self {
        case .club:
            return "Chercher un club"
        case .ligue:
            return "Chercher une ligue"
        case .rankMoreThan, .rankLessThan:
            return "Chercher un rang"
        case .rankBetween:
            return "Chercher une intervalle de classement"
        }
    }
    
    func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
        switch self {
        case .club:
            return "Club"
        case .ligue:
            return "Ligue"
        case .rankMoreThan:
            return "Rang supérieur ou égale à"
        case .rankLessThan:
            return "Rang inférieur ou égale à"
        case .rankBetween:
            return "Rang entre deux valeurs"
        }
    }
    
    var shortLocalizedLabel: String {
        switch self {
        case .club:
            return "Club"
        case .ligue:
            return "Ligue"
        case .rankMoreThan:
            return "Rang ≥"
        case .rankLessThan:
            return "Rang ≤"
        case .rankBetween:
            return "Rang ≥,≤"
        }
    }
    
    func icon() -> String {
        switch self {
        case .club:
            return "house.and.flag.fill"
        case .ligue:
            return "house.and.flag.fill"
        case .rankMoreThan:
            return "figure.racquetball"
        case .rankLessThan:
            return "figure.racquetball"
        case .rankBetween:
            return "figure.racquetball"
        }
    }
    
    var systemImage: String {
        switch self {
        case .club:
            return "house.and.flag.fill"
        case .ligue:
            return "house.and.flag.fill"
        case .rankMoreThan:
            return "figure.racquetball"
        case .rankLessThan:
            return "figure.racquetball"
        case .rankBetween:
            return "figure.racquetball"
        }
    }
}

enum DataSet: Int, Identifiable {
    case national
    case ligue
    case club
    case favoriteClubs
    case favoritePlayers
    
    static let allCases : [DataSet] = [.national, .ligue, .club, .favoriteClubs]
    
    var id: Int { rawValue }
    func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
        switch self {
        case .national:
            return "National"
        case .ligue:
            return "Ligue"
        case .club:
            return "Club"
        case .favoritePlayers, .favoriteClubs:
            return "Favori"
        }
    }
    
    var tokens: [SearchToken] {
        switch self {
        case .national:
            return [.club, .ligue, .rankMoreThan, .rankLessThan, .rankBetween]
        case .ligue:
            return [.club, .rankMoreThan, .rankLessThan, .rankBetween]
        case .club:
            return [.rankMoreThan, .rankLessThan, .rankBetween]
        case .favoritePlayers, .favoriteClubs:
            return [.rankMoreThan, .rankLessThan, .rankBetween]
        }
    }
}

enum SortOption: Int, CaseIterable, Identifiable {
    case name
    case rank
    case tournamentCount
    case points
    
    var id: Int { self.rawValue }
    func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
        switch self {
        case .name:
            return "Nom"
        case .rank:
            return "Rang"
        case .tournamentCount:
            return "Tournoi"
        case .points:
            return "Points"
        }
    }
    
    func sortDescriptors(_ ascending: Bool, dataSet: DataSet) -> [SortDescriptor<ImportedPlayer>] {
        switch self {
        case .name:
            return [SortDescriptor(\ImportedPlayer.lastName, order: ascending ? .forward : .reverse), SortDescriptor(\ImportedPlayer.rank), SortDescriptor(\ImportedPlayer.assimilation)]
        case .rank:
            if dataSet == .national {
                return [SortDescriptor(\ImportedPlayer.rank, order: ascending ? .forward : .reverse)]
            } else {
                return [SortDescriptor(\ImportedPlayer.rank, order: ascending ? .forward : .reverse), SortDescriptor(\ImportedPlayer.assimilation), SortDescriptor(\ImportedPlayer.lastName)]
            }
        case .tournamentCount:
            return [SortDescriptor(\ImportedPlayer.tournamentCount, order: ascending ? .forward : .reverse), SortDescriptor(\ImportedPlayer.rank), SortDescriptor(\ImportedPlayer.assimilation), SortDescriptor(\ImportedPlayer.lastName)]
        case .points:
            return [SortDescriptor(\ImportedPlayer.points, order: ascending ? .forward : .reverse), SortDescriptor(\ImportedPlayer.rank), SortDescriptor(\ImportedPlayer.assimilation), SortDescriptor(\ImportedPlayer.lastName)]
        }
    }
}

enum PlayerFilterOption: Int, Hashable, CaseIterable, Identifiable {
    case all = -1
    case male = 1
    case female = 0
    
    var id: Int { rawValue }

    func icon() -> String {
        switch self {
            case .all:
                return "Tous"
            case .male:
               return "Homme"
            case .female:
               return "Femme"
        }
    }
    
    var localizedPlayerLabel: String {
        switch self {
        case .female:
            return "joueuse"
        default:
            return "joueur"
        }
    }

}
