using MarketAlly.AIPlugin; using LibGit2Sharp; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; namespace MarketAlly.AIPlugin.Refactoring.Plugins { [AIPlugin("SolutionRefactoring", "Processes entire solutions with Git integration, respecting .gitignore and creating safe refactoring branches")] public class SolutionRefactoringPlugin : IAIPlugin { [AIParameter("Path to solution directory (containing .sln file)", required: true)] public string SolutionPath { get; set; } [AIParameter("Refactoring operations to perform", required: true)] public string Operations { get; set; } [AIParameter("Create new Git branch for refactoring", required: false)] public bool CreateBranch { get; set; } = true; [AIParameter("Branch name for refactoring (auto-generated if empty)", required: false)] public string BranchName { get; set; } [AIParameter("Apply changes or preview only", required: false)] public bool ApplyChanges { get; set; } = false; [AIParameter("Respect .gitignore file", required: false)] public bool RespectGitIgnore { get; set; } = true; [AIParameter("Maximum files to process per project", required: false)] public int MaxFilesPerProject { get; set; } = 100; [AIParameter("Skip projects matching patterns (comma-separated)", required: false)] public string SkipProjects { get; set; } = "*.Test,*.Tests,*.UnitTest"; public IReadOnlyDictionary SupportedParameters => new Dictionary { ["solutionPath"] = typeof(string), ["solutionpath"] = typeof(string), ["operations"] = typeof(string), ["createBranch"] = typeof(bool), ["createbranch"] = typeof(bool), ["branchName"] = typeof(string), ["branchname"] = typeof(string), ["applyChanges"] = typeof(bool), ["applychanges"] = typeof(bool), ["respectGitIgnore"] = typeof(bool), ["respectgitignore"] = typeof(bool), ["maxFilesPerProject"] = typeof(int), ["maxfilesperproject"] = typeof(int), ["skipProjects"] = typeof(string), ["skipprojects"] = typeof(string) }; public async Task ExecuteAsync(IReadOnlyDictionary parameters) { try { // Extract parameters string solutionPath = GetParameterValue(parameters, "solutionPath", "solutionpath")?.ToString(); string operations = GetParameterValue(parameters, "operations")?.ToString(); bool createBranch = GetBoolParameter(parameters, "createBranch", "createbranch", true); string branchName = GetParameterValue(parameters, "branchName", "branchname")?.ToString(); bool applyChanges = GetBoolParameter(parameters, "applyChanges", "applychanges", false); bool respectGitIgnore = GetBoolParameter(parameters, "respectGitIgnore", "respectgitignore", true); int maxFilesPerProject = GetIntParameter(parameters, "maxFilesPerProject", "maxfilesperproject", 100); string skipProjects = GetParameterValue(parameters, "skipProjects", "skipprojects")?.ToString() ?? "*.Test,*.Tests,*.UnitTest"; // Validate solution path if (!Directory.Exists(solutionPath)) { return new AIPluginResult(new DirectoryNotFoundException($"Solution directory not found: {solutionPath}"), "Invalid solution path"); } var solutionResult = new SolutionRefactoringResult { SolutionPath = solutionPath, StartTime = DateTime.UtcNow }; // Discover solution structure await DiscoverSolutionStructure(solutionResult, solutionPath, skipProjects); // Setup Git integration var gitManager = new GitRefactoringManager(solutionPath); if (createBranch && gitManager.IsGitRepository) { branchName = branchName ?? GenerateBranchName(operations); solutionResult.GitInfo = await gitManager.CreateRefactoringBranch(branchName, applyChanges); } // Process .gitignore var gitIgnoreRules = new List(); if (respectGitIgnore) { gitIgnoreRules = LoadGitIgnoreRules(solutionPath); solutionResult.GitIgnoreRules = gitIgnoreRules; } // Process each project foreach (var project in solutionResult.Projects) { try { await ProcessProject(project, operations, gitIgnoreRules, maxFilesPerProject, applyChanges); solutionResult.SuccessfulProjects++; } catch (Exception ex) { project.Error = ex.Message; solutionResult.FailedProjects++; } } solutionResult.EndTime = DateTime.UtcNow; solutionResult.TotalDuration = solutionResult.EndTime - solutionResult.StartTime; // Generate comprehensive summary var summary = GenerateSolutionSummary(solutionResult); return new AIPluginResult(new { Message = $"Solution refactoring completed: {solutionResult.Projects.Count} projects processed", SolutionPath = solutionPath, GitBranch = solutionResult.GitInfo?.NewBranchName, ChangesApplied = applyChanges, Summary = summary, DetailedResult = solutionResult, GitCommands = GenerateGitCommands(solutionResult), Timestamp = DateTime.UtcNow }); } catch (Exception ex) { return new AIPluginResult(ex, $"Solution refactoring failed: {ex.Message}"); } } private async Task DiscoverSolutionStructure(SolutionRefactoringResult result, string solutionPath, string skipPatterns) { // Find .sln files var solutionFiles = Directory.GetFiles(solutionPath, "*.sln", SearchOption.TopDirectoryOnly); if (solutionFiles.Any()) { result.SolutionFile = solutionFiles.First(); result.SolutionName = Path.GetFileNameWithoutExtension(result.SolutionFile); // NEW: Parse .sln file to understand logical project structure await ParseSolutionFileForProjectTypes(result, result.SolutionFile); } // Find all .csproj files var projectFiles = Directory.GetFiles(solutionPath, "*.csproj", SearchOption.AllDirectories); var skipPatternList = skipPatterns.Split(',', StringSplitOptions.RemoveEmptyEntries) .Select(p => p.Trim()).ToList(); foreach (var projectFile in projectFiles) { var projectName = Path.GetFileNameWithoutExtension(projectFile); // Skip test projects and other excluded patterns if (ShouldSkipProject(projectName, skipPatternList)) { result.SkippedProjects.Add(new SkippedProject { Name = projectName, Path = projectFile, Reason = "Matches skip pattern" }); continue; } var project = new ProjectRefactoringInfo { Name = projectName, ProjectFilePath = projectFile, ProjectDirectory = Path.GetDirectoryName(projectFile), CSharpFiles = new List() }; // NEW: Analyze project file for MAUI and other special properties await AnalyzeProjectFileProperties(project, projectFile); // Discover C# files in project with MAUI-aware filtering var csFiles = Directory.GetFiles(project.ProjectDirectory, "*.cs", SearchOption.AllDirectories); // NEW: Apply MAUI-aware file filtering var filteredFiles = csFiles.Where(file => !ShouldExcludeFileForProject(file, project, solutionPath)).ToList(); project.CSharpFiles.AddRange(filteredFiles); project.TotalFiles = csFiles.Length; project.ProcessableFiles = filteredFiles.Count; // NEW: Track how many we'll actually process // NEW: Add MAUI-specific analysis if this is a MAUI project if (project.IsMauiProject) { await AnalyzeMauiProjectStructure(project); } result.Projects.Add(project); } await Task.CompletedTask; } // NEW: Add these supporting methods to your class: private async Task ParseSolutionFileForProjectTypes(SolutionRefactoringResult result, string solutionFile) { try { var solutionContent = await File.ReadAllTextAsync(solutionFile); var lines = solutionContent.Split('\n'); foreach (var line in lines) { // Parse project entries from .sln file var projectMatch = System.Text.RegularExpressions.Regex.Match(line, @"Project\(""([^""]+)""\)\s*=\s*""([^""]+)"",\s*""([^""]+)"",\s*""([^""]+)"""); if (projectMatch.Success) { var projectTypeGuid = projectMatch.Groups[1].Value; var projectName = projectMatch.Groups[2].Value; var projectPath = projectMatch.Groups[3].Value; // Store solution-level project info for later correlation result.SolutionProjectEntries.Add(new SolutionProjectEntry { Name = projectName, RelativePath = projectPath, ProjectTypeGuid = projectTypeGuid, IsVirtualProject = DetermineIfVirtualProject(projectTypeGuid) }); } } } catch (Exception ex) { Console.WriteLine($"[WARNING] Could not parse solution file: {ex.Message}"); } } private async Task AnalyzeProjectFileProperties(ProjectRefactoringInfo project, string projectFile) { try { var projectContent = await File.ReadAllTextAsync(projectFile); // Check for MAUI project project.IsMauiProject = projectContent.Contains("true", StringComparison.OrdinalIgnoreCase); // Extract target frameworks var targetFrameworksMatch = System.Text.RegularExpressions.Regex.Match(projectContent, @"(.*?)", System.Text.RegularExpressions.RegexOptions.IgnoreCase); if (targetFrameworksMatch.Success) { project.TargetFrameworks = targetFrameworksMatch.Groups[1].Value .Split(';', StringSplitOptions.RemoveEmptyEntries) .Select(tf => tf.Trim()) .ToList(); } // Check for other project types if (projectContent.Contains(" fileName.Contains(pattern, StringComparison.OrdinalIgnoreCase))) return true; // MAUI-specific exclusions if (project.IsMauiProject) { // Always skip these MAUI infrastructure files var mauiInfrastructureFiles = new[] { "MauiProgram.cs", "App.xaml.cs", "AppShell.xaml.cs" }; if (mauiInfrastructureFiles.Any(file => fileName.Equals(file, StringComparison.OrdinalIgnoreCase))) { return true; } // Skip small platform-specific files (but not large ones that might need refactoring) if (IsInMauiPlatformFolder(filePath)) { try { var content = File.ReadAllText(filePath); var lineCount = content.Split('\n').Length; // Skip small platform files (< 50 lines), but process large ones if (lineCount < 50) { return true; } else { Console.WriteLine($"[MAUI] Large platform file detected: {fileName} ({lineCount} lines) - will analyze carefully"); } } catch { // If we can't read the file, don't exclude it } } } return false; } private bool IsInMauiPlatformFolder(string filePath) { var pathParts = filePath.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); return pathParts.Any(part => part.Equals("Platforms", StringComparison.OrdinalIgnoreCase) || part.Equals("Platform", StringComparison.OrdinalIgnoreCase) || part.Equals("Android", StringComparison.OrdinalIgnoreCase) || part.Equals("iOS", StringComparison.OrdinalIgnoreCase) || part.Equals("MacCatalyst", StringComparison.OrdinalIgnoreCase) || part.Equals("Windows", StringComparison.OrdinalIgnoreCase) || part.Equals("Tizen", StringComparison.OrdinalIgnoreCase)); } private async Task AnalyzeMauiProjectStructure(ProjectRefactoringInfo project) { project.MauiAnalysis = new MauiProjectAnalysis(); // Check for platform folders var projectDir = project.ProjectDirectory; var platformsDir = Path.Combine(projectDir, "Platforms"); if (Directory.Exists(platformsDir)) { project.MauiAnalysis.HasPlatformsFolder = true; var platformDirs = Directory.GetDirectories(platformsDir); foreach (var platformDir in platformDirs) { var platformName = Path.GetFileName(platformDir); var platformFiles = Directory.GetFiles(platformDir, "*.cs", SearchOption.AllDirectories); project.MauiAnalysis.PlatformSpecificFiles[platformName] = platformFiles.ToList(); } } // Generate MAUI-specific recommendations var recommendations = new List(); if (project.TargetFrameworks.Count > 4) { recommendations.Add("Consider reducing target frameworks - too many platforms increase complexity"); } var sharedBusinessLogicFiles = project.CSharpFiles .Where(f => !IsInMauiPlatformFolder(f)) .Where(f => f.Contains("ViewModel") || f.Contains("Service") || f.Contains("Model")) .ToList(); if (sharedBusinessLogicFiles.Count > 20) { recommendations.Add("Large number of shared business logic files - consider organizing into feature folders"); } project.MauiAnalysis.RefactoringRecommendations = recommendations; await Task.CompletedTask; } private bool DetermineIfVirtualProject(string projectTypeGuid) { // Solution folders and other virtual project types return projectTypeGuid.Equals("{2150E333-8FDC-42A3-9474-1A3956D46DE8}", StringComparison.OrdinalIgnoreCase); } private bool ShouldSkipProject(string projectName, List skipPatterns) { return skipPatterns.Any(pattern => { var regexPattern = pattern.Replace("*", ".*"); return Regex.IsMatch(projectName, regexPattern, RegexOptions.IgnoreCase); }); } private List LoadGitIgnoreRules(string solutionPath) { var gitIgnoreFile = Path.Combine(solutionPath, ".gitignore"); var rules = new List(); if (File.Exists(gitIgnoreFile)) { var lines = File.ReadAllLines(gitIgnoreFile); foreach (var line in lines) { var trimmed = line.Trim(); if (!string.IsNullOrEmpty(trimmed) && !trimmed.StartsWith("#")) { rules.Add(trimmed); } } } // Add common .NET ignore patterns if not present var commonPatterns = new[] { "bin/", "obj/", "*.user", "*.suo", ".vs/", "packages/" }; foreach (var pattern in commonPatterns) { if (!rules.Contains(pattern)) { rules.Add(pattern); } } return rules; } private bool IsFileIgnored(string filePath, string basePath, List gitIgnoreRules) { var relativePath = Path.GetRelativePath(basePath, filePath).Replace('\\', '/'); foreach (var rule in gitIgnoreRules) { if (string.IsNullOrEmpty(rule)) continue; var pattern = rule; if (pattern.EndsWith("/")) { // Directory pattern if (relativePath.StartsWith(pattern.TrimEnd('/'), StringComparison.OrdinalIgnoreCase)) return true; } else if (pattern.Contains("*")) { // Wildcard pattern var regexPattern = "^" + Regex.Escape(pattern).Replace("\\*", ".*") + "$"; if (Regex.IsMatch(relativePath, regexPattern, RegexOptions.IgnoreCase)) return true; } else { // Exact match if (relativePath.Equals(pattern, StringComparison.OrdinalIgnoreCase) || relativePath.EndsWith("/" + pattern, StringComparison.OrdinalIgnoreCase)) return true; } } return false; } private async Task ProcessProject(ProjectRefactoringInfo project, string operations, List gitIgnoreRules, int maxFiles, bool applyChanges) { project.StartTime = DateTime.UtcNow; // Filter files based on .gitignore and other exclusions var filteredFiles = project.CSharpFiles .Where(file => !IsFileIgnored(file, project.ProjectDirectory, gitIgnoreRules)) .Where(file => !ShouldExcludeFile(file)) .Take(maxFiles) .ToList(); project.FilesToProcess = filteredFiles.Count; project.FilesSkipped = project.TotalFiles - project.FilesToProcess; // Process each file with the refactoring plugins var codeRefactoringPlugin = new CodeRefactoringPlugin(); var codeAnalysisPlugin = new CodeAnalysisPlugin(); var documentationPlugin = new EnhancedDocumentationGeneratorPlugin(); foreach (var file in filteredFiles) { try { var fileResult = new FileRefactoringResult { FilePath = file, FileName = Path.GetFileName(file), StartTime = DateTime.UtcNow }; // Run analysis first var analysisParams = new Dictionary { ["path"] = file, ["analysisDepth"] = "detailed", ["includeComplexity"] = true, ["includeCodeSmells"] = true, ["includeSuggestions"] = true }; var analysisResult = await codeAnalysisPlugin.ExecuteAsync(analysisParams); fileResult.AnalysisResult = analysisResult; // Run refactoring if analysis succeeded if (analysisResult.Success) { var refactorParams = new Dictionary { ["filePath"] = file, ["operations"] = operations, ["applyChanges"] = applyChanges, ["maxMethodLength"] = 25, ["maxClassSize"] = 400, ["minComplexityForExtraction"] = 6 }; var refactorResult = await codeRefactoringPlugin.ExecuteAsync(refactorParams); fileResult.RefactoringResult = refactorResult; if (refactorResult.Success && applyChanges) { project.FilesModified++; } } // Add documentation if requested if (operations.Contains("documentation")) { var docParams = new Dictionary { ["filePath"] = file, ["style"] = "intelligent", ["applyChanges"] = applyChanges }; var docResult = await documentationPlugin.ExecuteAsync(docParams); fileResult.DocumentationResult = docResult; } fileResult.EndTime = DateTime.UtcNow; fileResult.Success = analysisResult.Success; project.FileResults.Add(fileResult); project.FilesProcessed++; } catch (Exception ex) { project.FileResults.Add(new FileRefactoringResult { FilePath = file, FileName = Path.GetFileName(file), Success = false, Error = ex.Message }); } } project.EndTime = DateTime.UtcNow; project.Duration = project.EndTime - project.StartTime; project.Success = project.FileResults.Any() && project.FileResults.All(f => f.Success); } private bool ShouldExcludeFile(string filePath) { var fileName = Path.GetFileName(filePath); var excludePatterns = new[] { ".Designer.cs", ".generated.cs", ".g.cs", "AssemblyInfo.cs", "GlobalAssemblyInfo.cs", "TemporaryGeneratedFile_", ".AssemblyAttributes.cs", "Reference.cs", "References.cs" }; return excludePatterns.Any(pattern => fileName.Contains(pattern, StringComparison.OrdinalIgnoreCase)); } private string GenerateBranchName(string operations) { var timestamp = DateTime.Now.ToString("yyyyMMdd-HHmm"); var operationSummary = operations.Split(',').FirstOrDefault()?.Trim() ?? "refactor"; return $"refactor/{operationSummary}-{timestamp}"; } private object GenerateSolutionSummary(SolutionRefactoringResult result) { var totalFiles = result.Projects.Sum(p => p.FilesToProcess); var totalModified = result.Projects.Sum(p => p.FilesModified); var totalSkipped = result.Projects.Sum(p => p.FilesSkipped); return new { SolutionName = result.SolutionName, TotalProjects = result.Projects.Count, SuccessfulProjects = result.SuccessfulProjects, FailedProjects = result.FailedProjects, SkippedProjects = result.SkippedProjects.Count, TotalFiles = totalFiles, FilesModified = totalModified, FilesSkipped = totalSkipped, ProcessingTime = result.TotalDuration, GitBranch = result.GitInfo?.NewBranchName, TopIssuesFound = GetTopIssues(result), ProjectSummaries = result.Projects.Select(p => new { p.Name, p.FilesToProcess, p.FilesModified, p.Success, Duration = p.Duration.TotalSeconds }).ToList() }; } private object GetTopIssues(SolutionRefactoringResult result) { // Aggregate issues across all files var allIssues = new List(); foreach (var project in result.Projects) { foreach (var file in project.FileResults) { // Extract issues from analysis results // This would need to parse the actual result data if (file.AnalysisResult?.Success == true) { // Add logic to extract code smells and suggestions } } } return new { TotalIssues = allIssues.Count, TopIssueTypes = allIssues.GroupBy(i => i).OrderByDescending(g => g.Count()).Take(5) .Select(g => new { Type = g.Key, Count = g.Count() }).ToList() }; } private object GenerateGitCommands(SolutionRefactoringResult result) { var commands = new List(); if (result.GitInfo != null) { commands.Add($"# Current branch: {result.GitInfo.NewBranchName}"); commands.Add("# To review changes:"); commands.Add("git diff HEAD~1"); commands.Add("git status"); commands.Add(""); commands.Add("# To commit changes:"); commands.Add($"git add ."); commands.Add($"git commit -m \"Automated refactoring: {string.Join(", ", result.GitInfo.OperationsPerformed)}\""); commands.Add(""); commands.Add("# To merge back to main:"); commands.Add($"git checkout {result.GitInfo.OriginalBranch}"); commands.Add($"git merge {result.GitInfo.NewBranchName}"); commands.Add(""); commands.Add("# To discard changes (if needed):"); commands.Add($"git checkout {result.GitInfo.OriginalBranch}"); commands.Add($"git branch -D {result.GitInfo.NewBranchName}"); } return commands; } // 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 solution processing public class SolutionRefactoringResult { public string SolutionPath { get; set; } public string SolutionFile { get; set; } public string SolutionName { get; set; } public List Projects { get; set; } = new List(); public List SkippedProjects { get; set; } = new List(); public List GitIgnoreRules { get; set; } = new List(); public GitRefactoringInfo GitInfo { get; set; } public int SuccessfulProjects { get; set; } public int FailedProjects { get; set; } public DateTime StartTime { get; set; } public DateTime EndTime { get; set; } public TimeSpan TotalDuration { get; set; } public List SolutionProjectEntries { get; set; } = new List(); } public class ProjectRefactoringInfo { public string Name { get; set; } public string ProjectFilePath { get; set; } public string ProjectDirectory { get; set; } public List CSharpFiles { get; set; } = new List(); public int TotalFiles { get; set; } public int FilesToProcess { get; set; } public int FilesProcessed { get; set; } public int FilesModified { get; set; } public int FilesSkipped { get; set; } public bool Success { get; set; } public string Error { get; set; } public DateTime StartTime { get; set; } public DateTime EndTime { get; set; } public TimeSpan Duration { get; set; } public List FileResults { get; set; } = new List(); public bool IsMauiProject { get; set; } public bool IsTestProject { get; set; } public List TargetFrameworks { get; set; } = new List(); public int ProcessableFiles { get; set; } public MauiProjectAnalysis MauiAnalysis { get; set; } } public class SolutionProjectEntry { public string Name { get; set; } = string.Empty; public string RelativePath { get; set; } = string.Empty; public string ProjectTypeGuid { get; set; } = string.Empty; public bool IsVirtualProject { get; set; } } public class MauiProjectAnalysis { public bool HasPlatformsFolder { get; set; } public Dictionary> PlatformSpecificFiles { get; set; } = new Dictionary>(); public List RefactoringRecommendations { get; set; } = new List(); } public class FileRefactoringResult { public string FilePath { get; set; } public string FileName { get; set; } public bool Success { get; set; } public string Error { get; set; } public DateTime StartTime { get; set; } public DateTime EndTime { get; set; } public AIPluginResult AnalysisResult { get; set; } public AIPluginResult RefactoringResult { get; set; } public AIPluginResult DocumentationResult { get; set; } } public class SkippedProject { public string Name { get; set; } public string Path { get; set; } public string Reason { get; set; } } public class GitRefactoringInfo { public string RepositoryPath { get; set; } public string OriginalBranch { get; set; } public string OriginalCommit { get; set; } public string NewBranchName { get; set; } public bool BranchCreated { get; set; } public bool Success { get; set; } public string Error { get; set; } public DateTime CreatedAt { get; set; } public List OperationsPerformed { get; set; } = new List(); } }