import Foundation import OpenClawChatUI struct CommandSessionSection: Identifiable { enum ID: Hashable { case pinned case category(String) case ungrouped } let id: ID let title: String let entries: [OpenClawChatSessionEntry] let showsHeader: Bool } enum CommandSessionGrouping { static func sections(from entries: [OpenClawChatSessionEntry]) -> [CommandSessionSection] { let pinned = self.sortedByActivity(entries.filter { $0.pinned == true }) let unpinned = entries.filter { $0.pinned != true } let categoryNames = Set(unpinned.compactMap { self.normalizedCategory($0.category) }) .sorted(by: self.categoryComesBefore) var sections: [CommandSessionSection] = [] if !pinned.isEmpty { sections.append(CommandSessionSection( id: .pinned, title: "Pinned", entries: pinned, showsHeader: true)) } for category in categoryNames { let categoryEntries = unpinned.filter { self.normalizedCategory($0.category) == category } sections.append(CommandSessionSection( id: .category(category), title: category, entries: self.sortedByActivity(categoryEntries), showsHeader: true)) } let ungrouped = self.sortedByActivity(unpinned.filter { self.normalizedCategory($0.category) == nil }) if !ungrouped.isEmpty { sections.append(CommandSessionSection( id: .ungrouped, title: "Ungrouped", entries: ungrouped, showsHeader: !categoryNames.isEmpty)) } return sections } static func previewOrder(_ entries: [OpenClawChatSessionEntry]) -> [OpenClawChatSessionEntry] { entries.sorted { lhs, rhs in if (lhs.pinned == true) != (rhs.pinned == true) { return lhs.pinned == true } let left = self.activityTimestamp(lhs) let right = self.activityTimestamp(rhs) return left == right ? lhs.key < rhs.key : left > right } } /// Capped preview that always keeps the open chat visible: when the current /// session falls outside the cap it leads the list (pre-existing Command /// Center contract), otherwise natural pinned/activity order wins. static func previewSelection( _ entries: [OpenClawChatSessionEntry], currentKey: String, limit: Int = 3) -> [OpenClawChatSessionEntry] { let ordered = self.previewOrder(entries) let capped = Array(ordered.prefix(limit)) guard !currentKey.isEmpty, !capped.contains(where: { $0.key == currentKey }), let current = ordered.first(where: { $0.key == currentKey }) else { return capped } return [current] + capped.prefix(max(0, limit - 1)) } static func categories(from entries: [OpenClawChatSessionEntry]) -> [String] { Set(entries.compactMap { self.normalizedCategory($0.category) }) .sorted(by: self.categoryComesBefore) } static func activityTimestamp(_ entry: OpenClawChatSessionEntry) -> Double { entry.lastActivityAt ?? entry.updatedAt ?? 0 } private static func sortedByActivity( _ entries: [OpenClawChatSessionEntry]) -> [OpenClawChatSessionEntry] { entries.sorted { lhs, rhs in let left = self.activityTimestamp(lhs) let right = self.activityTimestamp(rhs) return left == right ? lhs.key < rhs.key : left > right } } private static func normalizedCategory(_ category: String?) -> String? { guard let category else { return nil } let trimmed = category.trimmingCharacters(in: .whitespacesAndNewlines) return trimmed.isEmpty ? nil : trimmed } private static func categoryComesBefore(_ lhs: String, _ rhs: String) -> Bool { let order = lhs.localizedCaseInsensitiveCompare(rhs) return order == .orderedSame ? lhs < rhs : order == .orderedAscending } }