import AppKit import SwiftUI @MainActor @Observable final class TitleOptimizerViewModel { var draft = TitleDraft() var selectedTab: TitleOptimizerTab = .optimize var isOptimizing = false var errorMessage: String? var successMessage: String? var analysis: TitleAnalysis? var variants: [TitleVariant] = [] var selectedVariantID: UUID? private let optimizationService: any TitleOptimizationServiceProtocol init(optimizationService: any TitleOptimizationServiceProtocol = MockTitleOptimizationService()) { self.optimizationService = optimizationService } var formattedSubreddit: String { let trimmed = draft.subreddit.trimmingCharacters(in: .whitespaces) guard !trimmed.isEmpty else { return "r/subreddit" } return trimmed.hasPrefix("r/") ? trimmed : "r/\(trimmed)" } var canOptimize: Bool { !draft.topic.trimmingCharacters(in: .whitespaces).isEmpty && !draft.subreddit.trimmingCharacters(in: .whitespaces).isEmpty && !draft.originalTitle.trimmingCharacters(in: .whitespaces).isEmpty && !isOptimizing } var activeTitle: String { if let selectedID = selectedVariantID, let variant = variants.first(where: { $0.id == selectedID }) { return variant.title } return draft.originalTitle } var bestVariant: TitleVariant? { variants.max(by: { $0.score < $1.score }) } var previewDraft: PostDraft { PostDraft( postType: draft.postType, subreddit: draft.subreddit, topic: draft.topic, tone: draft.tone, title: activeTitle, isNSFW: draft.isNSFW, isSpoiler: draft.isSpoiler, isOC: draft.isOC, flair: draft.flair ) } func selectPostType(_ type: RedditPostType) { draft.postType = type clearMessages() } func selectVariant(_ variant: TitleVariant) { selectedVariantID = variant.id clearMessages() } func applyVariant(_ variant: TitleVariant) { draft.originalTitle = variant.title selectedVariantID = variant.id successMessage = "Title applied." errorMessage = nil } func optimizeTitles() async { guard canOptimize else { return } isOptimizing = true errorMessage = nil successMessage = nil do { let result = try await optimizationService.optimizeTitles(from: draft) analysis = result.analysis variants = result.variants selectedVariantID = result.variants.first?.id successMessage = "Generated \(result.variants.count) title suggestions." } catch { errorMessage = error.localizedDescription } isOptimizing = false } func copyToClipboard() { let title = activeTitle.trimmingCharacters(in: .whitespaces) guard !title.isEmpty else { return } NSPasteboard.general.clearContents() NSPasteboard.general.setString(title, forType: .string) successMessage = "Copied to clipboard." errorMessage = nil } func resetDraft() { draft = TitleDraft() selectedTab = .optimize analysis = nil variants = [] selectedVariantID = nil clearMessages() } func exportText() -> String { var lines: [String] = [] lines.append("Subreddit: \(formattedSubreddit)") lines.append("Topic: \(draft.topic)") lines.append("Goal: \(draft.titleGoal.title)") lines.append("Tone: \(draft.tone.title)") lines.append("") lines.append("Original: \(draft.originalTitle)") if let analysis { lines.append("") lines.append("Analysis Score: \(analysis.overallScore)/100") lines.append(" Length: \(analysis.lengthScore)") lines.append(" Engagement: \(analysis.engagementScore)") lines.append(" Clarity: \(analysis.clarityScore)") } if !variants.isEmpty { lines.append("") lines.append("Suggestions:") for (index, variant) in variants.enumerated() { lines.append("\(index + 1). [\(variant.score)] \(variant.title)") } } return lines.joined(separator: "\n") } private func clearMessages() { errorMessage = nil successMessage = nil } }