import Foundation import SaplingCore public struct WorkspaceMetadata: Hashable, Codable, Sendable { public var workspaceID: UUID public var lastOpenedAt: Date public var selectedDocumentURL: URL? public var expandedItemIDs: Set public init( workspaceID: UUID, lastOpenedAt: Date = Date(), selectedDocumentURL: URL? = nil, expandedItemIDs: Set = [] ) { self.workspaceID = workspaceID self.lastOpenedAt = lastOpenedAt self.selectedDocumentURL = selectedDocumentURL self.expandedItemIDs = expandedItemIDs } } public protocol WorkspaceMetadataStore: Sendable { func loadMetadata(for workspaceID: UUID) throws -> WorkspaceMetadata? func saveMetadata(_ metadata: WorkspaceMetadata) throws } public final class InMemoryWorkspaceMetadataStore: WorkspaceMetadataStore, @unchecked Sendable { private var metadataByWorkspaceID: [UUID: WorkspaceMetadata] public init(metadataByWorkspaceID: [UUID: WorkspaceMetadata] = [:]) { self.metadataByWorkspaceID = metadataByWorkspaceID } public func loadMetadata(for workspaceID: UUID) throws -> WorkspaceMetadata? { metadataByWorkspaceID[workspaceID] } public func saveMetadata(_ metadata: WorkspaceMetadata) throws { metadataByWorkspaceID[metadata.workspaceID] = metadata } } public final class JSONWorkspaceMetadataStore: WorkspaceMetadataStore { private let metadataDirectory: URL private let encoder: JSONEncoder private let decoder: JSONDecoder public init(metadataDirectory: URL) { self.metadataDirectory = metadataDirectory self.encoder = JSONEncoder() self.decoder = JSONDecoder() encoder.outputFormatting = [.prettyPrinted, .sortedKeys] } public func loadMetadata(for workspaceID: UUID) throws -> WorkspaceMetadata? { let url = fileURL(for: workspaceID) guard FileManager.default.fileExists(atPath: url.path) else { return nil } let data = try Data(contentsOf: url) return try decoder.decode(WorkspaceMetadata.self, from: data) } public func saveMetadata(_ metadata: WorkspaceMetadata) throws { try FileManager.default.createDirectory(at: metadataDirectory, withIntermediateDirectories: true) let data = try encoder.encode(metadata) try data.write(to: fileURL(for: metadata.workspaceID), options: [.atomic]) } private func fileURL(for workspaceID: UUID) -> URL { metadataDirectory.appendingPathComponent("\(workspaceID.uuidString).json") } }