#
tokens: 44696/50000 6/48 files (page 2/3)
lines: off (toggle) GitHub
raw markdown copy
This is page 2 of 3. Use http://codebase.md/kooshi/sharptoolsmcp?page={x} to view the full context.

# Directory Structure

```
├── .editorconfig
├── .github
│   └── copilot-instructions.md
├── .gitignore
├── LICENSE
├── Prompts
│   ├── github-copilot-sharptools.prompt
│   └── identity.prompt
├── README.md
├── SharpTools.sln
├── SharpTools.SseServer
│   ├── Program.cs
│   └── SharpTools.SseServer.csproj
├── SharpTools.StdioServer
│   ├── Program.cs
│   └── SharpTools.StdioServer.csproj
└── SharpTools.Tools
    ├── Extensions
    │   ├── ServiceCollectionExtensions.cs
    │   └── SyntaxTreeExtensions.cs
    ├── GlobalUsings.cs
    ├── Interfaces
    │   ├── ICodeAnalysisService.cs
    │   ├── ICodeModificationService.cs
    │   ├── IComplexityAnalysisService.cs
    │   ├── IDocumentOperationsService.cs
    │   ├── IEditorConfigProvider.cs
    │   ├── IFuzzyFqnLookupService.cs
    │   ├── IGitService.cs
    │   ├── ISemanticSimilarityService.cs
    │   ├── ISolutionManager.cs
    │   └── ISourceResolutionService.cs
    ├── Mcp
    │   ├── ContextInjectors.cs
    │   ├── ErrorHandlingHelpers.cs
    │   ├── Prompts.cs
    │   ├── ToolHelpers.cs
    │   └── Tools
    │       ├── AnalysisTools.cs
    │       ├── DocumentTools.cs
    │       ├── MemberAnalysisHelper.cs
    │       ├── MiscTools.cs
    │       ├── ModificationTools.cs
    │       ├── PackageTools.cs
    │       └── SolutionTools.cs
    ├── Services
    │   ├── ClassSemanticFeatures.cs
    │   ├── ClassSimilarityResult.cs
    │   ├── CodeAnalysisService.cs
    │   ├── CodeModificationService.cs
    │   ├── ComplexityAnalysisService.cs
    │   ├── DocumentOperationsService.cs
    │   ├── EditorConfigProvider.cs
    │   ├── EmbeddedSourceReader.cs
    │   ├── FuzzyFqnLookupService.cs
    │   ├── GitService.cs
    │   ├── LegacyNuGetPackageReader.cs
    │   ├── MethodSemanticFeatures.cs
    │   ├── MethodSimilarityResult.cs
    │   ├── NoOpGitService.cs
    │   ├── PathInfo.cs
    │   ├── SemanticSimilarityService.cs
    │   ├── SolutionManager.cs
    │   └── SourceResolutionService.cs
    └── SharpTools.Tools.csproj
```

# Files

--------------------------------------------------------------------------------
/SharpTools.Tools/Services/SourceResolutionService.cs:
--------------------------------------------------------------------------------

```csharp
using System.IO;
using System.Net.Http;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using System.Text;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.TypeSystem;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.Extensions.Logging;
using SharpTools.Tools.Interfaces;

namespace SharpTools.Tools.Services {
    public class SourceResolutionService : ISourceResolutionService {
        private readonly ISolutionManager _solutionManager;
        private readonly ILogger<SourceResolutionService> _logger;
        private readonly HttpClient _httpClient;

        public SourceResolutionService(ISolutionManager solutionManager, ILogger<SourceResolutionService> logger) {
            _solutionManager = solutionManager ?? throw new ArgumentNullException(nameof(solutionManager));
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
            _httpClient = new HttpClient();
        }

        public async Task<SourceResult?> ResolveSourceAsync(Microsoft.CodeAnalysis.ISymbol symbol, CancellationToken cancellationToken) {
            if (symbol == null) {
                _logger.LogWarning("Cannot resolve source: Symbol is null");
                return null;
            }

            // 1. Try to get from syntax references (source available)
            if (symbol.DeclaringSyntaxReferences.Length > 0) {
                var syntaxRef = symbol.DeclaringSyntaxReferences[0];
                var sourceText = await syntaxRef.GetSyntaxAsync(cancellationToken);
                if (sourceText != null) {
                    var tree = syntaxRef.SyntaxTree;
                    return new SourceResult {
                        Source = sourceText.ToString(),
                        FilePath = tree.FilePath,
                        IsOriginalSource = true,
                        IsDecompiled = false,
                        ResolutionMethod = "Local Source"
                    };
                }
            }

            // 2. Try Source Link
            var sourceLinkResult = await TrySourceLinkAsync(symbol, cancellationToken);
            if (sourceLinkResult != null) return sourceLinkResult;

            // 3. Try embedded source
            var embeddedResult = await TryEmbeddedSourceAsync(symbol, cancellationToken);
            if (embeddedResult != null) return embeddedResult;

            // 4. Try decompilation as fallback
            var decompiledResult = await TryDecompilationAsync(symbol, cancellationToken);
            if (decompiledResult != null) return decompiledResult;

            return null;
        }

        public async Task<SourceResult?> TrySourceLinkAsync(Microsoft.CodeAnalysis.ISymbol symbol, CancellationToken cancellationToken) {
            _logger.LogInformation("Attempting to retrieve source via Source Link for {SymbolName}", symbol.Name);
            try {
                // Get location of the assembly containing the symbol
                var assembly = symbol.ContainingAssembly;
                if (assembly == null) {
                    _logger.LogWarning("No containing assembly found for symbol {SymbolName}", symbol.Name);
                    return null;
                }

                // Find the PE reference for this assembly
                var metadataReference = GetMetadataReferenceForAssembly(assembly);
                if (metadataReference == null) {
                    _logger.LogWarning("No metadata reference found for assembly {AssemblyName}", assembly.Name);
                    return null;
                }

                // Check for PDB adjacent to the DLL
                var dllPath = metadataReference.Display;
                if (string.IsNullOrEmpty(dllPath) || !File.Exists(dllPath)) {
                    _logger.LogWarning("Assembly file not found: {DllPath}", dllPath);
                    return null;
                }

                var pdbPath = Path.ChangeExtension(dllPath, ".pdb");
                if (!File.Exists(pdbPath)) {
                    _logger.LogWarning("PDB file not found: {PdbPath}", pdbPath);
                    return null;
                }

                _logger.LogInformation("Found PDB file: {PdbPath}", pdbPath);

                // Open the PDB and look for Source Link information
                using var pdbStream = File.OpenRead(pdbPath);
                using var metadataReaderProvider = MetadataReaderProvider.FromPortablePdbStream(pdbStream);
                var metadataReader = metadataReaderProvider.GetMetadataReader();

                // Extract Source Link JSON document
                string? sourceLinkJson = null;
                foreach (var customDebugInfoHandle in metadataReader.CustomDebugInformation) {
                    var customDebugInfo = metadataReader.GetCustomDebugInformation(customDebugInfoHandle);
                    var kind = metadataReader.GetGuid(customDebugInfo.Kind);


                    // Source Link kind GUID
                    if (kind == new Guid("CC110556-A091-4D38-9FEC-25AB9A351A6A")) {
                        var blobReader = metadataReader.GetBlobReader(customDebugInfo.Value);
                        sourceLinkJson = Encoding.UTF8.GetString(blobReader.ReadBytes(blobReader.Length));
                        break;
                    }
                }

                if (string.IsNullOrEmpty(sourceLinkJson)) {
                    _logger.LogWarning("No Source Link information found in PDB");
                    return null;
                }

                _logger.LogInformation("Found Source Link JSON: {Json}", sourceLinkJson);

                // Parse the JSON and extract source URLs
                var sourceLinkDoc = System.Text.Json.JsonDocument.Parse(sourceLinkJson);
                var urlsElement = sourceLinkDoc.RootElement.GetProperty("documents");

                // Get the document containing our symbol
                string symbolDocumentPath = GetSymbolDocumentPath(symbol);
                if (string.IsNullOrEmpty(symbolDocumentPath)) {
                    _logger.LogWarning("Could not determine document path for symbol {SymbolName}", symbol.Name);
                    return null;
                }

                // Normalize path for comparison with Source Link entries
                symbolDocumentPath = symbolDocumentPath.Replace('\\', '/');

                // Find matching URL in Source Link data
                string? sourceUrl = null;
                foreach (var property in urlsElement.EnumerateObject()) {
                    var pattern = property.Name;
                    var url = property.Value.GetString();

                    // Source Link uses wildcard patterns like "C:/Projects/*" -> "https://raw.githubusercontent.com/user/repo/*"
                    if (IsPathMatch(symbolDocumentPath, pattern) && !string.IsNullOrEmpty(url)) {
                        // Replace the wildcard part in the URL
                        sourceUrl = url.Replace("*", GetWildcardMatch(symbolDocumentPath, pattern));
                        break;
                    }
                }

                if (string.IsNullOrEmpty(sourceUrl)) {
                    _logger.LogWarning("No matching source URL found for document {Path}", symbolDocumentPath);
                    return null;
                }

                // Download the source from the URL
                _logger.LogInformation("Downloading source from URL: {Url}", sourceUrl);
                var sourceCode = await _httpClient.GetStringAsync(sourceUrl, cancellationToken);

                return new SourceResult {
                    Source = sourceCode,
                    FilePath = sourceUrl,
                    IsOriginalSource = true,
                    IsDecompiled = false,
                    ResolutionMethod = "Source Link"
                };
            } catch (Exception ex) {
                _logger.LogError(ex, "Error retrieving source via Source Link for {SymbolName}", symbol.Name);
                return null;
            }
        }
        public async Task<SourceResult?> TryEmbeddedSourceAsync(Microsoft.CodeAnalysis.ISymbol symbol, CancellationToken cancellationToken) {
            _logger.LogInformation("Attempting to retrieve embedded source for {SymbolName}", symbol.Name);
            try {
                cancellationToken.ThrowIfCancellationRequested();

                // Get location of the assembly containing the symbol
                var assembly = symbol.ContainingAssembly;
                if (assembly == null) {
                    _logger.LogWarning("No containing assembly found for symbol {SymbolName}", symbol.Name);
                    return null;
                }

                // Find the PE reference for this assembly
                var metadataReference = GetMetadataReferenceForAssembly(assembly);
                if (metadataReference == null) {
                    _logger.LogWarning("No metadata reference found for assembly {AssemblyName}", assembly.Name);
                    return null;
                }

                // Get the assembly path
                var assemblyPath = metadataReference.Display;
                if (string.IsNullOrEmpty(assemblyPath) || !File.Exists(assemblyPath)) {
                    _logger.LogWarning("Assembly file not found: {AssemblyPath}", assemblyPath);
                    return null;
                }

                _logger.LogInformation("Checking for embedded source in assembly: {AssemblyPath}", assemblyPath);

                // Get embedded source information for this symbol
                var embeddedSourceInfo = EmbeddedSourceReader.GetEmbeddedSourceForSymbol(symbol);
                if (embeddedSourceInfo == null) {
                    _logger.LogInformation("No embedded source info available for {SymbolName}", symbol.Name);
                    return null;
                }

                // Check for PDB embedded in the assembly
                Dictionary<string, EmbeddedSourceReader.SourceResult> embeddedSources = new();
                try {
                    embeddedSources = await Task.Run(() => EmbeddedSourceReader.ReadEmbeddedSourcesFromAssembly(assemblyPath), cancellationToken);
                } catch (Exception ex) {
                    _logger.LogDebug(ex, "Error reading embedded sources from assembly: {AssemblyPath}", assemblyPath);
                    // Continue to check standalone PDB
                }

                // If no embedded sources found, check for standalone PDB
                if (!embeddedSources.Any()) {
                    var pdbPath = Path.ChangeExtension(assemblyPath, ".pdb");
                    if (File.Exists(pdbPath)) {
                        _logger.LogInformation("Checking standalone PDB file: {PdbPath}", pdbPath);

                        try {
                            // Read embedded sources in a background task to avoid blocking
                            embeddedSources = await Task.Run(() => EmbeddedSourceReader.ReadEmbeddedSources(pdbPath), cancellationToken);
                        } catch (Exception ex) {
                            _logger.LogDebug(ex, "Error reading embedded sources from PDB: {PdbPath}", pdbPath);
                        }
                    }
                }

                if (!embeddedSources.Any()) {
                    _logger.LogInformation("No embedded sources found in assembly or PDB for {SymbolName}", symbol.Name);
                    return null;
                }

                // Try to find matching source based on file name
                string symbolFileName = embeddedSourceInfo.FilePath ?? string.Empty;

                // Try exact match first
                if (!string.IsNullOrEmpty(symbolFileName) && embeddedSources.TryGetValue(symbolFileName, out var exactMatch)) {
                    _logger.LogInformation("Found exact matching source file: {FileName}", symbolFileName);
                    return new SourceResult {
                        Source = exactMatch.SourceCode ?? string.Empty,
                        FilePath = symbolFileName,
                        IsOriginalSource = true,
                        IsDecompiled = false,
                        ResolutionMethod = "Embedded Source (Exact Match)"
                    };
                }

                // Try filename match (ignoring path)
                string fileNameOnly = Path.GetFileName(symbolFileName);
                foreach (var source in embeddedSources) {
                    string sourceFileName = Path.GetFileName(source.Key);
                    if (string.Equals(sourceFileName, fileNameOnly, StringComparison.OrdinalIgnoreCase)) {
                        _logger.LogInformation("Found matching source file by name: {FileName}", sourceFileName);
                        return new SourceResult {
                            Source = source.Value.SourceCode ?? string.Empty,
                            FilePath = source.Key,
                            IsOriginalSource = true,
                            IsDecompiled = false,
                            ResolutionMethod = "Embedded Source (Filename Match)"
                        };
                    }
                }

                // If the symbol is a method, property, etc., try to find its containing type
                if (symbol.ContainingType != null) {
                    string containingTypeName = symbol.ContainingType.Name + ".cs";
                    foreach (var source in embeddedSources) {
                        string sourceFileName = Path.GetFileName(source.Key);
                        if (string.Equals(sourceFileName, containingTypeName, StringComparison.OrdinalIgnoreCase)) {
                            _logger.LogInformation("Found source file for containing type: {TypeName}", symbol.ContainingType.Name);
                            return new SourceResult {
                                Source = source.Value.SourceCode ?? string.Empty,
                                FilePath = source.Key,
                                IsOriginalSource = true,
                                IsDecompiled = false,
                                ResolutionMethod = "Embedded Source (Containing Type)"
                            };
                        }
                    }
                }

                // If we still don't have a match and there's just one source file, use it
                // This is common for small libraries with a single source file
                if (embeddedSources.Count == 1) {
                    var singleSource = embeddedSources.First();
                    _logger.LogInformation("Using single available source file: {FileName}", singleSource.Key);
                    return new SourceResult {
                        Source = singleSource.Value.SourceCode ?? string.Empty,
                        FilePath = singleSource.Key,
                        IsOriginalSource = true,
                        IsDecompiled = false,
                        ResolutionMethod = "Embedded Source (Single File)"
                    };
                }

                _logger.LogWarning("No matching embedded source found for symbol {SymbolName} among {Count} available files",
                    symbol.Name, embeddedSources.Count);
                return null;
            } catch (Exception ex) {
                _logger.LogError(ex, "Error retrieving embedded source for {SymbolName}", symbol.Name);
                return null;
            }
        }
        public async Task<SourceResult?> TryDecompilationAsync(Microsoft.CodeAnalysis.ISymbol symbol, CancellationToken cancellationToken) {
            _logger.LogInformation("Attempting decompilation for {SymbolName}", symbol.Name);
            try {
                // Get location of the assembly containing the symbol
                var assembly = symbol.ContainingAssembly;
                if (assembly == null) {
                    _logger.LogWarning("No containing assembly found for symbol {SymbolName}", symbol.Name);
                    return null;
                }

                // Find the PE reference for this assembly
                var metadataReference = GetMetadataReferenceForAssembly(assembly);
                if (metadataReference == null) {
                    _logger.LogWarning("No metadata reference found for assembly {AssemblyName}", assembly.Name);
                    return null;
                }

                var assemblyPath = metadataReference.Display;
                if (string.IsNullOrEmpty(assemblyPath) || !File.Exists(assemblyPath)) {
                    _logger.LogWarning("Assembly file not found: {AssemblyPath}", assemblyPath);
                    return null;
                }

                _logger.LogInformation("Decompiling from assembly: {AssemblyPath}", assemblyPath);

                // Create settings for the decompiler
                var decompilerSettings = new DecompilerSettings {
                    ThrowOnAssemblyResolveErrors = false,
                    UseExpressionBodyForCalculatedGetterOnlyProperties = true,
                    UsingDeclarations = true,
                    NullPropagation = true,
                    AlwaysUseBraces = true,
                    RemoveDeadCode = true
                };

                // Decompilation can be CPU intensive, so run it in a background task
                return await Task.Run(() => {
                    try {
                        cancellationToken.ThrowIfCancellationRequested();

                        // Create the decompiler
                        var decompiler = new CSharpDecompiler(assemblyPath, decompilerSettings);

                        // Process based on symbol type
                        string? typeFullName = null;
                        string? memberName = null;

                        if (symbol is Microsoft.CodeAnalysis.INamedTypeSymbol namedType) {
                            typeFullName = namedType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
                        } else if (symbol is Microsoft.CodeAnalysis.IMethodSymbol method) {
                            typeFullName = method.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
                            memberName = method.Name;
                        } else if (symbol is Microsoft.CodeAnalysis.IPropertySymbol property) {
                            typeFullName = property.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
                            memberName = property.Name;
                        } else if (symbol is Microsoft.CodeAnalysis.IFieldSymbol field) {
                            typeFullName = field.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
                            memberName = field.Name;
                        } else if (symbol is Microsoft.CodeAnalysis.IEventSymbol eventSymbol) {
                            typeFullName = eventSymbol.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
                            memberName = eventSymbol.Name;
                        }

                        if (string.IsNullOrEmpty(typeFullName)) {
                            _logger.LogWarning("Could not determine type name for symbol {SymbolName}", symbol.Name);
                            return null;
                        }

                        // Clean up the type name for the decompiler
                        typeFullName = typeFullName.Replace("global::", "")
                            .Replace("<", "{")
                            .Replace(">", "}");

                        try {
                            // Try to decompile the type or member
                            string decompiled;
                            if (string.IsNullOrEmpty(memberName)) {
                                // Decompile entire type
                                decompiled = decompiler.DecompileTypeAsString(new FullTypeName(typeFullName));
                            } else {
                                // Decompile specific member
                                var typeDef = decompiler.TypeSystem.FindType(new FullTypeName(typeFullName))?.GetDefinition();
                                if (typeDef == null) {
                                    _logger.LogWarning("Could not find type definition for {TypeName}", typeFullName);
                                    return null;
                                }

                                var memberDef = typeDef.Members.FirstOrDefault(m => m.Name == memberName);
                                if (memberDef == null) {
                                    _logger.LogWarning("Could not find member {MemberName} in type {TypeName}", memberName, typeFullName);
                                    return null;
                                }

                                decompiled = decompiler.DecompileAsString(memberDef.MetadataToken);
                            }

                            return new SourceResult {
                                Source = decompiled,
                                FilePath = $"{typeFullName}.cs (decompiled)",
                                IsOriginalSource = false,
                                IsDecompiled = true,
                                ResolutionMethod = "Decompilation"
                            };
                        } catch (Exception ex) {
                            _logger.LogWarning(ex, "Error during specific decompilation for {SymbolName}, falling back to full type decompilation", symbol.Name);

                            // Fallback: try to decompile just the containing type
                            try {
                                cancellationToken.ThrowIfCancellationRequested();

                                var containingTypeFullName = symbol.ContainingType?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
                                if (!string.IsNullOrEmpty(containingTypeFullName)) {
                                    containingTypeFullName = containingTypeFullName.Replace("global::", "")
                                        .Replace("<", "{")
                                        .Replace(">", "}");

                                    var decompiled = decompiler.DecompileTypeAsString(new FullTypeName(containingTypeFullName));
                                    return new SourceResult {
                                        Source = decompiled,
                                        FilePath = $"{containingTypeFullName}.cs (decompiled)",
                                        IsOriginalSource = false,
                                        IsDecompiled = true,
                                        ResolutionMethod = "Decompilation (Fallback)"
                                    };
                                }
                            } catch (Exception innerEx) {
                                _logger.LogError(innerEx, "Fallback decompilation failed for {SymbolName}", symbol.Name);
                            }
                        }

                        return null;
                    } catch (Exception ex) {
                        _logger.LogError(ex, "Error during decompilation for {SymbolName}", symbol.Name);
                        return null;
                    }
                }, cancellationToken);
            } catch (Exception ex) {
                _logger.LogError(ex, "Error during decompilation for {SymbolName}", symbol.Name);
                return null;
            }
        }
        #region Helper Methods

        private PortableExecutableReference? GetMetadataReferenceForAssembly(IAssemblySymbol assembly) {
            if (!_solutionManager.IsSolutionLoaded) {
                _logger.LogWarning("Cannot get metadata reference: Solution not loaded");
                return null;
            }

            foreach (var project in _solutionManager.GetProjects()) {
                foreach (var reference in project.MetadataReferences.OfType<PortableExecutableReference>()) {
                    if (Path.GetFileNameWithoutExtension(reference.FilePath) == assembly.Name) {
                        return reference;
                    }
                }
            }

            return null;
        }

        private string GetSymbolDocumentPath(Microsoft.CodeAnalysis.ISymbol symbol) {
            // For symbols with syntax references, get the file path directly
            if (symbol.DeclaringSyntaxReferences.Length > 0) {
                var syntaxRef = symbol.DeclaringSyntaxReferences[0];
                return syntaxRef.SyntaxTree.FilePath;
            }

            // For metadata symbols, try to infer document path
            // This is a simplistic approach and might not work in all cases
            return $"{symbol.ContainingType?.Name ?? symbol.Name}.cs";
        }

        private bool IsPathMatch(string path, string pattern) {
            // Source Link uses patterns with * wildcards
            if (!pattern.Contains('*')) {
                return string.Equals(path, pattern, StringComparison.OrdinalIgnoreCase);
            }

            // Simple wildcard matching for patterns like "C:/Projects/*"
            var prefix = pattern.Substring(0, pattern.IndexOf('*'));
            var suffix = pattern.Substring(pattern.IndexOf('*') + 1);

            return path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) &&
            (suffix.Length == 0 || path.EndsWith(suffix, StringComparison.OrdinalIgnoreCase));
        }

        private string GetWildcardMatch(string path, string pattern) {
            var prefix = pattern.Substring(0, pattern.IndexOf('*'));
            var suffix = pattern.Substring(pattern.IndexOf('*') + 1);

            if (suffix.Length == 0) {
                return path.Substring(prefix.Length);
            }

            return path.Substring(prefix.Length, path.Length - prefix.Length - suffix.Length);
        }

        #endregion
    }
}
```

--------------------------------------------------------------------------------
/SharpTools.Tools/Services/CodeModificationService.cs:
--------------------------------------------------------------------------------

```csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Text;
using Microsoft.Extensions.FileSystemGlobbing;
using Microsoft.Extensions.Logging;
using ModelContextProtocol;
using SharpTools.Tools.Interfaces;
using SharpTools.Tools.Mcp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace SharpTools.Tools.Services;

public class CodeModificationService : ICodeModificationService {
    private readonly ISolutionManager _solutionManager;
    private readonly IGitService _gitService;
    private readonly ILogger<CodeModificationService> _logger;

    public CodeModificationService(
        ISolutionManager solutionManager,
        IGitService gitService,
        ILogger<CodeModificationService> logger) {
        _solutionManager = solutionManager ?? throw new ArgumentNullException(nameof(solutionManager));
        _gitService = gitService ?? throw new ArgumentNullException(nameof(gitService));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    private Solution GetCurrentSolutionOrThrow() {
        if (!_solutionManager.IsSolutionLoaded) {
            throw new InvalidOperationException("No solution is currently loaded.");
        }
        return _solutionManager.CurrentSolution;
    }

    public async Task<Solution> AddMemberAsync(DocumentId documentId, INamedTypeSymbol targetTypeSymbol, MemberDeclarationSyntax newMember, int lineNumberHint = -1, CancellationToken cancellationToken = default) {
        var solution = GetCurrentSolutionOrThrow();
        var document = solution.GetDocument(documentId) ?? throw new ArgumentException($"Document with ID '{documentId}' not found in the current solution.", nameof(documentId));

        var typeDeclarationNode = targetTypeSymbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(cancellationToken) as TypeDeclarationSyntax;
        if (typeDeclarationNode == null) {
            throw new InvalidOperationException($"Could not find syntax node for type '{targetTypeSymbol.Name}'.");
        }

        _logger.LogInformation("Adding member to type {TypeName} in document {DocumentPath}", targetTypeSymbol.Name, document.FilePath);

        NormalizeMemberDeclarationTrivia(newMember);

        if (lineNumberHint > 0) {
            var root = await document.GetSyntaxRootAsync(cancellationToken);
            if (root != null) {
                var sourceText = await document.GetTextAsync(cancellationToken);

                var members = typeDeclarationNode.Members
                    .Select(member => new {
                        Member = member,
                        LineSpan = member.GetLocation().GetLineSpan()
                    })
                    .OrderBy(m => m.LineSpan.StartLinePosition.Line)
                    .ToList();

                int insertIndex = 0;
                for (int i = 0; i < members.Count; i++) {
                    if (members[i].LineSpan.StartLinePosition.Line >= lineNumberHint) {
                        insertIndex = i;
                        break;
                    }
                    insertIndex = i + 1;
                }

                var editor = await DocumentEditor.CreateAsync(document, cancellationToken);
                var membersList = typeDeclarationNode.Members.ToList();
                membersList.Insert(insertIndex, newMember);
                var newTypeDeclaration = typeDeclarationNode.WithMembers(SyntaxFactory.List(membersList));

                editor.ReplaceNode(typeDeclarationNode, newTypeDeclaration);

                var newDocument = editor.GetChangedDocument();
                var formattedDocument = await FormatDocumentAsync(newDocument, cancellationToken);
                return formattedDocument.Project.Solution;
            }
        }

        var defaultEditor = await DocumentEditor.CreateAsync(document, cancellationToken);
        defaultEditor.AddMember(typeDeclarationNode, newMember);

        var changedDocument = defaultEditor.GetChangedDocument();
        var finalDocument = await FormatDocumentAsync(changedDocument, cancellationToken);
        return finalDocument.Project.Solution;
    }

    public async Task<Solution> AddStatementAsync(DocumentId documentId, MethodDeclarationSyntax targetMethodNode, StatementSyntax newStatement, CancellationToken cancellationToken, bool addToBeginning = false) {
        var solution = GetCurrentSolutionOrThrow();
        var document = solution.GetDocument(documentId) ?? throw new ArgumentException($"Document with ID '{documentId}' not found in the current solution.", nameof(documentId));

        _logger.LogInformation("Adding statement to method {MethodName} in document {DocumentPath}", targetMethodNode.Identifier.Text, document.FilePath);
        var editor = await DocumentEditor.CreateAsync(document, cancellationToken);

        if (targetMethodNode.Body != null) {
            var currentBody = targetMethodNode.Body;
            BlockSyntax newBody;
            if (addToBeginning) {
                var newStatements = currentBody.Statements.Insert(0, newStatement);
                newBody = currentBody.WithStatements(newStatements);
            } else {
                newBody = currentBody.AddStatements(newStatement);
            }
            editor.ReplaceNode(currentBody, newBody);
        } else if (targetMethodNode.ExpressionBody != null) {
            // Converting expression body to block body
            var returnStatement = SyntaxFactory.ReturnStatement(targetMethodNode.ExpressionBody.Expression);
            BlockSyntax bodyBlock;
            if (addToBeginning) {
                bodyBlock = SyntaxFactory.Block(newStatement, returnStatement);
            } else {
                bodyBlock = SyntaxFactory.Block(returnStatement, newStatement);
            }
            // Create a new method node with the block body
            var newMethod = targetMethodNode.WithBody(bodyBlock)
                .WithExpressionBody(null) // Remove expression body
                .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.None)); // Remove semicolon if any
            editor.ReplaceNode(targetMethodNode, newMethod);
        } else {
            // Method has no body (e.g. abstract, partial, extern). Create one.
            var bodyBlock = SyntaxFactory.Block(newStatement);
            var newMethodWithBody = targetMethodNode.WithBody(bodyBlock)
                .WithExpressionBody(null)
                .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.None));
            editor.ReplaceNode(targetMethodNode, newMethodWithBody);
        }

        var newDocument = editor.GetChangedDocument();
        var formattedDocument = await FormatDocumentAsync(newDocument, cancellationToken);
        return formattedDocument.Project.Solution;
    }

    private static SyntaxTrivia newline = SyntaxFactory.EndOfLine("\n");
    private SyntaxTriviaList NormalizeLeadingTrivia(SyntaxTriviaList trivia) {
        // Remove all newlines
        var filtered = trivia.Where(t => !t.IsKind(SyntaxKind.EndOfLineTrivia));

        // Create a new list with a newline at the beginning followed by the filtered trivia
        return SyntaxFactory.TriviaList(newline, newline).AddRange(filtered);
    }

    private SyntaxTriviaList NormalizeTrailingTrivia(SyntaxTriviaList trivia) {
        // Remove all newlines
        var filtered = trivia.Where(t => !t.IsKind(SyntaxKind.EndOfLineTrivia));

        // Create a new SyntaxTriviaList from the filtered items
        var result = SyntaxFactory.TriviaList(filtered);

        // Add the end-of-line trivia and return
        return result.Add(newline).Add(newline);
    }

    private SyntaxNode NormalizeMemberDeclarationTrivia(SyntaxNode member) {
        if (member is MemberDeclarationSyntax memberDeclaration) {
            var leadingTrivia = memberDeclaration.GetLeadingTrivia();
            var trailingTrivia = memberDeclaration.GetTrailingTrivia();

            // Normalize trivia
            var normalizedLeading = NormalizeLeadingTrivia(leadingTrivia);
            var normalizedTrailing = NormalizeTrailingTrivia(trailingTrivia);

            // Apply the normalized trivia
            return memberDeclaration.WithLeadingTrivia(normalizedLeading)
                                    .WithTrailingTrivia(normalizedTrailing);
        }
        return member;
    }
    public async Task<Solution> ReplaceNodeAsync(DocumentId documentId, SyntaxNode oldNode, SyntaxNode newNode, CancellationToken cancellationToken) {
        var solution = GetCurrentSolutionOrThrow();
        var document = solution.GetDocument(documentId) ?? throw new ArgumentException($"Document with ID '{documentId}' not found in the current solution.", nameof(documentId));

        _logger.LogInformation("Replacing node in document {DocumentPath}", document.FilePath);

        // Check if this is a deletion operation (newNode is an EmptyStatement with a delete comment)
        bool isDeleteOperation = newNode is Microsoft.CodeAnalysis.CSharp.Syntax.EmptyStatementSyntax emptyStmt &&
                                 emptyStmt.HasLeadingTrivia &&
                                 emptyStmt.GetLeadingTrivia().Any(t => t.IsKind(SyntaxKind.SingleLineCommentTrivia) &&
                                                                     t.ToString().StartsWith("// Delete", StringComparison.OrdinalIgnoreCase));

        if (isDeleteOperation) {
            _logger.LogInformation("Detected deletion operation for node {NodeKind}", oldNode.Kind());

            // For deletion, we need to remove the node from its parent
            var root = await document.GetSyntaxRootAsync(cancellationToken);
            if (root == null) {
                throw new InvalidOperationException("Could not get syntax root for document.");
            }

            // Different approach based on the node's parent context
            SyntaxNode newRoot;

            if (oldNode.Parent is Microsoft.CodeAnalysis.CSharp.Syntax.CompilationUnitSyntax compilationUnit) {
                // Handle top-level members in the compilation unit
                if (oldNode is Microsoft.CodeAnalysis.CSharp.Syntax.MemberDeclarationSyntax memberToRemove) {
                    var newMembers = compilationUnit.Members.Remove(memberToRemove);
                    newRoot = compilationUnit.WithMembers(newMembers);
                } else {
                    throw new InvalidOperationException($"Cannot delete node of type {oldNode.GetType().Name} directly from compilation unit.");
                }
            } else if (oldNode.Parent is Microsoft.CodeAnalysis.CSharp.Syntax.NamespaceDeclarationSyntax namespaceDecl) {
                // Handle members in a namespace
                if (oldNode is Microsoft.CodeAnalysis.CSharp.Syntax.MemberDeclarationSyntax memberToRemove) {
                    var newMembers = namespaceDecl.Members.Remove(memberToRemove);
                    var newNamespace = namespaceDecl.WithMembers(newMembers);
                    newRoot = root.ReplaceNode(namespaceDecl, newNamespace);
                } else {
                    throw new InvalidOperationException($"Cannot delete node of type {oldNode.GetType().Name} from namespace declaration.");
                }
            } else if (oldNode.Parent is Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax typeDecl) {
                // Handle members in a type declaration (class, struct, interface, etc.)
                if (oldNode is Microsoft.CodeAnalysis.CSharp.Syntax.MemberDeclarationSyntax memberToRemove) {
                    var newMembers = typeDecl.Members.Remove(memberToRemove);
                    var newType = typeDecl.WithMembers(newMembers);
                    newRoot = root.ReplaceNode(typeDecl, newType);
                } else {
                    throw new InvalidOperationException($"Cannot delete node of type {oldNode.GetType().Name} from type declaration.");
                }
            } else {
                throw new InvalidOperationException($"Cannot delete node of type {oldNode.GetType().Name} from parent of type {oldNode.Parent?.GetType().Name ?? "null"}.");
            }

            var newDocument = document.WithSyntaxRoot(newRoot);
            var formattedDocument = await FormatDocumentAsync(newDocument, cancellationToken);
            return formattedDocument.Project.Solution;
        } else {
            // Standard node replacement
            NormalizeMemberDeclarationTrivia(newNode);

            var editor = await DocumentEditor.CreateAsync(document, cancellationToken);
            editor.ReplaceNode(oldNode, newNode);

            var newDocument = editor.GetChangedDocument();
            var formattedDocument = await FormatDocumentAsync(newDocument, cancellationToken);
            return formattedDocument.Project.Solution;
        }
    }
    public async Task<Solution> RenameSymbolAsync(ISymbol symbol, string newName, CancellationToken cancellationToken) {
        var solution = GetCurrentSolutionOrThrow();
        _logger.LogInformation("Renaming symbol {SymbolName} to {NewName}", symbol.ToDisplayString(), newName);

        var options = solution.Workspace.Options;

        // Using the older API for now
        // Temporarily disable obsolete warning
#pragma warning disable CS0618
        var newSolution = await Renamer.RenameSymbolAsync(solution, symbol, newName, options, cancellationToken);
#pragma warning restore CS0618

        return newSolution;
    }
    public async Task<Solution> ReplaceAllReferencesAsync(ISymbol symbol, string replacementText, CancellationToken cancellationToken, Func<SyntaxNode, bool>? predicateFilter = null) {
        var solution = GetCurrentSolutionOrThrow();
        _logger.LogInformation("Replacing all references to symbol {SymbolName} with text '{ReplacementText}'",
            symbol.ToDisplayString(), replacementText);

        // Find all references to the symbol
        var referencedSymbols = await SymbolFinder.FindReferencesAsync(symbol, solution, cancellationToken);
        var changedSolution = solution;

        foreach (var referencedSymbol in referencedSymbols) {
            foreach (var location in referencedSymbol.Locations) {
                cancellationToken.ThrowIfCancellationRequested();

                var document = changedSolution.GetDocument(location.Document.Id);
                if (document == null) continue;

                var root = await document.GetSyntaxRootAsync(cancellationToken);
                if (root == null) continue;

                var node = root.FindNode(location.Location.SourceSpan);

                // Apply filter if provided
                if (predicateFilter != null && !predicateFilter(node)) {
                    _logger.LogDebug("Skipping replacement for node at {Location} due to filter predicate",
                        location.Location.GetLineSpan());
                    continue;
                }

                // Create a new syntax node with the replacement text
                var replacementNode = SyntaxFactory.ParseExpression(replacementText)
                    .WithLeadingTrivia(node.GetLeadingTrivia())
                    .WithTrailingTrivia(node.GetTrailingTrivia());

                // Replace the node in the document
                var newRoot = root.ReplaceNode(node, replacementNode);
                var newDocument = document.WithSyntaxRoot(newRoot);

                // Format the document and update the solution
                var formattedDocument = await FormatDocumentAsync(newDocument, cancellationToken);
                changedSolution = formattedDocument.Project.Solution;
            }
        }

        return changedSolution;
    }
    public async Task<Solution> FindAndReplaceAsync(string targetString, string regexPattern, string replacementText, CancellationToken cancellationToken, RegexOptions options = RegexOptions.Multiline) {
        var solution = GetCurrentSolutionOrThrow();
        _logger.LogInformation("Performing find and replace with regex '{RegexPattern}' on target '{TargetString}'",
            regexPattern, targetString);

        // Create the regex with multiline option
        var regex = new Regex(regexPattern, options);
        Solution resultSolution = solution;

        // Check if the target is a fully qualified name (no wildcards)
        if (!targetString.Contains("*") && !targetString.Contains("?")) {
            try {
                // Try to resolve as a symbol
                var symbol = await _solutionManager.FindRoslynSymbolAsync(targetString, cancellationToken);
                if (symbol != null) {
                    _logger.LogInformation("Target is a valid symbol: {SymbolName}", symbol.ToDisplayString());

                    // For a symbol, we'll get its defining document and limit replacements to the symbol's span
                    var syntaxReferences = symbol.DeclaringSyntaxReferences;
                    if (syntaxReferences.Any()) {
                        foreach (var syntaxRef in syntaxReferences) {
                            cancellationToken.ThrowIfCancellationRequested();

                            var node = await syntaxRef.GetSyntaxAsync(cancellationToken);
                            var document = solution.GetDocument(node.SyntaxTree);

                            if (document == null) continue;

                            // Get the source text and limit replacement to the symbol's node span
                            var sourceText = await document.GetTextAsync(cancellationToken);
                            var originalText = sourceText.ToString();

                            // Extract only the text within the symbol's node span
                            var nodeSpan = node.Span;
                            var symbolText = originalText.Substring(nodeSpan.Start, nodeSpan.Length).NormalizeEndOfLines();

                            // Apply regex replacement only to the symbol's text
                            var newSymbolText = regex.Replace(symbolText, replacementText);

                            // Only update if changes were made to the symbol text
                            if (newSymbolText != symbolText) {
                                // Create new text by replacing the symbol's span with the modified text
                                var newFullText = originalText.Substring(0, nodeSpan.Start) +
                                    newSymbolText +
                                    originalText.Substring(nodeSpan.Start + nodeSpan.Length);

                                var newDocument = document.WithText(SourceText.From(newFullText, sourceText.Encoding));
                                var formattedDocument = await FormatDocumentAsync(newDocument, cancellationToken);
                                resultSolution = formattedDocument.Project.Solution;
                            }
                        }

                        return resultSolution;
                    }
                }
            } catch (Exception ex) {
                _logger.LogInformation("Target string is not a valid symbol: {Error}", ex.Message);
                // Fall through to file-based search
            }
        }

        // Handle as file path with potential wildcards
        var documentIds = new List<DocumentId>();

        // Log the pattern we're using
        _logger.LogInformation("Treating '{Target}' as a file path pattern", targetString);

        // Normalize path separators in the target pattern to use forward slashes consistently
        string normalizedTarget = targetString.Replace('\\', '/');

        Matcher matcher = new(StringComparison.OrdinalIgnoreCase);
        matcher.AddInclude(normalizedTarget);
        string root = Path.GetPathRoot(solution.FilePath) ?? Path.GetPathRoot(Environment.CurrentDirectory)!;

        // Process all projects and documents
        foreach (var project in solution.Projects) {
            foreach (var document in project.Documents) {
                if (string.IsNullOrWhiteSpace(document.FilePath)) continue;
                // Use wildcard matching
                if (matcher.Match(root, document.FilePath).HasMatches) {
                    _logger.LogInformation("Document matched pattern: {DocumentPath}", document.FilePath);
                    documentIds.Add(document.Id);
                }
            }
        }

        _logger.LogInformation("Found {Count} documents matching pattern '{Pattern}'",
            documentIds.Count, targetString);

        resultSolution = solution;
        // Process all matching documents
        foreach (var documentId in documentIds) {
            cancellationToken.ThrowIfCancellationRequested();

            // Apply regex replacement
            var document = resultSolution.GetDocument(documentId);
            if (document == null) continue;
            var sourceText = await document.GetTextAsync(cancellationToken);
            var originalText = sourceText.ToString().NormalizeEndOfLines();

            var newText = regex.Replace(originalText, replacementText);

            // Only update if changes were made
            if (newText != originalText) {
                var newDocument = document.WithText(SourceText.From(newText, sourceText.Encoding));
                var formattedDocument = await FormatDocumentAsync(newDocument, cancellationToken);
                resultSolution = formattedDocument.Project.Solution;
            }
        }

        return resultSolution;
    }
    public async Task<Document> FormatDocumentAsync(Document document, CancellationToken cancellationToken) {
        _logger.LogDebug("Formatting document: {DocumentPath}", document.FilePath);
        var formattingOptions = await document.GetOptionsAsync(cancellationToken);
        var formattedDocument = await Formatter.FormatAsync(document, formattingOptions, cancellationToken);
        _logger.LogDebug("Document formatted: {DocumentPath}", document.FilePath);
        return formattedDocument;
    }
    public async Task ApplyChangesAsync(Solution newSolution, CancellationToken cancellationToken, string commitMessage, IEnumerable<string>? additionalFilePaths = null) {
        if (_solutionManager.CurrentWorkspace is not MSBuildWorkspace workspace) {
            _logger.LogError("Cannot apply changes: Workspace is not an MSBuildWorkspace or is null.");
            throw new InvalidOperationException("Workspace is not suitable for applying changes.");
        }

        var originalSolution = _solutionManager.CurrentSolution ?? throw new InvalidOperationException("Original solution is null before applying changes.");
        var solutionPath = originalSolution.FilePath ?? "";

        var solutionChanges = newSolution.GetChanges(originalSolution);
        var finalSolutionToApply = newSolution;

        // Collect changed file paths for git operations - include both changed and new documents
        var changedFilePaths = new List<string>();

        foreach (var projectChange in solutionChanges.GetProjectChanges()) {
            // Handle changed documents
            foreach (var changedDocumentId in projectChange.GetChangedDocuments()) {
                var documentToFormat = finalSolutionToApply.GetDocument(changedDocumentId);
                if (documentToFormat != null) {
                    _logger.LogDebug("Pre-apply formatting for changed document: {DocumentPath}", documentToFormat.FilePath);
                    var formattedDocument = await FormatDocumentAsync(documentToFormat, cancellationToken);
                    finalSolutionToApply = formattedDocument.Project.Solution;

                    if (!string.IsNullOrEmpty(documentToFormat.FilePath)) {
                        changedFilePaths.Add(documentToFormat.FilePath);
                    }
                }
            }

            // Handle added documents (new files)
            foreach (var addedDocumentId in projectChange.GetAddedDocuments()) {
                var addedDocument = finalSolutionToApply.GetDocument(addedDocumentId);
                if (addedDocument != null) {
                    _logger.LogDebug("Pre-apply formatting for added document: {DocumentPath}", addedDocument.FilePath);
                    var formattedDocument = await FormatDocumentAsync(addedDocument, cancellationToken);
                    finalSolutionToApply = formattedDocument.Project.Solution;

                    if (!string.IsNullOrEmpty(addedDocument.FilePath)) {
                        changedFilePaths.Add(addedDocument.FilePath);
                        _logger.LogInformation("Added new document for git tracking: {DocumentPath}", addedDocument.FilePath);
                    }
                }
            }

            // Handle removed documents
            foreach (var removedDocumentId in projectChange.GetRemovedDocuments()) {
                var removedDocument = originalSolution.GetDocument(removedDocumentId);
                if (removedDocument != null && !string.IsNullOrEmpty(removedDocument.FilePath)) {
                    changedFilePaths.Add(removedDocument.FilePath);
                    _logger.LogInformation("Marked removed document for git tracking: {DocumentPath}", removedDocument.FilePath);
                }
            }
        }

        _logger.LogInformation("Applying changes to workspace for {DocumentCount} changed documents across {ProjectCount} projects.",
            solutionChanges.GetProjectChanges().SelectMany(pc => pc.GetChangedDocuments().Concat(pc.GetAddedDocuments()).Concat(pc.GetRemovedDocuments())).Count(),
            solutionChanges.GetProjectChanges().Count());

        if (workspace.TryApplyChanges(finalSolutionToApply)) {
            _logger.LogInformation("Changes applied successfully to the workspace.");

            // If additional file paths are provided, add them to the changed file paths
            if (additionalFilePaths != null) {
                changedFilePaths.AddRange(additionalFilePaths.Where(fp => !string.IsNullOrEmpty(fp) && File.Exists(fp)));
            }
            // Git operations after successful changes
            await ProcessGitOperationsAsync(solutionPath, changedFilePaths, commitMessage, cancellationToken);

            _solutionManager.RefreshCurrentSolution();
        } else {
            _logger.LogError("Failed to apply changes to the workspace.");
            throw new InvalidOperationException("Failed to apply changes to the workspace. Files might have been modified externally.");
        }
    }
    private async Task ProcessGitOperationsAsync(string solutionPath, List<string> changedFilePaths, string commitMessage, CancellationToken cancellationToken) {
        if (string.IsNullOrEmpty(solutionPath) || changedFilePaths.Count == 0) {
            return;
        }

        try {
            // Check if solution is in a git repo
            if (!await _gitService.IsRepositoryAsync(solutionPath, cancellationToken)) {
                _logger.LogDebug("Solution is not in a Git repository, skipping Git operations");
                return;
            }

            _logger.LogDebug("Solution is in a Git repository, processing Git operations");

            // Check if already on sharptools branch
            if (!await _gitService.IsOnSharpToolsBranchAsync(solutionPath, cancellationToken)) {
                _logger.LogInformation("Not on a SharpTools branch, creating one");
                await _gitService.EnsureSharpToolsBranchAsync(solutionPath, cancellationToken);
            }

            // Commit changes with the provided commit message
            await _gitService.CommitChangesAsync(solutionPath, changedFilePaths, commitMessage, cancellationToken);
            _logger.LogInformation("Git operations completed successfully with commit message: {CommitMessage}", commitMessage);
        } catch (Exception ex) {
            // Log but don't fail the operation if Git operations fail
            _logger.LogWarning(ex, "Git operations failed but code changes were still applied");
        }
    }
    public async Task<(bool success, string message)> UndoLastChangeAsync(CancellationToken cancellationToken) {
        if (_solutionManager.CurrentWorkspace is not MSBuildWorkspace workspace) {
            _logger.LogError("Cannot undo changes: Workspace is not an MSBuildWorkspace or is null.");
            var message = "Error: Workspace is not an MSBuildWorkspace or is null. Cannot undo.";
            return (false, message);
        }

        var currentSolution = _solutionManager.CurrentSolution;
        if (currentSolution?.FilePath == null) {
            _logger.LogError("Cannot undo changes: Current solution or its file path is null.");
            var message = "Error: No solution loaded or solution file path is null. Cannot undo.";
            return (false, message);
        }

        var solutionPath = currentSolution.FilePath;

        // Check if solution is in a git repository
        if (!await _gitService.IsRepositoryAsync(solutionPath, cancellationToken)) {
            _logger.LogError("Cannot undo changes: Solution is not in a Git repository.");
            throw new McpException("Error: Solution is not in a Git repository. Undo functionality requires Git version control.");
        }

        // Check if we're on a sharptools branch
        if (!await _gitService.IsOnSharpToolsBranchAsync(solutionPath, cancellationToken)) {
            _logger.LogError("Cannot undo changes: Not on a SharpTools branch.");
            var message = "Error: Not on a SharpTools branch. Undo is only available on SharpTools branches.";
            return (false, message);
        }

        _logger.LogInformation("Attempting to undo last change by reverting last Git commit.");

        // Perform git revert with diff
        var (revertSuccess, diff) = await _gitService.RevertLastCommitAsync(solutionPath, cancellationToken);
        if (!revertSuccess) {
            _logger.LogError("Git revert operation failed.");
            var message = "Error: Failed to revert the last Git commit. There may be no commits to revert or the operation failed.";
            return (false, message);
        }

        // Reload the solution from disk to reflect the reverted changes
        await _solutionManager.ReloadSolutionFromDiskAsync(cancellationToken);

        _logger.LogInformation("Successfully reverted the last change using Git.");
        var successMessage = "Successfully reverted the last change by reverting the last Git commit. Solution reloaded from disk.";

        // Add the diff to the success message if available
        if (!string.IsNullOrEmpty(diff)) {
            successMessage += "\n\nChanges undone:\n" + diff;
        }

        return (true, successMessage);
    }
}
```

--------------------------------------------------------------------------------
/SharpTools.Tools/Services/SolutionManager.cs:
--------------------------------------------------------------------------------

```csharp
using System.Runtime.InteropServices;
using System.Xml.Linq;
using ModelContextProtocol;
using SharpTools.Tools.Mcp.Tools;
namespace SharpTools.Tools.Services;

public sealed class SolutionManager : ISolutionManager {
    private readonly ILogger<SolutionManager> _logger;
    private readonly IFuzzyFqnLookupService _fuzzyFqnLookupService;
    private MSBuildWorkspace? _workspace;
    private Solution? _currentSolution;
    private MetadataLoadContext? _metadataLoadContext;
    private PathAssemblyResolver? _pathAssemblyResolver;
    private HashSet<string> _assemblyPathsForReflection = new();
    private readonly ConcurrentDictionary<ProjectId, Compilation> _compilationCache = new();
    private readonly ConcurrentDictionary<DocumentId, SemanticModel> _semanticModelCache = new();
    private readonly ConcurrentDictionary<string, Type> _allLoadedReflectionTypesCache = new();
    [MemberNotNullWhen(true, nameof(_workspace), nameof(_currentSolution))]
    public bool IsSolutionLoaded => _workspace != null && _currentSolution != null;
    public MSBuildWorkspace? CurrentWorkspace => _workspace;
    public Solution? CurrentSolution => _currentSolution;
    private readonly string? _buildConfiguration;

    public SolutionManager(ILogger<SolutionManager> logger, IFuzzyFqnLookupService fuzzyFqnLookupService, string? buildConfiguration = null) {
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        _fuzzyFqnLookupService = fuzzyFqnLookupService ?? throw new ArgumentNullException(nameof(fuzzyFqnLookupService));
        _buildConfiguration = buildConfiguration;
    }
    public async Task LoadSolutionAsync(string solutionPath, CancellationToken cancellationToken) {
        if (!File.Exists(solutionPath)) {
            _logger.LogError("Solution file not found: {SolutionPath}", solutionPath);
            throw new FileNotFoundException("Solution file not found.", solutionPath);
        }
        UnloadSolution(); // Clears previous state including _allLoadedReflectionTypesCache
        try {
            _logger.LogInformation("Creating MSBuildWorkspace...");
            var properties = new Dictionary<string, string> {
                { "DesignTimeBuild", "true" }
            };

            if (!string.IsNullOrEmpty(_buildConfiguration)) {
                properties.Add("Configuration", _buildConfiguration);
            }
            
            _workspace = MSBuildWorkspace.Create(properties, MefHostServices.DefaultHost);
            _workspace.WorkspaceFailed += OnWorkspaceFailed;
            _logger.LogInformation("Loading solution: {SolutionPath}", solutionPath);
            _currentSolution = await _workspace.OpenSolutionAsync(solutionPath, new ProgressReporter(_logger), cancellationToken);
            _logger.LogInformation("Solution loaded successfully with {ProjectCount} projects.", _currentSolution.Projects.Count());
            InitializeMetadataContextAndReflectionCache(_currentSolution, cancellationToken);
        } catch (Exception ex) {
            _logger.LogError(ex, "Failed to load solution: {SolutionPath}", solutionPath);
            UnloadSolution();
            throw;
        }
    }
    private void InitializeMetadataContextAndReflectionCache(Solution solution, CancellationToken cancellationToken = default) {
        // Check cancellation at entry point
        cancellationToken.ThrowIfCancellationRequested();

        _assemblyPathsForReflection.Clear();

        // Add runtime assemblies
        string[] runtimeAssemblies = Directory.GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), "*.dll");
        foreach (var assemblyPath in runtimeAssemblies) {
            // Check cancellation periodically
            cancellationToken.ThrowIfCancellationRequested();
            if (!_assemblyPathsForReflection.Contains(assemblyPath)) {
                _assemblyPathsForReflection.Add(assemblyPath);
            }
        }

        // Load NuGet package assemblies from global cache instead of output directories
        var nugetAssemblies = GetNuGetAssemblyPaths(solution, cancellationToken);
        foreach (var assemblyPath in nugetAssemblies) {
            cancellationToken.ThrowIfCancellationRequested();
            _assemblyPathsForReflection.Add(assemblyPath);
        }

        // Check cancellation before cleanup operations
        cancellationToken.ThrowIfCancellationRequested();

        // Remove mscorlib.dll from the list of assemblies as it is loaded by default
        _assemblyPathsForReflection.RemoveWhere(p => p.EndsWith("mscorlib.dll", StringComparison.OrdinalIgnoreCase));

        // Remove duplicate files regardless of path
        _assemblyPathsForReflection = _assemblyPathsForReflection
            .GroupBy(Path.GetFileName)
            .Select(g => g.First())
            .ToHashSet(StringComparer.OrdinalIgnoreCase);

        // Check cancellation before creating context
        cancellationToken.ThrowIfCancellationRequested();

        _pathAssemblyResolver = new PathAssemblyResolver(_assemblyPathsForReflection);
        _metadataLoadContext = new MetadataLoadContext(_pathAssemblyResolver);
        _logger.LogInformation("MetadataLoadContext initialized with {PathCount} distinct search paths.", _assemblyPathsForReflection.Count);

        // Check cancellation before populating cache
        cancellationToken.ThrowIfCancellationRequested();

        PopulateReflectionCache(_assemblyPathsForReflection, cancellationToken);
    }
    private void PopulateReflectionCache(IEnumerable<string> assemblyPathsToInspect, CancellationToken cancellationToken = default) {
        // Check cancellation at entry point
        cancellationToken.ThrowIfCancellationRequested();

        if (_metadataLoadContext == null) {
            _logger.LogWarning("Cannot populate reflection cache: MetadataLoadContext not initialized.");
            return;
        }
        // _allLoadedReflectionTypesCache is cleared in UnloadSolution
        _logger.LogInformation("Starting population of reflection type cache...");
        int typesCachedCount = 0;

        // Convert to list to avoid multiple enumeration and enable progress tracking
        var pathsList = assemblyPathsToInspect.ToList();
        int totalPaths = pathsList.Count;
        int processedPaths = 0;
        const int progressCheckInterval = 10; // Report progress and check cancellation every 10 assemblies

        foreach (var assemblyPath in pathsList) {
            // Check cancellation and log progress periodically
            if (++processedPaths % progressCheckInterval == 0) {
                cancellationToken.ThrowIfCancellationRequested();
                _logger.LogTrace("Reflection cache population progress: {Progress}% ({Current}/{Total})",
                (int)((float)processedPaths / totalPaths * 100), processedPaths, totalPaths);
            }

            LoadTypesFromAssembly(assemblyPath, ref typesCachedCount, cancellationToken);
        }

        _logger.LogInformation("Reflection type cache population complete. Cached {Count} types from {AssemblyCount} unique assembly paths processed.", typesCachedCount, pathsList.Count);
    }
    private void LoadTypesFromAssembly(string assemblyPath, ref int typesCachedCount, CancellationToken cancellationToken = default) {
        // Check cancellation at entry point
        cancellationToken.ThrowIfCancellationRequested();

        if (_metadataLoadContext == null || string.IsNullOrEmpty(assemblyPath) || !File.Exists(assemblyPath)) {
            if (string.IsNullOrEmpty(assemblyPath) || !File.Exists(assemblyPath)) {
                _logger.LogTrace("Assembly path is invalid or file does not exist, skipping for reflection cache: {Path}", assemblyPath);
            }
            return;
        }
        try {
            var assembly = _metadataLoadContext.LoadFromAssemblyPath(assemblyPath);

            // For large assemblies, check cancellation periodically during type collection
            // We can't check during GetTypes() directly since it's atomic, but we can after
            cancellationToken.ThrowIfCancellationRequested();

            var types = assembly.GetTypes();
            int processedTypes = 0;
            const int typeCheckInterval = 50; // Check cancellation every 50 types

            foreach (var type in types) {
                // Check cancellation periodically when processing many types
                if (++processedTypes % typeCheckInterval == 0) {
                    cancellationToken.ThrowIfCancellationRequested();
                }

                if (type?.FullName != null && !_allLoadedReflectionTypesCache.ContainsKey(type.FullName)) {
                    _allLoadedReflectionTypesCache.TryAdd(type.FullName, type);
                    typesCachedCount++;
                }
            }
        } catch (ReflectionTypeLoadException rtlex) {
            _logger.LogWarning("Could not load all types from assembly {Path} for reflection cache. LoaderExceptions: {Count}", assemblyPath, rtlex.LoaderExceptions.Length);
            foreach (var loaderEx in rtlex.LoaderExceptions.Where(e => e != null)) {
                _logger.LogTrace("LoaderException: {Message}", loaderEx!.Message);
            }

            // For partial load errors, still process the types that did load
            int processedTypes = 0;
            const int typeCheckInterval = 20; // Check cancellation more frequently when dealing with problematic assemblies

            foreach (var type in rtlex.Types.Where(t => t != null)) {
                // Check cancellation periodically
                if (++processedTypes % typeCheckInterval == 0) {
                    cancellationToken.ThrowIfCancellationRequested();
                }

                if (type!.FullName != null && !_allLoadedReflectionTypesCache.ContainsKey(type.FullName)) {
                    _allLoadedReflectionTypesCache.TryAdd(type.FullName, type);
                    typesCachedCount++;
                }
            }
        } catch (FileNotFoundException) { // Should be rare due to File.Exists check, but MLC might have its own resolution logic
            _logger.LogTrace("Assembly file not found by MetadataLoadContext: {Path}", assemblyPath);
        } catch (BadImageFormatException) {
            _logger.LogTrace("Bad image format for assembly file: {Path}", assemblyPath);
        } catch (Exception ex) {
            _logger.LogWarning(ex, "Error loading types from assembly {Path} for reflection cache.", assemblyPath);
        }
    }
    public void UnloadSolution() {
        _logger.LogInformation("Unloading current solution and workspace.");
        _compilationCache.Clear();
        _semanticModelCache.Clear();
        _allLoadedReflectionTypesCache.Clear();
        if (_workspace != null) {
            _workspace.WorkspaceFailed -= OnWorkspaceFailed;
            _workspace.CloseSolution();
            _workspace.Dispose();
            _workspace = null;
        }
        _metadataLoadContext?.Dispose();
        _metadataLoadContext = null;
        _pathAssemblyResolver = null; // PathAssemblyResolver doesn't implement IDisposable
        _assemblyPathsForReflection.Clear();
    }
    public void RefreshCurrentSolution() {
        if (_workspace == null) {
            _logger.LogWarning("Cannot refresh solution: Workspace is null.");
            return;
        }
        if (_workspace.CurrentSolution == null) {
            _logger.LogWarning("Cannot refresh solution: No solution loaded.");
            return;
        }
        _currentSolution = _workspace.CurrentSolution;
        _compilationCache.Clear();
        _semanticModelCache.Clear();
        _logger.LogDebug("Current solution state has been refreshed from workspace.");
    }
    public async Task ReloadSolutionFromDiskAsync(CancellationToken cancellationToken) {
        if (_workspace == null) {
            _logger.LogWarning("Cannot reload solution: Workspace is null.");
            return;
        }
        if (_workspace.CurrentSolution == null) {
            _logger.LogWarning("Cannot reload solution: No solution loaded.");
            return;
        }
        await LoadSolutionAsync(_workspace.CurrentSolution.FilePath!, cancellationToken);
        _logger.LogDebug("Current solution state has been refreshed from workspace.");
    }
    private void OnWorkspaceFailed(object? sender, WorkspaceDiagnosticEventArgs e) {
        var diagnostic = e.Diagnostic;
        var level = diagnostic.Kind == WorkspaceDiagnosticKind.Failure ? LogLevel.Error : LogLevel.Warning;
        _logger.Log(level, "Workspace diagnostic ({Kind}): {Message}", diagnostic.Kind, diagnostic.Message);
    }
    public async Task<INamedTypeSymbol?> FindRoslynNamedTypeSymbolAsync(string fullyQualifiedTypeName, CancellationToken cancellationToken) {
        if (!IsSolutionLoaded) {
            _logger.LogWarning("Cannot find Roslyn symbol: No solution loaded.");
            return null;
        }
        // Check cancellation before starting lookup
        cancellationToken.ThrowIfCancellationRequested();
        // Use fuzzy FQN lookup service
        var matches = await _fuzzyFqnLookupService.FindMatchesAsync(fullyQualifiedTypeName, this, cancellationToken);
        var matchList = matches.Where(m => m.Symbol is INamedTypeSymbol).ToList();
        // Check cancellation after initial matching
        cancellationToken.ThrowIfCancellationRequested();
        if (matchList.Count == 1) {
            var match = matchList.First();
            _logger.LogDebug("Roslyn named type symbol found: {FullyQualifiedTypeName} (score: {Score}, reason: {Reason})",
            match.CanonicalFqn, match.Score, match.MatchReason);
            return (INamedTypeSymbol)match.Symbol;
        }
        if (matchList.Count > 1) {
            _logger.LogWarning("Multiple matches found for {FullyQualifiedTypeName}", fullyQualifiedTypeName);
            throw new McpException($"FQN was ambiguous, did you mean one of these?\n{string.Join("\n", matchList.Select(m => m.CanonicalFqn))}");
        }
        // Direct lookup as fallback
        foreach (var project in CurrentSolution.Projects) {
            // Check cancellation before each project
            cancellationToken.ThrowIfCancellationRequested();
            var compilation = await GetCompilationAsync(project.Id, cancellationToken);
            if (compilation == null) {
                continue;
            }
            var symbol = compilation.GetTypeByMetadataName(fullyQualifiedTypeName);
            if (symbol != null) {
                _logger.LogDebug("Roslyn named type symbol found via direct lookup: {FullyQualifiedTypeName} in project {ProjectName}",
                fullyQualifiedTypeName, project.Name);
                return symbol;
            }
        }
        // Check cancellation before nested type check
        cancellationToken.ThrowIfCancellationRequested();
        // Check for nested type with dot notation as last resort
        var lastDotIndex = fullyQualifiedTypeName.LastIndexOf('.');
        if (lastDotIndex > 0) {
            var parentTypeName = fullyQualifiedTypeName.Substring(0, lastDotIndex);
            var nestedTypeName = fullyQualifiedTypeName.Substring(lastDotIndex + 1);
            foreach (var project in CurrentSolution.Projects) {
                // Check cancellation before each project
                cancellationToken.ThrowIfCancellationRequested();
                var compilation = await GetCompilationAsync(project.Id, cancellationToken);
                if (compilation == null) {
                    continue;
                }
                var parentSymbol = compilation.GetTypeByMetadataName(parentTypeName);
                if (parentSymbol != null) {
                    // Check if there's a nested type with this name
                    var nestedType = parentSymbol.GetTypeMembers(nestedTypeName).FirstOrDefault();
                    if (nestedType != null) {
                        var correctName = $"{parentTypeName}+{nestedTypeName}";
                        _logger.LogWarning("Type not found: '{FullyQualifiedTypeName}'. This appears to be a nested type - use '{CorrectName}' instead (use + instead of . for nested types)",
                        fullyQualifiedTypeName, correctName);
                        throw new McpException(
                        $"Type not found: '{fullyQualifiedTypeName}'. This appears to be a nested type - use '{correctName}' instead (use + instead of . for nested types)");
                    }
                }
            }
        }
        _logger.LogDebug("Roslyn named type symbol not found: {FullyQualifiedTypeName}", fullyQualifiedTypeName);
        return null;
    }
    public async Task<ISymbol?> FindRoslynSymbolAsync(string fullyQualifiedName, CancellationToken cancellationToken) {
        if (!IsSolutionLoaded) {
            _logger.LogWarning("Cannot find Roslyn symbol: No solution loaded.");
            return null;
        }

        // Check cancellation before starting lookup
        cancellationToken.ThrowIfCancellationRequested();

        // Use fuzzy FQN lookup service
        var matches = await _fuzzyFqnLookupService.FindMatchesAsync(fullyQualifiedName, this, cancellationToken);
        var matchList = matches.ToList();

        // Check cancellation after initial matching
        cancellationToken.ThrowIfCancellationRequested();

        if (matchList.Count == 1) {
            var match = matchList.First();
            _logger.LogDebug("Roslyn symbol found: {FullyQualifiedName} (score: {Score}, reason: {Reason})",
            match.CanonicalFqn, match.Score, match.MatchReason);
            return match.Symbol;
        }

        if (matchList.Count > 1) {
            _logger.LogWarning("Multiple matches found for {FullyQualifiedName}", fullyQualifiedName);
            throw new McpException($"FQN was ambiguous, did you mean one of these?\n{string.Join("\n", matchList.Select(m => m.CanonicalFqn))}");
        }

        // Check cancellation before fallback lookup
        cancellationToken.ThrowIfCancellationRequested();

        // Fall back to type lookup
        var typeSymbol = await FindRoslynNamedTypeSymbolAsync(fullyQualifiedName, cancellationToken);
        if (typeSymbol != null) {
            return typeSymbol;
        }

        // Check cancellation before member lookup
        cancellationToken.ThrowIfCancellationRequested();

        // Check for member of a type as fallback
        var lastDotIndex = fullyQualifiedName.LastIndexOf('.');
        if (lastDotIndex > 0 && lastDotIndex < fullyQualifiedName.Length - 1) {
            var typeName = fullyQualifiedName.Substring(0, lastDotIndex);
            var memberName = fullyQualifiedName.Substring(lastDotIndex + 1);

            var parentTypeSymbol = await FindRoslynNamedTypeSymbolAsync(typeName, cancellationToken);
            if (parentTypeSymbol != null) {
                // Check cancellation before final lookup step
                cancellationToken.ThrowIfCancellationRequested();

                var members = parentTypeSymbol.GetMembers(memberName);
                if (members.Any()) {
                    // TODO: Handle overloads if necessary, for now, take the first.
                    var memberSymbol = members.First();
                    _logger.LogDebug("Roslyn member symbol found: {FullyQualifiedName}", fullyQualifiedName);
                    return memberSymbol;
                }
            }
        }

        _logger.LogDebug("Roslyn symbol not found: {FullyQualifiedName}", fullyQualifiedName);
        return null;
    }
    public Task<Type?> FindReflectionTypeAsync(string fullyQualifiedTypeName, CancellationToken cancellationToken) {
        // Check cancellation at the beginning of the method
        cancellationToken.ThrowIfCancellationRequested();

        if (_metadataLoadContext == null) {
            _logger.LogWarning("Cannot find reflection type: MetadataLoadContext not initialized.");
            return Task.FromResult<Type?>(null);
        }
        if (_allLoadedReflectionTypesCache.TryGetValue(fullyQualifiedTypeName, out var type)) {
            _logger.LogDebug("Reflection type found in cache: {Name}", fullyQualifiedTypeName);
            return Task.FromResult<Type?>(type);
        }
        _logger.LogDebug("Reflection type '{FullyQualifiedTypeName}' not found in cache. It might not exist in the loaded solution's dependencies or was not loadable.", fullyQualifiedTypeName);
        return Task.FromResult<Type?>(null);
    }
    public Task<IEnumerable<Type>> SearchReflectionTypesAsync(string regexPattern, CancellationToken cancellationToken) {
        // Check cancellation at the method entry point
        cancellationToken.ThrowIfCancellationRequested();

        if (_metadataLoadContext == null) {
            _logger.LogWarning("Cannot search reflection types: MetadataLoadContext not initialized.");
            return Task.FromResult(Enumerable.Empty<Type>());
        }
        if (!_allLoadedReflectionTypesCache.Any()) {
            _logger.LogInformation("Reflection type cache is empty. Search will yield no results.");
            return Task.FromResult(Enumerable.Empty<Type>());
        }

        // Check cancellation before regex compilation
        cancellationToken.ThrowIfCancellationRequested();

        var regex = new Regex(regexPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
        var matchedTypes = new List<Type>();

        // Consider batching in chunks to check cancellation more frequently on large type caches
        int processedCount = 0;
        const int batchSize = 100; // Check cancellation every 100 types

        foreach (var typeEntry in _allLoadedReflectionTypesCache) { // Iterate KeyValuePair to access FQN directly
            if (++processedCount % batchSize == 0) {
                cancellationToken.ThrowIfCancellationRequested();
            }

            // Key is type.FullName which should not be null for cached types
            if (regex.IsMatch(typeEntry.Key)) { // Search FQN
                matchedTypes.Add(typeEntry.Value);
            } else if (regex.IsMatch(typeEntry.Value.Name)) { // Search simple name
                matchedTypes.Add(typeEntry.Value);
            }
        }

        // Check cancellation before returning results
        cancellationToken.ThrowIfCancellationRequested();

        _logger.LogDebug("Found {Count} reflection types matching pattern '{Pattern}'.", matchedTypes.Count, regexPattern);
        return Task.FromResult<IEnumerable<Type>>(matchedTypes.Distinct());
    }
    public IEnumerable<Project> GetProjects() {
        return CurrentSolution?.Projects ?? Enumerable.Empty<Project>();
    }
    public Project? GetProjectByName(string projectName) {
        if (!IsSolutionLoaded) {
            _logger.LogWarning("Cannot get project by name: No solution loaded.");
            return null;
        }
        var project = CurrentSolution?.Projects.FirstOrDefault(p => p.Name.Equals(projectName, StringComparison.OrdinalIgnoreCase));
        if (project == null) {
            _logger.LogWarning("Project not found: {ProjectName}", projectName);
        }
        return project;
    }
    public async Task<SemanticModel?> GetSemanticModelAsync(DocumentId documentId, CancellationToken cancellationToken) {
        // Check cancellation at entry point
        cancellationToken.ThrowIfCancellationRequested();

        if (!IsSolutionLoaded) {
            _logger.LogWarning("Cannot get semantic model: No solution loaded.");
            return null;
        }

        // Fast path: check cache first
        if (_semanticModelCache.TryGetValue(documentId, out var cachedModel)) {
            _logger.LogTrace("Returning cached semantic model for document ID: {DocumentId}", documentId);
            return cachedModel;
        }

        // Check cancellation before document lookup
        cancellationToken.ThrowIfCancellationRequested();

        var document = CurrentSolution.GetDocument(documentId);
        if (document == null) {
            _logger.LogWarning("Document not found for ID: {DocumentId}", documentId);
            return null;
        }

        _logger.LogTrace("Requesting semantic model for document: {DocumentFilePath}", document.FilePath);

        // Check cancellation before expensive GetSemanticModelAsync call
        cancellationToken.ThrowIfCancellationRequested();

        var model = await document.GetSemanticModelAsync(cancellationToken);
        if (model != null) {
            _semanticModelCache.TryAdd(documentId, model);
        } else {
            _logger.LogWarning("Failed to get semantic model for document: {DocumentFilePath}", document.FilePath);
        }
        return model;
    }
    public async Task<Compilation?> GetCompilationAsync(ProjectId projectId, CancellationToken cancellationToken) {
        // Check cancellation at entry point
        cancellationToken.ThrowIfCancellationRequested();

        if (!IsSolutionLoaded) {
            _logger.LogWarning("Cannot get compilation: No solution loaded.");
            return null;
        }

        // Fast path: check cache first
        if (_compilationCache.TryGetValue(projectId, out var cachedCompilation)) {
            _logger.LogTrace("Returning cached compilation for project ID: {ProjectId}", projectId);
            return cachedCompilation;
        }

        // Check cancellation before project lookup
        cancellationToken.ThrowIfCancellationRequested();

        var project = CurrentSolution.GetProject(projectId);
        if (project == null) {
            _logger.LogWarning("Project not found for ID: {ProjectId}", projectId);
            return null;
        }

        _logger.LogTrace("Requesting compilation for project: {ProjectName}", project.Name);

        // Check cancellation before expensive GetCompilationAsync call
        cancellationToken.ThrowIfCancellationRequested();

        var compilation = await project.GetCompilationAsync(cancellationToken);
        if (compilation != null) {
            _compilationCache.TryAdd(projectId, compilation);
        } else {
            _logger.LogWarning("Failed to get compilation for project: {ProjectName}", project.Name);
        }
        return compilation;
    }
    public void Dispose() {
        UnloadSolution();
        GC.SuppressFinalize(this);
    }
    private class ProgressReporter : IProgress<ProjectLoadProgress> {
        private readonly Microsoft.Extensions.Logging.ILogger _logger;
        public ProgressReporter(Microsoft.Extensions.Logging.ILogger logger) {
            _logger = logger;
        }
        public void Report(ProjectLoadProgress loadProgress) {
            var projectDisplay = Path.GetFileName(loadProgress.FilePath);
            _logger.LogTrace("Project Load Progress: {ProjectDisplayName}, Operation: {Operation}, Time: {TimeElapsed}",
            projectDisplay, loadProgress.Operation, loadProgress.ElapsedTime);
        }
    }
    private HashSet<string> GetNuGetAssemblyPaths(Solution solution, CancellationToken cancellationToken = default) {
        var nugetAssemblyPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        var nugetCacheDir = GetNuGetGlobalPackagesFolder();

        if (string.IsNullOrEmpty(nugetCacheDir) || !Directory.Exists(nugetCacheDir)) {
            _logger.LogWarning("NuGet global packages folder not found or inaccessible: {NuGetCacheDir}", nugetCacheDir);
            return nugetAssemblyPaths;
        }

        foreach (var project in solution.Projects) {
            cancellationToken.ThrowIfCancellationRequested();

            if (string.IsNullOrEmpty(project.FilePath)) {
                continue;
            }

            var packageReferences = LegacyNuGetPackageReader.GetAllPackageReferences(project.FilePath);
            var projectTargetFramework = SolutionTools.ExtractTargetFrameworkFromProjectFile(project.FilePath);

            foreach (var package in packageReferences) {
                cancellationToken.ThrowIfCancellationRequested();

                var packageDir = Path.Combine(nugetCacheDir, package.PackageId.ToLowerInvariant(), package.Version);
                if (!Directory.Exists(packageDir)) {
                    _logger.LogTrace("Package directory not found: {PackageDir}", packageDir);
                    continue;
                }

                var libDir = Path.Combine(packageDir, "lib");
                if (!Directory.Exists(libDir)) {
                    _logger.LogTrace("No lib directory found for package {PackageId} {Version}", package.PackageId, package.Version);
                    continue;
                }

                // Find assemblies using the project's target framework
                var assemblyPaths = GetAssembliesForTargetFramework(libDir, package.TargetFramework ?? projectTargetFramework, package.PackageId, package.Version);
                foreach (var assemblyPath in assemblyPaths) {
                    nugetAssemblyPaths.Add(assemblyPath);
                }
            }
        }

        _logger.LogInformation("Found {AssemblyCount} NuGet assemblies from global packages cache", nugetAssemblyPaths.Count);
        return nugetAssemblyPaths;
    }
    private static string GetNuGetGlobalPackagesFolder() {
        // Check environment variable first
        var globalPackagesPath = Environment.GetEnvironmentVariable("NUGET_PACKAGES");
        if (!string.IsNullOrEmpty(globalPackagesPath)) {
            return globalPackagesPath;
        }

        // Default location based on OS
        var userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
        return Path.Combine(userProfile, ".nuget", "packages");
    }
    private List<string> GetAssembliesForTargetFramework(string libDir, string targetFramework, string packageId, string version) {
        var assemblies = new List<string>();

        if (!Directory.Exists(libDir)) {
            return assemblies;
        }

        // First try exact target framework match
        var exactFrameworkDir = Path.Combine(libDir, targetFramework);
        if (Directory.Exists(exactFrameworkDir)) {
            var exactAssemblies = Directory.GetFiles(exactFrameworkDir, "*.dll", SearchOption.TopDirectoryOnly);
            assemblies.AddRange(exactAssemblies);
            _logger.LogTrace("Found {AssemblyCount} assemblies in exact framework match {Framework} for {PackageId} {Version}",
                exactAssemblies.Length, targetFramework, packageId, version);
            return assemblies;
        }

        // Try compatible frameworks in order of preference
        var compatibleFrameworks = GetCompatibleFrameworks(targetFramework);

        foreach (var framework in compatibleFrameworks) {
            var frameworkDir = Path.Combine(libDir, framework);
            if (Directory.Exists(frameworkDir)) {
                var frameworkAssemblies = Directory.GetFiles(frameworkDir, "*.dll", SearchOption.TopDirectoryOnly);
                assemblies.AddRange(frameworkAssemblies);
                _logger.LogTrace("Found {AssemblyCount} assemblies in compatible framework {Framework} for {PackageId} {Version}",
                    frameworkAssemblies.Length, framework, packageId, version);
                return assemblies; // Take the first compatible framework found
            }
        }

        // Fallback: check if there are any DLLs directly in lib directory
        if (assemblies.Count == 0) {
            var libAssemblies = Directory.GetFiles(libDir, "*.dll", SearchOption.TopDirectoryOnly);
            assemblies.AddRange(libAssemblies);
            if (libAssemblies.Length > 0) {
                _logger.LogTrace("Found {AssemblyCount} assemblies in lib root for {PackageId} {Version}",
                    libAssemblies.Length, packageId, version);
            }
        }

        return assemblies;
    }
    private static string[] GetCompatibleFrameworks(string targetFramework) {
        // Return frameworks in order of compatibility preference
        return targetFramework.ToLowerInvariant() switch {
            "net10.0" => new[] { "net10.0", "net9.0", "net8.0", "net7.0", "net6.0", "net5.0", "netcoreapp3.1", "netcoreapp3.0", "netcoreapp2.1", "netstandard2.1", "netstandard2.0", "netstandard1.6" },
            "net9.0" => new[] { "net9.0", "net8.0", "net7.0", "net6.0", "net5.0", "netcoreapp3.1", "netcoreapp3.0", "netcoreapp2.1", "netstandard2.1", "netstandard2.0", "netstandard1.6" },
            "net8.0" => new[] { "net8.0", "net7.0", "net6.0", "net5.0", "netcoreapp3.1", "netcoreapp3.0", "netcoreapp2.1", "netstandard2.1", "netstandard2.0", "netstandard1.6" },
            "net7.0" => new[] { "net7.0", "net6.0", "net5.0", "netcoreapp3.1", "netcoreapp3.0", "netcoreapp2.1", "netstandard2.1", "netstandard2.0", "netstandard1.6" },
            "net6.0" => new[] { "net6.0", "net5.0", "netcoreapp3.1", "netcoreapp3.0", "netcoreapp2.1", "netstandard2.1", "netstandard2.0", "netstandard1.6" },
            "net5.0" => new[] { "net5.0", "netcoreapp3.1", "netcoreapp3.0", "netcoreapp2.1", "netstandard2.1", "netstandard2.0", "netstandard1.6" },
            "netcoreapp3.1" => new[] { "netcoreapp3.1", "netcoreapp3.0", "netcoreapp2.1", "netstandard2.1", "netstandard2.0", "netstandard1.6" },
            "netcoreapp3.0" => new[] { "netcoreapp3.0", "netcoreapp2.1", "netstandard2.1", "netstandard2.0", "netstandard1.6" },
            "netcoreapp2.1" => new[] { "netcoreapp2.1", "netstandard2.0", "netstandard1.6" },
            "netstandard2.1" => new[] { "netstandard2.1", "netstandard2.0", "netstandard1.6" },
            "netstandard2.0" => new[] { "netstandard2.0", "netstandard1.6" },
            _ => new[] { "net8.0", "net7.0", "net6.0", "net5.0", "netcoreapp3.1", "netcoreapp3.0", "netcoreapp2.1", "netstandard2.1", "netstandard2.0", "netstandard1.6" }
        };
    }
}
```

--------------------------------------------------------------------------------
/SharpTools.Tools/Services/FuzzyFqnLookupService.cs:
--------------------------------------------------------------------------------

```csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using SharpTools.Tools.Interfaces;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using SharpTools.Tools.Mcp;

namespace SharpTools.Tools.Services {
    public class FuzzyFqnLookupService : IFuzzyFqnLookupService {
        private readonly ILogger<FuzzyFqnLookupService> _logger;
        private ISolutionManager? _solutionManager;

        // Match scoring constants - higher score is better
        private const double PerfectMatchScore = 1.0;
        private const double ConstructorShorthandTypeNameMatchScore = 0.98; // User typed "Namespace.Type" for constructor
        private const double ConstructorShorthandFullMatchScore = 0.97;   // User typed "Namespace.Type.Type" for constructor
        private const double ParamsOmittedScore = 0.9;
        private const double ArityOmittedScore = 0.85;
        private const double GenericArgsOmittedScore = 0.80;
        private const double NestedTypeDotForPlusScore = 0.75;
        private const double CaseInsensitiveMatchScore = 0.99;
        private const double ParametersContentMismatchScore = 0.7; // Base name matches, but params differ
        private const double MinScoreThreshold = 0.7; // Minimum score to be considered a match

        // Regex patterns for normalization
        private static readonly Regex ParamsRegex = new(@"\s*\([\s\S]*\)\s*$", RegexOptions.Compiled);
        private static readonly Regex ArityRegex = new(@"`\d+", RegexOptions.Compiled);
        private static readonly Regex GenericArgsRegex = new(@"<[^<>]+>", RegexOptions.Compiled); // Simplistic: removes <...>

        public FuzzyFqnLookupService(ILogger<FuzzyFqnLookupService> logger) {
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }
        public static bool IsPartialType(ISymbol typeSymbol) {
            if (typeSymbol is not INamedTypeSymbol namedTypeSymbol) {
                return false; // Not a type symbol
            }

            return typeSymbol.DeclaringSyntaxReferences.Length > 1 ||
                typeSymbol.DeclaringSyntaxReferences.Any(syntax =>
                    syntax.GetSyntax() is Microsoft.CodeAnalysis.CSharp.Syntax.BaseTypeDeclarationSyntax declaration &&
                    declaration.Modifiers.Any(modifier =>
                        modifier.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.PartialKeyword)));
        }
        /// <inheritdoc />
        public async Task<IEnumerable<FuzzyMatchResult>> FindMatchesAsync(string fuzzyFqnInput, ISolutionManager solutionManager, CancellationToken cancellationToken) {
            _solutionManager = solutionManager;
            if (!solutionManager.IsSolutionLoaded) {
                _logger.LogWarning("Cannot perform fuzzy FQN lookup: No solution loaded.");
                return Enumerable.Empty<FuzzyMatchResult>();
            }

            var potentialMatches = new List<FuzzyMatchResult>();
            string trimmedFuzzyFqn = fuzzyFqnInput.Trim().Replace(" ", string.Empty);
            var allRelevantSymbols = new HashSet<ISymbol>(SymbolEqualityComparer.Default);

            // Process each document in the solution to collect symbols
            foreach (var project in solutionManager.CurrentSolution.Projects) {
                // Check cancellation before starting work on each project
                cancellationToken.ThrowIfCancellationRequested();

                foreach (var document in project.Documents) {
                    // Check cancellation before starting work on each document
                    cancellationToken.ThrowIfCancellationRequested();

                    var semanticModel = await solutionManager.GetSemanticModelAsync(document.Id, cancellationToken);
                    if (semanticModel == null) {
                        _logger.LogWarning("Could not get semantic model for document {DocumentPath}", document.FilePath);
                        continue;
                    }

                    // Get all symbols from the semantic model
                    CollectSymbolsFromSemanticModel(semanticModel, allRelevantSymbols, cancellationToken);
                }
            }
            _logger.LogDebug("Collected {SymbolCount} symbols from solution documents", allRelevantSymbols.Count);
            int prefilter = allRelevantSymbols.Count;

            //Remove all duplicates, no idea why this is needed. //TODO
            var partialTypes = allRelevantSymbols
                //.OfType<INamedTypeSymbol>()
                //.Where(IsPartialType)
                .GroupBy(GetSearchableString)
                .ToList();

            allRelevantSymbols = new HashSet<ISymbol>(allRelevantSymbols.Except(partialTypes.SelectMany(g => g.Skip(1))), SymbolEqualityComparer.Default); // Remove all but one from each group



            _logger.LogDebug("Filtered {PrefilterCount} symbols to {SymbolCount} after removing duplicates", prefilter, allRelevantSymbols.Count);

            // Match symbols against the fuzzy FQN
            foreach (var symbol in allRelevantSymbols) {
                // Check cancellation periodically during symbol processing
                cancellationToken.ThrowIfCancellationRequested();

                //string canonicalFqn = symbol.ToDisplayString(ToolHelpers.FullyQualifiedFormatWithoutGlobal);
                string canonicalFqn = GetSearchableString(symbol);
                //_logger.LogDebug("Checking symbol: {SymbolName} with FQN: {CanonicalFqn}", symbol.Name, canonicalFqn);
                var (score, reason) = CalculateMatchScore(trimmedFuzzyFqn, symbol, canonicalFqn, cancellationToken);

                if (score >= MinScoreThreshold) {
                    potentialMatches.Add(new FuzzyMatchResult(canonicalFqn, symbol, score, reason));
                }
            }

            // Check cancellation before sorting results
            cancellationToken.ThrowIfCancellationRequested();

            // Sort by score descending, then by FQN alphabetically for stable results
            var results = potentialMatches
            .OrderByDescending(m => m.Score)
            .ThenBy(m => m.CanonicalFqn)
            .ToList();

            _logger.LogDebug("Found {MatchCount} matches for fuzzy FQN '{FuzzyFqn}'", results.Count, fuzzyFqnInput);

            // If multiple matches are found, but one is perfect, filter to that one
            if (results.Count > 1) {
                var perfectMatches = results.Where(m => m.Score >= PerfectMatchScore - 0.01).ToList();
                if (perfectMatches.Count == 1) {
                    _logger.LogDebug("Filtered to single perfect match for '{FuzzyFqn}'", fuzzyFqnInput);
                    return perfectMatches;
                }
            }

            // Log ambiguity details when multiple high-scoring matches are found
            await LogAmbiguityDetailsAsync(fuzzyFqnInput, results, cancellationToken);

            return results;
        }
        private void CollectSymbolsFromSemanticModel(SemanticModel semanticModel, HashSet<ISymbol> collectedSymbols, CancellationToken cancellationToken) {
            // Get the global namespace from the compilation
            var compilation = semanticModel.Compilation;

            // Collect from global namespace
            //CollectSymbols(compilation.GlobalNamespace, collectedSymbols, cancellationToken);

            // Also collect any declarations from the syntax tree of this document
            var root = semanticModel.SyntaxTree.GetRoot(cancellationToken);
            foreach (var node in root
                .DescendantNodes(descendIntoChildren: n => n is MemberDeclarationSyntax or TypeDeclarationSyntax or NamespaceDeclarationSyntax or CompilationUnitSyntax)
                .Where(n => n is MemberDeclarationSyntax or TypeDeclarationSyntax or NamespaceDeclarationSyntax)) {
                // Check cancellation periodically during node traversal
                cancellationToken.ThrowIfCancellationRequested();
                var declaredSymbol = semanticModel.GetDeclaredSymbol(node, cancellationToken);
                if (declaredSymbol != null) {
                    collectedSymbols.Add(declaredSymbol);
                }
            }
        }

        private void CollectSymbols(INamespaceOrTypeSymbol containerSymbol, HashSet<ISymbol> collectedSymbols, CancellationToken cancellationToken) {
            // Check cancellation at the beginning of recursive operations
            cancellationToken.ThrowIfCancellationRequested();

            if (containerSymbol is INamedTypeSymbol typeSymbol) {
                // Add the type itself
                collectedSymbols.Add(typeSymbol);
            }

            foreach (var member in containerSymbol.GetMembers()) {
                // Check cancellation periodically during member processing
                cancellationToken.ThrowIfCancellationRequested();

                switch (member.Kind) {
                    case SymbolKind.Namespace:
                        CollectSymbols((INamespaceSymbol)member, collectedSymbols, cancellationToken);
                        break;
                    case SymbolKind.NamedType:
                        CollectSymbols((INamedTypeSymbol)member, collectedSymbols, cancellationToken); // Recurse for nested types
                        break;
                    case SymbolKind.Method:
                        // Includes constructors, operators, accessors, destructors
                        collectedSymbols.Add(member);
                        break;
                    case SymbolKind.Property:
                        var prop = (IPropertySymbol)member;
                        collectedSymbols.Add(prop); // Add property itself for FQNs like Namespace.Type.Property
                        if (prop.GetMethod != null) collectedSymbols.Add(prop.GetMethod);
                        if (prop.SetMethod != null) collectedSymbols.Add(prop.SetMethod);
                        break;
                    case SymbolKind.Event:
                        var evt = (IEventSymbol)member;
                        collectedSymbols.Add(evt); // Add event itself for FQNs like Namespace.Type.Event
                        if (evt.AddMethod != null) collectedSymbols.Add(evt.AddMethod);
                        if (evt.RemoveMethod != null) collectedSymbols.Add(evt.RemoveMethod);
                        break;
                    case SymbolKind.Field:
                        collectedSymbols.Add(member);
                        break;
                }
            }
        }

        private (double score, string reason) CalculateMatchScore(string userInputFqn, ISymbol symbol, string canonicalFqn, CancellationToken cancellationToken) {
            // Periodically check cancellation during the scoring process
            cancellationToken.ThrowIfCancellationRequested();

            // 0. Direct case-sensitive match
            if (userInputFqn.Equals(canonicalFqn, StringComparison.Ordinal)) {
                return (PerfectMatchScore, "Exact match");
            }

            // 0.1. Direct case-insensitive match
            if (userInputFqn.Equals(canonicalFqn, StringComparison.OrdinalIgnoreCase)) {
                return (CaseInsensitiveMatchScore, "Case-insensitive exact match");
            }

            // Prepare normalized versions for comparison
            string userInputNoParams = RemoveParameters(userInputFqn);
            string canonicalFqnNoParams = RemoveParameters(canonicalFqn);

            // Normalize generic arguments - keep the angle brackets but normalize contents
            // For example, "List<int>" and "List<T>" might be considered equivalent in some cases
            string userInputWithNormalizedGenerics = NormalizeGenericArgs(userInputNoParams);
            string canonicalFqnWithNormalizedGenerics = NormalizeGenericArgs(canonicalFqnNoParams);

            // Check cancellation after regex processing
            cancellationToken.ThrowIfCancellationRequested();

            // 1. Constructor shorthands
            if (symbol is IMethodSymbol methodSymbol &&
                (methodSymbol.MethodKind == MethodKind.Constructor || methodSymbol.MethodKind == MethodKind.StaticConstructor)) {

                // Get containing type name with proper generic arguments
                string typeFullName = GetSearchableString(methodSymbol.ContainingType);

                // User typed "Namespace.Type.Type" or "Namespace.Type.Type()"
                if (userInputNoParams.Equals(typeFullName + "." + methodSymbol.ContainingType.Name, StringComparison.OrdinalIgnoreCase)) {
                    return (ConstructorShorthandFullMatchScore, "Constructor shorthand (Type.Type)");
                }

                // User typed "Namespace.Type" or "Namespace.Type()"
                if (userInputNoParams.Equals(typeFullName, StringComparison.OrdinalIgnoreCase)) {
                    return (ConstructorShorthandTypeNameMatchScore, "Constructor shorthand (Type)");
                }
            }

            // Check cancellation before the next set of comparisons
            cancellationToken.ThrowIfCancellationRequested();

            // 2. Match after removing parameters (user omitted them or canonical was parameterless)
            if (userInputNoParams.Equals(canonicalFqnNoParams, StringComparison.OrdinalIgnoreCase)) {
                bool userInputHadParams = userInputFqn.Length != userInputNoParams.Length;
                bool canonicalHadParams = canonicalFqn.Length != canonicalFqnNoParams.Length;

                if (userInputHadParams && canonicalHadParams) { // Both had params, but content differed (caught by initial exact match if same)
                    return (ParametersContentMismatchScore, "Parameter content mismatch");
                }

                return (ParamsOmittedScore, "Parameter list omitted/matched empty");
            }

            // 3. Match with normalized generic arguments
            if (userInputWithNormalizedGenerics.Equals(canonicalFqnWithNormalizedGenerics, StringComparison.OrdinalIgnoreCase)) {
                return (0.95, "Generic arguments normalized match");
            }

            // 4. Match with generic arguments stripped (user might search for base name)
            string userInputNoGenerics = StripGenericArgs(userInputNoParams);
            string canonicalFqnNoGenerics = StripGenericArgs(canonicalFqnNoParams);

            if (userInputNoGenerics.Equals(canonicalFqnNoGenerics, StringComparison.OrdinalIgnoreCase)) {
                // If the user actually included generic args, but we had to strip them to match, 
                // it could mean their generic args don't match the canonical ones
                bool userHadGenericArgs = userInputNoParams.Contains("<") && userInputNoParams != userInputNoGenerics;
                bool canonicalHadGenericArgs = canonicalFqnNoParams.Contains("<") && canonicalFqnNoParams != canonicalFqnNoGenerics;

                if (userHadGenericArgs && canonicalHadGenericArgs) {
                    // Both had generic args but they didn't match exactly
                    return (GenericArgsOmittedScore, "Generic arguments content mismatch");
                }

                return (ArityOmittedScore, "Generic arguments omitted/matched empty");
            }

            // 5. Nested Type: User used '.' where canonical FQN uses '+'
            // This should be less relevant now that we normalize '+' to '.' in GetSearchableString,
            // but kept for backward compatibility
            string? userNestedFixed = TryFixNestedTypeSeparator(userInputNoGenerics, canonicalFqnNoGenerics, cancellationToken);
            if (userNestedFixed != null && userNestedFixed.Equals(canonicalFqnNoGenerics, StringComparison.OrdinalIgnoreCase)) {
                return (NestedTypeDotForPlusScore, "Nested type separator '.' used for '+'");
            }

            return (0.0, "No significant match");
        }
        private string RemoveParameters(string fqn) {
            // A more robust way to find the first '(':
            int openParenIndex = fqn.IndexOf('(');
            if (openParenIndex != -1) {
                // Check if it's part of a generic type argument list like `Method(List<string>)`
                // or a method generic parameter list like `Method<T>(T p)`
                // SymbolDisplayFormat.FullyQualifiedFormat puts method type parameters like `Method``1`
                // and parameters like `(System.Int32)`.
                // So, `(` should reliably indicate start of parameter list for methods.
                // For delegates, it might be `System.Action()`
                string result = fqn.Substring(0, openParenIndex);
                return result;
            }
            return fqn;
        }

        private string? TryFixNestedTypeSeparator(string userInputFqnPart, string canonicalFqnPart, CancellationToken cancellationToken) {
            // Check cancellation at the beginning of processing
            cancellationToken.ThrowIfCancellationRequested();

            // This heuristic attempts to replace '.' with '+' in the type path part of the FQN.
            // It assumes member names (after the last type segment) don't contain '.' or '+'.
            // Example: User "N.O.N.M", Canonical "N.O+N.M"
            // We need to identify "N.O.N" vs "N.O+N" and "M" vs "M"
            int userLastDot = userInputFqnPart.LastIndexOf('.');
            int canonicalLastPlusOrDot = Math.Max(canonicalFqnPart.LastIndexOf('+'), canonicalFqnPart.LastIndexOf('.'));

            // If no dot/plus, it might be a global type or just a member name if type path was empty.
            string userTypePath = userLastDot > -1 ? userInputFqnPart.Substring(0, userLastDot) : "";
            string userMemberName = userLastDot > -1 ? userInputFqnPart.Substring(userLastDot) : userInputFqnPart; // Member name includes the leading '.' if present
            string canonicalTypePath = canonicalLastPlusOrDot > -1 ? canonicalFqnPart.Substring(0, canonicalLastPlusOrDot) : "";
            string canonicalMemberName = canonicalLastPlusOrDot > -1 ? canonicalFqnPart.Substring(canonicalLastPlusOrDot) : canonicalFqnPart;

            if (userMemberName.Equals(canonicalMemberName, StringComparison.OrdinalIgnoreCase)) {
                if (userTypePath.Replace('.', '+').Equals(canonicalTypePath, StringComparison.OrdinalIgnoreCase)) {
                    string result = canonicalTypePath + userMemberName;
                    return result; // Return the "fixed" version based on canonical structure
                }
            }
            // Special case: if the whole thing is a type name (no distinct member part)
            else if (string.IsNullOrEmpty(userMemberName) && string.IsNullOrEmpty(canonicalMemberName) || userLastDot == -1 && canonicalLastPlusOrDot == -1) {
                if (userInputFqnPart.Replace('.', '+').Equals(canonicalFqnPart, StringComparison.OrdinalIgnoreCase)) {
                    return canonicalFqnPart;
                }
            }

            return null; // No simple fix found
        }

        public static string GetSearchableString(ISymbol symbol) {
            var fullFormat = new SymbolDisplayFormat(
                globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted,
                typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
                genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters | SymbolDisplayGenericsOptions.IncludeVariance,
                memberOptions: SymbolDisplayMemberOptions.IncludeParameters |
                    SymbolDisplayMemberOptions.IncludeType |
                    SymbolDisplayMemberOptions.IncludeRef |
                    SymbolDisplayMemberOptions.IncludeContainingType,
                parameterOptions: SymbolDisplayParameterOptions.IncludeType |
                    SymbolDisplayParameterOptions.IncludeName |
                    SymbolDisplayParameterOptions.IncludeParamsRefOut |
                    SymbolDisplayParameterOptions.IncludeDefaultValue,
                propertyStyle: SymbolDisplayPropertyStyle.NameOnly,
                localOptions: SymbolDisplayLocalOptions.None,
                miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes |
                    SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers);

            // For parameters and return types, use a format that doesn't qualify types
            var shortFormat = new SymbolDisplayFormat(
                globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted,
                typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameOnly,
                genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
                memberOptions: SymbolDisplayMemberOptions.IncludeParameters |
                    SymbolDisplayMemberOptions.IncludeType,
                parameterOptions: SymbolDisplayParameterOptions.IncludeType,
                //SymbolDisplayParameterOptions.IncludeName |
                //SymbolDisplayParameterOptions.IncludeParamsRefOut |
                //SymbolDisplayParameterOptions.IncludeDefaultValue,
                propertyStyle: SymbolDisplayPropertyStyle.NameOnly,
                localOptions: SymbolDisplayLocalOptions.None,
                miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes |
                    SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier);

            var fqn = new SymbolDisplayFormat(
                globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.OmittedAsContaining,
                typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
                genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
                memberOptions: SymbolDisplayMemberOptions.IncludeContainingType,
                parameterOptions: SymbolDisplayParameterOptions.None,
                propertyStyle: SymbolDisplayPropertyStyle.NameOnly,
                miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes);

            if (symbol is IMethodSymbol methodSymbol) {
                // Format the method name and containing type with full qualification
                var methodNameAndType = methodSymbol.ToDisplayString(fqn);

                // Find the indices where return type ends and method name begins
                //var returnTypeEndIndex = methodParts.TakeWhile(p => p.Kind != SymbolDisplayPartKind.MethodName).Count();
                //var nameStartIndex = returnTypeEndIndex;
                //var nameEndIndex = methodParts.TakeWhile(p => p.Kind != SymbolDisplayPartKind.Punctuation && p.ToString() != "(").Count();

                // Get the parts we need
                //var modifiers = methodParts.Take(methodParts.TakeWhile(p => p.Kind == SymbolDisplayPartKind.Keyword).Count());
                //var returnType = methodSymbol.ReturnType.ToDisplayString(shortFormat);
                //var nameAndContainingType = string.Concat(methodParts.Take(nameEndIndex));

                // Get param types only
                var parameters = string.Join(", ", methodSymbol.Parameters
                    .Select(p => string.Concat(p.ToDisplayParts(shortFormat)
                        .TakeWhile(part => part.Kind != SymbolDisplayPartKind.ParameterName)
                        .Select(part => part.ToString()))));

                // Combine all parts
                var signature = methodNameAndType + "(" + parameters + ")";
                return signature.Replace(" ", string.Empty);
            }

            // For non-method symbols, use the original full format
            return symbol.ToDisplayString(fqn).Replace(" ", string.Empty);
        }
        private async Task LogAmbiguityDetailsAsync(string fuzzyFqnInput, List<FuzzyMatchResult> results, CancellationToken cancellationToken) {
            const double HighScoreThreshold = 0.8; // Threshold for considering a match "high-scoring"
            const int MaxDetailedLogsPerAmbiguity = 10; // Limit detailed logs to prevent spam

            // Group matches by score ranges to identify ambiguity patterns
            var highScoreMatches = results.Where(r => r.Score >= HighScoreThreshold).ToList();
            var perfectMatches = results.Where(r => r.Score >= PerfectMatchScore - 0.01).ToList();

            // Log ambiguity when we have multiple high-scoring matches
            if (highScoreMatches.Count > 1) {
                _logger.LogWarning("Ambiguity detected for input '{FuzzyFqn}': Found {HighScoreCount} high-scoring matches (>= {Threshold})",
                fuzzyFqnInput, highScoreMatches.Count, HighScoreThreshold);

                // Group by project to understand cross-project ambiguity
                var matchesByProject = highScoreMatches
                .GroupBy(match => GetProjectName(match.Symbol))
                .ToList();

                if (matchesByProject.Count > 1) {
                    _logger.LogWarning("Cross-project ambiguity detected: Matches span {ProjectCount} projects", matchesByProject.Count);
                    foreach (var projectGroup in matchesByProject) {
                        _logger.LogInformation("Project '{ProjectName}' has {MatchCount} ambiguous matches",
                        projectGroup.Key, projectGroup.Count());
                    }
                }

                // Log detailed information for the top matches
                var detailedMatches = highScoreMatches.Take(MaxDetailedLogsPerAmbiguity).ToList();
                for (int i = 0; i < detailedMatches.Count; i++) {
                    var match = detailedMatches[i];
                    LogDetailedMatchInfoAsync(match, i + 1, cancellationToken);
                }

                // Log summary statistics about the ambiguity
                LogAmbiguitySummary(fuzzyFqnInput, results, highScoreMatches, perfectMatches);
            }
        }
        private void LogDetailedMatchInfoAsync(FuzzyMatchResult match, int rank, CancellationToken cancellationToken) {
            try {
                var symbol = match.Symbol;
                var projectName = GetProjectName(symbol);
                var symbolKind = GetSymbolKindString(symbol);
                var containingType = GetContainingTypeInfo(symbol);
                var assemblyName = GetAssemblyName(symbol);
                var location = GetLocationInfo(symbol);
                var formattedSignature = CodeAnalysisService.GetFormattedSignatureAsync(symbol, false);

                _logger.LogWarning("Ambiguous Match #{Rank}: Score={Score:F3}, Reason='{Reason}'", rank, match.Score, match.MatchReason);
                _logger.LogInformation("  Symbol Details:");
                _logger.LogInformation("    FQN: {CanonicalFqn}", match.CanonicalFqn);
                _logger.LogInformation("    Formatted Signature: {FormattedSignature}", formattedSignature);
                _logger.LogInformation("    Symbol Kind: {SymbolKind}", symbolKind);
                _logger.LogInformation("    Symbol Name: {SymbolName}", symbol.Name);
                _logger.LogInformation("    Project: {ProjectName}", projectName);
                _logger.LogInformation("    Assembly: {AssemblyName}", assemblyName);
                _logger.LogInformation("    Location: {Location}", location);

                if (!string.IsNullOrEmpty(containingType)) {
                    _logger.LogInformation("    Containing Type: {ContainingType}", containingType);
                }

                // Log additional type-specific information
                LogTypeSpecificDetails(symbol);

                // Log accessibility and modifiers
                var accessibility = symbol.DeclaredAccessibility.ToString();
                var modifiers = GetSymbolModifiers(symbol);
                _logger.LogInformation("    Accessibility: {Accessibility}", accessibility);
                if (!string.IsNullOrEmpty(modifiers)) {
                    _logger.LogInformation("    Modifiers: {Modifiers}", modifiers);
                }
            } catch (Exception ex) {
                _logger.LogError(ex, "Error logging detailed match info for symbol {SymbolName}", match.Symbol.Name);
            }
        }
        private void LogTypeSpecificDetails(ISymbol symbol) {
            switch (symbol) {
                case INamedTypeSymbol namedType:
                    _logger.LogInformation("    Type Kind: {TypeKind}", namedType.TypeKind);
                    _logger.LogInformation("    Is Generic: {IsGeneric}", namedType.IsGenericType);
                    _logger.LogInformation("    Arity: {Arity}", namedType.Arity);
                    _logger.LogInformation("    Member Count: {MemberCount}", namedType.GetMembers().Length);
                    if (namedType.BaseType != null) {
                        _logger.LogInformation("    Base Type: {BaseType}", namedType.BaseType.ToDisplayString());
                    }
                    if (namedType.Interfaces.Any()) {
                        _logger.LogInformation("    Implements: {InterfaceCount} interfaces", namedType.Interfaces.Length);
                    }
                    break;

                case IMethodSymbol method:
                    _logger.LogInformation("    Method Kind: {MethodKind}", method.MethodKind);
                    _logger.LogInformation("    Return Type: {ReturnType}", method.ReturnType.ToDisplayString());
                    _logger.LogInformation("    Parameter Count: {ParameterCount}", method.Parameters.Length);
                    _logger.LogInformation("    Is Generic: {IsGeneric}", method.IsGenericMethod);
                    _logger.LogInformation("    Is Extension: {IsExtension}", method.IsExtensionMethod);
                    _logger.LogInformation("    Is Async: {IsAsync}", method.IsAsync);
                    if (method.Parameters.Any()) {
                        var parameterTypes = string.Join(", ", method.Parameters.Select(p => p.Type.ToDisplayString()));
                        _logger.LogInformation("    Parameters: {ParameterTypes}", parameterTypes);
                    }
                    break;

                case IPropertySymbol property:
                    _logger.LogInformation("    Property Type: {PropertyType}", property.Type.ToDisplayString());
                    _logger.LogInformation("    Is ReadOnly: {IsReadOnly}", property.IsReadOnly);
                    _logger.LogInformation("    Is WriteOnly: {IsWriteOnly}", property.IsWriteOnly);
                    _logger.LogInformation("    Is Indexer: {IsIndexer}", property.IsIndexer);
                    break;

                case IFieldSymbol field:
                    _logger.LogInformation("    Field Type: {FieldType}", field.Type.ToDisplayString());
                    _logger.LogInformation("    Is Const: {IsConst}", field.IsConst);
                    _logger.LogInformation("    Is ReadOnly: {IsReadOnly}", field.IsReadOnly);
                    _logger.LogInformation("    Is Static: {IsStatic}", field.IsStatic);
                    if (field.IsConst && field.ConstantValue != null) {
                        _logger.LogInformation("    Constant Value: {ConstantValue}", field.ConstantValue);
                    }
                    break;

                case IEventSymbol eventSymbol:
                    _logger.LogInformation("    Event Type: {EventType}", eventSymbol.Type.ToDisplayString());
                    break;
            }
        }
        private void LogAmbiguitySummary(string fuzzyFqnInput, List<FuzzyMatchResult> allResults, List<FuzzyMatchResult> highScoreMatches, List<FuzzyMatchResult> perfectMatches) {
            _logger.LogInformation("Ambiguity Summary for '{FuzzyFqn}':", fuzzyFqnInput);
            _logger.LogInformation("  Total matches: {TotalMatches}", allResults.Count);
            _logger.LogInformation("  Perfect matches (>= {PerfectThreshold:F3}): {PerfectCount}", PerfectMatchScore - 0.01, perfectMatches.Count);
            _logger.LogInformation("  High-scoring matches (>= 0.8): {HighScoreCount}", highScoreMatches.Count);

            // Score distribution
            var scoreRanges = new[] {
(1.0, 0.95, "Excellent"),
(0.95, 0.9, "Very Good"),
(0.9, 0.8, "Good"),
(0.8, 0.7, "Acceptable")
};

            foreach (var (upper, lower, label) in scoreRanges) {
                var count = allResults.Count(r => r.Score < upper && r.Score >= lower);
                if (count > 0) {
                    _logger.LogInformation("  {Label} matches ({Lower:F1}-{Upper:F1}): {Count}", label, lower, upper, count);
                }
            }

            // Symbol kind distribution
            var symbolKinds = highScoreMatches
            .GroupBy(m => GetSymbolKindString(m.Symbol))
            .OrderByDescending(g => g.Count())
            .ToList();

            if (symbolKinds.Any()) {
                _logger.LogInformation("  Symbol kinds in high-scoring matches:");
                foreach (var kind in symbolKinds) {
                    _logger.LogInformation("    {SymbolKind}: {Count}", kind.Key, kind.Count());
                }
            }

            // Project distribution
            var projects = highScoreMatches
            .GroupBy(m => GetProjectName(m.Symbol))
            .OrderByDescending(g => g.Count())
            .ToList();

            if (projects.Count > 1) {
                _logger.LogInformation("  Project distribution:");
                foreach (var project in projects) {
                    _logger.LogInformation("    {ProjectName}: {Count}", project.Key, project.Count());
                }
            }
        }

        private string GetProjectName(ISymbol symbol) {
            try {
                // Try to get the project from the symbol's containing assembly
                var assembly = symbol.ContainingAssembly;
                if (assembly?.Name != null) {
                    return assembly.Name;
                }

                // Fallback: look through loaded projects
                var syntaxRefs = symbol.DeclaringSyntaxReferences;
                if (syntaxRefs.Any()) {
                    var syntaxTree = syntaxRefs.First().SyntaxTree;
                    foreach (var project in _solutionManager?.CurrentSolution?.Projects ?? Enumerable.Empty<Project>()) {
                        if (project.Documents.Any(d => d.GetSyntaxTreeAsync().Result == syntaxTree)) {
                            return project.Name;
                        }
                    }
                }

                return "Unknown Project";
            } catch {
                return "Unknown Project";
            }
        }

        private string GetSymbolKindString(ISymbol symbol) {
            return symbol.Kind.ToString();
        }

        private string GetContainingTypeInfo(ISymbol symbol) {
            if (symbol.ContainingType != null) {
                return symbol.ContainingType.ToDisplayString();
            }
            return string.Empty;
        }

        private string GetAssemblyName(ISymbol symbol) {
            return symbol.ContainingAssembly?.Name ?? "Unknown Assembly";
        }

        private string GetLocationInfo(ISymbol symbol) {
            var location = symbol.Locations.FirstOrDefault(loc => loc.IsInSource);
            if (location?.SourceTree?.FilePath != null) {
                var lineSpan = location.GetLineSpan();
                return $"{Path.GetFileName(location.SourceTree.FilePath)}:{lineSpan.StartLinePosition.Line + 1}";
            }
            return "No source location";
        }

        private string GetSymbolModifiers(ISymbol symbol) {
            var modifiers = new List<string>();

            if (symbol.IsStatic) modifiers.Add("static");
            if (symbol.IsVirtual) modifiers.Add("virtual");
            if (symbol.IsOverride) modifiers.Add("override");
            if (symbol.IsAbstract) modifiers.Add("abstract");
            if (symbol.IsSealed) modifiers.Add("sealed");

            if (symbol is IMethodSymbol method) {
                if (method.IsAsync) modifiers.Add("async");
                if (method.IsExtern) modifiers.Add("extern");
            }

            if (symbol is IFieldSymbol field) {
                if (field.IsReadOnly) modifiers.Add("readonly");
                if (field.IsConst) modifiers.Add("const");
                if (field.IsVolatile) modifiers.Add("volatile");
            }

            return string.Join(" ", modifiers);
        }
        /// <summary>
        /// Normalizes generic arguments in a type name by replacing specific type names with placeholders.
        /// This allows matching between different generic instantiations.
        /// </summary>
        /// <param name="typeName">The type name with generic arguments</param>
        /// <returns>Type name with normalized generic arguments</returns>
        private string NormalizeGenericArgs(string typeName) {
            // If there are no generic arguments, return as is
            if (!typeName.Contains("<")) {
                return typeName;
            }

            // Match angle bracket content, keeping the brackets
            return Regex.Replace(typeName, @"<([^<>]*)>", match => {
                // Replace the content with a normalized form
                string content = match.Groups[1].Value;
                if (string.IsNullOrWhiteSpace(content)) {
                    return "<>";
                }

                // Handle multiple type arguments separated by commas
                var args = content.Split(',').Select(arg => "T").ToArray();
                return $"<{string.Join(",", args)}>";
            });
        }

        /// <summary>
        /// Completely strips generic arguments from a type name.
        /// </summary>
        /// <param name="typeName">The type name with generic arguments</param>
        /// <returns>Type name without generic arguments</returns>
        private string StripGenericArgs(string typeName) {
            // First, remove Roslyn-style arity indicators like List`1
            string withoutArity = ArityRegex.Replace(typeName, "");

            // Then remove angle bracket content including brackets
            return GenericArgsRegex.Replace(withoutArity, "");
        }
    }
}
```

--------------------------------------------------------------------------------
/SharpTools.Tools/Services/SemanticSimilarityService.cs:
--------------------------------------------------------------------------------

```csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.FlowAnalysis;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.Extensions.Logging;
using SharpTools.Tools.Extensions;
using SharpTools.Tools.Mcp;
using System;
using System.Collections.Concurrent; // Added
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace SharpTools.Tools.Services {
    public class SemanticSimilarityService : ISemanticSimilarityService {
        private static class Tuning {

            public static readonly int MaxDegreesOfParallelism = Math.Max(1, Environment.ProcessorCount / 2);

            public const int MethodLineCountFilter = 10;
            public const double DefaultSimilarityThreshold = 0.7;

            public static class Normalization {
                public const int MaxBasicBlockCount = 60;
                public const int MaxConditionalBranchCount = 25;
                public const int MaxLoopCount = 8;
                public const int MaxCyclomaticComplexity = 30;
            }

            public static class Weights {
                public enum Feature {
                    ReturnType,
                    ParamCount,
                    ParamTypes,
                    InvokedMethods,
                    BasicBlocks,
                    ConditionalBranches,
                    Loops,
                    CyclomaticComplexity,
                    OperationCounts,
                    AccessedMemberTypes
                }

                public static readonly Dictionary<Feature, double> FeatureWeights = new() {
                    { Feature.InvokedMethods, 0.25 },
                    { Feature.OperationCounts, 0.20 },
                    { Feature.AccessedMemberTypes, 0.15 },
                    { Feature.ParamTypes, 0.10 },
                    { Feature.CyclomaticComplexity, 0.075 },
                    { Feature.ReturnType, 0.05 },
                    { Feature.BasicBlocks, 0.05 },
                    { Feature.ConditionalBranches, 0.05 },
                    { Feature.Loops, 0.05 },
                    { Feature.ParamCount, 0.025 },
                };

                public static double ReturnType => FeatureWeights[Feature.ReturnType];
                public static double ParamCount => FeatureWeights[Feature.ParamCount];
                public static double ParamTypes => FeatureWeights[Feature.ParamTypes];
                public static double InvokedMethods => FeatureWeights[Feature.InvokedMethods];
                public static double BasicBlocks => FeatureWeights[Feature.BasicBlocks];
                public static double ConditionalBranches => FeatureWeights[Feature.ConditionalBranches];
                public static double Loops => FeatureWeights[Feature.Loops];
                public static double CyclomaticComplexity => FeatureWeights[Feature.CyclomaticComplexity];
                public static double OperationCounts => FeatureWeights[Feature.OperationCounts];
                public static double AccessedMemberTypes => FeatureWeights[Feature.AccessedMemberTypes];
                public static double TotalWeight => FeatureWeights.Values.Sum();
            }

            public const int ClassLineCountFilter = 20;

            public static class ClassNormalization {
                public const int MaxPropertyCount = 30;
                public const int MaxFieldCount = 50;
                public const int MaxMethodCount = 50;
                public const int MaxImplementedInterfaces = 10;
                public const int MaxReferencedExternalTypes = 75;
                public const int MaxUsedNamespaces = 20;
                public const double MaxAverageMethodComplexity = 15.0;
            }

            public static class ClassWeights {
                public enum Feature {
                    BaseClassName,
                    ImplementedInterfaceNames,
                    PublicMethodCount,
                    ProtectedMethodCount,
                    PrivateMethodCount,
                    StaticMethodCount,
                    AbstractMethodCount,
                    VirtualMethodCount,
                    PropertyCount,
                    ReadOnlyPropertyCount,
                    StaticPropertyCount,
                    FieldCount,
                    StaticFieldCount,
                    ReadonlyFieldCount,
                    ConstFieldCount,
                    EventCount,
                    NestedClassCount,
                    NestedStructCount,
                    NestedEnumCount,
                    NestedInterfaceCount,
                    AverageMethodComplexity,
                    DistinctReferencedExternalTypeFqns,
                    DistinctUsedNamespaceFqns,
                    TotalLinesOfCode,
                    MethodMatchingSimilarity // Added
                }

                public static readonly Dictionary<Feature, double> FeatureWeights = new()
                {
        { Feature.MethodMatchingSimilarity, 0.20 },                 // New and significant
        { Feature.ImplementedInterfaceNames, 0.15 },              // Was 0.20
        { Feature.DistinctReferencedExternalTypeFqns, 0.15 },       // Was 0.20
        { Feature.BaseClassName, 0.07 },                          // Was 0.10
        { Feature.AverageMethodComplexity, 0.05 },                // Was 0.05
        { Feature.PublicMethodCount, 0.03 },                      // Was 0.05
        { Feature.PropertyCount, 0.03 },                          // Was 0.05
        { Feature.FieldCount, 0.03 },                             // Was 0.05
        { Feature.DistinctUsedNamespaceFqns, 0.03 },              // Was 0.05
        { Feature.TotalLinesOfCode, 0.02 },                       // Was 0.025
        { Feature.ProtectedMethodCount, 0.02 },                   // Was 0.025
        { Feature.PrivateMethodCount, 0.02 },                     // Was 0.025
        { Feature.StaticMethodCount, 0.02 },                      // Was 0.025
        { Feature.AbstractMethodCount, 0.02 },                    // Was 0.025
        { Feature.VirtualMethodCount, 0.02 },                     // Was 0.025
        { Feature.ReadOnlyPropertyCount, 0.01 },
        { Feature.StaticPropertyCount, 0.01 },
        { Feature.StaticFieldCount, 0.01 },
        { Feature.ReadonlyFieldCount, 0.01 },
        { Feature.ConstFieldCount, 0.01 },
        { Feature.EventCount, 0.01 },
        { Feature.NestedClassCount, 0.01 },
        { Feature.NestedStructCount, 0.01 },
        { Feature.NestedEnumCount, 0.01 },
        { Feature.NestedInterfaceCount, 0.01 }
    };

                public static double BaseClassName => FeatureWeights[Feature.BaseClassName];
                public static double ImplementedInterfaceNames => FeatureWeights[Feature.ImplementedInterfaceNames];
                public static double PublicMethodCount => FeatureWeights[Feature.PublicMethodCount];
                public static double ProtectedMethodCount => FeatureWeights[Feature.ProtectedMethodCount];
                public static double PrivateMethodCount => FeatureWeights[Feature.PrivateMethodCount];
                public static double StaticMethodCount => FeatureWeights[Feature.StaticMethodCount];
                public static double AbstractMethodCount => FeatureWeights[Feature.AbstractMethodCount];
                public static double VirtualMethodCount => FeatureWeights[Feature.VirtualMethodCount];
                public static double PropertyCount => FeatureWeights[Feature.PropertyCount];
                public static double ReadOnlyPropertyCount => FeatureWeights[Feature.ReadOnlyPropertyCount];
                public static double StaticPropertyCount => FeatureWeights[Feature.StaticPropertyCount];
                public static double FieldCount => FeatureWeights[Feature.FieldCount];
                public static double StaticFieldCount => FeatureWeights[Feature.StaticFieldCount];
                public static double ReadonlyFieldCount => FeatureWeights[Feature.ReadonlyFieldCount];
                public static double ConstFieldCount => FeatureWeights[Feature.ConstFieldCount];
                public static double EventCount => FeatureWeights[Feature.EventCount];
                public static double NestedClassCount => FeatureWeights[Feature.NestedClassCount];
                public static double NestedStructCount => FeatureWeights[Feature.NestedStructCount];
                public static double NestedEnumCount => FeatureWeights[Feature.NestedEnumCount];
                public static double NestedInterfaceCount => FeatureWeights[Feature.NestedInterfaceCount];
                public static double AverageMethodComplexity => FeatureWeights[Feature.AverageMethodComplexity];
                public static double DistinctReferencedExternalTypeFqns => FeatureWeights[Feature.DistinctReferencedExternalTypeFqns];
                public static double DistinctUsedNamespaceFqns => FeatureWeights[Feature.DistinctUsedNamespaceFqns];
                public static double TotalLinesOfCode => FeatureWeights[Feature.TotalLinesOfCode];
                public static double MethodMatchingSimilarity => FeatureWeights[Feature.MethodMatchingSimilarity]; // Added
                public static double TotalWeight => FeatureWeights.Values.Sum();
            }
        }

        private readonly ISolutionManager _solutionManager;
        private readonly ICodeAnalysisService _codeAnalysisService;
        private readonly ILogger<SemanticSimilarityService> _logger;
        private readonly IComplexityAnalysisService _complexityAnalysisService;

        public SemanticSimilarityService(
            ISolutionManager solutionManager,
            ICodeAnalysisService codeAnalysisService,
            ILogger<SemanticSimilarityService> logger,
            IComplexityAnalysisService complexityAnalysisService) {
            _solutionManager = solutionManager ?? throw new ArgumentNullException(nameof(solutionManager));
            _codeAnalysisService = codeAnalysisService ?? throw new ArgumentNullException(nameof(codeAnalysisService));
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
            _complexityAnalysisService = complexityAnalysisService ?? throw new ArgumentNullException(nameof(complexityAnalysisService));
        }

        public async Task<List<MethodSimilarityResult>> FindSimilarMethodsAsync(
        double similarityThreshold,
        CancellationToken cancellationToken) {
            ToolHelpers.EnsureSolutionLoadedWithDetails(_solutionManager, _logger, nameof(FindSimilarMethodsAsync));
            _logger.LogInformation("Starting semantic similarity analysis with threshold {Threshold}, MaxDOP: {MaxDop}", similarityThreshold, Tuning.MaxDegreesOfParallelism);

            var allMethodFeatures = new System.Collections.Concurrent.ConcurrentBag<MethodSemanticFeatures>();

            var parallelOptions = new ParallelOptions {
                MaxDegreeOfParallelism = Tuning.MaxDegreesOfParallelism,
                CancellationToken = cancellationToken
            };

            var projects = _solutionManager.GetProjects().ToList(); // Materialize to avoid issues with concurrent modification if GetProjects() is lazy

            await Parallel.ForEachAsync(projects, parallelOptions, async (project, ct) => {
                if (ct.IsCancellationRequested) {
                    _logger.LogInformation("Semantic similarity analysis cancelled during project iteration for {ProjectName}.", project.Name);
                    return;
                }

                _logger.LogDebug("Analyzing project: {ProjectName}", project.Name);
                var compilation = await project.GetCompilationAsync(ct);
                if (compilation == null) {
                    _logger.LogWarning("Could not get compilation for project {ProjectName}", project.Name);
                    return;
                }
                var documents = project.Documents.ToList(); // Materialize documents for the current project

                await Parallel.ForEachAsync(documents, parallelOptions, async (document, docCt) => {
                    if (docCt.IsCancellationRequested) return;
                    if (!document.SupportsSyntaxTree || !document.SupportsSemanticModel) return;

                    _logger.LogTrace("Analyzing document: {DocumentFilePath}", document.FilePath);
                    var syntaxTree = await document.GetSyntaxTreeAsync(docCt);
                    var semanticModel = await document.GetSemanticModelAsync(docCt);
                    if (syntaxTree == null || semanticModel == null) return;

                    var methodDeclarations = syntaxTree.GetRoot(docCt).DescendantNodes().OfType<MethodDeclarationSyntax>();

                    foreach (var methodDecl in methodDeclarations) {
                        if (docCt.IsCancellationRequested) break;

                        var methodSymbol = semanticModel.GetDeclaredSymbol(methodDecl, docCt) as IMethodSymbol;
                        if (methodSymbol == null || methodSymbol.IsAbstract || methodSymbol.IsExtern || ToolHelpers.IsPropertyAccessor(methodSymbol)) {
                            continue;
                        }

                        try {
                            var features = await ExtractFeaturesAsync(methodSymbol, methodDecl, document, semanticModel, docCt);
                            if (features != null) {
                                allMethodFeatures.Add(features);
                            }
                        } catch (OperationCanceledException) {
                            _logger.LogInformation("Feature extraction cancelled for method {MethodName} in {FilePath}", methodSymbol?.Name ?? "Unknown", document.FilePath);
                        } catch (Exception ex) {
                            _logger.LogWarning(ex, "Failed to extract features for method {MethodName} in {FilePath}", methodSymbol?.Name ?? "Unknown", document.FilePath);
                        }
                    }
                });
            });

            if (cancellationToken.IsCancellationRequested) {
                _logger.LogInformation("Semantic similarity analysis was cancelled before comparison.");
                throw new OperationCanceledException("Semantic similarity analysis was cancelled.");
            }

            _logger.LogInformation("Extracted features for {MethodCount} methods. Starting similarity comparison.", allMethodFeatures.Count);
            return CompareFeatures(allMethodFeatures.ToList(), similarityThreshold, cancellationToken);
        }

        private async Task<MethodSemanticFeatures?> ExtractFeaturesAsync(
            IMethodSymbol methodSymbol,
            MethodDeclarationSyntax methodDecl,
            Document document,
            SemanticModel semanticModel,
            CancellationToken cancellationToken) {
            var filePath = document.FilePath ?? "unknown";
            var startLine = methodDecl.GetLocation().GetLineSpan().StartLinePosition.Line;
            var endLine = methodDecl.GetLocation().GetLineSpan().EndLinePosition.Line;
            var lineCount = endLine - startLine + 1;

            if (lineCount < Tuning.MethodLineCountFilter) {
                _logger.LogDebug("Method {MethodName} in {FilePath} has {LineCount} lines, which is less than the filter of {FilterCount}. Skipping.", methodSymbol.Name, filePath, lineCount, Tuning.MethodLineCountFilter);
                return null;
            }

            var methodName = methodSymbol.Name;
            var fullyQualifiedMethodName = methodSymbol.ToDisplayString(ToolHelpers.FullyQualifiedFormatWithoutGlobal);
            var returnTypeName = methodSymbol.ReturnType.ToDisplayString(ToolHelpers.FullyQualifiedFormatWithoutGlobal);
            var parameterTypeNames = methodSymbol.Parameters.Select(p => p.Type.ToDisplayString(ToolHelpers.FullyQualifiedFormatWithoutGlobal)).ToList();

            var invokedMethodSignatures = new HashSet<string>();
            var operationCounts = new Dictionary<string, int>();
            var distinctAccessedMemberTypes = new HashSet<string>();

            int basicBlockCount = 0;
            int conditionalBranchCount = 0;
            int loopCount = 0;

            var methodMetrics = new Dictionary<string, object>();
            var recommendations = new List<string>();
            await _complexityAnalysisService.AnalyzeMethodAsync(methodSymbol, methodMetrics, recommendations, cancellationToken);
            int cyclomaticComplexity = methodMetrics.TryGetValue("cyclomaticComplexity", out var cc) && cc is int ccVal ? ccVal : 1;

            SyntaxNode? bodyOrExpressionBody = methodDecl.Body ?? (SyntaxNode?)methodDecl.ExpressionBody?.Expression;

            if (bodyOrExpressionBody != null) {
                try {
                    // Use methodDecl directly for CFG creation
                    var controlFlowGraph = ControlFlowGraph.Create(methodDecl, semanticModel, cancellationToken);
                    if (controlFlowGraph != null && controlFlowGraph.Blocks.Any()) {
                        basicBlockCount = controlFlowGraph.Blocks.Length;
                    }
                    _logger.LogDebug("ControlFlowGraph created for method {MethodName} in {FilePath}. BasicBlockCount: {BasicBlockCount}", methodName, filePath, basicBlockCount);
                } catch (Exception ex) {
                    _logger.LogWarning(ex, "Failed to create ControlFlowGraph for method {MethodName} in {FilePath}. CFG-based features will be zero.", methodName, filePath);
                }

                var operation = semanticModel.GetOperation(bodyOrExpressionBody, cancellationToken);
                if (operation != null) {
                    foreach (var opNode in operation.DescendantsAndSelf()) {
                        if (cancellationToken.IsCancellationRequested) {
                            return null;
                        }

                        var opKindName = opNode.Kind.ToString();
                        operationCounts[opKindName] = operationCounts.GetValueOrDefault(opKindName, 0) + 1;

                        if (opNode is IInvocationOperation invocation) {
                            invokedMethodSignatures.Add(invocation.TargetMethod.OriginalDefinition.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat));
                        } else if (opNode is IFieldReferenceOperation fieldRef) {
                            distinctAccessedMemberTypes.Add(fieldRef.Field.Type.ToDisplayString(ToolHelpers.FullyQualifiedFormatWithoutGlobal));
                        } else if (opNode is IPropertyReferenceOperation propRef) {
                            distinctAccessedMemberTypes.Add(propRef.Property.Type.ToDisplayString(ToolHelpers.FullyQualifiedFormatWithoutGlobal));
                        }

                        if (opNode is ILoopOperation) {
                            loopCount++;
                        } else if (opNode is IConditionalOperation) {
                            conditionalBranchCount++;
                        }
                    }
                }
            }

            return new MethodSemanticFeatures(
                fullyQualifiedMethodName,
                filePath,
                startLine,
                methodName,
                returnTypeName,
                parameterTypeNames,
                invokedMethodSignatures,
                basicBlockCount,
                conditionalBranchCount,
                loopCount,
                cyclomaticComplexity,
                operationCounts,
                distinctAccessedMemberTypes);
        }

        private List<MethodSimilarityResult> CompareFeatures(
            List<MethodSemanticFeatures> allMethodFeatures,
            double similarityThreshold,
            CancellationToken cancellationToken) {
            var results = new List<MethodSimilarityResult>();
            var processedIndices = new HashSet<int>();
            _logger.LogInformation("Starting similarity comparison for {MethodCount} methods.", allMethodFeatures.Count);
            for (int i = 0; i < allMethodFeatures.Count; i++) {
                if (cancellationToken.IsCancellationRequested) {
                    throw new OperationCanceledException("Semantic similarity analysis was cancelled.");
                }
                if (processedIndices.Contains(i)) {
                    continue;
                }

                var currentMethod = allMethodFeatures[i];
                var similarGroup = new List<MethodSemanticFeatures> { currentMethod };
                processedIndices.Add(i);
                double groupTotalScore = 0;
                int comparisonsMade = 0;
                _logger.LogDebug("Comparing method {MethodName} ({FQN}) with other methods.", currentMethod.MethodName, currentMethod.FullyQualifiedMethodName);

                for (int j = i + 1; j < allMethodFeatures.Count; j++) {
                    if (cancellationToken.IsCancellationRequested) {
                        throw new OperationCanceledException("Semantic similarity analysis was cancelled.");
                    }
                    if (processedIndices.Contains(j)) {
                        continue;
                    }

                    var otherMethod = allMethodFeatures[j];

                    // Skip comparison if methods are overloads of each other
                    if (currentMethod.FullyQualifiedMethodName == otherMethod.FullyQualifiedMethodName &&
                        !currentMethod.ParameterTypeNames.SequenceEqual(otherMethod.ParameterTypeNames)) {
                        _logger.LogDebug("Skipping comparison between overloads: {Method1FQN} ({Params1}) and {Method2FQN} ({Params2})",
                                        currentMethod.FullyQualifiedMethodName, string.Join(", ", currentMethod.ParameterTypeNames),
                                        otherMethod.FullyQualifiedMethodName, string.Join(", ", otherMethod.ParameterTypeNames));
                        continue;
                    }

                    double similarity = CalculateSimilarity(currentMethod, otherMethod);

                    if (similarity >= similarityThreshold) {
                        similarGroup.Add(otherMethod);
                        processedIndices.Add(j);
                        groupTotalScore += similarity;
                        comparisonsMade++;
                        _logger.LogDebug("Method {OtherMethodName} ({OtherFQN}) is similar to {CurrentMethodName} ({CurrentFQN}) with score {SimilarityScore}",
                                         otherMethod.MethodName, otherMethod.FullyQualifiedMethodName,
                                         currentMethod.MethodName, currentMethod.FullyQualifiedMethodName,
                                         similarity);
                    }
                }

                if (similarGroup.Count > 1) {
                    double averageScore = comparisonsMade > 0 ? groupTotalScore / comparisonsMade : 1.0; // Avoid division by zero if only self-comparison
                    results.Add(new MethodSimilarityResult(similarGroup, averageScore));
                    _logger.LogInformation("Found similarity group of {GroupSize} methods, starting with {MethodName} ({FQN}), Avg Score: {Score:F2}",
                                         similarGroup.Count,
                                         currentMethod.MethodName,
                                         currentMethod.FullyQualifiedMethodName,
                                         averageScore);
                }
            }
            _logger.LogInformation("Semantic similarity analysis complete. Found {GroupCount} groups.", results.Count);
            return results.OrderByDescending(r => r.AverageSimilarityScore).ToList();
        }

        private double CalculateSimilarity(MethodSemanticFeatures method1, MethodSemanticFeatures method2) {
            double returnTypeSimilarity = (method1.ReturnTypeName == method2.ReturnTypeName) ? 1.0 : 0.0;
            double paramCountSimilarity = (method1.ParameterTypeNames.Count == method2.ParameterTypeNames.Count) ? 1.0 : 0.0;
            double paramTypeSimilarity = 0.0;
            if (method1.ParameterTypeNames.Count == method2.ParameterTypeNames.Count && method1.ParameterTypeNames.Any()) {
                int matchingParams = 0;
                for (int k = 0; k < method1.ParameterTypeNames.Count; k++) {
                    if (method1.ParameterTypeNames[k] == method2.ParameterTypeNames[k]) {
                        matchingParams++;
                    }
                }
                paramTypeSimilarity = (double)matchingParams / method1.ParameterTypeNames.Count;
            } else if (method1.ParameterTypeNames.Count == 0 && method2.ParameterTypeNames.Count == 0) {
                paramTypeSimilarity = 1.0;
            }

            double invokedSimilarity = 0.0;
            if (method1.InvokedMethodSignatures.Any() || method2.InvokedMethodSignatures.Any()) {
                var intersection = method1.InvokedMethodSignatures.Intersect(method2.InvokedMethodSignatures).Count();
                var union = method1.InvokedMethodSignatures.Union(method2.InvokedMethodSignatures).Count();
                invokedSimilarity = union > 0 ? (double)intersection / union : 1.0;
            } else {
                invokedSimilarity = 1.0;
            }

            double basicBlockSimilarity = 1.0 - CalculateNormalizedDifference(method1.BasicBlockCount, method2.BasicBlockCount, Tuning.Normalization.MaxBasicBlockCount);
            double conditionalBranchSimilarity = 1.0 - CalculateNormalizedDifference(method1.ConditionalBranchCount, method2.ConditionalBranchCount, Tuning.Normalization.MaxConditionalBranchCount);
            double loopSimilarity = 1.0 - CalculateNormalizedDifference(method1.LoopCount, method2.LoopCount, Tuning.Normalization.MaxLoopCount);
            double cyclomaticComplexitySimilarity = 1.0 - CalculateNormalizedDifference(method1.CyclomaticComplexity, method2.CyclomaticComplexity, Tuning.Normalization.MaxCyclomaticComplexity);
            double operationCountsSimilarity = CalculateCosineSimilarity(method1.OperationCounts, method2.OperationCounts);
            double accessedTypesSimilarity = 0.0;

            if (method1.DistinctAccessedMemberTypes.Any() || method2.DistinctAccessedMemberTypes.Any()) {
                var intersectionTypes = method1.DistinctAccessedMemberTypes.Intersect(method2.DistinctAccessedMemberTypes).Count();
                var unionTypes = method1.DistinctAccessedMemberTypes.Union(method2.DistinctAccessedMemberTypes).Count();
                accessedTypesSimilarity = unionTypes > 0 ? (double)intersectionTypes / unionTypes : 1.0;
            } else {
                accessedTypesSimilarity = 1.0;
            }

            double totalWeightedScore =
                returnTypeSimilarity * Tuning.Weights.ReturnType +
                paramCountSimilarity * Tuning.Weights.ParamCount +
                paramTypeSimilarity * Tuning.Weights.ParamTypes +
                invokedSimilarity * Tuning.Weights.InvokedMethods +
                basicBlockSimilarity * Tuning.Weights.BasicBlocks +
                conditionalBranchSimilarity * Tuning.Weights.ConditionalBranches +
                loopSimilarity * Tuning.Weights.Loops +
                cyclomaticComplexitySimilarity * Tuning.Weights.CyclomaticComplexity +
                operationCountsSimilarity * Tuning.Weights.OperationCounts +
                accessedTypesSimilarity * Tuning.Weights.AccessedMemberTypes;

            return totalWeightedScore / Tuning.Weights.TotalWeight;
        }

        private double CalculateNormalizedDifference(int val1, int val2, int maxValue) {
            if (maxValue == 0) return (val1 == val2) ? 0.0 : 1.0;
            double diff = Math.Abs(val1 - val2);
            return diff / maxValue;
        }

        private double CalculateCosineSimilarity(Dictionary<string, int> vec1, Dictionary<string, int> vec2) {
            if (!vec1.Any() && !vec2.Any()) return 1.0;
            if (!vec1.Any() || !vec2.Any()) return 0.0;

            var allKeys = vec1.Keys.Union(vec2.Keys).ToList();
            double dotProduct = 0.0;
            double magnitude1 = 0.0;
            double magnitude2 = 0.0;

            foreach (var key in allKeys) {
                int val1 = vec1.GetValueOrDefault(key, 0);
                int val2 = vec2.GetValueOrDefault(key, 0);

                dotProduct += val1 * val2;
                magnitude1 += val1 * val1;
                magnitude2 += val2 * val2;
            }

            magnitude1 = Math.Sqrt(magnitude1);
            magnitude2 = Math.Sqrt(magnitude2);

            if (magnitude1 == 0 || magnitude2 == 0) return 0.0;

            return dotProduct / (magnitude1 * magnitude2);
        }

        public async Task<List<ClassSimilarityResult>> FindSimilarClassesAsync(
        double similarityThreshold,
        CancellationToken cancellationToken) {
            ToolHelpers.EnsureSolutionLoadedWithDetails(_solutionManager, _logger, nameof(FindSimilarClassesAsync));
            _logger.LogInformation("Starting class semantic similarity analysis with threshold {Threshold}, MaxDOP: {MaxDop}", similarityThreshold, Tuning.MaxDegreesOfParallelism);

            var allClassFeatures = new System.Collections.Concurrent.ConcurrentBag<ClassSemanticFeatures>();

            var parallelOptions = new ParallelOptions {
                MaxDegreeOfParallelism = Tuning.MaxDegreesOfParallelism,
                CancellationToken = cancellationToken
            };

            var projects = _solutionManager.GetProjects().ToList(); // Materialize

            await Parallel.ForEachAsync(projects, parallelOptions, async (project, ct) => {
                if (ct.IsCancellationRequested) {
                    _logger.LogInformation("Class semantic similarity analysis cancelled during project iteration for {ProjectName}.", project.Name);
                    return;
                }

                _logger.LogDebug("Analyzing project for classes: {ProjectName}", project.Name);
                var compilation = await project.GetCompilationAsync(ct);
                if (compilation == null) {
                    _logger.LogWarning("Could not get compilation for project {ProjectName}", project.Name);
                    return;
                }
                var documents = project.Documents.ToList(); // Materialize

                await Parallel.ForEachAsync(documents, parallelOptions, async (document, docCt) => {
                    if (docCt.IsCancellationRequested) return;
                    if (!document.SupportsSyntaxTree || !document.SupportsSemanticModel) return;

                    _logger.LogTrace("Analyzing document for classes: {DocumentFilePath}", document.FilePath);
                    var syntaxTree = await document.GetSyntaxTreeAsync(docCt);
                    var semanticModel = await document.GetSemanticModelAsync(docCt);
                    if (syntaxTree == null || semanticModel == null) return;

                    var classDeclarations = syntaxTree.GetRoot(docCt).DescendantNodes()
                        .OfType<TypeDeclarationSyntax>()
                        .Where(tds => tds.Kind() == Microsoft.CodeAnalysis.CSharp.SyntaxKind.ClassDeclaration ||
                                      tds.Kind() == Microsoft.CodeAnalysis.CSharp.SyntaxKind.RecordDeclaration);

                    foreach (var classDecl in classDeclarations) {
                        if (docCt.IsCancellationRequested) break;

                        var classSymbol = semanticModel.GetDeclaredSymbol(classDecl, docCt) as INamedTypeSymbol;
                        if (classSymbol == null || classSymbol.IsAbstract || classSymbol.IsStatic) {
                            continue;
                        }

                        try {
                            var features = await ExtractClassFeaturesAsync(classSymbol, classDecl, document, semanticModel, docCt);
                            if (features != null) {
                                allClassFeatures.Add(features);
                            }
                        } catch (OperationCanceledException) {
                            _logger.LogInformation("Feature extraction cancelled for class {ClassName} in {FilePath}", classSymbol?.Name ?? "Unknown", document.FilePath);
                        } catch (Exception ex) {
                            _logger.LogWarning(ex, "Failed to extract features for class {ClassName} in {FilePath}", classSymbol?.Name ?? "Unknown", document.FilePath);
                        }
                    }
                });
            });

            if (cancellationToken.IsCancellationRequested) {
                _logger.LogInformation("Class semantic similarity analysis was cancelled before comparison.");
                throw new OperationCanceledException("Class semantic similarity analysis was cancelled.");
            }

            _logger.LogInformation("Extracted features for {ClassCount} classes. Starting similarity comparison.", allClassFeatures.Count);
            return CompareClassFeatures(allClassFeatures.ToList(), similarityThreshold, cancellationToken);
        }

        private async Task<ClassSemanticFeatures?> ExtractClassFeaturesAsync(
            INamedTypeSymbol classSymbol,
            TypeDeclarationSyntax classDecl,
            Document document,
            SemanticModel semanticModel,
            CancellationToken cancellationToken) {
            var filePath = document.FilePath ?? "unknown";
            var startLine = classDecl.GetLocation().GetLineSpan().StartLinePosition.Line;
            var endLine = classDecl.GetLocation().GetLineSpan().EndLinePosition.Line;
            var totalLinesOfCode = endLine - startLine + 1;

            if (totalLinesOfCode < Tuning.ClassLineCountFilter) {
                _logger.LogDebug("Class {ClassName} in {FilePath} has {LineCount} lines, less than filter {FilterCount}. Skipping.",
                    classSymbol.Name, filePath, totalLinesOfCode, Tuning.ClassLineCountFilter);
                return null;
            }

            var className = classSymbol.Name;
            var fullyQualifiedClassName = classSymbol.ToDisplayString(ToolHelpers.FullyQualifiedFormatWithoutGlobal);

            var distinctReferencedExternalTypeFqns = new HashSet<string>();
            var distinctUsedNamespaceFqns = new HashSet<string>();

            AddTypeAndNamespaceIfExternal(classSymbol.BaseType, classSymbol, distinctReferencedExternalTypeFqns, distinctUsedNamespaceFqns);
            var baseClassName = classSymbol.BaseType?.ToDisplayString(ToolHelpers.FullyQualifiedFormatWithoutGlobal);

            var implementedInterfaceNames = new List<string>();
            foreach (var iface in classSymbol.AllInterfaces) {
                AddTypeAndNamespaceIfExternal(iface, classSymbol, distinctReferencedExternalTypeFqns, distinctUsedNamespaceFqns);
                implementedInterfaceNames.Add(iface.ToDisplayString(ToolHelpers.FullyQualifiedFormatWithoutGlobal));
            }

            int publicMethodCount = 0, protectedMethodCount = 0, privateMethodCount = 0, staticMethodCount = 0, abstractMethodCount = 0, virtualMethodCount = 0;
            int propertyCount = 0, readOnlyPropertyCount = 0, staticPropertyCount = 0;
            int fieldCount = 0, staticFieldCount = 0, readonlyFieldCount = 0, constFieldCount = 0;
            int eventCount = 0;
            int nestedClassCount = 0, nestedStructCount = 0, nestedEnumCount = 0, nestedInterfaceCount = 0;
            double totalMethodComplexity = 0;
            int analyzedMethodCount = 0;
            var classMethodFeatures = new List<MethodSemanticFeatures>();

            // Collect used namespaces from using directives in the current file
            if (classDecl.SyntaxTree.GetRoot(cancellationToken) is CompilationUnitSyntax compilationUnit) {
                foreach (var usingDirective in compilationUnit.Usings) {
                    if (usingDirective.Name != null) {
                        var namespaceSymbol = semanticModel.GetSymbolInfo(usingDirective.Name, cancellationToken).Symbol as INamespaceSymbol;
                        if (namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace) {
                            distinctUsedNamespaceFqns.Add(namespaceSymbol.ToDisplayString(ToolHelpers.FullyQualifiedFormatWithoutGlobal));
                        }
                    }
                }
            }

            foreach (var memberSymbol in classSymbol.GetMembers()) {
                if (cancellationToken.IsCancellationRequested) return null;

                if (memberSymbol is IMethodSymbol methodMember) {
                    if (ToolHelpers.IsPropertyAccessor(methodMember) || methodMember.IsImplicitlyDeclared) continue;

                    if (methodMember.DeclaredAccessibility == Accessibility.Public) publicMethodCount++;
                    else if (methodMember.DeclaredAccessibility == Accessibility.Protected) protectedMethodCount++;
                    else if (methodMember.DeclaredAccessibility == Accessibility.Private) privateMethodCount++;
                    if (methodMember.IsStatic) staticMethodCount++;
                    if (methodMember.IsAbstract) abstractMethodCount++;
                    if (methodMember.IsVirtual) virtualMethodCount++;

                    AddTypeAndNamespaceIfExternal(methodMember.ReturnType, classSymbol, distinctReferencedExternalTypeFqns, distinctUsedNamespaceFqns);
                    foreach (var param in methodMember.Parameters) {
                        AddTypeAndNamespaceIfExternal(param.Type, classSymbol, distinctReferencedExternalTypeFqns, distinctUsedNamespaceFqns);
                    }

                    if (memberSymbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(cancellationToken) is MethodDeclarationSyntax methodDeclSyntax) {
                        var features = await ExtractFeaturesAsync(methodMember, methodDeclSyntax, document, semanticModel, cancellationToken);
                        if (features != null) {
                            classMethodFeatures.Add(features);
                            totalMethodComplexity += features.CyclomaticComplexity;
                            analyzedMethodCount++;

                            // Add types and namespaces from method body analysis
                            foreach (var invokedSig in features.InvokedMethodSignatures) {
                                // This is tricky as InvokedMethodSignatures are strings. A more robust way would be to get IMethodSymbol during ExtractFeaturesAsync
                                // For now, we'll skip adding these to avoid parsing strings back to symbols.
                            }
                            foreach (var accessedType in features.DistinctAccessedMemberTypes) {
                                // Similar to above, these are strings.
                            }
                        }
                    }
                } else if (memberSymbol is IPropertySymbol propertyMember) {
                    propertyCount++;
                    if (propertyMember.IsReadOnly) readOnlyPropertyCount++;
                    if (propertyMember.IsStatic) staticPropertyCount++;
                    AddTypeAndNamespaceIfExternal(propertyMember.Type, classSymbol, distinctReferencedExternalTypeFqns, distinctUsedNamespaceFqns);
                } else if (memberSymbol is IFieldSymbol fieldMember) {
                    fieldCount++;
                    if (fieldMember.IsStatic) staticFieldCount++;
                    if (fieldMember.IsReadOnly) readonlyFieldCount++;
                    if (fieldMember.IsConst) constFieldCount++;
                    AddTypeAndNamespaceIfExternal(fieldMember.Type, classSymbol, distinctReferencedExternalTypeFqns, distinctUsedNamespaceFqns);
                } else if (memberSymbol is IEventSymbol eventMember) {
                    eventCount++;
                    AddTypeAndNamespaceIfExternal(eventMember.Type, classSymbol, distinctReferencedExternalTypeFqns, distinctUsedNamespaceFqns);
                } else if (memberSymbol is INamedTypeSymbol nestedTypeMember) {
                    if (nestedTypeMember.TypeKind == TypeKind.Class) nestedClassCount++;
                    else if (nestedTypeMember.TypeKind == TypeKind.Struct) nestedStructCount++;
                    else if (nestedTypeMember.TypeKind == TypeKind.Enum) nestedEnumCount++;
                    else if (nestedTypeMember.TypeKind == TypeKind.Interface) nestedInterfaceCount++;
                    // We don't add nested types to external types/namespaces as they are part of the class itself.
                }
            }

            // Deeper scan for referenced types and namespaces within method bodies and other syntax elements
            // This is more robust than just looking at IdentifierNameSyntax.
            foreach (var node in classDecl.DescendantNodes(descendIntoChildren: n => n is not TypeDeclarationSyntax || n == classDecl)) { // Avoid descending into nested types again
                if (cancellationToken.IsCancellationRequested) return null;

                ISymbol? referencedSymbol = null;
                if (node is IdentifierNameSyntax identifierName) {
                    referencedSymbol = semanticModel.GetSymbolInfo(identifierName, cancellationToken).Symbol;
                } else if (node is MemberAccessExpressionSyntax memberAccess) {
                    referencedSymbol = semanticModel.GetSymbolInfo(memberAccess.Name, cancellationToken).Symbol;
                } else if (node is ObjectCreationExpressionSyntax objectCreation) {
                    referencedSymbol = semanticModel.GetSymbolInfo(objectCreation.Type, cancellationToken).Symbol;
                } else if (node is InvocationExpressionSyntax invocation && invocation.Expression is MemberAccessExpressionSyntax maes) {
                    referencedSymbol = semanticModel.GetSymbolInfo(maes.Name, cancellationToken).Symbol;
                }


                if (referencedSymbol is ITypeSymbol typeSym) {
                    AddTypeAndNamespaceIfExternal(typeSym, classSymbol, distinctReferencedExternalTypeFqns, distinctUsedNamespaceFqns);
                } else if (referencedSymbol is IMethodSymbol methodSym) {
                    AddTypeAndNamespaceIfExternal(methodSym.ReturnType, classSymbol, distinctReferencedExternalTypeFqns, distinctUsedNamespaceFqns);
                    foreach (var param in methodSym.Parameters) {
                        AddTypeAndNamespaceIfExternal(param.Type, classSymbol, distinctReferencedExternalTypeFqns, distinctUsedNamespaceFqns);
                    }
                } else if (referencedSymbol is IPropertySymbol propSym) {
                    AddTypeAndNamespaceIfExternal(propSym.Type, classSymbol, distinctReferencedExternalTypeFqns, distinctUsedNamespaceFqns);
                } else if (referencedSymbol is IFieldSymbol fieldSym) {
                    AddTypeAndNamespaceIfExternal(fieldSym.Type, classSymbol, distinctReferencedExternalTypeFqns, distinctUsedNamespaceFqns);
                } else if (referencedSymbol is IEventSymbol eventSym) {
                    AddTypeAndNamespaceIfExternal(eventSym.Type, classSymbol, distinctReferencedExternalTypeFqns, distinctUsedNamespaceFqns);
                } else if (referencedSymbol is INamespaceSymbol nsSym && !nsSym.IsGlobalNamespace) {
                    // Check if the namespace itself is from an external assembly (less common for direct usage like this, but possible)
                    if (nsSym.ContainingAssembly != null && classSymbol.ContainingAssembly != null &&
                        !SymbolEqualityComparer.Default.Equals(nsSym.ContainingAssembly, classSymbol.ContainingAssembly)) {
                        distinctUsedNamespaceFqns.Add(nsSym.ToDisplayString(ToolHelpers.FullyQualifiedFormatWithoutGlobal));
                    } else if (nsSym.ContainingAssembly == null && classSymbol.ContainingAssembly != null) {
                        // Namespace is likely global or part of the current compilation but not tied to a specific assembly in the same way types are.
                        // We primarily add namespaces based on types they contain.
                    }
                }
            }


            double averageMethodComplexity = analyzedMethodCount > 0 ? totalMethodComplexity / analyzedMethodCount : 0;

            return new ClassSemanticFeatures(
                fullyQualifiedClassName,
                filePath,
                startLine,
                className,
                baseClassName,
                implementedInterfaceNames.Distinct().ToList(), // Ensure distinct
                publicMethodCount,
                protectedMethodCount,
                privateMethodCount,
                staticMethodCount,
                abstractMethodCount,
                virtualMethodCount,
                propertyCount,
                readOnlyPropertyCount,
                staticPropertyCount,
                fieldCount,
                staticFieldCount,
                readonlyFieldCount,
                constFieldCount,
                eventCount,
                nestedClassCount,
                nestedStructCount,
                nestedEnumCount,
                nestedInterfaceCount,
                averageMethodComplexity,
                distinctReferencedExternalTypeFqns, // Already a HashSet
                distinctUsedNamespaceFqns,          // Already a HashSet
                totalLinesOfCode,
                classMethodFeatures
            );
        }

        private List<ClassSimilarityResult> CompareClassFeatures(
            List<ClassSemanticFeatures> allClassFeatures,
            double similarityThreshold,
            CancellationToken cancellationToken) {
            var results = new List<ClassSimilarityResult>();
            var processedIndices = new HashSet<int>();
            _logger.LogInformation("Starting class similarity comparison for {ClassCount} classes.", allClassFeatures.Count);

            for (int i = 0; i < allClassFeatures.Count; i++) {
                if (cancellationToken.IsCancellationRequested) {
                    throw new OperationCanceledException("Class similarity analysis was cancelled.");
                }
                if (processedIndices.Contains(i)) continue;

                var currentClass = allClassFeatures[i];
                var similarGroup = new List<ClassSemanticFeatures> { currentClass };
                processedIndices.Add(i);
                double groupTotalScore = 0;
                int comparisonsMade = 0;

                for (int j = i + 1; j < allClassFeatures.Count; j++) {
                    if (cancellationToken.IsCancellationRequested) {
                        throw new OperationCanceledException("Class similarity analysis was cancelled.");
                    }
                    if (processedIndices.Contains(j)) continue;

                    var otherClass = allClassFeatures[j];
                    double similarity = CalculateClassSimilarity(currentClass, otherClass);

                    if (similarity >= similarityThreshold) {
                        similarGroup.Add(otherClass);
                        processedIndices.Add(j);
                        groupTotalScore += similarity;
                        comparisonsMade++;
                    }
                }

                if (similarGroup.Count > 1) {
                    double averageScore = comparisonsMade > 0 ? groupTotalScore / comparisonsMade : 1.0;
                    results.Add(new ClassSimilarityResult(similarGroup, averageScore));
                    _logger.LogInformation("Found class similarity group of {GroupSize}, starting with {ClassName}, Avg Score: {Score:F2}",
                        similarGroup.Count, currentClass.ClassName, averageScore);
                }
            }
            _logger.LogInformation("Class semantic similarity analysis complete. Found {GroupCount} groups.", results.Count);
            return results.OrderByDescending(r => r.AverageSimilarityScore).ToList();
        }

        private double CalculateClassSimilarity(ClassSemanticFeatures class1, ClassSemanticFeatures class2) {
            double baseClassSimilarity = (class1.BaseClassName == class2.BaseClassName) ? 1.0 :
                (string.IsNullOrEmpty(class1.BaseClassName) && string.IsNullOrEmpty(class2.BaseClassName) ? 1.0 : 0.0);

            double interfaceSimilarity = CalculateJaccardSimilarity(class1.ImplementedInterfaceNames, class2.ImplementedInterfaceNames);
            double referencedTypesSimilarity = CalculateJaccardSimilarity(class1.DistinctReferencedExternalTypeFqns, class2.DistinctReferencedExternalTypeFqns);
            double usedNamespacesSimilarity = CalculateJaccardSimilarity(class1.DistinctUsedNamespaceFqns, class2.DistinctUsedNamespaceFqns);

            double methodMatchingSimilarity = 0.0;
            if (class1.MethodFeatures.Any() && class2.MethodFeatures.Any()) {
                var smallerList = class1.MethodFeatures.Count < class2.MethodFeatures.Count ? class1.MethodFeatures : class2.MethodFeatures;
                var largerList = class1.MethodFeatures.Count < class2.MethodFeatures.Count ? class2.MethodFeatures : class1.MethodFeatures;
                double totalMaxSimilarity = 0.0;
                HashSet<int> usedLargerListIndices = new HashSet<int>();

                foreach (var method1Feat in smallerList) {
                    double maxSimForMethod1 = 0.0;
                    int bestMatchIndex = -1;
                    for (int k = 0; k < largerList.Count; k++) {
                        if (usedLargerListIndices.Contains(k)) {
                            continue;
                        }
                        double sim = CalculateSimilarity(method1Feat, largerList[k]); // Uses existing method similarity
                        if (sim > maxSimForMethod1) {
                            maxSimForMethod1 = sim;
                            bestMatchIndex = k;
                        }
                    }
                    if (bestMatchIndex != -1) {
                        totalMaxSimilarity += maxSimForMethod1;
                        usedLargerListIndices.Add(bestMatchIndex);
                    }
                }
                methodMatchingSimilarity = smallerList.Any() ? totalMaxSimilarity / smallerList.Count : 1.0;
            } else if (!class1.MethodFeatures.Any() && !class2.MethodFeatures.Any()) {
                methodMatchingSimilarity = 1.0; // Both have no methods, considered perfectly similar in this aspect
            }

            double totalWeightedScore =
                baseClassSimilarity * Tuning.ClassWeights.BaseClassName +
                interfaceSimilarity * Tuning.ClassWeights.ImplementedInterfaceNames +
                referencedTypesSimilarity * Tuning.ClassWeights.DistinctReferencedExternalTypeFqns +
                usedNamespacesSimilarity * Tuning.ClassWeights.DistinctUsedNamespaceFqns +
                methodMatchingSimilarity * Tuning.ClassWeights.MethodMatchingSimilarity + // Added
                (1.0 - CalculateNormalizedDifference(class1.PublicMethodCount, class2.PublicMethodCount, Tuning.ClassNormalization.MaxMethodCount)) * Tuning.ClassWeights.PublicMethodCount +
                (1.0 - CalculateNormalizedDifference(class1.ProtectedMethodCount, class2.ProtectedMethodCount, Tuning.ClassNormalization.MaxMethodCount)) * Tuning.ClassWeights.ProtectedMethodCount +
                (1.0 - CalculateNormalizedDifference(class1.PrivateMethodCount, class2.PrivateMethodCount, Tuning.ClassNormalization.MaxMethodCount)) * Tuning.ClassWeights.PrivateMethodCount +
                (1.0 - CalculateNormalizedDifference(class1.StaticMethodCount, class2.StaticMethodCount, Tuning.ClassNormalization.MaxMethodCount)) * Tuning.ClassWeights.StaticMethodCount +
                (1.0 - CalculateNormalizedDifference(class1.AbstractMethodCount, class2.AbstractMethodCount, Tuning.ClassNormalization.MaxMethodCount)) * Tuning.ClassWeights.AbstractMethodCount +
                (1.0 - CalculateNormalizedDifference(class1.VirtualMethodCount, class2.VirtualMethodCount, Tuning.ClassNormalization.MaxMethodCount)) * Tuning.ClassWeights.VirtualMethodCount +
                (1.0 - CalculateNormalizedDifference(class1.PropertyCount, class2.PropertyCount, Tuning.ClassNormalization.MaxPropertyCount)) * Tuning.ClassWeights.PropertyCount +
                (1.0 - CalculateNormalizedDifference(class1.ReadOnlyPropertyCount, class2.ReadOnlyPropertyCount, Tuning.ClassNormalization.MaxPropertyCount)) * Tuning.ClassWeights.ReadOnlyPropertyCount +
                (1.0 - CalculateNormalizedDifference(class1.StaticPropertyCount, class2.StaticPropertyCount, Tuning.ClassNormalization.MaxPropertyCount)) * Tuning.ClassWeights.StaticPropertyCount +
                (1.0 - CalculateNormalizedDifference(class1.FieldCount, class2.FieldCount, Tuning.ClassNormalization.MaxFieldCount)) * Tuning.ClassWeights.FieldCount +
                (1.0 - CalculateNormalizedDifference(class1.StaticFieldCount, class2.StaticFieldCount, Tuning.ClassNormalization.MaxFieldCount)) * Tuning.ClassWeights.StaticFieldCount +
                (1.0 - CalculateNormalizedDifference(class1.ReadonlyFieldCount, class2.ReadonlyFieldCount, Tuning.ClassNormalization.MaxFieldCount)) * Tuning.ClassWeights.ReadonlyFieldCount +
                (1.0 - CalculateNormalizedDifference(class1.ConstFieldCount, class2.ConstFieldCount, Tuning.ClassNormalization.MaxFieldCount)) * Tuning.ClassWeights.ConstFieldCount +
                (1.0 - CalculateNormalizedDifference(class1.EventCount, class2.EventCount, 20)) * Tuning.ClassWeights.EventCount + // Max 20 events (consider making this a ClassNormalization constant)
                (1.0 - CalculateNormalizedDifference(class1.NestedClassCount, class2.NestedClassCount, 10)) * Tuning.ClassWeights.NestedClassCount + // Max 10 (consider ClassNormalization)
                (1.0 - CalculateNormalizedDifference(class1.NestedStructCount, class2.NestedStructCount, 10)) * Tuning.ClassWeights.NestedStructCount + // Max 10 (consider ClassNormalization)
                (1.0 - CalculateNormalizedDifference(class1.NestedEnumCount, class2.NestedEnumCount, 10)) * Tuning.ClassWeights.NestedEnumCount + // Max 10 (consider ClassNormalization)
                (1.0 - CalculateNormalizedDifference(class1.NestedInterfaceCount, class2.NestedInterfaceCount, 10)) * Tuning.ClassWeights.NestedInterfaceCount + // Max 10 (consider ClassNormalization)
                (1.0 - CalculateNormalizedDifference(class1.AverageMethodComplexity, class2.AverageMethodComplexity, Tuning.ClassNormalization.MaxAverageMethodComplexity)) * Tuning.ClassWeights.AverageMethodComplexity +
                (1.0 - CalculateNormalizedDifference(class1.TotalLinesOfCode, class2.TotalLinesOfCode, 2000)) * Tuning.ClassWeights.TotalLinesOfCode; // Assuming max 2000 LOC (consider ClassNormalization)

            // Ensure total weight is not zero to prevent division by zero if all weights are somehow zero.
            double totalWeight = Tuning.ClassWeights.TotalWeight;
            return totalWeight > 0 ? totalWeightedScore / totalWeight : 0.0;
        }

        private double CalculateJaccardSimilarity<T>(ICollection<T> set1, ICollection<T> set2) {
            if (!set1.Any() && !set2.Any()) return 1.0;
            if (!set1.Any() || !set2.Any()) return 0.0;

            var intersection = set1.Intersect(set2).Count();
            var union = set1.Union(set2).Count();
            return union > 0 ? (double)intersection / union : 0.0;
        }

        private double CalculateNormalizedDifference(double val1, double val2, double maxValue) {
            if (maxValue == 0.0) {
                return (val1 == val2) ? 0.0 : 1.0;
            }
            double diff = Math.Abs(val1 - val2);
            return diff / maxValue;
        }

        private void AddTypeAndNamespaceIfExternal(
            ITypeSymbol? typeSymbol,
            INamedTypeSymbol containingClassSymbol,
            HashSet<string> externalTypeFqns,
            HashSet<string> usedNamespaceFqns) {
            if (typeSymbol == null || typeSymbol.TypeKind == TypeKind.Error || typeSymbol.SpecialType == SpecialType.System_Void) {
                return;
            }

            // Add namespace
            if (typeSymbol.ContainingNamespace != null && !typeSymbol.ContainingNamespace.IsGlobalNamespace) {
                usedNamespaceFqns.Add(typeSymbol.ContainingNamespace.ToDisplayString(ToolHelpers.FullyQualifiedFormatWithoutGlobal));
            }

            // Add type if external
            if (typeSymbol.ContainingAssembly != null && containingClassSymbol.ContainingAssembly != null &&
                !SymbolEqualityComparer.Default.Equals(typeSymbol.ContainingAssembly, containingClassSymbol.ContainingAssembly)) {
                externalTypeFqns.Add(typeSymbol.OriginalDefinition.ToDisplayString(ToolHelpers.FullyQualifiedFormatWithoutGlobal));
            }

            // Handle generic type arguments
            if (typeSymbol is INamedTypeSymbol namedTypeSymbol && namedTypeSymbol.IsGenericType) {
                foreach (var typeArg in namedTypeSymbol.TypeArguments) {
                    AddTypeAndNamespaceIfExternal(typeArg, containingClassSymbol, externalTypeFqns, usedNamespaceFqns);
                }
            }
            // Handle array element type
            if (typeSymbol is IArrayTypeSymbol arrayTypeSymbol) {
                AddTypeAndNamespaceIfExternal(arrayTypeSymbol.ElementType, containingClassSymbol, externalTypeFqns, usedNamespaceFqns);
            }
            // Handle pointer element type
            if (typeSymbol is IPointerTypeSymbol pointerTypeSymbol) {
                AddTypeAndNamespaceIfExternal(pointerTypeSymbol.PointedAtType, containingClassSymbol, externalTypeFqns, usedNamespaceFqns);
            }
        }
    }
}

```

--------------------------------------------------------------------------------
/SharpTools.Tools/Mcp/Tools/SolutionTools.cs:
--------------------------------------------------------------------------------

```csharp
using ModelContextProtocol;
using SharpTools.Tools.Services;

namespace SharpTools.Tools.Mcp.Tools;

using System.Xml;
using System.Xml.Linq;
using Microsoft.CodeAnalysis.CSharp.Syntax;

// Marker class for ILogger<T> category specific to SolutionTools
public class SolutionToolsLogCategory { }

[McpServerToolType]
public static class SolutionTools {

    private const int MaxOutputLength = 50000;
    private enum DetailLevel {
        Full,
        NoConstantFieldNames,
        NoCommonDerivedOrImplementedClasses,
        NoEventEnumNames,
        NoMethodParamTypes,
        NoPropertyTypes,
        NoMethodParamNames,
        FiftyPercentPropertyNames,
        NoPropertyNames,
        FiftyPercentMethodNames,
        NoMethodNames,
        NamespacesAndTypesOnly
    }
    private const string LoadSolutionDescriptionText =
        "The the `SharpTool` suite provides you with focused, high quality, and high information density dotnet analysis and editing tools. " +
        "When using `SharpTool`s, you focus on individual components, and navigate with type hierarchies and call graphs instead of raw code. " +
        "Because of this, you create more modular, coherent, composable, type-safe, and thus inherently correct code. " +
        $"`{ToolHelpers.SharpToolPrefix}{nameof(LoadSolution)}` is the entry point for the suite, and should be called once at the beginning of your session to initialize the other tools with data from the solution.";

    [McpServerTool(Name = ToolHelpers.SharpToolPrefix + nameof(LoadSolution), Idempotent = true, Destructive = false, OpenWorld = false, ReadOnly = true)]
    [Description(LoadSolutionDescriptionText)]
    public static async Task<object> LoadSolution(
    ISolutionManager solutionManager,
    IEditorConfigProvider editorConfigProvider,
    ILogger<SolutionToolsLogCategory> logger,
    [Description("The absolute file path to the .sln or .slnx solution file.")] string solutionPath,
    CancellationToken cancellationToken) {

        return await ErrorHandlingHelpers.ExecuteWithErrorHandlingAsync(async () => {
            ErrorHandlingHelpers.ValidateStringParameter(solutionPath, "solutionPath", logger);
            logger.LogInformation("Executing '{LoadSolution}' tool for path: {SolutionPath}", nameof(LoadSolution), solutionPath);

            // Validate solution file exists and has correct extension
            if (!File.Exists(solutionPath)) {
                logger.LogError("Solution file not found at path: {SolutionPath}", solutionPath);
                throw new McpException($"Solution file does not exist at path: {solutionPath}");
            }

            var ext = Path.GetExtension(solutionPath);
            if (!ext.Equals(".sln", StringComparison.OrdinalIgnoreCase) &&
                !ext.Equals(".slnx", StringComparison.OrdinalIgnoreCase)) {
                logger.LogError("File is not a valid solution file: {SolutionPath}", solutionPath);
                throw new McpException($"File at path '{solutionPath}' is not a .sln or .slnx file.");
            }

            try {
                await solutionManager.LoadSolutionAsync(solutionPath, cancellationToken);
            } catch (Exception ex) when (!(ex is McpException || ex is OperationCanceledException)) {
                logger.LogError(ex, "Failed to load solution at {SolutionPath}", solutionPath);
                throw new McpException($"Failed to load solution: {ex.Message}");
            }

            // Get solution directory and initialize editor config
            var solutionDir = Path.GetDirectoryName(solutionPath);
            if (string.IsNullOrEmpty(solutionDir)) {
                logger.LogWarning(".editorconfig provider could not determine solution directory from path: {SolutionPath}", solutionPath);
                throw new McpException($"Could not determine directory for solution path: {solutionPath}");
            }

            try {
                await editorConfigProvider.InitializeAsync(solutionDir, cancellationToken);
            } catch (Exception ex) when (!(ex is McpException || ex is OperationCanceledException)) {
                // Log but don't fail - editor config is helpful but not critical
                logger.LogWarning(ex, "Failed to initialize .editorconfig from {SolutionDir}", solutionDir);
                // Continue execution, don't throw
            }

            var projectCount = solutionManager.GetProjects().Count();
            var successMessage = $"Solution '{Path.GetFileName(solutionPath)}' loaded successfully with {projectCount} project(s). Caches and .editorconfig initialized.";
            logger.LogInformation(successMessage);

            try {
                return await GetProjectStructure(solutionManager, logger, cancellationToken);
            } catch (Exception ex) when (!(ex is McpException || ex is OperationCanceledException)) {
                logger.LogWarning(ex, "Successfully loaded solution but failed to retrieve project structure");
                // Return basic info instead of detailed structure
                return ToolHelpers.ToJson(new {
                    solutionName = Path.GetFileName(solutionPath),
                    projectCount,
                    status = "Solution loaded successfully, but project structure retrieval failed."
                });
            }
        }, logger, nameof(LoadSolution), cancellationToken);
    }
    private static async Task<object> GetProjectStructure(
    ISolutionManager solutionManager,
    ILogger<SolutionToolsLogCategory> logger,
    CancellationToken cancellationToken) {

        return await ErrorHandlingHelpers.ExecuteWithErrorHandlingAsync(async () => {
            ToolHelpers.EnsureSolutionLoaded(solutionManager);

            var projectsData = new List<object>();

            try {
                foreach (var project in solutionManager.GetProjects()) {
                    cancellationToken.ThrowIfCancellationRequested();

                    try {
                        var compilation = await solutionManager.GetCompilationAsync(project.Id, cancellationToken);
                        var targetFramework = "Unknown";

                        // Get the actual target framework from the project file
                        if (!string.IsNullOrEmpty(project.FilePath) && File.Exists(project.FilePath)) {
                            targetFramework = ExtractTargetFrameworkFromProjectFile(project.FilePath);
                        }

                        // Get top level namespaces
                        var topLevelNamespaces = new HashSet<string>();

                        try {
                            foreach (var document in project.Documents) {
                                if (document.SourceCodeKind != SourceCodeKind.Regular || !document.SupportsSyntaxTree) {
                                    continue;
                                }

                                try {
                                    var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken);
                                    if (syntaxRoot == null) {
                                        continue;
                                    }
                                    foreach (var nsNode in syntaxRoot.DescendantNodes().OfType<BaseNamespaceDeclarationSyntax>()) {
                                        topLevelNamespaces.Add(nsNode.Name.ToString());
                                    }
                                } catch (Exception ex) {
                                    logger.LogWarning(ex, "Error getting namespaces from document {DocumentPath}", document.FilePath);
                                    // Continue with other documents
                                }
                            }
                        } catch (Exception ex) {
                            logger.LogWarning(ex, "Error getting namespaces for project {ProjectName}", project.Name);
                            // Continue with basic project info
                        }

                        // Get project references safely
                        var projectRefs = new List<string>();
                        try {
                            if (solutionManager.CurrentSolution != null) {
                                projectRefs = project.ProjectReferences
                                .Select(pr => solutionManager.CurrentSolution.GetProject(pr.ProjectId)?.Name)
                                .Where(name => name != null)
                                .OrderBy(name => name)
                                .ToList()!;
                            }
                        } catch (Exception ex) {
                            logger.LogWarning(ex, "Error getting project references for {ProjectName}", project.Name);
                            // Continue with empty project references
                        }

                        // Get NuGet package references from project file (with enhanced format detection)
                        var packageRefs = new List<string>();
                        try {
                            if (!string.IsNullOrEmpty(project.FilePath) && File.Exists(project.FilePath)) {
                                // Get all packages
                                var packages = Services.LegacyNuGetPackageReader.GetAllPackages(project.FilePath);
                                foreach (var package in packages) {
                                    packageRefs.Add($"{package.PackageId} ({package.Version})");
                                }
                            }
                        } catch (Exception ex) {
                            logger.LogWarning(ex, "Error getting NuGet package references for {ProjectName}", project.Name);
                            // Continue with empty package references
                        }

                        // Build namespace hierarchy as a nested tree representation
                        var namespaceTree = new Dictionary<string, HashSet<string>>();

                        foreach (var ns in topLevelNamespaces) {
                            var parts = ns.Split('.');
                            var current = "";

                            for (int i = 0; i < parts.Length; i++) {
                                var part = parts[i];
                                var nextNamespace = string.IsNullOrEmpty(current) ? part : $"{current}.{part}";

                                if (!namespaceTree.TryGetValue(current, out var children)) {
                                    children = new HashSet<string>();
                                    namespaceTree[current] = children;
                                }

                                children.Add(part);
                                current = nextNamespace;
                            }
                        }

                        // Format the namespace tree as a string representation
                        var namespaceTreeBuilder = new StringBuilder();
                        BuildNamespaceTreeString("", namespaceTree, namespaceTreeBuilder);
                        var namespaceStructure = namespaceTreeBuilder.ToString();

                        // Local function to recursively build the tree string
                        void BuildNamespaceTreeString(string current, Dictionary<string, HashSet<string>> tree, StringBuilder builder) {
                            if (!tree.TryGetValue(current, out var children) || children.Count == 0) {
                                return;
                            }

                            bool first = true;
                            foreach (var child in children.OrderBy(c => c)) {
                                if (!first) {
                                    builder.Append(',');
                                }
                                first = false;

                                builder.Append(child);

                                string nextNamespace = string.IsNullOrEmpty(current) ? child : $"{current}.{child}";
                                if (tree.ContainsKey(nextNamespace)) {
                                    builder.Append('{');
                                    BuildNamespaceTreeString(nextNamespace, tree, builder);
                                    builder.Append('}');
                                }
                            }
                        }

                        // Build the project data
                        projectsData.Add(new {
                            name = project.Name + (project.AssemblyName.Equals(project.Name, StringComparison.OrdinalIgnoreCase) ? "" : $" ({project.AssemblyName})"),
                            version = project.Version.ToString(),
                            targetFramework,
                            namespaces = namespaceStructure,
                            documentCount = project.DocumentIds.Count,
                            projectReferences = projectRefs,
                            packageReferences = packageRefs
                        });
                    } catch (Exception ex) when (!(ex is OperationCanceledException)) {
                        logger.LogWarning(ex, "Error processing project {ProjectName}, adding basic info only", project.Name);
                        // Add minimal project info when there's an error
                        projectsData.Add(new {
                            name = project.Name,
                            //filePath = project.FilePath,
                            language = project.Language,
                            error = $"Error processing project: {ex.Message}",
                            documentCount = project.DocumentIds.Count
                        });
                    }
                }

                // Create the result safely
                string? solutionName = null;
                try {
                    solutionName = Path.GetFileName(solutionManager.CurrentSolution?.FilePath ?? "unknown");
                } catch {
                    solutionName = "unknown";
                }

                var result = new {
                    solutionName,
                    projects = projectsData.OrderBy(p => ((dynamic)p).name).ToList(),
                    nextStep = $"Use `{ToolHelpers.SharpToolPrefix}{nameof(LoadProject)}` to get a detailed view of a specific project's structure."
                };

                logger.LogInformation("Project structure retrieved successfully for {ProjectCount} projects.", projectsData.Count);
                return ToolHelpers.ToJson(result);
            } catch (Exception ex) when (!(ex is McpException || ex is OperationCanceledException)) {
                logger.LogError(ex, "Error retrieving project structure");
                throw new McpException($"Failed to retrieve project structure: {ex.Message}");
            }
        }, logger, nameof(GetProjectStructure), cancellationToken);
    }
    public static string ExtractTargetFrameworkFromProjectFile(string projectFilePath) {
        try {
            if (string.IsNullOrEmpty(projectFilePath)) {
                return "Unknown";
            }

            if (!File.Exists(projectFilePath)) {
                return "Unknown";
            }

            var xDoc = XDocument.Load(projectFilePath);

            // New-style .csproj (SDK-style)
            var propertyGroupElements = xDoc.Descendants("PropertyGroup");
            foreach (var propertyGroup in propertyGroupElements) {
                var targetFrameworkElement = propertyGroup.Element("TargetFramework");
                if (targetFrameworkElement != null) {
                    var value = targetFrameworkElement.Value.Trim();
                    return !string.IsNullOrEmpty(value) ? value : "Unknown";
                }

                var targetFrameworksElement = propertyGroup.Element("TargetFrameworks");
                if (targetFrameworksElement != null) {
                    var value = targetFrameworksElement.Value.Trim();
                    return !string.IsNullOrEmpty(value) ? value : "Unknown";
                }
            }

            // Old-style .csproj format
            var targetFrameworkVersionElement = xDoc.Descendants("TargetFrameworkVersion").FirstOrDefault();
            if (targetFrameworkVersionElement != null) {
                var version = targetFrameworkVersionElement.Value.Trim();

                // Map from old-style version format (v4.x) to new-style (.NETFramework,Version=v4.x)
                if (!string.IsNullOrEmpty(version)) {
                    if (version.StartsWith("v")) {
                        return $"net{version.Substring(1).Replace(".", "")}";
                    }
                    return version;
                }
            }

            // Additional old-style property check
            var targetFrameworkProfile = xDoc.Descendants("TargetFrameworkProfile").FirstOrDefault()?.Value?.Trim();
            var targetFrameworkIdentifier = xDoc.Descendants("TargetFrameworkIdentifier").FirstOrDefault()?.Value?.Trim();

            if (!string.IsNullOrEmpty(targetFrameworkIdentifier)) {
                // Parse the old-style framework identifier
                if (targetFrameworkIdentifier.Contains(".NETFramework")) {
                    var version = xDoc.Descendants("TargetFrameworkVersion").FirstOrDefault()?.Value?.Trim();
                    if (!string.IsNullOrEmpty(version) && version.StartsWith("v")) {
                        return $"net{version.Substring(1).Replace(".", "")}";
                    }
                } else if (targetFrameworkIdentifier.Contains(".NETCore")) {
                    var version = xDoc.Descendants("TargetFrameworkVersion").FirstOrDefault()?.Value?.Trim();
                    if (!string.IsNullOrEmpty(version) && version.StartsWith("v")) {
                        return $"netcoreapp{version.Substring(1).Replace(".", "")}";
                    }
                } else if (targetFrameworkIdentifier.Contains(".NETStandard")) {
                    var version = xDoc.Descendants("TargetFrameworkVersion").FirstOrDefault()?.Value?.Trim();
                    if (!string.IsNullOrEmpty(version) && version.StartsWith("v")) {
                        return $"netstandard{version.Substring(1).Replace(".", "")}";
                    }
                }

                // Add profile if present
                if (!string.IsNullOrEmpty(targetFrameworkProfile)) {
                    return $"{targetFrameworkIdentifier},{targetFrameworkProfile}";
                }

                return targetFrameworkIdentifier;
            }

            return "Unknown";
        } catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or SecurityException) {
            // File access issues
            return "Unknown (Access Error)";
        } catch (Exception ex) when (ex is XmlException) {
            // XML parsing issues
            return "Unknown (XML Error)";
        } catch (Exception) {
            // Any other exceptions
            return "Unknown";
        }
    }
    [McpServerTool(Name = ToolHelpers.SharpToolPrefix + nameof(LoadProject), ReadOnly = true, OpenWorld = false, Destructive = false, Idempotent = false)]
    [Description($"Use this immediately after {nameof(LoadSolution)}. This injects a comprehensive understanding of the project structure into your context.")]
    public static async Task<object> LoadProject(
        ISolutionManager solutionManager,
        ILogger<SolutionToolsLogCategory> logger,
        ICodeAnalysisService codeAnalysisService,
        string projectName,
        CancellationToken cancellationToken) {

        return await ErrorHandlingHelpers.ExecuteWithErrorHandlingAsync(async () => {
            ErrorHandlingHelpers.ValidateStringParameter(projectName, "projectName", logger);
            logger.LogInformation("Executing '{LoadProjectToolName}' tool for project: {ProjectName}", nameof(LoadProject), projectName);

            ToolHelpers.EnsureSolutionLoadedWithDetails(solutionManager, logger, nameof(LoadProject));
            int indexOfParen = projectName.IndexOf('(');
            string projectNameNormalized = indexOfParen == -1
                ? projectName.Trim()
                : projectName[..indexOfParen].Trim();

            var project = solutionManager.GetProjects().FirstOrDefault(
                p => p.Name == projectName
                || p.AssemblyName == projectName
                || p.Name == projectNameNormalized);
            if (project == null) {
                logger.LogError("Project '{ProjectName}' not found in the loaded solution", projectName);
                throw new McpException($"Project '{projectName}' not found in the solution.");
            }

            cancellationToken.ThrowIfCancellationRequested();
            logger.LogDebug("Processing project: {ProjectName}", project.Name);

            // Get the compilation for the project
            Compilation? compilation;
            try {
                compilation = await solutionManager.GetCompilationAsync(project.Id, cancellationToken);
                if (compilation == null) {
                    logger.LogError("Failed to get compilation for project: {ProjectName}", project.Name);
                    throw new McpException($"Failed to get compilation for project: {project.Name}");
                }
            } catch (Exception ex) when (!(ex is McpException || ex is OperationCanceledException)) {
                logger.LogError(ex, "Error getting compilation for project {ProjectName}", project.Name);
                throw new McpException($"Error getting compilation for project '{project.Name}': {ex.Message}");
            }

            // For display (excludes nested types)
            var namespaceContents = new Dictionary<string, List<INamedTypeSymbol>>();
            var typesByNamespace = new Dictionary<string, List<INamedTypeSymbol>>();

            // For analysis (includes all types including nested)
            var allNamespaceContents = new Dictionary<string, List<INamedTypeSymbol>>();
            var allTypesByNamespace = new Dictionary<string, List<INamedTypeSymbol>>();

            try {
                // First pass: Collect all source symbols and organize types by namespace
                foreach (var document in project.Documents) {
                    cancellationToken.ThrowIfCancellationRequested();

                    if (document.SourceCodeKind != SourceCodeKind.Regular || !document.SupportsSyntaxTree) {
                        continue;
                    }

                    try {
                        var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken);
                        if (syntaxTree == null) continue;

                        var semanticModel = compilation.GetSemanticModel(syntaxTree);
                        if (semanticModel == null) continue;

                        var root = await syntaxTree.GetRootAsync(cancellationToken);

                        // Get all type declarations
                        foreach (var typeDecl in root.DescendantNodes().OfType<BaseTypeDeclarationSyntax>()) {
                            cancellationToken.ThrowIfCancellationRequested();

                            try {
                                if (semanticModel.GetDeclaredSymbol(typeDecl, cancellationToken) is INamedTypeSymbol symbol) {
                                    var nsName = symbol.ContainingNamespace?.ToDisplayString() ?? "global";

                                    // Always add to the "all types" collection for analysis
                                    if (!allTypesByNamespace.TryGetValue(nsName, out var allTypeList)) {
                                        allTypeList = new List<INamedTypeSymbol>();
                                        allTypesByNamespace[nsName] = allTypeList;
                                    }
                                    allTypeList.Add(symbol);

                                    // Only add non-nested types to the display collection
                                    if (symbol.ContainingType == null) {
                                        if (!typesByNamespace.TryGetValue(nsName, out var typeList)) {
                                            typeList = new List<INamedTypeSymbol>();
                                            typesByNamespace[nsName] = typeList;
                                        }
                                        typeList.Add(symbol);
                                    }
                                }
                            } catch (Exception ex) when (!(ex is OperationCanceledException)) {
                                logger.LogWarning(ex, "Error processing type declaration in document {DocumentPath}", document.FilePath);
                            }
                        }
                    } catch (Exception ex) when (!(ex is OperationCanceledException)) {
                        logger.LogWarning(ex, "Error processing document {DocumentPath}", document.FilePath);
                    }
                }

                // Populate the display namespace contents
                foreach (var nsEntry in typesByNamespace) {
                    if (!namespaceContents.TryGetValue(nsEntry.Key, out var globalTypeList)) {
                        globalTypeList = new List<INamedTypeSymbol>();
                        namespaceContents[nsEntry.Key] = globalTypeList;
                    }
                    globalTypeList.AddRange(nsEntry.Value);
                }

                // Populate the analysis namespace contents
                foreach (var nsEntry in allTypesByNamespace) {
                    if (!allNamespaceContents.TryGetValue(nsEntry.Key, out var globalTypeList)) {
                        globalTypeList = new List<INamedTypeSymbol>();
                        allNamespaceContents[nsEntry.Key] = globalTypeList;
                    }
                    globalTypeList.AddRange(nsEntry.Value);
                }

                // Collect derived/implemented type information for the NoCommonDerivedOrImplementedClasses detail level
                // Use the all types collection for analysis to include nested types
                var derivedTypesByNamespace = await CollectDerivedAndImplementedCounts(allNamespaceContents, codeAnalysisService, logger, cancellationToken);
                var commonImplementationInfo = new CommonImplementationInfo(derivedTypesByNamespace);

                logger.LogInformation("Found {ImplementationCount} types with derived classes or implementations. Mean count: {MeanCount:F2}, Common base types: {CommonBaseCount}",
                    commonImplementationInfo.TotalImplementationCounts.Count,
                    commonImplementationInfo.MedianImplementationCount,
                    commonImplementationInfo.CommonBaseTypes.Count);

                var structureBuilder = new StringBuilder();
                DetailLevel currentDetailLevel = DetailLevel.Full;
                string output = "";
                bool lengthAcceptable = false;
                Random random = new Random();

                while (!lengthAcceptable && currentDetailLevel <= DetailLevel.NamespacesAndTypesOnly) {
                    structureBuilder.Clear();
                    var sortedNamespaces = namespaceContents.Keys.OrderBy(ns => ns).ToList();
                    var namespaceParts = BuildNamespaceHierarchy(sortedNamespaces, namespaceContents, logger);
                    var rootNamespaces = namespaceParts.Keys.Where(ns => ns.IndexOf('.') == -1).OrderBy(n => n).ToList();

                    foreach (var rootNs in rootNamespaces) {
                        structureBuilder.Append(BuildNamespaceStructureText(rootNs, namespaceParts, namespaceContents, logger, currentDetailLevel, random, commonImplementationInfo));
                    }

                    output = structureBuilder.ToString();

                    if (output.Length <= MaxOutputLength) {
                        lengthAcceptable = true;
                    } else {
                        logger.LogInformation("Output string length ({Length}) exceeds limit ({Limit}). Reducing detail from {OldLevel} to {NewLevel}.", output.Length, MaxOutputLength, currentDetailLevel, currentDetailLevel + 1);
                        currentDetailLevel++;
                        if (currentDetailLevel > DetailLevel.NamespacesAndTypesOnly) {
                            logger.LogWarning("Even at the most compressed level, output length ({Length}) exceeds limit ({Limit}). Returning compressed output.", output.Length, MaxOutputLength);
                        }
                    }
                }

                return $"<typeTree note=\"Use {ToolHelpers.SharpToolPrefix}{nameof(AnalysisTools.GetMembers)} for more detailed information about specific types.\">" +
                    output +
                    "\n</typeTree>";

            } catch (OperationCanceledException) {
                logger.LogInformation("Operation was cancelled while analyzing project {ProjectName}", project.Name);
                throw;
            } catch (Exception ex) when (!(ex is McpException)) {
                logger.LogError(ex, "Error analyzing project structure for {ProjectName}", project.Name);
                throw new McpException($"Error analyzing project structure: {ex.Message}");
            }
        }, logger, nameof(LoadProject), cancellationToken);
    }
    private static Dictionary<string, Dictionary<string, List<INamedTypeSymbol>>> BuildNamespaceHierarchy(
                    List<string> sortedNamespaces,
                    Dictionary<string, List<INamedTypeSymbol>> namespaceContents,
                    ILogger<SolutionToolsLogCategory> logger) {

        // Process namespaces to build the hierarchy
        var namespaceParts = new Dictionary<string, Dictionary<string, List<INamedTypeSymbol>>>();

        foreach (var fullNamespace in sortedNamespaces) {
            try {
                // Skip empty global namespace
                if (string.IsNullOrEmpty(fullNamespace) || fullNamespace == "global") {
                    continue;
                }

                // Split namespace into parts
                var parts = fullNamespace.Split('.');

                // Create entries for each namespace part
                var currentNs = "";

                for (int i = 0; i < parts.Length; i++) {
                    var part = parts[i];

                    if (!string.IsNullOrEmpty(currentNs)) {
                        currentNs += ".";
                    }
                    currentNs += part;

                    if (!namespaceParts.TryGetValue(currentNs, out var children)) {
                        children = new Dictionary<string, List<INamedTypeSymbol>>();
                        namespaceParts[currentNs] = children;
                    }

                    // If not the last part, add the next part as child namespace
                    if (i < parts.Length - 1) {
                        var nextPart = parts[i + 1];
                        if (!children.ContainsKey(nextPart)) {
                            children[nextPart] = new List<INamedTypeSymbol>();
                        }
                    }
                }

                // Add types to the leaf namespace
                if (namespaceContents.TryGetValue(fullNamespace, out var types) && types.Any()) {
                    var leafNsParts = namespaceParts[fullNamespace];
                    foreach (var type in types) {
                        var typeName = type.Name;
                        if (!leafNsParts.TryGetValue(typeName, out var typeList)) {
                            typeList = new List<INamedTypeSymbol>();
                            leafNsParts[typeName] = typeList;
                        }
                        typeList.Add(type);
                    }
                }
            } catch (Exception ex) {
                logger.LogWarning(ex, "Error processing namespace {Namespace} in hierarchy", fullNamespace);
            }
        }
        return namespaceParts;
    }
    private static string BuildNamespaceStructureText(
        string namespaceName,
        Dictionary<string, Dictionary<string, List<INamedTypeSymbol>>> namespaceParts,
        Dictionary<string, List<INamedTypeSymbol>> namespaceContents,
        ILogger<SolutionToolsLogCategory> logger,
        DetailLevel detailLevel,
        Random random,
        CommonImplementationInfo? commonImplementationInfo = null) {

        var sb = new StringBuilder();
        try {
            var simpleName = namespaceName.Contains('.')
                ? namespaceName.Substring(namespaceName.LastIndexOf('.') + 1)
                : namespaceName;

            sb.Append('\n').Append(simpleName).Append('{');

            // If we're at NoCommonDerivedOrImplementedClasses level or above
            // show derived class counts for common base types in this namespace
            if (commonImplementationInfo != null &&
                detailLevel >= DetailLevel.NoCommonDerivedOrImplementedClasses) {

                // Build a dictionary of base types to their derived classes in this namespace
                var derivedCountsInNamespace = new Dictionary<INamedTypeSymbol, int>(SymbolEqualityComparer.Default);

                foreach (var baseType in commonImplementationInfo.CommonBaseTypes) {
                    if (commonImplementationInfo.DerivedTypesByNamespace.TryGetValue(baseType, out var derivedByNs) &&
                        derivedByNs.TryGetValue(namespaceName, out var derivedTypes) &&
                        derivedTypes.Count > 0) {

                        derivedCountsInNamespace[baseType] = derivedTypes.Count;
                    }
                }

                // If there are any derived classes from common base types in this namespace, show their counts
                if (derivedCountsInNamespace.Count > 0) {
                    foreach (var entry in derivedCountsInNamespace) {
                        var baseType = entry.Key;
                        var count = entry.Value;
                        string typeKindStr = baseType.TypeKind == TypeKind.Interface ? "implementation" : "derived class";
                        string baseTypeName = CommonImplementationInfo.GetTypeDisplayName(baseType);

                        sb.Append($"\n  {count} {typeKindStr}{(count == 1 ? "" : "es")} of {baseTypeName};");
                    }
                }
            }

            var typesInNamespace = namespaceContents.GetValueOrDefault(namespaceName);
            var typeContent = new StringBuilder();

            if (typesInNamespace != null) {
                foreach (var type in typesInNamespace.OrderBy(t => t.Name)) {
                    try {
                        var typeStructure = BuildTypeStructure(type, logger, detailLevel, random, 1, commonImplementationInfo);
                        if (!string.IsNullOrEmpty(typeStructure)) { // Skip empty results (filtered derived types)
                            typeContent.Append(typeStructure);
                        }
                    } catch (Exception ex) {
                        logger.LogWarning(ex, "Error building structure for type {TypeName} in namespace {Namespace}", type.Name, namespaceName);
                        typeContent.Append($"\n{new string(' ', 2 * 1)}{type.Name}{{/* Error: {ex.Message} */}}");
                    }
                }
            }

            var childNamespaceContent = new StringBuilder();
            if (namespaceParts.TryGetValue(namespaceName, out var children)) {
                foreach (var child in children.OrderBy(c => c.Key)) {
                    if (child.Value?.Count == 0) { // This indicates a child namespace rather than a type within the current namespace
                        var childNamespace = namespaceName + "." + child.Key;
                        try {
                            childNamespaceContent.Append(BuildNamespaceStructureText(childNamespace, namespaceParts, namespaceContents, logger, detailLevel, random, commonImplementationInfo));
                        } catch (Exception ex) {
                            logger.LogWarning(ex, "Error building structure for child namespace {Namespace}", childNamespace);
                            childNamespaceContent.Append($"\n{child.Key}{{/* Error: {ex.Message} */}}");
                        }
                    }
                }
            }

            sb.Append(typeContent);
            sb.Append(childNamespaceContent);
            sb.Append("\n}");

        } catch (Exception ex) {
            logger.LogError(ex, "Error building namespace structure text for {Namespace}", namespaceName);
            return $"\n{namespaceName}{{/* Error: {ex.Message} */}}";
        }
        return sb.ToString();
    }
    private static string BuildTypeStructure(
            INamedTypeSymbol type,
            ILogger<SolutionToolsLogCategory> logger,
            DetailLevel detailLevel,
            Random random,
            int indentLevel,
            CommonImplementationInfo? commonImplementationInfo = null) {

        var sb = new StringBuilder();
        var indent = string.Empty; // new string(' ', 2 * indentLevel);
        try {
            // Skip derived classes that are part of a common base type at NoCommonDerivedOrImplementedClasses level or above
            if (commonImplementationInfo != null &&
                detailLevel >= DetailLevel.NoCommonDerivedOrImplementedClasses) {

                // Check if this type inherits from or implements a common base type
                bool shouldSkip = false;
                foreach (var commonBaseType in commonImplementationInfo.CommonBaseTypes) {
                    // Check if this type directly inherits from a common base type
                    if (SymbolEqualityComparer.Default.Equals(type.BaseType, commonBaseType)) {
                        shouldSkip = true;
                        break;
                    }

                    // Check if this type implements a common interface
                    foreach (var iface in type.AllInterfaces) {
                        if (SymbolEqualityComparer.Default.Equals(iface, commonBaseType)) {
                            shouldSkip = true;
                            break;
                        }
                    }

                    if (shouldSkip) {
                        break;
                    }
                }

                if (shouldSkip) {
                    return string.Empty; // Skip this type
                }
            }

            sb.Append('\n').Append(indent).Append(type.Name);

            if (type.TypeParameters.Length > 0 && detailLevel < DetailLevel.NamespacesAndTypesOnly) {
                sb.Append('<').Append(type.TypeParameters.Length).Append('>');
            }
            sb.Append("{");

            if (detailLevel == DetailLevel.NamespacesAndTypesOnly) {
                foreach (var nestedType in type.GetTypeMembers().OrderBy(t => t.Name)) {
                    try {
                        sb.Append(BuildTypeStructure(nestedType, logger, detailLevel, random, indentLevel + 1, commonImplementationInfo));
                    } catch (Exception ex) {
                        logger.LogWarning(ex, "Error building structure for nested type {TypeName} in {ParentType}", nestedType.Name, type.Name);
                        sb.Append($"\n{new string(' ', 2 * (indentLevel + 1))}{nestedType.Name}{{/* Error: {ex.Message} */}}");
                    }
                }
                sb.Append('\n').Append(indent).Append("}");
                return sb.ToString();
            }

            // Regular member info for non-common base types
            var membersContent = AppendMemberInfo(sb, type, logger, detailLevel, random, indent);

            // Nested Types
            foreach (var nestedType in type.GetTypeMembers().OrderBy(t => t.Name)) {
                try {
                    sb.Append(BuildTypeStructure(nestedType, logger, detailLevel, random, indentLevel + 1, commonImplementationInfo));
                } catch (Exception ex) {
                    logger.LogWarning(ex, "Error building structure for nested type {TypeName} in {ParentType}", nestedType.Name, type.Name);
                    sb.Append($"\n{new string(' ', 2 * (indentLevel + 1))}{nestedType.Name}{{/* Error: {ex.Message} */}}");
                }
            }

            if (membersContent || type.GetTypeMembers().Any()) {
                sb.Append('\n').Append(indent).Append("}");
            } else {
                sb.Append("}"); // No newline if type is empty and no members shown
            }

        } catch (Exception ex) {
            logger.LogError(ex, "Error building structure for type {TypeName}", type.Name);
            return $"\n{indent}{type.Name}{{/* Error: {ex.Message} */}}";
        }
        return sb.ToString();
    }
    private static string GetTypeShortName(ITypeSymbol type) {
        try {
            if (type == null) return "?";

            if (type.SpecialType != SpecialType.None) {
                return type.SpecialType switch {
                    SpecialType.System_Boolean => "bool",
                    SpecialType.System_Byte => "byte",
                    SpecialType.System_SByte => "sbyte",
                    SpecialType.System_Char => "char",
                    SpecialType.System_Int16 => "short",
                    SpecialType.System_UInt16 => "ushort",
                    SpecialType.System_Int32 => "int",
                    SpecialType.System_UInt32 => "uint",
                    SpecialType.System_Int64 => "long",
                    SpecialType.System_UInt64 => "ulong",
                    SpecialType.System_Single => "float",
                    SpecialType.System_Double => "double",
                    SpecialType.System_Decimal => "decimal",
                    SpecialType.System_String => "string",
                    SpecialType.System_Object => "object",
                    SpecialType.System_Void => "void",
                    _ => type.Name
                };
            }

            if (type is IArrayTypeSymbol arrayType) {
                return $"{GetTypeShortName(arrayType.ElementType)}[]";
            }

            if (type is INamedTypeSymbol namedType) {
                if (namedType.IsTupleType && namedType.TupleElements.Any()) {
                    return $"({string.Join(", ", namedType.TupleElements.Select(te => $"{GetTypeShortName(te.Type)} {te.Name}"))})";
                }
                if (namedType.TypeArguments.Length > 0) {
                    var typeArgs = string.Join(", ", namedType.TypeArguments.Select(GetTypeShortName));
                    var baseName = namedType.Name;
                    // Handle common nullable syntax
                    if (namedType.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T) {
                        return $"{GetTypeShortName(namedType.TypeArguments[0])}?";
                    }
                    return $"{baseName}<{typeArgs}>";
                }
            }

            return type.Name;
        } catch (Exception) {
            return type?.Name ?? "?";
        }
    }
    private static async Task<Dictionary<INamedTypeSymbol, Dictionary<string, List<INamedTypeSymbol>>>> CollectDerivedAndImplementedCounts(
        Dictionary<string, List<INamedTypeSymbol>> namespaceContents,
        ICodeAnalysisService codeAnalysisService,
        ILogger<SolutionToolsLogCategory> logger,
        CancellationToken cancellationToken) {

        // Dictionary of base types to their derived types, organized by namespace
        var baseTypeImplementations = new Dictionary<INamedTypeSymbol, Dictionary<string, List<INamedTypeSymbol>>>(SymbolEqualityComparer.Default);
        var processedSymbols = new HashSet<INamedTypeSymbol>(SymbolEqualityComparer.Default);

        try {
            // Process each namespace and its types
            foreach (var typesList in namespaceContents.Values) {
                foreach (var typeSymbol in typesList) {
                    cancellationToken.ThrowIfCancellationRequested();

                    // Skip if we've already processed this type
                    if (processedSymbols.Contains(typeSymbol)) {
                        continue;
                    }

                    processedSymbols.Add(typeSymbol);

                    // Skip types that can't have derived classes (static, sealed, etc.) or implementations (non-interfaces)
                    if ((typeSymbol.IsStatic || typeSymbol.IsSealed) && typeSymbol.TypeKind != TypeKind.Interface) {
                        continue;
                    }

                    try {
                        var derivedTypes = new List<INamedTypeSymbol>();

                        // Find classes derived from this type
                        if (typeSymbol.TypeKind == TypeKind.Class) {
                            derivedTypes.AddRange(await codeAnalysisService.FindDerivedClassesAsync(typeSymbol, cancellationToken));
                        }

                        // Find implementations of this interface
                        if (typeSymbol.TypeKind == TypeKind.Interface) {
                            var implementations = await codeAnalysisService.FindImplementationsAsync(typeSymbol, cancellationToken);
                            foreach (var impl in implementations) {
                                if (impl is INamedTypeSymbol namedTypeImpl) {
                                    derivedTypes.Add(namedTypeImpl);
                                }
                            }
                        }

                        // Skip if there are no derived types or implementations
                        if (derivedTypes.Count == 0) {
                            continue;
                        }

                        // Group derived types by namespace
                        var byNamespace = new Dictionary<string, List<INamedTypeSymbol>>();
                        foreach (var derivedType in derivedTypes) {
                            var namespaceName = derivedType.ContainingNamespace?.ToDisplayString() ?? "global";
                            if (!byNamespace.TryGetValue(namespaceName, out var nsTypes)) {
                                nsTypes = new List<INamedTypeSymbol>();
                                byNamespace[namespaceName] = nsTypes;
                            }
                            nsTypes.Add(derivedType);
                        }

                        // Store the grouped derived types
                        baseTypeImplementations[typeSymbol] = byNamespace;
                    } catch (Exception ex) when (!(ex is OperationCanceledException)) {
                        logger.LogWarning(ex, "Error analyzing derived/implemented types for {TypeName}", typeSymbol.Name);
                    }
                }
            }
        } catch (Exception ex) when (!(ex is OperationCanceledException)) {
            logger.LogError(ex, "Error collecting derived/implemented type counts");
        }

        return baseTypeImplementations;
    }
    private class CommonImplementationInfo {
        // Maps base types to their derived/implemented types grouped by namespace
        public Dictionary<INamedTypeSymbol, Dictionary<string, List<INamedTypeSymbol>>> DerivedTypesByNamespace { get; }

        // Maps base types to their total derived/implemented type count
        public Dictionary<INamedTypeSymbol, int> TotalImplementationCounts { get; }

        // The mean number of derived/implemented types across all base types
        public double MedianImplementationCount { get; }

        // Base types with above-average number of derived/implemented types
        public HashSet<INamedTypeSymbol> CommonBaseTypes { get; }

        public CommonImplementationInfo(Dictionary<INamedTypeSymbol, Dictionary<string, List<INamedTypeSymbol>>> derivedTypesByNamespace) {
            DerivedTypesByNamespace = derivedTypesByNamespace;

            // Calculate total counts for each base type
            TotalImplementationCounts = new Dictionary<INamedTypeSymbol, int>(SymbolEqualityComparer.Default);
            foreach (var baseType in derivedTypesByNamespace.Keys) {
                int totalCount = 0;
                foreach (var nsTypes in derivedTypesByNamespace[baseType].Values) {
                    totalCount += nsTypes.Count;
                }
                TotalImplementationCounts[baseType] = totalCount;
            }

            // Calculate the mean implementation count
            if (TotalImplementationCounts.Count > 0) {
                var counts = TotalImplementationCounts.Values.OrderBy(c => c).ToList();
                MedianImplementationCount = counts.Count % 2 == 0
                    ? (counts[counts.Count / 2 - 1] + counts[counts.Count / 2]) / 2.0
                    : counts[counts.Count / 2];

                // Identify base types with above-average number of implementations
                CommonBaseTypes = new HashSet<INamedTypeSymbol>(SymbolEqualityComparer.Default);
                foreach (var pair in TotalImplementationCounts) {
                    if (pair.Value > MedianImplementationCount) {
                        CommonBaseTypes.Add(pair.Key);
                    }
                }
            } else {
                MedianImplementationCount = 0;
                CommonBaseTypes = new HashSet<INamedTypeSymbol>(SymbolEqualityComparer.Default);
            }
        }

        // Get the full qualified display name of a type for display purposes
        public static string GetTypeDisplayName(INamedTypeSymbol type) {
            return FuzzyFqnLookupService.GetSearchableString(type);
        }
    }
    // Helper method to append member information and return whether any members were added
    private static bool AppendMemberInfo(
        StringBuilder sb,
        INamedTypeSymbol type,
        ILogger<SolutionToolsLogCategory> logger,
        DetailLevel detailLevel,
        Random random,
        string indent) {

        var membersContent = new StringBuilder();
        var publicOrInternalMembers = type.GetMembers()
            .Where(m => !m.IsImplicitlyDeclared &&
                       !(m is INamedTypeSymbol) &&
                       (m.DeclaredAccessibility == Accessibility.Public ||
                        m.DeclaredAccessibility == Accessibility.Internal ||
                        m.DeclaredAccessibility == Accessibility.ProtectedOrInternal))
            .ToList();

        var fields = publicOrInternalMembers.OfType<IFieldSymbol>()
            .Where(f => !f.IsImplicitlyDeclared && !f.Name.Contains("k__BackingField") && !f.IsConst && type.TypeKind != TypeKind.Enum)
            .ToList();
        var constants = publicOrInternalMembers.OfType<IFieldSymbol>().Where(f => f.IsConst).ToList();
        var enumValues = type.TypeKind == TypeKind.Enum ? publicOrInternalMembers.OfType<IFieldSymbol>().ToList() : new List<IFieldSymbol>();
        var events = publicOrInternalMembers.OfType<IEventSymbol>().ToList();
        var properties = publicOrInternalMembers.OfType<IPropertySymbol>().ToList();
        var methods = publicOrInternalMembers.OfType<IMethodSymbol>()
            .Where(m => m.MethodKind != MethodKind.PropertyGet &&
                       m.MethodKind != MethodKind.PropertySet &&
                       m.MethodKind != MethodKind.EventAdd &&
                       m.MethodKind != MethodKind.EventRemove &&
                       !m.Name.StartsWith("<"))
            .ToList();

        // Fields
        if (fields.Any()) {
            if (detailLevel <= DetailLevel.NoConstantFieldNames) {
                foreach (var field in fields.OrderBy(f => f.Name)) {
                    membersContent.Append($"\n{indent}  {field.Name}:{GetTypeShortName(field.Type)};");
                }
            } else {
                membersContent.Append($"\n{indent}  {fields.Count} field{(fields.Count == 1 ? "" : "s")};");
            }
        }

        // Constants
        if (constants.Any()) {
            if (detailLevel < DetailLevel.NoConstantFieldNames) { // Show names if detail is Full
                foreach (var cnst in constants.OrderBy(c => c.Name)) {
                    membersContent.Append($"\n{indent}  const {cnst.Name}:{GetTypeShortName(cnst.Type)};");
                }
            } else {
                membersContent.Append($"\n{indent}  {constants.Count} constant{(constants.Count == 1 ? "" : "s")};");
            }
        }

        // Enum Members
        if (enumValues.Any()) {
            if (detailLevel < DetailLevel.NoEventEnumNames) {
                foreach (var enumVal in enumValues.OrderBy(e => e.Name)) {
                    membersContent.Append($"\n{indent}  {enumVal.Name};");
                }
            } else {
                membersContent.Append($"\n{indent}  {enumValues.Count} enum value{(enumValues.Count == 1 ? "" : "s")};");
            }
        }

        // Events
        if (events.Any()) {
            if (detailLevel < DetailLevel.NoEventEnumNames) {
                foreach (var evt in events.OrderBy(e => e.Name)) {
                    membersContent.Append($"\n{indent}  event {evt.Name}:{GetTypeShortName(evt.Type)};");
                }
            } else {
                membersContent.Append($"\n{indent}  {events.Count} event{(events.Count == 1 ? "" : "s")};");
            }
        }

        // Properties
        if (properties.Any()) {
            if (detailLevel < DetailLevel.NoPropertyTypes) { // Full, NoConstantFieldNames, NoEventEnumNames, NoMethodParamTypes
                foreach (var prop in properties.OrderBy(p => p.Name)) {
                    membersContent.Append($"\n{indent}  {prop.Name}:{GetTypeShortName(prop.Type)};");
                }
            } else if (detailLevel == DetailLevel.NoPropertyTypes || detailLevel == DetailLevel.NoMethodParamNames) { // Retain property names without types
                foreach (var prop in properties.OrderBy(p => p.Name)) {
                    membersContent.Append($"\n{indent}  {prop.Name};");
                }
            } else if (detailLevel == DetailLevel.FiftyPercentPropertyNames) {
                var shuffledProps = properties.OrderBy(_ => random.Next()).ToList();
                var propsToShow = shuffledProps.Take(Math.Max(1, properties.Count / 2)).ToList();
                foreach (var prop in propsToShow.OrderBy(p => p.Name)) {
                    membersContent.Append($"\n{indent}  {prop.Name};"); // Type omitted
                }
                if (propsToShow.Count < properties.Count) {
                    membersContent.Append($"\n{indent}  and {properties.Count - propsToShow.Count} more propert{(properties.Count - propsToShow.Count == 1 ? "y" : "ies")};");
                }
            } else if (detailLevel == DetailLevel.NoPropertyNames || detailLevel == DetailLevel.FiftyPercentMethodNames) { // Only count for NoPropertyNames or if method names are also being reduced
                membersContent.Append($"\n{indent}  {properties.Count} propert{(properties.Count == 1 ? "y" : "ies")};");
            } else if (detailLevel < DetailLevel.NamespacesAndTypesOnly) { // Default for levels more compressed than NoPropertyNames but not NamespacesAndTypesOnly (e.g. NoMethodNames)
                membersContent.Append($"\n{indent}  {properties.Count} propert{(properties.Count == 1 ? "y" : "ies")};");
            }
            // If detailLevel is NamespacesAndTypesOnly, properties are skipped entirely by the initial check.
        }

        // Methods (including constructors)
        if (methods.Any()) {
            if (detailLevel <= DetailLevel.FiftyPercentMethodNames) {
                var methodsToShow = methods;
                if (detailLevel == DetailLevel.FiftyPercentMethodNames) {
                    var shuffledMethods = methods.OrderBy(_ => random.Next()).ToList();
                    methodsToShow = shuffledMethods.Take(Math.Max(1, methods.Count / 2)).ToList();
                }
                foreach (var method in methodsToShow.OrderBy(m => m.Name)) {
                    membersContent.Append($"\n{indent}  {method.Name}");
                    if (detailLevel < DetailLevel.NoMethodParamNames) {
                        membersContent.Append("(");
                        if (method.Parameters.Length > 0) {
                            var paramStrings = method.Parameters.Select(p =>
                                detailLevel < DetailLevel.NoMethodParamTypes ? $"{p.Name}:{GetTypeShortName(p.Type)}" : p.Name
                            );
                            membersContent.Append(string.Join(", ", paramStrings));
                        }
                        membersContent.Append(")");
                    } else if (method.Parameters.Length > 0) {
                        membersContent.Append($"({method.Parameters.Length} param{(method.Parameters.Length == 1 ? "" : "s")})");
                    } else {
                        membersContent.Append("()");
                    }
                    if (method.MethodKind != MethodKind.Constructor && !method.ReturnsVoid) {
                        membersContent.Append($":{GetTypeShortName(method.ReturnType)}");
                    }
                    membersContent.Append(";");
                }
                if (detailLevel == DetailLevel.FiftyPercentMethodNames && methodsToShow.Count < methods.Count) {
                    membersContent.Append($"\n{indent}  and {methods.Count - methodsToShow.Count} more method{(methods.Count - methodsToShow.Count == 1 ? "" : "s")};");
                }
            } else { // NoMethodNames or higher compression
                membersContent.Append($"\n{indent}  {methods.Count} method{(methods.Count == 1 ? "" : "s")};");
            }
        }

        // Append the members content to the main StringBuilder
        if (membersContent.Length > 0) {
            sb.Append(membersContent);
            return true;
        }

        return false;
    }
}


```
Page 2/3FirstPrevNextLast