//
//  swift
//  PadelClub
//
//  Created by Laurent Morvillier on 02/02/2024.
//

import Foundation
import LeStorage
import SwiftUI

@Observable
final class Tournament: BaseTournament {

    //local variable
    var refreshInProgress: Bool = false
    var lastTeamRefresh: Date?
    var refreshRanking: Bool = false
    
    func shouldRefreshTeams(forced: Bool) -> Bool {
        if forced {
            return true
        }
        guard let lastTeamRefresh else { return true }
        return lastTeamRefresh.timeIntervalSinceNow < -600
    }
    
    @ObservationIgnored
    var navigationPath: [Screen] = []

//    internal init(event: String? = nil, name: String? = nil, startDate: Date = Date(), endDate: Date? = nil, creationDate: Date = Date(), isPrivate: Bool = false, groupStageFormat: MatchFormat? = nil, roundFormat: MatchFormat? = nil, loserRoundFormat: MatchFormat? = nil, groupStageSortMode: GroupStageOrderingMode, groupStageCount: Int = 4, rankSourceDate: Date? = nil, dayDuration: Int = 1, teamCount: Int = 24, teamSorting: TeamSortingType? = nil, federalCategory: TournamentCategory, federalLevelCategory: TournamentLevel, federalAgeCategory: FederalTournamentAge, closedRegistrationDate: Date? = nil, groupStageAdditionalQualified: Int = 0, courtCount: Int = 2, prioritizeClubMembers: Bool = false, qualifiedPerGroupStage: Int = 1, teamsPerGroupStage: Int = 4, entryFee: Double? = nil, additionalEstimationDuration: Int = 0, isDeleted: Bool = false, publishTeams: Bool = false, publishSummons: Bool = false, publishGroupStages: Bool = false, publishBrackets: Bool = false, shouldVerifyBracket: Bool = false, shouldVerifyGroupStage: Bool = false, hideTeamsWeight: Bool = false, publishTournament: Bool = false, hidePointsEarned: Bool = false, publishRankings: Bool = false, loserBracketMode: LoserBracketMode = .automatic, initialSeedRound: Int = 0, initialSeedCount: Int = 0) {
//        super.init()
//    }


    internal init(event: String? = nil, name: String? = nil, startDate: Date = Date(), endDate: Date? = nil, creationDate: Date = Date(), isPrivate: Bool = true, groupStageFormat: MatchFormat? = nil, roundFormat: MatchFormat? = nil, loserRoundFormat: MatchFormat? = nil, groupStageSortMode: GroupStageOrderingMode, groupStageCount: Int = 4, rankSourceDate: Date? = nil, dayDuration: Int = 1, teamCount: Int = 24, teamSorting: TeamSortingType? = nil, federalCategory: TournamentCategory, federalLevelCategory: TournamentLevel, federalAgeCategory: FederalTournamentAge, closedRegistrationDate: Date? = nil, groupStageAdditionalQualified: Int = 0, courtCount: Int = 2, prioritizeClubMembers: Bool = false, qualifiedPerGroupStage: Int = 1, teamsPerGroupStage: Int = 4, entryFee: Double? = nil, additionalEstimationDuration: Int = 0, isDeleted: Bool = false, publishTeams: Bool = false, publishSummons: Bool = false, publishGroupStages: Bool = false, publishBrackets: Bool = false, shouldVerifyBracket: Bool = false, shouldVerifyGroupStage: Bool = false, hideTeamsWeight: Bool = false, publishTournament: Bool = false, hidePointsEarned: Bool = false, publishRankings: Bool = false, loserBracketMode: LoserBracketMode = .automatic, initialSeedRound: Int = 0, initialSeedCount: Int = 0, enableOnlineRegistration: Bool = false, registrationDateLimit: Date? = nil, openingRegistrationDate: Date? = nil, waitingListLimit: Int? = nil, accountIsRequired: Bool = true, licenseIsRequired: Bool = true, minimumPlayerPerTeam: Int = 2, maximumPlayerPerTeam: Int = 2, information: String? = nil,
        umpireCustomMail: String? = nil,
        umpireCustomContact: String? = nil,
        umpireCustomPhone: String? = nil,
        hideUmpireMail: Bool = false,
        hideUmpirePhone: Bool = true,
        disableRankingFederalRuling: Bool = false,
        teamCountLimit: Bool = true
    ) {
        super.init()
        self.event = event
        self.name = name
        self.startDate = startDate
        self.endDate = endDate
        self.creationDate = creationDate
#if DEBUG
        self.isPrivate = false
#else
        self.isPrivate = isPrivate
#endif
        self.groupStageFormat = groupStageFormat
        self.roundFormat = roundFormat
        self.loserRoundFormat = loserRoundFormat
        self.groupStageSortMode = groupStageSortMode
        self.groupStageCount = groupStageCount
        self.rankSourceDate = rankSourceDate
        self.dayDuration = dayDuration
        self.teamCount = teamCount
        self.teamSorting = teamSorting ?? federalLevelCategory.defaultTeamSortingType
        self.federalCategory = federalCategory
        self.federalLevelCategory = federalLevelCategory
        self.federalAgeCategory = federalAgeCategory
        self.closedRegistrationDate = closedRegistrationDate
        self.groupStageAdditionalQualified = groupStageAdditionalQualified
        self.courtCount = courtCount
        self.prioritizeClubMembers = prioritizeClubMembers
        self.qualifiedPerGroupStage = qualifiedPerGroupStage
        self.teamsPerGroupStage = teamsPerGroupStage
        self.entryFee = entryFee
        self.additionalEstimationDuration = additionalEstimationDuration
        self.isDeleted = isDeleted
#if DEBUG
        self.publishTeams = true
        self.publishSummons = true
        self.publishBrackets = true
        self.publishGroupStages = true
        self.publishRankings = true
        self.publishTournament = true
#else
        self.publishTeams = publishTeams
        self.publishSummons = publishSummons
        self.publishBrackets = publishBrackets
        self.publishGroupStages = publishGroupStages
        self.publishRankings = publishRankings
        self.publishTournament = publishTournament
#endif
        self.shouldVerifyBracket = shouldVerifyBracket
        self.shouldVerifyGroupStage = shouldVerifyGroupStage
        self.hideTeamsWeight = hideTeamsWeight
        self.hidePointsEarned = hidePointsEarned
        self.loserBracketMode = loserBracketMode
        self.initialSeedRound = initialSeedRound
        self.initialSeedCount = initialSeedCount
        self.enableOnlineRegistration = enableOnlineRegistration
        self.registrationDateLimit = registrationDateLimit
        self.openingRegistrationDate = openingRegistrationDate
        self.waitingListLimit = waitingListLimit

        self.accountIsRequired = accountIsRequired
        self.licenseIsRequired = licenseIsRequired
        self.minimumPlayerPerTeam = minimumPlayerPerTeam
        self.maximumPlayerPerTeam = maximumPlayerPerTeam
        self.information = information
        self.umpireCustomMail = umpireCustomMail
        self.umpireCustomContact = umpireCustomContact
        self.disableRankingFederalRuling = disableRankingFederalRuling
        self.teamCountLimit = teamCountLimit
    }

    required init(from decoder: Decoder) throws {
        try super.init(from: decoder)
    }
    
    required public init() {
        super.init()
    }
    
    var tournamentStore: TournamentStore? {
        return TournamentLibrary.shared.store(tournamentId: self.id)
    }

    override func deleteDependencies() {
        guard let store = self.tournamentStore else { return }

        let drawLogs = Array(store.drawLogs)
        for drawLog in drawLogs {
            drawLog.deleteDependencies()
        }
        store.drawLogs.deleteDependencies(drawLogs)

        let teams = Array(store.teamRegistrations)
        for team in teams {
            team.deleteDependencies()
        }
        store.teamRegistrations.deleteDependencies(teams)

        let groups = Array(store.groupStages)
        for group in groups {
            group.deleteDependencies()
        }
        store.groupStages.deleteDependencies(groups)

        let rounds = Array(store.rounds)
        for round in rounds {
            round.deleteDependencies()
        }
        store.rounds.deleteDependencies(rounds)

        store.matchSchedulers.deleteDependencies(self._matchSchedulers())

    }

    // MARK: - Computed Dependencies

    func unsortedTeams() -> [TeamRegistration] {
        guard let tournamentStore = self.tournamentStore else { return [] }
        return Array(tournamentStore.teamRegistrations)
    }

    func unsortedTeamsCount() -> Int {
        return self.tournamentStore?.teamRegistrations.count ?? 0
    }

    func groupStages(atStep step: Int = 0) -> [GroupStage] {
        guard let tournamentStore = self.tournamentStore else { return [] }
        let groupStages: [GroupStage] = tournamentStore.groupStages.filter { $0.tournament == self.id && $0.step == step }
        return groupStages.sorted(by: \.index)
    }

    func allGroupStages() -> [GroupStage] {
        guard let tournamentStore = self.tournamentStore else { return [] }
        return tournamentStore.groupStages.sorted(by: \GroupStage.computedOrder)
    }

    func allRounds() -> [Round] {
        guard let tournamentStore = self.tournamentStore else { return [] }
        return Array(tournamentStore.rounds)
    }

    // MARK: -

    enum State {
        case initial
        case build
        case running
        case canceled
        case finished
    }

    func eventLabel() -> String {
        if let event = eventObject(), let name = event.name {
            return name
        } else {
            return ""
        }
    }

    func publishedTournamentDate() -> Date {
        return min(creationDate.tomorrowAtNine, startDate)
    }

    func publishedTeamsDate() -> Date {
        return self.startDate
    }

    func canBePublished() -> Bool {
        switch state() {
        case .build, .finished, .running:
            return unsortedTeams().count > 3
        default:
            return false
        }
    }

    func isTournamentPublished() -> Bool {
        return (Date() >= publishedTournamentDate()) || publishTournament
    }

    func areTeamsPublished() -> Bool {
        return Date() >= startDate || publishTeams
    }

    func areSummonsPublished() -> Bool {
        return Date() >= startDate || publishSummons
    }

    fileprivate func _publishedDateFromMatches(_ matches: [Match]) -> Date? {
        let startDates: [Date] = matches.compactMap { $0.startDate }
        let sortedDates: [Date] = startDates.sorted()

        if let first: Date = sortedDates.first?.atEightAM() {
            if first.isEarlierThan(startDate) {
                return startDate
            } else {
                return first
            }
        } else {
            return startDate
        }
    }

    func publishedGroupStagesDate() -> Date? {
        let matches: [Match] = self.groupStages().flatMap { $0.playedMatches() }
        return self._publishedDateFromMatches(matches)
    }

    func areGroupStagesPublished() -> Bool {
        if publishGroupStages { return true }
        if let publishedGroupStagesDate = publishedGroupStagesDate() {
            return Date() >= publishedGroupStagesDate
        } else {
            return false
        }
    }

    func publishedBracketsDate() -> Date? {
        let matches: [Match] = self.rounds().flatMap { $0.playedMatches() }
        return self._publishedDateFromMatches(matches)
    }

    func areBracketsPublished() -> Bool {
        if publishBrackets { return true }
        if let publishedBracketsDate = publishedBracketsDate() {
            return Date() >= publishedBracketsDate
        } else {
            return false
        }
    }

    func shareURL(_ pageLink: PageLink = .matches) -> URL? {
        if pageLink == .clubBroadcast {
            let club = club()
//            print("club", club)
//            print("club broadcast code", club?.broadcastCode)
            if let club, let broadcastCode = club.broadcastCode {
                return URLs.main.url.appending(path: "c/\(broadcastCode)")
            } else {
                return nil
            }
        }
        return URLs.main.url.appending(path: "tournament/\(id)").appending(path: pageLink.path)
    }

    func courtUsed(runningMatches: [Match]) -> [Int] {
#if _DEBUGING_TIME //DEBUGING TIME
let start = Date()
defer {
    let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
    print("func courtUsed()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif
        return Set(runningMatches.compactMap { $0.courtIndex }).sorted()
    }

    func hasStarted() -> Bool {
        return startDate <= Date()
    }

    func eventObject() -> Event? {
        guard let event else { return nil }
        return Store.main.findById(event)
    }

    func pasteDataForImporting(_ exportFormat: ExportFormat = .rawText) -> String {
        let _selectedSortedTeams = selectedSortedTeams()
        let selectedSortedTeams = _selectedSortedTeams + waitingListSortedTeams(selectedSortedTeams: _selectedSortedTeams)
        switch exportFormat {
        case .rawText:
            return (selectedSortedTeams.compactMap { $0.pasteData(exportFormat) } + ["Liste d'attente"] + waitingListTeams(in: selectedSortedTeams, includingWalkOuts: true).compactMap { $0.pasteData(exportFormat) }).joined(separator: exportFormat.newLineSeparator(2))
        case .csv:
            let headers = ["N°", "Nom Prénom", "rang", "Nom Prénom", "rang", "poids"].joined(separator: exportFormat.separator())
            var teamPaste = [headers]
            for (index, team) in selectedSortedTeams.enumerated() {
                teamPaste.append(team.pasteData(exportFormat, index + 1))
            }
            return teamPaste.joined(separator: exportFormat.newLineSeparator())
        }
    }

    func club() -> Club? {
        return eventObject()?.clubObject()
    }

    func locationLabel(_ displayStyle: DisplayStyle = .wide) -> String {
        if let club = club() {
            switch displayStyle {
            case .wide, .title:
                return club.name
            case .short:
                return club.acronym
            }
        } else {
            return ""
        }
    }

    func hasEnded() -> Bool {
        return endDate != nil
    }

    func state() -> State {
        if self.isCanceled == true {
            return .canceled
        }

        if self.hasEnded() { return .finished }

        let isBuild = (groupStageCount > 0 && groupStages().isEmpty == false)
            || rounds().isEmpty == false

        if isBuild && startDate <= Date() { return .running }

        if isBuild {
            return .build
        }
        return .initial
    }

    func seededTeams() -> [TeamRegistration] {
        return selectedSortedTeams().filter({ $0.bracketPosition != nil && $0.groupStagePosition == nil })
    }

    func groupStageTeams() -> [TeamRegistration] {
        return selectedSortedTeams().filter({ $0.groupStagePosition != nil })
    }

    func groupStageSpots() -> Int {
        return groupStages().map { $0.size }.reduce(0,+)
    }

    func seeds() -> [TeamRegistration] {
        let selectedSortedTeams = selectedSortedTeams()
        let seeds = max(selectedSortedTeams.count - groupStageSpots() , 0)
        return Array(selectedSortedTeams.prefix(seeds))
    }

    func availableSeeds() -> [TeamRegistration] {
#if _DEBUG_TIME //DEBUGING TIME
let start = Date()
defer {
    let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
    print("func availableSeeds()", duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif


        return seeds().filter { $0.isSeedable() }
    }

    func lastSeedRound() -> Int {
        if let last = seeds().filter({ $0.bracketPosition != nil }).last {
            return RoundRule.roundIndex(fromMatchIndex: last.bracketPosition! / 2)
        } else {
            return 0
        }
    }

    func getRound(atRoundIndex roundIndex: Int) -> Round? {
        return self.tournamentStore?.rounds.first(where: { $0.index == roundIndex })
//        return Store.main.filter(isIncluded: { $0.tournament == id && $0.index == roundIndex }).first
    }

    func availableSeedSpot(inRoundIndex roundIndex: Int) -> [Match] {
        return getRound(atRoundIndex: roundIndex)?.playedMatches().filter { $0.isEmpty() } ?? []
    }

    func availableSeedOpponentSpot(inRoundIndex roundIndex: Int) -> [Match] {
        return getRound(atRoundIndex: roundIndex)?.playedMatches().filter { $0.hasSpaceLeft() } ?? []
    }
    func availableSeedGroups(includeAll: Bool = false) -> [SeedInterval] {
        let seeds = seeds()
        var availableSeedGroup = Set<SeedInterval>()
        for (index, seed) in seeds.enumerated() {
            if seed.isSeedable(), let seedGroup = seedGroup(for: index) {
                if includeAll {
                    if let chunks = seedGroup.chunks() {
                        chunksBy(in: chunks, availableSeedGroup: &availableSeedGroup)
                    }
                } else {
                    availableSeedGroup.insert(seedGroup)
                }
            }
        }
        return availableSeedGroup.sorted(by: <)
    }
    
    func chunksBy(in chunks: [SeedInterval], availableSeedGroup: inout Set<SeedInterval>) {
        chunks.forEach { chunk in
            availableSeedGroup.insert(chunk)
            if let moreChunk = chunk.chunks() {
                self.chunksBy(in: moreChunk, availableSeedGroup: &availableSeedGroup)
            }
        }
    }
        
    func seedGroup(for alreadySetupSeeds: Int) -> SeedInterval? {
        switch alreadySetupSeeds {
        case 0...1:
            return SeedInterval(first: 1, last: 2)
        case 2...3:
            return SeedInterval(first: 3, last: 4)
        case 4...7:
            return SeedInterval(first: 5, last: 8)
        case 8...15:
            return SeedInterval(first: 9, last: 16)
        case 16...23:
            return SeedInterval(first: 17, last: 24)
        case 24...31:
            return SeedInterval(first: 25, last: 32)
        default:
            return nil
        }
    }

    func availableSeedGroup() -> SeedInterval? {
        let seeds = seeds()
        if let firstIndex = seeds.firstIndex(where: { $0.isSeedable() }) {
            guard let seedGroup = seedGroup(for: firstIndex) else { return nil }
            return seedGroup
        }
        return nil
    }

    func randomSeed(fromSeedGroup seedGroup: SeedInterval) -> TeamRegistration? {
        let availableSeeds = seeds(inSeedGroup: seedGroup)
        return availableSeeds.randomElement()
    }

    func seeds(inSeedGroup seedGroup: SeedInterval) -> [TeamRegistration] {
        let availableSeedInSeedGroup = (seedGroup.last - seedGroup.first) + 1
        let availableSeeds = seeds().dropFirst(seedGroup.first - 1).prefix(availableSeedInSeedGroup).filter({ $0.isSeedable() })
        return availableSeeds
    }

    func seedGroupAvailable(atRoundIndex roundIndex: Int) -> SeedInterval? {
        if let availableSeedGroup = availableSeedGroup() {
            return seedGroupAvailable(atRoundIndex: roundIndex, availableSeedGroup: availableSeedGroup)
        } else {
            return nil
        }
    }

    func seedGroupAvailable(atRoundIndex roundIndex: Int, availableSeedGroup: SeedInterval) -> SeedInterval? {
        let fullLeftSeeds = availableSeeds()
        if fullLeftSeeds.isEmpty == false && roundIndex >= lastSeedRound() {

            if availableSeedGroup == SeedInterval(first: 1, last: 2) { return availableSeedGroup }
            let availableSeeds = seeds(inSeedGroup: availableSeedGroup)
            let availableSeedSpot = availableSeedSpot(inRoundIndex: roundIndex)
            let availableSeedOpponentSpot = availableSeedOpponentSpot(inRoundIndex: roundIndex)
            let targetSpots = availableSeedSpot.isEmpty ? availableSeedOpponentSpot.count : availableSeedSpot.count
            if availableSeedGroup == SeedInterval(first: 3, last: 4), availableSeedSpot.count == 6 {
                return availableSeedGroup
            }
            if availableSeeds.count == availableSeedSpot.count && availableSeedGroup.count == availableSeeds.count {
                return availableSeedGroup
            } else if availableSeeds.count == availableSeedOpponentSpot.count && availableSeedGroup.count == availableSeedOpponentSpot.count {
                return availableSeedGroup
            } else if let chunks = availableSeedGroup.chunks() {
                let seededTeamsCount = self.seededTeams().count
                if let chunk = chunks.first(where: { seedInterval in
                    return seedInterval.first == seededTeamsCount
                }) {
                    return seedGroupAvailable(atRoundIndex: roundIndex, availableSeedGroup: chunk)
                } else if fullLeftSeeds.count > 1, targetSpots > 1, fullLeftSeeds.count >= targetSpots {
                    let currentSeeds = seeds()
                    if let firstIndex = currentSeeds.firstIndex(where: { $0.isSeedable() }) {
                        
                        if firstIndex < seededTeamsCount {
                            return nil
                        } else {
                            let sg = SeedInterval(first: seededTeamsCount + 1, last: seededTeamsCount + targetSpots)
                            let futureAvailableSeeds = self.seeds(inSeedGroup: sg)
                            if futureAvailableSeeds.count == targetSpots {
                                return seedGroupAvailable(atRoundIndex: roundIndex, availableSeedGroup: sg)
                            } else {
                                return nil
                            }
                        }
                    }
                }
            }
        }

        return nil
    }

    func setSeeds(inRoundIndex roundIndex: Int, inSeedGroup seedGroup: SeedInterval) {
        if seedGroup == SeedInterval(first: 1, last: 2) {
            let seeds = seeds()
            if let matches = getRound(atRoundIndex: roundIndex)?.playedMatches() {
                if let lastMatch = matches.last {
                    seeds.prefix(1).first?.setSeedPosition(inSpot: lastMatch, slot: .two, opposingSeeding: false)
                }
                if let firstMatch = matches.first {
                    seeds.prefix(2).dropFirst().first?.setSeedPosition(inSpot: firstMatch, slot: .one, opposingSeeding: false)
                }
            }
        } else {

            let availableSeedSpot = availableSeedSpot(inRoundIndex: roundIndex)
            let availableSeedOpponentSpot = availableSeedOpponentSpot(inRoundIndex: roundIndex)
            let availableSeeds = seeds(inSeedGroup: seedGroup)

            if seedGroup == SeedInterval(first: 3, last: 4), availableSeedSpot.count == 6 {
                var spots = [Match]()
                spots.append(availableSeedSpot[1])
                spots.append(availableSeedSpot[4])
                spots = spots.shuffled()
                for (index, seed) in availableSeeds.enumerated() {
                    seed.setSeedPosition(inSpot: spots[index], slot: nil, opposingSeeding: false)
                }
//            } else if seedGroup == SeedInterval(first: 5, last: 6), availableSeedSpot.count == 4 {
//                var spots = [Match]()
//                spots.append(availableSeedSpot[1])
//                spots.append(availableSeedSpot[2])
//                spots = spots.shuffled()
//                for (index, seed) in availableSeeds.enumerated() {
//                    seed.setSeedPosition(inSpot: spots[index], slot: nil, opposingSeeding: false)
//                }
            } else {
                if availableSeeds.count <= availableSeedSpot.count {
                    let spots = availableSeedSpot.shuffled()
                    for (index, seed) in availableSeeds.enumerated() {
                        seed.setSeedPosition(inSpot: spots[index], slot: nil, opposingSeeding: false)
                    }
                } else if (availableSeeds.count <= availableSeedOpponentSpot.count && availableSeeds.count <= self.availableSeeds().count) {

                    let spots = availableSeedOpponentSpot.shuffled()
                    for (index, seed) in availableSeeds.enumerated() {
                        seed.setSeedPosition(inSpot: spots[index], slot: nil, opposingSeeding: true)
                    }
                } else if let chunks = seedGroup.chunks() {
                    if let chunk = chunks.first(where: { seedInterval in
                        seedInterval.first >= self.seededTeams().count
                    }) {
                        setSeeds(inRoundIndex: roundIndex, inSeedGroup: chunk)
                    }
                }
            }
        }
    }


    func inscriptionClosed() -> Bool {
        closedRegistrationDate != nil
    }

    func getActiveGroupStage(atStep step: Int = 0) -> GroupStage? {
        let groupStages = groupStages(atStep: step)
        return groupStages.filter({ $0.hasStarted() && $0.hasEnded() == false }).sorted(by: \.index).first ?? groupStages.first
    }

    func matchesWithSpace() -> [Match] {
        getActiveRound()?.playedMatches().filter({ $0.hasSpaceLeft() }) ?? []
    }

    func getActiveRound(withSeeds: Bool = false) -> Round? {
        let rounds: [Round] = self.rounds()
        let unfinishedRounds: [Round] = rounds.filter { $0.hasStarted() && $0.hasEnded() == false }
        let sortedRounds: [Round] = unfinishedRounds.sorted(by: \.index).reversed()

        let round = sortedRounds.first ?? rounds.last(where: { $0.hasEnded() }) ?? rounds.first

        if withSeeds {
            if round?.seeds().isEmpty == false {
                return round
            } else {
                return nil
            }
        } else {
            return round
        }
    }

    func getPlayedMatchDateIntervals(in event: Event) -> [DateInterval] {
        let allMatches: [Match] = self.allMatches().filter { $0.courtIndex != nil && $0.startDate != nil }
        return allMatches.map { match in
            DateInterval(event: event.id, courtIndex: match.courtIndex!, startDate: match.startDate!, endDate: match.estimatedEndDate(additionalEstimationDuration)!)
        }
    }

    func allRoundMatches() -> [Match] {
        return allRounds().flatMap { $0._matches() }
    }

    func allMatches() -> [Match] {
        guard let tournamentStore = self.tournamentStore else { return [] }
        return tournamentStore.matches.filter { $0.disabled == false }
    }

    func _allMatchesIncludingDisabled() -> [Match] {
        guard let tournamentStore = self.tournamentStore else { return [] }
        return Array(tournamentStore.matches)
    }

    func rounds() -> [Round] {
        guard let tournamentStore = self.tournamentStore else { return [] }
        let rounds: [Round] = tournamentStore.rounds.filter { $0.isUpperBracket() }
        return rounds.sorted(by: \.index).reversed()
    }

    func sortedTeams(selectedSortedTeams: [TeamRegistration]) -> [TeamRegistration] {
        let teams = selectedSortedTeams
        return teams + waitingListTeams(in: teams, includingWalkOuts: true)
    }

    func waitingListSortedTeams(selectedSortedTeams: [TeamRegistration]) -> [TeamRegistration] {
        let teams = selectedSortedTeams
        return waitingListTeams(in: teams, includingWalkOuts: false)
    }


    func selectedSortedTeams() -> [TeamRegistration] {
        #if _DEBUG_TIME //DEBUGING TIME
        let start = Date()
        defer {
            let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
            print("func selectedSortedTeams", id, tournamentTitle(), duration.formatted(.units(allowed: [.seconds, .milliseconds])))
        }
        #endif
        var _sortedTeams : [TeamRegistration] = []
        var _teams = unsortedTeams().filter({ $0.isOutOfTournament() == false })

        if let closedRegistrationDate {
            _teams = _teams.filter({ team in
                if let registrationDate = team.registrationDate {
                    return registrationDate <= closedRegistrationDate
                } else {
                    return true
                }
            })
        }

        let defaultSorting : [MySortDescriptor<TeamRegistration>] = _defaultSorting()

        let _completeTeams = _teams.sorted(using: defaultSorting, order: .ascending).filter { $0.isWildCard() == false }.prefix(teamCount).sorted(using: [.keyPath(\.initialWeight), .keyPath(\.id)], order: .ascending)

        let wcGroupStage = _teams.filter { $0.wildCardGroupStage }.sorted(using: _currentSelectionSorting, order: .ascending)

        let wcBracket = _teams.filter { $0.wildCardBracket }.sorted(using: _currentSelectionSorting, order: .ascending)

        let groupStageSpots: Int = self.groupStageSpots()
        var bracketSeeds: Int = teamCount - groupStageSpots - wcBracket.count
        var groupStageTeamCount: Int = groupStageSpots - wcGroupStage.count
        if groupStageTeamCount < 0 { groupStageTeamCount = 0 }
        if bracketSeeds < 0 { bracketSeeds = 0 }

        if prioritizeClubMembers {

            var bracketTeams: [TeamRegistration] = []
            bracketTeams.append(contentsOf: _completeTeams.filter { $0.hasMemberOfClub(clubName) })

            let others: [TeamRegistration] = _completeTeams.filter { $0.hasMemberOfClub(clubName) == false }
            let sortedOthers: [TeamRegistration] = others.sorted(using: defaultSorting, order: .ascending)
            bracketTeams.append(contentsOf: sortedOthers)

            bracketTeams = bracketTeams
                .prefix(bracketSeeds)
                .sorted(using: _currentSelectionSorting, order: .ascending)
            bracketTeams.append(contentsOf: wcBracket)

//            let bracketTeams: [TeamRegistration] = (_completeTeams.filter { $0.hasMemberOfClub(clubName) } + _completeTeams.filter { $0.hasMemberOfClub(clubName) == false }.sorted(using: defaultSorting, order: .ascending)).prefix(bracketSeeds).sorted(using: _currentSelectionSorting, order: .ascending) + wcBracket

            let groupStageTeamsNoFiltering = Set(_completeTeams).subtracting(bracketTeams)
            let groupStageTeams = (groupStageTeamsNoFiltering.filter { $0.hasMemberOfClub(clubName) } + groupStageTeamsNoFiltering.filter { $0.hasMemberOfClub(clubName) == false }.sorted(using: defaultSorting, order: .ascending)).prefix(groupStageTeamCount).sorted(using: _currentSelectionSorting, order: .ascending) + wcGroupStage

            _sortedTeams = bracketTeams.sorted(using: _currentSelectionSorting, order: .ascending) + groupStageTeams.sorted(using: _currentSelectionSorting, order: .ascending)
        } else {
            let bracketTeams = _completeTeams.prefix(bracketSeeds).sorted(using: _currentSelectionSorting, order: .ascending) + wcBracket
            let groupStageTeams = Set(_completeTeams).subtracting(bracketTeams).sorted(using: defaultSorting, order: .ascending).prefix(groupStageTeamCount).sorted(using: _currentSelectionSorting, order: .ascending) + wcGroupStage
            _sortedTeams = bracketTeams.sorted(using: _currentSelectionSorting, order: .ascending) + groupStageTeams.sorted(using: _currentSelectionSorting, order: .ascending)
        }
        return _sortedTeams
    }

    func waitingListTeams(in teams: [TeamRegistration], includingWalkOuts: Bool) -> [TeamRegistration] {
        let waitingList = Set(unsortedTeams()).subtracting(teams)
        let waitings = waitingList.filter { $0.isOutOfTournament() == false }.sorted(using: _defaultSorting(), order: .ascending)
        let walkOuts = waitingList.filter { $0.walkOut == true }.sorted(using: _defaultSorting(), order: .ascending)
        if includingWalkOuts {
            return waitings + walkOuts
        } else {
            return waitings
        }
    }
    
    func bracketCut(teamCount: Int, groupStageCut: Int) -> Int {
        return self.teamCount - groupStageCut
    }

    func groupStageCut() -> Int {
        return groupStageSpots()
    }

    func cutLabel(index: Int, teamCount: Int?) -> String {
        let _teamCount = teamCount ?? selectedSortedTeams().count
        let groupStageCut = groupStageCut()
        let bracketCut = bracketCut(teamCount: _teamCount, groupStageCut: groupStageCut)
        
        if index < bracketCut {
            return "Tableau"
        } else if index - bracketCut < groupStageCut && _teamCount > 0 {
            return "Poule"
        } else {
            return "Attente"
        }
    }

    func cutLabelColor(index: Int?, teamCount: Int?) -> Color {
        guard let index else { return Color.grayNotUniversal }
        let _teamCount = teamCount ?? selectedSortedTeams().count
        let groupStageCut = groupStageCut()
        let bracketCut = bracketCut(teamCount: _teamCount, groupStageCut: groupStageCut)
        if index < bracketCut {
            return Color.mint
        } else if index - bracketCut < groupStageCut && _teamCount > 0 {
            return Color.indigo
        } else {
            return Color.grayNotUniversal
        }
    }

    func unsortedTeamsWithoutWO() -> [TeamRegistration] {
        guard let tournamentStore = self.tournamentStore else { return [] }
        return tournamentStore.teamRegistrations.filter { $0.isOutOfTournament() == false }
    }

    func walkoutTeams() -> [TeamRegistration] {
        guard let tournamentStore = self.tournamentStore else { return [] }
        return tournamentStore.teamRegistrations.filter { $0.walkOut == true }
//        return Store.main.filter { $0.tournament == self.id && $0.walkOut == true }
    }

    func duplicates(in players: [PlayerRegistration]) -> [PlayerRegistration] {
        var duplicates = [PlayerRegistration]()
        Set(players.compactMap({ $0.licenceId })).forEach { licenceId in
            let found = players.filter({ $0.licenceId == licenceId })
            if found.count > 1 {
                duplicates.append(found.first!)
            }
        }
        return duplicates
    }

    func homonyms(in players: [PlayerRegistration]) -> [PlayerRegistration] {
        players.filter({ $0.hasHomonym() })
    }

    func unsortedPlayers() -> [PlayerRegistration] {
        guard let tournamentStore = self.tournamentStore else { return [] }
        return Array(tournamentStore.playerRegistrations)
    }

    func selectedPlayers() -> [PlayerRegistration] {
        return self.selectedSortedTeams().flatMap { $0.unsortedPlayers() }.sorted(by: \.computedRank)
    }

    func paidSelectedPlayers(type: PlayerPaymentType) -> Double? {
        if let entryFee {
            let flat = self.selectedSortedTeams().flatMap { $0.unsortedPlayers() }
            let count = flat.filter { $0.paymentType == type }.count
            return Double(count) * entryFee
        } else {
            return nil
        }
    }

    func players() -> [PlayerRegistration] {
        guard let tournamentStore = self.tournamentStore else { return [] }
        return tournamentStore.playerRegistrations.sorted(by: \.computedRank)
    }

    func unrankValue(for malePlayer: Bool) -> Int? {
        switch tournamentCategory {
        case .unlisted:
            return nil
        case .men:
            return maleUnrankedValue
        case .women:
            return femaleUnrankedValue
        case .mix:
            return malePlayer ? maleUnrankedValue : femaleUnrankedValue
        }
    }

    //todo
    var clubName: String? {
        return self.eventObject()?.clubObject()?.name
    }

    //todo
    func significantPlayerCount() -> Int {
        return minimumPlayerPerTeam
    }

    func inadequatePlayers(in players: [PlayerRegistration]) -> [PlayerRegistration] {
        if startDate.isInCurrentYear() == false {
            return []
        }
        return players.filter { player in
            return isPlayerRankInadequate(player: player)
        }
    }

    func isPlayerRankInadequate(player: PlayerHolder) -> Bool {
        guard let rank = player.getRank() else { return false }
        let _rank = player.male ? rank : rank + PlayerRegistration.addon(for: rank, manMax: maleUnrankedValue ?? 0, womanMax: femaleUnrankedValue ?? 0)
        if _rank <= tournamentLevel.minimumPlayerRank(category: tournamentCategory, ageCategory: federalTournamentAge) {
            return true
        } else {
            return false
        }
    }

    func ageInadequatePlayers(in players: [PlayerRegistration]) -> [PlayerRegistration] {
        if startDate.isInCurrentYear() == false {
            return []
        }
        return players.filter { player in
            return isPlayerAgeInadequate(player: player)
        }
    }

    func isPlayerAgeInadequate(player: PlayerHolder) -> Bool {
        guard let computedAge = player.computedAge else { return false }
        if federalTournamentAge.isAgeValid(age: computedAge) == false {
            return true
        } else {
            return false
        }
    }

    func licenseYearValidity() -> Int {
        if startDate.get(.month) > 8 {
            return startDate.get(.year) + 1
        } else {
            return startDate.get(.year)
        }
    }

    func playersWithoutValidLicense(in players: [PlayerRegistration], isImported: Bool) -> [PlayerRegistration] {
        let licenseYearValidity = self.licenseYearValidity()
        return players.filter({ player in
            if player.isImported() {
                // Player is marked as imported: check if the license is valid
                return !player.isValidLicenseNumber(year: licenseYearValidity)
            } else {
                // Player is not imported: validate license and handle `isImported` flag for non-imported players
                let noLicenseId = player.licenceId == nil || player.licenceId?.isEmpty == true
                let invalidFormattedLicense = player.formattedLicense().isLicenseNumber == false

                // If global `isImported` is true, check license number as well
                let invalidLicenseForImportedFlag = isImported && !player.isValidLicenseNumber(year: licenseYearValidity)

                return noLicenseId || invalidFormattedLicense || invalidLicenseForImportedFlag
            }
        })
    }

    func getStartDate(ofSeedIndex seedIndex: Int?) -> Date? {
        guard let seedIndex else { return nil }
        return selectedSortedTeams()[safe: seedIndex]?.callDate
    }

    func importTeams(_ teams: [FileImportManager.TeamHolder]) {
        var teamsToImport = [TeamRegistration]()
        let players = players().filter { $0.licenceId != nil }
        teams.forEach { team in
            if let previousTeam = team.previousTeam {
                previousTeam.updatePlayers(team.players, inTournamentCategory: team.tournamentCategory)
                teamsToImport.append(previousTeam)
            } else {
                var registrationDate = team.registrationDate
                if let previousPlayer = players.first(where: { player in
                    let ids = team.players.compactMap({ $0.licenceId })
                    return ids.contains(player.licenceId!)
                }), let previousTeamRegistrationDate = previousPlayer.team()?.registrationDate {
                    registrationDate = previousTeamRegistrationDate
                }
                let newTeam = addTeam(team.players, registrationDate: registrationDate, name: team.name)
                if isAnimation() {
                    if newTeam.weight == 0 {
                        newTeam.weight = team.index(in: teams) ?? 0
                    }
                }
                teamsToImport.append(newTeam)
            }
        }

        if let tournamentStore = self.tournamentStore {
            tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teamsToImport)
            tournamentStore.playerRegistrations.addOrUpdate(contentOfs: teams.flatMap { $0.players })
        }

        if state() == .build && groupStageCount > 0 && groupStageTeams().isEmpty {
            setGroupStage(randomize: groupStageSortMode == .random)
        }
    }

    func maximumCourtsPerGroupSage() -> Int {
        if teamsPerGroupStage > 1 {
            return min(teamsPerGroupStage / 2, courtCount)
        } else {
            return max(1, courtCount)
        }
    }
    
    func registrationIssues(selectedTeams: [TeamRegistration]) async -> Int {
        let players : [PlayerRegistration] = unsortedPlayers()
        let callDateIssue : [TeamRegistration] = selectedTeams.filter { $0.callDate != nil && isStartDateIsDifferentThanCallDate($0) }
        let duplicates : [PlayerRegistration] = duplicates(in: players)
        let problematicPlayers : [PlayerRegistration] = players.filter({ $0.sex == nil })
        let inadequatePlayers : [PlayerRegistration] =  inadequatePlayers(in: players)
        let homonyms = homonyms(in: players)
        let ageInadequatePlayers = ageInadequatePlayers(in: players)
        let isImported = players.anySatisfy({ $0.isImported() })
        let playersWithoutValidLicense : [PlayerRegistration] = playersWithoutValidLicense(in: players, isImported: isImported)
        let playersMissing : [TeamRegistration] = selectedTeams.filter({ $0.unsortedPlayers().count < 2 })
        let waitingList : [TeamRegistration] = waitingListTeams(in: selectedTeams, includingWalkOuts: true)
        let waitingListInBracket = waitingList.filter({ $0.bracketPosition != nil })
        let waitingListInGroupStage = waitingList.filter({ $0.groupStage != nil })

        return callDateIssue.count + duplicates.count + problematicPlayers.count + inadequatePlayers.count + playersWithoutValidLicense.count + playersMissing.count + waitingListInBracket.count + waitingListInGroupStage.count + ageInadequatePlayers.count + homonyms.count
    }

    func isStartDateIsDifferentThanCallDate(_ team: TeamRegistration, expectedSummonDate: Date? = nil) -> Bool {
        guard let summonDate = team.callDate else { return true }
        let expectedSummonDate : Date? = team.expectedSummonDate() ?? expectedSummonDate
        guard let expectedSummonDate else { return true }
        return Calendar.current.compare(summonDate, to: expectedSummonDate, toGranularity: .minute) != ComparisonResult.orderedSame
    }

    func groupStagesMatches(atStep step: Int = 0) -> [Match] {
        return groupStages(atStep: step).flatMap({ $0._matches() })
//        return Store.main.filter(isIncluded: { $0.groupStage != nil && groupStageIds.contains($0.groupStage!) })
    }

    static let defaultSorting : [MySortDescriptor<Match>] = [.keyPath(\Match.computedStartDateForSorting), .keyPath(\Match.computedOrder)]

    static func availableToStart(_ allMatches: [Match], in runningMatches: [Match], checkCanPlay: Bool = true) -> [Match] {
        #if _DEBUG_TIME //DEBUGING TIME
        let start = Date()
        defer {
            let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
            print("func tournament availableToStart", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
        }
        #endif
        return allMatches.filter({  $0.isRunning() == false && $0.canBeStarted(inMatches: runningMatches, checkCanPlay: checkCanPlay) }).sorted(using: defaultSorting, order: .ascending)
    }

    static func runningMatches(_ allMatches: [Match]) -> [Match] {
        #if _DEBUG_TIME //DEBUGING TIME
        let start = Date()
        defer {
            let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
            print("func tournament runningMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
        }
        #endif
        return allMatches.filter({ $0.isRunning() && $0.isReady() }).sorted(using: defaultSorting, order: .ascending)
    }

    static func readyMatches(_ allMatches: [Match]) -> [Match] {
        #if _DEBUG_TIME //DEBUGING TIME
        let start = Date()
        defer {
            let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
            print("func tournament readyMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
        }
        #endif
        return allMatches.filter({ $0.isReady() && $0.isRunning() == false && $0.hasEnded() == false }).sorted(using: defaultSorting, order: .ascending)
    }

    static func matchesLeft(_ allMatches: [Match]) -> [Match] {
        #if _DEBUG_TIME //DEBUGING TIME
        let start = Date()
        defer {
            let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
            print("func tournament readyMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
        }
        #endif
        return allMatches.filter({ $0.isRunning() == false && $0.hasEnded() == false }).sorted(using: defaultSorting, order: .ascending)
    }


    static func finishedMatches(_ allMatches: [Match], limit: Int?) -> [Match] {
        #if _DEBUG_TIME //DEBUGING TIME
        let start = Date()
        defer {
            let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
            print("func tournament finishedMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
        }
        #endif
        if let limit {
            return Array(allMatches.filter({ $0.hasEnded() }).sorted(by: \.computedEndDateForSorting).reversed().prefix(limit))
        } else {
            return Array(allMatches.filter({ $0.hasEnded() }).sorted(by: \.computedEndDateForSorting).reversed())
        }
    }

    func teamsRanked() -> [TeamRegistration] {
        let selected = selectedSortedTeams().filter({ $0.finalRanking != nil })
        return selected.sorted(by: \.finalRanking!, order: .ascending)
    }

    private func _removeStrings(from dictionary: inout [Int: [String]], stringsToRemove: [String]) {
        for key in dictionary.keys {
            if var stringArray = dictionary[key] {
                // Remove all instances of each string in stringsToRemove
                stringArray.removeAll { stringsToRemove.contains($0) }
                dictionary[key] = stringArray
            }
        }
    }


    func finalRanking() async -> [Int: [String]] {
        var teams: [Int: [String]] = [:]
        var ids: Set<String> = Set<String>()
        let rounds = rounds()
        let lastStep = lastStep()
        if rounds.isEmpty, lastStep > 0 {
            let groupStages = groupStages(atStep: lastStep)

            for groupStage in groupStages {
                let groupStageTeams = groupStage.teams(true)
                for teamIndex in 0..<groupStageTeams.count {
                    teams[groupStage.index * groupStage.size + 1 + teamIndex] = [groupStageTeams[teamIndex].id]
                }
            }
        } else {

            let final = rounds.last?.playedMatches().last
            if let winner = final?.winningTeamId {
                teams[1] = [winner]
                ids.insert(winner)
            }
            if let finalist = final?.losingTeamId {
                teams[2] = [finalist]
                ids.insert(finalist)
            }

            let others: [Round] = rounds.flatMap { round in
                let losers = round.losers()
                let minimumFinalPosition = round.seedInterval()?.last ?? teamCount
                if teams[minimumFinalPosition] == nil {
                    teams[minimumFinalPosition] = losers.map { $0.id }
                } else {
                    teams[minimumFinalPosition]?.append(contentsOf: losers.map { $0.id })
                }

                print("round", round.roundTitle())
                let rounds = round.loserRoundsAndChildren().filter { $0.isRankDisabled() == false && $0.hasNextRound() == false }
                print(rounds.count, rounds.map { $0.roundTitle() })
                return rounds
            }.compactMap({ $0 })

            others.forEach { round in
                print("round", round.roundTitle())
                if let interval = round.seedInterval() {
                    print("interval", interval.localizedInterval())
                    let playedMatches = round.playedMatches().filter { $0.disabled == false || $0.isReady() }
                    print("playedMatches", playedMatches.count)
                    let winners = playedMatches.compactMap({ $0.winningTeamId }).filter({ ids.contains($0) == false })
                    print("winners", winners.count)
                    let losers = playedMatches.compactMap({ $0.losingTeamId }).filter({ ids.contains($0) == false })
                    print("losers", losers.count)
                    if winners.isEmpty {
                        let disabledIds = playedMatches.flatMap({ $0.teamScores.compactMap({ $0.teamRegistration }) }).filter({ ids.contains($0) == false })
                        if disabledIds.isEmpty == false {
                            _removeStrings(from: &teams, stringsToRemove: disabledIds)
                            teams[interval.last] = disabledIds
                            let teamNames : [String] = disabledIds.compactMap {
                                let t : TeamRegistration? = self.tournamentStore?.teamRegistrations.findById($0)
                                return t
                            }.map { $0.canonicalName }
                            print("winners.isEmpty", "\(interval.last) : ", teamNames)
                            disabledIds.forEach {
                                ids.insert($0)
                            }
                        }
                    } else {
                        if winners.isEmpty == false {
                            _removeStrings(from: &teams, stringsToRemove: winners)
                            teams[interval.first + winners.count - 1] = winners
                            let teamNames : [String] = winners.compactMap {
                                let t: TeamRegistration? = tournamentStore?.teamRegistrations.findById($0)
                                return t
                            }.map { $0.canonicalName }
                            print("winners", "\(interval.last + winners.count - 1) : ", teamNames)
                            winners.forEach { ids.insert($0) }
                        }

                        if losers.isEmpty == false {
                            _removeStrings(from: &teams, stringsToRemove: losers)
                            teams[interval.first + winners.count] = losers
                            let loserTeamNames : [String] = losers.compactMap {
                                let t: TeamRegistration? = tournamentStore?.teamRegistrations.findById($0)
                                return t
                            }.map { $0.canonicalName }
                            print("losers", "\(interval.first + winners.count) : ", loserTeamNames)
                            losers.forEach { ids.insert($0) }
                        }
                    }
                }
            }

            if let groupStageLoserBracketPlayedMatches = groupStageLoserBracket()?.playedMatches() {
                groupStageLoserBracketPlayedMatches.forEach({ match in
                    if match.hasEnded() {
                        let sameMatchIndexCount = groupStageLoserBracketPlayedMatches.filter({ $0.index == match.index }).count
                        teams.setOrAppend(match.winningTeamId, at: match.index)
                        teams.setOrAppend(match.losingTeamId, at: match.index + sameMatchIndexCount)
                    }
                })
            }

            let groupStages = groupStages()
            var baseRank = teamCount - groupStageSpots() + qualifiedPerGroupStage * groupStageCount + groupStageAdditionalQualified
            
            if disableRankingFederalRuling == false {
                baseRank += qualifiedPerGroupStage * groupStageCount + groupStageAdditionalQualified - 1
            }
            let alreadyPlaceTeams = Array(teams.values.flatMap({ $0 }))
            groupStages.forEach { groupStage in
                let groupStageTeams = groupStage.teams(true)
                for (index, team) in groupStageTeams.enumerated() {
                    if groupStage.hasEnded() {
                        if team.qualified == false && alreadyPlaceTeams.contains(team.id) == false {
                            let groupStageWidth = max(((index == qualifiedPerGroupStage) ? groupStageCount - groupStageAdditionalQualified : groupStageCount) * (index - qualifiedPerGroupStage), 0)

                            let _index = baseRank + groupStageWidth + 1 - (index > qualifiedPerGroupStage ? groupStageAdditionalQualified : 0)
                            print("finalRanking", team.teamLabel() , _index, baseRank, groupStageWidth)
                            if let existingTeams = teams[_index] {
                                teams[_index] = existingTeams + [team.id]
                            } else {
                                teams[_index] = [team.id]
                            }
                        }
                    }
                }
            }
        }

        return teams
    }

    func setRankings(assimilationLevel: TournamentLevel? = nil, finalRanks: [Int: [String]]) async -> [Int: [TeamRegistration]] {
        guard let tournamentStore = self.tournamentStore else { return [:] }
        let tournamentLevel = assimilationLevel ?? tournamentLevel
        var rankings: [Int: [TeamRegistration]] = [:]

        finalRanks.keys.sorted().forEach { rank in
            if let rankedTeamIds = finalRanks[rank] {
                let teams: [TeamRegistration] = rankedTeamIds.compactMap { tournamentStore.teamRegistrations.findById($0) }
                rankings[rank] = teams
            }
        }

        rankings.keys.sorted().forEach { rank in
            if let rankedTeams = rankings[rank] {
                rankedTeams.forEach { team in
                    team.finalRanking = rank
                    team.pointsEarned = isAnimation() ? nil : tournamentLevel.points(for: rank - 1, count: teamCount)
                }
            }
        }

        do {
            try self.tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: unsortedTeams())
        } catch {
            Logger.error(error)
        }

        if self.publishRankings == false {
            self.publishRankings = true
            do {
                try DataStore.shared.tournaments.addOrUpdate(instance: self)
            } catch {
                Logger.error(error)
            }
        }

        return rankings
    }

    func lockRegistration() {
        closedRegistrationDate = Date()
        let count = selectedSortedTeams().count
        if teamCount != count {
            teamCount = count
        }
        let teams = unsortedTeams()
        teams.forEach { team in
            team.lockedWeight = team.weight
        }
        self.tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: teams)
    }

    func unlockRegistration() {
        closedRegistrationDate = nil
        let teams = unsortedTeams()
        teams.forEach { team in
            team.lockedWeight = nil
        }
        self.tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: teams)
    }

    func updateWeights() {
        let teams = self.unsortedTeams()
        teams.forEach { team in
            let players = team.unsortedPlayers()
            players.forEach { $0.setComputedRank(in: self) }
            team.setWeight(from: players, inTournamentCategory: tournamentCategory)
            self.tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: players)
        }
        self.tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: teams)
    }
    
    func updateRank(to newDate: Date?, forceRefreshLockWeight: Bool, providedSources: [CSVParser]?) async throws {
        refreshRanking = true
        #if DEBUG_TIME
        let start = Date()
        defer {
            let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
            print("func updateRank()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
        }
        #endif

        guard let newDate else { return }
        rankSourceDate = newDate

        // Fetch current month data only once
        var monthData = currentMonthData()
        
        if monthData == nil {
            async let lastRankWoman = SourceFileManager.shared.getUnrankValue(forMale: false, rankSourceDate: rankSourceDate)
            async let lastRankMan = SourceFileManager.shared.getUnrankValue(forMale: true, rankSourceDate: rankSourceDate)

            let formatted = URL.importDateFormatter.string(from: newDate)
            let newMonthData = MonthData(monthKey: formatted)

            newMonthData.maleUnrankedValue = await lastRankMan
            newMonthData.femaleUnrankedValue = await lastRankWoman

            do {
                try DataStore.shared.monthData.addOrUpdate(instance: newMonthData)
            } catch {
                Logger.error(error)
            }
            
            monthData = newMonthData
        }

        let lastRankMan = monthData?.maleUnrankedValue
        let lastRankWoman = monthData?.femaleUnrankedValue

        var chunkedParsers: [CSVParser] = []
        if let providedSources {
            chunkedParsers = providedSources
        } else {
            // Fetch only the required files
            let dataURLs = SourceFileManager.shared.allFiles.filter { $0.dateFromPath == newDate }
            guard !dataURLs.isEmpty else { return } // Early return if no files found

            let sources = dataURLs.map { CSVParser(url: $0) }
            chunkedParsers = try await chunkAllSources(sources: sources, size: 10000)
        }
        
        let players = unsortedPlayers()
        try await players.concurrentForEach { player in
            let lastRank = (player.sex == .female) ? lastRankWoman : lastRankMan
            try await player.updateRank(from: chunkedParsers, lastRank: lastRank)
            player.setComputedRank(in: self)
        }
        
        if providedSources == nil {
            try chunkedParsers.forEach { chunk in
                try FileManager.default.removeItem(at: chunk.url)
            }
        }
        
        try tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: players)
        
        let unsortedTeams = unsortedTeams()
        unsortedTeams.forEach { team in
            team.setWeight(from: team.players(), inTournamentCategory: tournamentCategory)
            if forceRefreshLockWeight {
                team.lockedWeight = team.weight
            }
        }
        try tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: unsortedTeams)
        refreshRanking = false
    }


    func missingUnrankedValue() -> Bool {
        return maleUnrankedValue == nil || femaleUnrankedValue == nil
    }

    func findTeam(_ players: [PlayerRegistration]) -> TeamRegistration? {
        return unsortedTeams().first(where: { $0.includes(players: players) })
    }

    func tournamentTitle(_ displayStyle: DisplayStyle = .wide, hideSenior: Bool = false) -> String {
        if tournamentLevel == .unlisted, displayStyle == .title {
            if let name {
                return name
            } else {
                return tournamentLevel.localizedLevelLabel(.title)
            }
        }
        let displayStyleCategory = hideSenior ? .short : displayStyle
        var levelCategory = [tournamentLevel.localizedLevelLabel(displayStyle), tournamentCategory.localizedLabel(displayStyle)]
        if displayStyle == .short {
            levelCategory = [tournamentLevel.localizedLevelLabel(displayStyle) + tournamentCategory.localizedLabel(displayStyle)]
        }
        let array = levelCategory + [federalTournamentAge.localizedFederalAgeLabel(displayStyleCategory)]
        let title: String = array.filter({ $0.isEmpty == false }).joined(separator: " ")
        if displayStyle == .wide, let name {
            return [title, name].joined(separator: " - ")
        } else {
            return title
        }
    }

    func localizedTournamentType() -> String {
        switch tournamentLevel {
        case .unlisted:
            return tournamentLevel.localizedLevelLabel(.short)
        default:
            return tournamentLevel.localizedLevelLabel(.short) + tournamentCategory.localizedLabel(.short)
        }
    }

    func hideWeight() -> Bool {
        return hideTeamsWeight
    }

    func isAnimation() -> Bool {
        federalLevelCategory.isAnimation()
    }

    func subtitle(_ displayStyle: DisplayStyle = .wide) -> String {
        return name ?? ""
    }

    func formattedDate(_ displayStyle: DisplayStyle = .wide) -> String {
        switch displayStyle {
        case .title:
            startDate.formatted(.dateTime.weekday(.abbreviated).day().month(.abbreviated).year())
        case .wide:
            startDate.formatted(date: Date.FormatStyle.DateStyle.complete, time: Date.FormatStyle.TimeStyle.omitted)
        case .short:
            startDate.formatted(date: .numeric, time: .omitted)
        }
    }

    func qualifiedFromGroupStage() -> Int {
        return groupStageCount * qualifiedPerGroupStage
    }


    func availableQualifiedTeams() -> [TeamRegistration] {
#if _DEBUG_TIME //DEBUGING TIME
let start = Date()
defer {
    let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
    print("func availableQualifiedTeams()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif

        return unsortedTeams().filter({ $0.qualified && $0.bracketPosition == nil })
    }

    func qualifiedTeams() -> [TeamRegistration] {
        return unsortedTeams().filter({ $0.qualified })
    }

    func moreQualifiedToDraw() -> Int {
        return max((qualifiedFromGroupStage() + groupStageAdditionalQualified) - qualifiedTeams().count, 0)
    }

    func missingQualifiedFromGroupStages() -> [TeamRegistration] {
        if groupStageAdditionalQualified > 0 && groupStagesAreOver() {
            return groupStages().filter { $0.hasEnded() }.compactMap { groupStage in
                groupStage.teams(true)[safe: qualifiedPerGroupStage]
            }
            .filter({ $0.qualified == false })
        } else {
            return []
        }
    }

    func groupStagesAreOver(atStep: Int = 0) -> Bool {
        let groupStages = groupStages(atStep: atStep)
        guard groupStages.isEmpty == false else {
            return true
        }
        return groupStages.allSatisfy({ $0.hasEnded() })
        //return qualifiedTeams().count == qualifiedFromGroupStage() + groupStageAdditionalQualified
    }

    func groupStageLoserBracketAreOver() -> Bool {
        guard let groupStageLoserBracket = groupStageLoserBracket() else {
            return true
        }
        return groupStageLoserBracket.hasEnded()
    }

    fileprivate func _paymentMethodMessage() -> String? {
        return DataStore.shared.user.summonsAvailablePaymentMethods ?? ContactType.defaultAvailablePaymentMethods
    }

    var entryFeeMessage: String {
        if let entryFee {
            let message: String = "Inscription : \(entryFee.formatted(.currency(code: Locale.defaultCurrency()))) par joueur."
            return [message, self._paymentMethodMessage()].compactMap { $0 }.joined(separator: "\n")
        } else {
            return "Inscription : gratuite."
        }
    }

    func umpireMail() -> [String]? {
        return [umpireCustomMail ?? DataStore.shared.user.email]
    }

    func earnings() -> Double {
        if let entryFee {
            return Double(selectedPlayers().filter { $0.hasPaid() }.count) * entryFee
        } else {
            return 0.0
        }
    }

    func paidCompletion() -> Double {
        let selectedPlayers = selectedPlayers()
        if selectedPlayers.isEmpty { return 0 }
        return Double(selectedPlayers.filter { $0.hasPaid() }.count) / Double(selectedPlayers.count)
    }

    func presenceStatus() -> Double {
        let selectedPlayers = selectedPlayers()
        if selectedPlayers.isEmpty { return 0 }
        return Double(selectedPlayers.filter { $0.hasArrived }.count) / Double(selectedPlayers.count)
    }

    typealias TournamentStatus = (label:String, completion: String)
    func cashierStatus() async -> TournamentStatus {
        let selectedPlayers = selectedPlayers()
        var filteredPlayers = [PlayerRegistration]()
        var wording = ""
        if isFree() {
            wording = "présent"
            filteredPlayers = selectedPlayers.filter({ $0.hasArrived })
        } else {
            wording = "encaissé"
            filteredPlayers = selectedPlayers.filter({ $0.hasPaid() })
        }
//        let label = paid.count.formatted() + " / " + selectedPlayers.count.formatted() + " joueurs encaissés"
        let label = "\(filteredPlayers.count.formatted()) / \(selectedPlayers.count.formatted()) joueurs \(wording)\(filteredPlayers.count.pluralSuffix)"
        let completion = (Double(filteredPlayers.count) / Double(selectedPlayers.count))
        let completionLabel = completion.isNaN ? "" : completion.formatted(.percent.precision(.fractionLength(0)))
        return TournamentStatus(label: label, completion: completionLabel)
    }

    func scheduleStatus() async -> TournamentStatus {
        let allMatches = allMatches()
        let ready = allMatches.filter({ $0.startDate != nil })
//        let label = ready.count.formatted() + " / " + allMatches.count.formatted() + " matchs programmés"
        let label = "\(ready.count.formatted()) / \(allMatches.count.formatted()) matchs programmés"
        let completion = (Double(ready.count) / Double(allMatches.count))
        let completionLabel = completion.isNaN ? "" : completion.formatted(.percent.precision(.fractionLength(0)))
        return TournamentStatus(label: label, completion: completionLabel)
    }

    func callStatus() async -> TournamentStatus {
        let selectedSortedTeams = selectedSortedTeams()
        let called = selectedSortedTeams.filter { isStartDateIsDifferentThanCallDate($0) == false }
        let justCalled = selectedSortedTeams.filter { $0.called() }

        let label = "\(justCalled.count.formatted()) / \(selectedSortedTeams.count.formatted()) (\(called.count.formatted()) au bon horaire)"
        let completion = (Double(called.count) / Double(selectedSortedTeams.count))
        let completionLabel = completion.isNaN ? "" : completion.formatted(.percent.precision(.fractionLength(0)))
        return TournamentStatus(label: label, completion: completionLabel)
    }

    func confirmedSummonStatus() async -> TournamentStatus {
        let selectedSortedTeams = selectedSortedTeams()
        let called = selectedSortedTeams.filter { $0.confirmationDate != nil }
        let label = "\(called.count.formatted()) / \(selectedSortedTeams.count.formatted()) confirmées"
        let completion = (Double(called.count) / Double(selectedSortedTeams.count))
        let completionLabel = completion.isNaN ? "" : completion.formatted(.percent.precision(.fractionLength(0)))
        return TournamentStatus(label: label, completion: completionLabel)
    }

    func bracketStatus() async -> (status: String, description: String?, cut: TeamRegistration.TeamRange?) {
        let availableSeeds = availableSeeds()
        var description: String? = nil
        if availableSeeds.isEmpty == false {
            description = "placer \(availableSeeds.count) équipe\(availableSeeds.count.pluralSuffix)"
        }
        if description == nil {
            let availableQualifiedTeams = availableQualifiedTeams()
            if availableQualifiedTeams.isEmpty == false {
                description = "placer \(availableQualifiedTeams.count) qualifié" + availableQualifiedTeams.count.pluralSuffix
            }
        }

        var cut: TeamRegistration.TeamRange? = nil
        if description == nil && isAnimation() == false {
            cut = TeamRegistration.TeamRange(availableSeeds.first, availableSeeds.last)
        }

        if let round = getActiveRound() {
            return ([round.roundTitle(.short), round.roundStatus()].joined(separator: " ").lowercased(), description, cut)
        } else {
            return ("", description, nil)
        }
    }

    func groupStageStatus() async -> (status: String, cut: TeamRegistration.TeamRange?) {
        let groupStageTeams = groupStageTeams()
        let groupStageTeamsCount = groupStageTeams.count
        if groupStageTeamsCount == 0 || groupStageTeamsCount != groupStageSpots() {
            return ("à compléter", nil)
        }

        let cut : TeamRegistration.TeamRange? = isAnimation() ? nil : TeamRegistration.TeamRange(groupStageTeams.first, groupStageTeams.last)

        let runningGroupStages = groupStages().filter({ $0.isRunning() })
        if groupStagesAreOver() { return ("terminées", cut) }
        if runningGroupStages.isEmpty {

            let ongoingGroupStages = runningGroupStages.filter({ $0.hasStarted() && $0.hasEnded() == false })
            if ongoingGroupStages.isEmpty == false {
                return ("Poule" + ongoingGroupStages.count.pluralSuffix + " " + ongoingGroupStages.map { ($0.index + 1).formatted() }.joined(separator: ", ") + " en cours", cut)
            }
            return (groupStages().count.formatted() + " poule" + groupStages().count.pluralSuffix, cut)
        } else {
            return ("Poule" + runningGroupStages.count.pluralSuffix + " " + runningGroupStages.map { ($0.index + 1).formatted() }.joined(separator: ", ") + " en cours", cut)
        }
    }

    func settingsDescriptionLocalizedLabel() -> String {
        [courtCount.formatted() + " terrain\(courtCount.pluralSuffix)", entryFeeMessage].joined(separator: ", ")
    }

    func structureDescriptionLocalizedLabel() -> String {
        let groupStageLabel: String? = groupStageCount > 0 ? groupStageCount.formatted() + " poule\(groupStageCount.pluralSuffix)" : nil
        return [teamCount.formatted() + " équipes", groupStageLabel].compactMap({ $0 }).joined(separator: ", ")
    }

    func deleteAndBuildEverything(preset: PadelTournamentStructurePreset = .manual) {
        resetBracketPosition()
        deleteStructure()
        deleteGroupStages()

        switch preset {
        case .doubleGroupStage:
            buildGroupStages()
            addNewGroupStageStep()
            qualifiedPerGroupStage = 0
            groupStageAdditionalQualified = 0
        default:
            buildGroupStages()
            buildBracket()
        }
    }

    func addWildCardIfNeeded(_ count: Int, _ type: MatchType) {
        let currentCount = selectedSortedTeams().filter({
            if type == .bracket {
                return $0.wildCardBracket
            } else {
                return $0.wildCardGroupStage
            }
        }).count

        if currentCount < count {
            let _diff = count - currentCount
            addWildCard(_diff, type)
        }
    }

    func addWildCard(_ count: Int, _ type: MatchType) {
        let wcs = (0..<count).map { _ in
            let team = TeamRegistration(tournament: id)
            if type == .bracket {
                team.wildCardBracket = true
            } else {
                team.wildCardGroupStage = true
            }

            team.setWeight(from: [], inTournamentCategory: self.tournamentCategory)
            team.weight += 200_000
            return team
        }

        do {
            try self.tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: wcs)
        } catch {
            Logger.error(error)
        }
    }

    func addEmptyTeamRegistration(_ count: Int) {

        guard let tournamentStore = self.tournamentStore else { return }

        let teams = (0..<count).map { _ in
            let team = TeamRegistration(tournament: id)
            team.setWeight(from: [], inTournamentCategory: self.tournamentCategory)
            return team
        }

        do {
            try tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teams)
        } catch {
            Logger.error(error)
        }
    }

    func buildGroupStages() {
        guard groupStages().isEmpty, let tournamentStore = self.tournamentStore else {
            return
        }

        var _groupStages = [GroupStage]()
        for index in 0..<groupStageCount {
            let groupStage = GroupStage(tournament: id, index: index, size: teamsPerGroupStage, format: groupStageFormat)
            _groupStages.append(groupStage)
        }

        tournamentStore.groupStages.addOrUpdate(contentOfs: _groupStages)
        refreshGroupStages()
    }

    func bracketTeamCount() -> Int {
        let bracketTeamCount = teamCount - (teamsPerGroupStage - qualifiedPerGroupStage) * groupStageCount + (groupStageAdditionalQualified * (groupStageCount > 0 ? 1 : 0))
        return bracketTeamCount
    }

    func buildBracket(minimalBracketTeamCount: Int? = nil) {
        guard rounds().isEmpty else { return }
        let roundCount = RoundRule.numberOfRounds(forTeams: minimalBracketTeamCount ?? bracketTeamCount())
        let matchCount = RoundRule.numberOfMatches(forTeams: minimalBracketTeamCount ?? bracketTeamCount())

        let rounds = (0..<roundCount).map { //index 0 is the final
            return Round(tournament: id, index: $0, matchFormat: matchFormat, loserBracketMode: loserBracketMode)
        }

        if rounds.isEmpty {
            return
        }

        do {
            try self.tournamentStore?.rounds.addOrUpdate(contentOfs: rounds)
        } catch {
            Logger.error(error)
        }

        let matches = (0..<matchCount).map { //0 is final match
            let roundIndex = RoundRule.roundIndex(fromMatchIndex: $0)
            let round = rounds[roundIndex]
            return Match(round: round.id, index: $0, format: round.matchFormat, name: Match.setServerTitle(upperRound: round, matchIndex: RoundRule.matchIndexWithinRound(fromMatchIndex: $0)))
        }

        print(matches.map {
            (RoundRule.roundName(fromMatchIndex: $0.index), RoundRule.matchIndexWithinRound(fromMatchIndex: $0.index))
        })

        self.tournamentStore?.matches.addOrUpdate(contentOfs: matches)

        rounds.forEach { round in
            round.buildLoserBracket()
        }
    }

    func match(for bracketPosition: Int?) -> Match? {
        guard let bracketPosition else { return nil }
        let matchIndex = bracketPosition / 2
        let roundIndex = RoundRule.roundIndex(fromMatchIndex: matchIndex)
        if let round: Round = self.getRound(atRoundIndex: roundIndex) {
            return self.tournamentStore?.matches.first(where: { $0.round == round.id && $0.index == matchIndex })
//            return Store.main.filter(isIncluded: { $0.round == round.id && $0.index == matchIndex }).first

        }
        return nil
    }

    func resetTeamScores(in matchOfBracketPosition: Int?, outsideOf: [TeamScore] = []) {
        guard let match = match(for: matchOfBracketPosition) else { return }
        match.resetTeamScores(outsideOf: outsideOf)
    }

    func updateTeamScores(in matchOfBracketPosition: Int?) {
        guard let match = match(for: matchOfBracketPosition) else { return }
        match.updateTeamScores()
    }

    func deleteStructure() {
        self.tournamentStore?.rounds.delete(contentOfs: rounds())
    }

    func resetBracketPosition() {
        unsortedTeams().forEach({ $0.bracketPosition = nil })
    }

    func deleteGroupStages() {
        self.tournamentStore?.groupStages.delete(contentOfs: allGroupStages())
    }

    func refreshGroupStages(keepExistingMatches: Bool = false) {
        unsortedTeams().forEach { team in
            team.groupStage = nil
            team.groupStagePosition = nil
        }

        if groupStageCount > 0 {
            switch groupStageOrderingMode {
            case .random:
                setGroupStage(randomize: true, keepExistingMatches: keepExistingMatches)
            case .snake:
                setGroupStage(randomize: false, keepExistingMatches: keepExistingMatches)
            case .swiss:
                setGroupStage(randomize: true, keepExistingMatches: keepExistingMatches)
            }
        }
    }

    func setGroupStage(randomize: Bool, keepExistingMatches: Bool = false) {
        let groupStages = groupStages()
        let numberOfBracketsAsInt = groupStages.count
//        let teamsPerBracket = teamsPerBracket
        if groupStageCount != numberOfBracketsAsInt {
            deleteGroupStages()
            buildGroupStages()
        } else {
            setGroupStageTeams(randomize: randomize)
            groupStages.forEach { $0.buildMatches(keepExistingMatches: keepExistingMatches) }
        }
    }

    func removeWildCards() {
        let wcs = unsortedTeams().filter({ $0.isWildCard() && $0.unsortedPlayers().isEmpty })
        do {
            try tournamentStore?.teamRegistrations.delete(contentOfs: wcs)
        } catch {
            Logger.error(error)
        }
    }
    
    func setGroupStageTeams(randomize: Bool) {
        let groupStages = groupStages()
        let max = groupStages.map { $0.size }.reduce(0,+)
        var chunks = selectedSortedTeams().filter({ $0.wildCardBracket == false }).suffix(max).chunked(into: groupStageCount)
        for (index, _) in chunks.enumerated() {
            if randomize {
                chunks[index].shuffle()
            } else if index % 2 != 0 {
                chunks[index].reverse()
            }

            print("Equipes \(chunks[index].map { $0.weight })")
            for (jIndex, _) in chunks[index].enumerated() {
                print("Position \(index + 1) Poule \(groupStages[jIndex].index)")
                chunks[index][jIndex].groupStage = groupStages[jIndex].id
                chunks[index][jIndex].groupStagePosition = index
            }
        }

        self.tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: unsortedTeams())
    }

    func isFree() -> Bool {
        return entryFee == nil || entryFee == 0
    }

    func indexOf(team: TeamRegistration) -> Int? {
        return selectedSortedTeams().firstIndex(where: { $0.id == team.id })
    }

    func labelIndexOf(team: TeamRegistration) -> String? {
        if let teamIndex = indexOf(team: team) {
            return "Tête de série #" + (teamIndex + 1).formatted()
        } else {
            return nil
        }
    }

    func addTeam(_ players: Set<PlayerRegistration>, registrationDate: Date? = nil, name: String? = nil) -> TeamRegistration {
        let team = TeamRegistration(tournament: id, registrationDate: registrationDate, name: name)
        team.setWeight(from: Array(players), inTournamentCategory: tournamentCategory)
        players.forEach { player in
            player.teamRegistration = team.id
        }
        if isAnimation() {
            if team.weight == 0 {
                team.weight = unsortedTeams().count
            }
        }
        return team
    }

    var matchFormat: MatchFormat {
        get {
            roundFormat ?? .defaultFormatForMatchType(.bracket)
        }
        set {
            roundFormat = newValue
        }
    }

    var groupStageMatchFormat: MatchFormat {
        get {
            groupStageFormat ?? .defaultFormatForMatchType(.groupStage)
        }
        set {
            groupStageFormat = newValue
        }
    }

    var loserBracketMatchFormat: MatchFormat {
        get {
            loserRoundFormat ?? .defaultFormatForMatchType(.loserBracket)
        }
        set {
            loserRoundFormat = newValue
        }
    }

    var groupStageOrderingMode: GroupStageOrderingMode {
        get {
            groupStageSortMode
        }
        set {
            groupStageSortMode = newValue
        }
    }

    var tournamentCategory: TournamentCategory {
        get {
            federalCategory
        }
        set {
            if federalCategory != newValue {
                federalCategory = newValue
                updateWeights()
            } else {
                federalCategory = newValue
            }
        }
    }

    var tournamentLevel: TournamentLevel {
        get {
            federalLevelCategory
        }
        set {
            federalLevelCategory = newValue
            teamSorting = newValue.defaultTeamSortingType
            groupStageMatchFormat = groupStageSmartMatchFormat()
            loserBracketMatchFormat = loserBracketSmartMatchFormat(1)
            matchFormat = roundSmartMatchFormat(5)
        }
    }

    var federalTournamentAge: FederalTournamentAge {
        get {
            federalAgeCategory
        }
        set {
            federalAgeCategory = newValue
        }
    }

    func loserBracketSmartMatchFormat(_ roundIndex: Int) -> MatchFormat {
        let format = tournamentLevel.federalFormatForLoserBracketRound(roundIndex)
        if tournamentLevel == .p25 { return .superTie }
        if format.rank < loserBracketMatchFormat.rank {
            return format
        } else {
            return loserBracketMatchFormat
        }
    }

    func groupStageSmartMatchFormat() -> MatchFormat {
        let format = tournamentLevel.federalFormatForGroupStage()
        if tournamentLevel == .p25 { return .superTie }
        if format.rank < groupStageMatchFormat.rank {
            return format
        } else {
            return groupStageMatchFormat
        }
    }

    func setupFederalSettings() {
        teamSorting = tournamentLevel.defaultTeamSortingType
        groupStageMatchFormat = groupStageSmartMatchFormat()
        loserBracketMatchFormat = loserBracketSmartMatchFormat(5)
        matchFormat = roundSmartMatchFormat(5)
        entryFee = tournamentLevel.entryFee
        registrationDateLimit = deadline(for: .inscription)
    }

    func customizeUsingPreferences() {
        guard let lastTournamentWithSameBuild = DataStore.shared.tournaments.filter({ tournament in
            tournament.tournamentLevel == self.tournamentLevel
            && tournament.tournamentCategory == self.tournamentCategory
            && tournament.federalTournamentAge == self.federalTournamentAge
            && tournament.hasEnded() == true
            && tournament.isCanceled == false
            && tournament.isDeleted == false
        }).sorted(by: \.endDate!, order: .descending).first else {
            return
        }

        self.dayDuration = lastTournamentWithSameBuild.dayDuration
        self.teamCount = (lastTournamentWithSameBuild.teamCount / 2) * 2
        self.enableOnlineRegistration = lastTournamentWithSameBuild.enableOnlineRegistration
    }

    func onlineRegistrationCanBeEnabled() -> Bool {
        isAnimation() == false
    }

    func roundSmartMatchFormat(_ roundIndex: Int) -> MatchFormat {
        let format = tournamentLevel.federalFormatForBracketRound(roundIndex)
        if tournamentLevel == .p25 { return .superTie }
        if format.rank < matchFormat.rank {
            return format
        } else {
            return matchFormat
        }
    }

    private func _defaultSorting() -> [MySortDescriptor<TeamRegistration>] {
        switch teamSorting {
        case .rank:
            [.keyPath(\TeamRegistration.initialWeight), .keyPath(\TeamRegistration.id)]
        case .inscriptionDate:
            [.keyPath(\TeamRegistration.registrationDate!), .keyPath(\TeamRegistration.initialWeight), .keyPath(\TeamRegistration.id)]
        }
    }

    func isSameBuild(_ build: any TournamentBuildHolder) -> Bool {
        tournamentLevel == build.level
        && tournamentCategory == build.category
        && federalTournamentAge == build.age
    }

    private let _currentSelectionSorting : [MySortDescriptor<TeamRegistration>] = [.keyPath(\.weight), .keyPath(\.id)]

    private func _matchSchedulers() -> [MatchScheduler] {
        guard let tournamentStore = self.tournamentStore else { return [] }
        return tournamentStore.matchSchedulers.filter { $0.tournament == self.id }
//        DataStore.shared.matchSchedulers.filter(isIncluded: { $0.tournament == self.id })
    }

    func matchScheduler() -> MatchScheduler? {
        return self._matchSchedulers().first
    }

    func courtsAvailable() -> [Int] {
        (0..<courtCount).map { $0 }
    }

    func currentMonthData() -> MonthData? {
        guard let rankSourceDate else { return nil }
        let dateString = URL.importDateFormatter.string(from: rankSourceDate)
        return DataStore.shared.monthData.first(where: { $0.monthKey == dateString })
    }

    var maleUnrankedValue: Int? {
        return currentMonthData()?.maleUnrankedValue
    }

    var femaleUnrankedValue: Int? {
        return currentMonthData()?.femaleUnrankedValue
    }

    func courtNameIfAvailable(atIndex courtIndex: Int) -> String? {
        return club()?.customizedCourts.first(where: { $0.index == courtIndex })?.name
    }

    func courtName(atIndex courtIndex: Int) -> String {
        return courtNameIfAvailable(atIndex: courtIndex) ?? Court.courtIndexedTitle(atIndex: courtIndex)
    }

    func tournamentWinner() -> TeamRegistration? {
        let finals: Round? = self.tournamentStore?.rounds.first(where: { $0.index == 0 && $0.isUpperBracket() })
        return finals?.playedMatches().first?.winner()
    }

    func getGroupStageChunkValue() -> Int {
        if groupStageCount > 0 && teamsPerGroupStage >= 2 {
            let result = courtCount / (teamsPerGroupStage / 2)
            let remainder = courtCount % (teamsPerGroupStage / 2)
            let value = remainder == 0 ? result : result + 1
            return min(groupStageCount, value)
        } else {
            return 1
        }
    }

    func replacementRangeExtended(groupStagePosition: Int) -> TeamRegistration.TeamRange? {
        let selectedSortedTeams = selectedSortedTeams()
        var left: TeamRegistration? = nil
        if groupStagePosition == 0 {
            left = seeds().last
        } else {
            let previousHat = selectedSortedTeams.filter({ $0.groupStagePosition == groupStagePosition - 1 }).sorted(by: \.weight)
            left = previousHat.last
        }
        var right: TeamRegistration? = nil
        if groupStagePosition == teamsPerGroupStage - 1 {
            right = nil
        } else {
            let previousHat = selectedSortedTeams.filter({ $0.groupStagePosition == groupStagePosition + 1 }).sorted(by: \.weight)
            right = previousHat.first
        }
        return (left: left, right: right)
    }


    typealias TeamPlacementIssue = (shouldBeInIt: [String], shouldNotBeInIt: [String])
    func groupStageTeamPlacementIssue() -> TeamPlacementIssue {
        let selected = selectedSortedTeams()
        let allTeams = unsortedTeams()
        let newGroup = selected.suffix(groupStageSpots())
        let currentGroup = allTeams.filter({ $0.groupStagePosition != nil })
        let selectedIds = newGroup.map { $0.id }
        let groupIds = currentGroup.map { $0.id }
        let shouldBeInIt = Set(selectedIds).subtracting(groupIds)
        let shouldNotBeInIt = Set(groupIds).subtracting(selectedIds)
        return (Array(shouldBeInIt), Array(shouldNotBeInIt))
    }

    func bracketTeamPlacementIssue() -> TeamPlacementIssue {
        let selected = selectedSortedTeams()
        let allTeams = unsortedTeams()
        let seedCount = max(selected.count - groupStageSpots(), 0)
        let newGroup = selected.prefix(seedCount) + selected.filter({ $0.qualified })
        let currentGroup = allTeams.filter({ $0.bracketPosition != nil })
        let selectedIds = newGroup.map { $0.id }
        let groupStageTeamsInBracket = selected.filter({ $0.qualified == false && $0.inGroupStage() && $0.inRound() })
        let groupIds = currentGroup.map { $0.id } + groupStageTeamsInBracket.map { $0.id }
        let shouldBeInIt = Set(selectedIds).subtracting(groupIds)
        let shouldNotBeInIt = Set(groupIds).subtracting(selectedIds)
        return (Array(shouldBeInIt), Array(shouldNotBeInIt))
    }

    func groupStageLoserBracket() -> Round? {
        self.tournamentStore?.rounds.first(where: { $0.groupStageLoserBracket })
    }

    func groupStageLoserBracketsInitialPlace() -> Int {
        return teamCount - teamsPerGroupStage * groupStageCount + qualifiedPerGroupStage * groupStageCount + groupStageAdditionalQualified + 1
    }

    func addNewGroupStageStep() {
        let lastStep = lastStep() + 1
        for i in 0..<teamsPerGroupStage {
            let gs = GroupStage(tournament: id, index: i, size: groupStageCount, step: lastStep)
            self.tournamentStore?.groupStages.addOrUpdate(instance: gs)
        }

        groupStages(atStep: 1).forEach { $0.buildMatches() }
    }

    func lastStep() -> Int {
        self.tournamentStore?.groupStages.sorted(by: \.step).last?.step ?? 0
    }

    func generateSmartLoserGroupStageBracket() {
        guard let groupStageLoserBracket = groupStageLoserBracket() else { return }
        for i in qualifiedPerGroupStage..<teamsPerGroupStage {
            groupStages().chunked(into: 2).forEach { gss in
                let placeCount = i * 2 + 1
                let match = Match(round: groupStageLoserBracket.id, index: placeCount, format: groupStageLoserBracket.matchFormat)
                match.setMatchName("\(placeCount)\(placeCount.ordinalFormattedSuffix(feminine: true)) place")
                do {
                    try tournamentStore?.matches.addOrUpdate(instance: match)
                } catch {
                    Logger.error(error)
                }

                if let gs1 = gss.first, let gs2 = gss.last, let score1 = gs1.teams(true)[safe: i], let score2 = gs2.teams(true)[safe: i] {
                    print("rang \(i)")
                    print(score1.teamLabel(.short), "vs", score2.teamLabel(.short))

                    match.setLuckyLoser(team: score1, teamPosition: .one)
                    match.setLuckyLoser(team: score2, teamPosition: .two)
                }
            }
        }

    }

    func updateTournamentState() {
        Task {
            let fr = await finalRanking()
            _ = await setRankings(finalRanks: fr)
        }
    }

    func allLoserRoundMatches() -> [Match] {
        rounds().flatMap { $0.loserRoundsAndChildren().flatMap({ $0._matches() }) }
    }

    func seedsCount() -> Int {
        selectedSortedTeams().count - groupStageSpots()
    }

    func lastDrawnDate() -> Date? {
        drawLogs().last?.drawDate
    }

    func drawLogs() -> [DrawLog] {
        guard let tournamentStore = self.tournamentStore else { return [] }
        return tournamentStore.drawLogs.sorted(by: \.drawDate)
    }

    func seedSpotsLeft() -> Bool {
        let alreadySeededRounds = rounds().filter({ $0.seeds().isEmpty == false })
        if alreadySeededRounds.isEmpty { return true }
        let spotsLeft = alreadySeededRounds.flatMap({ $0.playedMatches() }).filter { $0.isEmpty() || $0.isValidSpot() }

        return spotsLeft.isEmpty == false
    }

    func isRoundValidForSeeding(roundIndex: Int) -> Bool {
        if let lastRoundWithSeeds = rounds().last(where: { $0.seeds().isEmpty == false }) {
            return roundIndex >= lastRoundWithSeeds.index
        } else {
            return true
        }
    }


    func updateSeedsBracketPosition() async {
        await removeAllSeeds(saveTeamsAtTheEnd: false)
        let drawLogs = drawLogs().reversed()
        let seeds = seeds()
        
        await MainActor.run {
            for (index, seed) in seeds.enumerated() {
                if let drawLog = drawLogs.first(where: { $0.drawSeed == index }) {
                    drawLog.updateTeamBracketPosition(seed)
                }
            }
        }

        do {
            try tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: seeds)
        } catch {
            Logger.error(error)
        }
    }
    
    func removeAllSeeds(saveTeamsAtTheEnd: Bool) async {
        let teams = unsortedTeams()
        teams.forEach({ team in
            team.bracketPosition = nil
            team._cachedRestingTime = nil
            team.finalRanking = nil
            team.pointsEarned = nil
        })
        let allMatches = allRoundMatches()
        let ts = allMatches.flatMap { match in
            match.teamScores
        }
        allMatches.forEach { match in
            match.disabled = false
            match.losingTeamId = nil
            match.winningTeamId = nil
            match.endDate = nil
            match.removeCourt()
            match.servingTeamId = nil
        }

        do {
            try tournamentStore?.teamScores.delete(contentOfs: ts)
        } catch {
            Logger.error(error)
        }

        if saveTeamsAtTheEnd {
            do {
                try tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: teams)
            } catch {
                Logger.error(error)
            }
        }
    }

    func addNewRound(_ roundIndex: Int) async {
        let round = Round(tournament: id, index: roundIndex, matchFormat: matchFormat)
        let matchCount = RoundRule.numberOfMatches(forRoundIndex: roundIndex)
        let matchStartIndex = RoundRule.matchIndex(fromRoundIndex: roundIndex)
        let nextRound = round.nextRound()
        var currentIndex = 0
        let matches = (0..<matchCount).map { index in //0 is final match
            let computedIndex = index + matchStartIndex
            let match = Match(round: round.id, index: computedIndex, format: round.matchFormat)
            if let nextRound, let followingMatch = self.tournamentStore?.matches.first(where: { $0.round == nextRound.id && $0.index == (computedIndex - 1) / 2 }) {
                if followingMatch.disabled {
                    match.disabled = true
                } else if computedIndex%2 == 1 && followingMatch.team(.one) != nil {
                    //index du match courant impair = position haut du prochain match
                    match.disabled = true
                } else if computedIndex%2 == 0 && followingMatch.team(.two) != nil {
                    //index du match courant pair = position basse du prochain match
                    match.disabled = true
                } else {
                    match.setMatchName(Match.setServerTitle(upperRound: round, matchIndex: currentIndex))
                    currentIndex += 1
                }
            } else {
                match.setMatchName(Match.setServerTitle(upperRound: round, matchIndex: currentIndex))
                currentIndex += 1
            }

            return match
        }
        do {
            try tournamentStore?.rounds.addOrUpdate(instance: round)
        } catch {
            Logger.error(error)
        }
        do {
            try tournamentStore?.matches.addOrUpdate(contentOfs: matches)
        } catch {
            Logger.error(error)
        }
        round.buildLoserBracket()
        matches.filter { $0.disabled }.forEach {
            $0._toggleLoserMatchDisableState(true)
        }
    }

    func exportedDrawLogs() -> String {
        var logs : [String] = ["Journal des tirages\n\n"]
        logs.append(drawLogs().map { $0.exportedDrawLog() }.joined(separator: "\n\n"))
        return logs.joined()
    }


    func courtUnavailable(courtIndex: Int, from startDate: Date, to endDate: Date) -> Bool {
        guard let source = eventObject()?.courtsUnavailability else { return false }
        let courtLockedSchedule = source.filter({ $0.courtIndex == courtIndex })
        return courtLockedSchedule.anySatisfy({ dateInterval in
            let range = startDate..<endDate
            return dateInterval.range.overlaps(range)
        })
    }

    func getOnlineRegistrationStatus() -> OnlineRegistrationStatus {
        if hasStarted() {
            return .inProgress
        }
        if closedRegistrationDate != nil {
            return .ended
        }
        if endDate != nil {
            return .endedWithResults
        }

        let now = Date()

        if let openingRegistrationDate = openingRegistrationDate {
            let timezonedDateTime = openingRegistrationDate // Assuming dates are already in local timezone
            if now < timezonedDateTime {
                return .notStarted
            }
        }

        if let registrationDateLimit = registrationDateLimit {
            let timezonedDateTime = registrationDateLimit // Assuming dates are already in local timezone
            if now > timezonedDateTime {
                return .ended
            }
        }

        let currentTeamCount = unsortedTeamsWithoutWO().count

        if currentTeamCount >= teamCount {
            if let waitingListLimit = waitingListLimit {
                let waitingListCount = currentTeamCount - teamCount
                if waitingListCount >= waitingListLimit {
                    return .waitingListFull
                }
            }
            return .waitingListPossible
        }

        return .open
    }

    // MARK: - Status
    func shouldTournamentBeOver() async -> Bool {
        return false
        if tournamentStore?.store.fileCollectionsAllLoaded() == false {
            return false
        }
#if _DEBUGING_TIME //DEBUGING TIME
let start = Date()
defer {
    let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
    print("func shouldTournamentBeOver()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif
        if isDeleted == false && hasEnded() == false && hasStarted() {
            let allMatches = allMatches()
            let remainingMatches = allMatches.filter({ $0.hasEnded() == false && $0.startDate != nil })

            let calendar = Calendar.current
            let anyTomorrow = remainingMatches.anySatisfy({ calendar.isDateInTomorrow($0.startDate!) })


            if anyTomorrow == false, let endDate = allMatches.filter({ $0.hasEnded() }).sorted(by: \.endDate!, order: .ascending).last?.endDate, endDate.timeIntervalSinceNow <= -2 * 3600 {
                return true
            }
        }

        return false
    }

    func rankSourceShouldBeRefreshed() -> Date? {
        if let mostRecentDate = SourceFileManager.shared.lastDataSourceDate(), let currentRankSourceDate = rankSourceDate, currentRankSourceDate < mostRecentDate, hasEnded() == false {
            return mostRecentDate
        } else {
            return nil
        }
    }
        
    func onlineTeams() -> [TeamRegistration] {
        unsortedTeams().filter({ $0.hasRegisteredOnline() })
    }
    
    func shouldWarnOnlineRegistrationUpdates() -> Bool {
        enableOnlineRegistration && onlineTeams().isEmpty == false && hasEnded() == false && hasStarted() == false
    }
    
    func refreshTeamList(forced: Bool) async {
        guard StoreCenter.main.isAuthenticated else { return }
        guard tournamentStore?.store.fileCollectionsAllLoaded() == true else { return }
        guard shouldRefreshTeams(forced: forced), refreshInProgress == false, enableOnlineRegistration, hasEnded() == false else { return }
        refreshInProgress = true
        do {
            try await self.tournamentStore?.playerRegistrations.loadDataFromServerIfAllowed(clear: true)
            try await self.tournamentStore?.teamScores.loadDataFromServerIfAllowed(clear: true)
            try await self.tournamentStore?.teamRegistrations.loadDataFromServerIfAllowed(clear: true)
            refreshInProgress = false
            lastTeamRefresh = Date()
        } catch {
            Logger.error(error)
            refreshInProgress = false
            lastTeamRefresh = Date()
        }
    }
    
    // MARK: -

    func insertOnServer() throws {

        DataStore.shared.tournaments.writeChangeAndInsertOnServer(instance: self)

        if let teamRegistrations = self.tournamentStore?.teamRegistrations {
            for teamRegistration in teamRegistrations {
                teamRegistration.insertOnServer()
            }
        }
        
        if let groupStages = self.tournamentStore?.groupStages {
            for groupStage in groupStages {
                groupStage.insertOnServer()
            }
        }
        if let rounds = self.tournamentStore?.rounds {
            for round in rounds {
                round.insertOnServer()
            }
        }

    }

    // MARK: - Payments & Crypto

    enum PaymentError: Error {
        case cantPayTournament
    }

    func payIfNecessary() throws {
        if self.payment != nil { return }
        if let payment = Guard.main.paymentForNewTournament() {
            self.payment = payment
            DataStore.shared.tournaments.addOrUpdate(instance: self)
            return
        }
        throw PaymentError.cantPayTournament
    }

}

extension Bool {
    var encodedValue: Int {
        switch self {
        case true:
            return Int.random(in: (0...4))
        case false:
            return Int.random(in: (5...9))
        }
    }
    static func decodeInt(_ int: Int) -> Bool {
        switch int {
        case (0...4):
            return true
        default:
            return false
        }
    }
}

//extension Tournament {
//    enum CodingKeys: String, CodingKey {
//        case _id = "id"
//        case _event = "event"
//        case _name = "name"
//        case _startDate = "startDate"
//        case _endDate = "endDate"
//        case _creationDate = "creationDate"
//        case _isPrivate = "isPrivate"
//        case _groupStageFormat = "groupStageFormat"
//        case _roundFormat = "roundFormat"
//        case _loserRoundFormat = "loserRoundFormat"
//        case _groupStageSortMode = "groupStageSortMode"
//        case _groupStageCount = "groupStageCount"
//        case _rankSourceDate = "rankSourceDate"
//        case _dayDuration = "dayDuration"
//        case _teamCount = "teamCount"
//        case _teamSorting = "teamSorting"
//        case _federalCategory = "federalCategory"
//        case _federalLevelCategory = "federalLevelCategory"
//        case _federalAgeCategory = "federalAgeCategory"
//        case _groupStageCourtCount = "groupStageCourtCount"
//        case _closedRegistrationDate = "closedRegistrationDate"
//        case _groupStageAdditionalQualified = "groupStageAdditionalQualified"
//        case _courtCount = "courtCount"
//        case _prioritizeClubMembers = "prioritizeClubMembers"
//        case _qualifiedPerGroupStage = "qualifiedPerGroupStage"
//        case _teamsPerGroupStage = "teamsPerGroupStage"
//        case _entryFee = "entryFee"
//        case _additionalEstimationDuration = "additionalEstimationDuration"
//        case _isDeleted = "isDeleted"
//        case _isCanceled = "localId"
//        case _payment = "globalId"
//    }
//}

extension Tournament: FederalTournamentHolder {
    func tournamentTitle(_ displayStyle: DisplayStyle, forBuild build: any TournamentBuildHolder) -> String {
        if isAnimation() {
            if let name {
                return name.trunc(length: DeviceHelper.charLength())
            } else if build.age == .unlisted, build.category == .unlisted {
                return build.level.localizedLevelLabel(.title)
            } else {
                return build.level.localizedLevelLabel(displayStyle)
            }
        }
        return build.level.localizedLevelLabel(displayStyle)
    }

    var codeClub: String? {
        club()?.code
    }

    var holderId: String { id }

    func clubLabel() -> String {
        locationLabel()
    }

    func subtitleLabel(forBuild build: any TournamentBuildHolder) -> String {
        if isAnimation() {
            if displayAgeAndCategory(forBuild: build) == false {
                return [build.category.localizedLabel(), build.age.localizedFederalAgeLabel()].filter({ $0.isEmpty == false }).joined(separator: " ")
            } else if name != nil {
                return build.level.localizedLevelLabel(.title)
            } else {
                return ""
            }
        } else {
            return subtitle()
        }
    }

    var tournaments: [any TournamentBuildHolder] {
        [
            self
        ]
    }

    var dayPeriod: DayPeriod {
        let day = startDate.get(.weekday)
        switch day {
        case 2...6:
            return .week
        default:
            return .weekend
        }
    }

    func displayAgeAndCategory(forBuild build: any TournamentBuildHolder) -> Bool {
        if isAnimation() {
            if let name, name.count < DeviceHelper.maxCharacter() {
                return true
            } else if build.age == .unlisted, build.category == .unlisted {
                return true
            } else {
                return DeviceHelper.isBigScreen()
            }
        }
        return true
    }
}

extension Tournament: TournamentBuildHolder {
    func buildHolderTitle(_ displayStyle: DisplayStyle) -> String {
        tournamentTitle(.short)
    }

    var category: TournamentCategory {
        tournamentCategory
    }

    var level: TournamentLevel {
        tournamentLevel
    }

    var age: FederalTournamentAge {
        federalTournamentAge
    }
}

extension Tournament {
    static func newEmptyInstance() -> Tournament {
        let lastDataSource: String? = DataStore.shared.appSettings.lastDataSource
        var _mostRecentDateAvailable: Date? {
            guard let lastDataSource else { return nil }
            return URL.importDateFormatter.date(from: lastDataSource)
        }

        let rankSourceDate = _mostRecentDateAvailable
        let tournaments : [Tournament] = DataStore.shared.tournaments.filter { $0.endDate != nil && $0.isDeleted == false }.sorted(by: \.endDate!, order: .descending)

        var shouldBePrivate = tournaments.first?.isPrivate ?? true

        if Guard.main.currentPlan == .monthlyUnlimited {
            shouldBePrivate = false
        } else if Guard.main.purchasedTransactions.isEmpty == false {
            shouldBePrivate = false
        }
        
        let disableRankingFederalRuling = tournaments.first?.disableRankingFederalRuling ?? false
        let umpireCustomMail = tournaments.first?.umpireCustomMail
        let umpireCustomPhone = tournaments.first?.umpireCustomPhone
        let umpireCustomContact = tournaments.first?.umpireCustomContact
        let hideUmpireMail = tournaments.first?.hideUmpireMail ?? false
        let hideUmpirePhone = tournaments.first?.hideUmpirePhone ?? true

        let tournamentLevel = TournamentLevel.mostUsed(inTournaments: tournaments)
        let tournamentCategory = TournamentCategory.mostUsed(inTournaments: tournaments)
        let federalTournamentAge = FederalTournamentAge.mostUsed(inTournaments: tournaments)
//creator: DataStore.shared.user?.id
        return Tournament(isPrivate: shouldBePrivate, groupStageSortMode: .snake, rankSourceDate: rankSourceDate, teamSorting: tournamentLevel.defaultTeamSortingType, federalCategory: tournamentCategory, federalLevelCategory: tournamentLevel, federalAgeCategory: federalTournamentAge, loserBracketMode: DataStore.shared.user.loserBracketMode, umpireCustomMail: umpireCustomMail, umpireCustomContact: umpireCustomContact, umpireCustomPhone: umpireCustomPhone, hideUmpireMail: hideUmpireMail, hideUmpirePhone: hideUmpirePhone, disableRankingFederalRuling: disableRankingFederalRuling)
    }

    static func fake() -> Tournament {
        return Tournament(event: "Roland Garros", name: "Magic P100", startDate: Date(), endDate: Date(), creationDate: Date(), isPrivate: false, groupStageFormat: .nineGames, roundFormat: nil, loserRoundFormat: nil, groupStageSortMode: .snake, groupStageCount: 4, rankSourceDate: nil, dayDuration: 2, teamCount: 24, teamSorting: .rank, federalCategory: .men, federalLevelCategory: .p100, federalAgeCategory: .a45, closedRegistrationDate: nil, groupStageAdditionalQualified: 0, courtCount: 4, prioritizeClubMembers: false, qualifiedPerGroupStage: 2, teamsPerGroupStage: 4, entryFee: nil)
    }

}

extension Tournament {
    func deadline(for type: TournamentDeadlineType) -> Date? {
        guard [.p500, .p1000, .p1500, .p2000].contains(tournamentLevel) else { return nil }
        
        let daysOffset = type.daysOffset(level: tournamentLevel)
        if let date = Calendar.current.date(byAdding: .day, value: daysOffset, to: startDate) {
            let startOfDay = Calendar.current.startOfDay(for: date)
            return Calendar.current.date(byAdding: type.timeOffset, to: startOfDay)
        }
        return nil
    }
}

/// Warning: if the enum has more than 10 cases, the payment algo is broken
enum TournamentPayment: Int, CaseIterable {
    case free, unit, subscriptionUnit, unlimited

    var isSubscription: Bool {
        switch self {
        case .subscriptionUnit, .unlimited:
            return true
        default:
            return false
        }
    }

}
