﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;

namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat
{
    internal sealed class SymbolMoniker(string scheme, string identifier)
    {
        public string Scheme { get; } = scheme;

        public string Identifier { get; } = identifier;

        public static bool HasMoniker(ISymbol symbol)
        {
            // We don't create monikers for symbols with type substitutions
            if (!symbol.OriginalDefinition.Equals(symbol))
                return false;

            // Don't create monikers for all local things that cannot escape outside of a single file: downstream consumers simply treat this as meaning a references/definition result
            // doesn't need to be stitched together across files or multiple projects or repositories.
            if (symbol.Kind is SymbolKind.Local or
                SymbolKind.RangeVariable or
                SymbolKind.Label or
                SymbolKind.Alias)
            {
                return false;
            }

            // Don't create monikers for built in-operators. We could pick some sort of moniker for these, but I doubt anybody really needs to search for all uses of
            // + in the world's projects at once.
            if (symbol is IMethodSymbol method && method.MethodKind == MethodKind.BuiltinOperator)
                return false;

            // TODO: some symbols for things some things in crefs don't have a ContainingAssembly. We'll skip those for now but do
            // want those to work.
            if (symbol.Kind != SymbolKind.Namespace && symbol.ContainingAssembly == null)
                return false;

            return true;
        }

        public static SymbolMoniker Create(ISymbol symbol)
        {
            // This uses the existing format that earlier prototypes of the Roslyn LSIF tool implemented; a different format may make more sense long term, but changing the
            // moniker makes it difficult for other systems that have older LSIF indexes to the connect the two indexes together.

            if (!HasMoniker(symbol))
                throw new InvalidOperationException($"'{symbol}' does not have a moniker.");

            // Namespaces are special: they're just a name that exists in the ether between compilations
            if (symbol.Kind == SymbolKind.Namespace)
            {
                return new SymbolMoniker(WellKnownSymbolMonikerSchemes.DotnetNamespace, symbol.ToDisplayString());
            }

            var symbolMoniker = symbol.ContainingAssembly.Name + "#";

            if (symbol.Kind == SymbolKind.Parameter)
            {
                symbolMoniker += GetRequiredDocumentationCommentId(symbol.ContainingSymbol) + "#" + symbol.Name;
            }
            else
            {
                symbolMoniker += GetRequiredDocumentationCommentId(symbol);
            }

            return new SymbolMoniker(WellKnownSymbolMonikerSchemes.DotnetXmlDoc, symbolMoniker);

            static string GetRequiredDocumentationCommentId(ISymbol symbol)
            {
                symbol = symbol.OriginalDefinition;
                var documentationCommentId = symbol.GetDocumentationCommentId();

                if (documentationCommentId == null)
                {
                    throw new Exception($"Unable to get documentation comment ID for {symbol.ToDisplayString()}");
                }

                return documentationCommentId;
            }
        }
    }
}
