import Foundation enum Mailbox: String, CaseIterable, Identifiable, Codable { case inbox case starred case sent case drafts case archive var id: String { rawValue } var title: String { switch self { case .inbox: "Inbox" case .starred: "Starred" case .sent: "Sent" case .drafts: "Drafts" case .archive: "Archive" } } var systemImage: String { switch self { case .inbox: "tray.full" case .starred: "star" case .sent: "paperplane" case .drafts: "doc.text" case .archive: "archivebox" } } } struct MailPerson: Identifiable, Hashable, Codable { let id: UUID let name: String let email: String init(id: UUID = UUID(), name: String, email: String) { self.id = id self.name = name self.email = email } } struct MailMessage: Identifiable, Hashable, Codable { let id: UUID let routeID: String let sender: MailPerson let recipients: [MailPerson] let sentAt: Date let body: String let isDraft: Bool init( id: UUID = UUID(), routeID: String = UUID().uuidString.lowercased(), sender: MailPerson, recipients: [MailPerson], sentAt: Date, body: String, isDraft: Bool = false ) { self.id = id self.routeID = routeID self.sender = sender self.recipients = recipients self.sentAt = sentAt self.body = body self.isDraft = isDraft } } struct MailThread: Identifiable, Hashable, Codable { let id: UUID let routeID: String var mailbox: Mailbox var subject: String var participants: [MailPerson] var messages: [MailMessage] var isUnread: Bool var isStarred: Bool var tags: [String] var lane: Lane var summary: [String]? init( id: UUID = UUID(), routeID: String = UUID().uuidString.lowercased(), mailbox: Mailbox, subject: String, participants: [MailPerson], messages: [MailMessage], isUnread: Bool, isStarred: Bool, tags: [String] = [], lane: Lane = .people, summary: [String]? = nil ) { self.id = id self.routeID = routeID self.mailbox = mailbox self.subject = subject self.participants = participants self.messages = messages.sorted { $0.sentAt < $1.sentAt } self.isUnread = isUnread self.isStarred = isStarred self.tags = tags self.lane = lane self.summary = summary } enum CodingKeys: String, CodingKey { case id, routeID, mailbox, subject, participants, messages case isUnread, isStarred, tags, lane, summary } init(from decoder: Decoder) throws { let c = try decoder.container(keyedBy: CodingKeys.self) self.id = try c.decode(UUID.self, forKey: .id) self.routeID = try c.decode(String.self, forKey: .routeID) self.mailbox = try c.decode(Mailbox.self, forKey: .mailbox) self.subject = try c.decode(String.self, forKey: .subject) self.participants = try c.decode([MailPerson].self, forKey: .participants) let rawMessages = try c.decode([MailMessage].self, forKey: .messages) self.messages = rawMessages.sorted { $0.sentAt < $1.sentAt } self.isUnread = try c.decode(Bool.self, forKey: .isUnread) self.isStarred = try c.decode(Bool.self, forKey: .isStarred) self.tags = try c.decodeIfPresent([String].self, forKey: .tags) ?? [] self.lane = try c.decodeIfPresent(Lane.self, forKey: .lane) ?? .people self.summary = try c.decodeIfPresent([String].self, forKey: .summary) } var latestMessage: MailMessage? { messages.max(by: { $0.sentAt < $1.sentAt }) } var previewText: String { latestMessage?.body.replacingOccurrences(of: "\n", with: " ") ?? "" } var lastUpdated: Date { latestMessage?.sentAt ?? .distantPast } } struct ComposeDraft: Equatable { var to = "" var subject = "" var body = "" }