import Foundation @MainActor public final class LaunchHistory { private struct Usage { var count: Int var lastLaunch: Date } private static let storageKey = "applicationLaunchHistory" private static let favoritesStorageKey = "applicationFavorites" private let defaults: UserDefaults private let now: () -> Date private var usage: [URL: Usage] = [:] private var favorites = Set() public init(defaults: UserDefaults = .standard, now: @escaping () -> Date = Date.init) { self.defaults = defaults self.now = now for (identifier, value) in defaults.dictionary(forKey: Self.storageKey) ?? [:] { guard let url = URL(string: identifier), url.isFileURL || Application.commands.contains(where: { $0.id == url }), let record = value as? [String: Any], let count = record["count"] as? Int, count > 0, let lastLaunch = record["lastLaunch"] as? Date, lastLaunch.timeIntervalSinceReferenceDate.isFinite else { continue } usage[url] = Usage(count: count, lastLaunch: lastLaunch) } for identifier in defaults.stringArray(forKey: Self.favoritesStorageKey) ?? [] { guard let url = URL(string: identifier), url.isFileURL || Application.commands.contains(where: { $0.id == url }) else { continue } favorites.insert(url) } } public func isFavorite(_ application: Application) -> Bool { favorites.contains(application.id) } public func toggleFavorite(_ application: Application) { if favorites.remove(application.id) == nil { favorites.insert(application.id) } defaults.set(favorites.map(\.absoluteString), forKey: Self.favoritesStorageKey) } public func recordLaunch(of application: Application) { let timestamp = now() var record = usage[application.id] ?? Usage(count: 0, lastLaunch: timestamp) if record.count < Int.max { record.count += 1 } record.lastLaunch = timestamp usage[application.id] = record save() } public func resetRanking(of application: Application) { usage.removeValue(forKey: application.id) save() } private func save() { var saved: [String: [String: Any]] = [:] saved.reserveCapacity(usage.count) for (url, record) in usage { saved[url.absoluteString] = ["count": record.count, "lastLaunch": record.lastLaunch] } defaults.set(saved, forKey: Self.storageKey) } public func suggestions(in applications: [Application]) -> [Application] { applications.sorted(by: preferred) } public func results(for query: String, in applications: [Application]) -> [Application] { ApplicationSearch.results(for: query, in: applications, breakingTiesWith: preferred) } private func preferred(_ lhs: Application, _ rhs: Application) -> Bool { let leftFavorite = isFavorite(lhs) let rightFavorite = isFavorite(rhs) if leftFavorite != rightFavorite { return leftFavorite } let left = usage[lhs.id] let right = usage[rhs.id] let leftCount = left?.count ?? 0 let rightCount = right?.count ?? 0 if leftCount != rightCount { return leftCount > rightCount } if let left, let right, left.lastLaunch != right.lastLaunch { return left.lastLaunch > right.lastLaunch } return alphabetical(lhs, rhs) } }