// // TodoStore.swift // iris // // Created by Codex. // import Foundation final class TodoStore { private struct Persisted: Codable { var schema: Int? var items: [TodoItem] } private let queue = DispatchQueue(label: "iris.todostore.queue") private let fileURL: URL private var items: [TodoItem] init(filename: String = "todos_v1.json") { self.fileURL = Self.defaultFileURL(filename: filename) let persisted = Self.load(from: fileURL) self.items = persisted?.items ?? [] } func snapshot() -> [TodoItem] { queue.sync { items } } @discardableResult func add(title: String, now: Date = Date()) -> TodoItem { queue.sync { let item = TodoItem(title: title, createdAt: now, updatedAt: now) items.append(item) save() return item } } @discardableResult func updateTitle(id: UUID, title: String, now: Date = Date()) -> TodoItem? { queue.sync { guard let index = items.firstIndex(where: { $0.id == id }) else { return nil } var item = items[index] item.title = title item.updatedAt = now items[index] = item save() return item } } @discardableResult func setCompleted(id: UUID, completed: Bool, now: Date = Date()) -> TodoItem? { queue.sync { guard let index = items.firstIndex(where: { $0.id == id }) else { return nil } var item = items[index] item.isCompleted = completed item.updatedAt = now item.completedAt = completed ? now : nil items[index] = item save() return item } } func delete(id: UUID) { queue.sync { items.removeAll { $0.id == id } save() } } private func save() { let persisted = Persisted(schema: 1, items: items) Self.save(persisted, to: fileURL) } private static func defaultFileURL(filename: String) -> URL { let fm = FileManager.default let base = fm.urls(for: .applicationSupportDirectory, in: .userDomainMask).first ?? fm.temporaryDirectory let bundle = Bundle.main.bundleIdentifier ?? "iris" return base .appendingPathComponent(bundle, isDirectory: true) .appendingPathComponent(filename, isDirectory: false) } private static func load(from url: URL) -> Persisted? { guard let data = try? Data(contentsOf: url) else { return nil } return try? makeDecoder().decode(Persisted.self, from: data) } private static func save(_ persisted: Persisted, to url: URL) { do { let fm = FileManager.default try fm.createDirectory(at: url.deletingLastPathComponent(), withIntermediateDirectories: true) let data = try makeEncoder().encode(persisted) try data.write(to: url, options: [.atomic]) } catch { // Best-effort persistence. } } private static func makeEncoder() -> JSONEncoder { let encoder = JSONEncoder() encoder.outputFormatting = [.prettyPrinted, .withoutEscapingSlashes] encoder.dateEncodingStrategy = .iso8601 return encoder } private static func makeDecoder() -> JSONDecoder { let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601 return decoder } }