package org.terst.doot.widget.data import java.time.Instant import java.time.temporal.ChronoUnit data class FloatingSlot( val task: WidgetItem, val slotStart: Instant, val slotEnd: Instant ) data class TaskFragment( val slots: List, val startTime: Instant, val endTime: Instant ) object SlotPacker { /** * Assigns 1-hour slots to floating tasks, packing them forward from [now], * bumping past any overlapping scheduled events. * Returns groups of consecutive task slots as TaskFragments. * A new fragment starts whenever a scheduled event falls between two task slots. */ fun pack( floatingTasks: List, scheduledEvents: List, now: Instant ): List { if (floatingTasks.isEmpty()) return emptyList() val eventIntervals: List> = scheduledEvents.mapNotNull { event -> val s = event.start?.let { runCatching { Instant.parse(it) }.getOrNull() } ?: return@mapNotNull null val e = event.end?.let { runCatching { Instant.parse(it) }.getOrNull() } ?: return@mapNotNull null s to e } val slots = mutableListOf() var cursor = now for (task in floatingTasks) { var slotStart = cursor var slotEnd = slotStart.plus(1, ChronoUnit.HOURS) // Bump cursor past any overlapping event, re-checking after each bump var bumped = true while (bumped) { bumped = false for ((evStart, evEnd) in eventIntervals) { if (slotStart < evEnd && slotEnd > evStart) { slotStart = evEnd slotEnd = slotStart.plus(1, ChronoUnit.HOURS) bumped = true break } } } slots.add(FloatingSlot(task, slotStart, slotEnd)) cursor = slotEnd } // Group consecutive slots into fragments, splitting when a scheduled event falls between them val fragments = mutableListOf() var current = mutableListOf(slots[0]) for (i in 1 until slots.size) { val prevEnd = slots[i - 1].slotEnd val nextStart = slots[i].slotStart val eventBetween = prevEnd < nextStart && eventIntervals.any { (evStart, _) -> evStart >= prevEnd && evStart < nextStart } if (eventBetween) { fragments.add(current.toFragment()) current = mutableListOf() } current.add(slots[i]) } fragments.add(current.toFragment()) return fragments } private fun List.toFragment() = TaskFragment( slots = this, startTime = first().slotStart, endTime = last().slotEnd ) }