import Foundation public enum SessionState: String, Codable, Sendable { case open // accepting captures case archived // its PDF has been built and sent; kept forever, never deleted } /// The manifest persisted as session.json alongside the PNGs. public struct CaptureSession: Codable, Sendable, Identifiable, Equatable { public let id: UUID public let createdAt: Date public private(set) var state: SessionState public private(set) var captures: [Capture] /// Set when the PDF is built, so a re-send reuses the same file. public private(set) var pdfFileName: String? public init( id: UUID, createdAt: Date, state: SessionState, captures: [Capture], pdfFileName: String? ) { self.id = id self.createdAt = createdAt self.state = state self.captures = captures self.pdfFileName = pdfFileName } public var isEmpty: Bool { captures.isEmpty } /// max(sequence)+1, or 1 when empty. public var nextSequence: Int { (captures.map(\.sequence).max() ?? 0) + 1 } /// Value-semantic: returns a new session; does not mutate `self`. public func appending(_ capture: Capture) -> CaptureSession { CaptureSession( id: id, createdAt: createdAt, state: state, captures: captures + [capture], pdfFileName: pdfFileName ) } /// Value-semantic: returns a new session; does not mutate `self`. /// Sequence numbers of remaining captures are left unchanged. public func removing(captureID: UUID) -> CaptureSession { CaptureSession( id: id, createdAt: createdAt, state: state, captures: captures.filter { $0.id != captureID }, pdfFileName: pdfFileName ) } /// Value-semantic: returns a new session; does not mutate `self`. public func markArchived(pdfFileName: String) -> CaptureSession { CaptureSession( id: id, createdAt: createdAt, state: .archived, captures: captures, pdfFileName: pdfFileName ) } }