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

namespace Microsoft.CodeAnalysis.Completion
{
    /// <summary>
    /// The set of completions to present to the user.
    /// </summary>
    public sealed class CompletionList
    {
        private readonly Lazy<ImmutableArray<CompletionItem>> _lazyItems;

        /// <summary>
        /// The completion items to present to the user.
        /// </summary>
        [Obsolete($"This property is obsolete. Use {nameof(ItemsList)} instead", error: false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public ImmutableArray<CompletionItem> Items => _lazyItems.Value;

        /// <summary>
        /// The completion items to present to the user.
        /// This property is preferred over `Items` because of the flexibility it provides. 
        /// For example, the list can be backed by types like SegmentedList to avoid LOH allocations.
        /// </summary>
        public IReadOnlyList<CompletionItem> ItemsList { get; }

        /// <summary>
        /// The span of the syntax element at the caret position when the <see cref="CompletionList"/> was created.
        /// Individual <see cref="CompletionItem"/> spans may vary.
        /// </summary>
        [Obsolete("Not used anymore.  CompletionList.Span is used instead.", error: true)]
        public TextSpan DefaultSpan { get; }

        /// <summary>
        /// The span of the syntax element at the caret position when the <see cref="CompletionList"/> 
        /// was created.
        /// 
        /// The span identifies the text in the document that is used to filter the initial list 
        /// presented to the user, and typically represents the region of the document that will 
        /// be changed if this item is committed.
        /// The latter is not always the case because each provider is free to make more complex changes 
        /// to the document. If this is the case, <see cref="CompletionItem.IsComplexTextEdit"/> must be
        /// set to <see langword="true"/>.
        /// </summary>
        public TextSpan Span { get; }

        /// <summary>
        /// The rules used to control behavior of the completion list shown to the user during typing.
        /// </summary>
        public CompletionRules Rules { get; }

        /// <summary>
        /// An optional <see cref="CompletionItem"/> that appears selected in the list presented to the user during suggestion mode.
        /// Suggestion mode disables auto-selection of items in the list, giving preference to the text typed by the user unless a specific item is selected manually.
        /// Specifying a <see cref="SuggestionModeItem"/> is a request that the completion host operate in suggestion mode.
        /// The item specified determines the text displayed and the description associated with it unless a different item is manually selected.
        /// No text is ever inserted when this item is completed, leaving the text the user typed instead.
        /// </summary>
        public CompletionItem? SuggestionModeItem { get; }

        /// <summary>
        /// Whether the items in this list should be the only items presented to the user.
        /// </summary>
        internal bool IsExclusive { get; }

        private CompletionList(
            TextSpan defaultSpan,
            IReadOnlyList<CompletionItem> itemsList,
            CompletionRules? rules,
            CompletionItem? suggestionModeItem,
            bool isExclusive)
        {
            Span = defaultSpan;
            ItemsList = itemsList;
            _lazyItems = new(() => ItemsList.ToImmutableArrayOrEmpty(), System.Threading.LazyThreadSafetyMode.PublicationOnly);

            Rules = rules ?? CompletionRules.Default;
            SuggestionModeItem = suggestionModeItem;
            IsExclusive = isExclusive;

            foreach (var item in ItemsList)
            {
                item.Span = defaultSpan;
            }
        }

        /// <summary>
        /// Creates a new <see cref="CompletionList"/> instance.
        /// </summary>
        /// <param name="defaultSpan">The span of the syntax element at the caret position when the <see cref="CompletionList"/> was created.</param>
        /// <param name="items">The completion items to present to the user.</param>
        /// <param name="rules">The rules used to control behavior of the completion list shown to the user during typing.</param>
        /// <param name="suggestionModeItem">An optional <see cref="CompletionItem"/> that appears selected in the list presented to the user during suggestion mode.</param>
        /// <returns></returns>
        public static CompletionList Create(
            TextSpan defaultSpan,
            ImmutableArray<CompletionItem> items,
            CompletionRules? rules = null,
            CompletionItem? suggestionModeItem = null)
        {
            return Create(defaultSpan, items, rules, suggestionModeItem, isExclusive: false);
        }

        internal static CompletionList Create(
            TextSpan defaultSpan,
            IReadOnlyList<CompletionItem> itemsList,
            CompletionRules? rules,
            CompletionItem? suggestionModeItem,
            bool isExclusive)
        {
            return new CompletionList(defaultSpan, itemsList, rules, suggestionModeItem, isExclusive);
        }

        private CompletionList With(
            Optional<TextSpan> span = default,
            Optional<IReadOnlyList<CompletionItem>> itemsList = default,
            Optional<CompletionRules> rules = default,
            Optional<CompletionItem> suggestionModeItem = default)
        {
            var newSpan = span.HasValue ? span.Value : Span;
            var newItemsList = itemsList.HasValue ? itemsList.Value : ItemsList;
            var newRules = rules.HasValue ? rules.Value : Rules;
            var newSuggestionModeItem = suggestionModeItem.HasValue ? suggestionModeItem.Value : SuggestionModeItem;

            if (newSpan == Span &&
                newItemsList == ItemsList &&
                newRules == Rules &&
                newSuggestionModeItem == SuggestionModeItem)
            {
                return this;
            }
            else
            {
                return Create(newSpan, newItemsList, newRules, newSuggestionModeItem, IsExclusive);
            }
        }

        /// <summary>
        /// Creates a copy of this <see cref="CompletionList"/> with the <see cref="DefaultSpan"/> property changed.
        /// </summary>
        [Obsolete("Not used anymore.  Use WithSpan instead.", error: true)]
        public CompletionList WithDefaultSpan(TextSpan span)
            => With(span: span);

        public CompletionList WithSpan(TextSpan span)
            => With(span: span);

#pragma warning disable RS0030 // Do not used banned APIs
        /// <summary>
        /// Creates a copy of this <see cref="CompletionList"/> with the <see cref="Items"/> property changed.
        /// </summary>
        public CompletionList WithItems(ImmutableArray<CompletionItem> items)
#pragma warning restore RS0030 // Do not used banned APIs
            => With(itemsList: items);

        /// <summary>
        /// Creates a copy of this <see cref="CompletionList"/> with the <see cref="ItemsList"/> property changed.
        /// </summary>
        internal CompletionList WithItemsList(IReadOnlyList<CompletionItem> itemsList)
            => With(itemsList: new(itemsList));

        /// <summary>
        /// Creates a copy of this <see cref="CompletionList"/> with the <see cref="Rules"/> property changed.
        /// </summary>
        public CompletionList WithRules(CompletionRules rules)
            => With(rules: rules);

        /// <summary>
        /// Creates a copy of this <see cref="CompletionList"/> with the <see cref="SuggestionModeItem"/> property changed.
        /// </summary>
        public CompletionList WithSuggestionModeItem(CompletionItem suggestionModeItem)
            => With(suggestionModeItem: suggestionModeItem);

        /// <summary>
        /// The default <see cref="CompletionList"/> returned when no items are found to populate the list.
        /// </summary>
        public static readonly CompletionList Empty = new(
            default, ImmutableArray<CompletionItem>.Empty, CompletionRules.Default,
            suggestionModeItem: null, isExclusive: false);

        internal bool IsEmpty => ItemsList.Count == 0 && SuggestionModeItem is null;
    }
}
