//
//  InscriptionManagerView.swift
//  PadelClub
//
//  Created by Razmig Sarkissian on 29/02/2024.
//

import SwiftUI
import TipKit
import LeStorage

let slideToDeleteTip = SlideToDeleteTip()
let inscriptionManagerWomanRankTip = InscriptionManagerWomanRankTip()
let fileTip = InscriptionManagerFileInputTip()
let pasteTip = InscriptionManagerPasteInputTip()
let searchTip = InscriptionManagerSearchInputTip()
let createTip = InscriptionManagerCreateInputTip()
let rankUpdateTip = InscriptionManagerRankUpdateTip()
let padelBeachExportTip = PadelBeachExportTip()
let padelBeachImportTip = PadelBeachImportTip()

struct InscriptionManagerView: View {
    @EnvironmentObject var dataStore: DataStore
    @EnvironmentObject var networkMonitor: NetworkMonitor
    @Environment(\.dismiss) var dismiss

    @FetchRequest(
        sortDescriptors: [],
        animation: .default)
    private var fetchPlayers: FetchedResults<ImportedPlayer>

    var tournament: Tournament
    var cancelShouldDismiss: Bool = false

    @State private var searchField: String = ""
    @State private var presentSearch: Bool = false
    @State private var presentPlayerSearch: Bool = false
    @State private var presentPlayerCreation: Bool = false
    @State private var presentImportView: Bool = false
    @State private var isLearningMore: Bool = false
    @State private var createdPlayers: Set<PlayerRegistration> = Set()
    @State private var createdPlayerIds: Set<String> = Set()
    @State private var editedTeam: TeamRegistration?
    @State private var pasteString: String?
    @State private var currentRankSourceDate: Date?
    @State private var confirmUpdateRank = false
    @State private var selectionSearchField: String?
    @State private var autoSelect: Bool = false
    @State private var teamsHash: Int?
    @State private var presentationCount: Int = 0
    @State private var filterMode: FilterMode = .all
    @State private var sortingMode: SortingMode = .teamWeight
    @State private var byDecreasingOrdering: Bool = false
    @State private var contactType: ContactType? = nil
    @State private var sentError: ContactManagerError? = nil
    @State private var showSubscriptionView: Bool = false
    @State private var registrationIssues: Int? = nil
    @State private var sortedTeams: [TeamRegistration] = []
    @State private var unfilteredTeams: [TeamRegistration] = []
    @State private var walkoutTeams: [TeamRegistration] = []
    @State private var unsortedTeamsWithoutWO: [TeamRegistration] = []
    @State private var unsortedPlayers: [PlayerRegistration] = []
    @State private var teamPaste: URL?
    @State private var confirmDuplicate: Bool = false
    
    var messageSentFailed: Binding<Bool> {
        Binding {
            sentError != nil
        } set: { newValue in
            if newValue == false {
                sentError = nil
            }
        }
    }


    enum SortingMode: Int, Identifiable, CaseIterable {
        var id: Int { self.rawValue }
        case registrationDate
        case teamWeight
        
        func localizedLabel() -> String {
            switch self {
            case .registrationDate:
                return "Date d'inscription"
            case .teamWeight:
                return "Poids d'équipe"
            }
        }
    }

    enum FilterMode: Int, Identifiable, CaseIterable {
        var id: Int { self.rawValue }
        case all
        case walkOut
        
        func localizedLabel() -> String {
            switch self {
            case .all:
                return "Toutes les équipes"
            case .walkOut:
                return "Voir les WOs"
            }
        }
    }

    init(tournament: Tournament, pasteString: String? = nil) {
        self.tournament = tournament
        if let pasteString {
            _pasteString = .init(wrappedValue: pasteString)
            _fetchPlayers = FetchRequest<ImportedPlayer>(sortDescriptors: [NSSortDescriptor(keyPath: \ImportedPlayer.rank, ascending: true)], predicate: Self._pastePredicate(pasteField: pasteString, mostRecentDate: tournament.rankSourceDate, filterOption: tournament.tournamentCategory.playerFilterOption))
            _autoSelect = .init(wrappedValue: true)
            cancelShouldDismiss = true
        }
        _currentRankSourceDate = State(wrappedValue: tournament.rankSourceDate)
    }
    
    private func _clearScreen() {
        teamPaste = nil
        unsortedPlayers.removeAll()
        unfilteredTeams.removeAll()
        walkoutTeams.removeAll()
        unsortedTeamsWithoutWO.removeAll()
        sortedTeams.removeAll()
        registrationIssues = nil
    }
    
    // Function to create a simple hash from a list of IDs
    private func _simpleHash(ids: [String]) -> Int {
        // Combine the hash values of each string
        return ids.reduce(0) { $0 ^ $1.hashValue }
    }

    // Function to check if two lists of IDs produce different hashes
    private func _areDifferent(ids1: [String], ids2: [String]) -> Bool {
        return _simpleHash(ids: ids1) != _simpleHash(ids: ids2)
    }

    private func _setHash() async {
        #if DEBUG_TIME
        let start = Date()
        defer {
            let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
            print("func _setHash", duration.formatted(.units(allowed: [.seconds, .milliseconds])))
        }
        #endif
        let selectedSortedTeams = tournament.selectedSortedTeams()
        if self.teamsHash == nil, selectedSortedTeams.isEmpty == false {
            self.teamsHash = _simpleHash(ids: selectedSortedTeams.map { $0.id })
        }
    }
    
    private func _handleHashDiff() async {
        let selectedSortedTeams = tournament.selectedSortedTeams()
        let newHash = _simpleHash(ids: selectedSortedTeams.map { $0.id })
        if (self.teamsHash != nil && newHash != teamsHash!) || (self.teamsHash == nil && selectedSortedTeams.isEmpty == false) {
            self.teamsHash = newHash
            if self.tournament.shouldVerifyBracket == false || self.tournament.shouldVerifyGroupStage == false {
                self.tournament.shouldVerifyBracket = true
                self.tournament.shouldVerifyGroupStage = true
                
                let waitingList = self.tournament.waitingListTeams(in: selectedSortedTeams)
                waitingList.forEach { team in
                    if team.bracketPosition != nil || team.groupStagePosition != nil {
                        team.resetPositions()
                    }
                }
                                    
                do {
                    try dataStore.teamRegistrations.addOrUpdate(contentOfs: waitingList)
                    try dataStore.tournaments.addOrUpdate(instance: tournament)
                } catch {
                    Logger.error(error)
                }
            }
        }
    }
    
    var body: some View {
        VStack(spacing: 0) {
            _managementView()
            if _isEditingTeam() {
                _buildingTeamView()
            } else if unfilteredTeams.isEmpty {
                _inscriptionTipsView()
            } else {
                _teamRegisteredView()
            }
        }
        .onAppear {
            _getTeams()
        }
        .onDisappear {
            Task {
                await _handleHashDiff()
                
            }
        }
        .alert("Cette équipe existe déjà", isPresented: $confirmDuplicate) {
            Button("Créer l'équipe quand même") {
                _createTeam(checkDuplicates: false)
            }
            
            Button("Annuler", role: .cancel) {
                pasteString = nil
                editedTeam = nil
                createdPlayers.removeAll()
                createdPlayerIds.removeAll()
            }

        } message: {
            Text("Cette équipe existe déjà dans votre liste d'inscription.")
        }
        .alert("Un problème est survenu", isPresented: messageSentFailed) {
            Button("OK") {
            }
        } message: {
            Text(_getErrorMessage())
        }
        .sheet(item: $contactType) { contactType in
            Group {
                switch contactType {
                case .message(_, let recipients, let body, _):
                    if Guard.main.paymentForNewTournament() != nil {
                        MessageComposeView(recipients: recipients, body: body) { result in
                            switch result {
                            case .cancelled:
                                break
                            case .failed:
                                self.sentError = .messageFailed
                            case .sent:
                                if networkMonitor.connected == false {
                                    self.sentError = .messageNotSent
                                }
                            @unknown default:
                                break
                            }
                        }
                    } else {
                        SubscriptionView(isPresented: self.$showSubscriptionView, showLackOfPlanMessage: true)
                    }
                case .mail(_, let recipients, let bccRecipients, let body, let subject, _):
                    if Guard.main.paymentForNewTournament() != nil {
                        MailComposeView(recipients: recipients, bccRecipients: bccRecipients, body: body, subject: subject) { result in
                            switch result {
                            case .cancelled, .saved:
                                self.contactType = nil
                            case .failed:
                                self.contactType = nil
                                self.sentError = .mailFailed
                            case .sent:
                                if networkMonitor.connected == false {
                                    self.contactType = nil
                                    self.sentError = .mailNotSent
                                }
                            @unknown default:
                                break
                            }
                        }
                    } else {
                        SubscriptionView(isPresented: self.$showSubscriptionView, showLackOfPlanMessage: true)
                    }
                }
            }
            .tint(.master)
        }

        .sheet(isPresented: $isLearningMore) {
            LearnMoreSheetView(tournament: tournament)
                .tint(.master)
        }
        .sheet(isPresented: $presentPlayerSearch, onDismiss: {
            selectionSearchField = nil
        }) {
            NavigationStack {
                SelectablePlayerListView(allowSelection: -1, filterOption: _filterOption(), showFemaleInMaleAssimilation: tournament.tournamentCategory.showFemaleInMaleAssimilation) { players in
                    selectionSearchField = nil
                    players.forEach { player in
                        let newPlayer = PlayerRegistration(importedPlayer: player)
                        newPlayer.setComputedRank(in: tournament)
                        createdPlayers.insert(newPlayer)
                        createdPlayerIds.insert(newPlayer.id)
                    }
                } contentUnavailableAction: { searchViewModel in
                    selectionSearchField = searchViewModel.searchText
                    presentPlayerSearch = false
                    presentPlayerCreation = true
                }
            }
            .tint(.master)
        }
        .sheet(isPresented: $presentPlayerCreation) {
            PlayerPopoverView(source: _searchSource(), sex: _addPlayerSex()) { p in
                p.setComputedRank(in: tournament)
                createdPlayers.insert(p)
                createdPlayerIds.insert(p.id)
            }
            .tint(.master)
        }
        .sheet(isPresented: $presentImportView, onDismiss: {
            _getTeams()
        }) {
            NavigationStack {
                FileImportView()
            }
            .tint(.master)
        }
        .onChange(of: tournament.prioritizeClubMembers) {
            _clearScreen()
            _save()
            _getTeams()
        }
        .onChange(of: tournament.teamSorting) {
            _clearScreen()
            _save()
            _getTeams()
        }
        .onChange(of: currentRankSourceDate) {
            if let currentRankSourceDate, tournament.rankSourceDate != currentRankSourceDate {
                confirmUpdateRank = true
            }
        }
        .sheet(isPresented: $confirmUpdateRank, onDismiss: {
            currentRankSourceDate = tournament.rankSourceDate
        }) {
            UpdateSourceRankDateView(currentRankSourceDate: $currentRankSourceDate, confirmUpdateRank: $confirmUpdateRank, tournament: tournament)
                .tint(.master)
        }
        .toolbar {
            if _isEditingTeam() {
                ToolbarItem(placement: .cancellationAction) {
                    Button("Annuler", role: .cancel) {
                        pasteString = nil
                        editedTeam = nil
                        createdPlayers.removeAll()
                        createdPlayerIds.removeAll()
                        if cancelShouldDismiss {
                            dismiss()
                        }
                    }
                }
            } else {
                ToolbarItemGroup(placement: .navigationBarTrailing) {
                    Menu {
                        Picker(selection: $filterMode) {
                            ForEach(FilterMode.allCases) {
                                Text($0.localizedLabel()).tag($0)
                            }
                        } label: {
                        }
                        Picker(selection: $sortingMode) {
                            ForEach(SortingMode.allCases) {
                                Text($0.localizedLabel()).tag($0)
                            }
                        } label: {
                        }
                        
                        Picker(selection: $byDecreasingOrdering) {
                            Text("Croissant").tag(false)
                            Text("Décroissant").tag(true)
                        } label: {
                        }
                    } label: {
                        LabelFilter()
                    }
                    Menu {
                        if tournament.inscriptionClosed() == false {
                            Menu {
                                _sortingTypePickerView()
                            } label: {
                                Text("Méthode de sélection")
                                Text(tournament.teamSorting.localizedLabel())
                            }
                            Divider()
                            rankingDateSourcePickerView(showDateInLabel: true)
                            if tournament.teamSorting == .inscriptionDate {
                                Divider()
                                //_prioritizeClubMembersButton()

                                Button("Bloquer une place") {
                                    _createTeam(checkDuplicates: false)
                                }
                            }
                            Divider()
                            Button {
                                tournament.lockRegistration()
                                _save()
                            } label: {
                                Label("Clôturer", systemImage: "lock")
                            }
                            Divider()
                            if let teamPaste {
                                ShareLink(item: teamPaste) {
                                    Label("Exporter les paires", systemImage: "square.and.arrow.up")
                                }
                            }
                            Button {
                                presentImportView = true
                            } label: {
                                Label("Importer beach-padel", systemImage: "square.and.arrow.down")
                            }
                            Link(destination: URLs.beachPadel.url) {
                                Label("beach-padel.app.fft.fr", systemImage: "safari")
                            }
                        } else {
                            Button {
                                tournament.closedRegistrationDate = nil
                                _save()
                            } label: {
                                Label("Ré-ouvrir", systemImage: "lock.open")
                            }
                        }
                    } label: {
                        if tournament.inscriptionClosed() == false {
                            LabelOptions()
                        } else {
                            Label("Clôturer", systemImage: "lock")
                        }
                    }
                }
            }
        }
        .navigationBarBackButtonHidden(_isEditingTeam())
        .toolbarBackground(.visible, for: .navigationBar)
        .navigationTitle("Inscriptions")
        .navigationBarTitleDisplayMode(.inline)
    }
    
    private func _isEditingTeam() -> Bool {
        createdPlayerIds.isEmpty == false || editedTeam != nil || pasteString != nil
    }
    
    private func _prepareStats() async {
        #if DEBUG_TIME
        let start = Date()
        defer {
            let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
            print("func _prepareStats", duration.formatted(.units(allowed: [.seconds, .milliseconds])))
        }
        #endif

        unsortedPlayers = tournament.unsortedPlayers()
        walkoutTeams = tournament.walkoutTeams()
        unsortedTeamsWithoutWO = tournament.unsortedTeamsWithoutWO()
        teamPaste = tournament.pasteDataForImporting().createTxtFile(self.tournament.tournamentTitle(.short))
    }
    
    private func _prepareTeams() {
        #if DEBUG_TIME
        let start = Date()
        defer {
            let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
            print("func _prepareTeams", duration.formatted(.units(allowed: [.seconds, .milliseconds])))
        }
        #endif
        sortedTeams = tournament.sortedTeams()

        var teams = sortedTeams
        if filterMode == .walkOut {
            teams = teams.filter({ $0.walkOut })
        }
        
        if sortingMode == .registrationDate {
            teams = teams.sorted(by: \.computedRegistrationDate)
        }
        
        if byDecreasingOrdering {
            self.unfilteredTeams = teams.reversed()
        } else {
            self.unfilteredTeams = teams
        }
    }
    
    private func _getIssues() async {
        #if DEBUG_TIME
        let start = Date()
        defer {
            let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
            print("func _getIssues", duration.formatted(.units(allowed: [.seconds, .milliseconds])))
        }
        #endif
        await registrationIssues = tournament.registrationIssues()
    }
    
    private func _getTeams() {
        _prepareTeams()
        Task {
            await _prepareStats()
            await _getIssues()
            await _setHash()
        }
    }
    
    private func _teamRegisteredView() -> some View {
        List {
            if presentSearch == false {
                _rankHandlerView()
                _relatedTips()
                _informationView(count: unfilteredTeams.count)
            }
            
            let teams = searchField.isEmpty ? unfilteredTeams : unfilteredTeams.filter({ $0.contains(searchField.canonicalVersion) })
            
            if teams.isEmpty && searchField.isEmpty == false {
                ContentUnavailableView {
                    Label("Aucun résultat", systemImage: "person.2.slash")
                } description: {
                    Text("\(searchField) est introuvable dans les équipes inscrites.")
                } actions: {
                    RowButtonView("Modifier la recherche") {
                        searchField = ""
                        presentSearch = true
                    }
                    
                    RowButtonView("Créer une équipe") {
                        Task {
                            await MainActor.run() {
                                fetchPlayers.nsPredicate = Self._pastePredicate(pasteField: searchField, mostRecentDate: SourceFileManager.shared.mostRecentDateAvailable, filterOption: _filterOption())
                                fetchPlayers.nsSortDescriptors = [NSSortDescriptor(keyPath: \ImportedPlayer.rank, ascending: true)]
                                pasteString = searchField
                            }
                        }
                    }
                    
                    RowButtonView("D'accord") {
                        searchField = ""
                        presentSearch = false
                    }
                }
            }
            
            ForEach(teams) { team in
                let teamIndex = team.index(in: sortedTeams)
                Section {
                    TeamDetailView(team: team)
                } header: {
                    TeamHeaderView(team: team, teamIndex: teamIndex, tournament: tournament)
                } footer: {
                    _teamFooterView(team)
                }
                .headerProminence(.increased)
            }
        }
        .searchable(text: $searchField, isPresented: $presentSearch, prompt: Text("Chercher parmi les équipes inscrites"))
        .keyboardType(.alphabet)
        .autocorrectionDisabled()
    }

    @MainActor
    private func _managementView() -> some View {
        HStack {
            Button {
                presentPlayerCreation = true
            } label: {
                HStack(spacing: 4) {
                    Image(systemName: "person.fill.badge.plus")
                        .resizable()
                        .scaledToFit()
                        .frame(width: 20)
                    Text("Créer")
                        .font(.headline)
                }
                .frame(maxWidth: .infinity)
            }

            PasteButton(payloadType: String.self) { strings in
                guard let first = strings.first else { return }
                Task {
                    await MainActor.run {
                        fetchPlayers.nsPredicate = Self._pastePredicate(pasteField: first, mostRecentDate: SourceFileManager.shared.mostRecentDateAvailable, filterOption: _filterOption())
                        fetchPlayers.nsSortDescriptors = [NSSortDescriptor(keyPath: \ImportedPlayer.rank, ascending: true)]
                        pasteString = first
                        autoSelect = true
                    }
                }
            }

            Button {
                presentPlayerSearch = true
            } label: {
                HStack(spacing: 4) {
                    Image(systemName: "person.fill.viewfinder")
                        .resizable()
                        .scaledToFit()
                        .frame(width: 20)
                    Text("FFT")
                        .font(.headline)
                }
                .frame(maxWidth: .infinity)
            }
        }
        .buttonStyle(.borderedProminent)
        .tint(.logoBackground)
        .fixedSize(horizontal: false, vertical: true)
        .padding(16)
    }
    
    
    @ViewBuilder
    func rankingDateSourcePickerView(showDateInLabel: Bool) -> some View {
        Section {
            let dates = Set(SourceFileManager.shared.allFilesSortedByDate(true).map({ $0.dateFromPath })).sorted().reversed()
            
            Picker(selection: $currentRankSourceDate) {
                if currentRankSourceDate == nil {
                    Text("inconnu").tag(nil as Date?)
                }
                ForEach(dates, id: \.self) { date in
                    Text(date.monthYearFormatted).tag(date as Date?)
                }
            } label: {
                Text("Classement utilisé")
                if showDateInLabel {
                    if let currentRankSourceDate {
                        Text(currentRankSourceDate.monthYearFormatted)
                    } else {
                        Text("Choisir le mois")
                    }
                }
            }
            .pickerStyle(.menu)
        }

    }

    private func _addPlayerSex() -> Int {
        switch tournament.tournamentCategory {
        case .men, .unlisted:
            return 1
        case .women:
            return 0
        case .mix:
            return 1
        }

    }
    
    private func _filterOption() -> PlayerFilterOption {
        return tournament.tournamentCategory.playerFilterOption
    }
    
    @ViewBuilder
    private func _inscriptionTipsView() -> some View {
        List {
            Section {
                
                TipView(fileTip) { action in
                    if action.id == "website" {
                        UIApplication.shared.open(URLs.beachPadel.url)
                    } else if action.id == "add-team-file" {
                        presentImportView = true
                    }
                }
                .tipStyle(tint: nil)
            }
            
            Section {
                
                TipView(pasteTip) { action in
                    if let paste = UIPasteboard.general.string {
                        Task {
                            await MainActor.run {
                                fetchPlayers.nsPredicate = Self._pastePredicate(pasteField: paste, mostRecentDate: SourceFileManager.shared.mostRecentDateAvailable, filterOption: _filterOption())
                                fetchPlayers.nsSortDescriptors = [NSSortDescriptor(keyPath: \ImportedPlayer.rank, ascending: true)]
                                pasteString = paste
                                autoSelect = true
                            }
                        }
                    }
                }
                .tipStyle(tint: nil)
            }
            
            Section {
                
                TipView(searchTip) { action in
                    presentPlayerSearch = true
                }
                .tipStyle(tint: nil)
            }
            
            Section {
                
                TipView(createTip) { action in
                    presentPlayerCreation = true
                }
                .tipStyle(tint: nil)
            }
            
            Section {
                ContentUnavailableView("Aucune équipe", systemImage: "person.2.slash", description: Text("Vous n'avez encore aucune équipe dans votre liste d'attente."))
            }
            
            _rankHandlerView()
        }
    }
    
    @ViewBuilder
    private func _rankHandlerView() -> some View {
        if let mostRecentDate = SourceFileManager.shared.lastDataSourceDate(), let currentRankSourceDate, currentRankSourceDate < mostRecentDate, tournament.hasEnded() == false {
            Section {
                TipView(rankUpdateTip) { action in
                    self.currentRankSourceDate = mostRecentDate
                }
                .tipStyle(tint: nil)
            }
            rankingDateSourcePickerView(showDateInLabel: false)
        } else if tournament.rankSourceDate == nil {
            rankingDateSourcePickerView(showDateInLabel: false)
        }
    }
    
    private func _informationView(count: Int) -> some View {
        Section {
            LabeledContent {
                Text(unsortedTeamsWithoutWO.count.formatted() + "/" + tournament.teamCount.formatted()).font(.largeTitle)
            } label: {
                Text("Paire\(unsortedTeamsWithoutWO.count.pluralSuffix) inscrite\(unsortedTeamsWithoutWO.count.pluralSuffix)")
            }
            
            LabeledContent {
                Text(walkoutTeams.count.formatted()).font(.largeTitle)
            } label: {
                Text("Forfait\(walkoutTeams.count.pluralSuffix)")
            }
            
            LabeledContent {
                Text(max(0, unsortedTeamsWithoutWO.count - tournament.teamCount).formatted()).font(.largeTitle)
            } label: {
                Text("Attente")
            }

            NavigationLink {
                InscriptionInfoView()
                    .environment(tournament)
            } label: {
                LabeledContent {
                    if let registrationIssues {
                        Text(registrationIssues.formatted()).font(.largeTitle)
                    } else {
                        ProgressView()
                    }
                } label: {
                    Text("Problèmes détéctés")
                    if let closedRegistrationDate = tournament.closedRegistrationDate {
                        Text("clôturé le " + closedRegistrationDate.formatted())
                    }
                }
            }
        }
    }
    
    @ViewBuilder
    private func _relatedTips() -> some View {
//        if pasteString == nil
//            && createdPlayerIds.isEmpty
//            && tournament.unsortedTeams().count >= tournament.teamCount
//            && tournament.unsortedPlayers().filter({ $0.source == .beachPadel }).isEmpty {
//            Section {
//                TipView(padelBeachExportTip) { action in
//                    if action.id == "more-info-export" {
//                        isLearningMore = true
//                    }
//                    if action.id == "padel-beach" {
//                        UIApplication.shared.open(URLs.beachPadel.url)
//                    }
//                }
//                .tipStyle(tint: nil)
//            }
//            Section {
//                TipView(padelBeachImportTip) { action in
//                    if action.id == "more-info-import" {
//                        presentImportView = true
//                    }
//                }
//                .tipStyle(tint: nil)
//            }
//        }
//        
        
        if tournament.tournamentCategory == .men && unsortedPlayers.filter({ $0.isMalePlayer() == false }).isEmpty == false {
            Section {
                TipView(inscriptionManagerWomanRankTip)
                    .tipStyle(tint: nil)
            }
        }
//        
//        Section {
//            TipView(slideToDeleteTip)
//                .tipStyle(tint: nil)
//        }
    }
    
    private func _searchSource() -> String? {
        selectionSearchField ?? pasteString
    }
    
    static private func _pastePredicate(pasteField: String, mostRecentDate: Date?, filterOption: PlayerFilterOption) -> NSPredicate? {
        let text = pasteField.canonicalVersion

        let textStrings: [String] = text.components(separatedBy: .whitespacesAndNewlines)
        let nonEmptyStrings: [String] = textStrings.compactMap { $0.isEmpty ? nil : $0 }
        let nameComponents = nonEmptyStrings.filter({ $0 != "de" && $0 != "la" && $0 != "le" && $0.count > 1 })
        var andPredicates = [NSPredicate]()
        var orPredicates = [NSPredicate]()
        //self.wordsCount = nameComponents.count

        
        if filterOption == .male {
            andPredicates.append(NSPredicate(format: "male == YES"))
        } else if filterOption == .female {
            andPredicates.append(NSPredicate(format: "male == NO"))
        }
        
        if let mostRecentDate {
            andPredicates.append(NSPredicate(format: "importDate == %@", mostRecentDate as CVarArg))
        }

        if nameComponents.count > 1 {
            orPredicates = nameComponents.pairs().map {
                return NSPredicate(format: "(firstName contains[cd] %@ AND lastName contains[cd] %@) OR (firstName contains[cd] %@ AND lastName contains[cd] %@)", $0, $1, $1, $0) }
        } else {
            orPredicates = nameComponents.map { NSPredicate(format: "firstName contains[cd] %@ OR lastName contains[cd] %@", $0,$0) }
        }
        
        let matches = text.licencesFound()
        let licensesPredicates = matches.map { NSPredicate(format: "license contains[cd] %@", $0) }
        orPredicates = orPredicates + licensesPredicates
        
        var predicate = NSCompoundPredicate(andPredicateWithSubpredicates: andPredicates)
        
        if orPredicates.isEmpty == false {
            predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [predicate, NSCompoundPredicate(orPredicateWithSubpredicates: orPredicates)])
        }
        
        return predicate
    }
    
    private func _currentSelection() -> Set<PlayerRegistration> {
        var currentSelection = Set<PlayerRegistration>()
        createdPlayerIds.compactMap { id in
            fetchPlayers.first(where: { id == $0.license })
        }.forEach { player in
            let player = PlayerRegistration(importedPlayer: player)
            player.setComputedRank(in: tournament)
            currentSelection.insert(player)
        }
        
        createdPlayerIds.compactMap { id in
            createdPlayers.first(where: { id == $0.id })
        }.forEach {
            currentSelection.insert($0)
        }
        return currentSelection
    }

    private func _currentSelectionIds() -> [String?] {
        var currentSelection = [String?]()
        createdPlayerIds.compactMap { id in
            fetchPlayers.first(where: { id == $0.license })
        }.forEach { player in
            currentSelection.append(player.license)
        }
        
        createdPlayerIds.compactMap { id in
            createdPlayers.first(where: { id == $0.id })
        }.forEach {
            currentSelection.append($0.licenceId)
        }
        return currentSelection
    }
    
    private func _isDuplicate() -> Bool {
        let ids : [String?] = _currentSelectionIds()
        if unfilteredTeams.anySatisfy({ $0.containsExactlyPlayerLicenses(ids) }) {
            return true
        }
        return false
    }
    
    private func _createTeam(checkDuplicates: Bool) {
        if checkDuplicates && _isDuplicate() {
            confirmDuplicate = true
            return
        }

        let players = _currentSelection()
        let team = tournament.addTeam(players)
        do {
            try dataStore.teamRegistrations.addOrUpdate(instance: team)
        } catch {
            Logger.error(error)
        }
        do {
            try dataStore.playerRegistrations.addOrUpdate(contentOfs: players)
        } catch {
            Logger.error(error)
        }

        createdPlayers.removeAll()
        createdPlayerIds.removeAll()
        pasteString = nil
        
        _clearScreen()
        _getTeams()
    }
    
    private func _updateTeam(checkDuplicates: Bool) {
        guard let editedTeam else { return }
        if checkDuplicates && _isDuplicate() {
            confirmDuplicate = true
            return
        }

        let players = _currentSelection()
        editedTeam.updatePlayers(players, inTournamentCategory: tournament.tournamentCategory)
        do {
            try dataStore.teamRegistrations.addOrUpdate(instance: editedTeam)
        } catch {
            Logger.error(error)
        }
        do {
            try dataStore.playerRegistrations.addOrUpdate(contentOfs: players)
        } catch {
            Logger.error(error)
        }
        createdPlayers.removeAll()
        createdPlayerIds.removeAll()
        pasteString = nil
        self.editedTeam = nil
        _clearScreen()
        _getTeams()
    }
    
    private func _buildingTeamView() -> some View {
        List(selection: $createdPlayerIds) {
            if let pasteString {
                
                Section {
                    Text(pasteString)
                } footer: {
                    HStack {
                        Text("contenu du presse-papier")
                        Spacer()
                        Button("effacer", role: .destructive) {
                            self.pasteString = nil
                            self.createdPlayers.removeAll()
                            self.createdPlayerIds.removeAll()
                        }
                        .buttonStyle(.borderless)
                    }
                }
            }
            
            Section {
                ForEach(createdPlayerIds.sorted(), id: \.self) { id in
                    if let p = createdPlayers.first(where: { $0.id == id }) {
                        VStack(alignment: .leading, spacing: 0) {
                            if let player = unsortedPlayers.first(where: { $0.licenceId == p.licenceId }), editedTeam?.includes(player: player) == false {
                                Text("Déjà inscrit !").foregroundStyle(.logoRed).bold()
                            }
                            PlayerView(player: p).tag(p.id)
                        }
                    }
                    if let p = fetchPlayers.first(where: { $0.license == id }) {
                        VStack(alignment: .leading, spacing: 0) {
                            if unsortedPlayers.first(where: { $0.licenceId == p.license }) != nil {
                                Text("Déjà inscrit !").foregroundStyle(.logoRed).bold()
                            }
                            ImportedPlayerView(player: p).tag(p.license!)
                        }
                    }
                }
            }

            if editedTeam == nil {
                if createdPlayerIds.isEmpty {
                    RowButtonView("Bloquer une place") {
                        _createTeam(checkDuplicates: false)
                    }
                } else {
                    RowButtonView("Ajouter l'équipe") {
                        _createTeam(checkDuplicates: true)
                    }
                }
            } else {
                RowButtonView("Modifier l'équipe") {
                    _updateTeam(checkDuplicates: false)
                }
            }
            
            if let pasteString {
                if fetchPlayers.isEmpty {
                    ContentUnavailableView {
                        Label("Aucun résultat", systemImage: "person.2.slash")
                    } description: {
                        Text("Aucun joueur classé n'a été trouvé dans ce message.")
                    } actions: {
                        RowButtonView("Créer un joueur non classé") {
                            presentPlayerCreation = true
                        }
                                                
                        RowButtonView("Effacer cette recherche") {
                            self.pasteString = nil
                        }
                    }

                } else {
                    Section {
                        ForEach(fetchPlayers.sorted(by: { $0.hitForSearch(pasteString) > $1.hitForSearch(pasteString) })) { player in
                            ImportedPlayerView(player: player).tag(player.license!)
                        }
                    } header: {
                        Text(fetchPlayers.count.formatted() + " résultat" + fetchPlayers.count.pluralSuffix)
                    }
                }
            }
        }
        .headerProminence(.increased)
        .onReceive(fetchPlayers.publisher.count()) { _ in  // <-- here
            if let pasteString, count == 2, autoSelect == true {
                fetchPlayers.filter { $0.hitForSearch(pasteString) >= hitTarget }.sorted(by: { $0.hitForSearch(pasteString) > $1.hitForSearch(pasteString) }).forEach { player in
                    createdPlayerIds.insert(player.license!)
                }
                autoSelect = false
            }
        }
        .environment(\.editMode, Binding.constant(EditMode.active))
    }
    
    private var count: Int {
        return fetchPlayers.filter { $0.hitForSearch(pasteString ?? "") >= hitTarget }.count
    }

    private var hitTarget: Int {
        if (pasteString?.matches(of: /[1-9][0-9]{5,7}/).count ?? 0) > 1 {
            if fetchPlayers.filter({ $0.hitForSearch(pasteString ?? "") == 100 }).count == 2 { return 100 }
        } else {
            return 2
        }
        return 1
    }

    @ViewBuilder
    private func _sortingTypePickerView() -> some View {
        @Bindable var tournament = tournament
        Picker(selection: $tournament.teamSorting) {
            ForEach(TeamSortingType.allCases) {
                Text($0.localizedLabel()).tag($0)
            }
        } label: {
        }
    }
    
    @ViewBuilder
    private func _prioritizeClubMembersButton() -> some View {
        @Bindable var tournament = tournament
        if let federalClub = tournament.club() {
            Menu {
                Picker(selection: $tournament.prioritizeClubMembers) {
                    Text("Oui").tag(true)
                    Text("Non").tag(false)
                } label: {
                    
                }
                .labelsHidden()
                
                Divider()
                NavigationLink {
                    ClubsView() { club in
                        if let event = tournament.eventObject() {
                            event.club = club.id
                            do {
                                try dataStore.events.addOrUpdate(instance: event)
                            } catch {
                                Logger.error(error)
                            }
                        }
                        _save()
                    }
                } label: {
                    Text("Changer de club")
                }
            } label: {
                Text("Membres prioritaires")
                Text(federalClub.acronym)
            }
            Divider()
        } else {
            NavigationLink {
                ClubsView() { club in
                    if let event = tournament.eventObject() {
                        event.club = club.id
                        do {
                            try dataStore.events.addOrUpdate(instance: event)
                        } catch {
                            Logger.error(error)
                        }
                    }
                    _save()
                }
            } label: {
                Text("Identifier le club")
            }
            Divider()
        }
    }
    
    private func _teamFooterView(_ team: TeamRegistration) -> some View {
        HStack {
            if let formattedRegistrationDate = team.formattedInscriptionDate() {
                Text(formattedRegistrationDate)
            }
            Spacer()
            _teamMenuOptionView(team)
        }
    }
    private func _teamMenuOptionView(_ team: TeamRegistration) -> some View {
        Menu {
            Section {
                MenuWarningView(teams: [team], contactType: $contactType)
                //Divider()
                Button("Copier") {
                    let pasteboard = UIPasteboard.general
                    pasteboard.string = team.playersPasteData()
                }
                //Divider()
                Button("Changer les joueurs") {
                    editedTeam = team
                    team.unsortedPlayers().forEach { player in
                        createdPlayers.insert(player)
                        createdPlayerIds.insert(player.id)
                    }
                }
                Divider()
                NavigationLink {
                    EditingTeamView(team: team)
                        .environment(tournament)
                } label: {
                    Text("Éditer une donnée de l'équipe")
                }
                Divider()
                Toggle(isOn: .init(get: {
                    return team.wildCardBracket
                }, set: { value in
                    _clearScreen()

                    Task {
                        team.resetPositions()
                        team.wildCardGroupStage = false
                        team.walkOut = false
                        team.wildCardBracket = value
                        do {
                            try dataStore.teamRegistrations.addOrUpdate(instance: team)
                        } catch {
                            Logger.error(error)
                        }
                        _getTeams()
                    }
                })) {
                    Label("Wildcard Tableau", systemImage: team.wildCardBracket ? "circle.inset.filled" : "circle")
                }

                Toggle(isOn: .init(get: {
                    return team.wildCardGroupStage
                }, set: { value in
                    _clearScreen()
                    
                    Task {
                        team.resetPositions()
                        team.wildCardBracket = false
                        team.walkOut = false
                        team.wildCardGroupStage = value
                        do {
                            try dataStore.teamRegistrations.addOrUpdate(instance: team)
                        } catch {
                            Logger.error(error)
                        }
                        _getTeams()
                    }
                })) {
                    Label("Wildcard Poule", systemImage: team.wildCardGroupStage ? "circle.inset.filled" : "circle")
                }
                
                Divider()
                Toggle(isOn: .init(get: {
                    return team.walkOut
                }, set: { value in
                    _clearScreen()
                    Task {
                        team.resetPositions()
                        team.wildCardBracket = false
                        team.wildCardGroupStage = false
                        team.walkOut = value
                        do {
                            try dataStore.teamRegistrations.addOrUpdate(instance: team)
                        } catch {
                            Logger.error(error)
                        }
                        _getTeams()
                    }
                })) {
                    Label("WO", systemImage: team.walkOut ? "circle.inset.filled" : "circle")
                }
                Divider()
                Button(role: .destructive) {
                    _clearScreen()
                    Task {
                        do {
                            try dataStore.teamRegistrations.delete(instance: team)
                        } catch {
                            Logger.error(error)
                        }
                        _getTeams()
                    }
                } label: {
                    LabelDelete()
                }
                
//            } header: {
//                Text(team.teamLabel(.short))
            }
        } label: {
            LabelOptions().labelStyle(.titleOnly)
        }
    }
    
    private func _getErrorMessage() -> String {
        let m1 : String? = (networkMonitor.connected == false ? "L'appareil n'est pas connecté à internet." : nil)
        let m2 : String? = (sentError == .mailNotSent ? "Le mail est dans la boîte d'envoi de l'app Mail. Vérifiez son état dans l'app Mail avant d'essayer de le renvoyer." : nil)
        let m3 : String? = ((sentError == .messageFailed || sentError == .messageNotSent) ? "Le SMS n'a pas été envoyé" : nil)
        let m4 : String? = (sentError == .mailFailed ? "Le mail n'a pas été envoyé" : nil)
        
        let message : String = [m1, m2, m3, m4].compacted().joined(separator: "\n")
        return message
    }
    
    private func _save() {
        do {
            try dataStore.tournaments.addOrUpdate(instance: tournament)
        } catch {
            Logger.error(error)
        }
    }
}

//#Preview {
//    NavigationStack {
//        InscriptionManagerView(tournament: Tournament.mock())
//            .environment(Tournament.mock())
//    }
//}
