import AppKit import SwiftUI @MainActor @Observable final class CommentWriterViewModel { var draft = CommentDraft() var selectedTab: CommentWriterTab = .compose var isGenerating = false var errorMessage: String? var successMessage: String? var variants: [CommentVariant] = [] var selectedVariantID: UUID? var showOverwriteConfirmation = false private var hasPremiumAccess = false private let injectedGenerationService: (any CommentGenerationServiceProtocol)? private let emptyDraft = CommentDraft() init( hasPremiumAccess: Bool = false, generationService: (any CommentGenerationServiceProtocol)? = nil ) { self.hasPremiumAccess = hasPremiumAccess self.injectedGenerationService = generationService } func setPremiumAccess(_ hasPremiumAccess: Bool) { self.hasPremiumAccess = hasPremiumAccess } private var generationService: any CommentGenerationServiceProtocol { injectedGenerationService ?? CommentGenerationServiceFactory.make(hasPremiumAccess: hasPremiumAccess) } var formattedSubreddit: String { let name = PostDraftValidator.normalizedSubreddit(draft.subreddit) guard !name.isEmpty else { return "r/subreddit" } return "r/\(name)" } var usesLiveAI: Bool { AIConfiguration.usesLiveAI(hasPremiumAccess: hasPremiumAccess) } var hasDraftChanges: Bool { draft != emptyDraft } var canGenerate: Bool { CommentDraftValidator.canGenerate(from: draft, isGenerating: isGenerating) } var canExport: Bool { let body = activeCommentBody.trimmingCharacters(in: .whitespaces) guard !body.isEmpty, body.count <= CommentDraftValidator.maxCommentLength else { return false } return (try? CommentDraftValidator.validateForGeneration(from: draft)) != nil } var activeCommentBody: String { if let selectedID = selectedVariantID, let variant = variants.first(where: { $0.id == selectedID }) { return variant.body } return draft.body } var bestVariant: CommentVariant? { variants.max(by: { $0.score < $1.score }) } var needsOverwriteConfirmation: Bool { !draft.body.trimmingCharacters(in: .whitespaces).isEmpty } func selectCommentType(_ type: CommentType) { draft.commentType = type if type == .topLevel { draft.parentComment = "" } clearMessages() } func selectVariant(_ variant: CommentVariant) { selectedVariantID = variant.id clearMessages() } func applyVariant(_ variant: CommentVariant) { draft.body = variant.body selectedVariantID = variant.id successMessage = "Variant applied." errorMessage = nil } func generateComment() async { guard canGenerate else { return } if needsOverwriteConfirmation { showOverwriteConfirmation = true return } await performGenerate(replaceExisting: true) } func confirmOverwriteAndGenerate() async { showOverwriteConfirmation = false await performGenerate(replaceExisting: true) } func cancelOverwriteConfirmation() { showOverwriteConfirmation = false } func copyToClipboard() { let content = activeCommentBody.trimmingCharacters(in: .whitespaces) guard !content.isEmpty else { errorMessage = CommentDraftValidationError.emptyComment.localizedDescription successMessage = nil return } guard content.count <= CommentDraftValidator.maxCommentLength else { errorMessage = CommentDraftValidationError.commentTooLong.localizedDescription successMessage = nil return } NSPasteboard.general.clearContents() NSPasteboard.general.setString(content, forType: .string) successMessage = "Comment copied to clipboard." errorMessage = nil } func resetDraft() { draft = CommentDraft() selectedTab = .compose variants = [] selectedVariantID = nil clearMessages() } func clearMessages() { errorMessage = nil successMessage = nil } func notifyDraftEdited() { clearMessages() } private func performGenerate(replaceExisting: Bool) async { isGenerating = true errorMessage = nil successMessage = nil do { let result = try await generationService.generateComment(from: draft) if replaceExisting || draft.body.trimmingCharacters(in: .whitespaces).isEmpty { draft.body = String(result.body.prefix(CommentDraftValidator.maxCommentLength)) } variants = result.variants selectedVariantID = result.variants.first?.id selectedTab = .preview let engine = usesLiveAI ? "AI" : "local AI templates" successMessage = "Generated comment with \(result.variants.count) variants using \(engine)." } catch { errorMessage = UserFacingError.message(for: error) } isGenerating = false } }