import Foundation public enum ImportProvider: String, CaseIterable, Codable, Identifiable, Sendable { case claude case codex case hermes case chatGPT case gemini public var id: String { rawValue } public var displayName: String { switch self { case .claude: "Claude" case .codex: "Codex" case .hermes: "Hermes" case .chatGPT: "ChatGPT" case .gemini: "Gemini" } } public var formats: [ImportFormat] { ImportFormat.allCases.filter { $0.provider == self } } } public enum ImportFormat: String, CaseIterable, Codable, Identifiable, Sendable { case claudeJSONL case codexHistoryJSONL case hermesStateDatabase case chatGPTAnalysisJSON case chatGPTRawConversationsJSON case geminiMarkdown case geminiJSONL public var id: String { rawValue } public var provider: ImportProvider { switch self { case .claudeJSONL: .claude case .codexHistoryJSONL: .codex case .hermesStateDatabase: .hermes case .chatGPTAnalysisJSON, .chatGPTRawConversationsJSON: .chatGPT case .geminiMarkdown, .geminiJSONL: .gemini } } public var displayName: String { switch self { case .claudeJSONL: "Claude Code JSONL" case .codexHistoryJSONL: "Codex history.jsonl" case .hermesStateDatabase: "Hermes state.db" case .chatGPTAnalysisJSON: "ChatGPT Analysis JSON" case .chatGPTRawConversationsJSON: "ChatGPT Conversations JSON" case .geminiMarkdown: "Gemini Markdown" case .geminiJSONL: "Gemini JSONL" } } public var allowsDirectory: Bool { switch self { case .hermesStateDatabase: true default: true } } } public struct ImportCandidate: Hashable, Sendable { public let title: String public let content: String public let source: String public let tags: String? public let model: String? public let promptedAt: String? public init( title: String, content: String, source: String, tags: String?, model: String?, promptedAt: String? = nil ) { self.title = title self.content = content self.source = source self.tags = tags self.model = model self.promptedAt = promptedAt } } public struct ImportStatistics: Codable, Equatable, Sendable { public var scanned = 0 public var imported = 0 public var skippedShort = 0 public var skippedLong = 0 public var skippedDuplicate = 0 public var skippedAgent = 0 public var skippedSystem = 0 public var skippedMalformed = 0 public init() {} } public struct ImportPreview: Sendable { public let format: ImportFormat public let sourceURL: URL public let candidates: [ImportCandidate] public let statistics: ImportStatistics public init( format: ImportFormat, sourceURL: URL, candidates: [ImportCandidate], statistics: ImportStatistics ) { self.format = format self.sourceURL = sourceURL self.candidates = candidates self.statistics = statistics } } public struct ImportReport: Codable, Sendable { public let provider: String public let format: String public let sourceName: String public let completedAt: Date public let statistics: ImportStatistics public init(preview: ImportPreview, statistics: ImportStatistics, completedAt: Date = Date()) { provider = preview.format.provider.rawValue format = preview.format.rawValue sourceName = preview.sourceURL.lastPathComponent self.completedAt = completedAt self.statistics = statistics } } public struct DedupIndex: Sendable { public var hashes: Set public let existingContents: [String] public init(hashes: Set, existingContents: [String]) { // Legacy imports encode their 12-character content hash in `source`. // Manual and other pre-native rows may not. Hash every exact stored // content as well so preview and commit use the same dedup identity // without rewriting any existing row. self.hashes = hashes.union(existingContents.map { ImportText.contentHash(ImportText.trimmed($0)) }) self.existingContents = existingContents } public func contains(_ hash: String) -> Bool { hashes.contains(hash) } }