﻿// 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;
using System.Collections.Immutable;
using System.Linq;
using System.Runtime.Serialization;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.NavigationBar
{
    internal abstract partial class RoslynNavigationBarItem
    {
        public sealed class SymbolItem(
            string name,
            string text,
            Glyph glyph,
            bool isObsolete,
            SymbolItemLocation location,
            ImmutableArray<RoslynNavigationBarItem> childItems = default,
            int indent = 0,
            bool bolded = false) : RoslynNavigationBarItem(
                  RoslynNavigationBarItemKind.Symbol,
                  text,
                  glyph,
                  bolded,
                  grayed: location.OtherDocumentInfo != null,
                  indent,
                  childItems), IEquatable<SymbolItem>
        {
            public readonly string Name = name;
            public readonly bool IsObsolete = isObsolete;

            public readonly SymbolItemLocation Location = location;

            protected internal override SerializableNavigationBarItem Dehydrate()
                => SerializableNavigationBarItem.SymbolItem(Text, Glyph, Name, IsObsolete, Location, SerializableNavigationBarItem.Dehydrate(ChildItems), Indent, Bolded, Grayed);

            public override bool Equals(object? obj)
                => Equals(obj as SymbolItem);

            public bool Equals(SymbolItem? other)
                => base.Equals(other) &&
                   Name == other.Name &&
                   IsObsolete == other.IsObsolete &&
                   Location.Equals(other.Location);

            public override int GetHashCode()
                => throw new NotImplementedException();
        }

        [DataContract]
        public readonly struct SymbolItemLocation : IEquatable<SymbolItemLocation>
        {
            /// <summary>
            /// The entity spans and navigation span in the originating document where this symbol was found.  Any time
            /// the caret is within any of the entity spans, the item should be appropriately 'selected' in whatever UI
            /// is displaying these.  The navigation span is the location in the starting document that should be
            /// navigated to when this item is selected If this symbol's location is in another document then this will
            /// be <see langword="null"/>.
            /// </summary>
            /// <remarks>Exactly one of <see cref="InDocumentInfo"/> and <see cref="OtherDocumentInfo"/> will be
            /// non-null.</remarks>
            [DataMember(Order = 0)]
            public readonly (ImmutableArray<TextSpan> spans, TextSpan navigationSpan)? InDocumentInfo;

            /// <summary>
            /// The document and navigation span this item should navigate to when the definition is not in the
            /// originating document. This is used for partial symbols where a child symbol is declared in another file,
            /// but should still be shown in the UI when in a part in a different file.
            /// </summary>
            /// <remarks>Exactly one of <see cref="InDocumentInfo"/> and <see cref="OtherDocumentInfo"/> will be
            /// non-null.</remarks>
            [DataMember(Order = 1)]
            public readonly (DocumentId documentId, TextSpan navigationSpan)? OtherDocumentInfo;

            public SymbolItemLocation(
                (ImmutableArray<TextSpan> spans, TextSpan navigationSpan)? inDocumentInfo,
                (DocumentId documentId, TextSpan navigationSpan)? otherDocumentInfo)
            {
                Contract.ThrowIfTrue(inDocumentInfo == null && otherDocumentInfo == null, "Both locations were null");
                Contract.ThrowIfTrue(inDocumentInfo != null && otherDocumentInfo != null, "Both locations were not null");

                if (inDocumentInfo != null)
                {
                    Contract.ThrowIfTrue(inDocumentInfo.Value.spans.IsEmpty, "If location is in document, it must have non-empty spans");
                }

                InDocumentInfo = inDocumentInfo;
                OtherDocumentInfo = otherDocumentInfo;
            }

            public override bool Equals(object? obj)
                => obj is SymbolItemLocation location && Equals(location);

            public bool Equals(SymbolItemLocation other)
            {
                if ((InDocumentInfo == null) != (other.InDocumentInfo == null))
                    return false;

                if ((OtherDocumentInfo == null) != (other.OtherDocumentInfo == null))
                    return false;

                if (InDocumentInfo != null)
                {
                    if (!this.InDocumentInfo.Value.spans.SequenceEqual(other.InDocumentInfo!.Value.spans) ||
                        this.InDocumentInfo.Value.navigationSpan != other.InDocumentInfo.Value.navigationSpan)
                    {
                        return false;
                    }
                }

                if (this.OtherDocumentInfo != null)
                {
                    if (this.OtherDocumentInfo.Value != other.OtherDocumentInfo!.Value)
                        return false;
                }

                return true;
            }

            public override int GetHashCode()
                => throw new NotImplementedException();
        }
    }
}
