// // 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 { 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")") // Scenario E: Team-First mode - user selects teams, finds optimal trip windows if request.preferences.planningMode == .teamFirst && request.preferences.selectedTeamIds.count >= 2 { print("🔍 ScenarioPlannerFactory: → ScenarioEPlanner (team-first)") return ScenarioEPlanner() } // Scenario D: User wants to follow a specific team if request.preferences.followTeamId != nil { print("🔍 ScenarioPlannerFactory: → ScenarioDPlanner (follow team)") return ScenarioDPlanner() } // Scenario B: User selected specific games if !request.selectedGames.isEmpty { print("🔍 ScenarioPlannerFactory: → ScenarioBPlanner (selected games)") return ScenarioBPlanner() } // Scenario C: User specified start and end locations if request.startLocation != nil && request.endLocation != nil { print("🔍 ScenarioPlannerFactory: → ScenarioCPlanner (start/end locations)") return ScenarioCPlanner() } // Scenario A: Date range only (default) print("🔍 ScenarioPlannerFactory: → ScenarioAPlanner (default/date range)") 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 } if request.preferences.followTeamId != nil { return .scenarioD } if !request.selectedGames.isEmpty { return .scenarioB } if request.startLocation != nil && request.endLocation != nil { return .scenarioC } return .scenarioA } }