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<UUID>

    public init(
        workspaceID: UUID,
        lastOpenedAt: Date = Date(),
        selectedDocumentURL: URL? = nil,
        expandedItemIDs: Set<UUID> = []
    ) {
        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")
    }
}
