import Foundation /// Pure gapped-fractional-ordering math: append, insert, midpoint, and the /// renumber target, plus the display-order tie-break rule shared by the /// loader and the writer. No filesystem or model dependency — see /// DESIGN/01-storage-format.md § Ordering, Deletion. enum Ranks: Sendable { /// Gap between successive ranks on append/head-insert, and the multiple /// used by `renumbered(count:)`. private static let gap: Double = 1024 // MARK: - Append / insert /// Rank for a new item appended after all visible siblings. /// An empty lane's first item lands at `1024` (the board convention). static func append(toVisible orders: some Sequence) -> Double { (orders.max() ?? 0) + gap } /// Rank for a new item inserted before all visible siblings. /// An empty lane's first item lands at `1024` (the board convention). static func insertAtHead(ofVisible orders: some Sequence) -> Double { guard let minOrder = orders.min() else { return gap } return minOrder - gap } /// Rank strictly between `a` and `b`, or `nil` if no `Double` is /// representable between them — including when `a == b` (a duplicate /// order, the tie case). Never returns a value ≤ min(a, b) or /// ≥ max(a, b). static func midpoint(between a: Double, and b: Double) -> Double? { let lower = min(a, b) let upper = max(a, b) guard lower < upper else { return nil } let mid = lower + (upper - lower) / 2 guard mid > lower, mid < upper else { return nil } return mid } /// `count` fresh ranks, whole multiples of 1024 in ascending order /// (1024, 2048, …) — the renumber target when midpoint precision is /// exhausted. Deterministic by construction; the writer applies these, /// in order, to the current visible siblings in display order. static func renumbered(count: Int) -> [Double] { guard count > 0 else { return [] } return (1...count).map { Double($0) * gap } } // MARK: - Display order /// Ascending display order: primary key `order`, ties broken by folder /// name (lexicographic) for deterministic rendering. Shared by the /// loader and the writer so both apply the same tie-break rule. static func isOrderedForDisplay( _ lhs: T, before rhs: T, order: (T) -> Double, name: (T) -> String ) -> Bool { let lhsOrder = order(lhs) let rhsOrder = order(rhs) return lhsOrder != rhsOrder ? lhsOrder < rhsOrder : name(lhs) < name(rhs) } /// Sorts siblings into display order — ascending `order`, ties broken by /// folder name. static func sortedForDisplay( _ items: [T], order: (T) -> Double, name: (T) -> String ) -> [T] { items.sorted { isOrderedForDisplay($0, before: $1, order: order, name: name) } } // MARK: - Tombstone exclusion /// `append(toVisible:)`, ignoring tombstoned siblings. Tombstones are /// inert to ordering — appends operate on visible siblings only. static func append(toVisible items: some Sequence<(order: Double, isDeleted: Bool)>) -> Double { append(toVisible: items.filter { !$0.isDeleted }.map { $0.order }) } /// `insertAtHead(ofVisible:)`, ignoring tombstoned siblings. Tombstones /// are inert to ordering — inserts operate on visible siblings only. static func insertAtHead(ofVisible items: some Sequence<(order: Double, isDeleted: Bool)>) -> Double { insertAtHead(ofVisible: items.filter { !$0.isDeleted }.map { $0.order }) } }