// // RoutineOrdering.swift // Workouts // // Copyright 2026 Rouslan Zenetl. All Rights Reserved. // import Foundation import SwiftUI /// Pure reordering math shared by any list backed by `.onMove` + a `SyncEngine` /// document write: given the row's current ids and a SwiftUI move gesture, compute /// the renumbered `order` values and hand back only the ones that actually changed, /// so a caller writes the smallest possible document diff. enum RoutineOrdering { /// Apply SwiftUI's `.onMove` semantics to `ids` (`Array.move(fromOffsets:toOffset:)`), /// renumber the result 0…n-1, and return only the `(id, newOrder)` pairs whose order /// differs from `currentOrders[id]`. An id absent from `currentOrders` always comes /// back (there's nothing to compare against, so it can't be assumed unchanged). /// /// Comparing every renumbered position against the stored value — rather than only /// the ones the drag actually touched — also normalizes legacy colliding order /// values (e.g. two exercises both stored at order 0) the first time the list is /// dragged: every row gets its correct 0…n-1 position written, not just the moved one. static func changedOrders( ids: [String], currentOrders: [String: Int], from source: IndexSet, to destination: Int ) -> [(id: String, order: Int)] { var reordered = ids reordered.move(fromOffsets: source, toOffset: destination) return reordered.enumerated().compactMap { index, id in currentOrders[id] == index ? nil : (id: id, order: index) } } } /// Naming helper for the "duplicate this routine" action. enum RoutineNaming { /// The next free "«base» Copy" name: `"«base» Copy"` when that's unused, else /// `"«base» Copy 2"`, `"«base» Copy 3"`, … — skipping any name already present in /// `existing` (case-sensitive match). static func uniqueName(base: String, existing: Set) -> String { let first = "\(base) Copy" guard existing.contains(first) else { return first } var suffix = 2 while existing.contains("\(base) Copy \(suffix)") { suffix += 1 } return "\(base) Copy \(suffix)" } }