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

import Foundation
import LeStorage

@Observable
class Match: ModelObject, Storable {
    static func resourceName() -> String { "matches" }
    static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
    
    static func setServerTitle(upperRound: Round, matchIndex: Int) -> String {
        if upperRound.index == 0 { return upperRound.roundTitle() }
        return upperRound.roundTitle() + " #" + (matchIndex + 1).formatted()
    }
    
    var byeState: Bool = false
    
    var id: String = Store.randomId()
    var round: String?
    var groupStage: String?
    var startDate: Date?
    var endDate: Date?
    var index: Int
    private var format: MatchFormat?
    //var court: String?
    var servingTeamId: String?
    var winningTeamId: String?
    var losingTeamId: String?
    //var broadcasted: Bool
    var name: String?
    //var order: Int
    var disabled: Bool = false
    private(set) var courtIndex: Int?
    var confirmed: Bool = false
    
    internal init(round: String? = nil, groupStage: String? = nil, startDate: Date? = nil, endDate: Date? = nil, index: Int, matchFormat: MatchFormat? = nil, servingTeamId: String? = nil, winningTeamId: String? = nil, losingTeamId: String? = nil, name: String? = nil, disabled: Bool = false, courtIndex: Int? = nil, confirmed: Bool = false) {
        self.round = round
        self.groupStage = groupStage
        self.startDate = startDate
        self.endDate = endDate
        self.index = index
        self.format = matchFormat
        //self.court = court
        self.servingTeamId = servingTeamId
        self.winningTeamId = winningTeamId
        self.losingTeamId = losingTeamId
        self.disabled = disabled
        self.name = name
        self.courtIndex = courtIndex
        self.confirmed = confirmed
//        self.broadcasted = broadcasted
//        self.order = order
    }
    
    // MARK: - Computed dependencies

    var teamScores: [TeamScore] {
        return Store.main.filter { $0.match == self.id }
    }
    
    // MARK: -
    
    override func deleteDependencies() throws {
        DataStore.shared.teamScores.deleteDependencies(self.teamScores)
    }
    
    func indexInRound(in matches: [Match]? = nil) -> Int {
        if groupStage != nil {
            return index
        } else if let index = (matches ?? roundObject?.playedMatches().sorted(by: \.index))?.firstIndex(where: { $0.id == id }) {
            return index
        }
        return RoundRule.matchIndexWithinRound(fromMatchIndex: index)
    }
    
    func matchWarningSubject() -> String {
        [roundTitle(), matchTitle(.short)].compacted().joined(separator: " ")
    }
    
    func matchWarningMessage() -> String {
        [roundTitle(), matchTitle(.short), startDate?.localizedDate(), courtName()].compacted().joined(separator: "\n")
    }
    
    func matchTitle(_ displayStyle: DisplayStyle = .wide, inMatches matches: [Match]? = nil) -> String {
        if let groupStageObject {
            return groupStageObject.localizedMatchUpLabel(for: index)
        }

        switch displayStyle {
        case .wide, .title:
            return "Match \(indexInRound(in: matches) + 1)"
        case .short:
            return "#\(indexInRound(in: matches) + 1)"
        }
    }
    
    func isSeedLocked(atTeamPosition teamPosition: TeamPosition) -> Bool {
        return previousMatch(teamPosition)?.disabled == true
    }
    
    func unlockSeedPosition(atTeamPosition teamPosition: TeamPosition) {
        previousMatch(teamPosition)?.enableMatch()
    }
    
    @discardableResult
    func lockAndGetSeedPosition(atTeamPosition slot: TeamPosition?, opposingSeeding: Bool = false) -> Int {
        let matchIndex = index
        var teamPosition : TeamPosition {
            if let slot {
                return slot
            } else {
                let seedRound = RoundRule.roundIndex(fromMatchIndex: matchIndex)
                let numberOfMatches = RoundRule.numberOfMatches(forRoundIndex: seedRound)
                let isUpper = RoundRule.matchIndexWithinRound(fromMatchIndex: matchIndex) < (numberOfMatches / 2)
                var teamPosition = slot ?? (isUpper ? .one : .two)
                if opposingSeeding {
                    teamPosition = slot ?? (isUpper ? .two : .one)
                }
                return teamPosition
            }
        }
        previousMatch(teamPosition)?.disableMatch()
        return matchIndex * 2 + teamPosition.rawValue
    }
    
    func isSeededBy(team: TeamRegistration, inTeamPosition teamPosition: TeamPosition) -> Bool {
        guard let bracketPosition = team.bracketPosition else { return false }
        return index * 2 + teamPosition.rawValue == bracketPosition
    }
    
    func estimatedEndDate(_ additionalEstimationDuration: Int) -> Date? {
        let minutesToAdd = Double(matchFormat.getEstimatedDuration(additionalEstimationDuration))
        return startDate?.addingTimeInterval(minutesToAdd * 60.0)
    }
    
    func winner() -> TeamRegistration? {
        guard let winningTeamId else { return nil }
        return Store.main.findById(winningTeamId)
    }
    
    func localizedStartDate() -> String {
        if let startDate {
            return startDate.formatted(date: .abbreviated, time: .shortened)
        } else {
            return ""
        }
    }
    
    func scoreLabel() -> String {
        if hasWalkoutTeam() == true {
            return "WO"
        }
        let scoreOne = teamScore(.one)?.score?.components(separatedBy: ",") ?? []
        let scoreTwo = teamScore(.two)?.score?.components(separatedBy: ",") ?? []
        let tuples = zip(scoreOne, scoreTwo).map { ($0, $1) }
        let scores = tuples.map { $0 + "/" + $1 }.joined(separator: " ")
        return scores
    }
    
    func cleanScheduleAndSave(_ targetStartDate: Date? = nil) {
        startDate = targetStartDate
        confirmed = targetStartDate == nil ? false : true
        endDate = nil
        followingMatch()?.cleanScheduleAndSave(nil)
        _loserMatch()?.cleanScheduleAndSave(nil)
        do {
            try DataStore.shared.matches.addOrUpdate(instance: self)
        } catch {
            Logger.error(error)
        }
    }

    func resetMatch() {
        losingTeamId = nil
        winningTeamId = nil
        endDate = nil
        removeCourt()
        servingTeamId = nil
    }
    
    func resetScores() {
        teamScores.forEach({ $0.score = nil })
        do {
            try DataStore.shared.teamScores.addOrUpdate(contentOfs: teamScores)
        } catch {
            Logger.error(error)
        }
    }
    
    func teamWillBeWalkOut(_ team: TeamRegistration) {
        resetMatch()
        let previousScores = teamScores.filter({ $0.luckyLoser != nil })
        do {
            try DataStore.shared.teamScores.delete(contentOfs: previousScores)
        } catch {
            Logger.error(error)
        }

        if let existingTeamScore = teamScore(ofTeam: team) {
            do {
                try DataStore.shared.teamScores.delete(instance: existingTeamScore)
            } catch {
                Logger.error(error)
            }
        }
        
        let teamScoreWalkout = TeamScore(match: id, team: team)
        teamScoreWalkout.walkOut = 1
        do {
            try DataStore.shared.teamScores.addOrUpdate(instance: teamScoreWalkout)
        } catch {
            Logger.error(error)
        }
    }

    func luckyLosers() -> [TeamRegistration] {
        return roundObject?.previousRound()?.losers() ?? []
    }
    
    func isWalkOutSpot(_ teamPosition: TeamPosition) -> Bool {
        return teamScore(teamPosition)?.walkOut == 1
    }
    
    func setLuckyLoser(team: TeamRegistration, teamPosition: TeamPosition) {
        resetMatch()
        let previousScores = teamScores.filter({ $0.luckyLoser != nil })
        do {
            try DataStore.shared.teamScores.delete(contentOfs: previousScores)
        } catch {
            Logger.error(error)
        }

        if let existingTeamScore = teamScore(ofTeam: team) {
            do {
                try DataStore.shared.teamScores.delete(instance: existingTeamScore)
            } catch {
                Logger.error(error)
            }
        }
        
        let matchIndex = index
        let position = matchIndex * 2 + teamPosition.rawValue
        let teamScoreLuckyLoser = TeamScore(match: id, team: team)
        teamScoreLuckyLoser.luckyLoser = position
        do {
            try DataStore.shared.teamScores.addOrUpdate(instance: teamScoreLuckyLoser)
        } catch {
            Logger.error(error)
        }
    }

    func disableMatch() {
        _toggleMatchDisableState(true)
    }
    
    func enableMatch() {
        _toggleMatchDisableState(false)
    }
    
    private func _loserMatch() -> Match? {
        let indexInRound = RoundRule.matchIndexWithinRound(fromMatchIndex: index)
        return roundObject?.loserRounds().first?.getMatch(atMatchIndexInRound: indexInRound / 2)
    }

    func _toggleLoserMatchDisableState(_ state: Bool) {
        guard let loserMatch = _loserMatch() else { return }
        guard let next = _otherMatch() else { return }
        loserMatch.byeState = true
        if next.disabled {
            loserMatch.byeState = false
        }
        loserMatch._toggleMatchDisableState(state, forward: true)
    }
    
    fileprivate func _otherMatch() -> Match? {
        guard let round else { return nil }
        guard index > 0 else { return nil }
        let nextIndex = (index - 1) / 2
        let topMatchIndex = (nextIndex * 2) + 1
        let bottomMatchIndex = (nextIndex + 1) * 2
        let isTopMatch = topMatchIndex + 1 == index
        let lookingForIndex = isTopMatch ? topMatchIndex : bottomMatchIndex
        return Store.main.filter(isIncluded: { $0.round == round && $0.index == lookingForIndex }).first
    }
    
    private func _forwardMatch(inRound round: Round) -> Match? {
        guard let roundObjectNextRound = round.nextRound() else { return nil }
        let nextIndex = (index - 1) / 2
        return Store.main.filter(isIncluded: { $0.round == roundObjectNextRound.id && $0.index == nextIndex }).first
    }

    func _toggleForwardMatchDisableState(_ state: Bool) {
        guard let roundObject else { return }
        guard roundObject.parent != nil else { return }
        guard let forwardMatch = _forwardMatch(inRound: roundObject) else { return }
        guard let next = _otherMatch() else { return }
        if next.disabled && byeState == false && next.byeState == false {
            forwardMatch.byeState = false
            forwardMatch._toggleMatchDisableState(state, forward: true)
        } else if byeState && next.byeState {
            print("don't disable forward match")
            forwardMatch.byeState = false
            forwardMatch._toggleMatchDisableState(false, forward: true)
        } else {
            forwardMatch.byeState = true
            forwardMatch._toggleMatchDisableState(state, forward: true)
        }
        
//        if next.disabled == false {
//            forwardMatch.byeState = state
//        }
        
        
        
//        
//        if next.disabled == state {
//            if next.byeState != byeState {
//                //forwardMatch.byeState = state
//                forwardMatch._toggleMatchDisableState(state)
//            } else {
//                forwardMatch._toggleByeState(state)
//            }
//        } else {
//        }
//        forwardMatch._toggleByeState(state)
    }

    func _toggleMatchDisableState(_ state: Bool, forward: Bool = false) {
        //if disabled == state { return }
        disabled = state
        //byeState = false
        do {
            try DataStore.shared.matches.addOrUpdate(instance: self)
        } catch {
            Logger.error(error)
        }

        _toggleLoserMatchDisableState(state)
        if forward {
            _toggleForwardMatchDisableState(state)
        } else {
            topPreviousRoundMatch()?._toggleMatchDisableState(state)
            bottomPreviousRoundMatch()?._toggleMatchDisableState(state)
        }
    }

    func next() -> Match? {
        return Store.main.filter(isIncluded: { $0.round == round && $0.index > index }).sorted(by: \.index).first
    }
    
    func followingMatch() -> Match? {
        guard let nextRoundId = roundObject?.nextRound()?.id else { return nil }
        return getFollowingMatch(fromNextRoundId: nextRoundId)
    }
    
    func getFollowingMatch(fromNextRoundId nextRoundId: String) -> Match? {
        return Store.main.filter(isIncluded: { $0.round == nextRoundId && $0.index == (index - 1) / 2 }).first
    }
    
    func getDuration() -> Int {
        if let tournament = currentTournament() {
            matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)
        } else {
            matchFormat.getEstimatedDuration()
        }
    }
    
    func roundTitle() -> String? {
        if groupStage != nil { return groupStageObject?.groupStageTitle() }
        else if let roundObject { return roundObject.roundTitle() }
        else { return nil }
    }
    
    func topPreviousRoundMatchIndex() -> Int {
        return index * 2 + 1
    }

    func bottomPreviousRoundMatchIndex() -> Int {
        return (index + 1) * 2
    }
    
    func topPreviousRoundMatch() -> Match? {
        guard let roundObject else { return nil }
        let matches: [Match] = Store.main.filter { match in
            match.index == topPreviousRoundMatchIndex() && match.round != nil && match.round == roundObject.previousRound()?.id
        }
        return matches.sorted(by: \.index).first
    }
    
    func bottomPreviousRoundMatch() -> Match? {
        guard let roundObject else { return nil }
        let matches: [Match] = Store.main.filter { match in
            match.index == bottomPreviousRoundMatchIndex() && match.round != nil && match.round == roundObject.previousRound()?.id
        }
        return matches.sorted(by: \.index).first
    }
    
    func upperBracketMatch(_ teamPosition: TeamPosition) -> Match? {
        if teamPosition == .one {
            return roundObject?.upperBracketTopMatch(ofMatchIndex: index)
        } else {
            return roundObject?.upperBracketBottomMatch(ofMatchIndex: index)
        }
    }
    
    func previousMatch(_ teamPosition: TeamPosition) -> Match? {
        if teamPosition == .one {
            return topPreviousRoundMatch()
        } else {
            return bottomPreviousRoundMatch()
        }
    }
    
    func upperMatches() -> [Match] {
        guard let roundObject else { return [] }
        return [roundObject.upperBracketTopMatch(ofMatchIndex: index), roundObject.upperBracketBottomMatch(ofMatchIndex: index)].compactMap({ $0 })
    }
    
    var computedOrder: Int {
        guard let roundObject else { return index }
        return roundObject.isLoserBracket() ? roundObject.index * 100 + indexInRound() : roundObject.index * 1000 + indexInRound()
    }
    
    func previousMatches() -> [Match] {
        guard let roundObject else { return [] }
        return Store.main.filter { match in
            (match.index == topPreviousRoundMatchIndex() || match.index == bottomPreviousRoundMatchIndex())
            && match.round == roundObject.previousRound()?.id
        }.sorted(by: \.index)
    }
    
    var matchFormat: MatchFormat {
        get {
            format ?? .defaultFormatForMatchType(.groupStage)
        }
        set {
            format = newValue
        }
    }
    
    func setWalkOut(_ teamPosition: TeamPosition) {
        let teamScoreWalkout = teamScore(teamPosition) ?? TeamScore(match: id, team: team(teamPosition))
        teamScoreWalkout.walkOut = 0
        let teamScoreWinning = teamScore(teamPosition.otherTeam) ?? TeamScore(match: id, team: team(teamPosition.otherTeam))
        teamScoreWinning.walkOut = nil
        do {
            try DataStore.shared.teamScores.addOrUpdate(contentOfs: [teamScoreWalkout, teamScoreWinning])
        } catch {
            Logger.error(error)
        }

        if endDate == nil {
            endDate = Date()
        }

        winningTeamId = teamScoreWinning.teamRegistration
        losingTeamId = teamScoreWalkout.teamRegistration
        groupStageObject?.updateGroupStageState()
        roundObject?.updateTournamentState()
        updateFollowingMatchTeamScore()
    }
    
    func setScore(fromMatchDescriptor matchDescriptor: MatchDescriptor) {
        updateScore(fromMatchDescriptor: matchDescriptor)
        if endDate == nil {
            endDate = Date()
        }
        if startDate == nil {
            startDate = endDate?.addingTimeInterval(Double(-getDuration()*60))
        }
        winningTeamId = team(matchDescriptor.winner)?.id
        losingTeamId = team(matchDescriptor.winner.otherTeam)?.id
        groupStageObject?.updateGroupStageState()
        roundObject?.updateTournamentState()
        updateFollowingMatchTeamScore()
    }

    func updateScore(fromMatchDescriptor matchDescriptor: MatchDescriptor) {
        let teamScoreOne = teamScore(.one) ?? TeamScore(match: id, team: team(.one))
        teamScoreOne.score = matchDescriptor.teamOneScores.joined(separator: ",")
        let teamScoreTwo = teamScore(.two) ?? TeamScore(match: id, team: team(.two))
        teamScoreTwo.score = matchDescriptor.teamTwoScores.joined(separator: ",")
        do {
            try DataStore.shared.teamScores.addOrUpdate(contentOfs: [teamScoreOne, teamScoreTwo])
        } catch {
            Logger.error(error)
        }
        matchFormat = matchDescriptor.matchFormat
    }
    
    func updateFollowingMatchTeamScore() {
        followingMatch()?.updateTeamScores()
        _loserMatch()?.updateTeamScores()
    }
    
    func resetTeamScores(outsideOf newTeamScores: [TeamScore]) {
        let ids = newTeamScores.map { $0.id }
        let teamScores = teamScores.filter({ ids.contains($0.id) == false })
        if teamScores.isEmpty == false {
            do {
                try DataStore.shared.teamScores.delete(contentOfs: teamScores)
            } catch {
                Logger.error(error)
            }
            followingMatch()?.resetTeamScores(outsideOf: [])
            _loserMatch()?.resetTeamScores(outsideOf: [])
        }
    }

    func createTeamScores() -> [TeamScore] {
        let teamOne = team(.one)
        let teamTwo = team(.two)
        let teams = [teamOne, teamTwo].compactMap({ $0 }).map { TeamScore(match: id, team: $0) }
        return teams
    }
    
    func getOrCreateTeamScores() -> [TeamScore] {
        let teamOne = team(.one)
        let teamTwo = team(.two)
        let teams = [teamOne, teamTwo].compactMap({ $0 }).map { teamScore(ofTeam: $0) ?? TeamScore(match: id, team: $0) }
        return teams
    }
    
    func updateTeamScores() {
        let teams = getOrCreateTeamScores()
        do {
            try DataStore.shared.teamScores.addOrUpdate(contentOfs: teams)
        } catch {
            Logger.error(error)
        }
        resetTeamScores(outsideOf: teams)
        if teams.isEmpty == false {
            updateFollowingMatchTeamScore()
        }
    }
    
    func validateMatch(fromStartDate: Date, toEndDate: Date, fieldSetup: MatchFieldSetup) {
        if hasEnded() == false {
            startDate = fromStartDate

            switch fieldSetup {
            case .random:
                if let _courtIndex = availableCourts().randomElement() {
                    setCourt(_courtIndex)
                }
            case .field(let _courtIndex):
                setCourt(_courtIndex)
            }

        } else {
            startDate = fromStartDate
            endDate = toEndDate
        }

        confirmed = true
    }
    
    func courtName() -> String? {
        guard let courtIndex else { return nil }
        if let courtName = currentTournament()?.courtName(atIndex: courtIndex) {
            return courtName
        } else {
            return Court.courtIndexedTitle(atIndex: courtIndex)
        }
    }
    
    func courtCount() -> Int {
        return currentTournament()?.courtCount ?? 1
    }
    
    func courtIsAvailable(_ courtIndex: Int) -> Bool {
        let courtUsed = currentTournament()?.courtUsed() ?? []
        return courtUsed.contains(courtIndex) == false
    }
    
    func courtIsPreferred(_ courtIndex: Int) -> Bool {
        return false
    }
    
    func availableCourts() -> [Int] {
        let courtUsed = currentTournament()?.courtUsed() ?? []
        let availableCourts = Array(0..<courtCount())
        return Array(Set(availableCourts.map { $0 }).subtracting(Set(courtUsed)))
    }
    
    func removeCourt() {
        courtIndex = nil
    }
    
    func setCourt(_ courtIndex: Int) {
        self.courtIndex = courtIndex
    }
    
    func canBeStarted(inMatches matches: [Match]) -> Bool {
        let teams = teamScores
        guard teams.count == 2 else { return false }
        guard hasEnded() == false else { return false }
        guard hasStarted() == false else { return false }
        return teams.compactMap({ $0.team }).allSatisfy({ $0.canPlay() && isTeamPlaying($0, inMatches: matches) == false })
    }
    
    func isTeamPlaying(_ team: TeamRegistration, inMatches matches: [Match]) -> Bool {
        return matches.filter({ $0.teamScores.compactMap { $0.teamRegistration }.contains(team.id) }).isEmpty == false
    }

    var computedStartDateForSorting: Date {
        return startDate ?? .distantFuture
    }

    var computedEndDateForSorting: Date {
        return endDate ?? .distantFuture
    }

    func hasSpaceLeft() -> Bool {
        return teamScores.count < 2
    }

    func isReady() -> Bool {
        return teamScores.count >= 2
//        teams().count == 2
    }
    
    func isEmpty() -> Bool {
        return teamScores.isEmpty
//        teams().isEmpty
    }
    
    func hasEnded() -> Bool {
        return endDate != nil
    }
    
    func isGroupStage() -> Bool {
        return groupStage != nil
    }
    
    func isBracket() -> Bool {
        return round != nil
    }
        
    func walkoutTeam() -> [TeamRegistration] { 
        //walkout 0 means real walkout, walkout 1 means lucky loser situation
        return scores().filter({ $0.walkOut == 0 }).compactMap { $0.team }
    }
    
    func hasWalkoutTeam() -> Bool {
        return walkoutTeam().isEmpty == false
    }
    
    func currentTournament() -> Tournament? {
        return groupStageObject?.tournamentObject() ?? roundObject?.tournamentObject()
    }

    func tournamentId() -> String? {
        return groupStageObject?.tournament ?? roundObject?.tournament
    }
    
    func scores() -> [TeamScore] {
        return Store.main.filter(isIncluded: { $0.match == id })
    }
    
    func teams() -> [TeamRegistration] {
        if groupStage != nil {
            return [groupStageProjectedTeam(.one), groupStageProjectedTeam(.two)].compactMap { $0 }
        }
        return [roundProjectedTeam(.one), roundProjectedTeam(.two)].compactMap { $0 }
    }
    
    func scoreDifference(_ teamPosition: Int) -> (set: Int, game: Int)? {
        guard let teamScoreTeam = teamScore(.one), let teamScoreOtherTeam = teamScore(.two) else { return nil }
        var reverseValue = 1
        if teamPosition == team(.two)?.groupStagePosition {
            reverseValue = -1
        }
        let endedSetsOne = teamScoreTeam.score?.components(separatedBy: ",").compactMap({ Int($0) }) ?? matchFormat.defaultWalkOutScore(teamScoreTeam.isWalkOut())
        let endedSetsTwo = teamScoreOtherTeam.score?.components(separatedBy: ",").compactMap({ Int($0) }) ?? matchFormat.defaultWalkOutScore(teamScoreOtherTeam.isWalkOut())
        var setDifference : Int = 0
        let zip = zip(endedSetsOne, endedSetsTwo)
        if matchFormat.setsToWin == 1 {
            setDifference = endedSetsOne[0] - endedSetsTwo[0]
        } else {
            setDifference = zip.filter { $0 > $1 }.count - zip.filter { $1 > $0 }.count
        }
        let gameDifference = zip.map { ($0, $1) }.map { $0.0 - $0.1 }.reduce(0,+)
        return (setDifference * reverseValue, gameDifference * reverseValue)
    }

    func groupStageProjectedTeam(_ team: TeamPosition) -> TeamRegistration? {
        guard let groupStageObject else { return nil }
        return groupStageObject.team(teamPosition: team, inMatchIndex: index)
    }
    
    func roundProjectedTeam(_ team: TeamPosition) -> TeamRegistration? {
        guard let roundObject else { return nil }
        return roundObject.roundProjectedTeam(team, inMatch: self)
    }
    
    func teamWon(_ team: TeamRegistration?) -> Bool {
        guard let winningTeamId else { return false }
        return winningTeamId == team?.id
    }

    func teamWon(atPosition teamPosition: TeamPosition) -> Bool {
        guard let winningTeamId else { return false }
        return winningTeamId == team(teamPosition)?.id
    }
    
    func team(_ team: TeamPosition) -> TeamRegistration? {
        #if DEBUG_TIME
        let start = Date()
        defer {
            let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
            print("func match get team", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
        }
        #endif
        if groupStage != nil {
            switch team {
            case .one:
                return groupStageProjectedTeam(.one)
            case .two:
                return groupStageProjectedTeam(.two)
            }
        } else {
            switch team {
            case .one:
                return roundProjectedTeam(.one)
            case .two:
                return roundProjectedTeam(.two)
            }
        }
    }

    func teamNames(_ team: TeamRegistration?) -> [String]? {
        return team?.players().map { $0.playerLabel() }
    }

    func teamWalkOut(_ team: TeamRegistration?) -> Bool {
        return teamScore(ofTeam: team)?.isWalkOut() == true
    }
    
    func teamScore(_ team: TeamPosition) -> TeamScore? {
        return teamScore(ofTeam: self.team(team))
    }

    func teamScore(ofTeam team: TeamRegistration?) -> TeamScore? {
        return scores().first(where: { $0.teamRegistration == team?.id })
    }
    
    func isRunning() -> Bool { // at least a match has started
        return confirmed && hasStarted() && hasEnded() == false
    }
    
    func hasStarted() -> Bool { // meaning at least one match is over
        if let startDate {
            return startDate.timeIntervalSinceNow < 0
        }
        if hasEnded() {
            return true
        }
        return false
        
        //todo scores
//        if let score {
//            return score.hasEnded == false && score.sets.isEmpty == false
//        } else {
//            return false
//        }
    }
    
    var roundObject: Round? {
        guard let round else { return nil }
        return Store.main.findById(round)
    }
    
    var groupStageObject: GroupStage? {
        guard let groupStage else { return nil }
        return Store.main.findById(groupStage)
    }
    
    var isLoserBracket: Bool {
        return roundObject?.parent != nil
    }
    
    enum CodingKeys: String, CodingKey {
        case _id = "id"
        case _round = "round"
        case _groupStage = "groupStage"
        case _startDate = "startDate"
        case _endDate = "endDate"
        case _index = "index"
        case _format = "format"
//        case _court = "court"
        case _courtIndex = "courtIndex"
        case _servingTeamId = "servingTeamId"
        case _winningTeamId = "winningTeamId"
        case _losingTeamId = "losingTeamId"
//        case _broadcasted = "broadcasted"
        case _name = "name"
//        case _order = "order"
        case _disabled = "disabled"
        case _confirmed = "confirmed"
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        
        try container.encode(id, forKey: ._id)
        
        if let round = round {
            try container.encode(round, forKey: ._round)
        } else {
            try container.encodeNil(forKey: ._round)
        }
        
        if let groupStage = groupStage {
            try container.encode(groupStage, forKey: ._groupStage)
        } else {
            try container.encodeNil(forKey: ._groupStage)
        }
        
        if let startDate = startDate {
            try container.encode(startDate, forKey: ._startDate)
        } else {
            try container.encodeNil(forKey: ._startDate)
        }
        
        if let endDate = endDate {
            try container.encode(endDate, forKey: ._endDate)
        } else {
            try container.encodeNil(forKey: ._endDate)
        }
        
        try container.encode(index, forKey: ._index)
        
        if let format = format {
            try container.encode(format, forKey: ._format)
        } else {
            try container.encodeNil(forKey: ._format)
        }
        
        if let servingTeamId = servingTeamId {
            try container.encode(servingTeamId, forKey: ._servingTeamId)
        } else {
            try container.encodeNil(forKey: ._servingTeamId)
        }
        
        if let winningTeamId = winningTeamId {
            try container.encode(winningTeamId, forKey: ._winningTeamId)
        } else {
            try container.encodeNil(forKey: ._winningTeamId)
        }
        
        if let losingTeamId = losingTeamId {
            try container.encode(losingTeamId, forKey: ._losingTeamId)
        } else {
            try container.encodeNil(forKey: ._losingTeamId)
        }
        
        if let name = name {
            try container.encode(name, forKey: ._name)
        } else {
            try container.encodeNil(forKey: ._name)
        }
        
        try container.encode(disabled, forKey: ._disabled)
        
        if let courtIndex = courtIndex {
            try container.encode(courtIndex, forKey: ._courtIndex)
        } else {
            try container.encodeNil(forKey: ._courtIndex)
        }
        
        try container.encode(confirmed, forKey: ._confirmed)
    }
    
    func insertOnServer() throws {
        try DataStore.shared.matches.writeChangeAndInsertOnServer(instance: self)
        for teamScore in self.teamScores {
            try teamScore.insertOnServer()
        }
    }
    
}

enum MatchDateSetup: Hashable, Identifiable {
    case inMinutes(Int)
    case now
    case customDate
    
    var id: Int { hashValue }
}

enum MatchFieldSetup: Hashable, Identifiable {
    case random
//        case firstAvailable
    case field(Int)
    
    var id: Int { hashValue }
}
