﻿// 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.IO;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.UnusedReferences;
using Microsoft.VisualStudio.Shell.TableManager;

namespace Microsoft.VisualStudio.LanguageServices.Implementation.UnusedReferences.Dialog
{
    internal partial class UnusedReferencesTableProvider
    {
        internal class UnusedReferencesDataSource : ITableDataSource
        {
            public const string Name = nameof(UnusedReferencesDataSource);

            public string SourceTypeIdentifier => Name;
            public string Identifier => Name;
            public string? DisplayName => null;

            private ImmutableList<SinkManager> _managers = ImmutableList<SinkManager>.Empty;
            private ImmutableArray<UnusedReferencesEntry> _currentEntries = ImmutableArray<UnusedReferencesEntry>.Empty;

            public IDisposable Subscribe(ITableDataSink sink)
            {
                return new SinkManager(this, sink);
            }

            public void AddTableData(Solution solution, string projectFilePath, ImmutableArray<ReferenceUpdate> referenceUpdates)
            {
                var solutionName = Path.GetFileName(solution.FilePath);
                var project = solution.Projects.First(project => projectFilePath.Equals(project.FilePath, StringComparison.OrdinalIgnoreCase));
                var entries = referenceUpdates
                    .Select(update => new UnusedReferencesEntry(solutionName, project.Name, project.Language, update))
                    .ToImmutableArray();

                foreach (var manager in _managers)
                {
                    manager.Sink.AddEntries(entries);
                }

                _currentEntries = _currentEntries.AddRange(entries);
            }

            public void RemoveAllTableData()
            {
                foreach (var manager in _managers)
                {
                    manager.Sink.RemoveAllEntries();
                }

                _currentEntries = ImmutableArray<UnusedReferencesEntry>.Empty;
            }

            internal void AddSinkManager(SinkManager manager)
            {
                _managers = _managers.Add(manager);

                manager.Sink.AddEntries(_currentEntries);
            }

            internal void RemoveSinkManager(SinkManager manager)
            {
                _managers = _managers.Remove(manager);
            }

            internal sealed class SinkManager : IDisposable
            {
                internal readonly UnusedReferencesDataSource UnusedReferencesDataSource;
                internal readonly ITableDataSink Sink;

                internal SinkManager(UnusedReferencesDataSource unusedReferencesDataSource, ITableDataSink sink)
                {
                    UnusedReferencesDataSource = unusedReferencesDataSource;
                    Sink = sink;

                    UnusedReferencesDataSource.AddSinkManager(this);
                }

                public void Dispose()
                {
                    UnusedReferencesDataSource.RemoveSinkManager(this);
                }
            }

            internal class UnusedReferencesEntry : ITableEntry
            {
                public string SolutionName { get; }
                public string ProjectName { get; }
                public string Language { get; }
                public ReferenceUpdate ReferenceUpdate { get; }

                public object Identity => ReferenceUpdate;

                public UnusedReferencesEntry(string solutionName, string projectName, string language, ReferenceUpdate referenceUpdate)
                {
                    SolutionName = solutionName;
                    ProjectName = projectName;
                    Language = language;
                    ReferenceUpdate = referenceUpdate;
                }

                public bool TryGetValue(string keyName, out object? content)
                {
                    content = null;

                    switch (keyName)
                    {
                        case UnusedReferencesTableKeyNames.SolutionName:
                            content = SolutionName;
                            break;
                        case UnusedReferencesTableKeyNames.ProjectName:
                            content = ProjectName;
                            break;
                        case UnusedReferencesTableKeyNames.Language:
                            content = Language;
                            break;
                        case UnusedReferencesTableKeyNames.ReferenceType:
                            content = ReferenceUpdate.ReferenceInfo.ReferenceType;
                            break;
                        case UnusedReferencesTableKeyNames.ReferenceName:
                            // For Project and Assembly references, use the file name instead of overwhelming the user with the full path.
                            content = ReferenceUpdate.ReferenceInfo.ReferenceType != ReferenceType.Package
                                ? Path.GetFileName(ReferenceUpdate.ReferenceInfo.ItemSpecification)
                                : ReferenceUpdate.ReferenceInfo.ItemSpecification;
                            break;
                        case UnusedReferencesTableKeyNames.UpdateAction:
                            content = ReferenceUpdate.Action;
                            break;
                    }

                    return content != null;
                }

                public bool CanSetValue(string keyName)
                {
                    return keyName == UnusedReferencesTableKeyNames.UpdateAction;
                }

                public bool TrySetValue(string keyName, object content)
                {
                    if (keyName != UnusedReferencesTableKeyNames.UpdateAction || content is not UpdateAction action)
                    {
                        return false;
                    }

                    ReferenceUpdate.Action = action;
                    return true;
                }
            }
        }
    }
}
