using MarketAlly.AIPlugin; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.Json; using System.Threading; using System.Threading.Tasks; namespace MarketAlly.AIPlugin.Refactoring.Plugins { [AIPlugin("BatchRefactor", "Orchestrates multiple refactoring operations across files and projects")] public class BatchRefactorPlugin : IAIPlugin { [AIParameter("Root directory to process", required: true)] public string RootDirectory { get; set; } [AIParameter("File pattern to match (e.g., *.cs)", required: false)] public string FilePattern { get; set; } = "*.cs"; [AIParameter("Refactoring operations to perform (comma-separated)", required: true)] public string Operations { get; set; } [AIParameter("Configuration file path for refactoring rules", required: false)] public string ConfigPath { get; set; } [AIParameter("Maximum concurrent operations", required: false)] public int MaxConcurrency { get; set; } = 3; [AIParameter("Apply changes to files", required: false)] public bool ApplyChanges { get; set; } = false; [AIParameter("Create detailed operation log", required: false)] public bool DetailedLogging { get; set; } = true; [AIParameter("Stop on first error", required: false)] public bool StopOnError { get; set; } = false; public IReadOnlyDictionary SupportedParameters => new Dictionary { ["rootDirectory"] = typeof(string), ["rootdirectory"] = typeof(string), // Allow lowercase ["filePattern"] = typeof(string), ["filepattern"] = typeof(string), // Allow lowercase ["operations"] = typeof(string), ["configPath"] = typeof(string), ["configpath"] = typeof(string), // Allow lowercase ["maxConcurrency"] = typeof(int), ["maxconcurrency"] = typeof(int), // Allow lowercase ["applyChanges"] = typeof(bool), ["applychanges"] = typeof(bool), // Allow lowercase ["detailedLogging"] = typeof(bool), ["detailedlogging"] = typeof(bool), // Allow lowercase ["stopOnError"] = typeof(bool), ["stoponerror"] = typeof(bool) // Allow lowercase }; public async Task ExecuteAsync(IReadOnlyDictionary parameters) { try { // Extract parameters with case-insensitive handling string rootDirectory = GetParameterValue(parameters, "rootDirectory", "rootdirectory")?.ToString(); string filePattern = GetParameterValue(parameters, "filePattern", "filepattern")?.ToString() ?? "*.cs"; string operations = GetParameterValue(parameters, "operations")?.ToString(); string configPath = GetParameterValue(parameters, "configPath", "configpath")?.ToString(); int maxConcurrency = GetIntParameter(parameters, "maxConcurrency", "maxconcurrency", 3); bool applyChanges = GetBoolParameter(parameters, "applyChanges", "applychanges", false); bool detailedLogging = GetBoolParameter(parameters, "detailedLogging", "detailedlogging", true); bool stopOnError = GetBoolParameter(parameters, "stopOnError", "stoponerror", false); // Validate inputs if (!Directory.Exists(rootDirectory)) { return new AIPluginResult( new DirectoryNotFoundException($"Directory not found: {rootDirectory}"), "Invalid root directory" ); } if (string.IsNullOrEmpty(operations)) { return new AIPluginResult( new ArgumentException("Operations parameter is required"), "Missing operations parameter" ); } // Load configuration if (configPath == null) configPath = rootDirectory; var config = await LoadConfiguration(configPath); // Parse operations var operationList = operations.Split(',', StringSplitOptions.RemoveEmptyEntries) .Select(op => op.Trim().ToLower()) .ToList(); // Discover files to process var filesToProcess = DiscoverFiles(rootDirectory, filePattern, config); if (!filesToProcess.Any()) { return new AIPluginResult(new { Message = "No files found matching the criteria", RootDirectory = rootDirectory, FilePattern = filePattern, FilesProcessed = 0 }); } // Execute batch refactoring var batchResult = await ExecuteBatchRefactoring( filesToProcess, operationList, config, maxConcurrency, applyChanges, detailedLogging, stopOnError ); // Generate summary var summary = GenerateBatchSummary(batchResult, operationList, applyChanges); return new AIPluginResult(new { Message = $"Batch refactoring completed: {batchResult.TotalFiles} files processed", RootDirectory = rootDirectory, FilePattern = filePattern, Operations = operationList, ChangesApplied = applyChanges, Summary = summary, DetailedResults = batchResult, Timestamp = DateTime.UtcNow }); } catch (Exception ex) { return new AIPluginResult(ex, $"Batch refactoring failed: {ex.Message}"); } } private async Task LoadConfiguration(string configPath) { var config = new RefactoringConfiguration(); if (!string.IsNullOrEmpty(configPath) && File.Exists(configPath)) { try { var configJson = await File.ReadAllTextAsync(configPath); config = JsonSerializer.Deserialize(configJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); } catch (Exception ex) { // Use default configuration if loading fails config = new RefactoringConfiguration(); config.Errors.Add($"Failed to load configuration: {ex.Message}"); } } else { config.RootDirectory = configPath; } // Set defaults if not specified if (config.ExcludedFiles == null || !config.ExcludedFiles.Any()) config.ExcludedFiles = new List { "*.Designer.cs", "*.generated.cs", "AssemblyInfo.cs" }; if (config.ExcludedDirectories == null || !config.ExcludedDirectories.Any()) config.ExcludedDirectories = new List { "bin", "obj", ".git", ".vs", "packages", "node_modules" }; return config; } private List DiscoverFiles(string rootDirectory, string filePattern, RefactoringConfiguration config) { var files = Directory.GetFiles(rootDirectory, filePattern, SearchOption.AllDirectories); return files.Where(file => !ShouldExcludeFile(file, config)).ToList(); } private bool ShouldExcludeFile(string filePath, RefactoringConfiguration config) { var fileName = Path.GetFileName(filePath); var relativePath = Path.GetRelativePath(config.RootDirectory ?? "", filePath); // Check excluded file patterns foreach (var pattern in config.ExcludedFiles) { if (MatchesPattern(fileName, pattern)) return true; } // Check excluded directories foreach (var excludedDir in config.ExcludedDirectories) { if (relativePath.Contains(excludedDir, StringComparison.OrdinalIgnoreCase)) return true; } // Check excluded patterns foreach (var pattern in config.ExcludedPatterns) { if (MatchesPattern(relativePath, pattern)) return true; } return false; } private bool MatchesPattern(string input, string pattern) { // Simple pattern matching with * wildcard support if (pattern.Contains('*')) { var regexPattern = pattern.Replace("*", ".*"); return System.Text.RegularExpressions.Regex.IsMatch(input, regexPattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase); } return input.Equals(pattern, StringComparison.OrdinalIgnoreCase); } private async Task ExecuteBatchRefactoring( List files, List operations, RefactoringConfiguration config, int maxConcurrency, bool applyChanges, bool detailedLogging, bool stopOnError) { var result = new BatchRefactoringResult { TotalFiles = files.Count, StartTime = DateTime.UtcNow, FileResults = new List() }; var semaphore = new SemaphoreSlim(maxConcurrency, maxConcurrency); var cancellationTokenSource = new CancellationTokenSource(); var tasks = files.Select(async file => { await semaphore.WaitAsync(cancellationTokenSource.Token); try { var fileResult = await ProcessSingleFile(file, operations, config, applyChanges, detailedLogging, cancellationTokenSource.Token); lock (result) { result.FileResults.Add(fileResult); if (fileResult.Success) { result.SuccessfulFiles++; } else { result.FailedFiles++; if (stopOnError) { cancellationTokenSource.Cancel(); } } } return fileResult; } finally { semaphore.Release(); } }); try { await Task.WhenAll(tasks); } catch (OperationCanceledException) { result.StoppedOnError = true; } result.EndTime = DateTime.UtcNow; result.TotalDuration = result.EndTime - result.StartTime; return result; } private async Task ProcessSingleFile( string filePath, List operations, RefactoringConfiguration config, bool applyChanges, bool detailedLogging, CancellationToken cancellationToken) { var fileResult = new BatchFileRefactoringResult { FilePath = filePath, FileName = Path.GetFileName(filePath), StartTime = DateTime.UtcNow, OperationResults = new List() }; try { foreach (var operation in operations) { cancellationToken.ThrowIfCancellationRequested(); var operationResult = await ExecuteOperation(filePath, operation, config, applyChanges, detailedLogging); fileResult.OperationResults.Add(operationResult); if (!operationResult.Success && config.StopOnFirstError) { break; } } fileResult.Success = fileResult.OperationResults.All(or => or.Success); fileResult.TotalOperations = fileResult.OperationResults.Count; fileResult.SuccessfulOperations = fileResult.OperationResults.Count(or => or.Success); } catch (Exception ex) { fileResult.Success = false; fileResult.Error = ex.Message; } finally { fileResult.EndTime = DateTime.UtcNow; fileResult.Duration = fileResult.EndTime - fileResult.StartTime; } return fileResult; } private async Task ExecuteOperation( string filePath, string operation, RefactoringConfiguration config, bool applyChanges, bool detailedLogging) { var result = new OperationResult { OperationType = operation, StartTime = DateTime.UtcNow }; try { switch (operation.ToLower()) { case "codeanalysis": case "code-analysis": result = await ExecuteCodeAnalysis(filePath, result, applyChanges); break; case "documentation": case "add-documentation": result = await ExecuteDocumentation(filePath, result, applyChanges); break; case "formatting": case "format-code": result = await ExecuteCodeFormatting(filePath, result, applyChanges); break; case "naming": case "naming-conventions": result = await ExecuteNamingConventions(filePath, result, applyChanges); break; case "cleanup": case "code-cleanup": result = await ExecuteCodeCleanup(filePath, result, applyChanges); break; default: result.Success = false; result.Error = $"Unknown operation: {operation}"; break; } } catch (Exception ex) { result.Success = false; result.Error = ex.Message; } finally { result.EndTime = DateTime.UtcNow; result.Duration = result.EndTime - result.StartTime; } return result; } private async Task ExecuteCodeAnalysis(string filePath, OperationResult result, bool applyChanges) { try { var analysisPlugin = new CodeAnalysisPlugin(); var parameters = new Dictionary { ["path"] = filePath, ["analysisDepth"] = "detailed", ["includeComplexity"] = true, ["includeCodeSmells"] = true, ["includeSuggestions"] = true }; var pluginResult = await analysisPlugin.ExecuteAsync(parameters); result.Success = pluginResult.Success; result.Details = pluginResult.Data; result.Message = pluginResult.Message; if (!pluginResult.Success) { result.Error = pluginResult.Message; } return result; } catch (Exception ex) { result.Success = false; result.Error = ex.Message; return result; } } private async Task ExecuteDocumentation(string filePath, OperationResult result, bool applyChanges) { try { var docPlugin = new EnhancedDocumentationGeneratorPlugin(); var parameters = new Dictionary { ["filePath"] = filePath, ["style"] = "intelligent", ["includeExamples"] = false, ["includeSeeAlso"] = false, ["applyChanges"] = applyChanges }; var pluginResult = await docPlugin.ExecuteAsync(parameters); result.Success = pluginResult.Success; result.Details = pluginResult.Data; result.Message = pluginResult.Message; if (!pluginResult.Success) { result.Error = pluginResult.Message; } return result; } catch (Exception ex) { result.Success = false; result.Error = ex.Message; return result; } } private async Task ExecuteCodeFormatting(string filePath, OperationResult result, bool applyChanges) { try { var formatPlugin = new CodeFormatterPlugin(); var parameters = new Dictionary { ["path"] = filePath, ["formattingStyle"] = "microsoft", ["fixIndentation"] = true, ["organizeUsings"] = true, ["removeUnnecessary"] = true, ["applyChanges"] = applyChanges }; var pluginResult = await formatPlugin.ExecuteAsync(parameters); result.Success = pluginResult.Success; result.Details = pluginResult.Data; result.Message = pluginResult.Message; if (!pluginResult.Success) { result.Error = pluginResult.Message; } return result; } catch (Exception ex) { result.Success = false; result.Error = ex.Message; return result; } } private async Task ExecuteNamingConventions(string filePath, OperationResult result, bool applyChanges) { try { var namingPlugin = new NamingConventionPlugin(); var parameters = new Dictionary { ["filePath"] = filePath, ["convention"] = "pascal", ["checkMeaningfulness"] = true, ["aiSuggestions"] = false, // Disable AI suggestions for batch processing ["applyChanges"] = applyChanges }; var pluginResult = await namingPlugin.ExecuteAsync(parameters); result.Success = pluginResult.Success; result.Details = pluginResult.Data; result.Message = pluginResult.Message; if (!pluginResult.Success) { result.Error = pluginResult.Message; } return result; } catch (Exception ex) { result.Success = false; result.Error = ex.Message; return result; } } private async Task ExecuteCodeCleanup(string filePath, OperationResult result, bool applyChanges) { try { // Execute multiple cleanup operations var cleanupOperations = new[] { "formatting", "documentation" }; var cleanupResults = new List(); foreach (var cleanup in cleanupOperations) { var cleanupResult = await ExecuteOperation(filePath, cleanup, new RefactoringConfiguration(), applyChanges, false); cleanupResults.Add(new { Operation = cleanup, Success = cleanupResult.Success, Message = cleanupResult.Message }); } result.Success = cleanupResults.Cast().All(r => r.Success); result.Details = new { CleanupOperations = cleanupResults }; result.Message = $"Executed {cleanupOperations.Length} cleanup operations"; return result; } catch (Exception ex) { result.Success = false; result.Error = ex.Message; return result; } } private object GenerateBatchSummary(BatchRefactoringResult batchResult, List operations, bool changesApplied) { var operationStats = batchResult.FileResults .SelectMany(fr => fr.OperationResults) .GroupBy(or => or.OperationType) .ToDictionary(g => g.Key, g => new { Total = g.Count(), Successful = g.Count(or => or.Success), Failed = g.Count(or => !or.Success), AverageDuration = g.Average(or => or.Duration.TotalMilliseconds) }); var fileTypeStats = batchResult.FileResults .GroupBy(fr => Path.GetExtension(fr.FilePath)) .ToDictionary(g => g.Key, g => new { Count = g.Count(), Successful = g.Count(fr => fr.Success) }); return new { TotalFiles = batchResult.TotalFiles, SuccessfulFiles = batchResult.SuccessfulFiles, FailedFiles = batchResult.FailedFiles, TotalDuration = batchResult.TotalDuration, AverageFileProcessingTime = batchResult.FileResults.Any() ? TimeSpan.FromMilliseconds(batchResult.FileResults.Average(fr => fr.Duration.TotalMilliseconds)) : TimeSpan.Zero, ChangesApplied = changesApplied, StoppedOnError = batchResult.StoppedOnError, OperationStatistics = operationStats, FileTypeStatistics = fileTypeStats, TopErrors = batchResult.FileResults .Where(fr => !fr.Success) .GroupBy(fr => fr.Error) .OrderByDescending(g => g.Count()) .Take(5) .Select(g => new { Error = g.Key, Count = g.Count() }) .ToList() }; } // Helper methods for parameter extraction private object GetParameterValue(IReadOnlyDictionary parameters, params string[] keys) { foreach (var key in keys) { if (parameters.TryGetValue(key, out var value)) return value; } return null; } private bool GetBoolParameter(IReadOnlyDictionary parameters, string key1, string key2, bool defaultValue = false) { var value = GetParameterValue(parameters, key1, key2); return value != null ? Convert.ToBoolean(value) : defaultValue; } private int GetIntParameter(IReadOnlyDictionary parameters, string key1, string key2, int defaultValue = 0) { var value = GetParameterValue(parameters, key1, key2); return value != null ? Convert.ToInt32(value) : defaultValue; } } // Supporting classes for batch refactoring public class RefactoringConfiguration { public string RootDirectory { get; set; } public List ExcludedFiles { get; set; } = new List(); public List ExcludedDirectories { get; set; } = new List(); public List ExcludedPatterns { get; set; } = new List(); public bool StopOnFirstError { get; set; } = false; public Dictionary OperationSettings { get; set; } = new Dictionary(); public List Errors { get; set; } = new List(); } public class BatchRefactoringResult { public int TotalFiles { get; set; } public int SuccessfulFiles { get; set; } public int FailedFiles { get; set; } public DateTime StartTime { get; set; } public DateTime EndTime { get; set; } public TimeSpan TotalDuration { get; set; } public bool StoppedOnError { get; set; } public List FileResults { get; set; } = new List(); } public class BatchFileRefactoringResult { public string FilePath { get; set; } public string FileName { get; set; } public bool Success { get; set; } public string Error { get; set; } public int TotalOperations { get; set; } public int SuccessfulOperations { get; set; } public DateTime StartTime { get; set; } public DateTime EndTime { get; set; } public TimeSpan Duration { get; set; } public List OperationResults { get; set; } = new List(); } public class OperationResult { public string OperationType { get; set; } public bool Success { get; set; } public string Message { get; set; } public string Error { get; set; } public object Details { get; set; } public DateTime StartTime { get; set; } public DateTime EndTime { get; set; } public TimeSpan Duration { get; set; } } }