// // TripPlanningEngine.swift // SportsTime // // Thin orchestrator that delegates to scenario-specific planners. // import Foundation /// Main entry point for trip planning. /// Delegates to scenario-specific planners via the ScenarioPlanner protocol. final class TripPlanningEngine { /// Plans itineraries based on the request inputs. /// Automatically detects which scenario applies and delegates to the appropriate planner. /// /// - Parameter request: The planning request containing all inputs /// - Returns: Ranked itineraries on success, or explicit failure with reason func planItineraries(request: PlanningRequest) -> ItineraryResult { // Detect scenario and get the appropriate planner let planner = ScenarioPlannerFactory.planner(for: request) // Delegate to the scenario planner let result = planner.plan(request: request) // Apply preference filters to successful results return applyPreferenceFilters(to: result, request: request) } // MARK: - Private /// Applies allowRepeatCities filter after scenario planners return. /// Note: Region filtering is done during game selection in scenario planners. private func applyPreferenceFilters( to result: ItineraryResult, request: PlanningRequest ) -> ItineraryResult { guard case .success(let originalOptions) = result else { return result } var options = originalOptions // Filter repeat cities (this is enforced during beam search, but double-check here) options = RouteFilters.filterRepeatCities( options, allow: request.preferences.allowRepeatCities ) if options.isEmpty && !request.preferences.allowRepeatCities { let violatingCities = RouteFilters.findRepeatCities(in: originalOptions) return .failure(PlanningFailure( reason: .repeatCityViolation(cities: violatingCities) )) } // Region filtering is applied during game selection in scenario planners return .success(options) } }