import Foundation import SwiftUI enum ScoreDialogKind { case setWon case closeSet case closeSetAnyway case matchWon } struct ScoreDialogState: Identifiable { let id = UUID() let kind: ScoreDialogKind var winnerSide: ScoringSide? var homePoints: Int = 0 var awayPoints: Int = 0 var homeSets: Int = 0 var awaySets: Int = 0 } @MainActor final class LiveScoreDialogHost: ObservableObject { @Published private(set) var pending: ScoreDialogState? private var waiter: CheckedContinuation? func show(_ state: ScoreDialogState) async -> Bool { guard pending == nil else { return false } return await withCheckedContinuation { continuation in pending = state waiter = continuation } } func resolve(_ value: Bool) { guard let continuation = waiter else { return } pending = nil waiter = nil continuation.resume(returning: value) } } @MainActor final class LiveScoreActions { private let rules: MatchScoringContext private let scoreController: ScoreController private let dialogHost: LiveScoreDialogHost private let onStopStream: () async -> Void private let onScoreboardChanged: () -> Void init( rules: MatchScoringContext, scoreController: ScoreController, dialogHost: LiveScoreDialogHost, onStopStream: @escaping () async -> Void, onScoreboardChanged: @escaping () -> Void = {} ) { self.rules = rules self.scoreController = scoreController self.dialogHost = dialogHost self.onStopStream = onStopStream self.onScoreboardChanged = onScoreboardChanged } func afterPointChange(score: ScoreState) async { guard rules.usesSetLogic else { return } guard let winner = rules.setWinnerFromPoints( homePoints: score.homePoints, awayPoints: score.awayPoints, currentSet: score.currentSet ) else { return } let close = await dialogHost.show( ScoreDialogState( kind: .setWon, winnerSide: winner, homePoints: score.homePoints, awayPoints: score.awayPoints ) ) if close { guard await scoreController.closeSetAsync() else { return } onScoreboardChanged() await afterCloseSet() } } func requestCloseSet() async { guard rules.usesSetLogic else { return } let score = scoreController.score let winner = rules.setWinnerFromPoints( homePoints: score.homePoints, awayPoints: score.awayPoints, currentSet: score.currentSet ) let ok = await dialogHost.show( ScoreDialogState(kind: winner == nil ? .closeSetAnyway : .closeSet) ) if !ok { return } guard await scoreController.closeSetAsync() else { return } onScoreboardChanged() await afterCloseSet() } private func afterCloseSet() async { let score = scoreController.score guard let winner = rules.matchWinner(homeSets: score.homeSets, awaySets: score.awaySets) else { return } let stop = await dialogHost.show( ScoreDialogState( kind: .matchWon, winnerSide: winner, homeSets: score.homeSets, awaySets: score.awaySets ) ) if stop { await onStopStream() } } } struct ScoreDialogRouter: View { @ObservedObject var host: LiveScoreDialogHost let homeName: String let awayName: String var body: some View { Color.clear .frame(width: 0, height: 0) .alert(item: dialogBinding) { dialog in alert(for: dialog) } } private var dialogBinding: Binding { Binding( get: { host.pending }, set: { _ in // Non risolvere qui: su iPad la dismiss dell'alert può azzerare l'item // dopo il tap su «Chiudi set» e sovrascrivere resolve(true) con false. } ) } private func alert(for dialog: ScoreDialogState) -> Alert { switch dialog.kind { case .setWon: let winner = dialog.winnerSide.map { scoringSideName($0, homeName: homeName, awayName: awayName) } ?? "" return Alert( title: Text(L10n.t("score.set.won.title")), message: Text(L10n.t("score.set.won.message", winner, dialog.homePoints, dialog.awayPoints)), primaryButton: .default(Text(L10n.t("score.action.close.set"))) { host.resolve(true) }, secondaryButton: .cancel(Text(L10n.t("score.action.continue.scoring"))) { host.resolve(false) } ) case .closeSet: return Alert( title: Text(L10n.t("score.action.close.set")), message: Text(L10n.t("broadcast.close.set.confirm.message")), primaryButton: .default(Text(L10n.t("score.action.close.set"))) { host.resolve(true) }, secondaryButton: .cancel(Text(L10n.t("action.cancel"))) { host.resolve(false) } ) case .closeSetAnyway: return Alert( title: Text(L10n.t("score.action.close.set")), message: Text(L10n.t("score.close.set.anyway.message")), primaryButton: .default(Text(L10n.t("score.action.close.anyway"))) { host.resolve(true) }, secondaryButton: .cancel(Text(L10n.t("action.cancel"))) { host.resolve(false) } ) case .matchWon: let winner = dialog.winnerSide.map { scoringSideName($0, homeName: homeName, awayName: awayName) } ?? "" return Alert( title: Text(L10n.t("score.match.won.title")), message: Text(L10n.t("score.match.won.message", winner, dialog.homeSets, dialog.awaySets)), primaryButton: .default(Text(L10n.t("score.action.close.live"))) { host.resolve(true) }, secondaryButton: .cancel(Text(L10n.t("score.action.continue.live"))) { host.resolve(false) } ) } } }