// // ScenarioPlanner.swift // SportsTime // // Protocol for scenario-based trip planning. // import Foundation /// Protocol that all scenario planners must implement. /// Each scenario (A, B, C, D, E) has its own isolated implementation. /// /// - Invariants: /// - Always returns either success or explicit failure, never throws /// - Success contains ranked itinerary options /// - Failure contains reason and any constraint violations protocol ScenarioPlanner { /// Plan itineraries for this scenario. /// - Parameter request: The planning request with all inputs /// - Returns: Success with ranked itineraries, or explicit failure func plan(request: PlanningRequest) -> ItineraryResult } /// Factory for creating the appropriate scenario planner. /// /// - Expected Behavior: /// - planningMode == .teamFirst with >= 2 teams → ScenarioEPlanner /// - followTeamId != nil → ScenarioDPlanner /// - selectedGames not empty → ScenarioBPlanner /// - startLocation AND endLocation != nil → ScenarioCPlanner /// - Otherwise → ScenarioAPlanner (default) /// /// Priority order: E > D > B > C > A (first matching wins) enum ScenarioPlannerFactory { /// Creates the appropriate planner based on the request inputs. /// /// - Expected Behavior: /// - planningMode == .teamFirst with >= 2 teams → ScenarioEPlanner /// - followTeamId set → ScenarioDPlanner /// - selectedGames not empty → ScenarioBPlanner /// - Both start and end locations → ScenarioCPlanner /// - Otherwise → ScenarioAPlanner static func planner(for request: PlanningRequest) -> ScenarioPlanner { #if DEBUG print("🔍 ScenarioPlannerFactory: Selecting planner...") print(" - planningMode: \(request.preferences.planningMode)") print(" - selectedTeamIds.count: \(request.preferences.selectedTeamIds.count)") print(" - followTeamId: \(request.preferences.followTeamId ?? "nil")") print(" - selectedGames.count: \(request.selectedGames.count)") print(" - startLocation: \(request.startLocation?.name ?? "nil")") print(" - endLocation: \(request.endLocation?.name ?? "nil")") #endif // Scenario E: Team-First mode - user selects teams, finds optimal trip windows if request.preferences.planningMode == .teamFirst && request.preferences.selectedTeamIds.count >= 2 { #if DEBUG print("🔍 ScenarioPlannerFactory: → ScenarioEPlanner (team-first)") #endif return ScenarioEPlanner() } // Scenario D fallback: Team-First with single team → follow that team if request.preferences.planningMode == .teamFirst && request.preferences.selectedTeamIds.count == 1 { // Single team in teamFirst mode → treat as follow-team #if DEBUG print("🔍 ScenarioPlannerFactory: → ScenarioDPlanner (team-first single team)") #endif return ScenarioDPlanner() } // Scenario D: User wants to follow a specific team if request.preferences.followTeamId != nil { #if DEBUG print("🔍 ScenarioPlannerFactory: → ScenarioDPlanner (follow team)") #endif return ScenarioDPlanner() } // Scenario B: User selected specific games if !request.selectedGames.isEmpty { #if DEBUG print("🔍 ScenarioPlannerFactory: → ScenarioBPlanner (selected games)") #endif return ScenarioBPlanner() } // Scenario C: User specified start and end locations if request.startLocation != nil && request.endLocation != nil { #if DEBUG print("🔍 ScenarioPlannerFactory: → ScenarioCPlanner (start/end locations)") #endif return ScenarioCPlanner() } // Scenario A: Date range only (default) #if DEBUG print("🔍 ScenarioPlannerFactory: → ScenarioAPlanner (default/date range)") #endif return ScenarioAPlanner() } /// Classifies which scenario applies to this request. /// /// - Expected Behavior: /// - planningMode == .teamFirst with >= 2 teams → .scenarioE /// - followTeamId set → .scenarioD /// - selectedGames not empty → .scenarioB /// - Both start and end locations → .scenarioC /// - Otherwise → .scenarioA static func classify(_ request: PlanningRequest) -> PlanningScenario { if request.preferences.planningMode == .teamFirst && request.preferences.selectedTeamIds.count >= 2 { return .scenarioE } // Scenario D fallback: Team-First with single team → follow that team if request.preferences.planningMode == .teamFirst && request.preferences.selectedTeamIds.count == 1 { return .scenarioD } if request.preferences.followTeamId != nil { return .scenarioD } if !request.selectedGames.isEmpty { return .scenarioB } if request.startLocation != nil && request.endLocation != nil { return .scenarioC } return .scenarioA } }