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

import Foundation
import LeStorage

@Observable
class Tournament : ModelObject, Storable {
    static func resourceName() -> String { "tournaments" }
    static func tokenExemptedMethods() -> [HTTPMethod] { return [] }

    var id: String = Store.randomId()
    var event: String?
    var name: String?
    var startDate: Date
    var endDate: Date?
    private(set) var creationDate: Date
    var isPrivate: Bool
    private(set) var groupStageFormat: MatchFormat?
    private(set) var roundFormat: MatchFormat?
    private(set) var loserRoundFormat: MatchFormat?
    var groupStageSortMode: GroupStageOrderingMode
    var groupStageCount: Int
    var rankSourceDate: Date?
    var dayDuration: Int
    var teamCount: Int
    var teamSorting: TeamSortingType
    var federalCategory: TournamentCategory
    var federalLevelCategory: TournamentLevel
    var federalAgeCategory: FederalTournamentAge
    var closedRegistrationDate: Date?
    var groupStageAdditionalQualified: Int
    var courtCount: Int = 2
    var prioritizeClubMembers: Bool
    var qualifiedPerGroupStage: Int
    var teamsPerGroupStage: Int
    var entryFee: Double?
    var payment: TournamentPayment? = nil
    var additionalEstimationDuration: Int = 0
    var isDeleted: Bool = false
    var isCanceled: Bool = false
    var publishTeams: Bool = false
    //var publishWaitingList: Bool = false
    var publishSummons: Bool = false
    var publishGroupStages: Bool = false
    var publishBrackets: Bool = false
    var shouldVerifyGroupStage: Bool = false
    var shouldVerifyBracket: Bool = false
    var hideTeamsWeight: Bool = false
    var publishTournament: Bool = false
    var hidePointsEarned: Bool = false

    @ObservationIgnored
    var navigationPath: [Screen] = []
    
    enum CodingKeys: String, CodingKey {
        case _id = "id"
        case _event = "event"
        case _creator = "creator"
        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 _seedCount = "seedCount"
        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"
        case _publishTeams = "publishTeams"
        //case _publishWaitingList = "publishWaitingList"
        case _publishSummons = "publishSummons"
        case _publishGroupStages = "publishGroupStages"
        case _publishBrackets = "publishBrackets"
        case _shouldVerifyGroupStage = "shouldVerifyGroupStage"
        case _shouldVerifyBracket = "shouldVerifyBracket"
        case _hideTeamsWeight = "hideTeamsWeight"
        case _publishTournament = "publishTournament"
        case _hidePointsEarned = "hidePointsEarned"
    }

    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) {
        self.event = event
        self.name = name
        self.startDate = startDate
        self.endDate = endDate
        self.creationDate = creationDate
        self.isPrivate = isPrivate
        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
        self.publishTeams = publishTeams
        self.publishSummons = publishSummons
        self.publishBrackets = publishBrackets
        self.publishGroupStages = publishGroupStages
        self.shouldVerifyBracket = shouldVerifyBracket
        self.shouldVerifyGroupStage = shouldVerifyGroupStage
        self.hideTeamsWeight = hideTeamsWeight
        self.publishTournament = publishTournament
        self.hidePointsEarned = hidePointsEarned
    }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(String.self, forKey: ._id)
        event = try container.decodeIfPresent(String.self, forKey: ._event)
        name = try container.decodeIfPresent(String.self, forKey: ._name)
        startDate = try container.decode(Date.self, forKey: ._startDate)
        endDate = try container.decodeIfPresent(Date.self, forKey: ._endDate)
        creationDate = try container.decode(Date.self, forKey: ._creationDate)
        isPrivate = try container.decode(Bool.self, forKey: ._isPrivate)
        groupStageFormat = try container.decodeIfPresent(MatchFormat.self, forKey: ._groupStageFormat)
        roundFormat = try container.decodeIfPresent(MatchFormat.self, forKey: ._roundFormat)
        loserRoundFormat = try container.decodeIfPresent(MatchFormat.self, forKey: ._loserRoundFormat)
        groupStageSortMode = try container.decode(GroupStageOrderingMode.self, forKey: ._groupStageSortMode)
        groupStageCount = try container.decode(Int.self, forKey: ._groupStageCount)
        rankSourceDate = try container.decodeIfPresent(Date.self, forKey: ._rankSourceDate)
        dayDuration = try container.decode(Int.self, forKey: ._dayDuration)
        teamCount = try container.decode(Int.self, forKey: ._teamCount)
        teamSorting = try container.decode(TeamSortingType.self, forKey: ._teamSorting)
        federalCategory = try container.decode(TournamentCategory.self, forKey: ._federalCategory)
        federalLevelCategory = try container.decode(TournamentLevel.self, forKey: ._federalLevelCategory)
        federalAgeCategory = try container.decode(FederalTournamentAge.self, forKey: ._federalAgeCategory)
        closedRegistrationDate = try container.decodeIfPresent(Date.self, forKey: ._closedRegistrationDate)
        groupStageAdditionalQualified = try container.decode(Int.self, forKey: ._groupStageAdditionalQualified)
        courtCount = try container.decode(Int.self, forKey: ._courtCount)
        prioritizeClubMembers = try container.decode(Bool.self, forKey: ._prioritizeClubMembers)
        qualifiedPerGroupStage = try container.decode(Int.self, forKey: ._qualifiedPerGroupStage)
        teamsPerGroupStage = try container.decode(Int.self, forKey: ._teamsPerGroupStage)
        entryFee = try container.decodeIfPresent(Double.self, forKey: ._entryFee)
        payment = try Tournament._decodePayment(container: container)
        additionalEstimationDuration = try container.decode(Int.self, forKey: ._additionalEstimationDuration)
        isDeleted = try container.decode(Bool.self, forKey: ._isDeleted)
        isCanceled = try Tournament._decodeCanceled(container: container)
        publishTeams = try container.decodeIfPresent(Bool.self, forKey: ._publishTeams) ?? false
        publishSummons = try container.decodeIfPresent(Bool.self, forKey: ._publishSummons) ?? false
        publishGroupStages = try container.decodeIfPresent(Bool.self, forKey: ._publishGroupStages) ?? false
        publishBrackets = try container.decodeIfPresent(Bool.self, forKey: ._publishBrackets) ?? false
        shouldVerifyBracket = try container.decodeIfPresent(Bool.self, forKey: ._shouldVerifyBracket) ?? false
        shouldVerifyGroupStage = try container.decodeIfPresent(Bool.self, forKey: ._shouldVerifyGroupStage) ?? false
        hideTeamsWeight = try container.decodeIfPresent(Bool.self, forKey: ._hideTeamsWeight) ?? false
        publishTournament = try container.decodeIfPresent(Bool.self, forKey: ._publishTournament) ?? false
        hidePointsEarned = try container.decodeIfPresent(Bool.self, forKey: ._hidePointsEarned) ?? false
    }
    
    fileprivate static let _numberFormatter: NumberFormatter = NumberFormatter()

    fileprivate static func _decodePayment(container: KeyedDecodingContainer<CodingKeys>) throws -> TournamentPayment? {
        let data = try container.decodeIfPresent(Data.self, forKey: ._payment)
        
        if let data {
            do {
                let decoded: String = try data.decryptData(pass: Key.pass.rawValue)
                let sequence = decoded.compactMap { _numberFormatter.number(from: String($0))?.intValue }
                return TournamentPayment(rawValue: sequence[18])
            } catch {
                Logger.error(error)
            }
        }
        return nil
    }
    
    fileprivate static  func _decodeCanceled(container: KeyedDecodingContainer<CodingKeys>) throws -> Bool {
        let data = try container.decodeIfPresent(Data.self, forKey: ._isCanceled)
        if let data {
            do {
                let decoded: String = try data.decryptData(pass: Key.pass.rawValue)
                let sequence = decoded.compactMap { _numberFormatter.number(from: String($0))?.intValue }
                return Bool.decodeInt(sequence[18])
            } catch {
                Logger.error(error)
            }
        }
        return false
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        
        try container.encode(id, forKey: ._id)
        if let event {
            try container.encode(event, forKey: ._event)
        } else {
            try container.encodeNil(forKey: ._event)
        }
        if let name {
            try container.encode(name, forKey: ._name)
        } else {
            try container.encodeNil(forKey: ._name)
        }
        try container.encode(startDate, forKey: ._startDate)
        if let endDate {
            try container.encode(endDate, forKey: ._endDate)
        } else {
            try container.encodeNil(forKey: ._endDate)
        }

        try container.encode(creationDate, forKey: ._creationDate)
        try container.encode(isPrivate, forKey: ._isPrivate)
        if let groupStageFormat {
            try container.encode(groupStageFormat, forKey: ._groupStageFormat)
        } else {
            try container.encodeNil(forKey: ._groupStageFormat)
        }
        if let roundFormat {
            try container.encode(roundFormat, forKey: ._roundFormat)
        } else {
            try container.encodeNil(forKey: ._roundFormat)
        }
        if let loserRoundFormat {
            try container.encode(loserRoundFormat, forKey: ._loserRoundFormat)
        } else {
            try container.encodeNil(forKey: ._loserRoundFormat)
        }
        try container.encode(groupStageSortMode, forKey: ._groupStageSortMode)
        try container.encode(groupStageCount, forKey: ._groupStageCount)
        if let rankSourceDate {
            try container.encode(rankSourceDate, forKey: ._rankSourceDate)
        } else {
            try container.encodeNil(forKey: ._rankSourceDate)
        }
        try container.encode(dayDuration, forKey: ._dayDuration)
        try container.encode(teamCount, forKey: ._teamCount)
        try container.encode(teamSorting, forKey: ._teamSorting)
        try container.encode(federalCategory, forKey: ._federalCategory)
        try container.encode(federalLevelCategory, forKey: ._federalLevelCategory)
        try container.encode(federalAgeCategory, forKey: ._federalAgeCategory)
        if let closedRegistrationDate {
            try container.encode(closedRegistrationDate, forKey: ._closedRegistrationDate)
        } else {
            try container.encodeNil(forKey: ._closedRegistrationDate)
        }

        try container.encode(groupStageAdditionalQualified, forKey: ._groupStageAdditionalQualified)
        try container.encode(courtCount, forKey: ._courtCount)
        try container.encode(prioritizeClubMembers, forKey: ._prioritizeClubMembers)
        try container.encode(qualifiedPerGroupStage, forKey: ._qualifiedPerGroupStage)
        try container.encode(teamsPerGroupStage, forKey: ._teamsPerGroupStage)
        if let entryFee {
            try container.encode(entryFee, forKey: ._entryFee)
        } else {
            try container.encodeNil(forKey: ._entryFee)
        }

        try self._encodePayment(container: &container)
        try container.encode(additionalEstimationDuration, forKey: ._additionalEstimationDuration)
        try container.encode(isDeleted, forKey: ._isDeleted)
        try self._encodeIsCanceled(container: &container)
        try container.encode(publishTeams, forKey: ._publishTeams)
        try container.encode(publishSummons, forKey: ._publishSummons)
        try container.encode(publishBrackets, forKey: ._publishBrackets)
        try container.encode(publishGroupStages, forKey: ._publishGroupStages)
        try container.encode(shouldVerifyBracket, forKey: ._shouldVerifyBracket)
        try container.encode(shouldVerifyGroupStage, forKey: ._shouldVerifyGroupStage)
        try container.encode(hideTeamsWeight, forKey: ._hideTeamsWeight)
        try container.encode(publishTournament, forKey: ._publishTournament)
        try container.encode(hidePointsEarned, forKey: ._hidePointsEarned)
    }
    
    fileprivate func _encodePayment(container: inout KeyedEncodingContainer<CodingKeys>) throws {
        
        guard let payment else {
            try container.encodeNil(forKey: ._payment)
            return
        }
        
        let max: Int = TournamentPayment.allCases.count
        var sequence = (1...18).map { _ in Int.random(in: (0..<max)) }
        sequence.append(payment.rawValue)
        sequence.append(contentsOf: (1...13).map { _ in Int.random(in: (0..<max ))} )

        let stringCombo: [String] = sequence.map { $0.formatted() }
        let joined: String = stringCombo.joined(separator: "")
        if let data = joined.data(using: .utf8) {
            let encryped: Data = try data.encrypt(pass: Key.pass.rawValue)
            try container.encodeIfPresent(encryped, forKey: ._payment)
        }
        
    }
    
    func _encodeIsCanceled(container: inout KeyedEncodingContainer<CodingKeys>) throws {
        
        let max: Int = 9
        var sequence = (1...18).map { _ in Int.random(in: (0...max)) }
        sequence.append(self.isCanceled.encodedValue)
        sequence.append(contentsOf: (1...13).map { _ in Int.random(in: (0...max ))} )
        
        let stringCombo: [String] = sequence.map { $0.formatted() }
        let joined: String = stringCombo.joined(separator: "")
        if let data = joined.data(using: .utf8) {
            let encryped: Data = try data.encrypt(pass: Key.pass.rawValue)
            try container.encode(encryped, forKey: ._isCanceled)
        }
    }

    override func deleteDependencies() throws {
        DataStore.shared.teamRegistrations.deleteDependencies(self.unsortedTeams())
        DataStore.shared.groupStages.deleteDependencies(self.groupStages())
        DataStore.shared.rounds.deleteDependencies(self.rounds())
        DataStore.shared.matchSchedulers.deleteDependencies(self._matchSchedulers())
    }

    // MARK: - Computed Dependencies
    
    func unsortedTeams() -> [TeamRegistration] {
        return Store.main.filter { $0.tournament == self.id }
    }
    
    func groupStages() -> [GroupStage] {
        let groupStages: [GroupStage] = DataStore.shared.groupStages.filter { $0.tournament == self.id }
        return groupStages.sorted(by: \.index)
    }
    
    func allRounds() -> [Round] {
        return Store.main.filter { $0.tournament == self.id }
    }
    
    // MARK: -
    
    /// 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
            }
        }
        
    }
    
    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() && canBePublished()) || 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() -> [Int] {
        let runningMatches : [Match] = Store.main.filter(isIncluded: { $0.isRunning() }).filter({ $0.tournamentId() == self.id })
        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() -> String {
        let selectedSortedTeams = selectedSortedTeams()
        return (selectedSortedTeams.compactMap { $0.pasteData() } + ["Liste d'attente"] + waitingListTeams(in: selectedSortedTeams).compactMap { $0.pasteData() }).joined(separator: "\n\n")
    }
    
    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() -> Tournament.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 {
        let _groupStagesCount = groupStages().count
        if groupStageCount != _groupStagesCount {
            return groupStageCount * teamsPerGroupStage
        } else {
            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] {
        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 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() -> [SeedInterval] {
        let seeds = seeds()
        var availableSeedGroup = Set<SeedInterval>()
        for (index, seed) in seeds.enumerated() {
            if seed.isSeedable(), let seedGroup = seedGroup(for: index) {
                availableSeedGroup.insert(seedGroup)
            }
        }
        return availableSeedGroup.sorted(by: <)
    }
        
    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? {
        
        if availableSeeds().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)

            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() {
                if let chunk = chunks.first(where: { seedInterval in
                    seedInterval.first >= self.seededTeams().count
                }) {
                    return seedGroupAvailable(atRoundIndex: roundIndex, availableSeedGroup: chunk)
                }
            }
        }
        
        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 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() -> GroupStage? {
        let groupStages = groupStages()
        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] {
        let unsortedGroupStages : [GroupStage] = Store.main.filter { $0.tournament == self.id }
        let matches: [Match] = unsortedGroupStages.flatMap { $0._matches() } + allRoundMatches()
        return matches.filter({ $0.disabled == false })
    }

    func _allMatchesIncludingDisabled() -> [Match] {
        let unsortedGroupStages : [GroupStage] = Store.main.filter { $0.tournament == self.id }
        return unsortedGroupStages.flatMap { $0._matches() } + allRounds().flatMap { $0._matches() }
    }

    func rounds() -> [Round] {
        Store.main.filter { $0.tournament == self.id && $0.parent == nil }.sorted(by: \.index).reversed()
    }
    
    func sortedTeams() -> [TeamRegistration] {
        let teams = selectedSortedTeams()
        return teams + waitingListTeams(in: teams)
    }
    
    func selectedSortedTeams() -> [TeamRegistration] {
        #if DEBUG_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] = []
        let _teams = unsortedTeams().filter({ $0.walkOut == false })

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

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

        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 = min(teamCount, _completeTeams.count) - 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]) -> [TeamRegistration] {
        let waitingList = Set(unsortedTeams()).subtracting(teams)
        return waitingList.filter { $0.walkOut == false }.sorted(using: _defaultSorting(), order: .ascending) + waitingList.filter { $0.walkOut == true }.sorted(using: _defaultSorting(), order: .ascending)
    }
    
    func bracketCut() -> Int {
        return max(0, teamCount - groupStageCut())
    }
    
    func groupStageCut() -> Int {
        return groupStageSpots()
    }
    
    func cutLabel(index: Int) -> String {
        if index < bracketCut() {
            return "Tableau"
        } else if index - bracketCut() < groupStageCut() {
            return "Poule"
        } else {
            return "Attente"
        }
    }
    
    func unsortedTeamsWithoutWO() -> [TeamRegistration] {
        return Store.main.filter { $0.tournament == self.id && $0.walkOut == false }
    }

    func walkoutTeams() -> [TeamRegistration] {
        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 unsortedPlayers() -> [PlayerRegistration] {
        return self.unsortedTeams().flatMap { $0.unsortedPlayers() }
    }

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

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

    func players() -> [PlayerRegistration] {
        return self.unsortedTeams().flatMap { $0.unsortedPlayers() }.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 2
    }
    
    func inadequatePlayers(in players: [PlayerRegistration]) -> [PlayerRegistration] {
        if startDate.isInCurrentYear() == false {
            return []
        }
        return players.filter { player in
            if player.rank == nil { return false }
            if player.computedRank <= tournamentLevel.minimumPlayerRank(category: tournamentCategory, ageCategory: federalTournamentAge) {
                return true
            } else {
                return false
            }
        }
    }
    
    func mandatoryRegistrationCloseDate() -> Date? {
        switch tournamentLevel {
        case .p500, .p1000, .p1500, .p2000:
            if let date = Calendar.current.date(byAdding: .day, value: -6, to: startDate) {
                let startOfDay = Calendar.current.startOfDay(for: date)
                return Calendar.current.date(byAdding: .minute, value: -1, to: startOfDay)
            }
        default:
            break
        }
        return nil
    }
    
    func licenseYearValidity() -> Int {
        if startDate.get(.month) > 8 {
            return startDate.get(.year) + 1
        } else {
            return startDate.get(.year)
        }
    }
    
    func playersWithoutValidLicense(in players: [PlayerRegistration]) -> [PlayerRegistration] {
        let licenseYearValidity = self.licenseYearValidity()
        return players.filter({
            ($0.isImported() && $0.isValidLicenseNumber(year: licenseYearValidity) == false) || ($0.isImported() == false && ($0.licenceId == nil || $0.formattedLicense().isLicenseNumber == false || $0.licenceId?.isEmpty == true))
        })
    }
    
    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)
                teamsToImport.append(newTeam)
            }
        }
        
        do {
            try DataStore.shared.teamRegistrations.addOrUpdate(contentOfs: teamsToImport)
        } catch {
            Logger.error(error)
        }
        do {
            try DataStore.shared.playerRegistrations.addOrUpdate(contentOfs: teams.flatMap { $0.players })
        } catch {
            Logger.error(error)
        }

    }
    
    func maximumCourtsPerGroupSage() -> Int {
        if teamsPerGroupStage > 1 {
            return min(teamsPerGroupStage / 2, courtCount)
        } else {
            return max(1, courtCount)
        }
    }
    
    func registrationIssues() async -> Int {
        let players : [PlayerRegistration] = unsortedPlayers()
        let selectedTeams : [TeamRegistration] = selectedSortedTeams()
        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 playersWithoutValidLicense : [PlayerRegistration] = playersWithoutValidLicense(in: players)
        let playersMissing : [TeamRegistration] = selectedTeams.filter({ $0.unsortedPlayers().count < 2 })
        let waitingList : [TeamRegistration] = waitingListTeams(in: selectedTeams)
        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
    }
    
    func isStartDateIsDifferentThanCallDate(_ team: TeamRegistration) -> Bool {
        guard let summonDate = team.callDate else { return true }
        guard let expectedSummonDate = team.expectedSummonDate() else { return true }
        return Calendar.current.compare(summonDate, to: expectedSummonDate, toGranularity: .minute) != ComparisonResult.orderedSame
    }
    
    func groupStagesMatches() -> [Match] {
        let groupStageIds = groupStages().map { $0.id }
        return Store.main.filter(isIncluded: { $0.groupStage != nil && groupStageIds.contains($0.groupStage!) })
    }
    
    func availableToStart(_ allMatches: [Match], in runningMatches: [Match]) async -> [Match] {
        #if DEBUG_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.canBeStarted(inMatches: runningMatches) && $0.isRunning() == false }).sorted(by: \.computedStartDateForSorting)
    }
    
    func asyncRunningMatches(_ allMatches: [Match]) async -> [Match] {
        #if DEBUG_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(by: \.computedStartDateForSorting)
    }

    func runningMatches(_ allMatches: [Match]) -> [Match] {
        #if DEBUG_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(by: \.computedStartDateForSorting)
    }

    func readyMatches(_ allMatches: [Match]) async -> [Match] {
        #if DEBUG_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(by: \.computedStartDateForSorting)
    }

    func finishedMatches(_ allMatches: [Match], limit: Int? = nil) -> [Match] {
        #if DEBUG_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
        let _limit = limit ?? courtCount
        return Array(allMatches.filter({ $0.hasEnded() }).sorted(by: \.computedEndDateForSorting).reversed().prefix(_limit))
    }
    
    func teamsRanked() -> [TeamRegistration] {
        let selected = selectedSortedTeams().filter({ $0.finalRanking != nil })
        return selected.sorted(by: \.finalRanking!, order: .ascending)
    }
    
    func finalRanking() async -> [Int: [String]] {
        var teams: [Int: [String]] = [:]
        var ids: Set<String> = Set<String>()
        let rounds = rounds()
        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
            round.loserRoundsAndChildren().filter { $0.isRankDisabled() == false && $0.hasNextRound() == false }
        }.compactMap({ $0 })
        
        others.forEach { round in
            if let interval = round.seedInterval() {
                let playedMatches = round.playedMatches().filter { $0.disabled == false || $0.isReady() }
                let winners = playedMatches.compactMap({ $0.winningTeamId }).filter({ ids.contains($0) == false })
                let losers = playedMatches.compactMap({ $0.losingTeamId }).filter({ ids.contains($0) == false })
                if winners.isEmpty {
                    let disabledIds = playedMatches.flatMap({ $0.teamScores.compactMap({ $0.teamRegistration }) }).filter({ ids.contains($0) == false })
                    teams[interval.last] = disabledIds
                    disabledIds.forEach { ids.insert($0) }
                } else {
                    teams[interval.first + winners.count - 1] = winners
                    winners.forEach { ids.insert($0) }
                    teams[interval.last] = losers
                    losers.forEach { ids.insert($0) }
                }
            }
        }
        
        let groupStages = groupStages()
        let baseRank = teamCount - groupStageSpots() + qualifiedPerGroupStage * groupStageCount + groupStageAdditionalQualified
        
        groupStages.forEach { groupStage in
            let groupStageTeams = groupStage.teams(true)
            for (index, team) in groupStageTeams.enumerated() {
                if team.qualified == false {
                    let groupStageWidth = max(((index == qualifiedPerGroupStage) ? groupStageCount - groupStageAdditionalQualified : groupStageCount) * (index - qualifiedPerGroupStage), 0)
                    
                    let _index = baseRank + groupStageWidth + 1
                    if let existingTeams = teams[_index] {
                        teams[_index] = existingTeams + [team.id]
                    } else {
                        teams[_index] = [team.id]
                    }
                }
            }
        }
        
        return teams
    }
    
    func lockRegistration() {
        closedRegistrationDate = Date()
        let count = selectedSortedTeams().count
        if teamCount != count {
            teamCount = count
        }
        let teams = unsortedTeams()
        teams.forEach { team in
            team.lockedWeight = team.weight
        }
        do {
            try DataStore.shared.teamRegistrations.addOrUpdate(contentOfs: teams)
        } catch {
            Logger.error(error)
        }
    }
    
    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)
            do {
                try DataStore.shared.playerRegistrations.addOrUpdate(contentOfs: players)
            } catch {
                Logger.error(error)
            }
        }
        do {
            try DataStore.shared.teamRegistrations.addOrUpdate(contentOfs: teams)
        } catch {
            Logger.error(error)
        }
    }
    
    func updateRank(to newDate: Date?) async throws {
        guard let newDate else { return }
        rankSourceDate = newDate
        
        if currentMonthData() == nil {
            let lastRankWoman = SourceFileManager.shared.getUnrankValue(forMale: false, rankSourceDate: rankSourceDate)
            let lastRankMan = SourceFileManager.shared.getUnrankValue(forMale: true, rankSourceDate: rankSourceDate)
            await MainActor.run {
                let formatted: String = URL.importDateFormatter.string(from: newDate)
                let monthData: MonthData = MonthData(monthKey: formatted)
                monthData.maleUnrankedValue = lastRankMan
                monthData.femaleUnrankedValue = lastRankWoman
                do {
                    try DataStore.shared.monthData.addOrUpdate(instance: monthData)
                } catch {
                    Logger.error(error)
                }
            }
        }
        
        let lastRankMan = currentMonthData()?.maleUnrankedValue
        let lastRankWoman = currentMonthData()?.femaleUnrankedValue
        let dataURLs = SourceFileManager.shared.allFiles.filter { $0.dateFromPath == newDate }
        let sources = dataURLs.map { CSVParser(url: $0) }

        try await unsortedPlayers().concurrentForEach { player in
            try await player.updateRank(from: sources, lastRank: (player.sex == .female ? lastRankWoman : lastRankMan) ?? 0)
        }
    }

    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) -> String {
        if tournamentLevel == .unlisted, displayStyle == .title, let name {
            return name
        }
        return [tournamentLevel.localizedLabel(displayStyle) + " " + tournamentCategory.localizedLabel(), displayStyle == .wide ? name : nil].compactMap({ $0 }).joined(separator: " - ")
    }
    
    func hideWeight() -> Bool {
        return tournamentLevel.hideWeight()
    }
    
    func isAnimation() -> Bool {
        federalLevelCategory == .unlisted
    }
    
    func subtitle(_ displayStyle: DisplayStyle = .wide) -> String {
        return name ?? ""
    }
    
    func formattedDate(_ displayStyle: DisplayStyle = .wide) -> String {
        switch displayStyle {
        case .wide, .title:
            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] {
        return unsortedTeams().filter({ $0.qualified && $0.bracketPosition == nil })
    }
    
    func qualifiedTeams() -> [TeamRegistration] {
        return unsortedTeams().filter({ $0.qualifiedFromGroupStage() })
    }
    
    func moreQualifiedToDraw() -> Int {
        return max(qualifiedTeams().count - (qualifiedFromGroupStage() + groupStageAdditionalQualified), 0)
    }

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

    func groupStagesAreOver() -> Bool {
        let groupStages = groupStages()
        guard groupStages.isEmpty == false else {
            return true
        }
        return groupStages.allSatisfy({ $0.hasEnded() })
        //return qualifiedTeams().count == qualifiedFromGroupStage() + groupStageAdditionalQualified
    }
    
    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: "EUR"))) par joueur."
            return [message, self._paymentMethodMessage()].compactMap { $0 }.joined(separator: "\n")
        } else {
            return "Inscription: gratuite."
        }
    }
    
    func umpireMail() -> [String]? {
        return [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)
    }

    typealias TournamentStatus = (label:String, completion: String)
    func cashierStatus() async -> TournamentStatus {
        let selectedPlayers = selectedPlayers()
        let paid = selectedPlayers.filter({ $0.hasPaid() })
//        let label = paid.count.formatted() + " / " + selectedPlayers.count.formatted() + " joueurs encaissés"
        let label = "\(paid.count.formatted()) / \(selectedPlayers.count.formatted()) joueurs encaissés"
        let completion = (Double(paid.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 label = "\(called.count.formatted()) / \(selectedSortedTeams.count.formatted()) convoquées 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, cut: TeamRegistration.TeamRange?) {
        let availableSeeds = availableSeeds()
        let cut = TeamRegistration.TeamRange(availableSeeds.first, availableSeeds.last)
        if availableSeeds.isEmpty == false {
            return ("placer \(availableSeeds.count) tête\(availableSeeds.count.pluralSuffix) de série", cut)
        }
        let availableQualifiedTeams = availableQualifiedTeams()
        if availableQualifiedTeams.isEmpty == false {
            return ("placer \(availableQualifiedTeams.count) qualifié" + availableQualifiedTeams.count.pluralSuffix, cut)
        }
        if let round = getActiveRound() {
            return ([round.roundTitle(.short), round.roundStatus()].joined(separator: " "), cut)
        } else {
            return ("à construire", nil)
        }
    }
    
    func groupStageStatus() async -> (status: String, cut: TeamRegistration.TeamRange?) {
        let groupStageTeams = groupStageTeams()
        let groupStageTeamsCount = groupStageTeams.count
        if groupStageTeamsCount == 0 || groupStageTeamsCount != groupStageSpots() {
            return ("à faire", nil)
        }
        
        let cut = 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 {
        [dayDuration.formatted() + " jour\(dayDuration.pluralSuffix)", courtCount.formatted() + " terrain\(courtCount.pluralSuffix)"].joined(separator: ", ")
    }
    
    func structureDescriptionLocalizedLabel() -> String {
        if state() == .initial {
            return "à valider"
        } else {
            let groupStageLabel: String? = groupStageCount > 0 ? groupStageCount.formatted() + " poule\(groupStageCount.pluralSuffix)" : nil
            return [teamCount.formatted() + " équipes", groupStageLabel].compactMap({ $0 }).joined(separator: ", ")
        }
    }

    func deleteAndBuildEverything() {
        deleteStructure()
        deleteGroupStages()
        buildGroupStages()
        buildBracket()
    }
    
    func buildGroupStages() {
        guard groupStages().isEmpty else {
            return
        }

        var _groupStages = [GroupStage]()
        for index in 0..<groupStageCount {
            let groupStage = GroupStage(tournament: id, index: index, size: teamsPerGroupStage, matchFormat: groupStageMatchFormat)
            _groupStages.append(groupStage)
        }
        
        do {
            try DataStore.shared.groupStages.addOrUpdate(contentOfs: _groupStages)
        } catch {
            Logger.error(error)
        }
        
        refreshGroupStages()
    }
    
    func bracketTeamCount() -> Int {
        let bracketTeamCount = teamCount - (teamsPerGroupStage - qualifiedPerGroupStage) * groupStageCount + (groupStageAdditionalQualified * (groupStageCount > 0 ? 1 : 0))
        return bracketTeamCount
    }

    func buildBracket() {
        guard rounds().isEmpty else { return }
        let roundCount = RoundRule.numberOfRounds(forTeams: bracketTeamCount())
        
        let rounds = (0..<roundCount).map { //index 0 is the final
            Round(tournament: id, index: $0)
        }
        
        do {
            try DataStore.shared.rounds.addOrUpdate(contentOfs: rounds)
        } catch {
            Logger.error(error)
        }
        let matchCount = RoundRule.numberOfMatches(forTeams: bracketTeamCount())

        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, matchFormat: matchFormat, name: Match.setServerTitle(upperRound: round, matchIndex: RoundRule.matchIndexWithinRound(fromMatchIndex: $0)))
        }

        print(matches.map {
            (RoundRule.roundName(fromMatchIndex: $0.index), RoundRule.matchIndexWithinRound(fromMatchIndex: $0.index))
        })
        
        do {
            try DataStore.shared.matches.addOrUpdate(contentOfs: matches)
        } catch {
            Logger.error(error)
        }
        
        self.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 = Store.main.filter(isIncluded: { $0.tournament == id && $0.index == roundIndex }).first {
            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() {
        do {
            try DataStore.shared.rounds.delete(contentOfs: rounds())
        } catch {
            Logger.error(error)
        }
        unsortedTeams().forEach({ $0.bracketPosition = nil })
        do {
            try DataStore.shared.teamRegistrations.addOrUpdate(contentOfs: unsortedTeams())
        } catch {
            Logger.error(error)
        }
    }
    
    func deleteGroupStages() {
        do {
            try DataStore.shared.groupStages.delete(contentOfs: groupStages())
        } catch {
            Logger.error(error)
        }
        
        do {
            unsortedTeams().forEach({
                $0.groupStage = nil
                $0.groupStagePosition = nil
            })
            try DataStore.shared.teamRegistrations.addOrUpdate(contentOfs: unsortedTeams())
        } catch {
            Logger.error(error)
        }
    }
    
    func refreshGroupStages() {
        unsortedTeams().forEach { team in
            team.groupStage = nil
            team.groupStagePosition = nil
        }

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

    func setGroupStage(randomize: Bool) {
        let groupStages = groupStages()
        let numberOfBracketsAsInt = groupStages.count
//        let teamsPerBracket = teamsPerBracket
        if groupStageCount != numberOfBracketsAsInt {
            deleteGroupStages()
            buildGroupStages()
        }
        
        let max = groupStages.map { $0.size }.reduce(0,+)
        var chunks = selectedSortedTeams().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
            }
        }
        
        do {
            try DataStore.shared.teamRegistrations.addOrUpdate(contentOfs: unsortedTeams())
        } catch {
            Logger.error(error)
        }
        groupStages.forEach { $0.buildMatches() }
    }


    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 "#" + (teamIndex + 1).formatted()
        } else {
            return nil
        }
    }
    
    func addTeam(_ players: Set<PlayerRegistration>, registrationDate: Date? = nil, name: String? = nil) -> TeamRegistration {
        let date: Date = registrationDate ?? Date()
        let team = TeamRegistration(tournament: id, registrationDate: date, name: name)
        team.setWeight(from: Array(players), inTournamentCategory: tournamentCategory)
        players.forEach { player in
            player.teamRegistration = team.id
        }
        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(1)
        }
    }
    
    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(1)
        matchFormat = roundSmartMatchFormat(1)
    }
    
    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.registrationDate!)]
        case .inscriptionDate:
            [.keyPath(\TeamRegistration.registrationDate!), .keyPath(\TeamRegistration.initialWeight)]
        }
    }
    
    func isSameBuild(_ build: any TournamentBuildHolder) -> Bool {
        tournamentLevel == build.level
        && tournamentCategory == build.category
        && federalTournamentAge == build.age
    }

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

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

    func matchScheduler() -> MatchScheduler? {
        return self._matchSchedulers().first
    }
    
    func currentMonthData() -> MonthData? {
        guard let rankSourceDate else { return nil }
        let dateString = URL.importDateFormatter.string(from: rankSourceDate)
        return Store.main.filter(isIncluded: { $0.monthKey == dateString }).first
    }
    
    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 rounds: [Round] = Store.main.filter(isIncluded: { $0.index == 0 && $0.tournament == id && $0.parent == nil })
//        let final: Round? = .first
        return rounds.first?.playedMatches().first?.winner()
    }
    
    func getGroupStageChunkValue() -> Int {
        if 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)
    }

    
    // MARK: -
    
    func insertOnServer() throws {
        
        try DataStore.shared.tournaments.writeChangeAndInsertOnServer(instance: self)

        for teamRegistration in self.unsortedTeams() {
            try teamRegistration.insertOnServer()
        }
        for groupStage in self.groupStages() {
            try groupStage.insertOnServer()
        }
        for round in self.allRounds() {
            try round.insertOnServer()
        }

    }
    
    // MARK: - Payments & Crypto
    
    enum PaymentError: Error {
        case cantPayTournament
    }
    
    func payIfNecessary() throws {
        if let payment = Guard.main.paymentForNewTournament() {
            self.payment = payment
            try DataStore.shared.tournaments.addOrUpdate(instance: self)
            return
        }
        throw PaymentError.cantPayTournament
    }
    
}

fileprivate 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: Hashable {
    static func == (lhs: Tournament, rhs: Tournament) -> Bool {
        lhs.id == rhs.id
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
}

extension Tournament: FederalTournamentHolder {
    var holderId: String { id }

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

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

extension Tournament: TournamentBuildHolder {
    func buildHolderTitle() -> 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 }
        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(groupStageSortMode: .snake, rankSourceDate: rankSourceDate, teamSorting: tournamentLevel.defaultTeamSortingType, federalCategory: tournamentCategory, federalLevelCategory: tournamentLevel, federalAgeCategory: federalTournamentAge)
    }

    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)
    }
    
}
