# ItineraryConstraints API **Location:** `SportsTime/Core/Models/Domain/ItineraryConstraints.swift` **Verified by:** 22 tests in `SportsTimeTests/Domain/ItineraryConstraintsTests.swift` ## Overview `ItineraryConstraints` validates item positions during drag-drop operations. It enforces: - **Games cannot move** (CONS-01) - **Travel segments have day range limits** (CONS-02) - **Travel segments must respect game sortOrder on same day** (CONS-03) - **Custom items have no constraints** (CONS-04) ## Construction ```swift let constraints = ItineraryConstraints( tripDayCount: days.count, items: allItineraryItems // All items including games ) ``` **Parameters:** - `tripDayCount`: Total days in trip (1-indexed, so a 5-day trip has days 1-5) - `items`: All itinerary items (games, travel, custom). Games are used to calculate constraints for travel items. ## Public API ### `isValidPosition(for:day:sortOrder:) -> Bool` Check if a specific position is valid for an item. ```swift func isValidPosition(for item: ItineraryItem, day: Int, sortOrder: Double) -> Bool ``` **Usage during drag:** ```swift // On each drag position update let dropPosition = calculateDropPosition(at: touchLocation) let isValid = constraints.isValidPosition( for: draggedItem, day: dropPosition.day, sortOrder: dropPosition.sortOrder ) if isValid { showValidDropIndicator() } else { showInvalidDropIndicator() } ``` **Returns:** - `true`: Position is valid, allow drop - `false`: Position is invalid, reject drop (snap back) **Rules by item type:** | Item Type | Day Constraint | SortOrder Constraint | |-----------|----------------|----------------------| | `.game` | Always `false` | Always `false` | | `.travel` | Within valid day range | After departure games, before arrival games | | `.custom` | Any day 1...tripDayCount | Any sortOrder | ### `validDayRange(for:) -> ClosedRange?` Get the valid day range for a travel item (for visual feedback). ```swift func validDayRange(for item: ItineraryItem) -> ClosedRange? ``` **Usage at drag start:** ```swift // When drag begins, precompute valid range guard case .travel = draggedItem.kind, let validRange = constraints.validDayRange(for: draggedItem) else { // Not a travel item or impossible constraints return } // Use range to dim invalid days for day in 1...tripDayCount { if !validRange.contains(day) { dimDay(day) } } ``` **Returns:** - `ClosedRange`: Valid day range (e.g., `2...4`) - `nil`: Constraints are impossible (e.g., departure game after arrival game) ### `barrierGames(for:) -> [ItineraryItem]` Get games that constrain a travel item (for visual highlighting). ```swift func barrierGames(for item: ItineraryItem) -> [ItineraryItem] ``` **Usage for visual feedback:** ```swift // Highlight barrier games during drag let barriers = constraints.barrierGames(for: travelItem) for barrier in barriers { highlightAsBarrier(barrier) // e.g., gold border } ``` **Returns:** - Array of game items: Last departure city game + first arrival city game - Empty array: Not a travel item or no constraining games ## Integration Points ### ItineraryTableViewController (existing) ```swift // In reloadData() self.constraints = ItineraryConstraints(tripDayCount: tripDayCount, items: itineraryItems) // In drag handling if constraints.isValidPosition(for: draggedItem, day: targetDay, sortOrder: targetSortOrder) { // Allow drop } else { // Reject drop, snap back } ``` ### Phase 4 Implementation Notes 1. **Drag Start:** - Check `item.isReorderable` (games return `false`) - Call `validDayRange(for:)` to precompute valid days - Call `barrierGames(for:)` to identify visual barriers 2. **Drag Move:** - Calculate target (day, sortOrder) from touch position - Call `isValidPosition(for:day:sortOrder:)` for real-time feedback - Update insertion line (valid) or red indicator (invalid) 3. **Drag End:** - Final `isValidPosition(for:day:sortOrder:)` check - Valid: Update item's day/sortOrder, animate settle - Invalid: Animate snap back, haptic feedback ## Test Coverage | Requirement | Tests | Verified | |-------------|-------|----------| | CONS-01 (games cannot move) | 2 | Yes | | CONS-02 (travel day range) | 3 | Yes | | CONS-03 (travel sortOrder) | 5 | Yes | | CONS-04 (custom flexibility) | 2 | Yes | | Edge cases | 8 | Yes | | Success criteria | 3 | Yes | | Barrier games | 1 | Yes | | **Total** | **22** | **100%** | --- *API documented: Phase 02* *Ready for: Phase 04 (Drag Interaction)*