import AppKit import SwiftUI import UniformTypeIdentifiers @MainActor @Observable final class PostGeneratorViewModel { var draft = PostDraft() var selectedTab: PostGeneratorTab = .compose var isGenerating = false var errorMessage: String? var successMessage: String? var showImageImporter = false private let generationService: any PostGenerationServiceProtocol init(generationService: any PostGenerationServiceProtocol = MockPostGenerationService()) { self.generationService = generationService } 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 canGenerate: Bool { !draft.topic.trimmingCharacters(in: .whitespaces).isEmpty && !draft.subreddit.trimmingCharacters(in: .whitespaces).isEmpty && !isGenerating } var canAddPollOption: Bool { draft.pollOptions.count < 6 } var canRemovePollOption: Bool { draft.pollOptions.count > 2 } func selectPostType(_ type: RedditPostType) { draft.postType = type clearMessages() } func addPollOption() { guard canAddPollOption else { return } draft.pollOptions.append(PollOption()) } func removePollOption(_ option: PollOption) { guard canRemovePollOption else { return } draft.pollOptions.removeAll { $0.id == option.id } } func setImage(from url: URL) { draft.imageFileURL = url clearMessages() } func removeImage() { draft.imageFileURL = nil } func generatePost() async { guard canGenerate else { return } isGenerating = true errorMessage = nil successMessage = nil do { let result = try await generationService.generatePost(from: draft) draft.title = result.title if draft.postType == .text || draft.postType == .poll { draft.body = result.body } if let flair = result.suggestedFlair, draft.flair.isEmpty { draft.flair = flair } successMessage = "Post generated successfully." } catch { errorMessage = error.localizedDescription } isGenerating = false } func copyToClipboard() { let content = exportText() NSPasteboard.general.clearContents() NSPasteboard.general.setString(content, forType: .string) successMessage = "Copied to clipboard." errorMessage = nil } func resetDraft() { draft = PostDraft() selectedTab = .compose clearMessages() } func exportText() -> String { var lines: [String] = [] lines.append("Subreddit: \(formattedSubreddit)") lines.append("Type: \(draft.postType.title)") if !draft.title.isEmpty { lines.append("Title: \(draft.title)") } switch draft.postType { case .text: if !draft.body.isEmpty { lines.append("\n\(draft.body)") } case .image: if let url = draft.imageFileURL { lines.append("Image: \(url.lastPathComponent)") } if !draft.body.isEmpty { lines.append("Caption: \(draft.body)") } case .link: if !draft.linkURL.isEmpty { lines.append("URL: \(draft.linkURL)") } if !draft.body.isEmpty { lines.append("\n\(draft.body)") } case .video: if !draft.videoURL.isEmpty { lines.append("Video URL: \(draft.videoURL)") } if !draft.body.isEmpty { lines.append("\n\(draft.body)") } case .poll: lines.append("Duration: \(draft.pollDuration.label)") for (index, option) in draft.pollOptions.enumerated() where !option.text.isEmpty { lines.append("Option \(index + 1): \(option.text)") } if !draft.body.isEmpty { lines.append("\n\(draft.body)") } } var tags: [String] = [] if draft.isNSFW { tags.append("NSFW") } if draft.isSpoiler { tags.append("Spoiler") } if draft.isOC { tags.append("OC") } if !tags.isEmpty { lines.append("Tags: \(tags.joined(separator: ", "))") } if !draft.flair.isEmpty { lines.append("Flair: \(draft.flair)") } return lines.joined(separator: "\n") } private func clearMessages() { errorMessage = nil successMessage = nil } }