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

import Foundation
import LeStorage
import SwiftUI

@Observable
final class Round: BaseRound, SideStorable {

    var _cachedSeedInterval: SeedInterval?

    internal init(tournament: String, index: Int, parent: String? = nil, matchFormat: MatchFormat? = nil, startDate: Date? = nil, groupStageLoserBracket: Bool = false, loserBracketMode: LoserBracketMode = .automatic) {
        
        super.init(tournament: tournament, index: index, parent: parent, format: matchFormat, startDate: startDate, groupStageLoserBracket: groupStageLoserBracket, loserBracketMode: loserBracketMode)
        
//        self.lastUpdate = Date()
//        self.tournament = tournament
//        self.index = index
//        self.parent = parent
//        self.format = matchFormat
//        self.startDate = startDate
//        self.groupStageLoserBracket = groupStageLoserBracket
//        self.loserBracketMode = loserBracketMode
    }
    
    required init(from decoder: any Decoder) throws {
        try super.init(from: decoder)
    }
    
    required public init() {
        super.init()
    }
    
    // MARK: - Computed dependencies
    
    var tournamentStore: TournamentStore? {
        return TournamentLibrary.shared.store(tournamentId: self.tournament)
    }

    func tournamentObject() -> Tournament? {
        return Store.main.findById(tournament)
    }
    
    func _matches() -> [Match] {
        guard let tournamentStore = self.tournamentStore else { return [] }
        return tournamentStore.matches.filter { $0.round == self.id }.sorted(by: \.index)
    }
    
    func getDisabledMatches() -> [Match] {
        guard let tournamentStore = self.tournamentStore else { return [] }
        return tournamentStore.matches.filter { $0.round == self.id && $0.disabled == true }
    }

    // MARK: -
    
    var matchFormat: MatchFormat {
        get {
            format ?? .defaultFormatForMatchType(.bracket)
        }
        set {
            format = newValue
        }
    }
    
    func hasStarted() -> Bool {
        return playedMatches().anySatisfy({ $0.hasStarted() })
    }
    
    func hasEnded() -> Bool {
        if isUpperBracket() {
            return playedMatches().anySatisfy({ $0.hasEnded() == false }) == false
        } else {
            return enabledMatches().anySatisfy({ $0.hasEnded() == false }) == false
        }
    }

    func upperMatches(ofMatch match: Match) -> [Match] {
        if parent != nil, previousRound() == nil, let parentRound {
            let matchIndex = match.index
            let indexInRound = RoundRule.matchIndexWithinRound(fromMatchIndex: matchIndex)
            return [parentRound.getMatch(atMatchIndexInRound: indexInRound * 2), parentRound.getMatch(atMatchIndexInRound: indexInRound * 2 + 1)].compactMap({ $0 })
        }
        return []
    }

    func previousMatches(ofMatch match: Match) -> [Match] {
        guard let previousRound = previousRound() else { return [] }
        guard let tournamentStore = self.tournamentStore else { return [] }
        
        return tournamentStore.matches.filter {
            $0.round == previousRound.id && ($0.index == match.topPreviousRoundMatchIndex() || $0.index == match.bottomPreviousRoundMatchIndex())
        }
        
//        return Store.main.filter {
//            ($0.index == match.topPreviousRoundMatchIndex() || $0.index == match.bottomPreviousRoundMatchIndex()) && $0.round == previousRound.id
//        }
    }

    func precedentMatches(ofMatch match: Match) -> [Match] {
        let upper = upperMatches(ofMatch: match)
        if upper.isEmpty == false {
            return upper
        }
        let previous : [Match] = previousMatches(ofMatch: match)
        if previous.isEmpty == false && previous.allSatisfy({ $0.disabled }), let previousRound = previousRound() {
            return previous.flatMap({ previousRound.precedentMatches(ofMatch: $0) })
        } else {
            return previous
        }        
    }
    
    func team(_ team: TeamPosition, inMatch match: Match, previousRound: Round?) -> TeamRegistration? {
        return roundProjectedTeam(team, inMatch: match, previousRound: previousRound)
    }
    
    func seed(_ team: TeamPosition, inMatchIndex matchIndex: Int) -> TeamRegistration? {
        return self.tournamentStore?.teamRegistrations.first(where: {
            $0.bracketPosition != nil
            && ($0.bracketPosition! / 2) == matchIndex
            && ($0.bracketPosition! % 2) == team.rawValue
        })
    }
    
    func seeds(inMatchIndex matchIndex: Int) -> [TeamRegistration] {
        guard let tournamentStore = self.tournamentStore else { return [] }
        return tournamentStore.teamRegistrations.filter {

            $0.tournament == tournament
            && $0.bracketPosition != nil
            && ($0.bracketPosition! / 2) == matchIndex
        }
        
//        return Store.main.filter(isIncluded: {
//            $0.tournament == tournament
//            && $0.bracketPosition != nil
//            && ($0.bracketPosition! / 2) == matchIndex
//        })
    }
    
    func seeds() -> [TeamRegistration] {
        guard let tournamentStore = self.tournamentStore else { return [] }
        let initialMatchIndex = RoundRule.matchIndex(fromRoundIndex: index)
        let numberOfMatches = RoundRule.numberOfMatches(forRoundIndex: index)
        return tournamentStore.teamRegistrations.filter {
            $0.bracketPosition != nil
            && ($0.bracketPosition! / 2) >= initialMatchIndex
            && ($0.bracketPosition! / 2) < initialMatchIndex + numberOfMatches
        }
    }
    
    func teamsOrSeeds() -> [TeamRegistration] {
        let seeds = seeds()
        if seeds.isEmpty {
            return playedMatches().flatMap({ $0.teams() })
        } else {
            return seeds
        }
    }


    func losers() -> [TeamRegistration] {
        let teamIds: [String] = self._matches().compactMap { $0.losingTeamId }
        return teamIds.compactMap { self.tournamentStore?.teamRegistrations.findById($0) }
    }

    func winners() -> [TeamRegistration] {
        let teamIds: [String] = self._matches().compactMap { $0.winningTeamId }
        return teamIds.compactMap { self.tournamentStore?.teamRegistrations.findById($0) }
    }

    func teams() -> [TeamRegistration] {
        return playedMatches().flatMap({ $0.teams() })
    }
    
    func roundProjectedTeam(_ team: TeamPosition, inMatch match: Match, previousRound: Round?) -> TeamRegistration? {
#if _DEBUG_TIME //DEBUGING TIME
let start = Date()
defer {
    let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
    print("func roundProjectedTeam", team.rawValue, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif
        if isLoserBracket() == false, let seed = seed(team, inMatchIndex: match.index) {
            return seed
        }

        switch team {
        case .one:
            if let luckyLoser = match.teamScores.first(where: { $0.luckyLoser == match.index * 2 }) {
                return luckyLoser.team
            } else if let previousMatch = topPreviousRoundMatch(ofMatch: match, previousRound: previousRound) {
                if let teamId = previousMatch.winningTeamId {
                    return self.tournamentStore?.teamRegistrations.findById(teamId)
                } else if previousMatch.disabled {
                    return previousMatch.teams().first
                }
            } else if let parent = upperBracketTopMatch(ofMatchIndex: match.index, previousRound: previousRound)?.losingTeamId {
                return self.store?.findById(parent)
            }
        case .two:
            if let luckyLoser = match.teamScores.first(where: { $0.luckyLoser == match.index * 2 + 1 }) {
                return luckyLoser.team
            } else if let previousMatch = bottomPreviousRoundMatch(ofMatch: match, previousRound: previousRound) {
                if let teamId = previousMatch.winningTeamId {
                    return self.tournamentStore?.teamRegistrations.findById(teamId)
                } else if previousMatch.disabled {
                    return previousMatch.teams().first
                }
            } else if let parent = upperBracketBottomMatch(ofMatchIndex: match.index, previousRound: previousRound)?.losingTeamId {
                return self.store?.findById(parent)
            }
        }
        
        return nil
    }
    
    func upperBracketTopMatch(ofMatchIndex matchIndex: Int, previousRound: Round?) -> Match? {
#if _DEBUG_TIME //DEBUGING TIME
let start = Date()
defer {
    let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
    print("func upperBracketTopMatch", matchIndex, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif
        let parentRound = parentRound
        if let parentRound, parentRound.parent == nil, groupStageLoserBracket == false, parentRound.loserBracketMode != .automatic {
            return nil
        }
        
        let indexInRound = RoundRule.matchIndexWithinRound(fromMatchIndex: matchIndex)
        if isLoserBracket(), previousRound == nil, let upperBracketTopMatch = parentRound?.getMatch(atMatchIndexInRound: indexInRound * 2) {
            return upperBracketTopMatch
        }
        return nil
    }

    func upperBracketBottomMatch(ofMatchIndex matchIndex: Int, previousRound: Round?) -> Match? {
#if _DEBUG_TIME //DEBUGING TIME
let start = Date()
defer {
    let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
    print("func upperBracketBottomMatch", matchIndex, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif

        let parentRound = parentRound
        if let parentRound, parentRound.parent == nil, groupStageLoserBracket == false, parentRound.loserBracketMode != .automatic {
            return nil
        }

        let indexInRound = RoundRule.matchIndexWithinRound(fromMatchIndex: matchIndex)
        if isLoserBracket(), previousRound == nil, let upperBracketBottomMatch = parentRound?.getMatch(atMatchIndexInRound: indexInRound * 2 + 1) {
            return upperBracketBottomMatch
        }
        return nil
    }
    

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

        guard let previousRound else { return nil }
        let topPreviousRoundMatchIndex = match.topPreviousRoundMatchIndex()
        return self.tournamentStore?.matches.first(where: {
            $0.round == previousRound.id && $0.index == topPreviousRoundMatchIndex
        })
    }
    
    func bottomPreviousRoundMatch(ofMatch match: Match, previousRound: Round?) -> Match? {
#if _DEBUG_TIME //DEBUGING TIME
let start = Date()
defer {
    let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
    print("func bottomPreviousRoundMatch", match.id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif

        guard let previousRound else { return nil }
        let bottomPreviousRoundMatchIndex = match.bottomPreviousRoundMatchIndex()
        return self.tournamentStore?.matches.first(where: {
            $0.round == previousRound.id && $0.index == bottomPreviousRoundMatchIndex
        })
    }
    
    func getMatch(atMatchIndexInRound matchIndexInRound: Int) -> Match? {
        self.tournamentStore?.matches.first(where: {
            let index = RoundRule.matchIndexWithinRound(fromMatchIndex: $0.index)
            return $0.round == id && index == matchIndexInRound
        })
    }

    func enabledMatches() -> [Match] {
        guard let tournamentStore = self.tournamentStore else { return [] }
        return tournamentStore.matches.filter { $0.round == self.id && $0.disabled == false }.sorted(by: \.index)
    }
    
//    func displayableMatches() -> [Match] {
//#if _DEBUG_TIME //DEBUGING TIME
//        let start = Date()
//        defer {
//            let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
//            print("func displayableMatches of round: ", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
//        }
//#endif
//
//        if index == 0 && isUpperBracket() {
//            var matches : [Match?] = [playedMatches().first]
//            matches.append(loserRounds().first?.playedMatches().first)
//            return matches.compactMap({ $0 })
//        } else {
//            return playedMatches()
//        }
//    }
    
    func playedMatches() -> [Match] {
        if isUpperBracket() {
            return enabledMatches()
        } else {
            return _matches()
        }
    }
    
    func previousRound() -> Round? {
#if _DEBUG_TIME //DEBUGING TIME
        let start = Date()
        defer {
            let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
            print("func previousRound of: ", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
        }
#endif
        return self.tournamentStore?.rounds.first(where: { $0.parent == parent && $0.index == index + 1 })
    }

    func nextRound() -> Round? {
        return self.tournamentStore?.rounds.first(where: { $0.parent == parent && $0.index == index - 1 })
    }
    
    func loserRounds(forRoundIndex roundIndex: Int) -> [Round] {
        return loserRoundsAndChildren().filter({ $0.index == roundIndex }).sorted(by: \.theoryCumulativeMatchCount)
    }

    func loserRounds(forRoundIndex roundIndex: Int, loserRoundsAndChildren: [Round]) -> [Round] {
        return loserRoundsAndChildren.filter({ $0.index == roundIndex }).sorted(by: \.theoryCumulativeMatchCount)
    }

    func isDisabled() -> Bool {
        return _matches().allSatisfy({ $0.disabled })
    }

    func isRankDisabled() -> Bool {
        return _matches().allSatisfy({ $0.disabled && $0.teamScores.isEmpty })
    }
    
    func resetFromRoundAllMatchesStartDate() {
        _matches().forEach({
            $0.startDate = nil
        })
        loserRoundsAndChildren().forEach { round in
            round.resetFromRoundAllMatchesStartDate()
        }
        nextRound()?.resetFromRoundAllMatchesStartDate()
    }
    
    func resetFromRoundAllMatchesStartDate(from match: Match) {
        let matches = _matches()
        if let index = matches.firstIndex(where: { $0.id == match.id }) {
            matches[index...].forEach { match in
                match.startDate = nil
            }
        }
        loserRoundsAndChildren().forEach { round in
            round.resetFromRoundAllMatchesStartDate()
        }
        nextRound()?.resetFromRoundAllMatchesStartDate()
    }
    
    func getActiveLoserRound() -> Round? {
        let rounds = loserRounds().filter({ $0.isDisabled() == false }).sorted(by: \.index).reversed()
        return rounds.first(where: { $0.hasStarted() && $0.hasEnded() == false }) ?? rounds.first
    }
    
    func enableRound() {
        _toggleRound(disable: false)
    }
    
    func disableRound() {
        _toggleRound(disable: true)
    }
    
    private func _toggleRound(disable: Bool) {
        let _matches = _matches()
        _matches.forEach { match in
            match.disabled = disable
            match.resetMatch()
            //we need to keep teamscores to handle disable ranking match round stuff
//            do {
//                try DataStore.shared.teamScores.delete(contentOfs: match.teamScores)
//            } catch {
//                Logger.error(error)
//            }
        }
        self.tournamentStore?.matches.addOrUpdate(contentOfs: _matches)
    }
    
    var cumulativeMatchCount: Int {
        var totalMatches = playedMatches().count
        if let parentRound {
            totalMatches += parentRound.cumulativeMatchCount
        }
        return totalMatches
    }
    
    func initialRound() -> Round? {
        if let parentRound {
            return parentRound.initialRound()
        } else {
            return self
        }
    }
    
    func estimatedEndDate(_ additionalEstimationDuration: Int) -> Date? {
        return enabledMatches().last?.estimatedEndDate(additionalEstimationDuration)
    }
    
    func getLoserRoundStartDate() -> Date? {
        return loserRoundsAndChildren().first(where: { $0.isDisabled() == false })?.enabledMatches().first?.startDate
    }
    
    func estimatedLoserRoundEndDate(_ additionalEstimationDuration: Int) -> Date? {
        let lastMatch = loserRoundsAndChildren().last(where: { $0.isDisabled() == false })?.enabledMatches().last
        return lastMatch?.estimatedEndDate(additionalEstimationDuration)
    }

    func disabledMatches() -> [Match] {
        return _matches().filter({ $0.disabled })
    }
    
    func allLoserRoundMatches() -> [Match] {
        loserRoundsAndChildren().flatMap({ $0._matches() })
    }
    
    var theoryCumulativeMatchCount: Int {
        var totalMatches = RoundRule.numberOfMatches(forRoundIndex: index)
        if let parentRound {
            totalMatches += parentRound.theoryCumulativeMatchCount
        }
        return totalMatches
    }

    
    func correspondingLoserRoundTitle(_ displayStyle: DisplayStyle = .wide) -> String {
        if let _cachedSeedInterval { return _cachedSeedInterval.localizedLabel(displayStyle) }
        
#if _DEBUG_TIME //DEBUGING TIME
        let start = Date()
        defer {
            let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
            print("func correspondingLoserRoundTitle()", duration.formatted(.units(allowed: [.seconds, .milliseconds])))
        }
#endif
        let initialMatchIndexFromRoundIndex = RoundRule.matchIndex(fromRoundIndex: index)
        var seedsAfterThisRound: [TeamRegistration] = []
        if let tournamentStore = tournamentStore {
            seedsAfterThisRound = tournamentStore.teamRegistrations.filter {
                $0.bracketPosition != nil
                && ($0.bracketPosition! / 2) < initialMatchIndexFromRoundIndex
            }
        }
        
//        let seedsAfterThisRound : [TeamRegistration] = Store.main.filter(isIncluded: {
//            $0.tournament == tournament
//            && $0.bracketPosition != nil
//            && ($0.bracketPosition! / 2) < initialMatchIndexFromRoundIndex
//        })
        
        var seedsCount = seedsAfterThisRound.count
        if seedsAfterThisRound.isEmpty {
            let nextRoundsDisableMatches = nextRoundsDisableMatches()
            seedsCount = disabledMatches().count - nextRoundsDisableMatches
        }
        let playedMatches = playedMatches()
        let seedInterval = SeedInterval(first: playedMatches.count + seedsCount + 1, last: playedMatches.count * 2 + seedsCount)
        
        _cachedSeedInterval = seedInterval
        return seedInterval.localizedLabel(displayStyle)
    }
    
    func hasNextRound() -> Bool {
        return nextRound()?.isRankDisabled() == false
    }
    
    func pasteData() -> String {
        var data: [String] = []
        data.append(self.roundTitle())

        playedMatches().forEach { match in
            data.append(match.matchTitle())
            data.append(match.team(.one)?.teamLabelRanked(displayRank: true, displayTeamName: true) ?? "-----")
            data.append(match.team(.two)?.teamLabelRanked(displayRank: true, displayTeamName: true) ?? "-----")
        }
        
        return data.joined(separator: "\n")
    }
    
    func seedInterval(initialMode: Bool = false) -> SeedInterval? {
        if initialMode == false, let _cachedSeedInterval { return _cachedSeedInterval }

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

        if isUpperBracket() {
            if index == 0 { return SeedInterval(first: 1, last: 2) }
            let initialMatchIndexFromRoundIndex = RoundRule.matchIndex(fromRoundIndex: index)
            
            if initialMode {
                let playedMatches = RoundRule.numberOfMatches(forRoundIndex: index)
                let seedInterval = SeedInterval(first: playedMatches + 1, last: playedMatches * 2)
                //print(seedInterval.localizedLabel())
                return seedInterval
            } else {
                var seedsAfterThisRound : [TeamRegistration] = []
                if let tournamentStore = self.tournamentStore {
                    seedsAfterThisRound = tournamentStore.teamRegistrations.filter {
                        $0.bracketPosition != nil
                        && ($0.bracketPosition! / 2) < initialMatchIndexFromRoundIndex
                    }
                }
                
                var seedsCount = seedsAfterThisRound.count
                if seedsAfterThisRound.isEmpty {
                    let nextRoundsDisableMatches = nextRoundsDisableMatches()
                    seedsCount = disabledMatches().count - nextRoundsDisableMatches
                }

                let playedMatches = playedMatches()
                //print("playedMatches \(playedMatches)", initialMode, parent, parentRound?.roundTitle(), seedsAfterThisRound.count)
                let seedInterval = SeedInterval(first: playedMatches.count + seedsCount + 1, last: playedMatches.count * 2 + seedsCount)
                //print(seedInterval.localizedLabel())
                _cachedSeedInterval = seedInterval
                return seedInterval

            }
        }
        
        if let previousRound = previousRound() {
            if (previousRound.enabledMatches().isEmpty == false || initialMode) {
                return previousRound.seedInterval(initialMode: initialMode)?.chunks()?.first
            } else {
                return previousRound.seedInterval(initialMode: initialMode)
            }
        } else if let parentRound {
            if parentRound.isUpperBracket() {
                return parentRound.seedInterval(initialMode: initialMode)
            }
            return parentRound.seedInterval(initialMode: initialMode)?.chunks()?.last
        }
        
        return nil
    }
    
    func roundTitle(_ displayStyle: DisplayStyle = .wide, initialMode: Bool = false) -> String {
        if groupStageLoserBracket {
            return "Classement Poules"
        }
        
        if parent != nil {
            if let seedInterval = seedInterval(initialMode: initialMode) {
                return seedInterval.localizedLabel(displayStyle)
            }
//            print("Round pas trouvé", id, parent, index)
            return "Match de classement"
        }
        return RoundRule.roundName(fromRoundIndex: index, displayStyle: displayStyle)
    }

    func updateTournamentState() {
        let tournamentObject = tournamentObject()
        if let tournamentObject, index == 0, isUpperBracket(), hasEnded() {
            tournamentObject.endDate = Date()
            DataStore.shared.tournaments.addOrUpdate(instance: tournamentObject)
        }
        
        tournamentObject?.updateTournamentState()
    }
    
    func roundStatus() -> String {
        let hasEnded = hasEnded()
        if hasStarted() && hasEnded == false {
            return "en cours"
        } else if hasEnded {
            return "terminée"
        } else {
            return "à démarrer"
        }
    }
        
    func loserRounds() -> [Round] {
        guard let tournamentStore = self.tournamentStore else { return [] }
#if _DEBUG_TIME //DEBUGING TIME
        let start = Date()
        defer {
            let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
            print("func loserRounds: ", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
        }
#endif

        return tournamentStore.rounds.filter( { $0.parent == id }).sorted(by: \.index).reversed()
    }
    
    func loserRoundsAndChildren() -> [Round] {
        let loserRounds = loserRounds()
        return loserRounds + loserRounds.flatMap({ $0.loserRoundsAndChildren() })
    }
    
    func isUpperBracket() -> Bool {
        return parent == nil && groupStageLoserBracket == false
    }
    
    func isLoserBracket() -> Bool {
        return parent != nil || groupStageLoserBracket
    }
    
    func deleteLoserBracket() {
        let loserRounds = loserRounds()
        self.tournamentStore?.rounds.delete(contentOfs: loserRounds)
    }
    
    func buildLoserBracket() {
        guard loserRounds().isEmpty else { return }
        let currentRoundMatchCount = RoundRule.numberOfMatches(forRoundIndex: index)
        guard currentRoundMatchCount > 1 else { return }
        let roundCount = RoundRule.numberOfRounds(forTeams: currentRoundMatchCount)
        
        var loserBracketMatchFormat = tournamentObject()?.loserBracketMatchFormat
//        if let parentRound {
//            loserBracketMatchFormat = tournamentObject()?.loserBracketSmartMatchFormat(parentRound.index)
//        }
        
        let rounds = (0..<roundCount).map { //index 0 is the final
            let round = Round(tournament: tournament, index: $0, matchFormat: loserBracketMatchFormat)
            round.parent = id //parent
            return round
        }
        self.tournamentStore?.rounds.addOrUpdate(contentOfs: rounds)
        let matchCount = RoundRule.numberOfMatches(forTeams: currentRoundMatchCount)

        let matches = (0..<matchCount).map { //0 is final match
            let roundIndex = RoundRule.roundIndex(fromMatchIndex: $0)
            let round = rounds[roundIndex]
            return Match(round: round.id, index: $0, format: loserBracketMatchFormat, name: round.roundTitle(initialMode: true))
            //initial mode let the roundTitle give a name without considering the playable match
        }
        
        self.tournamentStore?.matches.addOrUpdate(contentOfs: matches)
        
        loserRounds().forEach { round in
            round.buildLoserBracket()
        }
    }
    
    var parentRound: Round? {
        guard let parent = parent else { return nil }
        return self.tournamentStore?.rounds.findById(parent)
    }
    
    func nextRoundsDisableMatches() -> Int {
        if parent == nil, index > 0 {
            return tournamentObject()?.rounds().suffix(index).flatMap { $0.disabledMatches() }.count ?? 0
        } else {
            return 0
        }
    }

    func updateMatchFormat(_ updatedMatchFormat: MatchFormat, checkIfPossible: Bool, andLoserBracket: Bool) {
        if updatedMatchFormat.weight < self.matchFormat.weight {
            updateMatchFormatAndAllMatches(updatedMatchFormat)
            if andLoserBracket {
                loserRoundsAndChildren().forEach { round in
                    round.updateMatchFormat(updatedMatchFormat, checkIfPossible: checkIfPossible, andLoserBracket: true)
                }
            }
        }
    }
    
    func updateMatchFormatAndAllMatches(_ updatedMatchFormat: MatchFormat) {
        self.matchFormat = updatedMatchFormat
        self.updateMatchFormatOfAllMatches(updatedMatchFormat)
    }
    
    func updateMatchFormatOfAllMatches(_ updatedMatchFormat: MatchFormat) {
        let playedMatches = _matches()
        playedMatches.forEach { match in
            match.matchFormat = updatedMatchFormat
        }
        self.tournamentStore?.matches.addOrUpdate(contentOfs: playedMatches)
    }
    
    override func deleteDependencies() {
        let matches = self._matches()
        for match in matches {
            match.deleteDependencies()
        }
        
        self.tournamentStore?.matches.deleteDependencies(matches)
        
        let loserRounds = self.loserRounds()
        for round in loserRounds {
            round.deleteDependencies()
        }
        
        self.tournamentStore?.rounds.deleteDependencies(loserRounds)
    }
    
//    enum CodingKeys: String, CodingKey {
//        case _id = "id"
//        case _storeId = "storeId"
//        case _lastUpdate = "lastUpdate"
//        case _tournament = "tournament"
//        case _index = "index"
//        case _parent = "parent"
//        case _format = "format"
//        case _startDate = "startDate"
//        case _groupStageLoserBracket = "groupStageLoserBracket"
//        case _loserBracketMode = "loserBracketMode"
//    }
//    
//    required 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.decodeIfPresent(Date.self, forKey: ._lastUpdate) ?? Date()
//        tournament = try container.decode(String.self, forKey: ._tournament)
//        index = try container.decode(Int.self, forKey: ._index)
//        parent = try container.decodeIfPresent(String.self, forKey: ._parent)
//        format = try container.decodeIfPresent(MatchFormat.self, forKey: ._format)
//        startDate = try container.decodeIfPresent(Date.self, forKey: ._startDate)
//        groupStageLoserBracket = try container.decodeIfPresent(Bool.self, forKey: ._groupStageLoserBracket) ?? false
//        loserBracketMode = try container.decodeIfPresent(LoserBracketMode.self, forKey: ._loserBracketMode) ?? .automatic
//    }
//    
//    func encode(to encoder: Encoder) throws {
//        var container = encoder.container(keyedBy: CodingKeys.self)
//        
//        try container.encode(id, forKey: ._id)
//        try container.encode(lastUpdate, forKey: ._lastUpdate)
//        try container.encode(storeId, forKey: ._storeId)
//        try container.encode(tournament, forKey: ._tournament)
//        try container.encode(index, forKey: ._index)
//        try container.encode(groupStageLoserBracket, forKey: ._groupStageLoserBracket)
//        try container.encode(loserBracketMode, forKey: ._loserBracketMode)
//        
//        try container.encode(parent, forKey: ._parent)
//        try container.encode(format, forKey: ._format)
//        try container.encode(startDate, forKey: ._startDate)
//    
//    }
    
    func insertOnServer() {
        self.tournamentStore?.rounds.writeChangeAndInsertOnServer(instance: self)
        for match in self._matches() {
            match.insertOnServer()
        }
    }

}

extension Round: Selectable {
    
    func selectionLabel(index: Int) -> String {
        if let parentRound {
            return "Tour #\(parentRound.loserRounds().count - index)"
        } else {
            return roundTitle(.short)
        }
    }
    
    func badgeValue() -> Int? {
#if _DEBUG_TIME //DEBUGING TIME
        let start = Date()
        defer {
            let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
            print("func badgeValue round of: ", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
        }
#endif


        if let parentRound {
            return parentRound.loserRounds(forRoundIndex: index).flatMap { $0.playedMatches() }.filter({ $0.isRunning() }).count
        } else {
            return playedMatches().filter({ $0.isRunning() }).count
        }
    }
    
    func badgeValueColor() -> Color? {
        return nil
    }
    
    func badgeImage() -> Badge? {
#if _DEBUG_TIME //DEBUGING TIME
        let start = Date()
        defer {
            let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
            print("func badgeImage of round: ", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
        }
#endif
        return hasEnded() ? .checkmark : nil
    }
}


enum LoserBracketMode: Int, CaseIterable, Identifiable, Codable {
    var id: Int { self.rawValue }
    
    case automatic
    case manual
    
    func localizedLoserBracketMode() -> String {
        switch self {
        case .automatic:
            "Automatique"
        case .manual:
            "Manuelle"
        }
    }
    
    func localizedLoserBracketModeDescription() -> String {
        switch self {
        case .automatic:
            "Les perdants du tableau principal sont placés à leur place prévue."
        case .manual:
            "Aucun placement automatique n'est fait. Vous devez choisir les perdants qui se jouent."
        }
    }
}
