//
//  GroupStage.swift
//  Padel Tournament
//
//  Created by razmig on 10/03/2024.
//

import Foundation
import LeStorage
import Algorithms
import SwiftUI

@Observable
final class GroupStage: BaseGroupStage, SideStorable {
    
    var matchFormat: MatchFormat {
        get {
            format ?? .defaultFormatForMatchType(.groupStage)
        }
        set {
            format = newValue
        }
    }
    
    var tournamentStore: TournamentStore? {
        return TournamentLibrary.shared.store(tournamentId: self.tournament)
    }
    
    // MARK: - Computed dependencies

    func _matches() -> [Match] {
        guard let tournamentStore = self.tournamentStore else { return [] }
        return tournamentStore.matches.filter { $0.groupStage == self.id }.sorted(by: \.index)
//        Store.main.filter { $0.groupStage == self.id }
    }
    
    func tournamentObject() -> Tournament? {
        Store.main.findById(self.tournament)
    }
    
    // MARK: -
    
    func teamAt(groupStagePosition: Int) -> TeamRegistration? {
        if step > 0 {
            return teams().first(where: { $0.groupStagePositionAtStep(step) == groupStagePosition })
        }
        return teams().first(where: { $0.groupStagePosition == groupStagePosition })
    }
    
    func groupStageTitle(_ displayStyle: DisplayStyle = .wide) -> String {
        if let name { return name }
        
        var stepLabel = ""
        if step > 0 {
            stepLabel = " (" + (step + 1).ordinalFormatted(feminine: true) + " phase)"
        }
        
        switch displayStyle {
        case .title:
            return "Poule \(index + 1)" + stepLabel
        case .wide:
            return "Poule \(index + 1)"
        case .short:
            return "#\(index + 1)"
        }
    }
    
    var computedOrder: Int {
        index + step * 100
    }
    
    func isRunning() -> Bool { // at least a match has started
        _matches().anySatisfy({ $0.isRunning() })
    }
    
    func hasStarted() -> Bool { // meaning at least one match is over
        _matches().filter { $0.hasEnded() }.isEmpty == false
    }
    
    func hasEnded() -> Bool {
        let _matches = _matches()
        if _matches.isEmpty { return false }
        //guard teams().count == size else { return false }
        return _matches.anySatisfy { $0.hasEnded() == false } == false
    }
    
    fileprivate func _createMatch(index: Int) -> Match {
        let match: Match = Match(groupStage: self.id,
                                 index: index,
                                 format: self.matchFormat,
                                 name: self.localizedMatchUpLabel(for: index))
        match.store = self.store
//        print("_createMatch(index)", index)
        return match
    }
    
    func removeReturnMatches(onlyLast: Bool = false) {
        
        var returnMatches = _matches().filter({ $0.index >= matchCount })
        if onlyLast {
            let matchPhaseCount = matchPhaseCount - 1
            returnMatches = returnMatches.filter({ $0.index >= matchCount * matchPhaseCount })
        }
        do {
            try self.tournamentStore?.matches.delete(contentOfs: returnMatches)
        } catch {
            Logger.error(error)
        }
    }
    
    var matchPhaseCount: Int {
        let count = _matches().count
        if matchCount > 0 {
            return count / matchCount
        } else {
            return 0
        }
    }
    
    func addReturnMatches() {
        var teamScores = [TeamScore]()
        var matches = [Match]()
        let matchPhaseCount = matchPhaseCount
        for i in 0..<_numberOfMatchesToBuild() {
            let newMatch = self._createMatch(index: i + matchCount * matchPhaseCount)
//            let newMatch = Match(groupStage: self.id, index: i, matchFormat: self.matchFormat, name: localizedMatchUpLabel(for: i))
            teamScores.append(contentsOf: newMatch.createTeamScores())
            matches.append(newMatch)
        }
        
        self.tournamentStore?.matches.addOrUpdate(contentOfs: matches)
        self.tournamentStore?.teamScores.addOrUpdate(contentOfs: teamScores)
    }
    
    func buildMatches(keepExistingMatches: Bool = false) {
        var teamScores = [TeamScore]()
        var matches = [Match]()
        clearScoreCache()
        
        if keepExistingMatches == false {
            _removeMatches()
            
            for i in 0..<_numberOfMatchesToBuild() {
                let newMatch = self._createMatch(index: i)
    //            let newMatch = Match(groupStage: self.id, index: i, matchFormat: self.matchFormat, name: localizedMatchUpLabel(for: i))
                teamScores.append(contentsOf: newMatch.createTeamScores())
                matches.append(newMatch)
            }
        } else {
            for match in _matches() {
                match.resetTeamScores(outsideOf: [])
                teamScores.append(contentsOf: match.createTeamScores())
            }
        }
        
        do {
            try self.tournamentStore?.matches.addOrUpdate(contentOfs: matches)
            try self.tournamentStore?.teamScores.addOrUpdate(contentOfs: teamScores)
        } catch {
            Logger.error(error)
        }
    }
    
    func playedMatches() -> [Match] {
        let ordered = _matches()
        let order = _matchOrder()
        let matchCount = max(1, matchCount)
        let count = ordered.count / matchCount
        if ordered.isEmpty == false && ordered.count % order.count == 0 {
            let repeatedArray = (0..<count).flatMap { i in
                order.map { $0 + i * order.count }
            }
            let result = repeatedArray.map { ordered[$0] }
            return result
        } else {
            return ordered
        }
    }
    
    func orderedIndexOfMatch(_ match: Match) -> Int {
        _matchOrder()[safe: match.index] ?? match.index
    }
    
    func updateGroupStageState() {
        clearScoreCache()
        
        if hasEnded(), let tournament = tournamentObject() {

            let teams = teams(true)
            for (index, team) in teams.enumerated() {
                team.qualified = index < tournament.qualifiedPerGroupStage
                if team.bracketPosition != nil && team.qualified == false {
                    tournamentObject()?.shouldVerifyBracket = true
                }
            }
            try self.tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: teams)
            if let tournamentObject = tournamentObject() {
                try DataStore.shared.tournaments.addOrUpdate(instance: tournamentObject)
            }
            self.tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: teams)
            
            let groupStagesAreOverAtFirstStep = tournament.groupStagesAreOver(atStep: 0)
            let nextStepGroupStages = tournament.groupStages(atStep: 1)
            let groupStagesAreOverAtSecondStep = tournament.groupStagesAreOver(atStep: 1)

            if groupStagesAreOverAtFirstStep, nextStepGroupStages.isEmpty || groupStagesAreOverAtSecondStep == true, tournament.groupStageLoserBracketAreOver(), tournament.rounds().isEmpty {
                tournament.endDate = Date()
                DataStore.shared.tournaments.addOrUpdate(instance: tournament)
            }
            
            tournament.updateTournamentState()
        }
    }
    
    func scoreLabel(forGroupStagePosition groupStagePosition: Int, score: TeamGroupStageScore? = nil) -> (wins: String, losses: String, setsDifference: String?, gamesDifference: String?)? {
        if let scoreData = (score ?? _score(forGroupStagePosition: groupStagePosition, nilIfEmpty: true)) {
            let hideSetDifference = matchFormat.setsToWin == 1
            let setDifference = scoreData.setDifference.formatted(.number.sign(strategy: .always(includingZero: true))) + " set" + scoreData.setDifference.pluralSuffix
            let gameDifference = scoreData.gameDifference.formatted(.number.sign(strategy: .always(includingZero: true))) + " jeu" + scoreData.gameDifference.localizedPluralSuffix("x")
            return (wins: scoreData.wins.formatted(), losses: scoreData.loses.formatted(), setsDifference: hideSetDifference ? nil : setDifference, gamesDifference: gameDifference)
//            return "\(scoreData.wins)/\(scoreData.loses) " + differenceAsString
        } else {
            return nil
        }
    }
    
//    func _score(forGroupStagePosition groupStagePosition: Int, nilIfEmpty: Bool = false) -> TeamGroupStageScore? {
//        guard let team = teamAt(groupStagePosition: groupStagePosition) else { return nil }
//        let matches = matches(forGroupStagePosition: groupStagePosition).filter({ $0.hasEnded() })
//        if matches.isEmpty && nilIfEmpty { return nil }
//        let wins = matches.filter { $0.winningTeamId == team.id }.count
//        let loses = matches.filter { $0.losingTeamId == team.id }.count
//        let differences = matches.compactMap { $0.scoreDifference(groupStagePosition, atStep: step) }
//        let setDifference = differences.map { $0.set }.reduce(0,+)
//        let gameDifference = differences.map { $0.game }.reduce(0,+)
//        return (team, wins, loses, setDifference, gameDifference)
//        /*
//         • 2 points par rencontre gagnée
//         • 1 point par rencontre perdue
//         • -1 point en cas de rencontre perdue par disqualification (scores de 6/0 6/0 attribués aux trois matchs)
//         • -2 points en cas de rencontre perdu par WO (scores de 6/0 6/0 attribués aux trois matchs)
//         */
//    }
//    
    func matches(forGroupStagePosition groupStagePosition: Int) -> [Match] {
        let combos = Array((0..<size).combinations(ofCount: 2))
        var matchIndexes = [Int]()
        for (index, combo) in combos.enumerated() {
            if combo.contains(groupStagePosition) { //team is playing
                matchIndexes.append(index)
            }
        }
        return _matches().filter { matchIndexes.contains($0.index%matchCount) }
    }
    
    func initialStartDate(forTeam team: TeamRegistration) -> Date? {
        guard let groupStagePosition = team.groupStagePositionAtStep(step) else { return nil }
        return matches(forGroupStagePosition: groupStagePosition).compactMap({ $0.startDate }).sorted().first ?? startDate
    }
    
    func matchPlayed(by groupStagePosition: Int, againstPosition: Int) -> Match? {
        if groupStagePosition == againstPosition { return nil }
        let combos = Array((0..<size).combinations(ofCount: 2))
        var matchIndexes = [Int]()
        for (index, combo) in combos.enumerated() {
            if combo.contains(groupStagePosition) && combo.contains(againstPosition) { //teams are playing
                matchIndexes.append(index)
            }
        }
        return _matches().first(where: { matchIndexes.contains($0.index) })
    }
    
    func availableToStart(playedMatches: [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 group stage availableToStart", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
        }
        #endif
        return playedMatches.filter({ $0.isRunning() == false && $0.canBeStarted(inMatches: runningMatches, checkCanPlay: checkCanPlay) }).sorted(by: \.computedStartDateForSorting)
    }
    
    func runningMatches(playedMatches: [Match]) -> [Match] {
        #if _DEBUG_TIME //DEBUGING TIME
        let start = Date()
        defer {
            let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
            print("func group stage runningMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
        }
        #endif
        return playedMatches.filter({ $0.isRunning() }).sorted(by: \.computedStartDateForSorting)
    }
    
    func readyMatches(playedMatches: [Match]) -> [Match] {
        #if _DEBUG_TIME //DEBUGING TIME
        let start = Date()
        defer {
            let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
            print("func group stage readyMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
        }
        #endif
        return playedMatches.filter({ $0.isReady() && $0.isRunning() == false && $0.hasEnded() == false })
    }

    func finishedMatches(playedMatches: [Match]) -> [Match] {
        #if _DEBUG_TIME //DEBUGING TIME
        let start = Date()
        defer {
            let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
            print("func group stage finishedMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
        }
        #endif
        return playedMatches.filter({ $0.hasEnded() }).sorted(by: \.computedEndDateForSorting).reversed()
    }

    func isReturnMatchEnabled() -> Bool {
        _matches().count > matchCount
    }
    
    private func _matchOrder() -> [Int] {
        var order: [Int]
        
        switch size {
        case 3:
            order = [1, 2, 0]
        case 4:
            order = [2, 3, 1, 4, 5, 0]
        case 5:
            order = [3, 5, 8, 2, 6, 1, 9, 4, 7, 0]
        case 6:
            order = [4, 7, 9, 3, 6, 11, 2, 8, 10, 1, 13, 5, 12, 14, 0]
        default:
            order = []
        }
        
        return order
    }


    func indexOf(_ matchIndex: Int) -> Int {
        _matchOrder().firstIndex(of: matchIndex) ?? matchIndex
    }
    
    func _matchUp(for matchIndex: Int) -> [Int] {
        let combinations = Array((0..<size).combinations(ofCount: 2))
        return combinations[safe: matchIndex%matchCount] ?? []
    }

    func returnMatchesSuffix(for matchIndex: Int) -> String {
        if matchCount > 0 {
            let count = _matches().count
            if count > matchCount * 2 {
                return " - vague \((matchIndex / matchCount) + 1)"
            }
            
            if matchIndex >= matchCount {
                return " - retour"
            }
        }

        return ""
    }
    
    func localizedMatchUpLabel(for matchIndex: Int) -> String {
        let matchUp = _matchUp(for: matchIndex)
        if let index = matchUp.first, let index2 = matchUp.last {
            return "#\(index + 1) vs #\(index2 + 1)" + returnMatchesSuffix(for: matchIndex)
        } else {
            return "--"
        }
    }
    
    var matchCount: Int {
        (size * (size - 1)) / 2
    }
    
    func team(teamPosition team: TeamPosition, inMatchIndex matchIndex: Int) -> TeamRegistration? {
        let _teams = _teams(for: matchIndex)
        switch team {
        case .one:
            return _teams.first ?? nil
        case .two:
            return _teams.last ?? nil
        }
    }
    
    private func _teams(for matchIndex: Int) -> [TeamRegistration?] {
        let combinations = Array(0..<size).combinations(ofCount: 2).map {$0}
        return combinations[safe: matchIndex%matchCount]?.map { teamAt(groupStagePosition: $0) } ?? []
    }
    
    private func _removeMatches() {
        self.tournamentStore?.matches.delete(contentOfs: _matches())
    }
        
    private func _numberOfMatchesToBuild() -> Int {
        (size * (size - 1)) / 2
    }
    
    func unsortedPlayers() -> [PlayerRegistration] {
        unsortedTeams().flatMap({ $0.unsortedPlayers() })
    }
    
    typealias TeamScoreAreInIncreasingOrder = (TeamGroupStageScore, TeamGroupStageScore) -> Bool

    typealias TeamGroupStageScore = (team: TeamRegistration, wins: Int, loses: Int, setDifference: Int, gameDifference: Int)

    fileprivate func _headToHead(_ teamPosition: TeamRegistration, _ otherTeam: TeamRegistration) -> Bool {
        let indexes = [teamPosition, otherTeam].compactMap({ $0.groupStagePosition }).sorted()
        let combos = Array((0..<size).combinations(ofCount: 2))
        let matchIndexes = combos.enumerated().compactMap { $0.element == indexes ? $0.offset : nil }
        let matches = _matches().filter { matchIndexes.contains($0.index) }
        if matches.count > 1 {
            let scoreA = calculateScore(for: teamPosition, matches: matches, groupStagePosition: teamPosition.groupStagePosition!)
            let scoreB = calculateScore(for: otherTeam, matches: matches, groupStagePosition: otherTeam.groupStagePosition!)
            
            let teamsSorted = [scoreA, scoreB].sorted { (lhs, rhs) in
                let predicates: [TeamScoreAreInIncreasingOrder] = [
                    { $0.wins < $1.wins },
                    { $0.setDifference < $1.setDifference },
                    { $0.gameDifference < $1.gameDifference},
                    { [self] in $0.team.groupStagePositionAtStep(self.step)! > $1.team.groupStagePositionAtStep(self.step)! }
                ]
                
                for predicate in predicates {
                    if !predicate(lhs, rhs) && !predicate(rhs, lhs) {
                        continue
                    }
                    
                    return predicate(lhs, rhs)
                }
                
                return false
            }.map({ $0.team })
            
            return teamsSorted.first == teamPosition
        } else {
            
            if let matchIndex = combos.firstIndex(of: indexes), let match = _matches().first(where: { $0.index == matchIndex }) {
                return teamPosition.id == match.losingTeamId
            } else {
                return false
            }
            
        }
    }
    
    func unsortedTeams() -> [TeamRegistration] {
        guard let tournamentStore = self.tournamentStore else { return [] }
        if step > 0 {
            return tournamentStore.groupStages.filter({ $0.step == step - 1 }).compactMap({ $0.teams(true)[safe: index] })
        }
        return tournamentStore.teamRegistrations.filter { $0.groupStage == self.id && $0.groupStagePosition != nil }
    }
    
    
    var scoreCache: [Int: TeamGroupStageScore] = [:]
    
    func computedScore(forTeam team: TeamRegistration, step: Int = 0) -> TeamGroupStageScore? {
        guard let groupStagePositionAtStep = team.groupStagePositionAtStep(step) else {
            return nil
        }
        if let cachedScore = scoreCache[groupStagePositionAtStep] {
            return cachedScore
        } else {
            let score = _score(forGroupStagePosition: groupStagePositionAtStep)
            if let score = score {
                scoreCache[groupStagePositionAtStep] = score
            }
            return score
        }
    }
    
    func teams(_ sortedByScore: Bool = false, scores: [TeamGroupStageScore]? = nil) -> [TeamRegistration] {
        if sortedByScore {
            return unsortedTeams().compactMap({ team in
                // Check cache or use provided scores, otherwise calculate and store in cache
                scores?.first(where: { $0.team.id == team.id }) ?? {
                    return computedScore(forTeam: team, step: step)
                }()
            }).sorted { (lhs, rhs) in
                let predicates: [TeamScoreAreInIncreasingOrder] = [
                    { $0.wins < $1.wins },
                    { $0.setDifference < $1.setDifference },
                    { $0.gameDifference < $1.gameDifference},
                    { self._headToHead($0.team, $1.team) },
                    { [self] in $0.team.groupStagePositionAtStep(self.step)! > $1.team.groupStagePositionAtStep(self.step)! }
                ]
                
                for predicate in predicates {
                    if !predicate(lhs, rhs) && !predicate(rhs, lhs) {
                        continue
                    }
                    
                    return predicate(lhs, rhs)
                }
                
                return false
            }.map({ $0.team }).reversed()
        } else {
            return unsortedTeams().sorted(by: \TeamRegistration.groupStagePosition!)
        }
    }

    func _score(forGroupStagePosition groupStagePosition: Int, nilIfEmpty: Bool = false) -> TeamGroupStageScore? {
        // Check if the score for this position is already cached
        if let cachedScore = scoreCache[groupStagePosition] {
            return cachedScore
        }
        
        guard let team = teamAt(groupStagePosition: groupStagePosition) else { return nil }
        let matches = matches(forGroupStagePosition: groupStagePosition).filter({ $0.hasEnded() })
        if matches.isEmpty && nilIfEmpty { return nil }
        let score = calculateScore(for: team, matches: matches, groupStagePosition: groupStagePosition)
        scoreCache[groupStagePosition] = score
        return score
    }
    
    private func calculateScore(for team: TeamRegistration, matches: [Match], groupStagePosition: Int) -> TeamGroupStageScore {
        let wins = matches.filter { $0.winningTeamId == team.id }.count
        let loses = matches.filter { $0.losingTeamId == team.id }.count
        let differences = matches.compactMap { $0.scoreDifference(groupStagePosition, atStep: step) }
        let setDifference = differences.map { $0.set }.reduce(0,+)
        let gameDifference = differences.map { $0.game }.reduce(0,+)
        
        return (team, wins, loses, setDifference, gameDifference)
    }
    
    // Clear the cache if necessary, for example when starting a new step or when matches update
    func clearScoreCache() {
        scoreCache.removeAll()
    }

//    func teams(_ sortedByScore: Bool = false, scores: [TeamGroupStageScore]? = nil) -> [TeamRegistration] {
//        if sortedByScore {
//            return unsortedTeams().compactMap({ team in
//                scores?.first(where: { $0.team.id == team.id }) ?? _score(forGroupStagePosition: team.groupStagePositionAtStep(step)!)
//            }).sorted { (lhs, rhs) in
//                // Calculate intermediate values once and reuse them
//                let lhsWins = lhs.wins
//                let rhsWins = rhs.wins
//                let lhsSetDifference = lhs.setDifference
//                let rhsSetDifference = rhs.setDifference
//                let lhsGameDifference = lhs.gameDifference
//                let rhsGameDifference = rhs.gameDifference
//                let lhsHeadToHead = self._headToHead(lhs.team, rhs.team)
//                let rhsHeadToHead = self._headToHead(rhs.team, lhs.team)
//                let lhsGroupStagePosition = lhs.team.groupStagePositionAtStep(self.step)!
//                let rhsGroupStagePosition = rhs.team.groupStagePositionAtStep(self.step)!
//                
//                // Define comparison predicates in the same order
//                let predicates: [(Bool, Bool)] = [
//                    (lhsWins < rhsWins, lhsWins > rhsWins),
//                    (lhsSetDifference < rhsSetDifference, lhsSetDifference > rhsSetDifference),
//                    (lhsGameDifference < rhsGameDifference, lhsGameDifference > rhsGameDifference),
//                    (lhsHeadToHead, rhsHeadToHead),
//                    (lhsGroupStagePosition > rhsGroupStagePosition, lhsGroupStagePosition < rhsGroupStagePosition)
//                ]
//                
//                // Iterate over predicates and return as soon as a valid comparison is found
//                for (lhsPredicate, rhsPredicate) in predicates {
//                    if lhsPredicate { return true }
//                    if rhsPredicate { return false }
//                }
//                
//                return false
//            }.map({ $0.team }).reversed()
//        } else {
//            return unsortedTeams().sorted(by: \TeamRegistration.groupStagePosition!)
//        }
//    }

    func updateMatchFormat(_ updatedMatchFormat: MatchFormat) {
        self.matchFormat = updatedMatchFormat
        self.updateAllMatchesFormat()
    }
    
    func updateAllMatchesFormat() {
        let playedMatches = playedMatches()
        playedMatches.forEach { match in
            match.matchFormat = matchFormat
        }
        self.tournamentStore?.matches.addOrUpdate(contentOfs: playedMatches)
    }
    
    func pasteData() -> String {
        var data: [String] = []
        data.append(self.groupStageTitle())
        teams().forEach { team in
            data.append(team.teamLabelRanked(displayRank: true, displayTeamName: true))
        }
        
        return data.joined(separator: "\n")
    }
    
    func finalPosition(ofTeam team: TeamRegistration) -> Int? {
        guard hasEnded() else { return nil }
        return teams(true).firstIndex(of: team)
    }

    override func deleteDependencies() {
        let matches = self._matches()
        for match in matches {
            match.deleteDependencies()
        }
        self.tournamentStore?.matches.deleteDependencies(matches)
    }
    
//    init(from decoder: Decoder) throws {
//        let container = try decoder.container(keyedBy: CodingKeys.self)
//        
//        id = try container.decode(String.self, forKey: ._id)
//        storeId = try container.decode(String.self, forKey: ._storeId)
//        lastUpdate = try container.decode(Date.self, forKey: ._lastUpdate)
//        tournament = try container.decode(String.self, forKey: ._tournament)
//        index = try container.decode(Int.self, forKey: ._index)
//        size = try container.decode(Int.self, forKey: ._size)
//        format = try container.decodeIfPresent(MatchFormat.self, forKey: ._format)
//        startDate = try container.decodeIfPresent(Date.self, forKey: ._startDate)
//        name = try container.decodeIfPresent(String.self, forKey: ._name)
//        step = try container.decodeIfPresent(Int.self, forKey: ._step) ?? 0
//    }
//
//    func encode(to encoder: Encoder) throws {
//        var container = encoder.container(keyedBy: CodingKeys.self)
//        
//        try container.encode(id, forKey: ._id)
//        try container.encode(storeId, forKey: ._storeId)
//        try container.encode(lastUpdate, forKey: ._lastUpdate)
//        try container.encode(tournament, forKey: ._tournament)
//        try container.encode(index, forKey: ._index)
//        try container.encode(size, forKey: ._size)
//        try container.encode(format, forKey: ._format)
//        try container.encode(startDate, forKey: ._startDate)
//        try container.encode(name, forKey: ._name)
//        try container.encode(step, forKey: ._step)
//    }
    
    func insertOnServer() {
        self.tournamentStore?.groupStages.writeChangeAndInsertOnServer(instance: self)
        for match in self._matches() {
            match.insertOnServer()
        }
    }

}

extension GroupStage {
    enum CodingKeys: String, CodingKey {
        case _id = "id"
        case _storeId = "storeId"
        case _lastUpdate = "lastUpdate"
        case _tournament = "tournament"
        case _index = "index"
        case _size = "size"
        case _format = "format"
        case _startDate = "startDate"
        case _name = "name"
        case _step = "step"
    }
}

extension GroupStage: Selectable {
    func selectionLabel(index: Int) -> String {
        groupStageTitle()
    }
    
    func badgeValue() -> Int? {
        return runningMatches(playedMatches: _matches()).count
    }
    
    func badgeValueColor() -> Color? {
        return nil
    }
    
    func badgeImage() -> Badge? {
        if teams().count < size {
            return .xmark
        } else {
            return hasEnded() ? .checkmark : nil
        }
    }
}
