import Foundation public final class ExistingLyricsRevisionDirector: Sendable { private let aiService: any AIService public init(aiService: any AIService) { self.aiService = aiService } public func reviseExistingLyrics( in project: SongProject, sourceLyrics: Lyrics? = nil, instruction: String, mode: LyricsRevisionMode, conversation: [AIConversationMessage] = [], localeIdentifier: String? = nil, revisedAt: Date = Date() ) async throws -> SongProjectUpdateResult { guard mode != .writeFromIdea else { throw ExistingLyricsRevisionDirectorError.unsupportedMode(mode) } let trimmedInstruction = instruction.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmedInstruction.isEmpty else { throw ExistingLyricsRevisionDirectorError.emptyInstruction } let lyricsToRevise = sourceLyrics ?? project.lyrics guard !lyricsToRevise.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { throw ExistingLyricsRevisionDirectorError.emptySourceLyrics } guard !project.isUpdateScopeLocked(.lyrics) else { return SongProjectUpdateResult(project: project) } let request = LyricsRevisionRequest( context: AIRequestContext( userInstruction: trimmedInstruction, conversation: conversation, localeIdentifier: localeIdentifier ), project: project, sourceLyrics: lyricsToRevise, mode: mode ) let result = try await aiService.reviseLyrics(from: request) var proposedProject = project proposedProject.lyrics = result.lyrics return SongProjectAIUpdateMerger().apply( snapshot: SongProjectSnapshot(project: proposedProject), changedScopes: [.lyrics], notes: result.notes, to: project, updatedAt: revisedAt ) } } public enum ExistingLyricsRevisionDirectorError: Error, Equatable, Sendable { case emptyInstruction case emptySourceLyrics case unsupportedMode(LyricsRevisionMode) }