package cc.unitmesh.agent.util import cc.unitmesh.agent.diff.DiffUtils import cc.unitmesh.agent.platform.GitOperations /** * DiffProvider interface - provides diff generation capabilities * with Git as primary method and LCS-based fallback */ interface DiffProvider { /** * Calculate diff statistics between old and new content */ suspend fun calculateDiffStats(oldContent: String?, newContent: String?, filePath: String): DiffUtils.DiffStats /** * Generate unified diff format between old and new content */ suspend fun generateUnifiedDiff(oldContent: String?, newContent: String?, filePath: String): String /** * Check if this provider is available on the current platform */ fun isAvailable(): Boolean } /** * Git-based diff provider (preferred when Git is available) * Uses real git diff command for accurate, industry-standard diffs */ class GitDiffProvider(private val projectPath: String) : DiffProvider { private val gitOps = GitOperations(projectPath) override suspend fun calculateDiffStats( oldContent: String?, newContent: String?, filePath: String ): DiffUtils.DiffStats { if (!isAvailable()) { // Fallback to LCS-based calculation return DiffUtils.calculateDiffStats(oldContent, newContent) } // Try to get git diff val diff = gitOps.getFileDiff(filePath) if (diff != null) { return parseDiffStats(diff) } // Fallback to LCS-based calculation return DiffUtils.calculateDiffStats(oldContent, newContent) } override suspend fun generateUnifiedDiff( oldContent: String?, newContent: String?, filePath: String ): String { if (!isAvailable()) { // Fallback to LCS-based diff return DiffUtils.generateUnifiedDiff(oldContent, newContent, filePath) } // Try to get git diff val diff = gitOps.getFileDiff(filePath) if (diff != null && diff.isNotBlank()) { return diff } // Fallback to LCS-based diff return DiffUtils.generateUnifiedDiff(oldContent, newContent, filePath) } override fun isAvailable(): Boolean = gitOps.isSupported() /** * Parse diff statistics from git diff output */ private fun parseDiffStats(diff: String): DiffUtils.DiffStats { var added = 0 var deleted = 0 var context = 0 diff.lines().forEach { line -> when { line.startsWith("+") && !line.startsWith("+++") -> added++ line.startsWith("-") && !line.startsWith("---") -> deleted++ line.startsWith(" ") -> context++ } } return DiffUtils.DiffStats(added, deleted, context) } } /** * Fallback diff provider using LCS algorithm * Used when Git is not available (e.g., Android, or when git is not configured) */ class FallbackDiffProvider : DiffProvider { override suspend fun calculateDiffStats( oldContent: String?, newContent: String?, filePath: String ): DiffUtils.DiffStats { return DiffUtils.calculateDiffStats(oldContent, newContent) } override suspend fun generateUnifiedDiff( oldContent: String?, newContent: String?, filePath: String ): String { return DiffUtils.generateUnifiedDiff(oldContent, newContent, filePath) } override fun isAvailable(): Boolean = true } /** * Factory for creating the appropriate DiffProvider */ object DiffProviderFactory { /** * Create a diff provider with Git as primary and LCS as fallback * * @param projectPath Project root path for Git operations * @return GitDiffProvider if Git is available, otherwise FallbackDiffProvider */ fun create(projectPath: String): DiffProvider { val gitProvider = GitDiffProvider(projectPath) return if (gitProvider.isAvailable()) { gitProvider } else { FallbackDiffProvider() } } /** * Create a fallback-only provider (useful for testing or when Git is explicitly disabled) */ fun createFallback(): DiffProvider = FallbackDiffProvider() }