import AppKit import Combine import Foundation @MainActor final class PunctuationCheckerViewModel: ObservableObject { static let maxWords = 1_000 @Published var text: String = "" @Published private(set) var correctedText: String = "" @Published private(set) var issues: [GrammarIssue] = [] @Published private(set) var hasResults = false @Published private(set) var isChecking = false @Published private(set) var errorMessage: String? private let punctuationCheckService: any PunctuationCheckServicing private let textExtractionService: any TextExtracting init( punctuationCheckService: any PunctuationCheckServicing = PunctuationCheckService(), textExtractionService: any TextExtracting = TextExtractionService() ) { self.punctuationCheckService = punctuationCheckService self.textExtractionService = textExtractionService } var wordCountLimitLabel: String { "\(text.wordCount)/\(Self.maxWords)" } var canCheck: Bool { !text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && !isChecking } func pasteText() { guard let clipboard = NSPasteboard.general.string(forType: .string) else { return } text = WordLimit.applyImported(clipboard, maxWords: Self.maxWords, setError: { errorMessage = $0 }) resetResults() } func uploadFile() { Task { do { guard let extracted = try await textExtractionService.pickAndExtractText() else { return } text = WordLimit.applyImported(extracted, maxWords: Self.maxWords, setError: { errorMessage = $0 }) resetResults() } catch { errorMessage = error.localizedDescription } } } func notifyWordLimitReached() { errorMessage = WordLimit.message(maxWords: Self.maxWords) } func handleTextChange() { let (limited, exceeded) = WordLimit.apply(text, maxWords: Self.maxWords) if exceeded { text = limited errorMessage = WordLimit.message(maxWords: Self.maxWords) } else if errorMessage == WordLimit.message(maxWords: Self.maxWords) { errorMessage = nil } if hasResults { resetResults() } } func clearText() { text = "" resetResults() } func resetResults() { correctedText = "" issues = [] hasResults = false errorMessage = nil } func checkPunctuation() { let content = text.trimmingCharacters(in: .whitespacesAndNewlines) guard !content.isEmpty else { resetResults() return } isChecking = true errorMessage = nil Task { do { let result = try await punctuationCheckService.checkPunctuation(in: content) correctedText = result.correctedText issues = result.issues hasResults = true } catch { errorMessage = error.localizedDescription } isChecking = false } } func copyCorrectedText() { guard !correctedText.isEmpty else { return } NSPasteboard.general.clearContents() NSPasteboard.general.setString(correctedText, forType: .string) } }